forked from DecentralizedClimateFoundation/DCIPs
330 lines
10 KiB
Markdown
330 lines
10 KiB
Markdown
|
---
|
||
|
eip: 5164
|
||
|
title: Cross-Chain Execution
|
||
|
description: Defines an interface that supports execution across EVM networks.
|
||
|
author: Brendan Asselstine (@asselstine), Pierrick Turelier (@PierrickGT), Chris Whinfrey (@cwhinfrey)
|
||
|
discussions-to: https://ethereum-magicians.org/t/eip-5164-cross-chain-execution/9658
|
||
|
status: Review
|
||
|
type: Standards Track
|
||
|
category: ERC
|
||
|
created: 2022-06-14
|
||
|
---
|
||
|
|
||
|
## Abstract
|
||
|
|
||
|
This specification defines a cross-chain execution interface for EVM-based blockchains. Implementations of this specification will allow contracts on one chain to call contracts on another by sending a cross-chain message.
|
||
|
|
||
|
The specification defines two components: the "Message Dispatcher" and the "Message Executor". The Message Dispatcher lives on the calling side, and the executor lives on the receiving side. When a message is sent, a Message Dispatcher will move the message through a transport layer to a Message Executor, where they are executed. Implementations of this specification must implement both components.
|
||
|
|
||
|
## Motivation
|
||
|
|
||
|
Many Ethereum protocols need to coordinate state changes across multiple EVM-based blockchains. These chains often have native or third-party bridges that allow Ethereum contracts to execute code. However, bridges have different APIs so bridge integrations are custom. Each one affords different properties; with varying degrees of security, speed, and control. Defining a simple, common specification will increase code re-use and allow us to use common bridge implementations.
|
||
|
|
||
|
## Specification
|
||
|
|
||
|
The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC 2119.
|
||
|
|
||
|
This specification allows contracts on one chain to send messages to contracts on another chain. There are two key interfaces that needs to be implemented:
|
||
|
|
||
|
- `MessageDispatcher`
|
||
|
- `MessageExecutor`
|
||
|
|
||
|
The `MessageDispatcher` lives on the origin chain and dispatches messages to the `MessageExecutor` for execution. The `MessageExecutor` lives on the destination chain and executes dispatched messages.
|
||
|
|
||
|
There are also extensions of `MessageDispatcher`, each defining a method for initiating a message or message batch:
|
||
|
|
||
|
- `SingleMessageDispatcher`
|
||
|
- `BatchMessageDispatcher`
|
||
|
|
||
|
Alternatively, `MessageDispatcher`s may implement a custom interface for initiating messages.
|
||
|
|
||
|
### MessageDispatcher
|
||
|
|
||
|
The `MessageDispatcher` lives on the chain from which messages are sent. The Dispatcher's job is to broadcast messages through a transport layer to one or more `MessageExecutor` contracts.
|
||
|
|
||
|
A unique `messageId` MUST be generated for each message or message batch.
|
||
|
|
||
|
To ensure uniqueness, it is RECOMMENDED that a monotonically increasing nonce is used in the calculation of the `messageId`.
|
||
|
|
||
|
#### MessageDispatcher Events
|
||
|
|
||
|
**MessageDispatched**
|
||
|
|
||
|
The `MessageDispatched` event MUST be emitted by the `MessageDispatcher` when an individual message is dispatched.
|
||
|
|
||
|
```solidity
|
||
|
interface MessageDispatcher {
|
||
|
event MessageDispatched(
|
||
|
bytes32 indexed messageId,
|
||
|
address indexed from,
|
||
|
uint256 indexed toChainId,
|
||
|
address to,
|
||
|
bytes data,
|
||
|
);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```yaml
|
||
|
- name: MessageDispatched
|
||
|
type: event
|
||
|
inputs:
|
||
|
- name: messageId
|
||
|
indexed: true
|
||
|
type: bytes32
|
||
|
- name: from
|
||
|
indexed: true
|
||
|
type: address
|
||
|
- name: toChainId
|
||
|
indexed: true
|
||
|
type: uint256
|
||
|
- name: to
|
||
|
type: address
|
||
|
- name: data
|
||
|
type: bytes
|
||
|
```
|
||
|
|
||
|
**MessageBatchDispatched**
|
||
|
|
||
|
The `MessageBatchDispatched` event MUST be emitted by the `MessageDispatcher` when a batch of messages is dispatched.
|
||
|
|
||
|
```solidity
|
||
|
struct Message {
|
||
|
address to;
|
||
|
bytes data;
|
||
|
}
|
||
|
|
||
|
interface MessageDispatcher {
|
||
|
event MessageBatchDispatched(
|
||
|
bytes32 indexed messageId,
|
||
|
address indexed from,
|
||
|
uint256 indexed toChainId,
|
||
|
Message[] messages
|
||
|
);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```yaml
|
||
|
- name: MessageBatchDispatched
|
||
|
type: event
|
||
|
inputs:
|
||
|
- name: messageId
|
||
|
indexed: true
|
||
|
type: bytes32
|
||
|
- name: from
|
||
|
indexed: true
|
||
|
type: address
|
||
|
- name: toChainId
|
||
|
indexed: true
|
||
|
type: uint256
|
||
|
- name: messages
|
||
|
type: Message[]
|
||
|
```
|
||
|
|
||
|
### SingleMessageDispatcher
|
||
|
|
||
|
The `SingleMessageDispatcher` is an extension of `MessageDispatcher` that defines a method, `dispatchMessage`, for dispatching an individual message to be executed on the `toChainId`.
|
||
|
|
||
|
#### SingleMessageDispatcher Methods
|
||
|
|
||
|
**dispatchMessage**
|
||
|
|
||
|
Will dispatch a message to be executed by the `MessageExecutor` on the destination chain specified by `toChainId`.
|
||
|
|
||
|
`SingleMessageDispatcher`s MUST emit the `MessageDispatched` event when a message is dispatched.
|
||
|
|
||
|
`SingleMessageDispatcher`s MUST revert if `toChainId` is not supported.
|
||
|
|
||
|
`SingleMessageDispatcher`s MUST forward the message to a `MessageExecutor` on the `toChainId`.
|
||
|
|
||
|
`SingleMessageDispatcher`s MUST use a unique `messageId` for each message.
|
||
|
|
||
|
`SingleMessageDispatcher`s MUST return the `messageId` to allow the message sender to track the message.
|
||
|
|
||
|
`SingleMessageDispatcher`s MAY require payment.
|
||
|
|
||
|
```solidity
|
||
|
interface SingleMessageDispatcher is MessageDispatcher {
|
||
|
function dispatchMessage(uint256 toChainId, address to, bytes calldata data) external payable returns (bytes32 messageId);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```yaml
|
||
|
- name: dispatchMessage
|
||
|
type: function
|
||
|
stateMutability: payable
|
||
|
inputs:
|
||
|
- name: toChainId
|
||
|
type: uint256
|
||
|
- name: to
|
||
|
type: address
|
||
|
- name: data
|
||
|
type: bytes
|
||
|
outputs:
|
||
|
- name: messageId
|
||
|
type: bytes32
|
||
|
```
|
||
|
|
||
|
### BatchedMessageDispatcher
|
||
|
|
||
|
The `BatchedMessageDispatcher` is an extension of `MessageDispatcher` that defines a method, `dispatchMessageBatch`, for dispatching a batch of messages to be executed on the `toChainId`.
|
||
|
|
||
|
#### BatchedMessageDispatcher Methods
|
||
|
|
||
|
**dispatchMessageBatch**
|
||
|
|
||
|
Will dispatch a batch of messages to be executed by the `MessageExecutor` on the destination chain specified by `toChainId`.
|
||
|
|
||
|
`BatchedMessageDispatcher`s MUST emit the `MessageBatchDispatched` event when a message batch is dispatched.
|
||
|
|
||
|
`BatchedMessageDispatcher`s MUST revert if `toChainId` is not supported.
|
||
|
|
||
|
`BatchedMessageDispatcher`s MUST forward the message batch to the `MessageExecutor` on the `toChainId`.
|
||
|
|
||
|
`BatchedMessageDispatcher`s MUST use a unique `messageId` for each batch of messages.
|
||
|
|
||
|
`BatchedMessageDispatcher`s MUST return the `messageId` to allow the message sender to track the batch of messages.
|
||
|
|
||
|
`BatchedMessageDispatcher`s MAY require payment.
|
||
|
|
||
|
```solidity
|
||
|
interface BatchedMessageDispatcher is MessageDispatcher {
|
||
|
function dispatchMessageBatch(uint256 toChainId, Message[] calldata messages) external payable returns (bytes32 messageId);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```yaml
|
||
|
- name: dispatchMessageBatch
|
||
|
type: function
|
||
|
stateMutability: payable
|
||
|
inputs:
|
||
|
- name: toChainId
|
||
|
type: uint256
|
||
|
- name: messages
|
||
|
type: Message[]
|
||
|
outputs:
|
||
|
- name: messageId
|
||
|
type: bytes32
|
||
|
```
|
||
|
|
||
|
### MessageExecutor
|
||
|
|
||
|
The `MessageExecutor` executes dispatched messages and message batches. Developers must implement a `MessageExecutor` in order to execute messages on the receiving chain.
|
||
|
|
||
|
The `MessageExecutor` will execute a messageId only once, but may execute messageIds in any order. This specification makes no ordering guarantees, because messages and message batches may travel non-sequentially through the transport layer.
|
||
|
|
||
|
#### Execution
|
||
|
|
||
|
`MessageExecutor`s SHOULD verify all message data with the bridge transport layer.
|
||
|
|
||
|
`MessageExecutor`s MUST NOT successfully execute a message more than once.
|
||
|
|
||
|
`MessageExecutor`s MUST revert the transaction when a message fails to be executed allowing the message to be retried at a later time.
|
||
|
|
||
|
**Calldata**
|
||
|
|
||
|
`MessageExecutor`s MUST append the ABI-packed (`messageId`, `fromChainId`, `from`) to the calldata for each message being executed. This allows the receiver of the message to verify the cross-chain sender and the chain that the message is coming from.
|
||
|
|
||
|
```solidity
|
||
|
to.call(abi.encodePacked(data, messageId, fromChainId, from));
|
||
|
```
|
||
|
|
||
|
```yaml
|
||
|
- name: calldata
|
||
|
type: bytes
|
||
|
inputs:
|
||
|
- name: data
|
||
|
type: bytes
|
||
|
- name: messageId
|
||
|
type: bytes32
|
||
|
- name: fromChainId
|
||
|
type: uint256
|
||
|
- name: from
|
||
|
type: address
|
||
|
```
|
||
|
|
||
|
#### Error handling
|
||
|
|
||
|
**MessageAlreadyExecuted**
|
||
|
|
||
|
`MessageExecutor`s MUST revert if a messageId has already been executed and SHOULD emit a `MessageIdAlreadyExecuted` custom error.
|
||
|
|
||
|
```solidity
|
||
|
interface MessageExecutor {
|
||
|
error MessageIdAlreadyExecuted(
|
||
|
bytes32 messageId
|
||
|
);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
**MessageFailure**
|
||
|
|
||
|
`MessageExecutor`s MUST revert if an individual message fails and SHOULD emit a `MessageFailure` custom error.
|
||
|
|
||
|
```solidity
|
||
|
interface MessageExecutor {
|
||
|
error MessageFailure(
|
||
|
bytes32 messageId,
|
||
|
bytes errorData
|
||
|
);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
**MessageBatchFailure**
|
||
|
|
||
|
`MessageExecutor`s MUST revert the entire batch if any message in a batch fails and SHOULD emit a `MessageBatchFailure` custom error.
|
||
|
|
||
|
```solidity
|
||
|
interface MessageExecutor {
|
||
|
error MessageBatchFailure(
|
||
|
bytes32 messageId,
|
||
|
uint256 messageIndex,
|
||
|
bytes errorData
|
||
|
);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
#### MessageExecutor Events
|
||
|
|
||
|
**MessageIdExecuted**
|
||
|
|
||
|
`MessageIdExecuted` MUST be emitted once a message or message batch has been executed.
|
||
|
|
||
|
```solidity
|
||
|
interface MessageExecutor {
|
||
|
event MessageIdExecuted(
|
||
|
uint256 indexed fromChainId,
|
||
|
bytes32 indexed messageId
|
||
|
);
|
||
|
}
|
||
|
```
|
||
|
|
||
|
```yaml
|
||
|
- name: MessageIdExecuted
|
||
|
type: event
|
||
|
inputs:
|
||
|
- name: fromChainId
|
||
|
indexed: true
|
||
|
type: uint256
|
||
|
- name: messageId
|
||
|
indexed: true
|
||
|
type: bytes32
|
||
|
```
|
||
|
|
||
|
## Rationale
|
||
|
|
||
|
The `MessageDispatcher` can be coupled to one or more `MessageExecutor`. It is up to bridges to decide how to couple the two. Users can easily bridge a message by calling `dispatchMessage` without being aware of the `MessageExecutor` address. Messages can also be traced by a client using the data logged by the `MessageIdExecuted` event.
|
||
|
|
||
|
Some bridges may require payment in the native currency, so the `dispatchMessage` function is payable.
|
||
|
|
||
|
## Backwards Compatibility
|
||
|
|
||
|
This specification is compatible with existing governance systems as it offers simple cross-chain execution.
|
||
|
|
||
|
## Security Considerations
|
||
|
|
||
|
Bridge trust profiles are variable, so users must understand that bridge security depends on the implementation.
|
||
|
|
||
|
## Copyright
|
||
|
|
||
|
Copyright and related rights waived via [CC0](../LICENSE.md).
|