Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion deps/k_release
Original file line number Diff line number Diff line change
@@ -1 +1 @@
7.1.319
7.1.337
2 changes: 1 addition & 1 deletion docs/node-semantics.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ Step decoding pattern-matches on the `JSON` sort. Key order in the step objects
#decodeStep({ "op": "callTx", ... })→ callTx(...)
```

SCVal arguments are decoded by `#decodeArg`, which matches on `"type"` and produces a K `ScVal` constructor (`SCBool`, `I32`, `U32`, `I64`, `U64`, `I128`, `U128`, `Symbol`, `ScBytes`, `ScAddress`).
SCVal arguments are decoded by `#decodeArg`, which matches on `"type"` and produces a K `ScVal` constructor (`Void`, `SCBool`, `I32`, `U32`, `I64`, `U64`, `I128`, `U128`, `I256`, `U256`, `Symbol`, `ScString`, `ScBytes`, `ScAddress`, `ScVec`, `ScMap`) — the same set `scval_to_json` encodes, so the two stay in step.

The `steps-done` rule (mirroring KASMER's `steps-empty` but with a `...` frame) consumes the final `.Steps` so the `#finalizeTx` continuation can proceed.

Expand Down
30 changes: 15 additions & 15 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
# `k` package directly (replacing the imperative `kup install k`); its
# nixpkgs is intentionally NOT followed, so the k-framework binary caches
# are hit instead of rebuilding K against our nixpkgs.
k-framework.url = "github:runtimeverification/k/v7.1.323";
k-framework.url = "github:runtimeverification/k/v7.1.337";
# Use the same uv2nix as k-framework so we inherit the pyproject-nix version
# that fixes the missing 'riscv64' attribute in pep600.nix (pep599.manyLinuxTargetMachines
# lookup now uses `or tagArch` as a safe default for unknown architectures).
Expand Down
2 changes: 1 addition & 1 deletion package/version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.0
0.1.1
6 changes: 3 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ build-backend = "hatchling.build"

[project]
name = "komet-node"
version = "0.1.0"
version = "0.1.1"
description = "Local development testnet for Stellar based on K semantics"
readme = "README.md"
requires-python = "~=3.10"
dependencies = [
"stellar-sdk>=13.2.1",
"komet@git+https://github.com/runtimeverification/komet.git@v0.1.88",
"kframework>=7.1.323,<7.1.324",
"komet@git+https://github.com/runtimeverification/komet.git@v0.1.89",
"kframework>=7.1.337,<7.1.338",
]

[[project.authors]]
Expand Down
4 changes: 4 additions & 0 deletions src/komet_node/kdist/node.md
Original file line number Diff line number Diff line change
Expand Up @@ -1262,14 +1262,18 @@ SCVal arg encoding (key order also significant):
rule #decodeArgList(.JSONs) => .List
rule #decodeArgList(A:JSON, AS:JSONs) => ListItem(#decodeArg(A)) #decodeArgList(AS)

rule #decodeArg({ "type" : "void" }) => Void
rule #decodeArg({ "type" : "bool" , "value" : V:Bool }) => SCBool(V)
rule #decodeArg({ "type" : "i32" , "value" : V:Int }) => I32(V)
rule #decodeArg({ "type" : "u32" , "value" : V:Int }) => U32(V)
rule #decodeArg({ "type" : "i64" , "value" : V:Int }) => I64(V)
rule #decodeArg({ "type" : "u64" , "value" : V:Int }) => U64(V)
rule #decodeArg({ "type" : "i128" , "value" : V:Int }) => I128(V)
rule #decodeArg({ "type" : "u128" , "value" : V:Int }) => U128(V)
rule #decodeArg({ "type" : "i256" , "value" : V:Int }) => I256(V)
rule #decodeArg({ "type" : "u256" , "value" : V:Int }) => U256(V)
rule #decodeArg({ "type" : "symbol" , "value" : V:String }) => Symbol(V)
rule #decodeArg({ "type" : "string" , "value" : V:String }) => ScString(V)
rule #decodeArg({ "type" : "bytes" , "value" : V:String }) => ScBytes(HexBytes(V))
rule #decodeArg({ "type" : "address" , "addrType" : "account" , "value" : V:String }) => ScAddress(Account(HexBytes(V)))
rule #decodeArg({ "type" : "address" , "addrType" : "contract" , "value" : V:String }) => ScAddress(Contract(HexBytes(V)))
Expand Down
34 changes: 31 additions & 3 deletions src/komet_node/scval.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ def scval_to_json(scval: SCVal) -> dict:
produced with keys in the same order as the ``#decodeArg`` rules in ``node.md``.
"""
match scval.type:
case SCValType.SCV_VOID:
return {'type': 'void'}
case SCValType.SCV_BOOL:
assert scval.b is not None
return {'type': 'bool', 'value': scval.b}
Expand All @@ -42,9 +44,25 @@ def scval_to_json(scval: SCVal) -> dict:
assert scval.u128 is not None
val = (scval.u128.hi.uint64 << 64) | scval.u128.lo.uint64
return {'type': 'u128', 'value': val}
case SCValType.SCV_U256:
assert scval.u256 is not None
u = scval.u256
val = (u.hi_hi.uint64 << 192) | (u.hi_lo.uint64 << 128) | (u.lo_hi.uint64 << 64) | u.lo_lo.uint64
return {'type': 'u256', 'value': val}
case SCValType.SCV_I256:
assert scval.i256 is not None
i = scval.i256
# Only the top word is signed; the lower words are its two's complement, and
# OR-ing them onto the shifted signed word reproduces the value (inverse of
# the masking scval_from_json does).
val = (i.hi_hi.int64 << 192) | (i.hi_lo.uint64 << 128) | (i.lo_hi.uint64 << 64) | i.lo_lo.uint64
return {'type': 'i256', 'value': val}
case SCValType.SCV_SYMBOL:
assert scval.sym is not None
return {'type': 'symbol', 'value': scval.sym.sc_symbol.decode()}
case SCValType.SCV_STRING:
assert scval.str is not None
return {'type': 'string', 'value': scval.str.sc_string.decode()}
case SCValType.SCV_BYTES:
assert scval.bytes is not None
return {'type': 'bytes', 'value': scval.bytes.sc_bytes.hex()}
Expand Down Expand Up @@ -79,9 +97,8 @@ def scval_to_json(scval: SCVal) -> dict:
def scval_from_json(value: dict) -> SCVal:
"""Decode the JSON ScVal encoding emitted by the semantics back into an XDR SCVal.

Inverse of :func:`scval_to_json`, extended with the value-only types the semantics can
hold in contract storage or return from a contract call but that never appear as call
arguments (``void``, ``string``, ``u256``, ``vec``, ``map``). Covers all three K-side
Inverse of :func:`scval_to_json`, which now covers every type in both directions, so
this decoder and that encoder handle the same set. Covers all three K-side
encoders (``#scVal2JSON``, ``#scValJSON``, ``#scValToJSON`` in ``node.md``); the ``map``
case accepts both entry shapes they emit — ``{"key": ..., "val": ...}`` objects and
``[key, val]`` pairs — so keep the encoders and this decoder in sync. Raises
Expand Down Expand Up @@ -120,6 +137,17 @@ def scval_from_json(value: dict) -> SCVal:
lo_lo=stellar_xdr.Uint64(val & _UINT64_MASK),
)
return stellar_xdr.SCVal(type=SCValType.SCV_U256, u256=parts256)
case 'i256':
# Only the top word is signed, so a negative value's remaining words are
# its two's complement -- which is what masking a negative Python int gives.
val = value['value']
parts256i = stellar_xdr.Int256Parts(
hi_hi=stellar_xdr.Int64(val >> 192),
hi_lo=stellar_xdr.Uint64((val >> 128) & _UINT64_MASK),
lo_hi=stellar_xdr.Uint64((val >> 64) & _UINT64_MASK),
lo_lo=stellar_xdr.Uint64(val & _UINT64_MASK),
)
return stellar_xdr.SCVal(type=SCValType.SCV_I256, i256=parts256i)
case 'symbol':
return stellar_xdr.SCVal(type=SCValType.SCV_SYMBOL, sym=stellar_xdr.SCSymbol(value['value'].encode()))
case 'string':
Expand Down
12 changes: 12 additions & 0 deletions src/tests/integration/data/wasm/args.wat
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@
(func $test_map (type 1) (param i64) (result i64)
i64.const 2)

;; test_string / test_void: accept 1 arg (a String object handle, resp. the small
;; Void value), return Void. test_wide256: accept u256 and i256 handles.
(func $test_string (type 1) (param i64) (result i64)
i64.const 2)
(func $test_void (type 1) (param i64) (result i64)
i64.const 2)
(func $test_wide256 (type 3) (param i64 i64) (result i64)
i64.const 2)

(memory (;0;) 16)
(global (;0;) (mut i32) (i32.const 1048576))
(global (;1;) i32 (i32.const 1048576))
Expand All @@ -45,6 +54,9 @@
(export "_" (func 4))
(export "test_vec" (func $test_vec))
(export "test_map" (func $test_map))
(export "test_string" (func $test_string))
(export "test_void" (func $test_void))
(export "test_wide256" (func $test_wide256))
(export "__data_end" (global 1))
(export "__heap_base" (global 2))
)
31 changes: 30 additions & 1 deletion src/tests/integration/test_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,8 @@ def test_call_tx_with_args(server: StellarRpcServer) -> None:
Uses a minimal contract (args.wat) whose functions accept various arg types and return
Void. For each call the arguments echoed in the trace's ``callContract`` frame must
round-trip back to the exact SCVals that were sent — so a decoding bug is caught even
when the transaction still succeeds. Covers: bool, u32, i32, u64, i64, u128, i128, symbol.
when the transaction still succeeds. Covers: bool, u32, i32, u64, i64, u128, i128, symbol,
string, void, u256, i256. (Composite args have their own test below.)
"""
invoke = deploy_and_get_invoker(server, ARGS_CONTRACT_WAT)

Expand Down Expand Up @@ -733,6 +734,34 @@ def assert_args_round_trip(func: str, args: list[xdr.SCVal]) -> None:
],
)
assert_args_round_trip('test_symbol', [xdr.SCVal(type=SCValType.SCV_SYMBOL, sym=xdr.SCSymbol(sc_symbol=b'hello'))])
assert_args_round_trip(
'test_string', [xdr.SCVal(type=SCValType.SCV_STRING, str=xdr.SCString(sc_string=b'Soroban'))]
)
assert_args_round_trip('test_void', [xdr.SCVal(type=SCValType.SCV_VOID)])

def u256(value: int) -> xdr.SCVal:
mask = (1 << 64) - 1
parts = xdr.UInt256Parts(
hi_hi=xdr.Uint64(value >> 192),
hi_lo=xdr.Uint64((value >> 128) & mask),
lo_hi=xdr.Uint64((value >> 64) & mask),
lo_lo=xdr.Uint64(value & mask),
)
return xdr.SCVal(type=SCValType.SCV_U256, u256=parts)

def i256(value: int) -> xdr.SCVal:
# As for i128: only the top word is signed, so a negative value's lower words
# are its two's complement.
mask = (1 << 64) - 1
parts = xdr.Int256Parts(
hi_hi=xdr.Int64(value >> 192),
hi_lo=xdr.Uint64((value >> 128) & mask),
lo_hi=xdr.Uint64((value >> 64) & mask),
lo_lo=xdr.Uint64(value & mask),
)
return xdr.SCVal(type=SCValType.SCV_I256, i256=parts)

assert_args_round_trip('test_wide256', [u256(2**200 + 33), i256(-(2**200) - 33)])


def test_call_tx_with_composite_args(server: StellarRpcServer) -> None:
Expand Down
Loading
Loading