Cross-Chain Execution
ERC-5164 defines a cross-chain execution interface for EVM-based blockchains, allowing contracts on one chain to call contracts on another by sending a cross-chain message. The specification includes 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. The motivation for this specification is to coordinate state changes across multiple EVM-based blockchains, which often have different APIs for bridge integrations. Defining a common specification will increase code re-use and allow for the use of common bridge implementations. The specification allows contracts on one chain to send messages to contracts on another chain, and there are two key interfaces that need to be implemented. The specification uses key words such as "MUST", "SHOULD", and "OPTIONAL" to define requirements and recommendations for implementation.
Video
Original
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.
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. The message identifier MUST be unique across chains and dispatchers. This can be achieved by hashing a tuple of chainId, dispatcherAddress, messageNonce
where messageNonce is a monotonically increasing integer per message.
MessageDispatcher Methods
dispatchMessage
Will dispatch a message to be executed by the MessageExecutor
on the destination chain specified by toChainId
.
MessageDispatcher
s MUST emit the MessageDispatched
event when a message is dispatched.
MessageDispatcher
s MUST revert if toChainId
is not supported.
MessageDispatcher
s MUST forward the message to a MessageExecutor
on the toChainId
.
MessageDispatcher
s MUST use a unique messageId
for each message.
MessageDispatcher
s MUST return the messageId
to allow the message sender to track the message.
MessageDispatcher
s MAY require payment.
interface MessageDispatcher { function dispatchMessage(uint256 toChainId, address to, bytes calldata data) external payable returns (bytes32 messageId); }
- name: dispatchMessage type: function stateMutability: payable inputs: - name: toChainId type: uint256 - name: to type: address - name: data type: bytes outputs: - name: messageId type: bytes32
MessageDispatcher Events
MessageDispatched
The MessageDispatched
event MUST be emitted by the MessageDispatcher
when an individual message is dispatched.
interface MessageDispatcher { event MessageDispatched( bytes32 indexed messageId, address indexed from, uint256 indexed toChainId, address to, bytes data, ); }
- 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
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.
to.call(abi.encodePacked(data, messageId, fromChainId, from));
- name: calldata type: bytes inputs: - name: data type: bytes - name: messageId type: bytes32 - name: fromChainId type: uint256 - name: from type: address
MessageExecutor Events
MessageIdExecuted
MessageIdExecuted
MUST be emitted once a message or message batch has been executed.
interface MessageExecutor { event MessageIdExecuted( uint256 indexed fromChainId, bytes32 indexed messageId ); }
- name: MessageIdExecuted type: event inputs: - name: fromChainId indexed: true type: uint256 - name: messageId indexed: true type: bytes32
MessageExecutor Errors
MessageAlreadyExecuted
MessageExecutor
s MUST revert if a messageId has already been executed and SHOULD emit a MessageIdAlreadyExecuted
custom error.
interface MessageExecutor { error MessageIdAlreadyExecuted( bytes32 messageId ); }
MessageFailure
MessageExecutor
s MUST revert if an individual message fails and SHOULD emit a MessageFailure
custom error.
interface MessageExecutor { error MessageFailure( bytes32 messageId, bytes errorData ); }
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.
Adopted by projects
Not miss a beat of EIPs' update?
Subscribe EIPs Fun to receive the latest updates of EIPs Good for Buidlers to follow up.
View all