Skip to content

Commit

Permalink
changes (#387)
Browse files Browse the repository at this point in the history
  • Loading branch information
hamdiallam authored Sep 27, 2024
1 parent 8163c9d commit 34dd8bd
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 81 deletions.
7 changes: 5 additions & 2 deletions specs/interop/messaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,11 @@ An initiating message may be executed many times: no replay-protection is enshri
### Executing Messages

An executing message is represented by the [ExecutingMessage event][event] that is emitted by
the `CrossL2Inbox` predeploy. This event is coupled to a `CALL` with the payload that is emitted
within the event, allowing introspection of the data without needing to do call tracing.
the `CrossL2Inbox` predeploy. If the cross chain message is directly executed via [executeMessage](./predeploys.md#executemessage),
the event is coupled to a `CALL` with the payload that is emitted within the event to the target
address, allowing introspection of the data. Contracts can also introduce their own public
entrypoints and solely trigger validation of the cross chain message with [validateMessage](./predeploys.md#validatemessage).

All of the information required to satisfy the invariants MUST be included in this event.

[event]: ./predeploys.md#executingmessage-event
Expand Down
191 changes: 113 additions & 78 deletions specs/interop/predeploys.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,9 @@
**Table of Contents**

- [CrossL2Inbox](#crossl2inbox)
- [Message execution arguments](#message-execution-arguments)
- [`_msg`](#_msg)
- [`_id`](#_id)
- [`_target`](#_target)
- [Functions](#functions)
- [executeMessage](#executemessage)
- [validateMessage](#validatemessage)
- [Interop Start Timestamp](#interop-start-timestamp)
- [`ExecutingMessage` Event](#executingmessage-event)
- [Reference implementation](#reference-implementation)
Expand All @@ -31,7 +30,7 @@
- [Proxy](#proxy)
- [Beacon Pattern](#beacon-pattern)
- [Deployment history](#deployment-history)
- [Functions](#functions)
- [Functions](#functions-1)
- [`deploy`](#deploy)
- [Events](#events)
- [`OptimismSuperchainERC20Created`](#optimismsuperchainerc20created)
Expand All @@ -47,7 +46,7 @@
- [OptimismMintableERC20Factory](#optimismmintableerc20factory)
- [OptimismMintableERC20](#optimismmintableerc20)
- [Updates](#updates)
- [Functions](#functions-1)
- [Functions](#functions-2)
- [`createOptimismMintableERC20WithDecimals`](#createoptimismmintableerc20withdecimals)
- [`createOptimismMintableERC20`](#createoptimismmintableerc20)
- [`createStandardL2Token`](#createstandardl2token)
Expand All @@ -62,7 +61,7 @@
- [Conversion Flow](#conversion-flow)
- [SuperchainERC20Bridge](#superchainerc20bridge)
- [Overview](#overview-2)
- [Functions](#functions-2)
- [Functions](#functions-3)
- [`sendERC20`](#senderc20)
- [`relayERC20`](#relayerc20)
- [Events](#events-2)
Expand All @@ -83,47 +82,30 @@ an update to the `L1Block`, `OptimismMintableERC20Factory` and `L2StandardBridge
| -------- | -------------------------------------------- |
| Address | `0x4200000000000000000000000000000000000022` |

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.
The `CrossL2Inbox` is the system predeploy for cross chain messaging. Anyone can trigger the execution or validation
of cross chain messages, on behalf of any user.

To ensure safety of the protocol, the [Message Invariants](./messaging.md#messaging-invariants) must be enforced.

### Message execution arguments

The following fields are required for executing a cross chain message:

[message payload]: ./messaging.md#message-payload
[`Identifier`]: ./messaging.md#message-identifier

### Functions

#### executeMessage

Executes a cross chain message and performs a `CALL` with the payload to the provided target address, allowing
introspection of the data.
Signals the transaction has a cross chain message to validate by emitting the `ExecuteMessage` event.

The following fields are required for executing a cross chain message:

| Name | Type | Description |
| --------- | ------------ | ------------------------------------------------------- |
| `_msg` | `bytes` | The [message payload], matching the initiating message. |
| `_id` | `Identifier` | A [`Identifier`] pointing to the initiating message. |
| `_target` | `address` | Account that is called with `_msg`. |

#### `_msg`

The [message payload] of the executing message.

This must match the emitted payload of the initiating message identified by `_id`.

#### `_id`

A pointer to the `_msg` in a remote (or local) chain.

The message [`Identifier`] of the executing message.
This is required to enforce the message executes an existing and valid initiating message.

By including the [`Identifier`] in the calldata, it makes static analysis much easier for block builders.
It is impossible to check that the [`Identifier`] matches the cross chain message on chain. If the block
builder includes a message that does not correspond to the [`Identifier`], their block will be reorganized
by the derivation pipeline.

A possible upgrade path to this contract would involve adding a new function. If any fields in the [`Identifier`]
change, then a new 4byte selector will be generated by solc.

#### `_target`

Messages are broadcast, not directed. Upon execution the caller can specify which `address` to target:
there is no protocol enforcement on what this value is.

Expand All @@ -132,6 +114,26 @@ In practice, the `_target` will be a contract that needs to know the schema of t
It MAY call back to the `CrossL2Inbox` to authenticate
properties about the `_msg` using the information in the `Identifier`.

```solidity
executeMessage(Identifier calldata _id, address _target, bytes memory _message)
```

#### validateMessage

A helper to enable contracts to provide their own public entrypoints for cross chain interactions.
Emits the `ExecutingMessage` event to signal the transaction has a cross chain message to validate.

The following fields are required for validating a cross chain message:

| Name | Type | Description |
| -------- | ---------- | -------------------------------------------------------------------------- |
| `_id` | Identifier | A [`Identifier`] pointing to the initiating message. |
| `_msgHash` | `bytes32` | The keccak256 hash of the message payload matching the initiating message. |

```solidity
validateMessage(Identifier calldata _id, bytes32 _msgHash)
```

### Interop Start Timestamp

The Interop Start Timestamp represents the earliest timestamp which an initiating message (identifier) can have to be
Expand All @@ -146,7 +148,7 @@ that timestamp into the pre-determined storage slot.
### `ExecutingMessage` Event

The `ExecutingMessage` event represents an executing message. It MUST be emitted on every call
to `executeMessage`.
to `executeMessage` and `validateMessage`.

```solidity
event ExecutingMessage(bytes32 indexed msgHash, Identifier identifier);
Expand Down Expand Up @@ -196,11 +198,61 @@ function executeMessage(Identifier calldata _id, address _target, bytes calldata

Note that the `executeMessage` function is `payable` to enable relayers to earn in the gas paying asset.

An example of encoding a cross chain call directly in an event. However realize the
[L2ToL2CrossDomainMessenger](#l2tol2crossdomainmessenger) predeploy provides a cleaner and user
friendly abstraction for cross chain calls.

```solidity
contract MyCrossChainApp {
function sendMessage() external {
bytes memory data = abi.encodeCall(MyCrossChainApp.relayMessage, (1, address(0x20)));
// Encoded payload matches the required calldata by omission of an event topic
assembly {
log0(add(data, 0x20), mload(data))
}
}
function relayMessage(uint256 value, address recipient) external {
// Assert that this is only executed directly from the inbox
require(msg.sender == Predeploys.CrossL2Inbox);
}
}
```

An example of a custom entrypoint utilizing `validateMessage` to consume a known
event. Note that in this example, the contract is consuming its own event
from another chain, however **any** event emitted from **any** contract is consumable!

```solidity
contract MyCrossChainApp {
event MyCrossChainEvent();
function sendMessage() external {
emit MyCrossChainEvent();
}
function relayMessage(Identifier calldata _id, bytes calldata _msg) external {
// Example app-level validation
// - Expected event via the selector (first topic)
// - Assertion on the expected emitter of the event
require(MyCrossChainEvent.selector == _msg[:32]);
require(_id.origin == address(this));
// Authenticate this cross chain message
CrossL2Inbox.validateMessage(_id, keccak256(_msg));
// ABI decode the event message & perform actions.
// ...
}
}
```

### Deposit Handling

Any call to the `CrossL2Inbox` that would emit an `ExecutingMessage` event will reverts
if the call is made in a [deposit context](./derivation.md#deposit-context).
The deposit context status can be determined by callling `isDeposit` on the `L1Block` contract.
The deposit context status can be determined by calling `isDeposit` on the `L1Block` contract.

In the future, deposit handling will be modified to be more permissive.
It will revert only in specific cases where interop dependency resolution is not feasible.
Expand All @@ -219,16 +271,14 @@ properties about the `_msg`.
| `EXPIRY_WINDOW` | `uint256(7200)` |

The `L2ToL2CrossDomainMessenger` is a higher level abstraction on top of the `CrossL2Inbox` that
provides features necessary for secure transfers ERC20 tokens between L2 chains.
provides general message passing, utilized 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, ie the executing transaction can only be valid on a single chain.

### `relayMessage` Invariants

- Only callable by the `CrossL2Inbox`
- The `Identifier.origin` MUST be `address(L2ToL2CrossDomainMessenger)`
- The `_destination` chain id MUST be equal to the local chain id
- The `CrossL2Inbox` cannot call itself

### `sendExpire` Invariants

Expand Down Expand Up @@ -276,18 +326,12 @@ The following function is used for sending messages between domains:
function sendMessage(uint256 _destination, address _target, bytes calldata _message) external;
```

It creates an initiating message that is represented by an anonymous event:
It emits a `SentMessage` event with the necessary metadata to execute when relayed on the destination chain.

```solidity
assembly {
log0(add(_data, 0x20), mload(_data))
}
event SentMessage(uint256 indexed destination, address indexed target, uint256 indexed messageNonce, address sender, bytes message);``
```

The `_data` is an ABI encoded call to `relayMessage`. The event is done with Yul so that an extra layer
of ABI encoding as `bytes` is not wrapped around `relayMessage` call. The exact calldata meant to be passed
to the `L2ToL2CrossDomainMessenger` on the remote domain is included in the log.

An explicit `_destination` chain and `nonce` are used to ensure that the message can only be played on a single remote
chain a single time. The `_destination` is enforced to not be the local chain to avoid edge cases.

Expand Down Expand Up @@ -317,42 +361,33 @@ first failed along with its source chain id are stored. This is
needed for calculation of the failed message's expiry. The source chain id
is also required to simplify the function signature of `sendExpire`.

```solidity
function relayMessage(uint256 _destination, uint256 _source, uint256 _nonce, address _sender, address _target, bytes memory _message) external payable {
require(msg.sender == address(CROSS_L2_INBOX));
require(_destination == block.chainid);
require(CROSS_L2_INBOX.origin() == address(this));
bytes32 messageHash = keccak256(abi.encode(_destination, _source, _nonce, _sender, _target, _message));
require(sentMessages[messageHash] == false);
A message is relayed by providing the [identifier](./messaging.md#message-identifier) to a `SentMessage`
event and its corresponding [message payload](./messaging.md#message-payload).

assembly {
tstore(CROSS_DOMAIN_MESSAGE_SENDER_SLOT, _sender)
tstore(CROSS_DOMAIN_MESSAGE_SOURCE_SLOT, _source)
}
```solidity
function relayMessage(ICrossL2Inbox.Identifier calldata _id, bytes calldata _sentMessage) external payable {
require(_id.origin == Predeploys.L2_TO_L2_CROSS_DOMAIN_MESSENGER);
CrossL2Inbox(Predeploys.CROSS_L2_INBOX).validateMessage(_id, keccak256(_sentMessage));
sentMessages[messageHash] = true;
// log topics
(bytes32 selector, uint256 _destination, address _target, uint256 _nonce) =
abi.decode(_sentMessage[:128], (bytes32,uint256,address,uint256));
bool success = SafeCall.call({
_target: _target,
_value: msg.value,
_calldata: _message
});
require(selector == SentMessage.selector);
require(_destination == block.chainid);
if (!success) {
emit FailedRelayedMessage(messageHash);
// log data
(address _sender, bytes memory _message) = abi.decode(_sentMessage[128:], (address,bytes));
if (failedMessages[messageHash].timestamp == 0) {
failedMessages[messageHash] = FailedMessage({timestamp: block.timestamp, sourceChainId: _source});
}
bool success = SafeCall.call(_target, msg.value, _message);
return;
if (success) {
successfulMessages[messageHash] = true;
emit RelayedMessage(_source, _nonce, messageHash);
} else {
emit FailedRelayedMessage(_source, _nonce, messageHash);
}
successfulMessages[messageHash] = true;
delete failedMessages[messageHash];
emit RelayedMessage(messageHash);
};
}
```

Note that the `relayMessage` function is `payable` to enable relayers to earn in the gas paying asset.
Expand Down
2 changes: 1 addition & 1 deletion specs/protocol/isthmus/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@ This document is not finalized and should be considered experimental.

## Consensus Layer

- [Interop](../interop/overview.md)
- [Interop](../../interop/overview.md)

0 comments on commit 34dd8bd

Please sign in to comment.