diff --git a/packages/core/src/internal/execution/abi.ts b/packages/core/src/internal/execution/abi.ts index 1b03f75ca..15d3760cb 100644 --- a/packages/core/src/internal/execution/abi.ts +++ b/packages/core/src/internal/execution/abi.ts @@ -278,7 +278,7 @@ export function getEventArgumentFromReceipt( emitterAddress: string, eventName: string, eventIndex: number, - argument: string | number + nameOrIndex: string | number ): EvmValue { const emitterLogs = receipt.logs.filter((l) => l.address === emitterAddress); @@ -300,11 +300,11 @@ export function getEventArgumentFromReceipt( const evmTuple = ethersResultIntoEvmTuple(ethersResult, eventFragment.inputs); - if (typeof argument === "string") { - return evmTuple.named[argument]; + if (typeof nameOrIndex === "string") { + return evmTuple.named[nameOrIndex]; } - return evmTuple.positional[argument]; + return evmTuple.positional[nameOrIndex]; } /** diff --git a/packages/core/src/internal/execution/execution-strategy-helpers.ts b/packages/core/src/internal/execution/execution-strategy-helpers.ts index 92b7d6917..05326aaec 100644 --- a/packages/core/src/internal/execution/execution-strategy-helpers.ts +++ b/packages/core/src/internal/execution/execution-strategy-helpers.ts @@ -31,7 +31,6 @@ import { StaticCallResponse, SuccessfulTransaction, } from "./types/execution-strategy"; -import { convertEvmTupleToSolidityParam } from "./utils/convert-evm-tuple-to-solidity-param"; /** * Returns true if the given response is an onchain interaction response. @@ -205,12 +204,16 @@ export async function* executeStaticCallRequest( * @returns The value that should be used as the result of the static call execution state. */ export function getStaticCallExecutionStateResultValue( - _exState: StaticCallExecutionState, + exState: StaticCallExecutionState, lastStaticCallResult: SuccessfulEvmExecutionResult ): SolidityParameterType { - const values = convertEvmTupleToSolidityParam(lastStaticCallResult.values); - // TODO: We should have a way to handle other results. - return values[0]; + return typeof exState.nameOrIndex === "string" + ? (lastStaticCallResult.values.named[ + exState.nameOrIndex + ] as SolidityParameterType) + : (lastStaticCallResult.values.positional[ + exState.nameOrIndex + ] as SolidityParameterType); } export * from "./abi"; diff --git a/packages/core/src/internal/execution/future-processor/helpers/messages-helpers.ts b/packages/core/src/internal/execution/future-processor/helpers/messages-helpers.ts index 741b0d7eb..8a922d7be 100644 --- a/packages/core/src/internal/execution/future-processor/helpers/messages-helpers.ts +++ b/packages/core/src/internal/execution/future-processor/helpers/messages-helpers.ts @@ -3,7 +3,6 @@ import { CallExecutionResult, SendDataExecutionResult, StaticCallExecutionResult, - ExecutionResultType, } from "../../types/execution-result"; import { DeploymentExecutionState, @@ -47,19 +46,10 @@ export function createExecutionStateCompleteMessage( | SendDataExecutionStateCompleteMessage | StaticCallExecutionStateCompleteMessage { if (exState.type === ExecutionSateType.STATIC_CALL_EXECUTION_STATE) { - const newResult = result as StaticCallExecutionResult; - if (newResult.type === ExecutionResultType.SUCCESS) { - newResult.value = Array.isArray(newResult.value) - ? newResult.value[exState.nameOrIndex as number] - : typeof newResult.value === "object" - ? newResult.value[exState.nameOrIndex] - : newResult.value; - } - return { type: JournalMessageType.STATIC_CALL_EXECUTION_STATE_COMPLETE, futureId: exState.id, - result: newResult, + result: result as StaticCallExecutionResult, }; } diff --git a/packages/core/test/readEventArgument.ts b/packages/core/test/readEventArgument.ts index 14a6a4756..3241171dd 100644 --- a/packages/core/test/readEventArgument.ts +++ b/packages/core/test/readEventArgument.ts @@ -90,7 +90,7 @@ describe("Read event argument", () => { const call = m.call(contract, "fuc"); m.readEventArgument(contract, "EventName1", "arg1"); - m.readEventArgument(call, "EventName2", "arg2"); + m.readEventArgument(call, "EventName2", 2); return { contract }; }); @@ -103,7 +103,7 @@ describe("Read event argument", () => { assert.equal(read1.nameOrIndex, "arg1"); assert.equal(read2.eventName, "EventName2"); - assert.equal(read2.nameOrIndex, "arg2"); + assert.equal(read2.nameOrIndex, 2); }); it("should default the eventIndex to 0", () => { @@ -191,6 +191,21 @@ describe("Read event argument", () => { }); describe("validation", () => { + describe("module stage", () => { + it("should not validate a nameOrIndex that is not a number or string", () => { + assert.throws( + () => + buildModule("Module1", (m) => { + const another = m.contract("Another", []); + m.readEventArgument(another, "test", {} as any); + + return { another }; + }), + /Invalid nameOrIndex given/ + ); + }); + }); + describe("stage one", () => { let vm: typeof import("../src/internal/validation/stageOne/validateReadEventArgument"); let validateReadEventArgument: typeof vm.validateReadEventArgument; diff --git a/packages/core/test/staticCall.ts b/packages/core/test/staticCall.ts index e0d78aa1d..e0f6bbb43 100644 --- a/packages/core/test/staticCall.ts +++ b/packages/core/test/staticCall.ts @@ -143,6 +143,38 @@ describe("static call", () => { assert(callFuture.dependencies.has(staticCallFuture!)); }); + it("should be able to use a string or number to index its result", () => { + const moduleWithASingleContract = buildModule("Module1", (m) => { + const contract1 = m.contract("Contract1"); + + m.staticCall(contract1, "test", [], "testName"); + m.staticCall(contract1, "test2", [], 2); + + return { contract1 }; + }); + + assert.isDefined(moduleWithASingleContract); + + const staticCallFuture = [...moduleWithASingleContract.futures].find( + ({ id }) => id === "Module1:Contract1#test" + ); + + const staticCallFuture2 = [...moduleWithASingleContract.futures].find( + ({ id }) => id === "Module1:Contract1#test2" + ); + + if (!(staticCallFuture instanceof NamedStaticCallFutureImplementation)) { + assert.fail("Not a named static contract deployment"); + } + + if (!(staticCallFuture2 instanceof NamedStaticCallFutureImplementation)) { + assert.fail("Not a named static contract deployment"); + } + + assert.equal(staticCallFuture.nameOrIndex, "testName"); + assert.equal(staticCallFuture2.nameOrIndex, 2); + }); + it("should be able to pass a string as from option", () => { const moduleWithDependentContracts = buildModule("Module1", (m) => { const example = m.contract("Example"); @@ -420,6 +452,19 @@ describe("static call", () => { ); }); + it("should not validate a nameOrIndex that is not a number or string", () => { + assert.throws( + () => + buildModule("Module1", (m) => { + const another = m.contract("Another", []); + m.staticCall(another, "test", [], {} as any); + + return { another }; + }), + /Invalid nameOrIndex given/ + ); + }); + it("should not validate a non-contract", () => { assert.throws( () => diff --git a/packages/hardhat-plugin/test/events.ts b/packages/hardhat-plugin/test/events.ts index d129ee857..1667f58af 100644 --- a/packages/hardhat-plugin/test/events.ts +++ b/packages/hardhat-plugin/test/events.ts @@ -58,4 +58,29 @@ describe("events", () => { assert.equal(await result.fooFactory.isDeployed(), true); assert.equal(await result.foo.x(), Number(1)); }); + + it("should be able to use the output of a readEvent with an indexed tuple result", async function () { + const moduleDefinition = buildModule("FooModule", (m) => { + const tupleContract = m.contract("TupleEmitter"); + + const tupleCall = m.call(tupleContract, "emitTuple"); + + const arg1 = m.readEventArgument(tupleCall, "TupleEvent", "arg1", { + id: "arg1", + }); + const arg2 = m.readEventArgument(tupleCall, "TupleEvent", 1, { + id: "arg2", + }); + + m.call(tupleContract, "verifyArg1", [arg1], { id: "call1" }); + m.call(tupleContract, "verifyArg2", [arg2], { id: "call2" }); + + return { tupleContract }; + }); + + const result = await this.deploy(moduleDefinition); + + assert.equal(await result.tupleContract.arg1Captured(), true); + assert.equal(await result.tupleContract.arg2Captured(), true); + }); }); diff --git a/packages/hardhat-plugin/test/fixture-projects/minimal/contracts/Contracts.sol b/packages/hardhat-plugin/test/fixture-projects/minimal/contracts/Contracts.sol index 03e094119..a01a2f824 100644 --- a/packages/hardhat-plugin/test/fixture-projects/minimal/contracts/Contracts.sol +++ b/packages/hardhat-plugin/test/fixture-projects/minimal/contracts/Contracts.sol @@ -3,138 +3,190 @@ pragma solidity >=0.7.0 <0.9.0; pragma experimental ABIEncoderV2; contract Foo { - bool public isFoo = true; - uint256 public x = 1; - - function inc() public { - x++; - } - - function incByPositiveNumber(uint256 n) public { - require(n > 0, "n must be positive"); - x += n; - } - - function incTwoNumbers(uint256 first, uint256 second) public { - x += first; - x += second; - } + bool public isFoo = true; + uint256 public x = 1; + + function inc() public { + x++; + } + + function incByPositiveNumber(uint256 n) public { + require(n > 0, "n must be positive"); + x += n; + } + + function incTwoNumbers(uint256 first, uint256 second) public { + x += first; + x += second; + } } contract Bar { - bool public isBar = true; + bool public isBar = true; } contract UsesContract { - address public contractAddress; + address public contractAddress; - constructor(address _contract) { - contractAddress = _contract; - } + constructor(address _contract) { + contractAddress = _contract; + } - function setAddress(address _contract) public { - contractAddress = _contract; - } + function setAddress(address _contract) public { + contractAddress = _contract; + } } contract Greeter { - string private _greeting; + string private _greeting; - constructor(string memory greeting) { - _greeting = greeting; - } + constructor(string memory greeting) { + _greeting = greeting; + } - function getGreeting() public view returns (string memory) { - return _greeting; - } + function getGreeting() public view returns (string memory) { + return _greeting; + } } contract PassingValue { - constructor() payable {} + constructor() payable {} - function deposit() public payable {} + function deposit() public payable {} +} + +contract TupleReturn { + bool public arg1Captured; + bool public arg2Captured; + + function getTuple() public pure returns (bool arg1, uint256 arg2) { + return (true, 1234); + } + + function verifyArg1(bool arg) public returns (uint256 output) { + arg1Captured = true; + + require(arg == true, "arg1 is wrong"); + + return 1; + } + + function verifyArg2(uint256 arg) public returns (uint256 output) { + arg2Captured = true; + + require(arg == 1234, "arg2 is wrong"); + + return 1; + } +} + +contract TupleEmitter { + bool public arg1Captured; + bool public arg2Captured; + + event TupleEvent(bool arg1, uint256 arg2); + + function emitTuple() public { + emit TupleEvent(true, 1234); + } + + function verifyArg1(bool arg) public returns (uint256 output) { + arg1Captured = true; + + require(arg == true, "arg1 is wrong"); + + return 1; + } + + function verifyArg2(uint256 arg) public returns (uint256 output) { + arg2Captured = true; + + require(arg == 1234, "arg2 is wrong"); + + return 1; + } } contract CaptureArraysContract { - bool public arraysCaptured; - - constructor() { - arraysCaptured = false; - } - - function recordArrays( - uint256[] memory first, - string[] memory second, - bool[] memory third - ) public returns (uint256 output) { - arraysCaptured = true; - - require(first.length == 3, "Wrong number of args on first arg"); - require(first[0] == 1, "First value is wrong"); - require(first[1] == 2, "Second value is wrong"); - require(first[2] == 3, "Third value is wrong"); - - require(second.length == 3, "Wrong number of args on second arg"); - require( - keccak256(abi.encodePacked(second[0])) == - keccak256(abi.encodePacked("a")), - "First value is wrong" - ); - require( - keccak256(abi.encodePacked(second[1])) == - keccak256(abi.encodePacked("b")), - "Second value is wrong" - ); - require( - keccak256(abi.encodePacked(second[2])) == - keccak256(abi.encodePacked("c")), - "Third value is wrong" - ); - - require(third.length == 3, "Wrong number of args on third arg"); - require(third[0] == true, "First value is wrong"); - require(third[1] == false, "Second value is wrong"); - require(third[2] == true, "Third value is wrong"); - - return 1; - } + bool public arraysCaptured; + + constructor() { + arraysCaptured = false; + } + + function recordArrays( + uint256[] memory first, + string[] memory second, + bool[] memory third + ) public returns (uint256 output) { + arraysCaptured = true; + + require(first.length == 3, "Wrong number of args on first arg"); + require(first[0] == 1, "First value is wrong"); + require(first[1] == 2, "Second value is wrong"); + require(first[2] == 3, "Third value is wrong"); + + require(second.length == 3, "Wrong number of args on second arg"); + require( + keccak256(abi.encodePacked(second[0])) == + keccak256(abi.encodePacked("a")), + "First value is wrong" + ); + require( + keccak256(abi.encodePacked(second[1])) == + keccak256(abi.encodePacked("b")), + "Second value is wrong" + ); + require( + keccak256(abi.encodePacked(second[2])) == + keccak256(abi.encodePacked("c")), + "Third value is wrong" + ); + + require(third.length == 3, "Wrong number of args on third arg"); + require(third[0] == true, "First value is wrong"); + require(third[1] == false, "Second value is wrong"); + require(third[2] == true, "Third value is wrong"); + + return 1; + } } contract CaptureComplexObjectContract { - bool public complexArgCaptured; - - constructor() { - complexArgCaptured = false; - } - - struct SubComplex { - string sub; - } - - struct Complex { - bool firstBool; - uint256[] secondArray; - SubComplex thirdSubcomplex; - } - - function testComplexObject( - Complex memory complexArg - ) public returns (uint256 output) { - complexArgCaptured = true; - - require(complexArg.firstBool, "bad first bool"); - - require(complexArg.secondArray.length == 3, "bad second array"); - require(complexArg.secondArray[0] == 1, "First value is wrong"); - require(complexArg.secondArray[1] == 2, "Second value is wrong"); - require(complexArg.secondArray[2] == 3, "Third value is wrong"); - - require( - keccak256(abi.encodePacked(complexArg.thirdSubcomplex.sub)) == - keccak256(abi.encodePacked("sub")), - "The complex sub object property is wrong" - ); - - return 1; - } + bool public complexArgCaptured; + + constructor() { + complexArgCaptured = false; + } + + struct SubComplex { + string sub; + } + + struct Complex { + bool firstBool; + uint256[] secondArray; + SubComplex thirdSubcomplex; + } + + function testComplexObject( + Complex memory complexArg + ) public returns (uint256 output) { + complexArgCaptured = true; + + require(complexArg.firstBool, "bad first bool"); + + require(complexArg.secondArray.length == 3, "bad second array"); + require(complexArg.secondArray[0] == 1, "First value is wrong"); + require(complexArg.secondArray[1] == 2, "Second value is wrong"); + require(complexArg.secondArray[2] == 3, "Third value is wrong"); + + require( + keccak256(abi.encodePacked(complexArg.thirdSubcomplex.sub)) == + keccak256(abi.encodePacked("sub")), + "The complex sub object property is wrong" + ); + + return 1; + } } diff --git a/packages/hardhat-plugin/test/static-calls.ts b/packages/hardhat-plugin/test/static-calls.ts index a4e4db03a..fe5c68dda 100644 --- a/packages/hardhat-plugin/test/static-calls.ts +++ b/packages/hardhat-plugin/test/static-calls.ts @@ -107,6 +107,29 @@ describe("static calls", () => { assert.equal(await result.foo.x(), Number(1)); }); + it("should be able to use the output of a static call with an indexed tuple result", async function () { + const moduleDefinition = buildModule("FooModule", (m) => { + const tupleContract = m.contract("TupleReturn"); + + const arg1 = m.staticCall(tupleContract, "getTuple", [], "arg1", { + id: "arg1", + }); + const arg2 = m.staticCall(tupleContract, "getTuple", [], "arg2", { + id: "arg2", + }); + + m.call(tupleContract, "verifyArg1", [arg1], { id: "call1" }); + m.call(tupleContract, "verifyArg2", [arg2], { id: "call2" }); + + return { tupleContract }; + }); + + const result = await this.deploy(moduleDefinition); + + assert.equal(await result.tupleContract.arg1Captured(), true); + assert.equal(await result.tupleContract.arg2Captured(), true); + }); + it("should not be able to use the output of a non-address static call in a contract at", async function () { const moduleDefinition = buildModule("FooModule", (m) => { const account1 = m.getAccount(1);