Commit 472322e8 authored by Diego's avatar Diego Committed by GitHub

feat/L2ToL2CrossDomainMessenger: create (#10297)

* contracts-bedrock: create CrossL2Inbox

* contracts-bedrock: create ICrossL2Inbox

* contracts-bedrock: create tests for CrossL2Inbox

* contracts-bedrock: update CrossL2Inbox sol version to ^0.8.24

* contracts-bedrock: rename test to .t.sol ext

* contracts-bedrock: make snapshots

* contracts-bedrock: update semver-lock

* contracts-bedrock: drop snapshots for CrossL2InboxTest

* contracts-bedrock: update license for tests CrossL2Inbox

* contracts-bedrock: add CrossL2Inbox to predeploys

* contracts-bedrock: pin sol version of CrossL2Inbox to 0.8.25

* contracts-bedrock: update semver-lock for CrossL2Inbox

* contracts-bedrock: add CROSS_L2_INBOX to predeploys

* contracts-bedrock: make slots internal in CrossL2Inbox

* contracts-bedrock: add custom errors to CrossL2Inbox

* contracts-bedrock: refactor tests for CrossL2Inbox

* contracts-bedrock: use TransientContext in CrossL2Inbox

* contracts-bedrock: fix L2Genesis test

* contracts-bedrock: minor tweaks to documentation in tests for CrossL2Inbox

* contracts-bedrock: relabel BLOCKNUMBER_SLOT to BLOCK_NUMBER_SLOT in CrossL2Inbox

* contracts-bedrock: update snapshots for CrossL2Inbox

* contracts-bedrock: update semver-lock for CrossL2Inbox

* contracts-bedrock: improve documentation for CrossL2Inbox

* contracts-bedrock: update semver-lock for CrossL2Inbox

* contracts-bedrock: fix tests for CrossL2Inbox

* contracts-bedrock: update modifier in CrossL2Inbox

* contracts-bedrock: drop arguments in custom errors for CrossL2Inbox

* contracts-bedrock: update snapshots for CrossL2Inbox

* contracts-bedrock: update semver-lock for CrossL2Inbox

* contracts-bedrock: fix tests for CrossL2Inbox

* contracts-bedrock: remove redundant lines in CrossL2Inbox

* contracts-bedrock: add tests for CrossL2Inbox

* contracts-bedrock: update semver-lock for CrossL2Inbox

* contracts-bedrock: minor improvements to tests for CrossL2Inbox

* contracts-bedrock: remove ENTERED_SLOT in CrossL2Inbox

* contracts-bedrock: remove CrossL2Inbox from isSupportedPredeploy

* contracts-bedrock: update semver-lock for CrossL2Inbox

* contracts-bedrock: update CrossL2Inbox with eip3074 specs

* contracts-bedrock: update tests for CrossL2Inbox with eip3074 specs

* contracts-bedrock: improve doc in tests for CrossL2Inbox

* contracts-bedrock: update snapshots for CrossL2Inbox

* contracts-bedrock: update semver-lock for CrossL2Inbox

* contracts-bedrock: create L2ToL2CrossDomainMessenger

* contracts-bedrock: create IL2ToL2CrossDomainMessenger

* contracts-bedrock: create tests for L2ToL2CrossDomainMessenger

* contracts-bedrock: make snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: add semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: add L2ToL2CrossDomainMessenger to predeploys

* contracts-bedrock: make slots in L2ToL2CrossDomainMessenger internal

* contracts-bedrock: add reentrancy protection to L2ToL2CrossDomainMessenger

* contracts-bedrock: pin sol version of L2ToL2CrossDomainMessenger to 0.8.25

* contracts-bedrock: add custom errors to L2ToL2CrossDomainMessenger

* contracts-bedrock: add missing documentation in L2ToL2CrossDomainMessenger

* contracts-bedrock: reorder imports for L2ToL2CrossDomainMessenger

* contracts-bedrock: rename errors for L2ToL2CrossDomainMessenger

* contracts-bedrock: reorder vars in L2ToL2CrossDomainMessenger

* contracts-bedrock: refactor tests for L2ToL2CrossDomainMessenger

* contracts-bedrock: add L2_TO_L2_CROSS_DOMAIN_MESSENGER to predeploys

* contracts-bedrock: fix tests for L2ToL2CrossDomainMessenger

* contracts-bedrock: make snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: reorder functions in L2ToL2CrossDomainMessenger

* contracts-bedrock: mock calls to target contract for L2ToL2CrossDomainMessenger tests

* contracts-bedrock: add additional checks to sendMessage in L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: minor tweaks to tests for L2ToL2CrossDomainMessenger

* contracts-bedrock: add TransientContext to L2ToL2CrossDomainMessenger

* contracts-bedrock: add reentrancy test for L2ToL2CrossDomainMessenger

* contracts-bedrock: improve tests for L2ToL2CrossDomainMessenger

* contracts-bedrock: improve documentation of tests for L2ToL2CrossDomainMessenger

* contracts-bedrock: fix L2Genesis test

* contracts-bedrock: update snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: improve documentation for L2ToL2CrossDomainMessenger

* contracts-bedrock: fix test for L2ToL2CrossDomainMessenger

* contracts-bedrock: update snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: fix tests for L2ToL2CrossDomainMessenger

* contracts-bedrock: update modifier in L2ToL2CrossDomainMessenger

* contracts-bedrock: remove legacy line in L2ToL2CrossDomainMessenger

* contracts-bedrock: add additional tests for L2ToL2CrossDomainMessenger

* contracts-bedrock: add assumePayable in tests for L2ToL2CrossDomainMessenger

* contracts-bedrock: fix test for L2ToL2CrossDomainMessenger

* contracts-bedrock: remove args from custom errors in L2ToL2CrossDomainMessenger

* contracts-bedrock: update snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: remove ENTERED_SLOT in L2ToL2CrossDomainMessenger

* contracts-bedrock: remove L2ToL2CrossDomainMessenger from isSupportedPredeploy

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: update L2ToL2CrossDomainMessenger with eip3074 specs

* contracts-bedrock: add tests for 3074 changes on L2ToL2CrossDomainMessenger

* contracts-bedrock: add missing checks for L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: make SentMessage anonymous in L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: update snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: fix title for TransientContext

* contracts-bedrock: use messageVersion instead of MESSAGE_VERSION in L2ToL2CrossDomainMessenger

* contracts-bedrock: reintroduce ReentrancyGuard to L2ToL2CrossDomainMessenger

* contracts-bedrock: update snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for contracts-bedrock: update snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: remove TransientContext from L2ToL2CrossDomainMessenger

* contracts-bedrock: update snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: fix tests for L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: fix tstore in L2ToL2CrossDomainMessenger

* contracts-bedrock: improve logic in L2ToL2CrossDomainMessenger

* contracts-bedrock: update snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock L2ToL2CrossDomainMessenger

* contracts-bedrock: improve logic for L2ToL2CrossDomainMessenger

* contracts-bedrock: update snapshots for L2ToL2CrossDomainMessenger

* contracts-bedrock: update semver-lock for L2ToL2CrossDomainMessenger

* contracts-bedrock: simplify vars in tests for L2ToL2CrossDomainMessenger
parent acd8cf8a
...@@ -87,6 +87,10 @@ ...@@ -87,6 +87,10 @@
"initCodeHash": "0x08bbede75cd6dfd076903b8f04d24f82fa7881576c135825098778632e37eebc", "initCodeHash": "0x08bbede75cd6dfd076903b8f04d24f82fa7881576c135825098778632e37eebc",
"sourceCodeHash": "0x8388b9b8075f31d580fed815b66b45394e40fb1a63cd8cda2272d2c390fc908c" "sourceCodeHash": "0x8388b9b8075f31d580fed815b66b45394e40fb1a63cd8cda2272d2c390fc908c"
}, },
"src/L2/L2ToL2CrossDomainMessenger.sol": {
"initCodeHash": "0x26995afaa790e4daa74deb8dc5088b915633d3d037a2feb5cd8dd2ec8d3c51fc",
"sourceCodeHash": "0x0cbdda4623c9c10653332af3c43ee37d9c4a6e4e0214f9a0fb51ae053b0b6220"
},
"src/L2/SequencerFeeVault.sol": { "src/L2/SequencerFeeVault.sol": {
"initCodeHash": "0xd62e193d89b1661d34031227a45ce1eade9c2a89b0bd7f362f511d03cceef294", "initCodeHash": "0xd62e193d89b1661d34031227a45ce1eade9c2a89b0bd7f362f511d03cceef294",
"sourceCodeHash": "0xa304b4b556162323d69662b4dd9a1d073d55ec661494465489bb67f1e465e7b3" "sourceCodeHash": "0xa304b4b556162323d69662b4dd9a1d073d55ec661494465489bb67f1e465e7b3"
......
[
{
"inputs": [],
"name": "crossDomainMessageSender",
"outputs": [
{
"internalType": "address",
"name": "_sender",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "crossDomainMessageSource",
"outputs": [
{
"internalType": "uint256",
"name": "_source",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "messageNonce",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "messageVersion",
"outputs": [
{
"internalType": "uint16",
"name": "",
"type": "uint16"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_destination",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_source",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_nonce",
"type": "uint256"
},
{
"internalType": "address",
"name": "_sender",
"type": "address"
},
{
"internalType": "address",
"name": "_target",
"type": "address"
},
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
}
],
"name": "relayMessage",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_destination",
"type": "uint256"
},
{
"internalType": "address",
"name": "_target",
"type": "address"
},
{
"internalType": "bytes",
"name": "_message",
"type": "bytes"
}
],
"name": "sendMessage",
"outputs": [],
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [
{
"internalType": "bytes32",
"name": "",
"type": "bytes32"
}
],
"name": "successfulMessages",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "version",
"outputs": [
{
"internalType": "string",
"name": "",
"type": "string"
}
],
"stateMutability": "view",
"type": "function"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "messageHash",
"type": "bytes32"
}
],
"name": "FailedRelayedMessage",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "bytes32",
"name": "messageHash",
"type": "bytes32"
}
],
"name": "RelayedMessage",
"type": "event"
},
{
"anonymous": true,
"inputs": [
{
"indexed": false,
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "SentMessage",
"type": "event"
},
{
"inputs": [],
"name": "CrossL2InboxOriginNotL2ToL2CrossDomainMessenger",
"type": "error"
},
{
"inputs": [],
"name": "MessageAlreadyRelayed",
"type": "error"
},
{
"inputs": [],
"name": "MessageDestinationNotRelayChain",
"type": "error"
},
{
"inputs": [],
"name": "MessageDestinationSameChain",
"type": "error"
},
{
"inputs": [],
"name": "MessageTargetCrossL2Inbox",
"type": "error"
},
{
"inputs": [],
"name": "MessageTargetL2ToL2CrossDomainMessenger",
"type": "error"
},
{
"inputs": [],
"name": "NotEntered",
"type": "error"
},
{
"inputs": [],
"name": "ReentrantCall",
"type": "error"
},
{
"inputs": [],
"name": "RelayMessageCallerNotCrossL2Inbox",
"type": "error"
}
]
\ No newline at end of file
[
{
"bytes": "32",
"label": "successfulMessages",
"offset": 0,
"slot": "0",
"type": "mapping(bytes32 => bool)"
},
{
"bytes": "30",
"label": "msgNonce",
"offset": 0,
"slot": "1",
"type": "uint240"
}
]
\ No newline at end of file
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/// @title IL2ToL2CrossDomainMessenger
/// @notice Interface for the L2ToL2CrossDomainMessenger contract.
interface IL2ToL2CrossDomainMessenger {
/// @notice Retrieves the sender of the current cross domain message.
/// @return _sender Address of the sender of the current cross domain message.
function crossDomainMessageSender() external view returns (address _sender);
/// @notice Retrieves the source of the current cross domain message.
/// @return _source Chain ID of the source of the current cross domain message.
function crossDomainMessageSource() external view returns (uint256 _source);
/// @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.
function sendMessage(uint256 _destination, address _target, bytes calldata _message) external payable;
/// @notice Relays a message that was sent by the other CrossDomainMessenger contract. Can only
/// be executed via cross-chain call from the other messenger OR if the message was
/// already received once and is currently being replayed.
/// @param _destination Chain ID of the destination chain.
/// @param _nonce Nonce of the message being relayed.
/// @param _sender Address of the user who sent the message.
/// @param _source Chain ID of the source chain.
/// @param _target Address that the message is targeted at.
/// @param _message Message to send to the target.
function relayMessage(
uint256 _destination,
uint256 _source,
uint256 _nonce,
address _sender,
address _target,
bytes calldata _message
)
external
payable;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
import { Encoding } from "src/libraries/Encoding.sol";
import { Predeploys } from "src/libraries/Predeploys.sol";
import { CrossL2Inbox } from "src/L2/CrossL2Inbox.sol";
import { IL2ToL2CrossDomainMessenger } from "src/L2/IL2ToL2CrossDomainMessenger.sol";
import { ISemver } from "src/universal/ISemver.sol";
/// @notice Thrown when a non-written slot in transient storage is attempted to be read from.
error NotEntered();
/// @notice Thrown when attempting to send a message to the chain that the message is being sent from.
error MessageDestinationSameChain();
/// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not CrossL2Inbox.
error RelayMessageCallerNotCrossL2Inbox();
/// @notice Thrown when attempting to relay a message where CrossL2Inbox's origin is not L2ToL2CrossDomainMessenger.
error CrossL2InboxOriginNotL2ToL2CrossDomainMessenger();
/// @notice Thrown when attempting to relay a message whose destination chain is not the chain relaying it.
error MessageDestinationNotRelayChain();
/// @notice Thrown when attempting to relay a message whose target is CrossL2Inbox.
error MessageTargetCrossL2Inbox();
/// @notice Thrown when attempting to relay a message whose target is L2ToL2CrossDomainMessenger.
error MessageTargetL2ToL2CrossDomainMessenger();
/// @notice Thrown when attempting to relay a message that has already been relayed.
error MessageAlreadyRelayed();
/// @notice Thrown when a reentrant call is detected.
error ReentrantCall();
/// @custom:proxied
/// @custom:predeploy 0x4200000000000000000000000000000000000023
/// @title L2ToL2CrossDomainMessenger
/// @notice The L2ToL2CrossDomainMessenger is a higher level abstraction on top of the CrossL2Inbox that provides
/// 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 IL2ToL2CrossDomainMessenger, ISemver {
/// @notice Storage slot for `entered` value.
/// Equal to bytes32(uint256(keccak256("l2tol2crossdomainmessenger.entered")) - 1)
bytes32 internal constant ENTERED_SLOT = 0xf53fc38c5e461bdcbbeb47887fecf014abd399293109cd50f65e5f9078cfd025;
/// @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 =
0xb83444d07072b122e2e72a669ce32857d892345c19856f4e7142d06a167ab3f3;
/// @notice Storage slot for the source of the current cross domain message.
/// Equal to bytes32(uint256(keccak256("l2tol2crossdomainmessenger.source")) - 1)
bytes32 internal constant CROSS_DOMAIN_MESSAGE_SOURCE_SLOT =
0x711dfa3259c842fffc17d6e1f1e0fc5927756133a2345ca56b4cb8178589fee7;
/// @notice Current message version identifier.
uint16 public constant messageVersion = uint16(0);
/// @notice Semantic version.
/// @custom:semver 1.0.0
string public constant version = "1.0.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.
mapping(bytes32 => bool) public successfulMessages;
/// @notice Nonce for the next message to be sent, without the message version applied. Use the messageNonce getter,
/// which will insert the message version into the nonce to give you the actual nonce to be used for the
/// message.
uint240 internal msgNonce;
/// @notice Emitted whenever a message is sent to the other chain.
/// @param data Encoded data of the message that was sent.
event SentMessage(bytes data) anonymous;
/// @notice Emitted whenever a message is successfully relayed on this chain.
/// @param messageHash Hash of the message that was relayed.
event RelayedMessage(bytes32 indexed messageHash);
/// @notice Emitted whenever a message fails to be relayed on this chain.
/// @param messageHash Hash of the message that failed to be relayed.
event FailedRelayedMessage(bytes32 indexed messageHash);
/// @notice Enforces that a function cannot be re-entered.
modifier nonReentrant() {
if (_entered()) revert ReentrantCall();
assembly {
tstore(ENTERED_SLOT, 1)
}
_;
assembly {
tstore(ENTERED_SLOT, 0)
}
}
/// @notice Enforces that cross domain message sender and source are set. Reverts if not.
/// Used to differentiate between 0 and nil in transient storage.
modifier onlyEntered() {
if (!_entered()) revert NotEntered();
_;
}
/// @notice Retrieves the sender of the current cross domain message. If not entered, reverts.
/// @return _sender Address of the sender of the current cross domain message.
function crossDomainMessageSender() external view onlyEntered returns (address _sender) {
assembly {
_sender := tload(CROSS_DOMAIN_MESSAGE_SENDER_SLOT)
}
}
/// @notice Retrieves the source of the current cross domain message. If not entered, reverts.
/// @return _source Chain ID of the source of the current cross domain message.
function crossDomainMessageSource() external view onlyEntered returns (uint256 _source) {
assembly {
_source := tload(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT)
}
}
/// @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 payload to call target with.
function sendMessage(uint256 _destination, address _target, bytes calldata _message) external payable {
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();
bytes memory data = abi.encodeCall(
L2ToL2CrossDomainMessenger.relayMessage,
(_destination, block.chainid, messageNonce(), msg.sender, _target, _message)
);
emit SentMessage(data);
msgNonce++;
}
/// @notice Relays a message that was sent by the other CrossDomainMessenger contract. Can only be executed via
/// cross-chain call from the other messenger OR if the message was already received once and is currently
/// being replayed.
/// @param _destination Chain ID of the destination chain.
/// @param _source Chain ID of the source chain.
/// @param _nonce Nonce of the message being relayed.
/// @param _sender Address of the user who sent the message.
/// @param _target Address that the message is targeted at.
/// @param _message Message payload to call target with.
function relayMessage(
uint256 _destination,
uint256 _source,
uint256 _nonce,
address _sender,
address _target,
bytes memory _message
)
external
payable
nonReentrant
{
if (msg.sender != Predeploys.CROSS_L2_INBOX) revert RelayMessageCallerNotCrossL2Inbox();
if (CrossL2Inbox(Predeploys.CROSS_L2_INBOX).origin() != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
revert CrossL2InboxOriginNotL2ToL2CrossDomainMessenger();
}
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();
}
bytes32 messageHash = keccak256(abi.encode(_destination, _source, _nonce, _sender, _target, _message));
if (successfulMessages[messageHash]) {
revert MessageAlreadyRelayed();
}
_storeMessageMetadata(_source, _sender);
bool success = _callWithAllGas(_target, _message);
if (success) {
successfulMessages[messageHash] = true;
emit RelayedMessage(messageHash);
} else {
emit FailedRelayedMessage(messageHash);
}
_storeMessageMetadata(0, address(0));
}
/// @notice Retrieves the next message nonce. Message version will be added to the upper two bytes of the message
/// nonce. Message version allows us to treat messages as having different structures.
/// @return Nonce of the next message to be sent, with added message version.
function messageNonce() public view returns (uint256) {
return Encoding.encodeVersionedNonce(msgNonce, messageVersion);
}
/// @notice Retrieves whether the contract is currently entered or not.
/// @return True if the contract is entered, and false otherwise.
function _entered() internal view returns (bool) {
uint256 value;
assembly {
value := tload(ENTERED_SLOT)
}
return value != 0;
}
/// @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 {
assembly {
tstore(CROSS_DOMAIN_MESSAGE_SENDER_SLOT, _sender)
tstore(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT, _source)
}
}
/// @notice Calls the target address with the message payload and all available gas.
/// @param _target Target address to call.
/// @param _message Message payload to call target with.
/// @return _success True if the call was successful, and false otherwise.
function _callWithAllGas(address _target, bytes memory _message) internal returns (bool _success) {
assembly {
_success :=
call(
gas(), // gas
_target, // recipient
callvalue(), // ether value
add(_message, 32), // inloc
mload(_message), // inlen
0, // outloc
0 // outlen
)
}
}
}
...@@ -86,6 +86,9 @@ library Predeploys { ...@@ -86,6 +86,9 @@ library Predeploys {
/// @notice Address of the CrossL2Inbox predeploy. /// @notice Address of the CrossL2Inbox predeploy.
address internal constant CROSS_L2_INBOX = 0x4200000000000000000000000000000000000022; address internal constant CROSS_L2_INBOX = 0x4200000000000000000000000000000000000022;
/// @notice Address of the L2ToL2CrossDomainMessenger predeploy.
address internal constant L2_TO_L2_CROSS_DOMAIN_MESSENGER = 0x4200000000000000000000000000000000000023;
/// @notice Returns the name of the predeploy at the given address. /// @notice Returns the name of the predeploy at the given address.
function getName(address _addr) internal pure returns (string memory out_) { function getName(address _addr) internal pure returns (string memory out_) {
require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy"); require(isPredeployNamespace(_addr), "Predeploys: address must be a predeploy");
...@@ -111,6 +114,7 @@ library Predeploys { ...@@ -111,6 +114,7 @@ library Predeploys {
if (_addr == GOVERNANCE_TOKEN) return "GovernanceToken"; if (_addr == GOVERNANCE_TOKEN) return "GovernanceToken";
if (_addr == LEGACY_ERC20_ETH) return "LegacyERC20ETH"; if (_addr == LEGACY_ERC20_ETH) return "LegacyERC20ETH";
if (_addr == CROSS_L2_INBOX) return "CrossL2Inbox"; if (_addr == CROSS_L2_INBOX) return "CrossL2Inbox";
if (_addr == L2_TO_L2_CROSS_DOMAIN_MESSENGER) return "L2ToL2CrossDomainMessenger";
revert("Predeploys: unnamed predeploy"); revert("Predeploys: unnamed predeploy");
} }
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity ^0.8.24; pragma solidity ^0.8.24;
/// @title Transient /// @title TransientContext
/// @notice Library for transient storage. /// @notice Library for transient storage.
library TransientContext { library TransientContext {
/// @notice Slot for call depth. /// @notice Slot for call depth.
......
// SPDX-License-Identifier: MIT
pragma solidity 0.8.25;
// Testing utilities
import { Test } from "forge-std/Test.sol";
import { Vm } from "forge-std/Vm.sol";
// Libraries
import { Predeploys } from "src/libraries/Predeploys.sol";
// Target contract
import {
L2ToL2CrossDomainMessenger,
NotEntered,
MessageDestinationSameChain,
RelayMessageCallerNotCrossL2Inbox,
CrossL2InboxOriginNotL2ToL2CrossDomainMessenger,
MessageDestinationNotRelayChain,
MessageTargetCrossL2Inbox,
MessageTargetL2ToL2CrossDomainMessenger,
MessageAlreadyRelayed,
ReentrantCall
} from "src/L2/L2ToL2CrossDomainMessenger.sol";
import { CrossL2Inbox } from "src/L2/CrossL2Inbox.sol";
/// @title L2ToL2CrossDomainMessengerWithModifiableTransientStorage
/// @dev L2ToL2CrossDomainMessenger contract with methods to modify the transient storage.
/// This is used to test the transient storage of L2ToL2CrossDomainMessenger.
contract L2ToL2CrossDomainMessengerWithModifiableTransientStorage is L2ToL2CrossDomainMessenger {
/// @dev Returns the value of the entered slot in transient storage.
/// @return Value of the entered slot.
function entered() external view returns (bool) {
return _entered();
}
/// @dev Sets the entered slot value in transient storage.
/// @param _value Value to set.
function setEntered(uint256 _value) external {
assembly {
tstore(ENTERED_SLOT, _value)
}
}
/// @dev Sets the cross domain messenger sender in transient storage.
/// @param _sender Sender address to set.
function setCrossDomainMessageSender(address _sender) external {
assembly {
tstore(CROSS_DOMAIN_MESSAGE_SENDER_SLOT, _sender)
}
}
/// @dev Sets the cross domain messenger source in transient storage.
/// @param _source Source chain ID to set.
function setCrossDomainMessageSource(uint256 _source) external {
assembly {
tstore(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT, _source)
}
}
}
/// @title L2ToL2CrossDomainMessengerTest
/// @dev Contract for testing the L2ToL2CrossDomainMessenger contract.
contract L2ToL2CrossDomainMessengerTest is Test {
/// @dev L2ToL2CrossDomainMessenger contract instance with modifiable transient storage.
L2ToL2CrossDomainMessengerWithModifiableTransientStorage l2ToL2CrossDomainMessenger;
/// @dev Sets up the test suite.
function setUp() public {
// Deploy the L2ToL2CrossDomainMessenger contract
vm.etch(
Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
address(new L2ToL2CrossDomainMessengerWithModifiableTransientStorage()).code
);
l2ToL2CrossDomainMessenger =
L2ToL2CrossDomainMessengerWithModifiableTransientStorage(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
}
/// @dev Tests that `sendMessage` succeeds and emits the correct SentMessage event.
function testFuzz_sendMessage_succeeds(
uint256 _destination,
address _target,
bytes calldata _message,
uint256 _value
)
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);
// Get the current message nonce
uint256 messageNonce = l2ToL2CrossDomainMessenger.messageNonce();
// Add sufficient value to the contract to send the message with
vm.deal(address(this), _value);
// Look for correct emitted event
vm.recordLogs();
// Call the sendMessage function
l2ToL2CrossDomainMessenger.sendMessage{ value: _value }({
_destination: _destination,
_target: _target,
_message: _message
});
// Check that the SentMessage event was emitted with the correct parameters
Vm.Log[] memory logs = vm.getRecordedLogs();
assertEq(logs.length, 1);
assertEq(
abi.decode(logs[0].data, (bytes)),
abi.encodeCall(
L2ToL2CrossDomainMessenger.relayMessage,
(_destination, block.chainid, messageNonce, address(this), _target, _message)
)
);
// Check that the message nonce has been incremented
assertEq(l2ToL2CrossDomainMessenger.messageNonce(), messageNonce + 1);
}
/// @dev Tests that the `sendMessage` 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);
// Call `sendMessage` with the current chain as the destination to prevent revert due to invalid destination
l2ToL2CrossDomainMessenger.sendMessage({ _destination: block.chainid, _target: _target, _message: _message });
}
/// @dev Tests that the `sendMessage` 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);
// Expect a revert with the MessageTargetCrossL2Inbox selector
vm.expectRevert(MessageTargetCrossL2Inbox.selector);
// Call `senderMessage` with the CrossL2Inbox as the target to provoke revert
l2ToL2CrossDomainMessenger.sendMessage({
_destination: _destination,
_target: Predeploys.CROSS_L2_INBOX,
_message: _message
});
}
/// @dev Tests that the `sendMessage` function reverts when the target is L2ToL2CrossDomainMessenger.
function testFuzz_sendMessage_targetL2ToL2CrossDomainMessenger_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);
// Expect a revert with the MessageTargetL2ToL2CrossDomainMessenger selector
vm.expectRevert(MessageTargetL2ToL2CrossDomainMessenger.selector);
// Call `senderMessage` with the L2ToL2CrossDomainMessenger as the target to provoke revert
l2ToL2CrossDomainMessenger.sendMessage({
_destination: _destination,
_target: Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
_message: _message
});
}
/// @dev Tests that the `relayMessage` function succeeds and emits the correct RelayedMessage event.
function testFuzz_relayMessage_succeeds(
uint256 _source,
uint256 _nonce,
address _sender,
address _target,
bytes calldata _message,
uint256 _value
)
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 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) });
// Mock the CrossL2Inbox origin to return the L2ToL2CrossDomainMessenger contract
vm.mockCall({
callee: Predeploys.CROSS_L2_INBOX,
data: abi.encodeWithSelector(CrossL2Inbox.origin.selector),
returnData: abi.encode(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER)
});
// Look for correct emitted event
vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
emit L2ToL2CrossDomainMessenger.RelayedMessage(
keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message))
);
// Ensure the target contract is called with the correct parameters
vm.expectCall({ callee: _target, msgValue: _value, data: _message });
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check and that it has sufficient value
hoax(Predeploys.CROSS_L2_INBOX, _value);
// Call the relayMessage function
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: block.chainid, // ensure the destination is the chain of L2ToL2CrossDomainMessenger
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: _target,
_message: _message
});
// Check that successfulMessages mapping updates the message hash correctly
assertEq(
l2ToL2CrossDomainMessenger.successfulMessages(
keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message))
),
true
);
// Check that entered slot is cleared after the function call
assertEq(l2ToL2CrossDomainMessenger.entered(), false);
// Check that metadata is cleared after the function call. We need to set the `entered` slot to non-zero value
// to prevent NotEntered revert when calling the crossDomainMessageSender and crossDomainMessageSource functions
l2ToL2CrossDomainMessenger.setEntered(1);
assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageSource(), 0);
assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageSender(), address(0));
}
/// @dev Mock target function that checks the source and sender of the message in transient storage.
/// @param _source Source chain ID of the message.
/// @param _sender Sender of the message.
function mockTarget(uint256 _source, address _sender) external payable {
// Ensure that the contract is entered
assertEq(l2ToL2CrossDomainMessenger.entered(), true);
// Ensure that the sender is correct
assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageSource(), _source);
// Ensure that the source is correct
assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageSender(), _sender);
}
/// @dev Tests that the `relayMessage` function succeeds and stores the correct metadata in transient storage.
function testFuzz_relayMessage_metadataStore_succeeds(
uint256 _source,
uint256 _nonce,
address _sender,
uint256 _value
)
external
{
// Since the target is this contract, we want to ensure the payment doesn't lead to overflow, since this
// contract has a non-zero balance. Thus, we set this contract's balance to zero and we hoax afterwards.
vm.deal(address(this), 0);
// Mock the CrossL2Inbox origin to return the L2ToL2CrossDomainMessenger contract
vm.mockCall({
callee: Predeploys.CROSS_L2_INBOX,
data: abi.encodeWithSelector(CrossL2Inbox.origin.selector),
returnData: abi.encode(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER)
});
// Set the target and message for the reentrant call
address target = address(this);
bytes memory message = abi.encodeWithSelector(this.mockTarget.selector, _source, _sender);
// Look for correct emitted event
vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
emit L2ToL2CrossDomainMessenger.RelayedMessage(
keccak256(abi.encode(block.chainid, _source, _nonce, _sender, target, message))
);
// Ensure the target contract is called with the correct parameters
vm.expectCall({ callee: target, msgValue: _value, data: message });
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check and that it has sufficient value
hoax(Predeploys.CROSS_L2_INBOX, _value);
// Call the relayMessage function
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: block.chainid, // ensure the destination is the chain of L2ToL2CrossDomainMessenger
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: target,
_message: message
});
// Check that successfulMessages mapping updates the message hash correctly
assertEq(
l2ToL2CrossDomainMessenger.successfulMessages(
keccak256(abi.encode(block.chainid, _source, _nonce, _sender, target, message))
),
true
);
// Check that entered slot is cleared after the function call
assertEq(l2ToL2CrossDomainMessenger.entered(), false);
// Check that metadata is cleared after the function call. We need to set the `entered` slot to non-zero value
// to prevent NotEntered revert when calling the crossDomainMessageSender and crossDomainMessageSource functions
l2ToL2CrossDomainMessenger.setEntered(1);
assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageSource(), 0);
assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageSender(), address(0));
}
/// @dev Mock reentrant function that calls the `relayMessage` function.
/// @param _source Source chain ID of the message.
/// @param _nonce Nonce of the message.
/// @param _sender Sender of the message.
function mockTargetReentrant(uint256 _source, uint256 _nonce, address _sender) external payable {
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check
vm.prank(Predeploys.CROSS_L2_INBOX);
// Ensure that the contract is entered
assertEq(l2ToL2CrossDomainMessenger.entered(), true);
vm.expectRevert(ReentrantCall.selector);
l2ToL2CrossDomainMessenger.relayMessage({
_destination: block.chainid,
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: address(0),
_message: ""
});
// Ensure the function still reverts if `expectRevert` succeeds
revert();
}
/// @dev Tests that the `relayMessage` function reverts when reentrancy is attempted.
function testFuzz_relayMessage_reentrant_reverts(
uint256 _source1, // source passed to `relayMessage` by the initial call.
address _sender1, // sender passed to `relayMessage` by the initial call.
uint256 _source2, // sender passed to `relayMessage` by the reentrant call.
address _sender2, // sender passed to `relayMessage` by the reentrant call.
uint256 _nonce,
uint256 _value
)
external
{
// Since the target is this contract, we want to ensure the payment doesn't lead to overflow, since this
// contract has a non-zero balance. Thus, we set this contract's balance to zero and we hoax afterwards.
vm.deal(address(this), 0);
// Mock the CrossL2Inbox origin to return the L2ToL2CrossDomainMessenger contract
vm.mockCall({
callee: Predeploys.CROSS_L2_INBOX,
data: abi.encodeWithSelector(CrossL2Inbox.origin.selector),
returnData: abi.encode(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER)
});
// Set the target and message for the reentrant call
address target = address(this);
bytes memory message = abi.encodeWithSelector(this.mockTargetReentrant.selector, _source2, _nonce, _sender2);
// Look for correct emitted event
vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
emit L2ToL2CrossDomainMessenger.FailedRelayedMessage(
keccak256(abi.encode(block.chainid, _source1, _nonce, _sender1, target, message))
);
// Ensure the target contract is called with the correct parameters
vm.expectCall({ callee: target, msgValue: _value, data: message });
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check and that it has sufficient value
hoax(Predeploys.CROSS_L2_INBOX, _value);
// Call the relayMessage function
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: block.chainid, // ensure the destination is the chain of L2ToL2CrossDomainMessenger
_source: _source1,
_nonce: _nonce,
_sender: _sender1,
_target: target,
_message: message
});
// Check that entered slot is cleared after the function call
assertEq(l2ToL2CrossDomainMessenger.entered(), false);
// Check that metadata is cleared after the function call. We need to set the `entered` slot to non-zero value
// to prevent NotEntered revert when calling the crossDomainMessageSender and crossDomainMessageSource functions
l2ToL2CrossDomainMessenger.setEntered(1);
assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageSource(), 0);
assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageSender(), address(0));
}
/// @dev Tests that the `relayMessage` function reverts when the caller is not the CrossL2Inbox contract.
function testFuzz_relayMessage_callerNotCrossL2Inbox_reverts(
uint256 _destination,
uint256 _source,
uint256 _nonce,
address _sender,
address _target,
bytes calldata _message,
uint256 _value
)
external
{
// Add sufficient value to the contract to relay the message with
vm.deal(address(this), _value);
// Expect a revert with the RelayMessageCallerNotCrossL2Inbox selector
vm.expectRevert(RelayMessageCallerNotCrossL2Inbox.selector);
// Call `relayMessage` with the current contract as the caller to provoke revert
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: _destination,
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: _target,
_message: _message
});
}
/// @dev Tests that the `relayMessage` function reverts when CrossL2Inbox's origin is not
/// L2ToL2CrossDomainMessenger.
function testFuzz_relayMessage_crossL2InboxOriginNotL2ToL2CrossDomainMessenger_reverts(
uint256 _destination,
uint256 _source,
uint256 _nonce,
address _sender,
address _target,
bytes calldata _message,
uint256 _value
)
external
{
// Set address(0) as the origin of the CrossL2Inbox contract, which is not the L2ToL2CrossDomainMessenger
vm.mockCall({
callee: Predeploys.CROSS_L2_INBOX,
data: abi.encodeWithSelector(CrossL2Inbox.origin.selector),
returnData: abi.encode(address(0))
});
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check and that it has sufficient value
hoax(Predeploys.CROSS_L2_INBOX, _value);
// Expect a revert with the CrossL2InboxOriginNotL2ToL2CrossDomainMessenger selector
vm.expectRevert(CrossL2InboxOriginNotL2ToL2CrossDomainMessenger.selector);
// Call `relayMessage` with invalid CrossL2Inbox origin to provoke revert
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: _destination,
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: _target,
_message: _message
});
}
/// @dev Tests that the `relayMessage` function reverts when the destination is not the relay chain.
function testFuzz_relayMessage_destinationNotRelayChain_reverts(
uint256 _destination,
uint256 _source,
uint256 _nonce,
address _sender,
address _target,
bytes calldata _message,
uint256 _value
)
external
{
// Ensure the destination is not this chain
vm.assume(_destination != block.chainid);
// Mock the CrossL2Inbox origin to return the L2ToL2CrossDomainMessenger contract
vm.mockCall({
callee: Predeploys.CROSS_L2_INBOX,
data: abi.encodeWithSelector(CrossL2Inbox.origin.selector),
returnData: abi.encode(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER)
});
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check and that it has sufficient value
hoax(Predeploys.CROSS_L2_INBOX, _value);
// Expect a revert with the MessageDestinationNotRelayChain selector
vm.expectRevert(MessageDestinationNotRelayChain.selector);
// Call `relayMessage`
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: _destination,
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: _target,
_message: _message
});
}
/// @dev Tests that the `relayMessage` function reverts when the message target is CrossL2Inbox.
function testFuzz_relayMessage_targetCrossL2Inbox_reverts(
uint256 _source,
uint256 _nonce,
address _sender,
bytes calldata _message,
uint256 _value
)
external
{
// Mock the CrossL2Inbox origin to return the L2ToL2CrossDomainMessenger contract
vm.mockCall({
callee: Predeploys.CROSS_L2_INBOX,
data: abi.encodeWithSelector(CrossL2Inbox.origin.selector),
returnData: abi.encode(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER)
});
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check and that it has sufficient value
hoax(Predeploys.CROSS_L2_INBOX, _value);
// Expect a revert with the MessageTargetCrossL2Inbox selector
vm.expectRevert(MessageTargetCrossL2Inbox.selector);
// Call `relayMessage` with CrossL2Inbox as the target to provoke revert. The current chain is the destination
// to prevent revert due to invalid destination
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: block.chainid,
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: Predeploys.CROSS_L2_INBOX,
_message: _message
});
}
/// @dev Tests that the `relayMessage` function reverts when the message target is L2ToL2CrossDomainMessenger.
function testFuzz_relayMessage_targetL2ToL2CrossDomainMessenger_reverts(
uint256 _source,
uint256 _nonce,
address _sender,
bytes calldata _message,
uint256 _value
)
external
{
// Mock the CrossL2Inbox origin to return the L2ToL2CrossDomainMessenger contract
vm.mockCall({
callee: Predeploys.CROSS_L2_INBOX,
data: abi.encodeWithSelector(CrossL2Inbox.origin.selector),
returnData: abi.encode(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER)
});
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check and that it has sufficient value
hoax(Predeploys.CROSS_L2_INBOX, _value);
// Expect a revert with the MessageTargetL2ToL2CrossDomainMessenger selector
vm.expectRevert(MessageTargetL2ToL2CrossDomainMessenger.selector);
// Call `relayMessage` with L2ToL2CrossDomainMessenger as the target to provoke revert. The current chain is the
// destination to prevent revert due to invalid destination
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: block.chainid,
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER,
_message: _message
});
}
/// @dev Tests that the `relayMessage` function reverts when the message has already been relayed.
function testFuzz_relayMessage_alreadyRelayed_reverts(
uint256 _source,
uint256 _nonce,
address _sender,
address _target,
bytes calldata _message,
uint256 _value
)
external
{
// Ensure that payment doesn't overflow since we send value to L2ToL2CrossDomainMessenger twice
_value = bound(_value, 0, type(uint256).max / 2);
// Ensure that the target call is payable if value is sent
if (_value > 0) assumePayable(_target);
// 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 that the target contract does not revert
vm.mockCall({ callee: _target, msgValue: _value, data: _message, returnData: abi.encode(true) });
// Mock the CrossL2Inbox origin to return the L2ToL2CrossDomainMessenger contract
vm.mockCall({
callee: Predeploys.CROSS_L2_INBOX,
data: abi.encodeWithSelector(CrossL2Inbox.origin.selector),
returnData: abi.encode(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER)
});
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check and that it has sufficient value
hoax(Predeploys.CROSS_L2_INBOX, _value);
// Look for correct emitted event for first call.
vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
emit L2ToL2CrossDomainMessenger.RelayedMessage(
keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message))
);
// First call to `relayMessage` should succeed. The current chain is the destination to prevent revert due to
// invalid destination
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: block.chainid,
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: _target,
_message: _message
});
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check and that it has sufficient value
hoax(Predeploys.CROSS_L2_INBOX, _value);
// Second call should fail with MessageAlreadyRelayed selector
vm.expectRevert(MessageAlreadyRelayed.selector);
// Call `relayMessage` again. The current chain is the destination to prevent revert due to invalid destination
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: block.chainid,
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: _target,
_message: _message
});
}
/// @dev Tests that the `relayMessage` function reverts when the target call fails.
function testFuzz_relayMessage_targetCallFails_reverts(
uint256 _source,
uint256 _nonce,
address _sender,
address _target,
bytes calldata _message,
uint256 _value
)
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 that the target call is payable if value is sent
if (_value > 0) assumePayable(_target);
// Ensure that the target contract reverts
vm.mockCallRevert({ callee: _target, msgValue: _value, data: _message, revertData: abi.encode(false) });
// Mock the CrossL2Inbox origin to return the L2ToL2CrossDomainMessenger contract
vm.mockCall({
callee: Predeploys.CROSS_L2_INBOX,
data: abi.encodeWithSelector(CrossL2Inbox.origin.selector),
returnData: abi.encode(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER)
});
// Ensure caller is CrossL2Inbox to prevent a revert from the caller check and that it has sufficient value
hoax(Predeploys.CROSS_L2_INBOX, _value);
// Look for correct emitted event
vm.expectEmit(Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
emit L2ToL2CrossDomainMessenger.FailedRelayedMessage(
keccak256(abi.encode(block.chainid, _source, _nonce, _sender, _target, _message))
);
l2ToL2CrossDomainMessenger.relayMessage{ value: _value }({
_destination: block.chainid,
_source: _source,
_nonce: _nonce,
_sender: _sender,
_target: _target,
_message: _message
});
}
/// @dev Tests that the `crossDomainMessageSender` function returns the correct value.
function testFuzz_crossDomainMessageSender_succeeds(address _sender) 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 sender in the transient storage
l2ToL2CrossDomainMessenger.setCrossDomainMessageSender(_sender);
// Check that the `crossDomainMessageSender` function returns the correct value
assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageSender(), _sender);
}
/// @dev Tests that the `crossDomainMessageSender` function reverts when not entered.
function test_crossDomainMessageSender_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 `crossDomainMessageSender` to provoke revert
l2ToL2CrossDomainMessenger.crossDomainMessageSender();
}
/// @dev Tests that the `crossDomainMessageSource` function returns the correct value.
function testFuzz_crossDomainMessageSource_succeeds(uint256 _source) 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 source in the transient storage
l2ToL2CrossDomainMessenger.setCrossDomainMessageSource(_source);
// Check that the `crossDomainMessageSource` function returns the correct value
assertEq(l2ToL2CrossDomainMessenger.crossDomainMessageSource(), _source);
}
/// @dev Tests that the `crossDomainMessageSource` function reverts when not entered.
function test_crossDomainMessageSource_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 `crossDomainMessageSource` to provoke revert
l2ToL2CrossDomainMessenger.crossDomainMessageSource();
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment