diff --git a/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol b/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol index 0d888652308..737b146258b 100644 --- a/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol @@ -45,8 +45,14 @@ interface IL2ToL2CrossDomainMessenger { /// @param messageNonce Nonce associated with the messsage sent /// @param sender Address initiating this message call /// @param message Message payload to call target with. + /// @param originContext Context of the message. event SentMessage( - uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message + uint256 indexed destination, + address indexed target, + uint256 indexed messageNonce, + address sender, + bytes message, + bytes originContext ); /// @notice Emitted whenever a message is successfully relayed on this chain. @@ -58,6 +64,10 @@ interface IL2ToL2CrossDomainMessenger { uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash, bytes32 returnDataHash ); + /// @notice Retrieves the current origin context encoding version. + function ORIGIN_CONTEXT_ENCODING_VERSION() external view returns (uint8); + + /// @notice Semantic version. function version() external view returns (string memory); /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only @@ -85,6 +95,10 @@ interface IL2ToL2CrossDomainMessenger { /// @return source_ Chain ID of the source of the current cross domain message. function crossDomainMessageSource() external view returns (uint256 source_); + /// @notice Retrieves the origin context of the current cross domain message. If not entered, reverts. + /// @return context_ Origin context of the current cross domain message. + function crossDomainMessageOriginContext() external view returns (bytes memory context_); + /// @notice Retrieves the context of the current cross domain message. If not entered, reverts. /// @return sender_ Address of the sender of the current cross domain message. /// @return source_ Chain ID of the source of the current cross domain message. @@ -116,13 +130,15 @@ interface IL2ToL2CrossDomainMessenger { /// @param _sender Address that sent the message /// @param _target Target contract or wallet address. /// @param _message Message payload to call target with. + /// @param _originContext Context of the message. /// @return messageHash_ The hash of the message being re-sent. function resendMessage( uint256 _destination, uint256 _nonce, address _sender, address _target, - bytes calldata _message + bytes calldata _message, + bytes calldata _originContext ) external returns (bytes32 messageHash_); diff --git a/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json b/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json index c19fa9a2594..c091f9f464f 100644 --- a/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json +++ b/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json @@ -1,4 +1,17 @@ [ + { + "inputs": [], + "name": "ORIGIN_CONTEXT_ENCODING_VERSION", + "outputs": [ + { + "internalType": "uint8", + "name": "", + "type": "uint8" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [], "name": "crossDomainMessageContext", @@ -12,6 +25,24 @@ "internalType": "uint256", "name": "source_", "type": "uint256" + }, + { + "internalType": "bytes", + "name": "originContext_", + "type": "bytes" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "crossDomainMessageOriginContext", + "outputs": [ + { + "internalType": "bytes", + "name": "context_", + "type": "bytes" } ], "stateMutability": "view", @@ -146,6 +177,11 @@ "internalType": "bytes", "name": "_message", "type": "bytes" + }, + { + "internalType": "bytes", + "name": "_originContext", + "type": "bytes" } ], "name": "resendMessage", @@ -302,6 +338,12 @@ "internalType": "bytes", "name": "message", "type": "bytes" + }, + { + "indexed": false, + "internalType": "bytes", + "name": "originContext", + "type": "bytes" } ], "name": "SentMessage", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index 2f86528c917..89acb14e60e 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -88,8 +88,8 @@ "sourceCodeHash": "0x83396cbd12a0c5c02e09a4d99c4b62ab4e9d9eb762745e63283e2e818a78a39c" }, "src/L2/L2ToL2CrossDomainMessenger.sol:L2ToL2CrossDomainMessenger": { - "initCodeHash": "0xd997db3cb7c84c8c851719bcf561ef35eb660262b2f4093dd6a3d86c5426240f", - "sourceCodeHash": "0x33393a279f32ecd6641adf267b59413d86595357b4f50b8a09afb62b8fc7639c" + "initCodeHash": "0x909b075c03430827c7ce3bb69f688636b03ff2db3ca919a7789342fc449f79ea", + "sourceCodeHash": "0xde1b23bc36ea30461ad73e0d094d608d57bb61ad552e92fc6fadf212bc486055" }, "src/L2/OperatorFeeVault.sol:OperatorFeeVault": { "initCodeHash": "0x3d8c0d7736e8767f2f797da1c20c5fe30bd7f48a4cf75f376290481ad7c0f91f", diff --git a/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol index f436c629117..2c6620a9d37 100644 --- a/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol @@ -38,6 +38,22 @@ error ReentrantCall(); /// @notice Thrown when the provided message parameters do not match any hash of a previously sent message. error InvalidMessage(); +/// @notice Decoded payload of a SentMessage event. +/// @param destination Chain ID of the destination chain. +/// @param target Target contract or wallet address. +/// @param nonce Nonce associated with the message sent +/// @param sender Address initiating this message call +/// @param message Message payload to call target with. +/// @param originContext Context of the message +struct DecodedPayload { + uint256 destination; + address target; + uint256 nonce; + address sender; + bytes message; + bytes originContext; +} + /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000023 /// @title L2ToL2CrossDomainMessenger @@ -45,6 +61,9 @@ error InvalidMessage(); /// features necessary for secure transfers ERC20 tokens between L2 chains. Messages sent through the /// L2ToL2CrossDomainMessenger on the source chain receive both replay protection as well as domain binding. contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { + /// @notice Current origin context encoding version identifier. + uint8 public constant ORIGIN_CONTEXT_ENCODING_VERSION = 1; + /// @notice Storage slot for the sender of the current cross domain message. /// Equal to bytes32(uint256(keccak256("l2tol2crossdomainmessenger.sender")) - 1) bytes32 internal constant CROSS_DOMAIN_MESSAGE_SENDER_SLOT = @@ -55,17 +74,22 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { bytes32 internal constant CROSS_DOMAIN_MESSAGE_SOURCE_SLOT = 0x711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee7; - /// @notice Event selector for the SentMessage event. Will be removed in favor of reading - // the `selector` property directly once crytic/slithe/#2566 is fixed. - bytes32 internal constant SENT_MESSAGE_EVENT_SELECTOR = - 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320; + /// @notice First storage slot for the context of the current cross domain message. + /// Equal to bytes32(uint256(keccak256("l2tol2crossdomainmessenger.context.version")) - 1) + bytes32 internal constant ORIGIN_CONTEXT_VERSION_SLOT = + 0xaf29438f3d49a80862278626ba8ccaf84aebc36dcd6f78f3e9101efa0aaef129; + + /// @notice Second storage slot for the context of the current cross domain message. + /// Equal to bytes32(uint256(keccak256("l2tol2crossdomainmessenger.context.messagePayloadHash")) - 1) + bytes32 internal constant ORIGIN_CONTEXT_MESSAGE_PAYLOAD_HASH_SLOT = + 0x1599376b7dd96feafb3dee69530b7c0f4ac6e0447ea06adb0f7c431e59c5547c; /// @notice Current message version identifier. uint16 public constant messageVersion = uint16(0); /// @notice Semantic version. - /// @custom:semver 1.2.0 - string public constant version = "1.2.0"; + /// @custom:semver 1.3.0 + string public constant version = "1.3.0"; /// @notice Mapping of message hashes to boolean receipt values. Note that a message will only be present in this /// mapping if it has successfully been relayed on this chain, and can therefore not be relayed again. @@ -86,8 +110,14 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { /// @param messageNonce Nonce associated with the message sent /// @param sender Address initiating this message call /// @param message Message payload to call target with. + /// @param originContext Context of the message event SentMessage( - uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message + uint256 indexed destination, + address indexed target, + uint256 indexed messageNonce, + address sender, + bytes message, + bytes originContext ); /// @notice Emitted whenever a message is successfully relayed on this chain. @@ -115,14 +145,33 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { } } + /// @notice Retrieves the origin context of the current cross domain message. If not entered, reverts. + /// @return context_ Origin context of the current cross domain message. + function crossDomainMessageOriginContext() external view onlyEntered returns (bytes memory context_) { + return _crossDomainMessageOriginContext(); + } + /// @notice Retrieves the context of the current cross domain message. If not entered, reverts. /// @return sender_ Address of the sender of the current cross domain message. /// @return source_ Chain ID of the source of the current cross domain message. - function crossDomainMessageContext() external view onlyEntered returns (address sender_, uint256 source_) { + /// @return originContext_ Origin context of the current cross domain message. Leaving as bytes instead of the fixed + /// struct for flexibility. + function crossDomainMessageContext() + external + view + onlyEntered + returns (address sender_, uint256 source_, bytes memory originContext_) + { + uint8 encodingVersion; + bytes32 messagePayloadHash; assembly { sender_ := tload(CROSS_DOMAIN_MESSAGE_SENDER_SLOT) source_ := tload(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT) + encodingVersion := tload(ORIGIN_CONTEXT_VERSION_SLOT) + messagePayloadHash := tload(ORIGIN_CONTEXT_MESSAGE_PAYLOAD_HASH_SLOT) } + + originContext_ = abi.encode(encodingVersion, messagePayloadHash); } /// @notice Sends a message to some target address on a destination chain. Note that if the call always reverts, @@ -145,7 +194,7 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { if (_target == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert MessageTargetL2ToL2CrossDomainMessenger(); uint256 nonce = messageNonce(); - messageHash_ = Hashing.hashL2toL2CrossDomainMessage({ + bytes32 messagePayloadHash = Hashing.hashL2toL2CrossDomainMessagePayload({ _destination: _destination, _source: block.chainid, _nonce: nonce, @@ -154,10 +203,20 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { _message: _message }); + bytes memory originContext = _crossDomainMessageOriginContext(); + (uint8 encodingVersion,) = _parseOriginContext(originContext); + + if (encodingVersion == 0) { + originContext = abi.encode(ORIGIN_CONTEXT_ENCODING_VERSION, messagePayloadHash); + } + + // new "top-level" cross domain call (messageHash_ == outbound message) + messageHash_ = Hashing.hashL2toL2CrossDomainMessage(messagePayloadHash, originContext); + sentMessages[messageHash_] = true; msgNonce++; - emit SentMessage(_destination, _target, nonce, msg.sender, _message); + emit SentMessage(_destination, _target, nonce, msg.sender, _message, originContext); } /// @notice Re-emits a previously sent message event for old messages that haven't been @@ -169,18 +228,20 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { /// @param _sender Address that sent the message /// @param _target Target contract or wallet address. /// @param _message Message payload to call target with. + /// @param _originContext Origin context of the message. /// @return messageHash_ The hash of the message being re-sent. function resendMessage( uint256 _destination, uint256 _nonce, address _sender, address _target, - bytes calldata _message + bytes calldata _message, + bytes calldata _originContext ) external returns (bytes32 messageHash_) { - messageHash_ = Hashing.hashL2toL2CrossDomainMessage({ + bytes32 messagePayloadHash = Hashing.hashL2toL2CrossDomainMessagePayload({ _destination: _destination, _source: block.chainid, _nonce: _nonce, @@ -189,9 +250,11 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { _message: _message }); + messageHash_ = Hashing.hashL2toL2CrossDomainMessage(messagePayloadHash, _originContext); + if (!sentMessages[messageHash_]) revert InvalidMessage(); - emit SentMessage(_destination, _target, _nonce, _sender, _message); + emit SentMessage(_destination, _target, _nonce, _sender, _message, _originContext); } /// @notice Relays a message that was sent by the other L2ToL2CrossDomainMessenger contract. Can only be executed @@ -218,31 +281,32 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { ICrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_sentMessage)); // Decode the payload - (uint256 destination, address target, uint256 nonce, address sender, bytes memory message) = - _decodeSentMessagePayload(_sentMessage); + DecodedPayload memory decodedPayload = _decodeSentMessagePayload(_sentMessage); // Assert invariants on the message - if (destination != block.chainid) revert MessageDestinationNotRelayChain(); + if (decodedPayload.destination != block.chainid) revert MessageDestinationNotRelayChain(); uint256 source = _id.chainId; - bytes32 messageHash = Hashing.hashL2toL2CrossDomainMessage({ - _destination: destination, + bytes32 messagePayloadHash = Hashing.hashL2toL2CrossDomainMessagePayload({ + _destination: decodedPayload.destination, _source: source, - _nonce: nonce, - _sender: sender, - _target: target, - _message: message + _nonce: decodedPayload.nonce, + _sender: decodedPayload.sender, + _target: decodedPayload.target, + _message: decodedPayload.message }); + bytes32 messageHash = Hashing.hashL2toL2CrossDomainMessage(messagePayloadHash, decodedPayload.originContext); + if (successfulMessages[messageHash]) { revert MessageAlreadyRelayed(); } successfulMessages[messageHash] = true; - _storeMessageMetadata(source, sender); + _storeMessageMetadata(source, decodedPayload.sender, decodedPayload.originContext); bool success; - (success, returnData_) = target.call{ value: msg.value }(message); + (success, returnData_) = decodedPayload.target.call{ value: msg.value }(decodedPayload.message); if (!success) { assembly { @@ -250,9 +314,9 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { } } - emit RelayedMessage(source, nonce, messageHash, keccak256(returnData_)); + emit RelayedMessage(source, decodedPayload.nonce, messageHash, keccak256(returnData_)); - _storeMessageMetadata(0, address(0)); + _storeMessageMetadata(0, address(0), ""); } /// @notice Retrieves the next message nonce. Message version will be added to the upper two bytes of the message @@ -265,11 +329,44 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { /// @notice Stores message data such as sender and source in transient storage. /// @param _source Chain ID of the source chain. /// @param _sender Address of the sender of the message. - function _storeMessageMetadata(uint256 _source, address _sender) internal { + function _storeMessageMetadata(uint256 _source, address _sender, bytes memory _originContext) internal { + uint8 encodingVersion; + bytes32 messagePayloadHash; + + if (_originContext.length != 0) { + // Decode the origin context + (encodingVersion, messagePayloadHash) = _parseOriginContext(_originContext); + } + + // Store the message metadata assembly { tstore(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT, _source) tstore(CROSS_DOMAIN_MESSAGE_SENDER_SLOT, _sender) + tstore(ORIGIN_CONTEXT_VERSION_SLOT, encodingVersion) + tstore(ORIGIN_CONTEXT_MESSAGE_PAYLOAD_HASH_SLOT, messagePayloadHash) + } + } + + // Use this internal function just to know how to write and read the context, so if it changes, it's only here + function _parseOriginContext(bytes memory _originContext) + internal + pure + returns (uint8 encodingVersion_, bytes32 messagePayloadHash_) + { + (encodingVersion_, messagePayloadHash_) = abi.decode(_originContext, (uint8, bytes32)); + } + + /// @notice Retrieves the context of the current cross domain message. If not entered, reverts. + /// @return originContext_ Origin context of the current cross domain message. + function _crossDomainMessageOriginContext() internal view returns (bytes memory originContext_) { + uint8 encodingVersion; + bytes32 messagePayloadHash; + assembly { + encodingVersion := tload(ORIGIN_CONTEXT_VERSION_SLOT) + messagePayloadHash := tload(ORIGIN_CONTEXT_MESSAGE_PAYLOAD_HASH_SLOT) } + + originContext_ = abi.encode(encodingVersion, messagePayloadHash); } /// @notice Decodes the payload of a SentMessage event. @@ -279,24 +376,30 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { /// encode(sender, message) /// ) /// @param _payload Payload of the SentMessage event. - /// @return destination_ Destination chain ID. - /// @return target_ Target contract of the message. - /// @return nonce_ Nonce associated with the messsage sent. - /// @return sender_ Address initiating this message call. - /// @return message_ Message payload to call target with. + /// @return decodedPayload_ Decoded message. function _decodeSentMessagePayload(bytes calldata _payload) internal pure - returns (uint256 destination_, address target_, uint256 nonce_, address sender_, bytes memory message_) + returns (DecodedPayload memory decodedPayload_) { // Validate Selector (also reverts if LOG0 with no topics) bytes32 selector = abi.decode(_payload[:32], (bytes32)); - if (selector != SENT_MESSAGE_EVENT_SELECTOR) revert EventPayloadNotSentMessage(); + if (selector != SentMessage.selector) revert EventPayloadNotSentMessage(); // Topics - (destination_, target_, nonce_) = abi.decode(_payload[32:128], (uint256, address, uint256)); + (uint256 destination, address target, uint256 nonce) = abi.decode(_payload[32:128], (uint256, address, uint256)); // Data - (sender_, message_) = abi.decode(_payload[128:], (address, bytes)); + (address sender, bytes memory message, bytes memory originContext) = + abi.decode(_payload[128:], (address, bytes, bytes)); + + decodedPayload_ = DecodedPayload({ + destination: destination, + target: target, + nonce: nonce, + sender: sender, + message: message, + originContext: originContext + }); } } diff --git a/packages/contracts-bedrock/src/libraries/Hashing.sol b/packages/contracts-bedrock/src/libraries/Hashing.sol index 782bbbe4f99..f9549c75e5a 100644 --- a/packages/contracts-bedrock/src/libraries/Hashing.sol +++ b/packages/contracts-bedrock/src/libraries/Hashing.sol @@ -132,7 +132,7 @@ library Hashing { /// @param _target Address of the contract or wallet that the message is targeting on the destination chain. /// @param _message The message payload to be relayed to the target on the destination chain. /// @return Hash of the encoded message parameters, used to uniquely identify the message. - function hashL2toL2CrossDomainMessage( + function hashL2toL2CrossDomainMessagePayload( uint256 _destination, uint256 _source, uint256 _nonce, @@ -147,6 +147,21 @@ library Hashing { return keccak256(abi.encode(_destination, _source, _nonce, _sender, _target, _message)); } + /// @notice Hashes a cross domain message with an origin context. + /// @param _messagePayloadHash Hash of the message payload. + /// @param _originContext Origin context of the message. + /// @return Hashed cross domain message. + function hashL2toL2CrossDomainMessage( + bytes32 _messagePayloadHash, + bytes memory _originContext + ) + internal + pure + returns (bytes32) + { + return keccak256(abi.encodePacked(_messagePayloadHash, _originContext)); + } + /// @notice Hashes a Super Root proof into a Super Root. /// @param _superRootProof Super Root proof to hash. /// @return Hashed super root proof. diff --git a/packages/contracts-bedrock/test/L2/ExecutingMessageEmitted.t.sol b/packages/contracts-bedrock/test/L2/ExecutingMessageEmitted.t.sol index 8f1974b79a5..b658538661f 100644 --- a/packages/contracts-bedrock/test/L2/ExecutingMessageEmitted.t.sol +++ b/packages/contracts-bedrock/test/L2/ExecutingMessageEmitted.t.sol @@ -16,7 +16,7 @@ import { ISuperchainTokenBridge } from "interfaces/L2/ISuperchainTokenBridge.sol /// @notice Integration test that checks that the `ExecutingMessage` event is emitted on crosschain mints. contract ExecutingMessageEmittedTest is CommonTest { bytes32 internal constant SENT_MESSAGE_EVENT_SELECTOR = - 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320; + 0x687289caffce8cccd179ad6b3eebf5b30d65912f573a6b50d0525642b073297e; event ExecutingMessage(bytes32 indexed msgHash, Identifier id); @@ -76,6 +76,10 @@ contract ExecutingMessageEmittedTest is CommonTest { // Ensure that the target is not a forge address. assumeNotForgeAddress(_to); + // Construct the origin context + bytes32 messagePayloadHash = keccak256(abi.encode(block.chainid, _nonce, _sender, _to, _amount)); + bytes memory originContext = abi.encode(uint8(1), messagePayloadHash); + // Construct the SentMessage payload & identifier _id.origin = address(MESSENGER); _id.blockNumber = bound(_id.blockNumber, 0, type(uint64).max); @@ -84,7 +88,7 @@ contract ExecutingMessageEmittedTest is CommonTest { bytes memory message = abi.encodeCall(ISuperchainTokenBridge.relayERC20, (_token, _sender, _to, _amount)); bytes memory sentMessage = abi.encodePacked( abi.encode(SENT_MESSAGE_EVENT_SELECTOR, block.chainid, SUPERCHAIN_TOKEN_BRIDGE, _nonce), // topics - abi.encode(_sender, message) // data + abi.encode(_sender, message, originContext) // data ); // Mock `crossDomainMessageContext` call for it to succeed diff --git a/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol index 81adb5b6bf8..f846106f1e9 100644 --- a/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol @@ -59,6 +59,17 @@ contract L2ToL2CrossDomainMessengerWithModifiableTransientStorage is L2ToL2Cross tstore(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT, _source) } } + + /// @dev Sets the cross domain messenger context in transient storage. + /// @param _originContext Context to set. + function setCrossDomainMessageOriginContext(bytes memory _originContext) external { + (uint8 encodingVersion, bytes32 messagePayloadHash) = _parseOriginContext(_originContext); + + assembly { + tstore(ORIGIN_CONTEXT_VERSION_SLOT, encodingVersion) + tstore(ORIGIN_CONTEXT_MESSAGE_PAYLOAD_HASH_SLOT, messagePayloadHash) + } + } } /// @title L2ToL2CrossDomainMessenger_TestInit @@ -142,21 +153,35 @@ contract L2ToL2CrossDomainMessenger_CrossDomainMessageSource_Test is L2ToL2Cross /// @notice Tests the `crossDomainMessageContext` function of the `L2ToL2CrossDomainMessenger` contract. contract L2ToL2CrossDomainMessenger_CrossDomainMessageContext_Test is L2ToL2CrossDomainMessenger_TestInit { /// @notice Tests that the `crossDomainMessageContext` function returns the correct value. - function testFuzz_crossDomainMessageContext_succeeds(address _sender, uint256 _source) external { + function testFuzz_crossDomainMessageContext_succeeds( + address _sender, + uint256 _source, + bytes32 _messagePayloadHash + ) + external + { // Set `entered` to non-zero value to prevent NotEntered revert l2ToL2CrossDomainMessenger.setEntered(1); // Ensure that the contract is now entered assertEq(l2ToL2CrossDomainMessenger.entered(), true); + bytes memory originContext = + abi.encode(l2ToL2CrossDomainMessenger.ORIGIN_CONTEXT_ENCODING_VERSION(), _messagePayloadHash); + // Set cross domain message source in the transient storage l2ToL2CrossDomainMessenger.setCrossDomainMessageSender(_sender); l2ToL2CrossDomainMessenger.setCrossDomainMessageSource(_source); + l2ToL2CrossDomainMessenger.setCrossDomainMessageOriginContext(originContext); // Check that the `crossDomainMessageContext` function returns the correct value - (address crossDomainContextSender, uint256 crossDomainContextSource) = - l2ToL2CrossDomainMessenger.crossDomainMessageContext(); + ( + address crossDomainContextSender, + uint256 crossDomainContextSource, + bytes memory crossDomainContextOriginContext + ) = l2ToL2CrossDomainMessenger.crossDomainMessageContext(); assertEq(crossDomainContextSender, _sender); assertEq(crossDomainContextSource, _source); + assertEq(crossDomainContextOriginContext, originContext); } /// @notice Tests that the `crossDomainMessageContext` function reverts when not entered. @@ -191,12 +216,12 @@ contract L2ToL2CrossDomainMessenger_SendMessage_Test is L2ToL2CrossDomainMesseng // Call the sendMessage function bytes32 msgHash = l2ToL2CrossDomainMessenger.sendMessage(_destination, _target, _message); - assertEq( - msgHash, - Hashing.hashL2toL2CrossDomainMessage( - _destination, block.chainid, messageNonce, address(this), _target, _message - ) + bytes32 messagePayloadHash = Hashing.hashL2toL2CrossDomainMessagePayload( + _destination, block.chainid, messageNonce, address(this), _target, _message ); + bytes memory originContext = + abi.encode(l2ToL2CrossDomainMessenger.ORIGIN_CONTEXT_ENCODING_VERSION(), messagePayloadHash); + assertEq(msgHash, keccak256(abi.encodePacked(messagePayloadHash, originContext))); // Check that the event was emitted with the correct parameters Vm.Log[] memory logs = vm.getRecordedLogs(); @@ -209,7 +234,7 @@ contract L2ToL2CrossDomainMessenger_SendMessage_Test is L2ToL2CrossDomainMesseng assertEq(logs[0].topics[3], bytes32(messageNonce)); // data - assertEq(logs[0].data, abi.encode(address(this), _message)); + assertEq(logs[0].data, abi.encode(address(this), _message, originContext)); // Check that the message nonce has been incremented and the message hash has been stored assertEq(l2ToL2CrossDomainMessenger.messageNonce(), messageNonce + 1); @@ -291,20 +316,21 @@ contract L2ToL2CrossDomainMessenger_ResendMessage_Test is L2ToL2CrossDomainMesse uint256 _nonce, address _sender, address _target, - bytes calldata _message + bytes calldata _message, + bytes calldata _context ) external { // Get the message hash and ensure it has not been sent yet bytes32 msgHash = - Hashing.hashL2toL2CrossDomainMessage(_destination, block.chainid, _nonce, _sender, _target, _message); + Hashing.hashL2toL2CrossDomainMessagePayload(_destination, block.chainid, _nonce, _sender, _target, _message); vm.assume(l2ToL2CrossDomainMessenger.sentMessages(msgHash) == false); // Expect a revert with the InvalidMessage selector vm.expectRevert(InvalidMessage.selector); // Call the resendMessage function - l2ToL2CrossDomainMessenger.resendMessage(_destination, _nonce, _sender, _target, _message); + l2ToL2CrossDomainMessenger.resendMessage(_destination, _nonce, _sender, _target, _message, _context); } /// @notice Tests that `resendMessage` succeeds and emits the same SentMessage event as the one @@ -332,11 +358,15 @@ contract L2ToL2CrossDomainMessenger_ResendMessage_Test is L2ToL2CrossDomainMesse // Call the `sendMessage` function vm.prank(_sender); bytes32 msgHash = l2ToL2CrossDomainMessenger.sendMessage(_destination, _target, _message); - assertEq( - msgHash, - Hashing.hashL2toL2CrossDomainMessage(_destination, block.chainid, messageNonce, _sender, _target, _message) + bytes32 messagePayloadHash = Hashing.hashL2toL2CrossDomainMessagePayload( + _destination, block.chainid, messageNonce, _sender, _target, _message ); + bytes memory originContext = + abi.encode(l2ToL2CrossDomainMessenger.ORIGIN_CONTEXT_ENCODING_VERSION(), messagePayloadHash); + + assertEq(msgHash, keccak256(abi.encodePacked(messagePayloadHash, originContext))); + // Check that the event was emitted with the correct parameters Vm.Log[] memory logs = vm.getRecordedLogs(); assertEq(logs.length, 1); @@ -348,15 +378,16 @@ contract L2ToL2CrossDomainMessenger_ResendMessage_Test is L2ToL2CrossDomainMesse assertEq(logs[0].topics[3], bytes32(messageNonce)); // data - assertEq(logs[0].data, abi.encode(_sender, _message)); + assertEq(logs[0].data, abi.encode(_sender, _message, originContext)); // Check that the message nonce has been incremented and the message hash has been stored assertEq(l2ToL2CrossDomainMessenger.messageNonce(), messageNonce + 1); assertEq(l2ToL2CrossDomainMessenger.sentMessages(msgHash), true); // Call the `resendMessage` function - bytes32 resendMsgHash = - l2ToL2CrossDomainMessenger.resendMessage(_destination, messageNonce, _sender, _target, _message); + bytes32 resendMsgHash = l2ToL2CrossDomainMessenger.resendMessage( + _destination, messageNonce, _sender, _target, _message, originContext + ); // Check that the event was emitted with the correct parameters logs = vm.getRecordedLogs(); @@ -470,7 +501,12 @@ contract L2ToL2CrossDomainMessenger_RelayMessage_Test is L2ToL2CrossDomainMessen address target = address(this); bytes memory message = abi.encodeCall(this.mockTarget, (_source, _sender)); - bytes32 msgHash = keccak256(abi.encode(block.chainid, _source, _nonce, _sender, target, message)); + bytes32 messagePayloadHash = keccak256(abi.encode(block.chainid, _source, _nonce, _sender, target, message)); + + bytes memory originContext = + abi.encode(l2ToL2CrossDomainMessenger.ORIGIN_CONTEXT_ENCODING_VERSION(), messagePayloadHash); + + bytes32 msgHash = keccak256(abi.encodePacked(messagePayloadHash, originContext)); // Look for correct emitted event vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); @@ -484,7 +520,7 @@ contract L2ToL2CrossDomainMessenger_RelayMessage_Test is L2ToL2CrossDomainMessen Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, target, _nonce), // topics - abi.encode(_sender, message) // data + abi.encode(_sender, message, originContext) // data ); // Ensure the CrossL2Inbox validates this message @@ -540,12 +576,16 @@ contract L2ToL2CrossDomainMessenger_RelayMessage_Test is L2ToL2CrossDomainMessen // Declare a random call to be made over the target bytes memory message = abi.encodePacked("randomCall()"); + bytes32 messagePayloadHash = keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, message)); + bytes memory originContext = + abi.encode(l2ToL2CrossDomainMessenger.ORIGIN_CONTEXT_ENCODING_VERSION(), messagePayloadHash); + // Construct the message Identifier memory id = Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, _target, _nonce), // topics - abi.encode(_sender, message) // data + abi.encode(_sender, message, originContext) // data ); // Ensure the CrossL2Inbox validates this message @@ -591,12 +631,16 @@ contract L2ToL2CrossDomainMessenger_RelayMessage_Test is L2ToL2CrossDomainMessen // Ensure the target contract is called with the correct parameters vm.expectCall({ callee: target, msgValue: _value, data: message }); + bytes32 messagePayloadHash = keccak256(abi.encode(block.chainid, _source1, _nonce, _sender1, target, message)); + bytes memory originContext = + abi.encode(l2ToL2CrossDomainMessenger.ORIGIN_CONTEXT_ENCODING_VERSION(), messagePayloadHash); + // Construct and relay the message Identifier memory id = Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source1); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, target, _nonce), // topics - abi.encode(_sender1, message) // data + abi.encode(_sender1, message, originContext) // data ); // Ensure the CrossL2Inbox validates this message @@ -646,7 +690,7 @@ contract L2ToL2CrossDomainMessenger_RelayMessage_Test is L2ToL2CrossDomainMessen Identifier memory id = Identifier(_origin, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, _target, _nonce), // topics - abi.encode(_sender, _message) // data + abi.encode(_sender, _message, "") // data (no context) ); // Call @@ -680,7 +724,7 @@ contract L2ToL2CrossDomainMessenger_RelayMessage_Test is L2ToL2CrossDomainMessen Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, _destination, _target, _nonce), // topics - abi.encode(_sender, _message) // data + abi.encode(_sender, _message, "") // data (no context) ); // Ensure the CrossL2Inbox validates this message @@ -728,12 +772,17 @@ contract L2ToL2CrossDomainMessenger_RelayMessage_Test is L2ToL2CrossDomainMessen // data) vm.mockCall({ callee: _target, msgValue: _value, data: _message, returnData: _message }); + bytes32 messagePayloadHash = keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message)); + bytes memory originContext = + abi.encode(l2ToL2CrossDomainMessenger.ORIGIN_CONTEXT_ENCODING_VERSION(), messagePayloadHash); + // Look for correct emitted event for first call. vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); emit L2ToL2CrossDomainMessenger.RelayedMessage( _source, _nonce, - keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message)), + // message payload hash + context + keccak256(abi.encodePacked(messagePayloadHash, originContext)), keccak256(_message) ); @@ -741,7 +790,7 @@ contract L2ToL2CrossDomainMessenger_RelayMessage_Test is L2ToL2CrossDomainMessen Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _blockNum, _logIndex, _time, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, _target, _nonce), // topics - abi.encode(_sender, _message) // data + abi.encode(_sender, _message, originContext) ); // Ensure the CrossL2Inbox validates this message @@ -791,9 +840,13 @@ contract L2ToL2CrossDomainMessenger_RelayMessage_Test is L2ToL2CrossDomainMessen // and time to avoid stack too deep errors. Identifier memory id = Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, 1, 1, 1, _source); + bytes32 messagePayloadHash = keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message)); + bytes memory originContext = + abi.encode(l2ToL2CrossDomainMessenger.ORIGIN_CONTEXT_ENCODING_VERSION(), messagePayloadHash); + bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, _target, _nonce), // topics - abi.encode(_sender, _message) // data + abi.encode(_sender, _message, originContext) // data ); // Ensure the CrossL2Inbox validates this message