diff --git a/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol b/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol index 58f07a9c92d9..bfc997d55288 100644 --- a/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/interfaces/L2/IL2ToL2CrossDomainMessenger.sol @@ -45,19 +45,28 @@ interface IL2ToL2CrossDomainMessenger { /// @notice Thrown when attempting to use a chain ID that is not in the dependency set. error InvalidChainId(); + /// @notice Thrown when relaying a message from an address different than the specified entrypoint. + error InvalidEntrypoint(); + /// @notice Emitted whenever a message is sent to a destination /// @param destination Chain ID of the destination chain. /// @param target Target contract or wallet address. - /// @param messageNonce Nonce associated with the messsage sent + /// @param messageNonce Nonce associated with the message sent /// @param sender Address initiating this message call + /// @param entrypoint Address of the entrypoint on the destination chain. /// @param message Message payload to call target with. 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, + address entrypoint, + bytes message ); /// @notice Emitted whenever a message is successfully relayed on this chain. /// @param source Chain ID of the source chain. - /// @param messageNonce Nonce associated with the messsage sent + /// @param messageNonce Nonce associated with the message sent /// @param messageHash Hash of the message that was relayed. event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash); @@ -83,20 +92,51 @@ 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 entrypoint of the current cross domain message. + /// @return entrypoint_ Address of the entrypoint of the current cross domain message. + function crossDomainMessageEntrypoint() external view returns (address entrypoint_); + + /// @notice Retrieves the nonce of the current cross domain message. + /// @return nonce_ Nonce of the current cross domain message. + function crossDomainMessageNonce() external view returns (uint256 nonce_); + /// @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 returns (address sender_, uint256 source_); + /// @return entrypoint_ Address of the entrypoint of the current cross domain message. + /// @return nonce_ Nonce of the current cross domain message. + function crossDomainMessageContext() + external + view + returns (address sender_, uint256 source_, address entrypoint_, uint256 nonce_); + + /// @notice Sends a message to some target address on a destination chain with an entrypoint address as an + /// authorized relayer. Note that if the call always reverts, then the message will be unrelayable and any + /// ETH sent will be permanently locked. The same will occur if the target on the other chain is considered + /// unsafe (see the _isUnsafeTarget() function). The entrypoint must have the capability to call + /// `relayMessage` for successful relaying. + /// @param _destination Chain ID of the destination chain. + /// @param _target Target contract or wallet address. + /// @param _entrypoint Address of the entrypoint on the destination chain. If it is address(0) then the + /// message can be relayed by any address on the destination chain. + /// @param _message Message payload to call target with. + /// @return The hash of the message being sent, used to track whether the message has successfully been relayed. + function sendMessage( + uint256 _destination, + address _target, + address _entrypoint, + bytes calldata _message + ) + external + returns (bytes32); - /// @notice Sends a message to some target address on a destination chain. Note that if the call - /// always reverts, then the message will be unrelayable, and any ETH sent will be - /// permanently locked. The same will occur if the target on the other chain is - /// considered unsafe (see the _isUnsafeTarget() function). + /// @notice Sends a message to some target address on a destination chain. Note that if the call always reverts, + /// then the message will be unrelayable and any ETH sent will be permanently locked. The same will occur + /// if the target on the other chain is considered unsafe (see the _isUnsafeTarget() function). /// @param _destination Chain ID of the destination chain. /// @param _target Target contract or wallet address. - /// @param _message Message to trigger the target address with. - /// @return msgHash_ The hash of the message being sent, which can be used for tracking whether - /// the message has successfully been relayed. + /// @param _message Message payload to call target with. + /// @return The hash of the message being sent, used to track whether the message has successfully been relayed. function sendMessage(uint256 _destination, address _target, bytes calldata _message) external returns (bytes32); /// @notice Relays a message that was sent by the other CrossDomainMessenger contract. Can only diff --git a/packages/contracts-bedrock/scripts/checks/interfaces/main.go b/packages/contracts-bedrock/scripts/checks/interfaces/main.go index d1410efeef26..035753adf724 100644 --- a/packages/contracts-bedrock/scripts/checks/interfaces/main.go +++ b/packages/contracts-bedrock/scripts/checks/interfaces/main.go @@ -266,7 +266,13 @@ func abiItemLess(a, b map[string]interface{}) bool { aName := getString(a, "name") bName := getString(b, "name") - return aName < bName + if aName != bName { + return aName < bName + } + + aInputs := a["inputs"].([]interface{}) + bInputs := b["inputs"].([]interface{}) + return len(aInputs) < len(bInputs) } func getString(m map[string]interface{}, key string) string { diff --git a/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json b/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json index 3d381ec9ae51..42bb22108f3e 100644 --- a/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json +++ b/packages/contracts-bedrock/snapshots/abi/L2ToL2CrossDomainMessenger.json @@ -12,6 +12,42 @@ "internalType": "uint256", "name": "source_", "type": "uint256" + }, + { + "internalType": "address", + "name": "entrypoint_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "nonce_", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "crossDomainMessageEntrypoint", + "outputs": [ + { + "internalType": "address", + "name": "entrypoint_", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "crossDomainMessageNonce", + "outputs": [ + { + "internalType": "uint256", + "name": "nonce_", + "type": "uint256" } ], "stateMutability": "view", @@ -149,6 +185,40 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "_destination", + "type": "uint256" + }, + { + "internalType": "address", + "name": "_target", + "type": "address" + }, + { + "internalType": "address", + "name": "_entrypoint", + "type": "address" + }, + { + "internalType": "bytes", + "name": "_message", + "type": "bytes" + } + ], + "name": "sendMessage", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -233,6 +303,12 @@ "name": "sender", "type": "address" }, + { + "indexed": false, + "internalType": "address", + "name": "entrypoint", + "type": "address" + }, { "indexed": false, "internalType": "bytes", @@ -258,6 +334,11 @@ "name": "InvalidChainId", "type": "error" }, + { + "inputs": [], + "name": "InvalidEntrypoint", + "type": "error" + }, { "inputs": [], "name": "MessageAlreadyRelayed", diff --git a/packages/contracts-bedrock/snapshots/semver-lock.json b/packages/contracts-bedrock/snapshots/semver-lock.json index beb691157243..be41205ec737 100644 --- a/packages/contracts-bedrock/snapshots/semver-lock.json +++ b/packages/contracts-bedrock/snapshots/semver-lock.json @@ -100,8 +100,8 @@ "sourceCodeHash": "0xaef8ea36c5b78cd12e0e62811d51db627ccf0dfd2cc5479fb707a10ef0d42048" }, "src/L2/L2ToL2CrossDomainMessenger.sol": { - "initCodeHash": "0xc56db8cb569efa0467fd53ab3fa218af3051e54f5517d7fafb7b5831b4350618", - "sourceCodeHash": "0x72062343a044e9c56f4143dcfc71706286eb205902006c2afcf6a4cd90c3e9f8" + "initCodeHash": "0x486127eb0deebf15dcedbbdae026f1a3f43f68601b4b8e30b6aef2aed3923e74", + "sourceCodeHash": "0xa61bd92bce98a66d3261d6be4220fe113a50ac495d8aa8594b151c7dc91e94ee" }, "src/L2/OptimismSuperchainERC20.sol": { "initCodeHash": "0xdac32a1057a6bc8a8d2ffdce1db8f34950cd0ffd1454d2133865736d21869192", @@ -124,12 +124,12 @@ "sourceCodeHash": "0x981dca5b09da9038a9dff071b40a880e1b52b20268c6780ef54be3bc98a4f629" }, "src/L2/SuperchainTokenBridge.sol": { - "initCodeHash": "0x6b568ed564aede82a3a4cbcdb51282cad0e588a3fe6d91cf76616d3113df3901", - "sourceCodeHash": "0xcd2b49cb7cf6d18616ee8bec9183fe5b5b460941875bc0b4158c4d5390ec3b0c" + "initCodeHash": "0x4c3d5990c37eef0ce09c7b32ecb668fab45151f2bcbf7a0b923cb2e8d3e3538b", + "sourceCodeHash": "0x421394215e658a2018cd4d6043089e07319f05c261d17abdaa43b01cdb329575" }, "src/L2/SuperchainWETH.sol": { - "initCodeHash": "0x6ded8aeea6edf7e0ead7b0d2a12ef236f1fb7d21980a1dd564cbe86affca7927", - "sourceCodeHash": "0x11d711704a5afcae6076d017ee001b25bc705728973b1ad2e6a32274a8475f50" + "initCodeHash": "0x7813e841bea7fac3fda1d47ceb07edfa4e839c1c491a9e921b90bf2fcc823c7c", + "sourceCodeHash": "0x1884441724b07b2625675bf2dd30b94ff74691d2c9456d01e6e25a913479eb7c" }, "src/L2/WETH.sol": { "initCodeHash": "0x480d4f8dbec1b0d3211bccbbdfb69796f3e90c784f724b1bbfd4703b0aafdeba", diff --git a/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol b/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol index fc1f74e99c53..ccf6d90f096c 100644 --- a/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol +++ b/packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol @@ -45,6 +45,9 @@ error TargetCallFailed(); /// @notice Thrown when attempting to use a chain ID that is not in the dependency set. error InvalidChainId(); +/// @notice Thrown when relaying a message from an address different from the specified entrypoint. +error InvalidEntrypoint(); + /// @custom:proxied true /// @custom:predeploy 0x4200000000000000000000000000000000000023 /// @title L2ToL2CrossDomainMessenger @@ -62,17 +65,27 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { bytes32 internal constant CROSS_DOMAIN_MESSAGE_SOURCE_SLOT = 0x711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee7; + /// @notice Storage slot for the entrypoint of the current cross domain message. + /// Equal to bytes32(uint256(keccak256("l2tol2crossdomainmessenger.entrypoint")) - 1) + bytes32 internal constant CROSS_DOMAIN_MESSAGE_ENTRYPOINT_SLOT = + 0x4f785a87c3805277007014d2b9bc19a6bf5d719f15bbf276e96c0e164571d512; + + /// @notice Storage slot for the nonce of the current cross domain message. + /// Equal to bytes32(uint256(keccak256("l2tol2crossdomainmessenger.nonce")) - 1) + bytes32 internal constant CROSS_DOMAIN_MESSAGE_NONCE_SLOT = + 0xa231ed9d17d28f3f4325cc5bdfd293c5d31aa3b46490909780afcf572cf92e64; + /// @notice Event selector for the SentMessage event. Will be removed in favor of reading - // the `selector` property directly once crytic/slithe/#2566 is fixed. + // the `selector` property directly once crytic/slither/#2566 is fixed. bytes32 internal constant SENT_MESSAGE_EVENT_SELECTOR = - 0x382409ac69001e11931a28435afef442cbfd20d9891907e8fa373ba7d351f320; + 0xb6b27857168ee0136e68e746bb12d3abcd605fd8a719100d88901127632100e3; /// @notice Current message version identifier. uint16 public constant messageVersion = uint16(0); /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.14 - string public constant version = "1.0.0-beta.14"; + /// @custom:semver 1.0.0-beta.15 + string public constant version = "1.0.0-beta.15"; /// @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,16 +99,22 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { /// @notice Emitted whenever a message is sent to a destination /// @param destination Chain ID of the destination chain. /// @param target Target contract or wallet address. - /// @param messageNonce Nonce associated with the messsage sent + /// @param messageNonce Nonce associated with the message sent /// @param sender Address initiating this message call + /// @param entrypoint Address of the entrypoint on the destination chain. /// @param message Message payload to call target with. 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, + address entrypoint, + bytes message ); /// @notice Emitted whenever a message is successfully relayed on this chain. /// @param source Chain ID of the source chain. - /// @param messageNonce Nonce associated with the messsage sent + /// @param messageNonce Nonce associated with the message sent /// @param messageHash Hash of the message that was relayed. event RelayedMessage(uint256 indexed source, uint256 indexed messageNonce, bytes32 indexed messageHash); @@ -115,16 +134,64 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { } } + /// @notice Retrieves the entrypoint of the current cross domain message. + /// @return entrypoint_ Address of the entrypoint of the current cross domain message. + function crossDomainMessageEntrypoint() external view onlyEntered returns (address entrypoint_) { + assembly { + entrypoint_ := tload(CROSS_DOMAIN_MESSAGE_ENTRYPOINT_SLOT) + } + } + + /// @notice Retrieves the nonce of the current cross domain message. + /// @return nonce_ Nonce of the current cross domain message. + function crossDomainMessageNonce() external view onlyEntered returns (uint256 nonce_) { + assembly { + nonce_ := tload(CROSS_DOMAIN_MESSAGE_NONCE_SLOT) + } + } + /// @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 entrypoint_ Address of the entrypoint of the current cross domain message. + /// @return nonce_ Nonce of the current cross domain message. + function crossDomainMessageContext() + external + view + onlyEntered + returns (address sender_, uint256 source_, address entrypoint_, uint256 nonce_) + { assembly { sender_ := tload(CROSS_DOMAIN_MESSAGE_SENDER_SLOT) source_ := tload(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT) + entrypoint_ := tload(CROSS_DOMAIN_MESSAGE_ENTRYPOINT_SLOT) + nonce_ := tload(CROSS_DOMAIN_MESSAGE_NONCE_SLOT) } } + /// @notice Sends a message to some target address on a destination chain with an entrypoint address as an + /// authorized relayer. Note that if the call always reverts, then the message will be unrelayable and any + /// ETH sent will be permanently locked. The same will occur if the target on the other chain is considered + /// unsafe (see the _isUnsafeTarget() function). The entrypoint must have the capability to call + /// `relayMessage` for successful relaying. + /// @param _destination Chain ID of the destination chain. + /// @param _target Target contract or wallet address. + /// @param _entrypoint Address of the entrypoint on the destination chain. If it is address(0) then the + /// message can be relayed by any address on the destination chain. + /// @param _message Message payload to call target with. + /// @return The hash of the message being sent, used to track whether the message has successfully been relayed. + function sendMessage( + uint256 _destination, + address _target, + address _entrypoint, + bytes calldata _message + ) + external + returns (bytes32) + { + return _sendMessage(_destination, _target, _entrypoint, _message); + } + /// @notice Sends a message to some target address on a destination chain. Note that if the call always reverts, /// then the message will be unrelayable and any ETH sent will be permanently locked. The same will occur /// if the target on the other chain is considered unsafe (see the _isUnsafeTarget() function). @@ -133,24 +200,8 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { /// @param _message Message payload to call target with. /// @return The hash of the message being sent, used to track whether the message has successfully been relayed. function sendMessage(uint256 _destination, address _target, bytes calldata _message) external returns (bytes32) { - if (_destination == block.chainid) revert MessageDestinationSameChain(); - if (_target == Predeploys.CROSS_L2_INBOX) revert MessageTargetCrossL2Inbox(); - if (_target == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert MessageTargetL2ToL2CrossDomainMessenger(); - if (!IDependencySet(Predeploys.L1_BLOCK_ATTRIBUTES).isInDependencySet(_destination)) revert InvalidChainId(); - - uint256 nonce = messageNonce(); - emit SentMessage(_destination, _target, nonce, msg.sender, _message); - - msgNonce++; - - return Hashing.hashL2toL2CrossDomainMessage({ - _destination: _destination, - _source: block.chainid, - _nonce: nonce, - _sender: msg.sender, - _target: _target, - _message: _message - }); + // If entrypoint is not specified, then anyone can relay the message + return _sendMessage(_destination, _target, address(0), _message); } /// @notice Relays a message that was sent by the other L2ToL2CrossDomainMessenger contract. Can only be executed @@ -178,13 +229,14 @@ 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) = + (uint256 destination, address target, uint256 nonce, address sender, address entrypoint, bytes memory message) = _decodeSentMessagePayload(_sentMessage); // Assert invariants on the message if (destination != block.chainid) revert MessageDestinationNotRelayChain(); if (target == Predeploys.CROSS_L2_INBOX) revert MessageTargetCrossL2Inbox(); if (target == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert MessageTargetL2ToL2CrossDomainMessenger(); + if (entrypoint != address(0) && entrypoint != msg.sender) revert InvalidEntrypoint(); uint256 source = _id.chainId; bytes32 messageHash = Hashing.hashL2toL2CrossDomainMessage({ @@ -193,6 +245,7 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { _nonce: nonce, _sender: sender, _target: target, + _entrypoint: entrypoint, _message: message }); @@ -200,7 +253,7 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { revert MessageAlreadyRelayed(); } - _storeMessageMetadata(source, sender); + _storeMessageMetadata(source, sender, entrypoint, nonce); bool success; (success, returnData_) = target.call{ value: msg.value }(message); @@ -212,7 +265,7 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { successfulMessages[messageHash] = true; emit RelayedMessage(source, nonce, messageHash); - _storeMessageMetadata(0, address(0)); + _storeMessageMetadata(0, address(0), address(0), 0); } /// @notice Retrieves the next message nonce. Message version will be added to the upper two bytes of the message @@ -225,17 +278,36 @@ 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 { + /// @param _entrypoint Address of the entrypoint of the message. + /// @param _nonce Nonce of the message. + function _storeMessageMetadata(uint256 _source, address _sender, address _entrypoint, uint256 _nonce) internal { assembly { tstore(CROSS_DOMAIN_MESSAGE_SENDER_SLOT, _sender) tstore(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT, _source) + tstore(CROSS_DOMAIN_MESSAGE_ENTRYPOINT_SLOT, _entrypoint) + tstore(CROSS_DOMAIN_MESSAGE_NONCE_SLOT, _nonce) } } + /// @notice Decodes the payload of a SentMessage event. + /// @param _payload Payload of the SentMessage event. + /// @return destination_ Chain ID of the destination chain. + /// @return target_ Target contract or wallet address. + /// @return nonce_ Nonce associated with the message sent. + /// @return sender_ Address initiating this message call. + /// @return entrypoint_ Address of the entrypoint on the destination chain. + /// @return message_ Message payload to call target with. function _decodeSentMessagePayload(bytes calldata _payload) internal pure - returns (uint256 destination_, address target_, uint256 nonce_, address sender_, bytes memory message_) + returns ( + uint256 destination_, + address target_, + uint256 nonce_, + address sender_, + address entrypoint_, + bytes memory message_ + ) { // Validate Selector (also reverts if LOG0 with no topics) bytes32 selector = abi.decode(_payload[:32], (bytes32)); @@ -245,6 +317,46 @@ contract L2ToL2CrossDomainMessenger is ISemver, TransientReentrancyAware { (destination_, target_, nonce_) = abi.decode(_payload[32:128], (uint256, address, uint256)); // Data - (sender_, message_) = abi.decode(_payload[128:], (address, bytes)); + (sender_, entrypoint_, message_) = abi.decode(_payload[128:], (address, address, bytes)); + } + + /// @notice Sends a message to a target address on a destination chain. + /// This function checks that the destination is not the same as the current chain and that the target + /// is not the CrossL2Inbox or the L2ToL2CrossDomainMessenger itself. It emits a SentMessage event + /// and increments the message nonce. + /// @param _destination Chain ID of the destination chain. + /// @param _target Target contract or wallet address. + /// @param _entrypoint Address of the entrypoint on the destination chain or address(0) if there is none -- meaning + /// that anyone can relay the message. + /// @param _message Message payload to call target with. + /// @return The hash of the message being sent, used to track whether the message has successfully been relayed. + function _sendMessage( + uint256 _destination, + address _target, + address _entrypoint, + bytes calldata _message + ) + internal + returns (bytes32) + { + if (_destination == block.chainid) revert MessageDestinationSameChain(); + if (_target == Predeploys.CROSS_L2_INBOX) revert MessageTargetCrossL2Inbox(); + if (_target == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert MessageTargetL2ToL2CrossDomainMessenger(); + if (!IDependencySet(Predeploys.L1_BLOCK_ATTRIBUTES).isInDependencySet(_destination)) revert InvalidChainId(); + + uint256 nonce = messageNonce(); + emit SentMessage(_destination, _target, nonce, msg.sender, _entrypoint, _message); + + msgNonce++; + + return Hashing.hashL2toL2CrossDomainMessage({ + _destination: _destination, + _source: block.chainid, + _nonce: nonce, + _sender: msg.sender, + _target: _target, + _entrypoint: _entrypoint, + _message: _message + }); } } diff --git a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol index fc8c3d961088..ea7b577b6f4c 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainTokenBridge.sol @@ -46,8 +46,8 @@ contract SuperchainTokenBridge { address internal constant MESSENGER = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER; /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.4 - string public constant version = "1.0.0-beta.4"; + /// @custom:semver 1.0.0-beta.5 + string public constant version = "1.0.0-beta.5"; /// @notice Sends tokens to a target address on another chain. /// @dev Tokens are burned on the source chain. @@ -86,7 +86,7 @@ contract SuperchainTokenBridge { function relayERC20(address _token, address _from, address _to, uint256 _amount) external { if (msg.sender != MESSENGER) revert Unauthorized(); - (address crossDomainMessageSender, uint256 source) = + (address crossDomainMessageSender, uint256 source,,) = IL2ToL2CrossDomainMessenger(MESSENGER).crossDomainMessageContext(); if (crossDomainMessageSender != address(this)) revert InvalidCrossDomainSender(); diff --git a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol index ab6ff44a33ab..7407e4d65190 100644 --- a/packages/contracts-bedrock/src/L2/SuperchainWETH.sol +++ b/packages/contracts-bedrock/src/L2/SuperchainWETH.sol @@ -43,8 +43,8 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver { event RelayETH(address indexed from, address indexed to, uint256 amount, uint256 source); /// @notice Semantic version. - /// @custom:semver 1.0.0-beta.13 - string public constant version = "1.0.0-beta.13"; + /// @custom:semver 1.0.0-beta.14 + string public constant version = "1.0.0-beta.14"; /// @inheritdoc WETH98 function deposit() public payable override { @@ -150,7 +150,7 @@ contract SuperchainWETH is WETH98, IERC7802, ISemver { function relayETH(address _from, address _to, uint256 _amount) external { if (msg.sender != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert Unauthorized(); - (address crossDomainMessageSender, uint256 source) = + (address crossDomainMessageSender, uint256 source,,) = IL2ToL2CrossDomainMessenger(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER).crossDomainMessageContext(); if (crossDomainMessageSender != address(this)) revert InvalidCrossDomainSender(); diff --git a/packages/contracts-bedrock/src/libraries/Hashing.sol b/packages/contracts-bedrock/src/libraries/Hashing.sol index b736ad9e4b7e..2f216c466110 100644 --- a/packages/contracts-bedrock/src/libraries/Hashing.sol +++ b/packages/contracts-bedrock/src/libraries/Hashing.sol @@ -130,6 +130,7 @@ library Hashing { /// @param _nonce Unique nonce associated with the message to prevent replay attacks. /// @param _sender Address of the user who originally sent the message. /// @param _target Address of the contract or wallet that the message is targeting on the destination chain. + /// @param _entrypoint Address of the entrypoint on the destination chain that will relay the message. /// @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( @@ -138,12 +139,13 @@ library Hashing { uint256 _nonce, address _sender, address _target, + address _entrypoint, bytes memory _message ) internal pure returns (bytes32) { - return keccak256(abi.encode(_destination, _source, _nonce, _sender, _target, _message)); + return keccak256(abi.encode(_destination, _source, _nonce, _sender, _target, _entrypoint, _message)); } } diff --git a/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol b/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol index 5cfcc3a8da44..7a88439bf3f6 100644 --- a/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol +++ b/packages/contracts-bedrock/test/L2/L2ToL2CrossDomainMessenger.t.sol @@ -23,7 +23,8 @@ import { ReentrantCall, TargetCallFailed, IDependencySet, - InvalidChainId + InvalidChainId, + InvalidEntrypoint } from "src/L2/L2ToL2CrossDomainMessenger.sol"; // Interfaces @@ -62,6 +63,22 @@ contract L2ToL2CrossDomainMessengerWithModifiableTransientStorage is L2ToL2Cross tstore(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT, _source) } } + + /// @dev Sets the cross domain messenger entrypoint in transient storage. + /// @param _entrypoint Entrypoint address to set. + function setCrossDomainMessageEntrypoint(address _entrypoint) external { + assembly { + tstore(CROSS_DOMAIN_MESSAGE_ENTRYPOINT_SLOT, _entrypoint) + } + } + + /// @dev Sets the cross domain messenger nonce in transient storage. + /// @param _nonce Nonce to set. + function setCrossDomainMessageNonce(uint256 _nonce) external { + assembly { + tstore(CROSS_DOMAIN_MESSAGE_NONCE_SLOT, _nonce) + } + } } /// @title L2ToL2CrossDomainMessengerTest @@ -83,7 +100,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { L2ToL2CrossDomainMessengerWithModifiableTransientStorage(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); } - /// @dev Tests that `sendMessage` succeeds and emits the correct event. + /// @dev Tests that `sendMessage(uint256,address,bytes)` succeeds and emits the correct event. function testFuzz_sendMessage_succeeds(uint256 _destination, address _target, bytes calldata _message) external { // Ensure the destination is not the same as the source, otherwise the function will revert vm.assume(_destination != block.chainid); @@ -109,7 +126,61 @@ contract L2ToL2CrossDomainMessengerTest is Test { assertEq( msgHash, Hashing.hashL2toL2CrossDomainMessage( - _destination, block.chainid, messageNonce, address(this), _target, _message + _destination, block.chainid, messageNonce, address(this), _target, address(0), _message + ) + ); + + // Check that the event was emitted with the correct parameters + Vm.Log[] memory logs = vm.getRecordedLogs(); + assertEq(logs.length, 1); + + // topics + assertEq(logs[0].topics[0], L2ToL2CrossDomainMessenger.SentMessage.selector); + assertEq(logs[0].topics[1], bytes32(_destination)); + assertEq(logs[0].topics[2], bytes32(uint256(uint160(_target)))); + assertEq(logs[0].topics[3], bytes32(messageNonce)); + + // data + assertEq(logs[0].data, abi.encode(address(this), address(0), _message)); + + // Check that the message nonce has been incremented + assertEq(l2ToL2CrossDomainMessenger.messageNonce(), messageNonce + 1); + } + + /// @dev Tests that `sendMessage(uint256,address,bytes,address)` succeeds and emits the correct event. + function testFuzz_sendMessage_withCustomEntrypoint_succeeds( + uint256 _destination, + address _target, + bytes calldata _message, + address _entrypoint + ) + external + { + // Ensure the destination is not the same as the source, otherwise the function will revert + vm.assume(_destination != block.chainid); + + // Ensure that the target contract is not CrossL2Inbox or L2ToL2CrossDomainMessenger + vm.assume(_target != Predeploys.CROSS_L2_INBOX && _target != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + // Mock the call over the `isInDependencySet` function to return true + vm.mockCall( + Predeploys.L1_BLOCK_ATTRIBUTES, + abi.encodeCall(IDependencySet.isInDependencySet, (_destination)), + abi.encode(true) + ); + + // Get the current message nonce + uint256 messageNonce = l2ToL2CrossDomainMessenger.messageNonce(); + + // Look for correct emitted event + vm.recordLogs(); + + // Call the sendMessage function + bytes32 msgHash = l2ToL2CrossDomainMessenger.sendMessage(_destination, _target, _entrypoint, _message); + assertEq( + msgHash, + Hashing.hashL2toL2CrossDomainMessage( + _destination, block.chainid, messageNonce, address(this), _target, _entrypoint, _message ) ); @@ -124,13 +195,13 @@ contract L2ToL2CrossDomainMessengerTest is Test { 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), _entrypoint, _message)); // Check that the message nonce has been incremented assertEq(l2ToL2CrossDomainMessenger.messageNonce(), messageNonce + 1); } - /// @dev Tests that the `sendMessage` function reverts when sending a ETH + /// @dev Tests that the `sendMessage(uint256,address,bytes)` function reverts when sending a ETH function testFuzz_sendMessage_nonPayable_reverts( uint256 _destination, address _target, @@ -152,15 +223,18 @@ contract L2ToL2CrossDomainMessengerTest is Test { vm.deal(address(this), _value); // Call the sendMessage function with value to provoke revert + // NOTE: using encodeWithSignature to target the correct overloaded function signature + // nosemgrep: sol-style-use-abi-encodecall (bool success,) = address(l2ToL2CrossDomainMessenger).call{ value: _value }( - abi.encodeCall(l2ToL2CrossDomainMessenger.sendMessage, (_destination, _target, _message)) + abi.encodeWithSignature("sendMessage(uint256, address, bytes)", _destination, _target, _message) ); // Check that the function reverts assertFalse(success); } - /// @dev Tests that the `sendMessage` function reverts when destination is the same as the source chain. + /// @dev Tests that the `sendMessage(uint256,address,bytes)` function reverts when destination is the same as the + /// source chain. function testFuzz_sendMessage_destinationSameChain_reverts(address _target, bytes calldata _message) external { // Expect a revert with the MessageDestinationSameChain selector vm.expectRevert(MessageDestinationSameChain.selector); @@ -169,7 +243,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { l2ToL2CrossDomainMessenger.sendMessage({ _destination: block.chainid, _target: _target, _message: _message }); } - /// @dev Tests that the `sendMessage` function reverts when the target is CrossL2Inbox. + /// @dev Tests that the `sendMessage(uint256,address,bytes)` function reverts when the target is CrossL2Inbox. function testFuzz_sendMessage_targetCrossL2Inbox_reverts(uint256 _destination, bytes calldata _message) external { // Ensure the destination is not the same as the source, otherwise the function will revert regardless of target vm.assume(_destination != block.chainid); @@ -185,7 +259,8 @@ contract L2ToL2CrossDomainMessengerTest is Test { }); } - /// @dev Tests that the `sendMessage` function reverts when the target is L2ToL2CrossDomainMessenger. + /// @dev Tests that the `sendMessage(uint256,address,bytes)` function reverts when the target is + /// L2ToL2CrossDomainMessenger. function testFuzz_sendMessage_targetL2ToL2CrossDomainMessenger_reverts( uint256 _destination, bytes calldata _message @@ -206,6 +281,50 @@ contract L2ToL2CrossDomainMessengerTest is Test { }); } + /// @dev Tests that the `relayMessage` function reverts if called by an address other than the specified entrypoint. + function testFuzz_relayMessage_wrongEntrypoint_reverts( + uint256 _source, + uint256 _nonce, + address _sender, + bytes calldata _message, + address _entrypoint, + uint256 _value, + uint256 _blockNum, + uint256 _logIndex, + uint256 _time, + address _caller + ) + external + { + address _target = makeAddr("target"); + + // Ensure entrypoint is not address(0) or the caller + vm.assume(_entrypoint != _caller && _entrypoint != address(0)); + + // Construct the SentMessage payload & identifier + 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, address(_entrypoint), _message) // data + ); + + // Ensure the CrossL2Inbox validates this message + // nosemgrep: sol-style-use-abi-encodecall + vm.mockCall({ + callee: Predeploys.CROSS_L2_INBOX, + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), + returnData: "" + }); + + // Expect a revert with the InvalidEntrypoint selector + vm.expectRevert(InvalidEntrypoint.selector); + + // Call + hoax(_caller, _value); + l2ToL2CrossDomainMessenger.relayMessage{ value: _value }(id, sentMessage); + } + /// @notice Tests the `sendMessage` function reverts when the `destination` is not in the dependency set. function testFuzz_sendMessage_notInDependencySet_reverts( uint256 _destination, @@ -268,7 +387,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { 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, address(0), _message) // data ); // Ensure the CrossL2Inbox validates this message @@ -278,23 +397,72 @@ contract L2ToL2CrossDomainMessengerTest is Test { returnData: "" }); + bytes32 msgHash = _getMessageHash(_source, _nonce, _sender, _target, address(0), _message); + // Look for correct emitted event vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); - emit L2ToL2CrossDomainMessenger.RelayedMessage( - _source, _nonce, keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message)) - ); + emit L2ToL2CrossDomainMessenger.RelayedMessage(_source, _nonce, msgHash); // relay the message hoax(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _value); l2ToL2CrossDomainMessenger.relayMessage{ value: _value }(id, sentMessage); - assertEq( - l2ToL2CrossDomainMessenger.successfulMessages( - keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message)) - ), - true + assertEq(l2ToL2CrossDomainMessenger.successfulMessages(msgHash), true); + } + + function testFuzz_relayMessage_customEntryPoint_succeeds( + uint256 _source, + uint256 _nonce, + address _sender, + address _target, + bytes calldata _message, + address _entrypoint, + uint256 _value, + uint256 _blockNum, + uint256 _logIndex, + uint256 _time + ) + external + { + // Ensure that the target contract is not CrossL2Inbox or L2ToL2CrossDomainMessenger + vm.assume(_target != Predeploys.CROSS_L2_INBOX && _target != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + + // Ensure _entrypoint is not address(0) + vm.assume(_entrypoint != address(0)); + + // Ensure that the target call is payable if value is sent + if (_value > 0) assumePayable(_target); + + // Ensure that the target contract does not revert + vm.mockCall({ callee: _target, msgValue: _value, data: _message, returnData: abi.encode(true) }); + + // Construct the SentMessage payload & identifier + 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, _entrypoint, _message) // data ); + + // Ensure the CrossL2Inbox validates this message + vm.mockCall({ + callee: Predeploys.CROSS_L2_INBOX, + data: abi.encodeCall(ICrossL2Inbox.validateMessage, (id, keccak256(sentMessage))), + returnData: "" + }); + + bytes32 msgHash = _getMessageHash(_source, _nonce, _sender, _target, _entrypoint, _message); + + // Look for correct emitted event + vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); + emit L2ToL2CrossDomainMessenger.RelayedMessage(_source, _nonce, msgHash); + + // relay the message + hoax(_entrypoint, _value); + l2ToL2CrossDomainMessenger.relayMessage{ value: _value }(id, sentMessage); + assertEq(l2ToL2CrossDomainMessenger.successfulMessages(msgHash), true); } + /// @dev Tests that the `relayMessage` function reverts when the message has not been sent. function testFuzz_relayMessage_eventPayloadNotSentMessage_reverts( uint256 _source, uint256 _nonce, @@ -361,7 +529,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { 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 msgHash = _getMessageHash(_source, _nonce, _sender, target, address(0), message); // Look for correct emitted event vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER); @@ -375,7 +543,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { 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, address(0), message) // data ); // Ensure the CrossL2Inbox validates this message @@ -432,7 +600,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { 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, address(0), message) // data ); // Ensure the CrossL2Inbox validates this message @@ -468,7 +636,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { Identifier memory id = Identifier(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, 1, 1, 1, _source); bytes memory sentMessage = abi.encodePacked( abi.encode(L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, address(0), _nonce), // topics - abi.encode(_sender, "") // data + abi.encode(_sender, address(0), "") // data ); l2ToL2CrossDomainMessenger.relayMessage(id, sentMessage); @@ -507,7 +675,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { 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, address(0), message) // data ); // Ensure the CrossL2Inbox validates this message @@ -556,7 +724,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { 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, address(0), _message) // data ); // Call @@ -589,7 +757,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { 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, address(0), _message) // data ); // Ensure the CrossL2Inbox validates this message @@ -628,7 +796,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { abi.encode( L2ToL2CrossDomainMessenger.SentMessage.selector, block.chainid, Predeploys.CROSS_L2_INBOX, _nonce ), // topics - abi.encode(_sender, _message) // data + abi.encode(_sender, address(0), _message) // data ); // Ensure the CrossL2Inbox validates this message @@ -670,7 +838,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, _nonce ), // topics - abi.encode(_sender, _message) // data + abi.encode(_sender, address(0), _message) // data ); // Ensure the CrossL2Inbox validates this message @@ -716,14 +884,14 @@ contract L2ToL2CrossDomainMessengerTest is Test { // 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)) + _source, _nonce, _getMessageHash(_source, _nonce, _sender, _target, address(0), _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, address(0), _message) // data ); // Ensure the CrossL2Inbox validates this message @@ -773,7 +941,7 @@ contract L2ToL2CrossDomainMessengerTest is Test { 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, address(0), _message) // data ); // Ensure the CrossL2Inbox validates this message @@ -837,8 +1005,63 @@ contract L2ToL2CrossDomainMessengerTest is Test { l2ToL2CrossDomainMessenger.crossDomainMessageSource(); } + /// @dev Tests that the `crossDomainMessageEntrypoint` function returns the correct value. + function testFuzz_crossDomainMessageEntrypoint_succeeds(address _entrypoint) 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); + // Set cross domain message entrypoint in the transient storage + l2ToL2CrossDomainMessenger.setCrossDomainMessageEntrypoint(_entrypoint); + // Check that the `crossDomainMessageEntrypoint` function returns the correct value + assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageEntrypoint(), _entrypoint); + } + + /// @dev Tests that the `crossDomainMessageEntrypoint` function reverts when not entered. + function test_crossDomainMessageEntrypoint_notEntered_reverts() external { + // Ensure that the contract is not entered + assertEq(l2ToL2CrossDomainMessenger.entered(), false); + + // Expect a revert with the NotEntered selector + vm.expectRevert(NotEntered.selector); + + // Call `crossDomainMessageEntrypoint` to provoke revert + l2ToL2CrossDomainMessenger.crossDomainMessageEntrypoint(); + } + + /// @dev Tests that the `crossDomainMessageNonce` function returns the correct value. + function testFuzz_crossDomainMessageNonce_succeeds(uint256 _nonce) 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); + // Set cross domain message nonce in the transient storage + l2ToL2CrossDomainMessenger.setCrossDomainMessageNonce(_nonce); + // Check that the `crossDomainMessageNonce` function returns the correct value + assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageNonce(), _nonce); + } + + /// @dev Tests that the `crossDomainMessageNonce` function reverts when not entered. + function test_crossDomainMessageNonce_notEntered_reverts() external { + // Ensure that the contract is not entered + assertEq(l2ToL2CrossDomainMessenger.entered(), false); + + // Expect a revert with the NotEntered selector + vm.expectRevert(NotEntered.selector); + + // Call `crossDomainMessageNonce` to provoke revert + l2ToL2CrossDomainMessenger.crossDomainMessageNonce(); + } + /// @dev 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, + address _entrypoint, + uint256 _nonce + ) + external + { // Set `entered` to non-zero value to prevent NotEntered revert l2ToL2CrossDomainMessenger.setEntered(1); // Ensure that the contract is now entered @@ -847,12 +1070,16 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Set cross domain message source in the transient storage l2ToL2CrossDomainMessenger.setCrossDomainMessageSender(_sender); l2ToL2CrossDomainMessenger.setCrossDomainMessageSource(_source); + l2ToL2CrossDomainMessenger.setCrossDomainMessageEntrypoint(_entrypoint); + l2ToL2CrossDomainMessenger.setCrossDomainMessageNonce(_nonce); // Check that the `crossDomainMessageContext` function returns the correct value - (address crossDomainContextSender, uint256 crossDomainContextSource) = + (address crossDomainContextSender, uint256 crossDomainContextSource, address entrypoint, uint256 nonce) = l2ToL2CrossDomainMessenger.crossDomainMessageContext(); assertEq(crossDomainContextSender, _sender); assertEq(crossDomainContextSource, _source); + assertEq(entrypoint, _entrypoint); + assertEq(nonce, _nonce); } /// @dev Tests that the `crossDomainMessageContext` function reverts when not entered. @@ -866,4 +1093,20 @@ contract L2ToL2CrossDomainMessengerTest is Test { // Call `crossDomainMessageContext` to provoke revert l2ToL2CrossDomainMessenger.crossDomainMessageContext(); } + + /// @dev Gets the hash of a message based on the message parameters. + function _getMessageHash( + uint256 _source, + uint256 _nonce, + address _sender, + address _target, + address _entrypoint, + bytes memory _message + ) + internal + view + returns (bytes32) + { + return keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _entrypoint, _message)); + } } diff --git a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol index 2a63961ce414..82b243478758 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainTokenBridge.t.sol @@ -121,10 +121,11 @@ contract SuperchainTokenBridgeTest is CommonTest { // Mock the call over the `sendMessage` function and expect it to be called properly bytes memory _message = abi.encodeCall(superchainTokenBridge.relayERC20, (address(superchainERC20), _sender, _to, _amount)); + // nosemgrep: sol-style-use-abi-encodecall _mockAndExpect( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall( - IL2ToL2CrossDomainMessenger.sendMessage, (_chainId, address(superchainTokenBridge), _message) + abi.encodeWithSignature( + "sendMessage(uint256,address,bytes)", _chainId, address(superchainTokenBridge), _message ), abi.encode(_msgHash) ); @@ -166,6 +167,8 @@ contract SuperchainTokenBridgeTest is CommonTest { function testFuzz_relayERC20_notCrossDomainSender_reverts( address _crossDomainMessageSender, uint256 _source, + address _entrypoint, + uint256 _nonce, address _to, uint256 _amount ) @@ -177,7 +180,7 @@ contract SuperchainTokenBridgeTest is CommonTest { vm.mockCall( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()), - abi.encode(_crossDomainMessageSender, _source) + abi.encode(_crossDomainMessageSender, _source, _entrypoint, _nonce) ); // Expect the revert with `InvalidCrossDomainSender` selector @@ -189,14 +192,23 @@ contract SuperchainTokenBridgeTest is CommonTest { } /// @notice Tests the `relayERC20` mints the proper amount and emits the `RelayERC20` event. - function testFuzz_relayERC20_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public { + function testFuzz_relayERC20_succeeds( + address _from, + address _to, + uint256 _amount, + uint256 _source, + address _entrypoint, + uint256 _nonce + ) + public + { vm.assume(_to != ZERO_ADDRESS); // Mock the call over the `crossDomainMessageContext` function setting the same address as value _mockAndExpect( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()), - abi.encode(address(superchainTokenBridge), _source) + abi.encode(address(superchainTokenBridge), _source, _entrypoint, _nonce) ); // Get the total supply and balance of `_to` before the relay to compare later on the assertions diff --git a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol index bc59c76c116f..46ade70b81ac 100644 --- a/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol +++ b/packages/contracts-bedrock/test/L2/SuperchainWETH.t.sol @@ -525,9 +525,10 @@ contract SuperchainWETH_Test is CommonTest { // Mock the call over the `sendMessage` function and expect it to be called properly bytes memory _message = abi.encodeCall(superchainWeth.relayETH, (_sender, _to, _amount)); + // nosemgrep: sol-style-use-abi-encodecall _mockAndExpect( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, - abi.encodeCall(IL2ToL2CrossDomainMessenger.sendMessage, (_chainId, address(superchainWeth), _message)), + abi.encodeWithSignature("sendMessage(uint256,address,bytes)", _chainId, address(superchainWeth), _message), abi.encode(_msgHash) ); @@ -584,6 +585,8 @@ contract SuperchainWETH_Test is CommonTest { function testFuzz_relayETH_notCrossDomainSender_reverts( address _crossDomainMessageSender, uint256 _source, + address _entrypoint, + uint256 _nonce, address _to, uint256 _amount ) @@ -595,7 +598,7 @@ contract SuperchainWETH_Test is CommonTest { vm.mockCall( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()), - abi.encode(_crossDomainMessageSender, _source) + abi.encode(_crossDomainMessageSender, _source, _entrypoint, _nonce) ); // Expect the revert with `InvalidCrossDomainSender` selector @@ -612,7 +615,9 @@ contract SuperchainWETH_Test is CommonTest { address _from, address _to, uint256 _amount, - uint256 _source + uint256 _source, + address _entrypoint, + uint256 _nonce ) public { @@ -636,7 +641,7 @@ contract SuperchainWETH_Test is CommonTest { _mockAndExpect( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()), - abi.encode(address(superchainWeth), _source) + abi.encode(address(superchainWeth), _source, _entrypoint, _nonce) ); // Expect to not call the `mint` function in the `ETHLiquidity` contract vm.expectCall(Predeploys.ETH_LIQUIDITY, abi.encodeCall(IETHLiquidity.mint, (_amount)), 0); @@ -652,7 +657,16 @@ contract SuperchainWETH_Test is CommonTest { } /// @notice Tests the `relayETH` function relays the proper amount of ETH and emits the `RelayETH` event. - function testFuzz_relayETH_succeeds(address _from, address _to, uint256 _amount, uint256 _source) public { + function testFuzz_relayETH_succeeds( + address _from, + address _to, + uint256 _amount, + uint256 _source, + address _entrypoint, + uint256 _nonce + ) + public + { // Assume vm.assume(_to != ZERO_ADDRESS); assumePayable(_to); @@ -665,7 +679,7 @@ contract SuperchainWETH_Test is CommonTest { _mockAndExpect( Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, abi.encodeCall(IL2ToL2CrossDomainMessenger.crossDomainMessageContext, ()), - abi.encode(address(superchainWeth), _source) + abi.encode(address(superchainWeth), _source, _entrypoint, _nonce) ); uint256 _toBalanceBefore = _to.balance;