Handle multiple return types on a contract call

This commit is contained in:
Mark Spanbroek 2022-06-09 15:30:34 +02:00 committed by markspanbroek
parent 78115cdd4b
commit 9ef6c08072
5 changed files with 125 additions and 9 deletions

View File

@ -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:

View File

@ -1,6 +1,7 @@
import ./testJsonRpcProvider
import ./testJsonRpcSigner
import ./testContracts
import ./testReturns
import ./testEvents
{.warning[UnusedImport]:off.}

View File

@ -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)

View File

@ -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));
}
}

View File

@ -0,0 +1,6 @@
module.exports = async ({ deployments, getNamedAccounts }) => {
const { deployer } = await getNamedAccounts();
await deployments.deploy("TestReturns", { from: deployer });
};
module.exports.tags = ["TestReturns"];