diff --git a/solidity/test/isms/ArbL2ToL1Ism.t.sol b/solidity/test/isms/ArbL2ToL1Ism.t.sol index f34c1a74e..a251cfda4 100644 --- a/solidity/test/isms/ArbL2ToL1Ism.t.sol +++ b/solidity/test/isms/ArbL2ToL1Ism.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT or Apache-2.0 pragma solidity ^0.8.13; -import {Test} from "forge-std/Test.sol"; - import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {MessageUtils} from "./IsmTestUtils.sol"; import {TestMailbox} from "../../contracts/test/TestMailbox.sol"; @@ -13,13 +11,9 @@ import {ArbL2ToL1Hook} from "../../contracts/hooks/ArbL2ToL1Hook.sol"; import {ArbL2ToL1Ism} from "../../contracts/isms/hook/ArbL2ToL1Ism.sol"; import {MockArbBridge, MockArbSys} from "../../contracts/mock/MockArbBridge.sol"; import {TestRecipient} from "../../contracts/test/TestRecipient.sol"; +import {ExternalBridgeTest} from "./ExternalBridgeTest.sol"; -contract ArbL2ToL1IsmTest is Test { - uint8 internal constant HYPERLANE_VERSION = 1; - uint32 internal constant MAINNET_DOMAIN = 1; - uint32 internal constant ARBITRUM_DOMAIN = 42161; - uint256 internal constant GAS_QUOTE = 120_000; - +contract ArbL2ToL1IsmTest is ExternalBridgeTest { uint256 internal constant MOCK_LEAF_INDEX = 40160; uint256 internal constant MOCK_L2_BLOCK = 54220000; uint256 internal constant MOCK_L1_BLOCK = 6098300; @@ -28,26 +22,14 @@ contract ArbL2ToL1IsmTest is Test { 0x0000000000000000000000000000000000000064; MockArbBridge internal arbBridge; - TestMailbox public l2Mailbox; - ArbL2ToL1Hook public hook; - ArbL2ToL1Ism public ism; - TestRecipient internal testRecipient; - bytes internal testMessage = - abi.encodePacked("Hello from the other chain!"); - bytes internal encodedMessage; - bytes internal testMetadata = - StandardHookMetadata.overrideRefundAddress(address(this)); - bytes32 internal messageId; - - function setUp() public { + function setUp() public override { // Arbitrum bridge mock setup + GAS_QUOTE = 120_000; vm.etch(L2_ARBSYS_ADDRESS, address(new MockArbSys()).code); - testRecipient = new TestRecipient(); - - encodedMessage = _encodeTestMessage(); - messageId = Message.id(encodedMessage); + deployAll(); + super.setUp(); } /////////////////////////////////////////////////////////////////// @@ -55,10 +37,10 @@ contract ArbL2ToL1IsmTest is Test { /////////////////////////////////////////////////////////////////// function deployHook() public { - l2Mailbox = new TestMailbox(ARBITRUM_DOMAIN); + originMailbox = new TestMailbox(ORIGIN_DOMAIN); hook = new ArbL2ToL1Hook( - address(l2Mailbox), - MAINNET_DOMAIN, + address(originMailbox), + DESTINATION_DOMAIN, TypeCasts.addressToBytes32(address(ism)), L2_ARBSYS_ADDRESS, GAS_QUOTE @@ -67,7 +49,6 @@ contract ArbL2ToL1IsmTest is Test { function deployIsm() public { arbBridge = new MockArbBridge(); - ism = new ArbL2ToL1Ism(address(arbBridge)); } @@ -75,196 +56,39 @@ contract ArbL2ToL1IsmTest is Test { deployIsm(); deployHook(); + arbBridge.setL2ToL1Sender(address(hook)); ism.setAuthorizedHook(TypeCasts.addressToBytes32(address(hook))); } - function test_postDispatch() public { - deployAll(); - - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (messageId) - ); - - l2Mailbox.updateLatestDispatchedId(messageId); + /* ============ helper functions ============ */ + function _expectOriginExternalBridgeCall( + bytes memory _encodedHookData + ) internal override { vm.expectCall( L2_ARBSYS_ADDRESS, abi.encodeCall( MockArbSys.sendTxToL1, - (address(ism), encodedHookData) + (address(ism), _encodedHookData) ) ); - hook.postDispatch(testMetadata, encodedMessage); - } - - function testFork_postDispatch_revertWhen_chainIDNotSupported() public { - deployAll(); - - bytes memory message = MessageUtils.formatMessage( - 0, - uint32(0), - ARBITRUM_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - 2, // wrong domain - TypeCasts.addressToBytes32(address(testRecipient)), - testMessage - ); - - l2Mailbox.updateLatestDispatchedId(Message.id(message)); - vm.expectRevert( - "AbstractMessageIdAuthHook: invalid destination domain" - ); - hook.postDispatch(testMetadata, message); - } - - function test_postDispatch_revertWhen_notLastDispatchedMessage() public { - deployAll(); - - vm.expectRevert( - "AbstractMessageIdAuthHook: message not latest dispatched" - ); - hook.postDispatch(testMetadata, encodedMessage); - } - - function test_verify_outboxCall() public { - deployAll(); - - bytes memory encodedOutboxTxMetadata = _encodeOutboxTx( - address(hook), - address(ism), - messageId, - 1 ether - ); - - vm.deal(address(arbBridge), 1 ether); - arbBridge.setL2ToL1Sender(address(hook)); - assertTrue(ism.verify(encodedOutboxTxMetadata, encodedMessage)); - assertEq(address(testRecipient).balance, 1 ether); } - function test_verify_statefulVerify() public { - deployAll(); - - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (messageId) - ); - - arbBridge.setL2ToL1Sender(address(hook)); - arbBridge.executeTransaction{value: 1 ether}( - new bytes32[](0), - MOCK_LEAF_INDEX, - address(hook), - address(ism), - MOCK_L2_BLOCK, - MOCK_L1_BLOCK, - block.timestamp, - 1 ether, - encodedHookData - ); - - vm.etch(address(arbBridge), new bytes(0)); // this is a way to test that the arbBridge isn't called again - assertTrue(ism.verify(new bytes(0), encodedMessage)); - assertEq(address(testRecipient).balance, 1 ether); + function _encodeExternalDestinationBridgeCall( + address _from, + address _to, + uint256 _msgValue, + bytes32 _messageId + ) internal override returns (bytes memory) { + vm.deal(address(arbBridge), _msgValue); + return _encodeOutboxTx(_from, _to, _messageId, _msgValue); } - function test_verify_statefulAndOutbox() public { - deployAll(); - - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (messageId) - ); - - arbBridge.setL2ToL1Sender(address(hook)); - arbBridge.executeTransaction{value: 1 ether}( - new bytes32[](0), - MOCK_LEAF_INDEX, - address(hook), - address(ism), - MOCK_L2_BLOCK, - MOCK_L1_BLOCK, - block.timestamp, - 1 ether, - encodedHookData - ); - - bytes memory encodedOutboxTxMetadata = _encodeOutboxTx( - address(hook), - address(ism), - messageId, - 1 ether - ); - - vm.etch(address(arbBridge), new bytes(0)); // this is a way to test that the arbBridge isn't called again - assertTrue(ism.verify(encodedOutboxTxMetadata, encodedMessage)); - assertEq(address(testRecipient).balance, 1 ether); - } - - function test_verify_revertsWhen_noStatefulOrOutbox() public { - deployAll(); - - vm.expectRevert(); - ism.verify(new bytes(0), encodedMessage); - } - - function test_verify_revertsWhen_notAuthorizedHook() public { - deployAll(); - - bytes memory encodedOutboxTxMetadata = _encodeOutboxTx( - address(this), - address(ism), - messageId, - 0 - ); - - arbBridge.setL2ToL1Sender(address(this)); - - vm.expectRevert("ArbL2ToL1Ism: l2Sender != authorizedHook"); - ism.verify(encodedOutboxTxMetadata, encodedMessage); - } - - function test_verify_revertsWhen_invalidIsm() public { - deployAll(); - - bytes memory encodedOutboxTxMetadata = _encodeOutboxTx( - address(hook), - address(this), - messageId, - 0 - ); - - arbBridge.setL2ToL1Sender(address(hook)); - - vm.expectRevert(); // BridgeCallFailed() - ism.verify(encodedOutboxTxMetadata, encodedMessage); - } - - function test_verify_revertsWhen_incorrectMessageId() public { - deployAll(); - - bytes32 incorrectMessageId = keccak256("incorrect message id"); - - bytes memory encodedOutboxTxMetadata = _encodeOutboxTx( - address(hook), - address(ism), - incorrectMessageId, - 0 - ); - - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (incorrectMessageId) - ); - - arbBridge.setL2ToL1Sender(address(hook)); - - // through outbox call - vm.expectRevert("ArbL2ToL1Ism: invalid message id"); - ism.verify(encodedOutboxTxMetadata, encodedMessage); - - // through statefulVerify + function _externalBridgeDestinationCall( + bytes memory _encodedHookData, + uint256 _msgValue + ) internal override { + vm.deal(address(arbBridge), _msgValue); arbBridge.executeTransaction( new bytes32[](0), MOCK_LEAF_INDEX, @@ -273,16 +97,17 @@ contract ArbL2ToL1IsmTest is Test { MOCK_L2_BLOCK, MOCK_L1_BLOCK, block.timestamp, - 0, - encodedHookData + _msgValue, + _encodedHookData ); - - vm.etch(address(arbBridge), new bytes(0)); // to stop the outbox route - vm.expectRevert(); - assertFalse(ism.verify(new bytes(0), encodedMessage)); } - /* ============ helper functions ============ */ + function _setExternalOriginSender( + address _sender + ) internal override returns (bytes memory unauthorizedHookErrorMsg) { + arbBridge.setL2ToL1Sender(_sender); + return "ArbL2ToL1Ism: l2Sender != authorizedHook"; + } function _encodeOutboxTx( address _hook, @@ -309,17 +134,4 @@ contract ArbL2ToL1IsmTest is Test { encodedHookData ); } - - function _encodeTestMessage() internal view returns (bytes memory) { - return - MessageUtils.formatMessage( - HYPERLANE_VERSION, - uint32(0), - ARBITRUM_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - MAINNET_DOMAIN, - TypeCasts.addressToBytes32(address(testRecipient)), - testMessage - ); - } } diff --git a/solidity/test/isms/ERC5164ISM.t.sol b/solidity/test/isms/ERC5164ISM.t.sol index c94052e25..75c79fb82 100644 --- a/solidity/test/isms/ERC5164ISM.t.sol +++ b/solidity/test/isms/ERC5164ISM.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT or Apache-2.0 pragma solidity ^0.8.13; -import {Test} from "forge-std/Test.sol"; - import {LibBit} from "../../contracts/libs/LibBit.sol"; import {Message} from "../../contracts/libs/Message.sol"; import {MessageUtils} from "./IsmTestUtils.sol"; @@ -17,8 +15,9 @@ import {ERC5164Ism} from "../../contracts/isms/hook/ERC5164Ism.sol"; import {TestMailbox} from "../../contracts/test/TestMailbox.sol"; import {TestRecipient} from "../../contracts/test/TestRecipient.sol"; import {MockMessageDispatcher, MockMessageExecutor} from "../../contracts/mock/MockERC5164.sol"; +import {ExternalBridgeTest} from "./ExternalBridgeTest.sol"; -contract ERC5164IsmTest is Test { +contract ERC5164IsmTest is ExternalBridgeTest { using LibBit for uint256; using TypeCasts for address; using Message for bytes; @@ -27,23 +26,8 @@ contract ERC5164IsmTest is Test { IMessageDispatcher internal dispatcher; MockMessageExecutor internal executor; - ERC5164Hook internal hook; - ERC5164Ism internal ism; - TestMailbox internal originMailbox; - TestRecipient internal testRecipient; - - uint32 internal constant TEST1_DOMAIN = 1; - uint32 internal constant TEST2_DOMAIN = 2; - - uint8 internal constant VERSION = 0; - bytes internal testMessage = - abi.encodePacked("Hello from the other chain!"); address internal alice = address(0x1); - // req for most tests - bytes encodedMessage = _encodeTestMessage(0, address(testRecipient)); - bytes32 messageId = encodedMessage.id(); - event MessageDispatched( bytes32 indexed messageId, address indexed from, @@ -56,19 +40,19 @@ contract ERC5164IsmTest is Test { /// SETUP /// /////////////////////////////////////////////////////////////////// - function setUp() public { + function setUp() public override { dispatcher = new MockMessageDispatcher(); executor = new MockMessageExecutor(); - testRecipient = new TestRecipient(); - originMailbox = new TestMailbox(TEST1_DOMAIN); + originMailbox = new TestMailbox(ORIGIN_DOMAIN); ism = new ERC5164Ism(address(executor)); hook = new ERC5164Hook( address(originMailbox), - TEST2_DOMAIN, + DESTINATION_DOMAIN, address(ism).addressToBytes32(), address(dispatcher) ); ism.setAuthorizedHook(TypeCasts.addressToBytes32(address(hook))); + super.setUp(); } /////////////////////////////////////////////////////////////////// @@ -100,7 +84,7 @@ contract ERC5164IsmTest is Test { vm.expectRevert("AbstractMessageIdAuthHook: invalid ISM"); hook = new ERC5164Hook( address(originMailbox), - TEST2_DOMAIN, + DESTINATION_DOMAIN, address(0).addressToBytes32(), address(dispatcher) ); @@ -108,125 +92,83 @@ contract ERC5164IsmTest is Test { vm.expectRevert("ERC5164Hook: invalid dispatcher"); hook = new ERC5164Hook( address(originMailbox), - TEST2_DOMAIN, + DESTINATION_DOMAIN, address(ism).addressToBytes32(), address(0) ); } - function test_postDispatch() public { - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (messageId) - ); - originMailbox.updateLatestDispatchedId(messageId); + function testTypes() public view { + assertEq(hook.hookType(), uint8(IPostDispatchHook.Types.ID_AUTH_ISM)); + assertEq(ism.moduleType(), uint8(IInterchainSecurityModule.Types.NULL)); + } - // note: not checking for messageId since this is implementation dependent on each vendor + function _expectOriginExternalBridgeCall( + bytes memory _encodedHookData + ) internal override { vm.expectEmit(false, true, true, true, address(dispatcher)); emit MessageDispatched( messageId, address(hook), - TEST2_DOMAIN, + DESTINATION_DOMAIN, address(ism), - encodedHookData + _encodedHookData ); - - hook.postDispatch(bytes(""), encodedMessage); - } - - function testTypes() public { - assertEq(hook.hookType(), uint8(IPostDispatchHook.Types.ID_AUTH_ISM)); - assertEq(ism.moduleType(), uint8(IInterchainSecurityModule.Types.NULL)); } - function test_postDispatch_RevertWhen_ChainIDNotSupported() public { - encodedMessage = MessageUtils.formatMessage( - VERSION, - 0, - TEST1_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - 3, // unsupported chain id - TypeCasts.addressToBytes32(address(testRecipient)), - testMessage - ); - originMailbox.updateLatestDispatchedId(Message.id(encodedMessage)); - - vm.expectRevert( - "AbstractMessageIdAuthHook: invalid destination domain" - ); - hook.postDispatch(bytes(""), encodedMessage); + function test_verify_revertWhen_invalidMetadata() public override { + assertFalse(ism.verify(new bytes(0), encodedMessage)); } - function test_postDispatch_RevertWhen_msgValueNotAllowed() public payable { + function test_postDispatch_revertWhen_msgValueNotAllowed() public payable { originMailbox.updateLatestDispatchedId(messageId); vm.expectRevert("ERC5164Hook: no value allowed"); hook.postDispatch{value: 1}(bytes(""), encodedMessage); } - /* ============ ISM.verifyMessageId ============ */ - - function test_verifyMessageId() public { - vm.startPrank(address(executor)); - - ism.verifyMessageId(messageId); - assertTrue(ism.verifiedMessages(messageId).isBitSet(255)); - - vm.stopPrank(); - } - - function test_verifyMessageId_RevertWhen_NotAuthorized() public { - vm.startPrank(alice); + // override to omit direct external bridge call + function test_verify_revertsWhen_notAuthorizedHook() public override { + vm.prank(alice); - // needs to be called by the authorized hook contract on Ethereum vm.expectRevert( "AbstractMessageIdAuthorizedIsm: sender is not the hook" ); ism.verifyMessageId(messageId); - - vm.stopPrank(); + assertFalse(ism.isVerified(encodedMessage)); } - /* ============ ISM.verify ============ */ + // SKIP - duplicate of test_verify_revertWhen_invalidMetadata + function test_verify_revertsWhen_incorrectMessageId() public override {} - function test_verify() public { - vm.startPrank(address(executor)); + function test_verify_revertsWhen_invalidIsm() public override {} - ism.verifyMessageId(messageId); + // SKIP - 5164 ism does not support msg.value + function test_verify_msgValue_asyncCall() public override {} - bool verified = ism.verify(new bytes(0), encodedMessage); - assertTrue(verified); + function test_verify_msgValue_externalBridgeCall() public override {} - vm.stopPrank(); - } + function test_verify_valueAlreadyClaimed(uint256) public override {} - function test_verify_RevertWhen_InvalidMessage() public { - vm.startPrank(address(executor)); + /* ============ helper functions ============ */ + function _externalBridgeDestinationCall( + bytes memory _encodedHookData, + uint256 _msgValue + ) internal override { + vm.prank(address(executor)); ism.verifyMessageId(messageId); - - bytes memory invalidMessage = _encodeTestMessage(0, address(this)); - bool verified = ism.verify(new bytes(0), invalidMessage); - assertFalse(verified); - - vm.stopPrank(); } - /* ============ helper functions ============ */ - - function _encodeTestMessage( - uint32 _msgCount, - address _receipient - ) internal view returns (bytes memory) { - return - MessageUtils.formatMessage( - VERSION, - _msgCount, - TEST1_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - TEST2_DOMAIN, - TypeCasts.addressToBytes32(_receipient), - testMessage - ); + function _encodeExternalDestinationBridgeCall( + address _from, + address _to, + uint256 _msgValue, + bytes32 _messageId + ) internal override returns (bytes memory) { + if (_from == address(hook)) { + vm.prank(address(executor)); + ism.verifyMessageId{value: _msgValue}(messageId); + } } } diff --git a/solidity/test/isms/ExternalBridgeTest.sol b/solidity/test/isms/ExternalBridgeTest.sol new file mode 100644 index 000000000..8db043fcf --- /dev/null +++ b/solidity/test/isms/ExternalBridgeTest.sol @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: MIT or Apache-2.0 +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; +import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol"; +import {TestMailbox} from "../../contracts/test/TestMailbox.sol"; +import {Message} from "../../contracts/libs/Message.sol"; +import {MessageUtils} from "./IsmTestUtils.sol"; +import {TestRecipient} from "../../contracts/test/TestRecipient.sol"; +import {AbstractMessageIdAuthorizedIsm} from "../../contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol"; +import {AbstractMessageIdAuthHook} from "../../contracts/hooks/libs/AbstractMessageIdAuthHook.sol"; + +abstract contract ExternalBridgeTest is Test { + using TypeCasts for address; + using MessageUtils for bytes; + + uint8 internal constant HYPERLANE_VERSION = 1; + uint32 internal constant ORIGIN_DOMAIN = 1; + uint32 internal constant DESTINATION_DOMAIN = 2; + uint256 internal constant MAX_MSG_VALUE = 2 ** 255 - 1; + uint256 internal GAS_QUOTE; + + TestMailbox internal originMailbox; + TestRecipient internal testRecipient; + + AbstractMessageIdAuthHook internal hook; + AbstractMessageIdAuthorizedIsm internal ism; + + bytes internal testMessage = + abi.encodePacked("Hello from the other chain!"); + bytes internal testMetadata = + StandardHookMetadata.overrideRefundAddress(address(this)); + bytes internal encodedMessage; + bytes32 internal messageId; + + function setUp() public virtual { + testRecipient = new TestRecipient(); + encodedMessage = _encodeTestMessage(); + messageId = Message.id(encodedMessage); + } + + /* ============ hook.quoteDispatch ============ */ + + function test_quoteDispatch() public view { + assertEq(hook.quoteDispatch(testMetadata, encodedMessage), GAS_QUOTE); + } + + /* ============ Hook.postDispatch ============ */ + + function test_postDispatch() public { + bytes memory encodedHookData = _encodeHookData(messageId); + originMailbox.updateLatestDispatchedId(messageId); + _expectOriginExternalBridgeCall(encodedHookData); + + hook.postDispatch{value: GAS_QUOTE}(testMetadata, encodedMessage); + } + + function test_postDispatch_revertWhen_chainIDNotSupported() public { + bytes memory message = originMailbox.buildOutboundMessage( + 3, + TypeCasts.addressToBytes32(address(this)), + testMessage + ); + + originMailbox.updateLatestDispatchedId(Message.id(message)); + vm.expectRevert( + "AbstractMessageIdAuthHook: invalid destination domain" + ); + hook.postDispatch(testMetadata, message); + } + + function test_postDispatch_revertWhen_notLastDispatchedMessage() public { + vm.expectRevert( + "AbstractMessageIdAuthHook: message not latest dispatched" + ); + hook.postDispatch(testMetadata, encodedMessage); + } + + function test_postDispatch_revertWhen_tooMuchValue() public { + vm.deal(address(this), uint256(MAX_MSG_VALUE + 1)); + bytes memory excessValueMetadata = StandardHookMetadata + .overrideMsgValue(uint256(2 ** 255 + 1)); + + originMailbox.updateLatestDispatchedId(messageId); + vm.expectRevert( + "AbstractMessageIdAuthHook: msgValue must be less than 2 ** 255" + ); + hook.postDispatch(excessValueMetadata, encodedMessage); + } + + /* ============ ISM.verifyMessageId ============ */ + + function test_verifyMessageId_asyncCall() public { + bytes memory encodedHookData = abi.encodeCall( + AbstractMessageIdAuthorizedIsm.verifyMessageId, + (messageId) + ); + _externalBridgeDestinationCall(encodedHookData, 0); + + assertTrue(ism.isVerified(encodedMessage)); + } + + function test_verifyMessageId_externalBridgeCall() public virtual { + bytes memory externalCalldata = _encodeExternalDestinationBridgeCall( + address(hook), + address(ism), + 0, + messageId + ); + + assertTrue(ism.verify(externalCalldata, encodedMessage)); + assertTrue(ism.isVerified(encodedMessage)); + } + + /* ============ ISM.verify ============ */ + + function test_verify_revertWhen_invalidMetadata() public virtual { + vm.expectRevert(); + assertFalse(ism.verify(new bytes(0), encodedMessage)); + } + + function test_verify_msgValue_asyncCall() public virtual { + bytes memory encodedHookData = _encodeHookData(messageId); + _externalBridgeDestinationCall(encodedHookData, 1 ether); + + assertTrue(ism.verify(new bytes(0), encodedMessage)); + assertEq(address(testRecipient).balance, 1 ether); + } + + function test_verify_msgValue_externalBridgeCall() public virtual { + bytes memory externalCalldata = _encodeExternalDestinationBridgeCall( + address(hook), + address(ism), + 1 ether, + messageId + ); + ism.verify(externalCalldata, encodedMessage); + assertEq(address(testRecipient).balance, 1 ether); + } + + function test_verify_revertsWhen_invalidIsm() public virtual { + bytes memory externalCalldata = _encodeExternalDestinationBridgeCall( + address(hook), + address(this), + 0, + messageId + ); + + vm.expectRevert(); + assertFalse(ism.verify(externalCalldata, encodedMessage)); + } + + function test_verify_revertsWhen_notAuthorizedHook() public virtual { + bytes memory unauthorizedHookErrorMsg = _setExternalOriginSender( + address(this) + ); + + bytes memory externalCalldata = _encodeExternalDestinationBridgeCall( + address(this), + address(ism), + 0, + messageId + ); + + // external call + vm.expectRevert(unauthorizedHookErrorMsg); + assertFalse(ism.verify(externalCalldata, encodedMessage)); + + // async call vm.expectRevert(NotCrossChainCall.selector); + vm.expectRevert(); + _externalBridgeDestinationCall(externalCalldata, 0); + assertFalse(ism.isVerified(encodedMessage)); + } + + function test_verify_revertsWhen_incorrectMessageId() public virtual { + bytes32 incorrectMessageId = keccak256("incorrect message id"); + bytes memory externalCalldata = _encodeExternalDestinationBridgeCall( + address(hook), + address(ism), + 0, + incorrectMessageId + ); + + // external call + vm.expectRevert(); + assertFalse(ism.verify(externalCalldata, encodedMessage)); + + // async call - native bridges might have try catch block to prevent revert + try + this.externalBridgeDestinationCallWrapper( + _encodeHookData(incorrectMessageId), + 0 + ) + {} catch {} + assertFalse(ism.isVerified(testMessage)); + } + + /// forge-config: default.fuzz.runs = 10 + function test_verify_valueAlreadyClaimed(uint256 _msgValue) public virtual { + _msgValue = bound(_msgValue, 0, MAX_MSG_VALUE); + _externalBridgeDestinationCall(_encodeHookData(messageId), _msgValue); + + bool verified = ism.verify(new bytes(0), encodedMessage); + assertTrue(verified); + assertEq(address(ism).balance, 0); + assertEq(address(testRecipient).balance, _msgValue); + + // send more value to the ISM + vm.deal(address(ism), _msgValue); + + // verified still true + verified = ism.verify(new bytes(0), encodedMessage); + assertTrue(verified); + // value which was already sent + assertEq(address(ism).balance, _msgValue); + assertEq(address(testRecipient).balance, _msgValue); + } + + /* ============ helper functions ============ */ + + function _encodeTestMessage() internal view returns (bytes memory) { + return + originMailbox.buildOutboundMessage( + DESTINATION_DOMAIN, + TypeCasts.addressToBytes32(address(testRecipient)), + testMessage + ); + } + + function _encodeHookData( + bytes32 _messageId + ) internal pure returns (bytes memory) { + return + abi.encodeCall( + AbstractMessageIdAuthorizedIsm.verifyMessageId, + (_messageId) + ); + } + + // wrapper function needed for _externalBridgeDestinationCall because try catch cannot call an internal function + function externalBridgeDestinationCallWrapper( + bytes memory _encodedHookData, + uint256 _msgValue + ) external { + _externalBridgeDestinationCall(_encodedHookData, _msgValue); + } + + function _expectOriginExternalBridgeCall( + bytes memory _encodedHookData + ) internal virtual; + + function _externalBridgeDestinationCall( + bytes memory _encodedHookData, + uint256 _msgValue + ) internal virtual; + + function _encodeExternalDestinationBridgeCall( + address _from, + address _to, + uint256 _msgValue, + bytes32 _messageId + ) internal virtual returns (bytes memory); + + function _setExternalOriginSender( + address _sender + ) internal virtual returns (bytes memory) {} +} diff --git a/solidity/test/isms/OPL2ToL1Ism.t.sol b/solidity/test/isms/OPL2ToL1Ism.t.sol index 9466a7c93..d829e0211 100644 --- a/solidity/test/isms/OPL2ToL1Ism.t.sol +++ b/solidity/test/isms/OPL2ToL1Ism.t.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: MIT or Apache-2.0 pragma solidity ^0.8.13; -import {Test} from "forge-std/Test.sol"; - import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {Message} from "../../contracts/libs/Message.sol"; import {MessageUtils} from "./IsmTestUtils.sol"; @@ -15,57 +13,41 @@ import {TestRecipient} from "../../contracts/test/TestRecipient.sol"; import {MockOptimismMessenger, MockOptimismPortal} from "../../contracts/mock/MockOptimism.sol"; import {OPL2ToL1Hook} from "../../contracts/hooks/OPL2ToL1Hook.sol"; import {OPL2ToL1Ism} from "../../contracts/isms/hook/OPL2ToL1Ism.sol"; +import {ExternalBridgeTest} from "./ExternalBridgeTest.sol"; -contract OPL2ToL1IsmTest is Test { - uint8 internal constant HYPERLANE_VERSION = 1; - uint32 internal constant MAINNET_DOMAIN = 1; - uint32 internal constant OPTIMISM_DOMAIN = 10; - uint32 internal constant GAS_QUOTE = 120_000; - +contract OPL2ToL1IsmTest is ExternalBridgeTest { address internal constant L2_MESSENGER_ADDRESS = 0x4200000000000000000000000000000000000007; uint256 internal constant MOCK_NONCE = 0; - TestMailbox public l2Mailbox; - TestRecipient internal testRecipient; - bytes internal testMessage = - abi.encodePacked("Hello from the other chain!"); - bytes internal encodedMessage; - bytes internal testMetadata = - StandardHookMetadata.overrideRefundAddress(address(this)); - bytes32 internal messageId; - MockOptimismPortal internal portal; MockOptimismMessenger internal l1Messenger; - OPL2ToL1Hook public hook; - OPL2ToL1Ism public ism; /////////////////////////////////////////////////////////////////// /// SETUP /// /////////////////////////////////////////////////////////////////// - function setUp() public { + function setUp() public override { // Optimism messenger mock setup + GAS_QUOTE = 120_000; vm.etch( L2_MESSENGER_ADDRESS, address(new MockOptimismMessenger()).code ); - testRecipient = new TestRecipient(); - - encodedMessage = _encodeTestMessage(); - messageId = Message.id(encodedMessage); + deployAll(); + super.setUp(); } function deployHook() public { - l2Mailbox = new TestMailbox(OPTIMISM_DOMAIN); + originMailbox = new TestMailbox(ORIGIN_DOMAIN); hook = new OPL2ToL1Hook( - address(l2Mailbox), - MAINNET_DOMAIN, + address(originMailbox), + DESTINATION_DOMAIN, TypeCasts.addressToBytes32(address(ism)), L2_MESSENGER_ADDRESS, - GAS_QUOTE + uint32(GAS_QUOTE) ); } @@ -85,197 +67,58 @@ contract OPL2ToL1IsmTest is Test { ism.setAuthorizedHook(TypeCasts.addressToBytes32(address(hook))); } - function test_postDispatch() public { - deployAll(); - - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (messageId) - ); - - l2Mailbox.updateLatestDispatchedId(messageId); + /* ============ helper functions ============ */ + function _expectOriginExternalBridgeCall( + bytes memory _encodedHookData + ) internal override { vm.expectCall( L2_MESSENGER_ADDRESS, abi.encodeCall( ICrossDomainMessenger.sendMessage, - (address(ism), encodedHookData, GAS_QUOTE) + (address(ism), _encodedHookData, uint32(GAS_QUOTE)) ) ); - - hook.postDispatch{value: GAS_QUOTE}(testMetadata, encodedMessage); - } - - function testFork_postDispatch_revertWhen_chainIDNotSupported() public { - deployAll(); - - bytes memory message = MessageUtils.formatMessage( - 0, - uint32(0), - OPTIMISM_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - 2, // wrong domain - TypeCasts.addressToBytes32(address(testRecipient)), - testMessage - ); - - l2Mailbox.updateLatestDispatchedId(Message.id(message)); - vm.expectRevert( - "AbstractMessageIdAuthHook: invalid destination domain" - ); - hook.postDispatch(testMetadata, message); - } - - function test_postDispatch_revertWhen_notLastDispatchedMessage() public { - deployAll(); - - vm.expectRevert( - "AbstractMessageIdAuthHook: message not latest dispatched" - ); - hook.postDispatch(testMetadata, encodedMessage); - } - - function test_verify_directWithdrawalCall() public { - deployAll(); - - bytes memory encodedWithdrawalTx = _encodeFinalizeWithdrawalTx( - address(ism), - 0, - messageId - ); - - assertTrue(ism.verify(encodedWithdrawalTx, encodedMessage)); - } - - function test_verify_directWithdrawalCall_revertsWhen_invalidSender() - public - { - deployAll(); - l1Messenger.setXDomainMessageSender(address(this)); - - bytes memory encodedWithdrawalTx = _encodeFinalizeWithdrawalTx( - address(ism), - 0, - messageId - ); - - vm.expectRevert(); // evmRevert in MockOptimismPortal - ism.verify(encodedWithdrawalTx, encodedMessage); - } - - function test_verify_statefulVerify() public { - deployAll(); - - vm.deal(address(portal), 1 ether); - IOptimismPortal.WithdrawalTransaction - memory withdrawal = IOptimismPortal.WithdrawalTransaction({ - nonce: MOCK_NONCE, - sender: L2_MESSENGER_ADDRESS, - target: address(l1Messenger), - value: 1 ether, - gasLimit: uint256(GAS_QUOTE), - data: _encodeMessengerCalldata(address(ism), 1 ether, messageId) - }); - portal.finalizeWithdrawalTransaction(withdrawal); - - vm.etch(address(portal), new bytes(0)); // this is a way to test that the portal isn't called again - assertTrue(ism.verify(new bytes(0), encodedMessage)); - assertEq(address(testRecipient).balance, 1 ether); // testing msg.value - } - - function test_verify_statefulAndDirectWithdrawal() public { - deployAll(); - - IOptimismPortal.WithdrawalTransaction - memory withdrawal = IOptimismPortal.WithdrawalTransaction({ - nonce: MOCK_NONCE, - sender: L2_MESSENGER_ADDRESS, - target: address(l1Messenger), - value: 0, - gasLimit: uint256(GAS_QUOTE), - data: _encodeMessengerCalldata(address(ism), 0, messageId) - }); - portal.finalizeWithdrawalTransaction(withdrawal); - - bytes memory encodedWithdrawalTx = _encodeFinalizeWithdrawalTx( - address(ism), - 0, - messageId - ); - - vm.etch(address(portal), new bytes(0)); // this is a way to test that the portal isn't called again - assertTrue(ism.verify(encodedWithdrawalTx, encodedMessage)); } - function test_verify_revertsWhen_noStatefulAndDirectWithdrawal() public { - deployAll(); - - vm.expectRevert(); - ism.verify(new bytes(0), encodedMessage); + function _encodeExternalDestinationBridgeCall( + address, + /*_from*/ + address _to, + uint256 _msgValue, + bytes32 _messageId + ) internal override returns (bytes memory) { + vm.deal(address(portal), _msgValue); + return _encodeFinalizeWithdrawalTx(_to, _msgValue, _messageId); } - function test_verify_revertsWhen_invalidIsm() public { - deployAll(); - - bytes memory encodedWithdrawalTx = _encodeFinalizeWithdrawalTx( - address(this), - 0, - messageId - ); - - vm.expectRevert(); // evmRevert in MockOptimismPortal - ism.verify(encodedWithdrawalTx, encodedMessage); + function _setExternalOriginSender( + address _sender + ) internal override returns (bytes memory) { + l1Messenger.setXDomainMessageSender(_sender); + return "AbstractMessageIdAuthorizedIsm: sender is not the hook"; } - function test_verify_revertsWhen_incorrectMessageId() public { - deployAll(); - - bytes32 incorrectMessageId = keccak256("incorrect message id"); - - bytes memory encodedWithdrawalTx = _encodeFinalizeWithdrawalTx( - address(this), - 0, - incorrectMessageId - ); - - // through portal call - vm.expectRevert("OPL2ToL1Ism: invalid message id"); - ism.verify(encodedWithdrawalTx, encodedMessage); - - // through statefulVerify + function _externalBridgeDestinationCall( + bytes memory, + /*_encodedHookData*/ + uint256 _msgValue + ) internal override { + vm.deal(address(portal), _msgValue); IOptimismPortal.WithdrawalTransaction memory withdrawal = IOptimismPortal.WithdrawalTransaction({ nonce: MOCK_NONCE, sender: L2_MESSENGER_ADDRESS, target: address(l1Messenger), - value: 0, + value: _msgValue, gasLimit: uint256(GAS_QUOTE), data: _encodeMessengerCalldata( address(ism), - 0, - incorrectMessageId + _msgValue, + messageId ) }); portal.finalizeWithdrawalTransaction(withdrawal); - - vm.etch(address(portal), new bytes(0)); // to stop the portal route - vm.expectRevert(); // evmRevert() - assertFalse(ism.verify(new bytes(0), encodedMessage)); - } - - /* ============ helper functions ============ */ - - function _encodeTestMessage() internal view returns (bytes memory) { - return - MessageUtils.formatMessage( - HYPERLANE_VERSION, - uint32(0), - OPTIMISM_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - MAINNET_DOMAIN, - TypeCasts.addressToBytes32(address(testRecipient)), - testMessage - ); } function _encodeMessengerCalldata( @@ -283,10 +126,7 @@ contract OPL2ToL1IsmTest is Test { uint256 _value, bytes32 _messageId ) internal view returns (bytes memory) { - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (_messageId) - ); + bytes memory encodedHookData = _encodeHookData(_messageId); return abi.encodeCall( diff --git a/solidity/test/isms/OPStackIsm.t.sol b/solidity/test/isms/OPStackIsm.t.sol index 9862717ad..45c818ec3 100644 --- a/solidity/test/isms/OPStackIsm.t.sol +++ b/solidity/test/isms/OPStackIsm.t.sol @@ -1,13 +1,14 @@ // SPDX-License-Identifier: MIT or Apache-2.0 pragma solidity ^0.8.13; -import {Test} from "forge-std/Test.sol"; +import "forge-std/console.sol"; import {LibBit} from "../../contracts/libs/LibBit.sol"; import {TypeCasts} from "../../contracts/libs/TypeCasts.sol"; import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol"; import {StandardHookMetadata} from "../../contracts/hooks/libs/StandardHookMetadata.sol"; import {AbstractMessageIdAuthorizedIsm} from "../../contracts/isms/hook/AbstractMessageIdAuthorizedIsm.sol"; +import {MockOptimismMessenger} from "../../contracts/mock/MockOptimism.sol"; import {TestMailbox} from "../../contracts/test/TestMailbox.sol"; import {Message} from "../../contracts/libs/Message.sol"; import {MessageUtils} from "./IsmTestUtils.sol"; @@ -18,16 +19,14 @@ import {TestRecipient} from "../../contracts/test/TestRecipient.sol"; import {NotCrossChainCall} from "@openzeppelin/contracts/crosschain/errors.sol"; import {AddressAliasHelper} from "@eth-optimism/contracts/standards/AddressAliasHelper.sol"; -import {ICrossDomainMessenger, IL2CrossDomainMessenger} from "../../contracts/interfaces/optimism/ICrossDomainMessenger.sol"; +import {ICrossDomainMessenger} from "../../contracts/interfaces/optimism/ICrossDomainMessenger.sol"; +import {ExternalBridgeTest} from "./ExternalBridgeTest.sol"; -contract OPStackIsmTest is Test { +contract OPStackIsmTest is ExternalBridgeTest { using LibBit for uint256; using TypeCasts for address; using MessageUtils for bytes; - uint256 internal mainnetFork; - uint256 internal optimismFork; - address internal constant L1_MESSENGER_ADDRESS = 0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1; address internal constant L1_CANNONICAL_CHAIN = @@ -36,36 +35,12 @@ contract OPStackIsmTest is Test { 0x4200000000000000000000000000000000000007; uint8 internal constant OPTIMISM_VERSION = 0; - uint8 internal constant HYPERLANE_VERSION = 1; uint256 internal constant DEFAULT_GAS_LIMIT = 1_920_000; address internal alice = address(0x1); - ICrossDomainMessenger internal l1Messenger; - IL2CrossDomainMessenger internal l2Messenger; - TestMailbox internal l1Mailbox; - OPStackIsm internal opISM; - OPStackHook internal opHook; - - TestRecipient internal testRecipient; - bytes internal testMessage = - abi.encodePacked("Hello from the other chain!"); - bytes internal testMetadata = - StandardHookMetadata.overrideRefundAddress(address(this)); - - bytes internal encodedMessage; - bytes32 internal messageId; - - uint32 internal constant MAINNET_DOMAIN = 1; - uint32 internal constant OPTIMISM_DOMAIN = 10; - - event SentMessage( - address indexed target, - address sender, - bytes message, - uint256 messageNonce, - uint256 gasLimit - ); + MockOptimismMessenger internal l1Messenger; + MockOptimismMessenger internal l2Messenger; event RelayedMessage(bytes32 indexed msgHash); @@ -73,469 +48,145 @@ contract OPStackIsmTest is Test { event ReceivedMessage(bytes32 indexed messageId); - function setUp() public { - // block numbers to fork from, chain data is cached to ../../forge-cache/ - mainnetFork = vm.createFork(vm.rpcUrl("mainnet"), 18_992_500); - optimismFork = vm.createFork(vm.rpcUrl("optimism"), 114_696_811); + function setUp() public override { + GAS_QUOTE = 0; - testRecipient = new TestRecipient(); + vm.etch( + L1_MESSENGER_ADDRESS, + address(new MockOptimismMessenger()).code + ); + vm.etch( + L2_MESSENGER_ADDRESS, + address(new MockOptimismMessenger()).code + ); + l1Messenger = MockOptimismMessenger(L1_MESSENGER_ADDRESS); + l2Messenger = MockOptimismMessenger(L2_MESSENGER_ADDRESS); - encodedMessage = _encodeTestMessage(); - messageId = Message.id(encodedMessage); + deployAll(); + super.setUp(); } /////////////////////////////////////////////////////////////////// /// SETUP /// /////////////////////////////////////////////////////////////////// - function deployOptimismHook() public { - vm.selectFork(mainnetFork); - - l1Messenger = ICrossDomainMessenger(L1_MESSENGER_ADDRESS); - l1Mailbox = new TestMailbox(MAINNET_DOMAIN); - - opHook = new OPStackHook( - address(l1Mailbox), - OPTIMISM_DOMAIN, - TypeCasts.addressToBytes32(address(opISM)), + function deployHook() public { + originMailbox = new TestMailbox(ORIGIN_DOMAIN); + hook = new OPStackHook( + address(originMailbox), + DESTINATION_DOMAIN, + TypeCasts.addressToBytes32(address(ism)), L1_MESSENGER_ADDRESS ); - - vm.makePersistent(address(opHook)); } - function deployOPStackIsm() public { - vm.selectFork(optimismFork); - - l2Messenger = IL2CrossDomainMessenger(L2_MESSENGER_ADDRESS); - opISM = new OPStackIsm(L2_MESSENGER_ADDRESS); - - vm.makePersistent(address(opISM)); + function deployIsm() public { + ism = new OPStackIsm(L2_MESSENGER_ADDRESS); } function deployAll() public { - deployOPStackIsm(); - deployOptimismHook(); + deployIsm(); + deployHook(); - vm.selectFork(optimismFork); - - opISM.setAuthorizedHook(TypeCasts.addressToBytes32(address(opHook))); - // for sending value - vm.deal( - AddressAliasHelper.applyL1ToL2Alias(L1_MESSENGER_ADDRESS), - 2 ** 255 - ); + ism.setAuthorizedHook(TypeCasts.addressToBytes32(address(hook))); + l2Messenger.setXDomainMessageSender(address(hook)); } - /////////////////////////////////////////////////////////////////// - /// FORK TESTS /// - /////////////////////////////////////////////////////////////////// - - /* ============ hook.quoteDispatch ============ */ - - function testFork_quoteDispatch() public { - deployAll(); - - vm.selectFork(mainnetFork); - - assertEq(opHook.quoteDispatch(testMetadata, encodedMessage), 0); + function test_verify_revertWhen_invalidMetadata() public override { + assertFalse(ism.verify(new bytes(0), encodedMessage)); } - /* ============ hook.postDispatch ============ */ + function test_verify_revertsWhen_incorrectMessageId() public override { + bytes32 incorrectMessageId = keccak256("incorrect message id"); - function testFork_postDispatch() public { - deployAll(); - - vm.selectFork(mainnetFork); - - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (messageId) - ); - - uint40 testNonce = 123; - l1Mailbox.updateLatestDispatchedId(messageId); - - vm.expectEmit(true, true, true, false, L1_MESSENGER_ADDRESS); - emit SentMessage( - address(opISM), - address(opHook), - encodedHookData, - testNonce, - DEFAULT_GAS_LIMIT - ); - opHook.postDispatch(testMetadata, encodedMessage); + _externalBridgeDestinationCall(_encodeHookData(incorrectMessageId), 0); + assertFalse(ism.isVerified(testMessage)); } - function testFork_postDispatch_RevertWhen_ChainIDNotSupported() public { - deployAll(); - - vm.selectFork(mainnetFork); - - bytes memory message = MessageUtils.formatMessage( - OPTIMISM_VERSION, - uint32(0), - MAINNET_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - 11, // wrong domain - TypeCasts.addressToBytes32(address(testRecipient)), - testMessage - ); + /* ============ helper functions ============ */ - l1Mailbox.updateLatestDispatchedId(Message.id(message)); - vm.expectRevert( - "AbstractMessageIdAuthHook: invalid destination domain" + function _expectOriginExternalBridgeCall( + bytes memory _encodedHookData + ) internal override { + vm.expectCall( + L1_MESSENGER_ADDRESS, + abi.encodeCall( + ICrossDomainMessenger.sendMessage, + (address(ism), _encodedHookData, uint32(DEFAULT_GAS_LIMIT)) + ) ); - opHook.postDispatch(testMetadata, message); } - function testFork_postDispatch_RevertWhen_TooMuchValue() public { - deployAll(); - - vm.selectFork(mainnetFork); - - vm.deal(address(this), uint256(2 ** 255 + 1)); - bytes memory excessValueMetadata = StandardHookMetadata - .overrideMsgValue(uint256(2 ** 255 + 1)); - - l1Mailbox.updateLatestDispatchedId(messageId); - vm.expectRevert( - "AbstractMessageIdAuthHook: msgValue must be less than 2 ** 255" + function _externalBridgeDestinationCall( + bytes memory _encodedHookData, + uint256 _msgValue + ) internal override { + vm.deal(L2_MESSENGER_ADDRESS, _msgValue); + l2Messenger.relayMessage( + 0, + address(hook), + address(ism), + _msgValue, + uint32(GAS_QUOTE), + _encodedHookData ); - opHook.postDispatch(excessValueMetadata, encodedMessage); } - function testFork_postDispatch_RevertWhen_NotLastDispatchedMessage() - public - { - deployAll(); - - vm.selectFork(mainnetFork); - - vm.expectRevert( - "AbstractMessageIdAuthHook: message not latest dispatched" - ); - opHook.postDispatch(testMetadata, encodedMessage); + function _encodeExternalDestinationBridgeCall( + address _from, + address _to, + uint256 _msgValue, + bytes32 _messageId + ) internal pure override returns (bytes memory) { + return new bytes(0); } - /* ============ ISM.verifyMessageId ============ */ - - function testFork_verifyMessageId() public { - deployAll(); - - vm.selectFork(optimismFork); - - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (messageId) - ); - - (uint240 nonce, uint16 version) = decodeVersionedNonce( - l2Messenger.messageNonce() - ); - uint256 versionedNonce = encodeVersionedNonce(nonce + 1, version); + // SKIP - no external bridge call + function test_verifyMessageId_externalBridgeCall() public override {} - bytes32 versionedHash = hashCrossDomainMessageV1( - versionedNonce, - address(opHook), - address(opISM), - 0, - DEFAULT_GAS_LIMIT, - encodedHookData - ); - - vm.startPrank( - AddressAliasHelper.applyL1ToL2Alias(L1_MESSENGER_ADDRESS) - ); + function test_verify_msgValue_externalBridgeCall() public override {} - vm.expectEmit(true, false, false, false, address(opISM)); - emit ReceivedMessage(messageId); + function test_verify_revertsWhen_invalidIsm() public override {} - vm.expectEmit(true, false, false, false, L2_MESSENGER_ADDRESS); - emit RelayedMessage(versionedHash); - - l2Messenger.relayMessage( - versionedNonce, - address(opHook), - address(opISM), - 0, - DEFAULT_GAS_LIMIT, - encodedHookData - ); - - assertTrue(opISM.verifiedMessages(messageId).isBitSet(255)); - vm.stopPrank(); - } - - function testFork_verifyMessageId_RevertWhen_NotAuthorized() public { - deployAll(); - - vm.selectFork(optimismFork); + /* ============ ISM.verifyMessageId ============ */ + function test_verify_revertsWhen_notAuthorizedHook() public override { // needs to be called by the canonical messenger on Optimism vm.expectRevert(NotCrossChainCall.selector); - opISM.verifyMessageId(messageId); - - // set the xDomainMessageSender storage slot as alice - bytes32 key = bytes32(uint256(204)); - bytes32 value = TypeCasts.addressToBytes32(alice); - vm.store(address(l2Messenger), key, value); + ism.verifyMessageId(messageId); vm.startPrank(L2_MESSENGER_ADDRESS); + _setExternalOriginSender(address(this)); // needs to be called by the authorized hook contract on Ethereum vm.expectRevert( "AbstractMessageIdAuthorizedIsm: sender is not the hook" ); - opISM.verifyMessageId(messageId); + ism.verifyMessageId(messageId); } - /* ============ ISM.verify ============ */ - - function testFork_verify() public { - deployAll(); - - vm.selectFork(optimismFork); - - orchestrateRelayMessage(0, messageId); - - bool verified = opISM.verify(new bytes(0), encodedMessage); - assertTrue(verified); + function _setExternalOriginSender( + address _sender + ) internal override returns (bytes memory) { + l2Messenger.setXDomainMessageSender(_sender); + return ""; } - /// forge-config: default.fuzz.runs = 10 - function testFork_verify_WithValue(uint256 _msgValue) public { - _msgValue = bound(_msgValue, 0, 2 ** 254); - deployAll(); - - orchestrateRelayMessage(_msgValue, messageId); - - bool verified = opISM.verify(new bytes(0), encodedMessage); - assertTrue(verified); - - assertEq(address(opISM).balance, 0); - assertEq(address(testRecipient).balance, _msgValue); - } - - /// forge-config: default.fuzz.runs = 10 - function testFork_verify_valueAlreadyClaimed(uint256 _msgValue) public { - _msgValue = bound(_msgValue, 0, 2 ** 254); - deployAll(); - - orchestrateRelayMessage(_msgValue, messageId); - - bool verified = opISM.verify(new bytes(0), encodedMessage); - assertTrue(verified); - - assertEq(address(opISM).balance, 0); - assertEq(address(testRecipient).balance, _msgValue); - - // send more value to the ISM - vm.deal(address(opISM), _msgValue); - - verified = opISM.verify(new bytes(0), encodedMessage); - // verified still true - assertTrue(verified); - - assertEq(address(opISM).balance, _msgValue); - // value which was already sent - assertEq(address(testRecipient).balance, _msgValue); - } - - function testFork_verify_tooMuchValue() public { - deployAll(); + /* ============ ISM.verify ============ */ + function test_verify_tooMuchValue() public { uint256 _msgValue = 2 ** 255 + 1; - vm.expectEmit(false, false, false, false, address(l2Messenger)); - emit FailedRelayedMessage(messageId); - orchestrateRelayMessage(_msgValue, messageId); - - bool verified = opISM.verify(new bytes(0), encodedMessage); - assertFalse(verified); - - assertEq(address(opISM).balance, 0); - assertEq(address(testRecipient).balance, 0); - } - - // sending over invalid message - function testFork_verify_RevertWhen_HyperlaneInvalidMessage() public { - deployAll(); - - orchestrateRelayMessage(0, messageId); - - bytes memory invalidMessage = MessageUtils.formatMessage( - HYPERLANE_VERSION, - uint8(0), - MAINNET_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - OPTIMISM_DOMAIN, - TypeCasts.addressToBytes32(address(this)), // wrong recipient - testMessage + vm.expectRevert( + "AbstractMessageIdAuthorizedIsm: msg.value must be less than 2^255" ); - bool verified = opISM.verify(new bytes(0), invalidMessage); - assertFalse(verified); - } + _externalBridgeDestinationCall(_encodeHookData(messageId), _msgValue); - // invalid messageID in postDispatch - function testFork_verify_RevertWhen_InvalidOptimismMessageID() public { - deployAll(); - vm.selectFork(optimismFork); - - bytes memory invalidMessage = MessageUtils.formatMessage( - HYPERLANE_VERSION, - uint8(0), - MAINNET_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - OPTIMISM_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - testMessage - ); - bytes32 _messageId = Message.id(invalidMessage); - orchestrateRelayMessage(0, _messageId); + assertFalse(ism.isVerified(encodedMessage)); - bool verified = opISM.verify(new bytes(0), encodedMessage); - assertFalse(verified); + assertEq(address(ism).balance, 0); + assertEq(address(testRecipient).balance, 0); } /* ============ helper functions ============ */ - - function _encodeTestMessage() internal view returns (bytes memory) { - return - MessageUtils.formatMessage( - HYPERLANE_VERSION, - uint32(0), - MAINNET_DOMAIN, - TypeCasts.addressToBytes32(address(this)), - OPTIMISM_DOMAIN, - TypeCasts.addressToBytes32(address(testRecipient)), - testMessage - ); - } - - /// @dev from eth-optimism/contracts-bedrock/contracts/libraries/Hashing.sol - /// @notice Hashes a cross domain message based on the V1 (current) encoding. - /// @param _nonce Message nonce. - /// @param _sender Address of the sender of the message. - /// @param _target Address of the target of the message. - /// @param _value ETH value to send to the target. - /// @param _gasLimit Gas limit to use for the message. - /// @param _data Data to send with the message. - /// @return Hashed cross domain message. - function hashCrossDomainMessageV1( - uint256 _nonce, - address _sender, - address _target, - uint256 _value, - uint256 _gasLimit, - bytes memory _data - ) internal pure returns (bytes32) { - return - keccak256( - encodeCrossDomainMessageV1( - _nonce, - _sender, - _target, - _value, - _gasLimit, - _data - ) - ); - } - - /// @dev from eth-optimism/contracts-bedrock/contracts/libraries/Encoding.sol - /// @notice Encodes a cross domain message based on the V1 (current) encoding. - /// @param _nonce Message nonce. - /// @param _sender Address of the sender of the message. - /// @param _target Address of the target of the message. - /// @param _value ETH value to send to the target. - /// @param _gasLimit Gas limit to use for the message. - /// @param _data Data to send with the message. - /// @return Encoded cross domain message. - function encodeCrossDomainMessageV1( - uint256 _nonce, - address _sender, - address _target, - uint256 _value, - uint256 _gasLimit, - bytes memory _data - ) internal pure returns (bytes memory) { - return - abi.encodeWithSignature( - "relayMessage(uint256,address,address,uint256,uint256,bytes)", - _nonce, - _sender, - _target, - _value, - _gasLimit, - _data - ); - } - - /// @dev from eth-optimism/contracts-bedrock/contracts/libraries/Encoding.sol - /// @notice Adds a version number into the first two bytes of a message nonce. - /// @param _nonce Message nonce to encode into. - /// @param _version Version number to encode into the message nonce. - /// @return Message nonce with version encoded into the first two bytes. - function encodeVersionedNonce( - uint240 _nonce, - uint16 _version - ) internal pure returns (uint256) { - uint256 nonce; - assembly { - nonce := or(shl(240, _version), _nonce) - } - return nonce; - } - - /// @dev from eth-optimism/contracts-bedrock/contracts/libraries/Encoding.sol - /// @notice Pulls the version out of a version-encoded nonce. - /// @param _nonce Message nonce with version encoded into the first two bytes. - /// @return Nonce without encoded version. - /// @return Version of the message. - function decodeVersionedNonce( - uint256 _nonce - ) internal pure returns (uint240, uint16) { - uint240 nonce; - uint16 version; - assembly { - nonce := and( - _nonce, - 0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff - ) - version := shr(240, _nonce) - } - return (nonce, version); - } - - function orchestrateRelayMessage( - uint256 _msgValue, - bytes32 _messageId - ) internal { - vm.selectFork(optimismFork); - - bytes memory encodedHookData = abi.encodeCall( - AbstractMessageIdAuthorizedIsm.verifyMessageId, - (_messageId) - ); - - (uint240 nonce, uint16 version) = decodeVersionedNonce( - l2Messenger.messageNonce() - ); - uint256 versionedNonce = encodeVersionedNonce(nonce + 1, version); - - vm.deal( - AddressAliasHelper.applyL1ToL2Alias(L1_MESSENGER_ADDRESS), - 2 ** 256 - 1 - ); - vm.prank(AddressAliasHelper.applyL1ToL2Alias(L1_MESSENGER_ADDRESS)); - l2Messenger.relayMessage{value: _msgValue}( - versionedNonce, - address(opHook), - address(opISM), - _msgValue, - DEFAULT_GAS_LIMIT, - encodedHookData - ); - } }