From 9ef6c08072bf4a1d2ffd151ad4c3728261730f60 Mon Sep 17 00:00:00 2001 From: Mark Spanbroek Date: Thu, 9 Jun 2022 15:30:34 +0200 Subject: [PATCH] Handle multiple return types on a contract call --- ethers/contract.nim | 29 +++++++++++----- testmodule/test.nim | 1 + testmodule/testReturns.nim | 53 ++++++++++++++++++++++++++++++ testnode/contracts/TestReturns.sol | 45 +++++++++++++++++++++++++ testnode/deploy/testreturns.js | 6 ++++ 5 files changed, 125 insertions(+), 9 deletions(-) create mode 100644 testmodule/testReturns.nim create mode 100644 testnode/contracts/TestReturns.sol create mode 100644 testnode/deploy/testreturns.js diff --git a/ethers/contract.nim b/ethers/contract.nim index 036341c..b36d7eb 100644 --- a/ethers/contract.nim +++ b/ethers/contract.nim @@ -52,10 +52,13 @@ proc createTransaction(contract: Contract, let data = @selector & AbiEncoder.encode(parameters) Transaction(to: contract.address, data: data) -proc decodeResponse(T: type, bytes: seq[byte]): T = - without decoded =? AbiDecoder.decode(bytes, (T,)): - raiseContractError "unable to decode return value as " & $T - return decoded[0] +proc decodeResponse(T: type, multiple: static bool, bytes: seq[byte]): T = + when multiple: + without decoded =? AbiDecoder.decode(bytes, T): + raiseContractError "unable to decode return value as " & $T + return decoded + else: + return decodeResponse((T,), true, bytes)[0] proc call(contract: Contract, function: string, @@ -68,10 +71,11 @@ proc call(contract: Contract, function: string, parameters: tuple, ReturnType: type, + returnMultiple: static bool, blockTag = BlockTag.latest): Future[ReturnType] {.async.} = let transaction = createTransaction(contract, function, parameters) let response = await contract.provider.call(transaction, blockTag) - return decodeResponse(ReturnType, response) + return decodeResponse(ReturnType, returnMultiple, response) proc send(contract: Contract, function: string, parameters: tuple): Future[?TransactionResponse] {.async.} = @@ -102,22 +106,29 @@ func isConstant(procedure: NimNode): bool = return true false +func isMultipleReturn(returnType: NimNode): bool = + (returnType.kind == nnkPar and returnType.len > 1) or + (returnType.kind == nnkTupleConstr) or + (returnType.kind == nnkTupleTy) + func addContractCall(procedure: var NimNode) = let contract = procedure[3][1][0] let function = $basename(procedure[0]) let parameters = getParameterTuple(procedure) - let returntype = procedure[3][0] + let returnType = procedure[3][0] + let returnMultiple = returnType.isMultipleReturn.newLit func call: NimNode = - if returntype.kind == nnkEmpty: + if returnType.kind == nnkEmpty: quote: await call(`contract`, `function`, `parameters`) else: quote: - return await call(`contract`, `function`, `parameters`, `returntype`) + return await call( + `contract`, `function`, `parameters`, `returnType`, `returnMultiple`) func send: NimNode = - if returntype.kind == nnkEmpty: + if returnType.kind == nnkEmpty: quote: discard await send(`contract`, `function`, `parameters`) else: diff --git a/testmodule/test.nim b/testmodule/test.nim index b374b52..faeaf18 100644 --- a/testmodule/test.nim +++ b/testmodule/test.nim @@ -1,6 +1,7 @@ import ./testJsonRpcProvider import ./testJsonRpcSigner import ./testContracts +import ./testReturns import ./testEvents {.warning[UnusedImport]:off.} diff --git a/testmodule/testReturns.nim b/testmodule/testReturns.nim new file mode 100644 index 0000000..d76fb9c --- /dev/null +++ b/testmodule/testReturns.nim @@ -0,0 +1,53 @@ +import pkg/asynctest +import pkg/ethers +import ./hardhat + +type + TestReturns = ref object of Contract + Static = (UInt256, UInt256) + Dynamic = (string, UInt256) + +suite "Contract return values": + + var contract: TestReturns + var provider: JsonRpcProvider + var snapshot: JsonNode + + setup: + provider = JsonRpcProvider.new("ws://localhost:8545") + snapshot = await provider.send("evm_snapshot") + let deployment = readDeployment() + contract = TestReturns.new(!deployment.address(TestReturns), provider) + + teardown: + discard await provider.send("evm_revert", @[snapshot]) + + test "handles static size structs": + proc getStatic(contract: TestReturns): Static {.contract, pure.} + proc getStatics(contract: TestReturns): (Static, Static) {.contract, pure.} + check (await contract.getStatic()) == (1.u256, 2.u256) + check (await contract.getStatics()) == ((1.u256, 2.u256), (3.u256, 4.u256)) + + test "handles dynamic size structs": + proc getDynamic(contract: TestReturns): Dynamic {.contract, pure.} + proc getDynamics(contract: TestReturns): (Dynamic, Dynamic) {.contract, pure.} + check (await contract.getDynamic()) == ("1", 2.u256) + check (await contract.getDynamics()) == (("1", 2.u256), ("3", 4.u256)) + + test "handles mixed dynamic and static size structs": + proc getDynamicAndStatic(contract: TestReturns): (Dynamic, Static) {.contract, pure.} + check (await contract.getDynamicAndStatic()) == (("1", 2.u256), (3.u256, 4.u256)) + + test "handles return type that is a tuple with a single element": + proc getDynamic(contract: TestReturns): (Dynamic,) {.contract, pure.} + check (await contract.getDynamic()) == (("1", 2.u256),) + + test "handles parentheses around return type": + proc getDynamic(contract: TestReturns): (Dynamic) {.contract, pure.} + check (await contract.getDynamic()) == ("1", 2.u256) + + test "handles return type that is an explicit tuple": + proc getDynamics(contract: TestReturns): tuple[a, b: Dynamic] {.contract, pure.} + let values = await contract.getDynamics() + check values.a == ("1", 2.u256) + check values.b == ("3", 4.u256) diff --git a/testnode/contracts/TestReturns.sol b/testnode/contracts/TestReturns.sol new file mode 100644 index 0000000..bf36853 --- /dev/null +++ b/testnode/contracts/TestReturns.sol @@ -0,0 +1,45 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +contract TestReturns { + struct StaticStruct { + uint256 a; + uint256 b; + } + struct DynamicStruct { + string a; + uint256 b; + } + + function getStatic() external pure returns (StaticStruct memory) { + return StaticStruct(1, 2); + } + + function getDynamic() external pure returns (DynamicStruct memory) { + return DynamicStruct("1", 2); + } + + function getStatics() + external + pure + returns (StaticStruct memory, StaticStruct memory) + { + return (StaticStruct(1, 2), StaticStruct(3, 4)); + } + + function getDynamics() + external + pure + returns (DynamicStruct memory, DynamicStruct memory) + { + return (DynamicStruct("1", 2), DynamicStruct("3", 4)); + } + + function getDynamicAndStatic() + external + pure + returns (DynamicStruct memory, StaticStruct memory) + { + return (DynamicStruct("1", 2), StaticStruct(3, 4)); + } +} diff --git a/testnode/deploy/testreturns.js b/testnode/deploy/testreturns.js new file mode 100644 index 0000000..5e0970a --- /dev/null +++ b/testnode/deploy/testreturns.js @@ -0,0 +1,6 @@ +module.exports = async ({ deployments, getNamedAccounts }) => { + const { deployer } = await getNamedAccounts(); + await deployments.deploy("TestReturns", { from: deployer }); +}; + +module.exports.tags = ["TestReturns"];