From f9d67aa0b58a9bf96c3b9e4428b5cc674f7e8e56 Mon Sep 17 00:00:00 2001 From: Kunal Arora <55632507+aroralanuk@users.noreply.github.com> Date: Tue, 12 Mar 2024 21:59:14 -0400 Subject: [PATCH] feat: update ICA to v3 (#3314) ### Description - Update ICA to accept _hookMetadata per call and per batch of calls. - Allow ICA to accept msg.value ### Drive-by changes - Moved IGP from MockMailbox to the InterchainAccountRouterTest contract as it's not needed in the former and doesn't break any tests ### Related issues - Fixes https://github.com/hyperlane-xyz/hyperlane-monorepo/issues/3197 ### Backward compatibility Yes with V3 ### Testing Unit tests with focus on igp as the hook as this will be the one used in production --- .../middleware/InterchainAccountRouter.sol | 160 ++++++++- .../mock/MockHyperlaneEnvironment.sol | 3 - .../test/TestInterchainGasPaymaster.sol | 4 + solidity/test/InterchainAccountRouter.t.sol | 340 ++++++++++++++---- .../account/accounts.hardhat-test.ts | 3 +- 5 files changed, 427 insertions(+), 83 deletions(-) diff --git a/solidity/contracts/middleware/InterchainAccountRouter.sol b/solidity/contracts/middleware/InterchainAccountRouter.sol index b6ae7bdd4e..dc949c1713 100644 --- a/solidity/contracts/middleware/InterchainAccountRouter.sol +++ b/solidity/contracts/middleware/InterchainAccountRouter.sol @@ -1,12 +1,25 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; +/*@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@ HYPERLANE @@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ + @@@@@@@@@ @@@@@@@@@ +@@@@@@@@@ @@@@@@@@*/ + // ============ Internal Imports ============ import {OwnableMulticall} from "./libs/OwnableMulticall.sol"; import {InterchainAccountMessage} from "./libs/InterchainAccountMessage.sol"; import {CallLib} from "./libs/Call.sol"; import {MinimalProxy} from "../libs/MinimalProxy.sol"; import {TypeCasts} from "../libs/TypeCasts.sol"; +import {StandardHookMetadata} from "../hooks/libs/StandardHookMetadata.sol"; import {EnumerableMapExtended} from "../libs/EnumerableMapExtended.sol"; import {Router} from "../client/Router.sol"; @@ -82,17 +95,17 @@ contract InterchainAccountRouter is Router { /** * @notice Initializes the contract with HyperlaneConnectionClient contracts - * @param _interchainGasPaymaster Unused but required by HyperlaneConnectionClient + * @param _customHook used by the Router to set the hook to override with * @param _interchainSecurityModule The address of the local ISM contract * @param _owner The address with owner privileges */ function initialize( - address _interchainGasPaymaster, + address _customHook, address _interchainSecurityModule, address _owner ) external initializer { _MailboxClient_initialize( - _interchainGasPaymaster, + _customHook, _interchainSecurityModule, _owner ); @@ -157,7 +170,7 @@ contract InterchainAccountRouter is Router { address _to, uint256 _value, bytes memory _data - ) external returns (bytes32) { + ) external payable returns (bytes32) { bytes32 _router = routers(_destination); bytes32 _ism = isms[_destination]; bytes memory _body = InterchainAccountMessage.encode( @@ -170,6 +183,44 @@ contract InterchainAccountRouter is Router { return _dispatchMessage(_destination, _router, _ism, _body); } + /** + * @notice Dispatches a single remote call to be made by an owner's + * interchain account on the destination domain + * @dev Uses the default router and ISM addresses for the destination + * domain, reverting if none have been configured + * @param _destination The remote domain of the chain to make calls on + * @param _to The address of the contract to call + * @param _value The value to include in the call + * @param _data The calldata + * @param _hookMetadata The hook metadata to override with for the hook set by the owner + * @return The Hyperlane message ID + */ + function callRemote( + uint32 _destination, + address _to, + uint256 _value, + bytes memory _data, + bytes memory _hookMetadata + ) external payable returns (bytes32) { + bytes32 _router = routers(_destination); + bytes32 _ism = isms[_destination]; + bytes memory _body = InterchainAccountMessage.encode( + msg.sender, + _ism, + _to, + _value, + _data + ); + return + _dispatchMessageWithMetadata( + _destination, + _router, + _ism, + _body, + _hookMetadata + ); + } + /** * @notice Dispatches a sequence of remote calls to be made by an owner's * interchain account on the destination domain @@ -183,10 +234,44 @@ contract InterchainAccountRouter is Router { function callRemote( uint32 _destination, CallLib.Call[] calldata _calls - ) external returns (bytes32) { + ) external payable returns (bytes32) { bytes32 _router = routers(_destination); bytes32 _ism = isms[_destination]; - return callRemoteWithOverrides(_destination, _router, _ism, _calls); + bytes memory _body = InterchainAccountMessage.encode( + msg.sender, + _ism, + _calls + ); + + return _dispatchMessage(_destination, _router, _ism, _body); + } + + /** + * @notice Dispatches a sequence of remote calls to be made by an owner's + * interchain account on the destination domain + * @dev Uses the default router and ISM addresses for the destination + * domain, reverting if none have been configured + * @dev Recommend using CallLib.build to format the interchain calls. + * @param _destination The remote domain of the chain to make calls on + * @param _calls The sequence of calls to make + * @param _hookMetadata The hook metadata to override with for the hook set by the owner + * @return The Hyperlane message ID + */ + function callRemote( + uint32 _destination, + CallLib.Call[] calldata _calls, + bytes calldata _hookMetadata + ) external payable returns (bytes32) { + bytes32 _router = routers(_destination); + bytes32 _ism = isms[_destination]; + return + callRemoteWithOverrides( + _destination, + _router, + _ism, + _calls, + _hookMetadata + ); } /** @@ -395,7 +480,7 @@ contract InterchainAccountRouter is Router { bytes32 _router, bytes32 _ism, CallLib.Call[] calldata _calls - ) public returns (bytes32) { + ) public payable returns (bytes32) { bytes memory _body = InterchainAccountMessage.encode( msg.sender, _ism, @@ -404,6 +489,39 @@ contract InterchainAccountRouter is Router { return _dispatchMessage(_destination, _router, _ism, _body); } + /** + * @notice Dispatches a sequence of remote calls to be made by an owner's + * interchain account on the destination domain + * @dev Recommend using CallLib.build to format the interchain calls + * @param _destination The remote domain of the chain to make calls on + * @param _router The remote router address + * @param _ism The remote ISM address + * @param _calls The sequence of calls to make + * @param _hookMetadata The hook metadata to override with for the hook set by the owner + * @return The Hyperlane message ID + */ + function callRemoteWithOverrides( + uint32 _destination, + bytes32 _router, + bytes32 _ism, + CallLib.Call[] calldata _calls, + bytes memory _hookMetadata + ) public payable returns (bytes32) { + bytes memory _body = InterchainAccountMessage.encode( + msg.sender, + _ism, + _calls + ); + return + _dispatchMessageWithMetadata( + _destination, + _router, + _ism, + _body, + _hookMetadata + ); + } + // ============ Internal Functions ============ /** @@ -474,7 +592,33 @@ contract InterchainAccountRouter is Router { ) private returns (bytes32) { require(_router != bytes32(0), "no router specified for destination"); emit RemoteCallDispatched(_destination, msg.sender, _router, _ism); - return mailbox.dispatch(_destination, _router, _body); + return mailbox.dispatch{value: msg.value}(_destination, _router, _body); + } + + /** + * @notice Dispatches an InterchainAccountMessage to the remote router with hook metadata + * @param _destination The remote domain + * @param _router The address of the remote InterchainAccountRouter + * @param _ism The address of the remote ISM + * @param _body The InterchainAccountMessage body + * @param _hookMetadata The hook metadata to override with for the hook set by the owner + */ + function _dispatchMessageWithMetadata( + uint32 _destination, + bytes32 _router, + bytes32 _ism, + bytes memory _body, + bytes memory _hookMetadata + ) private returns (bytes32) { + require(_router != bytes32(0), "no router specified for destination"); + emit RemoteCallDispatched(_destination, msg.sender, _router, _ism); + return + mailbox.dispatch{value: msg.value}( + _destination, + _router, + _body, + _hookMetadata + ); } /** diff --git a/solidity/contracts/mock/MockHyperlaneEnvironment.sol b/solidity/contracts/mock/MockHyperlaneEnvironment.sol index 9f51b61ad3..1664951da8 100644 --- a/solidity/contracts/mock/MockHyperlaneEnvironment.sol +++ b/solidity/contracts/mock/MockHyperlaneEnvironment.sol @@ -31,9 +31,6 @@ contract MockHyperlaneEnvironment { originMailbox.setDefaultIsm(address(isms[originDomain])); destinationMailbox.setDefaultIsm(address(isms[destinationDomain])); - igps[originDomain] = new TestInterchainGasPaymaster(); - igps[destinationDomain] = new TestInterchainGasPaymaster(); - originMailbox.transferOwnership(msg.sender); destinationMailbox.transferOwnership(msg.sender); diff --git a/solidity/contracts/test/TestInterchainGasPaymaster.sol b/solidity/contracts/test/TestInterchainGasPaymaster.sol index 08a5a0aa3f..33f1480296 100644 --- a/solidity/contracts/test/TestInterchainGasPaymaster.sol +++ b/solidity/contracts/test/TestInterchainGasPaymaster.sol @@ -17,4 +17,8 @@ contract TestInterchainGasPaymaster is InterchainGasPaymaster { ) public pure override returns (uint256) { return gasPrice * gasAmount; } + + function getDefaultGasUsage() public pure returns (uint256) { + return DEFAULT_GAS_USAGE; + } } diff --git a/solidity/test/InterchainAccountRouter.t.sol b/solidity/test/InterchainAccountRouter.t.sol index 1f52848963..a7008060aa 100644 --- a/solidity/test/InterchainAccountRouter.t.sol +++ b/solidity/test/InterchainAccountRouter.t.sol @@ -1,13 +1,16 @@ // SPDX-License-Identifier: Apache-2.0 pragma solidity ^0.8.13; +import {Test} from "forge-std/Test.sol"; import {TransparentUpgradeableProxy} from "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol"; -import "forge-std/Test.sol"; -import "../contracts/mock/MockMailbox.sol"; -import "../contracts/mock/MockHyperlaneEnvironment.sol"; + +import {StandardHookMetadata} from "../contracts/hooks/libs/StandardHookMetadata.sol"; +import {MockMailbox} from "../contracts/mock/MockMailbox.sol"; +import {MockHyperlaneEnvironment} from "../contracts/mock/MockHyperlaneEnvironment.sol"; import {TypeCasts} from "../contracts/libs/TypeCasts.sol"; import {IInterchainSecurityModule} from "../contracts/interfaces/IInterchainSecurityModule.sol"; -import {IInterchainGasPaymaster} from "../contracts/interfaces/IInterchainGasPaymaster.sol"; +import {TestInterchainGasPaymaster} from "../contracts/test/TestInterchainGasPaymaster.sol"; +import {IPostDispatchHook} from "../contracts/interfaces/hooks/IPostDispatchHook.sol"; import {CallLib, OwnableMulticall, InterchainAccountRouter} from "../contracts/middleware/InterchainAccountRouter.sol"; import {InterchainAccountIsm} from "../contracts/isms/routing/InterchainAccountIsm.sol"; @@ -45,29 +48,26 @@ contract InterchainAccountRouterTest is Test { address account ); - struct Bytes32Pair { - bytes32 a; - bytes32 b; - } - - MockHyperlaneEnvironment environment; + MockHyperlaneEnvironment internal environment; - uint32 origin = 1; - uint32 destination = 2; + uint32 internal origin = 1; + uint32 internal destination = 2; - InterchainAccountIsm icaIsm; - InterchainAccountRouter originRouter; - InterchainAccountRouter destinationRouter; - bytes32 ismOverride; - bytes32 routerOverride; + TestInterchainGasPaymaster internal igp; + InterchainAccountIsm internal icaIsm; + InterchainAccountRouter internal originRouter; + InterchainAccountRouter internal destinationRouter; + bytes32 internal ismOverride; + bytes32 internal routerOverride; + uint256 gasPaymentQuote; - OwnableMulticall ica; + OwnableMulticall internal ica; - Callable target; + Callable internal target; function deployProxiedIcaRouter( MockMailbox _mailbox, - IInterchainGasPaymaster _igp, + IPostDispatchHook _customHook, IInterchainSecurityModule _ism, address _owner ) public returns (InterchainAccountRouter) { @@ -80,7 +80,7 @@ contract InterchainAccountRouterTest is Test { address(1), // no proxy owner necessary for testing abi.encodeWithSelector( InterchainAccountRouter.initialize.selector, - address(_igp), + address(_customHook), address(_ism), _owner ) @@ -92,6 +92,12 @@ contract InterchainAccountRouterTest is Test { function setUp() public { environment = new MockHyperlaneEnvironment(origin, destination); + igp = new TestInterchainGasPaymaster(); + gasPaymentQuote = igp.quoteGasPayment( + destination, + igp.getDefaultGasUsage() + ); + icaIsm = new InterchainAccountIsm( address(environment.mailboxes(destination)) ); @@ -110,6 +116,8 @@ contract InterchainAccountRouterTest is Test { owner ); + environment.mailboxes(origin).setDefaultHook(address(igp)); + routerOverride = TypeCasts.addressToBytes32(address(destinationRouter)); ismOverride = TypeCasts.addressToBytes32( address(environment.isms(destination)) @@ -124,80 +132,102 @@ contract InterchainAccountRouterTest is Test { target = new Callable(); } - function testConstructor() public { - // The deployed ICA should be owned by the router - destinationRouter.getDeployedInterchainAccount( - origin, - address(this), - address(originRouter), - address(environment.isms(destination)) - ); - assertEq(ica.owner(), address(destinationRouter)); + function testFuzz_constructor(address _localOwner) public { + OwnableMulticall _account = destinationRouter + .getDeployedInterchainAccount( + origin, + _localOwner, + address(originRouter), + address(environment.isms(destination)) + ); + assertEq(_account.owner(), address(destinationRouter)); } - function testGetRemoteInterchainAccount() public { - assertEq( - originRouter.getRemoteInterchainAccount( - address(this), - address(destinationRouter), - address(environment.isms(destination)) - ), - address(ica) + function testFuzz_getRemoteInterchainAccount( + address _localOwner, + address _ism + ) public { + address _account = originRouter.getRemoteInterchainAccount( + address(_localOwner), + address(destinationRouter), + _ism ); originRouter.enrollRemoteRouterAndIsm( destination, routerOverride, - ismOverride + TypeCasts.addressToBytes32(_ism) ); assertEq( - originRouter.getRemoteInterchainAccount(destination, address(this)), - address(ica) + originRouter.getRemoteInterchainAccount( + destination, + address(_localOwner) + ), + _account ); } - function testEnrollRemoteRouters( + function testFuzz_enrollRemoteRouters( uint8 count, uint32 domain, bytes32 router ) public { vm.assume(count > 0 && count < uint256(router) && count < domain); + + // arrange + // count - # of domains and routers uint32[] memory domains = new uint32[](count); bytes32[] memory routers = new bytes32[](count); for (uint256 i = 0; i < count; i++) { domains[i] = domain - uint32(i); routers[i] = bytes32(uint256(router) - i); } + + // act originRouter.enrollRemoteRouters(domains, routers); + + // assert uint32[] memory actualDomains = originRouter.domains(); assertEq(actualDomains.length, domains.length); + assertEq(abi.encode(originRouter.domains()), abi.encode(domains)); + for (uint256 i = 0; i < count; i++) { bytes32 actualRouter = originRouter.routers(domains[i]); bytes32 actualIsm = originRouter.isms(domains[i]); + assertEq(actualRouter, routers[i]); assertEq(actualIsm, bytes32(0)); assertEq(actualDomains[i], domains[i]); } - assertEq(abi.encode(originRouter.domains()), abi.encode(domains)); } - function testEnrollRemoteRouterAndIsm(bytes32 router, bytes32 ism) public { + function testFuzz_enrollRemoteRouterAndIsm( + bytes32 router, + bytes32 ism + ) public { vm.assume(router != bytes32(0)); + + // arrange pre-condition bytes32 actualRouter = originRouter.routers(destination); bytes32 actualIsm = originRouter.isms(destination); assertEq(actualRouter, bytes32(0)); assertEq(actualIsm, bytes32(0)); + + // act originRouter.enrollRemoteRouterAndIsm(destination, router, ism); + + // assert actualRouter = originRouter.routers(destination); actualIsm = originRouter.isms(destination); assertEq(actualRouter, router); assertEq(actualIsm, ism); } - function testEnrollRemoteRouterAndIsms( + function testFuzz_enrollRemoteRouterAndIsms( uint32[] calldata destinations, bytes32[] calldata routers, bytes32[] calldata isms ) public { + // check reverts if ( destinations.length != routers.length || destinations.length != isms.length @@ -207,7 +237,10 @@ contract InterchainAccountRouterTest is Test { return; } + // act originRouter.enrollRemoteRouterAndIsms(destinations, routers, isms); + + // assert for (uint256 i = 0; i < destinations.length; i++) { bytes32 actualRouter = originRouter.routers(destinations[i]); bytes32 actualIsm = originRouter.isms(destinations[i]); @@ -216,27 +249,35 @@ contract InterchainAccountRouterTest is Test { } } - function testEnrollRemoteRouterAndIsmImmutable( + function testFuzz_enrollRemoteRouterAndIsmImmutable( bytes32 routerA, bytes32 ismA, bytes32 routerB, bytes32 ismB ) public { vm.assume(routerA != bytes32(0) && routerB != bytes32(0)); + + // act originRouter.enrollRemoteRouterAndIsm(destination, routerA, ismA); + + // assert vm.expectRevert( bytes("router and ISM defaults are immutable once set") ); originRouter.enrollRemoteRouterAndIsm(destination, routerB, ismB); } - function testEnrollRemoteRouterAndIsmNonOwner( + function testFuzz_enrollRemoteRouterAndIsmNonOwner( address newOwner, bytes32 router, bytes32 ism ) public { vm.assume(newOwner != address(0) && newOwner != originRouter.owner()); + + // act originRouter.transferOwnership(newOwner); + + // assert vm.expectRevert(bytes("Ownable: caller is not the owner")); originRouter.enrollRemoteRouterAndIsm(destination, router, ism); } @@ -245,6 +286,7 @@ contract InterchainAccountRouterTest is Test { bytes32 data ) private view returns (CallLib.Call[] memory) { vm.assume(data != bytes32(0)); + CallLib.Call memory call = CallLib.Call( TypeCasts.addressToBytes32(address(target)), 0, @@ -268,89 +310,240 @@ contract InterchainAccountRouterTest is Test { assertEq(target.data(address(ica)), data); } - function testSingleCallRemoteWithDefault(bytes32 data) public { + function assertIgpPayment( + uint256 balanceBefore, + uint256 balanceAfter, + uint256 gasLimit + ) private { + uint256 expectedGasPayment = gasLimit * igp.gasPrice(); + assertEq(balanceBefore - balanceAfter, expectedGasPayment); + assertEq(address(igp).balance, expectedGasPayment); + } + + function testFuzz_singleCallRemoteWithDefault(bytes32 data) public { + // arrange originRouter.enrollRemoteRouterAndIsm( destination, routerOverride, ismOverride ); + uint256 balanceBefore = address(this).balance; + + // act CallLib.Call[] memory calls = getCalls(data); - originRouter.callRemote( + originRouter.callRemote{value: gasPaymentQuote}( destination, TypeCasts.bytes32ToAddress(calls[0].to), calls[0].value, calls[0].data ); + + // assert + uint256 balanceAfter = address(this).balance; assertRemoteCallReceived(data); + assertIgpPayment(balanceBefore, balanceAfter, igp.getDefaultGasUsage()); } - function testCallRemoteWithDefault(bytes32 data) public { + function testFuzz_callRemoteWithDefault(bytes32 data) public { + // arrange originRouter.enrollRemoteRouterAndIsm( destination, routerOverride, ismOverride ); - originRouter.callRemote(destination, getCalls(data)); + uint256 balanceBefore = address(this).balance; + + // act + originRouter.callRemote{value: gasPaymentQuote}( + destination, + getCalls(data) + ); + + // assert + uint256 balanceAfter = address(this).balance; assertRemoteCallReceived(data); + assertIgpPayment(balanceBefore, balanceAfter, igp.getDefaultGasUsage()); } - function testOverrideAndCallRemote(bytes32 data) public { + function testFuzz_overrideAndCallRemote(bytes32 data) public { + // arrange originRouter.enrollRemoteRouterAndIsm( destination, routerOverride, ismOverride ); - originRouter.callRemote(destination, getCalls(data)); + uint256 balanceBefore = address(this).balance; + + // act + originRouter.callRemote{value: gasPaymentQuote}( + destination, + getCalls(data) + ); + + // assert + uint256 balanceAfter = address(this).balance; assertRemoteCallReceived(data); + assertIgpPayment(balanceBefore, balanceAfter, igp.getDefaultGasUsage()); } - function testCallRemoteWithoutDefaults(bytes32 data) public { + function testFuzz_callRemoteWithoutDefaults_revert_noRouter( + bytes32 data + ) public { + // assert error CallLib.Call[] memory calls = getCalls(data); vm.expectRevert(bytes("no router specified for destination")); originRouter.callRemote(destination, calls); } - function testCallRemoteWithOverrides(bytes32 data) public { - originRouter.callRemoteWithOverrides( + function testFuzz_customMetadata_forIgp( + uint64 gasLimit, + uint64 overpayment, + bytes32 data + ) public { + // arrange + bytes memory metadata = StandardHookMetadata.formatMetadata( + 0, + gasLimit, + address(this), + "" + ); + originRouter.enrollRemoteRouterAndIsm( + destination, + routerOverride, + ismOverride + ); + uint256 balanceBefore = address(this).balance; + + // act + originRouter.callRemote{value: gasLimit * igp.gasPrice() + overpayment}( + destination, + getCalls(data), + metadata + ); + + // assert + uint256 balanceAfter = address(this).balance; + assertRemoteCallReceived(data); + assertIgpPayment(balanceBefore, balanceAfter, gasLimit); + } + + function testFuzz_customMetadata_reverts_underpayment( + uint64 gasLimit, + uint64 payment, + bytes32 data + ) public { + vm.assume(payment < gasLimit * igp.gasPrice()); + // arrange + bytes memory metadata = StandardHookMetadata.formatMetadata( + 0, + gasLimit, + address(this), + "" + ); + originRouter.enrollRemoteRouterAndIsm( + destination, + routerOverride, + ismOverride + ); + + // act + vm.expectRevert("IGP: insufficient interchain gas payment"); + originRouter.callRemote{value: payment}( + destination, + getCalls(data), + metadata + ); + } + + function testFuzz_callRemoteWithOverrides_default(bytes32 data) public { + // arrange + uint256 balanceBefore = address(this).balance; + + // act + originRouter.callRemoteWithOverrides{value: gasPaymentQuote}( destination, routerOverride, ismOverride, getCalls(data) ); + + // assert + uint256 balanceAfter = address(this).balance; + assertRemoteCallReceived(data); + assertIgpPayment(balanceBefore, balanceAfter, igp.getDefaultGasUsage()); + } + + function testFuzz_callRemoteWithOverrides_metadata( + uint64 gasLimit, + bytes32 data + ) public { + // arrange + bytes memory metadata = StandardHookMetadata.formatMetadata( + 0, + gasLimit, + address(this), + "" + ); + uint256 balanceBefore = address(this).balance; + + // act + originRouter.callRemoteWithOverrides{value: gasLimit * igp.gasPrice()}( + destination, + routerOverride, + ismOverride, + getCalls(data), + metadata + ); + + // assert + uint256 balanceAfter = address(this).balance; assertRemoteCallReceived(data); + assertIgpPayment(balanceBefore, balanceAfter, gasLimit); } - function testCallRemoteWithFailingIsmOverride(bytes32 data) public { + function testFuzz_callRemoteWithFailingIsmOverride(bytes32 data) public { + // arrange string memory failureMessage = "failing ism"; bytes32 failingIsm = TypeCasts.addressToBytes32( address(new FailingIsm(failureMessage)) ); - originRouter.callRemoteWithOverrides( + + // act + originRouter.callRemoteWithOverrides{value: gasPaymentQuote}( destination, routerOverride, failingIsm, - getCalls(data) + getCalls(data), + "" ); + + // assert vm.expectRevert(bytes(failureMessage)); environment.processNextPendingMessage(); } - function testCallRemoteWithFailingDefaultIsm(bytes32 data) public { + function testFuzz_callRemoteWithFailingDefaultIsm(bytes32 data) public { + // arrange string memory failureMessage = "failing ism"; FailingIsm failingIsm = new FailingIsm(failureMessage); + // act environment.mailboxes(destination).setDefaultIsm(address(failingIsm)); - originRouter.callRemoteWithOverrides( + originRouter.callRemoteWithOverrides{value: gasPaymentQuote}( destination, routerOverride, bytes32(0), - getCalls(data) + getCalls(data), + "" ); + + // assert vm.expectRevert(bytes(failureMessage)); environment.processNextPendingMessage(); } - function testGetLocalInterchainAccount(bytes32 data) public { + function testFuzz_getLocalInterchainAccount(bytes32 data) public { + // check OwnableMulticall destinationIca = destinationRouter .getLocalInterchainAccount( origin, @@ -369,21 +562,23 @@ contract InterchainAccountRouterTest is Test { ) ) ); - assertEq(address(destinationIca).code.length, 0); - originRouter.callRemoteWithOverrides( + // act + originRouter.callRemoteWithOverrides{value: gasPaymentQuote}( destination, routerOverride, ismOverride, - getCalls(data) + getCalls(data), + "" ); - assertRemoteCallReceived(data); + // recheck + assertRemoteCallReceived(data); assert(address(destinationIca).code.length != 0); } - function testReceiveValue(uint256 value) public { + function testFuzz_receiveValue(uint256 value) public { vm.assume(value > 1 && value <= address(this).balance); // receive value before deployed assert(address(ica).code.length == 0); @@ -408,7 +603,7 @@ contract InterchainAccountRouterTest is Test { assertEq(value, msg.value); } - function testSendValue(uint256 value) public { + function testFuzz_sendValue(uint256 value) public { vm.assume(value > 0 && value <= address(this).balance); payable(address(ica)).transfer(value); @@ -417,13 +612,16 @@ contract InterchainAccountRouterTest is Test { CallLib.Call[] memory calls = new CallLib.Call[](1); calls[0] = call; - originRouter.callRemoteWithOverrides( + originRouter.callRemoteWithOverrides{value: gasPaymentQuote}( destination, routerOverride, ismOverride, - calls + calls, + "" ); vm.expectCall(address(this), value, data); environment.processNextPendingMessage(); } + + receive() external payable {} } diff --git a/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts b/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts index f837eb3554..19431c7a52 100644 --- a/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts +++ b/typescript/sdk/src/middleware/account/accounts.hardhat-test.ts @@ -78,11 +78,12 @@ describe.skip('InterchainAccounts', async () => { ethers.constants.AddressZero, ); - await local['callRemote(uint32,address,uint256,bytes)']( + await local['callRemote(uint32,address,uint256,bytes,bytes)']( multiProvider.getDomainId(remoteChain), recipient.address, 0, data, + '', ); await coreApp.processMessages(); expect(await recipient.lastCallMessage()).to.eql(fooMessage);