Commit fb45215a authored by Michael Amadi's avatar Michael Amadi Committed by GitHub

Add Interop Start timestamp to CrossL2Inbox (#11398)

* add interop start timestamp to CrossL2Inbox with tests

* change to function, add assertions, update tests

* correct the preimage of interop start storage slot

* rename custome error

* require id timestamp must be > interopStartTime not >=, update tests to use realistic interopStartTime (non-zero)

* bump CrossL2Inbox semver, run just semver-lock & just snapshots

* add natspec for setInteropStart

* update semver lock
parent 872ff5db
......@@ -68,8 +68,8 @@
"sourceCodeHash": "0x3a725791a0f5ed84dc46dcdae26f6170a759b2fe3dc360d704356d088b76cfd6"
},
"src/L2/CrossL2Inbox.sol": {
"initCodeHash": "0x80124454d2127d5ff340b0ef048be6d5bf5984e84c75021b6a1ffa81703a2503",
"sourceCodeHash": "0xfb26fc80fbc7febdc91ac73ea91ceb479b238e0e81804a0a21192d78c261a755"
"initCodeHash": "0x071b53cd8cf0503af856c4ee0ee34643e85059d53c096453891225472e02abfa",
"sourceCodeHash": "0x3c78129b91d9f06afa4787d4b3039f45a3b22b3edf5155ed73d4f0c3ab33c6c8"
},
"src/L2/ETHLiquidity.sol": {
"initCodeHash": "0x98177562fca0de0dfea5313c9acefe2fdbd73dee5ce6c1232055601f208f0177",
......
......@@ -75,6 +75,19 @@
"stateMutability": "payable",
"type": "function"
},
{
"inputs": [],
"name": "interopStart",
"outputs": [
{
"internalType": "uint256",
"name": "interopStart_",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "logIndex",
......@@ -101,6 +114,13 @@
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "setInteropStart",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "timestamp",
......@@ -218,6 +238,11 @@
"name": "ExecutingMessage",
"type": "event"
},
{
"inputs": [],
"name": "InteropStartAlreadySet",
"type": "error"
},
{
"inputs": [],
"name": "InvalidChainId",
......@@ -228,6 +253,11 @@
"name": "InvalidTimestamp",
"type": "error"
},
{
"inputs": [],
"name": "NotDepositor",
"type": "error"
},
{
"inputs": [],
"name": "NotEntered",
......
......@@ -17,6 +17,12 @@ interface IDependencySet {
function isInDependencySet(uint256 _chainId) external view returns (bool);
}
/// @notice Thrown when the caller is not DEPOSITOR_ACCOUNT when calling `setInteropStart()`
error NotDepositor();
/// @notice Thrown when attempting to set interop start when it's already set.
error InteropStartAlreadySet();
/// @notice Thrown when a non-written transient storage slot is attempted to be read from.
error NotEntered();
......@@ -35,6 +41,10 @@ error TargetCallFailed();
/// @notice The CrossL2Inbox is responsible for executing a cross chain message on the destination
/// chain. It is permissionless to execute a cross chain message on behalf of any user.
contract CrossL2Inbox is ICrossL2Inbox, ISemver, TransientReentrancyAware {
/// @notice Storage slot that the interop start timestamp is stored at.
/// Equal to bytes32(uint256(keccak256("crossl2inbox.interopstart")) - 1)
bytes32 internal constant INTEROP_START_SLOT = 0x5c769ee0ee8887661922049dc52480bb60322d765161507707dd9b190af5c149;
/// @notice Transient storage slot that the origin for an Identifier is stored at.
/// Equal to bytes32(uint256(keccak256("crossl2inbox.identifier.origin")) - 1)
bytes32 internal constant ORIGIN_SLOT = 0xd2b7c5071ec59eb3ff0017d703a8ea513a7d0da4779b0dbefe845808c300c815;
......@@ -55,15 +65,42 @@ contract CrossL2Inbox is ICrossL2Inbox, ISemver, TransientReentrancyAware {
/// Equal to bytes32(uint256(keccak256("crossl2inbox.identifier.chainid")) - 1)
bytes32 internal constant CHAINID_SLOT = 0x6e0446e8b5098b8c8193f964f1b567ec3a2bdaeba33d36acb85c1f1d3f92d313;
/// @notice The address that represents the system caller responsible for L1 attributes
/// transactions.
address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001;
/// @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 Emitted when a cross chain message is being executed.
/// @param msgHash Hash of message payload being executed.
/// @param id Encoded Identifier of the message.
event ExecutingMessage(bytes32 indexed msgHash, Identifier id);
/// @notice Sets the Interop Start Timestamp for this chain. Can only be performed once and when the caller is the
/// DEPOSITOR_ACCOUNT.
function setInteropStart() external {
// Check that caller is the DEPOSITOR_ACCOUNT
if (msg.sender != DEPOSITOR_ACCOUNT) revert NotDepositor();
// Check that it has not been set already
if (interopStart() != 0) revert InteropStartAlreadySet();
// Set Interop Start to block.timestamp
assembly {
sstore(INTEROP_START_SLOT, timestamp())
}
}
/// @notice Returns the interop start timestamp.
/// @return interopStart_ interop start timestamp.
function interopStart() public view returns (uint256 interopStart_) {
assembly {
interopStart_ := sload(INTEROP_START_SLOT)
}
}
/// @notice Returns the origin address of the Identifier. If not entered, reverts.
/// @return Origin address of the Identifier.
function origin() external view notEntered returns (address) {
......@@ -140,7 +177,7 @@ contract CrossL2Inbox is ICrossL2Inbox, ISemver, TransientReentrancyAware {
/// is in the destination chain's dependency set.
/// @param _id Identifier of the message.
function _checkIdentifier(Identifier calldata _id) internal view {
if (_id.timestamp > block.timestamp) revert InvalidTimestamp();
if (_id.timestamp > block.timestamp || _id.timestamp <= interopStart()) revert InvalidTimestamp();
if (!IDependencySet(Predeploys.L1_BLOCK_ATTRIBUTES).isInDependencySet(_id.chainId)) {
revert InvalidChainId();
}
......
......@@ -13,6 +13,10 @@ interface ICrossL2Inbox {
uint256 chainId;
}
/// @notice Returns the interop start timestamp.
/// @return interopStart_ interop start timestamp.
function interopStart() external view returns (uint256 interopStart_);
/// @notice Returns the origin address of the Identifier.
/// @return _origin The origin address of the Identifier.
function origin() external view returns (address _origin);
......@@ -44,4 +48,12 @@ interface ICrossL2Inbox {
)
external
payable;
/// @notice Validates a cross chain message on the destination chain
/// and emits an ExecutingMessage event. This function is useful
/// for applications that understand the schema of the _message payload and want to
/// process it in a custom way.
/// @param _id Identifier of the message.
/// @param _msgHash Hash of the message payload to call target with.
function validateMessage(Identifier calldata _id, bytes32 _msgHash) external;
}
......@@ -9,7 +9,15 @@ import { Predeploys } from "src/libraries/Predeploys.sol";
import { TransientContext } from "src/libraries/TransientContext.sol";
// Target contracts
import { CrossL2Inbox, NotEntered, InvalidTimestamp, InvalidChainId, TargetCallFailed } from "src/L2/CrossL2Inbox.sol";
import {
CrossL2Inbox,
NotEntered,
InvalidTimestamp,
InvalidChainId,
TargetCallFailed,
NotDepositor,
InteropStartAlreadySet
} from "src/L2/CrossL2Inbox.sol";
import { ICrossL2Inbox } from "src/L2/ICrossL2Inbox.sol";
/// @title CrossL2InboxWithModifiableTransientStorage
......@@ -58,9 +66,20 @@ contract CrossL2InboxTest is Test {
/// @dev Selector for the `isInDependencySet` method of the L1Block contract.
bytes4 constant L1BlockIsInDependencySetSelector = bytes4(keccak256("isInDependencySet(uint256)"));
/// @dev Storage slot that the interop start timestamp is stored at.
/// Equal to bytes32(uint256(keccak256("crossl2inbox.interopstart")) - 1)
bytes32 internal constant INTEROP_START_SLOT = bytes32(uint256(keccak256("crossl2inbox.interopstart")) - 1);
/// @dev CrossL2Inbox contract instance.
CrossL2Inbox crossL2Inbox;
// interop start timestamp
uint256 interopStartTime = 420;
/// @dev The address that represents the system caller responsible for L1 attributes
/// transactions.
address internal constant DEPOSITOR_ACCOUNT = 0xDeaDDEaDDeAdDeAdDEAdDEaddeAddEAdDEAd0001;
/// @dev Sets up the test suite.
function setUp() public {
// Deploy the L2ToL2CrossDomainMessenger contract
......@@ -68,6 +87,56 @@ contract CrossL2InboxTest is Test {
crossL2Inbox = CrossL2Inbox(Predeploys.CROSS_L2_INBOX);
}
modifier setInteropStart() {
// Set interop start
vm.store(address(crossL2Inbox), INTEROP_START_SLOT, bytes32(interopStartTime));
// Set timestamp to be after interop start
vm.warp(interopStartTime + 1 hours);
_;
}
/// @dev Tests that the setInteropStart function updates the INTEROP_START_SLOT storage slot correctly
function testFuzz_setInteropStart_succeeds(uint256 time) external {
// Jump to time.
vm.warp(time);
// Impersonate the depositor account.
vm.prank(DEPOSITOR_ACCOUNT);
// Set interop start.
crossL2Inbox.setInteropStart();
// Check that the storage slot was set correctly and the public getter function returns the right value.
assertEq(crossL2Inbox.interopStart(), time);
assertEq(uint256(vm.load(address(crossL2Inbox), INTEROP_START_SLOT)), time);
}
/// @dev Tests that the setInteropStart function reverts when the caller is not the DEPOSITOR_ACCOUNT.
function test_setInteropStart_notDepositorAccount_reverts() external {
// Expect revert with OnlyDepositorAccount selector
vm.expectRevert(NotDepositor.selector);
// Call setInteropStart function
crossL2Inbox.setInteropStart();
}
/// @dev Tests that the setInteropStart function reverts if called when already set
function test_setInteropStart_interopStartAlreadySet_reverts() external {
// Impersonate the depositor account.
vm.startPrank(DEPOSITOR_ACCOUNT);
// Call setInteropStart function
crossL2Inbox.setInteropStart();
// Expect revert with InteropStartAlreadySet selector if called a second time
vm.expectRevert(InteropStartAlreadySet.selector);
// Call setInteropStart function again
crossL2Inbox.setInteropStart();
}
/// @dev Tests that the `executeMessage` function succeeds.
function testFuzz_executeMessage_succeeds(
ICrossL2Inbox.Identifier memory _id,
......@@ -77,9 +146,11 @@ contract CrossL2InboxTest is Test {
)
external
payable
setInteropStart
{
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp)
_id.timestamp = bound(_id.timestamp, 0, block.timestamp);
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp and greater than
// interop start time)
_id.timestamp = bound(_id.timestamp, interopStartTime + 1, block.timestamp);
// Ensure that the target call is payable if value is sent
if (_value > 0) assumePayable(_target);
......@@ -132,10 +203,12 @@ contract CrossL2InboxTest is Test {
)
external
payable
setInteropStart
{
// Ensure that the ids' timestamp are valid (less than or equal to the current block timestamp)
_id1.timestamp = bound(_id1.timestamp, 0, block.timestamp);
_id2.timestamp = bound(_id2.timestamp, 0, block.timestamp);
// Ensure that the ids' timestamp are valid (less than or equal to the current block timestamp and greater than
// interop start time)
_id1.timestamp = bound(_id1.timestamp, interopStartTime + 1, block.timestamp);
_id2.timestamp = bound(_id2.timestamp, interopStartTime + 1, block.timestamp);
// Ensure that id1's chain ID is in the dependency set
vm.mockCall({
......@@ -189,6 +262,7 @@ contract CrossL2InboxTest is Test {
uint256 _value
)
external
setInteropStart
{
// Ensure that the id's timestamp is invalid (greater than the current block timestamp)
vm.assume(_id.timestamp > block.timestamp);
......@@ -203,6 +277,30 @@ contract CrossL2InboxTest is Test {
crossL2Inbox.executeMessage{ value: _value }({ _id: _id, _target: _target, _message: _message });
}
/// @dev Tests that the `executeMessage` function reverts when called with an identifier with a timestamp earlier
/// than INTEROP_START timestamp
function testFuzz_executeMessage_invalidTimestamp_interopStart_reverts(
ICrossL2Inbox.Identifier memory _id,
address _target,
bytes calldata _message,
uint256 _value
)
external
setInteropStart
{
// Ensure that the id's timestamp is invalid (less than or equal to interopStartTime)
_id.timestamp = bound(_id.timestamp, 0, crossL2Inbox.interopStart());
// Ensure that the contract has enough balance to send with value
vm.deal(address(this), _value);
// Expect a revert with the InvalidTimestamp selector
vm.expectRevert(InvalidTimestamp.selector);
// Call the executeMessage function
crossL2Inbox.executeMessage{ value: _value }({ _id: _id, _target: _target, _message: _message });
}
/// @dev Tests that the `executeMessage` function reverts when called with an identifier with a chain ID not in
/// dependency set.
function testFuzz_executeMessage_invalidChainId_reverts(
......@@ -212,9 +310,11 @@ contract CrossL2InboxTest is Test {
uint256 _value
)
external
setInteropStart
{
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp)
_id.timestamp = bound(_id.timestamp, 0, block.timestamp);
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp and greater than
// interop start time)
_id.timestamp = bound(_id.timestamp, interopStartTime + 1, block.timestamp);
// Ensure that the chain ID is NOT in the dependency set
vm.mockCall({
......@@ -241,9 +341,11 @@ contract CrossL2InboxTest is Test {
uint256 _value
)
external
setInteropStart
{
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp)
_id.timestamp = bound(_id.timestamp, 0, block.timestamp);
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp and greater than
// interop start time)
_id.timestamp = bound(_id.timestamp, interopStartTime + 1, block.timestamp);
// Ensure that the target call is payable if value is sent
if (_value > 0) assumePayable(_target);
......@@ -271,9 +373,16 @@ contract CrossL2InboxTest is Test {
crossL2Inbox.executeMessage{ value: _value }({ _id: _id, _target: _target, _message: _message });
}
function testFuzz_validateMessage_succeeds(ICrossL2Inbox.Identifier memory _id, bytes32 _messageHash) external {
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp)
_id.timestamp = bound(_id.timestamp, 1, block.timestamp);
function testFuzz_validateMessage_succeeds(
ICrossL2Inbox.Identifier memory _id,
bytes32 _messageHash
)
external
setInteropStart
{
// Ensure that the id's timestamp is valid (less than or equal to the current block timestamp and greater than
// interop start time)
_id.timestamp = bound(_id.timestamp, interopStartTime + 1, block.timestamp);
// Ensure that the chain ID is in the dependency set
vm.mockCall({
......@@ -290,14 +399,16 @@ contract CrossL2InboxTest is Test {
crossL2Inbox.validateMessage(_id, _messageHash);
}
/// @dev Tests that the `validateMessage` function reverts when called with an identifier with an invalid timestamp.
/// @dev Tests that the `validateMessage` function reverts when called with an identifier with a timestamp later
/// than current block.timestamp.
function testFuzz_validateMessage_invalidTimestamp_reverts(
ICrossL2Inbox.Identifier calldata _id,
bytes32 _messageHash
)
external
setInteropStart
{
// Ensure that the id's timestamp is invalid (greater thsan the current block timestamp)
// Ensure that the id's timestamp is invalid (greater than the current block timestamp)
vm.assume(_id.timestamp > block.timestamp);
// Expect a revert with the InvalidTimestamp selector
......@@ -307,6 +418,25 @@ contract CrossL2InboxTest is Test {
crossL2Inbox.validateMessage(_id, _messageHash);
}
/// @dev Tests that the `validateMessage` function reverts when called with an identifier with a timestamp earlier
/// than INTEROP_START timestamp
function testFuzz_validateMessage_invalidTimestamp_interopStart_reverts(
ICrossL2Inbox.Identifier memory _id,
bytes32 _messageHash
)
external
setInteropStart
{
// Ensure that the id's timestamp is invalid (less than or equal to interopStartTime)
_id.timestamp = bound(_id.timestamp, 0, crossL2Inbox.interopStart());
// Expect a revert with the InvalidTimestamp selector
vm.expectRevert(InvalidTimestamp.selector);
// Call the validateMessage function
crossL2Inbox.validateMessage(_id, _messageHash);
}
/// @dev Tests that the `validateMessage` function reverts when called with an identifier with a chain ID not in the
/// dependency set.
function testFuzz_validateMessage_invalidChainId_reverts(
......@@ -314,9 +444,11 @@ contract CrossL2InboxTest is Test {
bytes32 _messageHash
)
external
setInteropStart
{
// Ensure that the timestamp is valid (less than or equal to the current block timestamp)
_id.timestamp = bound(_id.timestamp, 0, block.timestamp);
// Ensure that the timestamp is valid (less than or equal to the current block timestamp and greater than
// interopStartTime)
_id.timestamp = bound(_id.timestamp, interopStartTime + 1, block.timestamp);
// Ensure that the chain ID is NOT in the dependency set.
vm.mockCall({
......
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