Skip to content

Commit 8f392c5

Browse files
Merge pull request #30 from runtimeverification/feat/tx-return-values
Run transactions with non-Void return values
2 parents 37c3ffb + 0a51010 commit 8f392c5

3 files changed

Lines changed: 105 additions & 2 deletions

File tree

src/komet_node/kdist/node.md

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -393,10 +393,10 @@ SCVal arg encoding (key order also significant):
393393
=> deployContract(Account(HexBytes(FROM)), Contract(HexBytes(ADDR)), HexBytes(HASH))
394394
395395
rule #decodeStep({ "op" : "callTx" , "from" : FROM:String , "fromIsContract" : false , "func" : FUNC:String , "to" : TO:String , "args" : [ARGS:JSONs] })
396-
=> callTx(Account(HexBytes(FROM)), Contract(HexBytes(TO)), string2WasmToken("\"" +String FUNC +String "\""), #decodeArgList(ARGS), Void)
396+
=> uncheckedCallTx(Account(HexBytes(FROM)), Contract(HexBytes(TO)), string2WasmToken("\"" +String FUNC +String "\""), #decodeArgList(ARGS))
397397
398398
rule #decodeStep({ "op" : "callTx" , "from" : FROM:String , "fromIsContract" : true , "func" : FUNC:String , "to" : TO:String , "args" : [ARGS:JSONs] })
399-
=> callTx(Contract(HexBytes(FROM)), Contract(HexBytes(TO)), string2WasmToken("\"" +String FUNC +String "\""), #decodeArgList(ARGS), Void)
399+
=> uncheckedCallTx(Contract(HexBytes(FROM)), Contract(HexBytes(TO)), string2WasmToken("\"" +String FUNC +String "\""), #decodeArgList(ARGS))
400400
401401
syntax List ::= #decodeArgList(JSONs) [function]
402402
syntax ScVal ::= #decodeArg(JSON) [function]
@@ -415,6 +415,23 @@ SCVal arg encoding (key order also significant):
415415
rule #decodeArg({ "type" : "bytes" , "value" : V:String }) => ScBytes(HexBytes(V))
416416
rule #decodeArg({ "type" : "address" , "addrType" : "account" , "value" : V:String }) => ScAddress(Account(HexBytes(V)))
417417
rule #decodeArg({ "type" : "address" , "addrType" : "contract" , "value" : V:String }) => ScAddress(Contract(HexBytes(V)))
418+
```
419+
420+
`uncheckedCallTx` is like komet's `callTx` but it does not entail a return value check.
421+
422+
423+
```k
424+
syntax Step ::= uncheckedCallTx( from: Address, to: Address, func: WasmString, args: List) [symbol(uncheckedCallTx)]
425+
426+
rule [uncheckedCallTx]:
427+
<k> uncheckedCallTx(FROM, TO, FUNC, ARGS)
428+
=> allocObjects(ARGS)
429+
~> callContractFromStack(FROM, TO, FUNC)
430+
~> #resetHost
431+
...
432+
</k>
433+
// clear the host cell before contract calls
434+
(_:HostCell => <host> <hostStack> .HostStack </hostStack> ... </host>)
418435
419436
endmodule
420437
```
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
(module
2+
(type (;0;) (func))
3+
(type (;1;) (func (param i64 i64) (result i64)))
4+
5+
;; add: accept two u32 args, return their sum as u32 (a non-Void value)
6+
;; u32 HostVals carry the payload in the high 32 bits and tag 4 in the low bits
7+
(func (;0;) (type 1) (param i64 i64) (result i64)
8+
local.get 0
9+
i64.const 32
10+
i64.shr_u
11+
local.get 1
12+
i64.const 32
13+
i64.shr_u
14+
i64.add
15+
i64.const 32
16+
i64.shl
17+
i64.const 4
18+
i64.or)
19+
20+
;; _ (Soroban ABI stub)
21+
(func (;1;) (type 0))
22+
23+
(memory (;0;) 16)
24+
(global (;0;) (mut i32) (i32.const 1048576))
25+
(global (;1;) i32 (i32.const 1048576))
26+
(global (;2;) i32 (i32.const 1048576))
27+
28+
(export "memory" (memory 0))
29+
(export "add" (func 0))
30+
(export "_" (func 1))
31+
(export "__data_end" (global 1))
32+
(export "__heap_base" (global 2))
33+
)

src/tests/integration/test_server.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
EMPTY_CONTRACT_WAT = (Path(__file__).parent / 'data' / 'wasm' / 'empty.wat').resolve(strict=True)
2020
ARGS_CONTRACT_WAT = (Path(__file__).parent / 'data' / 'wasm' / 'args.wat').resolve(strict=True)
21+
ADDER_CONTRACT_WAT = (Path(__file__).parent / 'data' / 'wasm' / 'adder.wat').resolve(strict=True)
2122

2223

2324
def wat_to_wasm(wat_path: Path) -> bytes:
@@ -445,3 +446,55 @@ def invoke(func: str, args: list[xdr.SCVal]) -> None:
445446
],
446447
)
447448
invoke('test_symbol', [xdr.SCVal(type=SCValType.SCV_SYMBOL, sym=xdr.SCSymbol(sc_symbol=b'hello'))])
449+
450+
451+
def test_call_tx_with_return_value(server: StellarRpcServer) -> None:
452+
"""A contract invocation that returns a non-Void value succeeds.
453+
454+
Regression test: transactions used to be decoded into ``callTx(..., Void)``, which
455+
asserts the call returns Void. Invoking ``add(2, 3)`` (returning U32(5)) therefore got
456+
stuck in the semantics and was recorded as FAILED. ``uncheckedCallTx`` drops the return
457+
value check.
458+
"""
459+
keypair = Keypair.random()
460+
account = Account(keypair.public_key, sequence=0)
461+
462+
def send(tb: TransactionBuilder) -> None:
463+
env = tb.set_timeout(30).build()
464+
env.sign(keypair)
465+
res = _rpc(server.port(), 'sendTransaction', {'transaction': env.to_xdr()})
466+
assert res['result']['status'] == 'PENDING'
467+
tx_hash = res['result']['hash']
468+
get_res = _rpc(server.port(), 'getTransaction', {'hash': tx_hash})['result']
469+
assert get_res['status'] == 'SUCCESS', f'Transaction failed: {get_res}'
470+
471+
def builder() -> TransactionBuilder:
472+
return TransactionBuilder(account, Network.TESTNET_NETWORK_PASSPHRASE)
473+
474+
# Set up: create account, upload adder.wat, deploy contract
475+
send(builder().append_create_account_op(keypair.public_key, '1000'))
476+
477+
wasm_bytecode = wat_to_wasm(ADDER_CONTRACT_WAT)
478+
send(builder().append_upload_contract_wasm_op(wasm_bytecode))
479+
480+
from stellar_sdk.utils import sha256
481+
482+
wasm_hash = sha256(wasm_bytecode)
483+
salt = b'\x00' * 32
484+
send(builder().append_create_contract_op(wasm_hash, keypair.public_key, None, salt))
485+
486+
# add(2, 3) returns U32(5), not Void — the send() helper asserts SUCCESS.
487+
contract_address = server.encoder.contract_address_from_deployer_address(keypair.public_key, salt)
488+
send(
489+
builder().append_invoke_contract_function_op(
490+
contract_address,
491+
'add',
492+
[
493+
xdr.SCVal(type=SCValType.SCV_U32, u32=xdr.Uint32(2)),
494+
xdr.SCVal(type=SCValType.SCV_U32, u32=xdr.Uint32(3)),
495+
],
496+
)
497+
)
498+
499+
# All four transactions, including the non-Void invocation, advanced the ledger.
500+
assert _rpc(server.port(), 'getLatestLedger', {})['result']['sequence'] == 4

0 commit comments

Comments
 (0)