ERC-5164 Cross-chain execution standard (Cross-Chain Execution)

Last Call · Other · created 2022-06-14

Defines a common interface that lets a contract on one chain send a message to a contract on another EVM chain to execute code. It consists of two parts: a dispatcher on the sending side and an executor on the receiving side.

Why it is needed

Many protocols in the Ethereum ecosystem need to coordinate state changes across multiple EVM chains. Bridges, services that connect chains, already let a contract on one chain execute code on another chain, but each bridge has a different API, so integration code has to be written separately for each bridge. Security level, speed, and control also vary by bridge. This makes it hard to reuse cross-chain code written once across different bridges. This proposal aims to define a simple common specification to increase code reuse and let multiple projects share the same bridge implementation.

What to implement

In depth

Two part structure: MessageDispatcher and MessageExecutor

The standard defines two interfaces, and an implementation must implement both. MessageDispatcher resides on the origin chain that sends messages and relays them through a transport layer to one or more MessageExecutor contracts. MessageExecutor resides on the destination chain and executes dispatched messages. Each bridge decides how to pair dispatchers with executors. Users can send a message just by calling dispatchMessage without knowing the executor's address.

messageId generation rule

A unique messageId must be generated for each message or batch of messages, and this identifier must be unique across chains and dispatchers (MUST). The spec suggests a method of hashing a tuple of three values, chainId, dispatcherAddress, and messageNonce, where messageNonce is an integer that increments by 1 for each message.

Obligations of dispatchMessage

The dispatcher must emit a MessageDispatched event when sending a message (MUST), and must revert if given an unsupported toChainId (MUST). It must deliver the message to the MessageExecutor on toChainId, use a unique messageId for each message, and must return the messageId so the sender can track it. Since it may require a fee (MAY), the function is declared payable, because some bridges may require payment in the native currency.

Executor execution rules: no duplicates, no guaranteed order

An executor must not successfully execute the same messageId more than once (MUST NOT). If an already executed messageId comes in again, it should revert and emit a MessageIdAlreadyExecuted error (SHOULD). Execution order, on the other hand, is not guaranteed. Because messages can pass through the transport layer out of order, messageIds may be executed in any order. The executor should also verify all message data through the bridge's transport layer (SHOULD).

Failure handling and retries

If message execution fails, the executor must revert the transaction (MUST), so the same message can be retried later. In this case it should emit a MessageFailure(messageId, errorData) custom error (SHOULD). Once execution completes, it must emit a MessageIdExecuted event (MUST), and clients can track messages using the data recorded in this event.

Sender information appended after calldata

When executing a message, the executor must append the ABI packed (messageId, fromChainId, from) after the call data (MUST). In the spec's code this takes the form to.call(abi.encodePacked(data, messageId, fromChainId, from)). The contract receiving the message can read this appended data to verify which address on which chain sent the message.

Proposal status: Last Call

This proposal is currently in Last Call status, with a deadline listed as November 15, 2023. Since this is the last review stage before Final status, details may still change.

Things to watch out for

Original ERC · ← ERCs