@ -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 r outer
destinationRouter . getDeployedInterchainAccount (
origin ,
address ( this ) ,
address ( originRouter ) ,
address ( environment . isms ( destination ) )
) ;
assertEq ( ica . owner ( ) , address ( destinationRouter ) ) ;
function testFuzz_constructor ( address _localOwner ) public {
OwnableMulticall _account = destinationR outer
. 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 testE nrollRemoteRouters (
function testFuzz_e nrollRemoteRouters (
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 testE nrollRemoteRouterAndIsms (
function testFuzz_e nrollRemoteRouterAndIsms (
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 testE nrollRemoteRouterAndIsmImmutable (
function testFuzz_e nrollRemoteRouterAndIsmImmutable (
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 testE nrollRemoteRouterAndIsmNonOwner (
function testFuzz_e nrollRemoteRouterAndIsmNonOwner (
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 testR eceiveValue ( uint256 value ) public {
function testFuzz_r eceiveValue ( 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 testS endValue ( uint256 value ) public {
function testFuzz_s endValue ( 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 { }
}