|
| 1 | += Cross-chain messaging |
| 2 | + |
| 3 | +Developers building contracts may require cross-chain functionality. To accomplish this, multiple protocols have implemented their own ways to process operations across chains. |
| 4 | + |
| 5 | +The variety of these bridges is outlined in https://x.com/norswap[@norswap]'s https://github.com/0xFableOrg/xchain/blob/master/README.md[Cross-Chain Interoperability Report] that proposes https://github.com/0xFableOrg/xchain/blob/master/README.md#bridge-taxonomy[a taxonomy of 7 bridge categories]. This diversity makes it difficult for developers to design cross-chain applications given the lack of portability. |
| 6 | + |
| 7 | +This guide will teach you how to follow https://eips.ethereum.org/EIPS/eip-7786[ERC-7786] to establish messaging gateways across chains regardless of the underlying bridge. Developers can implement gateway contracts that process cross-chain messages and connect any crosschain protocol they want (or implement themselves). |
| 8 | + |
| 9 | +== ERC-7786 Gateway |
| 10 | + |
| 11 | +To address the lack of composability in a simple and unopinionated way, ERC-7786 proposes a standard for implementing gateways that relay messages to other chains. This generalized approach is expressive enough to enable new types of applications and can be adapted to any bridge taxonomy or specific bridge interface with standardized attributes. |
| 12 | + |
| 13 | +=== Message passing overview |
| 14 | + |
| 15 | +The ERC defines a source and a destination gateway. Both are contracts that implement a protocol to send a message and process its reception respectively. These two processes are identified explicitly by the ERC-7786 specification since they define the minimal requirements for both gateways. |
| 16 | + |
| 17 | +* On the **source chain**, the contract implements a standard xref:api:crosschain.adoc#AxelarGatewaySource-sendMessage-string-string-bytes-bytes---[`sendMessage`] function and emits a xref:api:crosschain.adoc#AxelarGatewaySource-MessagePosted-bytes32-string-string-bytes-bytes---[`MessagePosted`] event to signal that the message should be relayed by the underlying protocol. |
| 18 | + |
| 19 | +* On the **destination chain**, the gateway receives the message and passes it to the receiver contract by calling the xref:api:crosschain.adoc#ERC7786Receiver-executeMessage-string-string-string-bytes-bytes---[`executeMessage`] function. |
| 20 | + |
| 21 | +Smart contract developers only need to worry about implementing the xref:api:crosschain.adoc#IERC7786GatewaySource[IERC7786GatewaySource] interface to send a message on the source chain and the xref:api:crosschain.adoc#IERC7786GatewaySource[IERC7786GatewaySource] and xref:api:crosschain.adoc#IERC7786Receiver[IERC7786Receiver] interface to receive such message on the destination chain. |
| 22 | + |
| 23 | +=== Getting started with Axelar Network |
| 24 | + |
| 25 | +To start sending cross-chain messages, developers can get started with a duplex gateway powered by Axelar Network. This will allow a contract to send or receive cross-chain messages leveraging automated execution by Axelar relayers on the destination chain. |
| 26 | + |
| 27 | +[source,solidity] |
| 28 | +---- |
| 29 | +include::api:example$crosschain/MyCustomAxelarGatewayDuplex.sol[] |
| 30 | +---- |
| 31 | + |
| 32 | +For more details of how the duplex gateway works, see xref:crosschain.adoc#axelar_network[how to send and receive messages with the Axelar Network] below |
| 33 | + |
| 34 | +NOTE: Developers can register supported chains and destination gateways using the xref:api:crosschain.adoc#AxelarGatewayBase-registerChainEquivalence-string-string-[`registerChainEquivalence`] and xref:api:crosschain.adoc#AxelarGatewayBase-registerRemoteGateway-string-string-[`registerRemoteGateway`] functions |
| 35 | + |
| 36 | +== Cross-chain communication |
| 37 | + |
| 38 | +=== Sending a message |
| 39 | + |
| 40 | +The interface for a source gateway is general enough that it allows wrapping a custom protocol to authenticate messages. Depending on the use case, developers can implement any offchain mechanism to read the standard xref:api:crosschain.adoc#IERC7786GatewaySource-MessagePosted-bytes32-string-string-bytes-bytes---[`MessagePosted`] event and deliver it to the receiver on the destination chain. |
| 41 | + |
| 42 | +[source,solidity] |
| 43 | +---- |
| 44 | +include::api:example$crosschain/MyERC7786GatewaySource.sol[] |
| 45 | +---- |
| 46 | + |
| 47 | +NOTE: The standard represents chains using https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md[CAIP-2] identifiers and accounts using https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md[CAIP-10] identifiers for increased interoperability with non-EVM chains. Consider using the Strings library in the contracts library to process these identifiers. |
| 48 | + |
| 49 | +=== Receiving a message |
| 50 | + |
| 51 | +To successfully process a message on the destination chain, a destination gateway is required. Although ERC-7786 doesn't define a standard interface for the destination gateway, it requires that it calls the `executeMessage` upon message reception. |
| 52 | + |
| 53 | +Every cross-chain message protocol already offers a way to receive the message either through a canonical bridge or an intermediate contract. Developers can easily wrap the receiving contract into a gateway that calls the `executeMessage` function as mandated by the ERC. |
| 54 | + |
| 55 | +To receive a message on a custom smart contract, OpenZeppelin Community Contracts provide an xref:api:crosschain.adoc#ERC7786Receiver[ERC7786Receiver] implementation for developers to inherit. This way your contracts can receive a cross-chain message relayed through a known destination gateway gateway. |
| 56 | + |
| 57 | +[source,solidity] |
| 58 | +---- |
| 59 | +include::api:example$crosschain/MyERC7786ReceiverContract.sol[] |
| 60 | +---- |
| 61 | + |
| 62 | +The standard receiving interface abstracts away the underlying protocol. This way, it is possible for a contract to send a message through an ERC-7786 compliant gateway (or through an adapter) and get it received on the destination chain without worrying about the protocol implementation details. |
| 63 | + |
| 64 | +=== Axelar Network |
| 65 | + |
| 66 | +Aside from the xref:api:crosschain.adoc#AxelarGatewayDuplex[AxelarGatewayDuplex], the library offers an implementation of the xref:api:crosschain.adoc#IERC7786GatewaySource[IERC7786GatewaySource] interface called xref:api:crosschain.adoc#AxelarGatewaySource[AxelarGatewaySource] that works as an adapter for sending messages in compliance with ERC-7786 |
| 67 | + |
| 68 | +The implementation takes a local gateway address that MUST correspond to https://axelarscan.io/resources/chains?type=evm[Axelar's native gateways] and has mechanisms to: |
| 69 | + |
| 70 | +* Keep track of equivalences between Axelar chain names and CAIP-2 identifiers |
| 71 | +* Record a destination gateway per network using their CAIP-2 identifier |
| 72 | + |
| 73 | +The xref:api:crosschain.adoc#AxelarGatewaySource[AxelarGatewaySource] implementation can be used out of the box |
| 74 | + |
| 75 | +[source,solidity] |
| 76 | +---- |
| 77 | +include::api:example$crosschain/MyCustomAxelarGatewaySource.sol[] |
| 78 | +---- |
| 79 | + |
| 80 | +For a destination gateway, the library provides an adapter of the `AxelarExecutable` interface to receive messages and relay them to an xref:api:crosschain.adoc#IERC7786Receiver[IERC7786Receiver]. |
| 81 | + |
| 82 | +[source,solidity] |
| 83 | +---- |
| 84 | +include::api:example$crosschain/MyCustomAxelarGatewayDestination.sol[] |
| 85 | +---- |
| 86 | + |
| 87 | +=== Multi Bridge Aggregator |
| 88 | + |
| 89 | +The xref:api:crosschain.adoc#ERC7786Aggregator[ERC7786Aggregator] is a special gateway that implements both xref:api:crosschain.adoc#IERC7786GatewaySource[IERC7786GatewaySource] and xref:api:crosschain.adoc#IERC7786Receiver[IERC7786Receiver] interfaces. It provides a way to send messages across multiple bridges simultaneously and ensures message delivery through a threshold-based confirmation system. |
| 90 | + |
| 91 | +The aggregator maintains a list of known gateways and a confirmation threshold. When sending a message, it broadcasts to all registered gateways, and when receiving, it requires a minimum number of confirmations before executing the message. This approach increases reliability by ensuring messages are properly delivered and validated across multiple bridges. |
| 92 | + |
| 93 | +When sending a message, the aggregator tracks the message IDs from each gateway to maintain a record of the message's journey across different bridges: |
| 94 | + |
| 95 | +```solidity |
| 96 | +function sendMessage( |
| 97 | + string calldata destinationChain, |
| 98 | + string memory receiver, |
| 99 | + bytes memory payload, |
| 100 | + bytes[] memory attributes |
| 101 | +) public payable virtual whenNotPaused returns (bytes32 outboxId) { |
| 102 | + |
| 103 | + // ... Initialize variables and prepare payload ... |
| 104 | + |
| 105 | + // Post on all gateways |
| 106 | + Outbox[] memory outbox = new Outbox[](_gateways.length()); |
| 107 | + bool needsId = false; |
| 108 | + for (uint256 i = 0; i < outbox.length; ++i) { |
| 109 | + address gateway = _gateways.at(i); |
| 110 | + // send message |
| 111 | + bytes32 id = IERC7786GatewaySource(gateway).sendMessage( |
| 112 | + destinationChain, |
| 113 | + aggregator, |
| 114 | + wrappedPayload, |
| 115 | + attributes |
| 116 | + ); |
| 117 | + // if ID, track it |
| 118 | + if (id != bytes32(0)) { |
| 119 | + outbox[i] = Outbox(gateway, id); |
| 120 | + needsId = true; |
| 121 | + } |
| 122 | + } |
| 123 | + |
| 124 | + // ... Handle message tracking and return value ... |
| 125 | +} |
| 126 | +``` |
| 127 | + |
| 128 | +On the receiving end, the aggregator implements a threshold-based confirmation system. Messages are only executed after receiving enough confirmations from the gateways, ensuring message validity and preventing double execution. The xref:api:crosschain.adoc#ERC7786Aggregator-executeMessage-string-string-string-bytes-bytes---[`executeMessage`] function handles this process: |
| 129 | + |
| 130 | +```solidity |
| 131 | +function executeMessage( |
| 132 | + string calldata /*messageId*/, // gateway specific, empty or unique |
| 133 | + string calldata sourceChain, // CAIP-2 chain identifier |
| 134 | + string calldata sender, // CAIP-10 account address (does not include the chain identifier) |
| 135 | + bytes calldata payload, |
| 136 | + bytes[] calldata attributes |
| 137 | +) public payable virtual whenNotPaused returns (bytes4) { |
| 138 | + |
| 139 | + // ... Validate message format and extract message ID ... |
| 140 | + |
| 141 | + // If call is first from a trusted gateway |
| 142 | + if (_gateways.contains(msg.sender) && !tracker.receivedBy[msg.sender]) { |
| 143 | + // Count number of time received |
| 144 | + tracker.receivedBy[msg.sender] = true; |
| 145 | + ++tracker.countReceived; |
| 146 | + emit Received(id, msg.sender); |
| 147 | + |
| 148 | + // if already executed, leave gracefully |
| 149 | + if (tracker.executed) return IERC7786Receiver.executeMessage.selector; |
| 150 | + } else if (tracker.executed) { |
| 151 | + revert ERC7786AggregatorAlreadyExecuted(); |
| 152 | + } |
| 153 | + |
| 154 | + // ... Validate sender and prepare payload for execution ... |
| 155 | + |
| 156 | + // If ready to execute, and not yet executed |
| 157 | + if (tracker.countReceived >= getThreshold()) { |
| 158 | + // prevent re-entry |
| 159 | + tracker.executed = true; |
| 160 | + |
| 161 | + // ... Prepare execution context and validate state ... |
| 162 | + bytes memory call = abi.encodeCall( |
| 163 | + IERC7786Receiver.executeMessage, |
| 164 | + (uint256(id).toHexString(32), sourceChain, originalSender, unwrappedPayload, attributes) |
| 165 | + ); |
| 166 | + |
| 167 | + (bool success, bytes memory returndata) = receiver.parseAddress().call(call); |
| 168 | + |
| 169 | + // ... Handle the result ... |
| 170 | + } |
| 171 | + |
| 172 | + return IERC7786Receiver.executeMessage.selector; |
| 173 | +} |
| 174 | +``` |
| 175 | + |
| 176 | +The aggregator is designed to be configurable. As an `Ownable` contract, it allows the owner to manage the list of trusted gateways and adjust the confirmation threshold. The `_gateways` list and threshold are initially set during contract deployment using the xref:api:crosschain.adoc#ERC7786Aggregator-_addGateway-address-[`++_addGateway++`] and xref:api:crosschain.adoc#ERC7786Aggregator-_setThreshold-uint8-[`++_setThreshold++`] functions. The owner can update these settings as needed to adapt to changing requirements or add new gateways. |
0 commit comments