Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: rollback update #405

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
78 changes: 51 additions & 27 deletions specs/interop/predeploys.md
Original file line number Diff line number Diff line change
Expand Up @@ -285,18 +285,18 @@ as well as domain binding, ie the executing transaction can only be valid on a s

### `sendExpire` Invariants

- The message MUST have not been successfully relayed
- The message MUST have failed to be relayed and not have succeded in later attemps.
- The `EXPIRY_WINDOW` MUST have elapsed since the message first failed to be relayed
- The expired message MUST not have been previously sent back to source
- The expired message MUST not be relayable after being sent back
- The expired message MUST NOT have been previously sent back to source
- The expired message MUST NOT be relayable after being sent back to the origin chain
- The `returnedMessages` mapping MUST only contain messages that were sent back to their origin chain.

### `relayExpire` Invariants

- Only callable by the `CrossL2Inbox`
- The message source MUST be `block.chainid`
- The `Identifier.origin` MUST be `address(L2ToL2CrossDomainMessenger)`
- The `Identifier.origin` and `sender` MUST be `address(L2ToL2CrossDomainMessenger)`
- The `expiredMessages` mapping MUST only contain messages that originated in this chain and failed to be relayed on destination.
- Already expired messages MUST NOT be relayed.
- It MUST NOT accept previously-expired messages, that is, messages stored in the `expiredMessages` mapping.

### Message Versioning

Expand Down Expand Up @@ -380,7 +380,7 @@ flowchart LR
```

When relaying a message through the `L2ToL2CrossDomainMessenger`, it is important to require that
the `_destination` equal to `block.chainid` to ensure that the message is only valid on a single
the `_destination` is equal to `block.chainid` to ensure that the message is only valid on a single
chain. The hash of the message is used for replay protection.

It is important to ensure that the source chain is in the dependency set of the destination chain, otherwise
Expand Down Expand Up @@ -409,12 +409,17 @@ function relayMessage(ICrossL2Inbox.Identifier calldata _id, bytes calldata _sen
// log data
(address _sender, bytes memory _message) = abi.decode(_sentMessage[128:], (address,bytes));

require(returnedMessages[messageHash] == 0);

bool success = SafeCall.call(_target, msg.value, _message);

if (success) {
successfulMessages[messageHash] = true;
emit RelayedMessage(_source, _nonce, messageHash);
} else {
if (failedMessages[messageHash].timestamp == 0) {
failedMessages[messageHash] = FailedMessage({timestamp: block.timestamp, sourceChain: _source});
}
emit FailedRelayedMessage(_source, _nonce, messageHash);
}
}
Expand All @@ -437,54 +442,73 @@ failed messages and to prevent malicious actors from performing a griefing attac
by expiring messages upon arrival.

Once the expired message is sent to the source chain, the message on the local chain is set
as successful in the `successfulMessages` mapping to ensure non-replayability and deleted
from `failedMessages`. An initiating message is then emitted to `relayExpire`
as expired in the `returnedMessages` mapping to ensure non-replayability and deleted from `failedMessages`.
An initiating message is then emitted to `relayExpire`.

`sendExpire` sets the sender of the message as the `L2ToL2CrossDomainMessenger` to add a path
for `relayExpire` to ensure that the message originated in `sendExpire`. There should be no
other path where a message can have the `L2ToL2CrossDomainMessenger` as `origin` and `sender`.

```solidity
function sendExpire(bytes32 _expiredHash) external nonReentrant {
if (successfulMessages[_expiredHash]) revert MessageAlreadyRelayed();
require(!successfulMessages[_expiredHash]);
require(failedMessages[_expiredHash].timestamp != 0);

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

if (block.timestamp < messageTimestamp + EXPIRY_WINDOW) revert ExpiryWindowHasNotEnsued();
require(block.timestamp >= messageTimestamp + EXPIRY_WINDOW);

delete failedMessages[_expiredHash];
successfulMessages[_expiredHash] = true;
returnedMessages[_expiredHash] = block.timestamp;

bytes memory data = abi.encodeCall(
L2ToL2CrossDomainMessenger.expired,
(_expiredHash, messageSource)
);
emit SentMessage(data);
uint256 destination = messageSource;
address target;
uint256 nonce = messageNonce();
address sender = Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER;

bytes memory message = abi.encode(_expiredHash);

emit SentMessage(destination, target, nonce, sender, message);

msgNonce++;
}
```

#### Relaying Expired Message Hashes

When relaying an expired message, only message hashes
of actual failed messages should be stored, for this we must ensure the origin
of the log, and caller are all expected contracts.
of the log, and sender is the `L2_TO_L2_CROSS_DOMAIN_MESSENGER`.

It's also important to ensure only the hashes of messages that were initiated
in this chain are accepted.

If all checks have been successful, the message has is stored in the
If all checks have been successful, the message hash is stored in the
`expiredMessages` mapping. This enables smart contracts to read from it and
check whether a message expired or not, and handle this case accordingly.

An expiry message is relayed by providing the [identifier](./messaging.md#message-identifier)
to a `SentMessage` event and its corresponding [message payload](./messaging.md#message-payload).

```solidity
function relayExpire(bytes32 _expiredHash, uint256 _messageSource) external {
if (_messageSource != block.chainid) revert IncorrectMessageSource();
if (expiredMessages[_expiredHash] != 0) revert ExpiredMessageAlreadyRelayed();
if (msg.sender != Predeploys.CROSS_L2_INBOX) revert ExpiredMessageCallerNotCrossL2Inbox();
function relayExpire(ICrossL2Inbox.Identifier calldata _id, bytes calldata _sentMessage) external {
require(_id.origin == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);

if (CrossL2Inbox(Predeploys.CROSS_L2_INBOX).origin() != Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER) {
revert CrossL2InboxOriginNotL2ToL2CrossDomainMessenger();
}
CrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_sentMessage));

(uint256 destination, , , address sender, bytes memory message) =
_decodeSentMessagePayload(_sentMessage);

require(destination == block.chainId);
require(sender == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);

bytes32 expiredHash = abi.decode(_sentMessage);

require(expiredMessages[expiredHash] == 0);

expiredMessages[_expiredHash] = block.timestamp;

emit MessageHashExpired(_expiredHash);
emit MessageHashExpired(_expiredHash, block.timestamp);
}
```

Expand Down