Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions packages/contracts-bedrock/src/L2/L2ToL2CrossDomainMessenger.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,24 @@ 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 send back a message hash to the same chain.
error MessageSourceSameChain();

/// @notice Thrown when attempting to receive a message hash to the wrong source chain.
error MessageSourceIsIncorrect();

/// @notice Thrown when attempting to relay a message and the function caller (msg.sender) is not CrossL2Inbox.
error RelayMessageCallerNotCrossL2Inbox();

/// @notice Thrown when attempting to send back a message before the delay has elapsed.
error DelayHasNotEnsued();

/// @notice Thrown when attempting to relay a message where CrossL2Inbox's origin is not L2ToL2CrossDomainMessenger.
error CrossL2InboxOriginNotL2ToL2CrossDomainMessenger();

/// @notice Thrown when attempting receive a message hash from the wrong destination chain.
error CrossL2InboxDestinationDoesntMatch();

/// @notice Thrown when attempting to relay a message whose destination chain is not the chain relaying it.
error MessageDestinationNotRelayChain();

Expand Down Expand Up @@ -66,6 +78,23 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver {
/// mapping if it has successfully been relayed on this chain, and can therefore not be relayed again.
mapping(bytes32 => bool) public successfulMessages;
Comment thread
skeletor-spaceman marked this conversation as resolved.

/// @notice Mapping of message hashes to the timestamp when they failed to be relayed and their source chain id.
/// Messages included in this mapping can be returned to the source chain after a given delay has elapsed.
mapping(bytes32 => FailedMessage) public failedMessages;

/// @notice Mapping of message hashes to the timestamp when they were returned to the source chain.
mapping(bytes32 => uint256) public returnedMessages;

/// @notice Struct containing the timestamp at which a message failed for the first time along with
/// the source's chain id.
struct FailedMessage {
uint256 timestamp;
uint256 sourceChainId;
}

/// @notice Time that must elapse before a returnable message can be returned to its source chain.
uint256 constant RETURN_DELAY;

/// @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.
Expand All @@ -75,6 +104,12 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver {
/// @param data Encoded data of the message that was sent.
event SentMessage(bytes data) anonymous;

/// @notice Emitted whenever a message is sent to the other chain.
/// @param messageHash Hash of the unsuccessful message that is received.
/// @param source Chain id where the message was sent from at its inception.
/// @param destination Chain id of the chain where the message failed to be relayed.
event MessageHashReceived(bytes32 indexed messageHash, uint256 indexed source, uint256 indexed destination);

/// @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);
Expand Down Expand Up @@ -137,6 +172,50 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver {
msgNonce++;
}

/// @notice Sends a failed message hash back to the source chain after a given amount of time has passed.
/// @param _messageHash The messageHash of the message to be returned.
function revert(bytes32 _messageHash) external nonReentrant {
if (successfulMessages[messageHash]) revert MessageAlreadyRelayed();

(uint256 messageTimestamp, uint256 messageSource) = failedMessages[messageHash];

// Return delay is necessary to avoid griefing attacks by providing users with time to
// replay their transactions give this function has no auth
if (block.timestamp < messageTimestamp + RETURN_DELAY) revert DelayHasNotEnsued();

delete failedMessages[messageHash];
successfulMessages[messageHash] = true;

bytes memory data = abi.encodeCall(
L2ToL2CrossDomainMessenger.receiveMessageHash,
(messageSource, block.chainid, Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER, messageHash)
);
emit SentMessage(data);
}

/// @notice Receives the message hash of a message that could not be relayed on the destination chain.
/// @param _messageSource Chain ID of the original message's source chain. Should be this chain.
/// @param _messageDestination Chain ID of the original message's destination chain.
/// @param _sender Sender of the message containing the message hash to be stored.
/// @param _messageHash Message hash of the message that failed to be relayed on the destination chain.
function receiveMessageHash(uint256 _messageSource, uint256 _messageDestination, address _sender, bytes32 _messageHash) external {
if (_messageSource != block.chainid) revert IncorrectMessageSource();
if (_sender != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) revert SenderIsNotCrossDomainMesenger();
if (msg.sender != Predeploys.CROSS_L2_INBOX) revert ReceiveMessageHashCallerNotCrossL2Inbox();

if (CrossL2Inbox(Predeploys.CROSS_L2_INBOX).origin() != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
revert CrossL2InboxOriginNotL2ToL2CrossDomainMessenger();
}

if (CrossL2Inbox(Predeploys.CROSS_L2_INBOX).chainId() != _messageDestination) {
revert CrossL2InboxDestinationDoesntMatch();
}

returnedMessages[_messageHash] = block.timestamp;

emit MessageHashReceived(_messageHash);
}

/// @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.
Expand Down Expand Up @@ -179,8 +258,14 @@ contract L2ToL2CrossDomainMessenger is IL2ToL2CrossDomainMessenger, ISemver {

if (success) {
successfulMessages[messageHash] = true;
delete failedMessages[messageHash];
emit RelayedMessage(messageHash);
} else {
// Will set the timestamp only once on the *first* failed execution to protect against extending
// the time-window and blocking the rollback.
if (failedMessages[messageHash].timestamp == 0) {
failedMessages[messageHash] = FailedMessage({timestamp: block.timestamp, sourceChainId: _source});
}
emit FailedRelayedMessage(messageHash);
}

Expand Down