From 0ac6bdb9574a2462dae61f8f2185ed08e4a2f688 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 2 Dec 2024 19:14:58 -0600 Subject: [PATCH 01/10] Add documentation for crosschain message passing --- contracts/crosschain/README.adoc | 38 +++++++++++++ docs/modules/ROOT/nav.adoc | 1 + docs/modules/ROOT/pages/crosschain.adoc | 72 +++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 contracts/crosschain/README.adoc create mode 100644 docs/modules/ROOT/pages/crosschain.adoc diff --git a/contracts/crosschain/README.adoc b/contracts/crosschain/README.adoc new file mode 100644 index 00000000..1a6f8899 --- /dev/null +++ b/contracts/crosschain/README.adoc @@ -0,0 +1,38 @@ += Crosschain + +[.readme-notice] +NOTE: This document is better viewed at https://docs.openzeppelin.com/community-contracts/proxy + +Gateways are contracts that enable cross-chain communication. These can either be a message source or a destination according to ERC-7786, which defines the following interfaces: + + * {IERC7786GatewaySource}: A contract interface to send a message to another contract on a different chain. + * {IERC7786Receiver}: An interface that allows an smart contract to receive a crosschain message provided by a trusted destination gateway. + +The library provides an implementation of an ERC-7786 receiver contract as a building block: + + * {ERC7786Receiver}: ERC-7786 cross-chain message receiver. + +Given ERC-7786 could be enabled natively or via adapters. Developers can access interoperability protocols through gateway adapters. The library includes the following gateway adapters: + + * {AxelarGatewayBase}: Gateway adapter implementation for the https://www.axelar.network/[Axelar Network]. + * {AxelarGatewaySource}: ERC-7786 sender gateway. It sends a cross-chain message through Axelar. + * {AxelarGatewayDestination}: ERC-7786 destination gateway. It executes a cross-chain message from Axelar. + * {AxelarGatewayDuplex}: ERC-7786 gateway that operates in both directions (i.e. send and receive messages) using the Axelar network. + +== Gateways + +{{IERC7786GatewaySource}} + +{{IERC7786Receiver}} + +{{ERC7786Receiver}} + +== Axelar Adapters + +{{AxelarGatewayBase}} + +{{AxelarGatewaySource}} + +{{AxelarGatewayDestination}} + +{{AxelarGatewayDuplex}} diff --git a/docs/modules/ROOT/nav.adoc b/docs/modules/ROOT/nav.adoc index 27f6f899..7ad1107c 100644 --- a/docs/modules/ROOT/nav.adoc +++ b/docs/modules/ROOT/nav.adoc @@ -1 +1,2 @@ * xref:index.adoc[Overview] +* xref:crosschain.adoc[Crosschain] diff --git a/docs/modules/ROOT/pages/crosschain.adoc b/docs/modules/ROOT/pages/crosschain.adoc new file mode 100644 index 00000000..f8eb328d --- /dev/null +++ b/docs/modules/ROOT/pages/crosschain.adoc @@ -0,0 +1,72 @@ += Cross-chain messaging + +Developers building contracts may require cross-chain functionality. To accomplish it, multiple protocols have implemented their own ways to process operations across chains. + +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. + +This guide will teach you how to follow 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). + +== ERC-7786 gateway + +To address the lack of composability in a simple and unopinionated way, ERC-7786 proposes a standard to implement a gateway that relays 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. + +=== Message passing overview + +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. + +* On the **source chain**, the contract implements a standard xref:api:crosschain.adoc#IERC7786GatewaySource#sendMessage[`sendMessage`] function and emits a xref:api:crosschain.adoc#IERC7786GatewaySource#MessagePosted[`MessagePosted`] event to signal that the message should be relayed by the underlying protocol. + +* On the **destination chain**, the gateway that receives the message and passes it to the contract receiver by calling the xref:api:crosschain.adoc#IERC7786Receiver#executeMessage[`executeMessage`] function. + +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. + +== Sending a message + +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[`MessagePosted`] event and deliver it to the receiver on the destination chain. + +[source,solidity] +---- +include::api:example$crosschain/MyERC7786GatewaySource.sol[] +---- + +NOTE: The standard defines the chains in https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md[CAIP-2] identifiers and the accounts 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. + +=== Axelar Network + +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 ERC-7786. + +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: + +* Keep track of equivalences between Axelar chain names and CAIP-2 identifiers +* Record a destination gateway per network using their CAIP-2 identifier + +The xref:api:crosschain.adoc#AxelarGatewaySource[AxelarGatewaySource] implementation can be used out of the box + +[source,solidity] +---- +include::api:example$crosschain/MyCustomAxelarGatewaySource.sol[] +---- + +== Receiving a message + +To sucessfully 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. + +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. + +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. + +[source,solidity] +---- +include::api:example$crosschain/MyERC7786ReceiverContract.sol[] +---- + +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. + +=== Axelar Network + +To solve the requirement of 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]. + +[source,solidity] +---- +include::api:example$crosschain/MyCustomAxelarGatewayDestination.sol[] +---- From 886a498068ae0546b5551c0a095f6b39153c53cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Mon, 2 Dec 2024 19:34:04 -0600 Subject: [PATCH 02/10] Fix codespell --- docs/modules/ROOT/pages/crosschain.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/crosschain.adoc b/docs/modules/ROOT/pages/crosschain.adoc index f8eb328d..4466ffc8 100644 --- a/docs/modules/ROOT/pages/crosschain.adoc +++ b/docs/modules/ROOT/pages/crosschain.adoc @@ -49,7 +49,7 @@ include::api:example$crosschain/MyCustomAxelarGatewaySource.sol[] == Receiving a message -To sucessfully 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. +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. 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. From e1426a1a096ea6772bd3b48cae86b018d58bfa16 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 3 Dec 2024 08:58:11 -0600 Subject: [PATCH 03/10] Update contracts/crosschain/README.adoc Co-authored-by: Hadrien Croubois --- contracts/crosschain/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/crosschain/README.adoc b/contracts/crosschain/README.adoc index 1a6f8899..3b61c744 100644 --- a/contracts/crosschain/README.adoc +++ b/contracts/crosschain/README.adoc @@ -1,7 +1,7 @@ = Crosschain [.readme-notice] -NOTE: This document is better viewed at https://docs.openzeppelin.com/community-contracts/proxy +NOTE: This document is better viewed at https://docs.openzeppelin.com/community-contracts/crosschain Gateways are contracts that enable cross-chain communication. These can either be a message source or a destination according to ERC-7786, which defines the following interfaces: From 9be5fff2c5d54a4da591629c76b0b4b09ec560f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 3 Dec 2024 08:59:05 -0600 Subject: [PATCH 04/10] Update contracts/crosschain/README.adoc Co-authored-by: Hadrien Croubois --- contracts/crosschain/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/crosschain/README.adoc b/contracts/crosschain/README.adoc index 3b61c744..5a794b7d 100644 --- a/contracts/crosschain/README.adoc +++ b/contracts/crosschain/README.adoc @@ -14,7 +14,7 @@ The library provides an implementation of an ERC-7786 receiver contract as a bui Given ERC-7786 could be enabled natively or via adapters. Developers can access interoperability protocols through gateway adapters. The library includes the following gateway adapters: - * {AxelarGatewayBase}: Gateway adapter implementation for the https://www.axelar.network/[Axelar Network]. + * {AxelarGatewayBase}: Core gateway logic for the https://www.axelar.network/[Axelar] adapter. * {AxelarGatewaySource}: ERC-7786 sender gateway. It sends a cross-chain message through Axelar. * {AxelarGatewayDestination}: ERC-7786 destination gateway. It executes a cross-chain message from Axelar. * {AxelarGatewayDuplex}: ERC-7786 gateway that operates in both directions (i.e. send and receive messages) using the Axelar network. From 1bfadfcdfbf75d80dbd8c118105156d2f1fbe0d9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 3 Dec 2024 09:01:09 -0600 Subject: [PATCH 05/10] Update contracts/crosschain/README.adoc Co-authored-by: Hadrien Croubois --- contracts/crosschain/README.adoc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/crosschain/README.adoc b/contracts/crosschain/README.adoc index 5a794b7d..39ce26b2 100644 --- a/contracts/crosschain/README.adoc +++ b/contracts/crosschain/README.adoc @@ -15,9 +15,9 @@ The library provides an implementation of an ERC-7786 receiver contract as a bui Given ERC-7786 could be enabled natively or via adapters. Developers can access interoperability protocols through gateway adapters. The library includes the following gateway adapters: * {AxelarGatewayBase}: Core gateway logic for the https://www.axelar.network/[Axelar] adapter. - * {AxelarGatewaySource}: ERC-7786 sender gateway. It sends a cross-chain message through Axelar. - * {AxelarGatewayDestination}: ERC-7786 destination gateway. It executes a cross-chain message from Axelar. - * {AxelarGatewayDuplex}: ERC-7786 gateway that operates in both directions (i.e. send and receive messages) using the Axelar network. + * {AxelarGatewaySource}: ERC-7786 source gateway adapter (sending side) for Axelar. + * {AxelarGatewayDestination}: ERC-7786 destination gateway adapter (receiving side) for Axelar. + * {AxelarGatewayDuplex}: ERC-7786 gateway adapter that operates in both directions (i.e. send and receive messages) using the Axelar network. == Gateways From 1f6058a45d3cc7b71bae7854cf6cf515720c82fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 13 Dec 2024 09:42:46 -0600 Subject: [PATCH 06/10] Update docs/modules/ROOT/pages/crosschain.adoc Co-authored-by: Hadrien Croubois --- docs/modules/ROOT/pages/crosschain.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/modules/ROOT/pages/crosschain.adoc b/docs/modules/ROOT/pages/crosschain.adoc index 4466ffc8..2f317ccb 100644 --- a/docs/modules/ROOT/pages/crosschain.adoc +++ b/docs/modules/ROOT/pages/crosschain.adoc @@ -29,7 +29,7 @@ The interface for a source gateway is general enough that it allows wrapping a c include::api:example$crosschain/MyERC7786GatewaySource.sol[] ---- -NOTE: The standard defines the chains in https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-2.md[CAIP-2] identifiers and the accounts 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. +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. === Axelar Network From 36e4de1fd0aec21458353fb6e046ab2f075319fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Fri, 13 Dec 2024 09:45:06 -0600 Subject: [PATCH 07/10] Apply review suggestions --- contracts/crosschain/README.adoc | 6 +++++- lib/@openzeppelin-contracts | 2 +- lib/@openzeppelin-contracts-upgradeable | 2 +- lib/forge-std | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/contracts/crosschain/README.adoc b/contracts/crosschain/README.adoc index 39ce26b2..c91402e5 100644 --- a/contracts/crosschain/README.adoc +++ b/contracts/crosschain/README.adoc @@ -23,11 +23,15 @@ Given ERC-7786 could be enabled natively or via adapters. Developers can access {{IERC7786GatewaySource}} +== Clients + {{IERC7786Receiver}} {{ERC7786Receiver}} -== Axelar Adapters +== Adapters + +=== Axelar {{AxelarGatewayBase}} diff --git a/lib/@openzeppelin-contracts b/lib/@openzeppelin-contracts index 653963be..ff313419 160000 --- a/lib/@openzeppelin-contracts +++ b/lib/@openzeppelin-contracts @@ -1 +1 @@ -Subproject commit 653963beb27f4dcda72ca2773f0a6bd7b733a6f4 +Subproject commit ff3134197fbc00183f782a7dc085302b7068c6c6 diff --git a/lib/@openzeppelin-contracts-upgradeable b/lib/@openzeppelin-contracts-upgradeable index 94c7b7c8..f37666cd 160000 --- a/lib/@openzeppelin-contracts-upgradeable +++ b/lib/@openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 94c7b7c87a69139c4e4d33ac142a1d1820b0d7d5 +Subproject commit f37666cd2b2ee147c92a0c27dd4a8c6eb8b71b1d diff --git a/lib/forge-std b/lib/forge-std index 83c5d212..d3db4ef9 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 83c5d212a01f8950727da4095cdfe2654baccb5b +Subproject commit d3db4ef90a72b7d24aa5a2e5c649593eaef7801d From 3e8620d0e05998252dc1a4c801d1ed32263366f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 7 Jan 2025 18:44:14 -0600 Subject: [PATCH 08/10] Update docs --- .../axelar/AxelarGatewayDestination.sol | 2 +- .../crosschain/utils/ERC7786Receiver.sol | 4 +- contracts/crosschain/utils/IERC7786.sol | 68 +++++++++++++++++++ contracts/interfaces/IERC7786.sol | 65 +----------------- .../MyCustomAxelarGatewayDestination.sol | 11 +++ .../MyCustomAxelarGatewayDuplex.sol | 11 +++ .../MyCustomAxelarGatewaySource.sol | 13 ++++ .../crosschain/MyERC7786GatewaySource.sol | 47 +++++++++++++ .../crosschain/MyERC7786ReceiverContract.sol | 29 ++++++++ docs/modules/ROOT/pages/crosschain.adoc | 45 +++++++----- 10 files changed, 212 insertions(+), 83 deletions(-) create mode 100644 contracts/crosschain/utils/IERC7786.sol create mode 100644 contracts/mocks/docs/crosschain/MyCustomAxelarGatewayDestination.sol create mode 100644 contracts/mocks/docs/crosschain/MyCustomAxelarGatewayDuplex.sol create mode 100644 contracts/mocks/docs/crosschain/MyCustomAxelarGatewaySource.sol create mode 100644 contracts/mocks/docs/crosschain/MyERC7786GatewaySource.sol create mode 100644 contracts/mocks/docs/crosschain/MyERC7786ReceiverContract.sol diff --git a/contracts/crosschain/axelar/AxelarGatewayDestination.sol b/contracts/crosschain/axelar/AxelarGatewayDestination.sol index 96bd841b..f21c548d 100644 --- a/contracts/crosschain/axelar/AxelarGatewayDestination.sol +++ b/contracts/crosschain/axelar/AxelarGatewayDestination.sol @@ -4,7 +4,7 @@ pragma solidity ^0.8.27; import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol"; import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; -import {IERC7786Receiver} from "../../interfaces/IERC7786.sol"; +import {IERC7786Receiver} from "../utils/IERC7786.sol"; import {AxelarGatewayBase} from "./AxelarGatewayBase.sol"; /** diff --git a/contracts/crosschain/utils/ERC7786Receiver.sol b/contracts/crosschain/utils/ERC7786Receiver.sol index a889bf02..ba8ea409 100644 --- a/contracts/crosschain/utils/ERC7786Receiver.sol +++ b/contracts/crosschain/utils/ERC7786Receiver.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.27; -import {IERC7786Receiver} from "../../interfaces/IERC7786.sol"; +import {IERC7786Receiver} from "./IERC7786.sol"; /** * @dev Base implementation of an ERC-7786 compliant cross-chain message receiver. @@ -33,7 +33,7 @@ abstract contract ERC7786Receiver is IERC7786Receiver { } /// @dev Virtual getter that returns whether an address is a valid ERC-7786 gateway. - function _isKnownGateway(address instance) internal view virtual returns (bool); + function _isKnownGateway(address instance) internal virtual returns (bool); /// @dev Virtual function that should contain the logic to execute when a cross-chain message is received. function _processMessage( diff --git a/contracts/crosschain/utils/IERC7786.sol b/contracts/crosschain/utils/IERC7786.sol new file mode 100644 index 00000000..3195cde6 --- /dev/null +++ b/contracts/crosschain/utils/IERC7786.sol @@ -0,0 +1,68 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @dev Interface for ERC-7786 source gateways. + * + * See ERC-7786 for more details + */ +interface IERC7786GatewaySource { + /** + * @dev Event emitted when a message is created. If `outboxId` is zero, no further processing is necessary. If + * `outboxId` is not zero, then further (gateway specific, and non-standardized) action is required. + */ + event MessagePosted( + bytes32 indexed outboxId, + string sender, // CAIP-10 account identifier (chain identifier + ":" + account address) + string receiver, // CAIP-10 account identifier (chain identifier + ":" + account address) + bytes payload, + bytes[] attributes + ); + + /// @dev This error is thrown when a message creation fails because of an unsupported attribute being specified. + error UnsupportedAttribute(bytes4 selector); + + /// @dev Getter to check whether an attribute is supported or not. + function supportsAttribute(bytes4 selector) external view returns (bool); + + /** + * @dev Endpoint for creating a new message. If the message requires further (gateway specific) processing before + * it can be sent to the destination chain, then a non-zero `outboxId` must be returned. Otherwise, the + * message MUST be sent and this function must return 0. + * @param destinationChain {CAIP2} chain identifier + * @param receiver {CAIP10} account address (does not include the chain identifier) + * + * * MUST emit a {MessagePosted} event. + * + * If any of the `attributes` is not supported, this function SHOULD revert with an {UnsupportedAttribute} error. + * Other errors SHOULD revert with errors not specified in ERC-7786. + */ + function sendMessage( + string calldata destinationChain, + string calldata receiver, + bytes calldata payload, + bytes[] calldata attributes + ) external payable returns (bytes32 outboxId); +} + +/** + * @dev Interface for the ERC-7786 client contract (receiver). + * + * See ERC-7786 for more details + */ +interface IERC7786Receiver { + /** + * @dev Endpoint for receiving cross-chain message. + * @param sourceChain {CAIP2} chain identifier + * @param sender {CAIP10} account address (does not include the chain identifier) + * + * This function may be called directly by the gateway. + */ + function executeMessage( + string calldata sourceChain, // CAIP-2 chain identifier + string calldata sender, // CAIP-10 account address (does not include the chain identifier) + bytes calldata payload, + bytes[] calldata attributes + ) external payable returns (bytes4); +} diff --git a/contracts/interfaces/IERC7786.sol b/contracts/interfaces/IERC7786.sol index 3195cde6..06112fea 100644 --- a/contracts/interfaces/IERC7786.sol +++ b/contracts/interfaces/IERC7786.sol @@ -2,67 +2,4 @@ pragma solidity ^0.8.0; -/** - * @dev Interface for ERC-7786 source gateways. - * - * See ERC-7786 for more details - */ -interface IERC7786GatewaySource { - /** - * @dev Event emitted when a message is created. If `outboxId` is zero, no further processing is necessary. If - * `outboxId` is not zero, then further (gateway specific, and non-standardized) action is required. - */ - event MessagePosted( - bytes32 indexed outboxId, - string sender, // CAIP-10 account identifier (chain identifier + ":" + account address) - string receiver, // CAIP-10 account identifier (chain identifier + ":" + account address) - bytes payload, - bytes[] attributes - ); - - /// @dev This error is thrown when a message creation fails because of an unsupported attribute being specified. - error UnsupportedAttribute(bytes4 selector); - - /// @dev Getter to check whether an attribute is supported or not. - function supportsAttribute(bytes4 selector) external view returns (bool); - - /** - * @dev Endpoint for creating a new message. If the message requires further (gateway specific) processing before - * it can be sent to the destination chain, then a non-zero `outboxId` must be returned. Otherwise, the - * message MUST be sent and this function must return 0. - * @param destinationChain {CAIP2} chain identifier - * @param receiver {CAIP10} account address (does not include the chain identifier) - * - * * MUST emit a {MessagePosted} event. - * - * If any of the `attributes` is not supported, this function SHOULD revert with an {UnsupportedAttribute} error. - * Other errors SHOULD revert with errors not specified in ERC-7786. - */ - function sendMessage( - string calldata destinationChain, - string calldata receiver, - bytes calldata payload, - bytes[] calldata attributes - ) external payable returns (bytes32 outboxId); -} - -/** - * @dev Interface for the ERC-7786 client contract (receiver). - * - * See ERC-7786 for more details - */ -interface IERC7786Receiver { - /** - * @dev Endpoint for receiving cross-chain message. - * @param sourceChain {CAIP2} chain identifier - * @param sender {CAIP10} account address (does not include the chain identifier) - * - * This function may be called directly by the gateway. - */ - function executeMessage( - string calldata sourceChain, // CAIP-2 chain identifier - string calldata sender, // CAIP-10 account address (does not include the chain identifier) - bytes calldata payload, - bytes[] calldata attributes - ) external payable returns (bytes4); -} +import {IERC7786GatewaySource, IERC7786Receiver} from "../crosschain/utils/IERC7786.sol"; diff --git a/contracts/mocks/docs/crosschain/MyCustomAxelarGatewayDestination.sol b/contracts/mocks/docs/crosschain/MyCustomAxelarGatewayDestination.sol new file mode 100644 index 00000000..c9724403 --- /dev/null +++ b/contracts/mocks/docs/crosschain/MyCustomAxelarGatewayDestination.sol @@ -0,0 +1,11 @@ +// contracts/MyCustomAxelarGatewayDestination.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AxelarGatewayDestination, AxelarExecutable} from "../../../crosschain/axelar/AxelarGatewayDestination.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; + +abstract contract MyCustomAxelarGatewayDestination is AxelarGatewayDestination { + /// @dev Initializes the contract with the Axelar gateway and the initial owner. + constructor(IAxelarGateway gateway, address initialOwner) AxelarExecutable(address(gateway)) {} +} diff --git a/contracts/mocks/docs/crosschain/MyCustomAxelarGatewayDuplex.sol b/contracts/mocks/docs/crosschain/MyCustomAxelarGatewayDuplex.sol new file mode 100644 index 00000000..fa2afe6c --- /dev/null +++ b/contracts/mocks/docs/crosschain/MyCustomAxelarGatewayDuplex.sol @@ -0,0 +1,11 @@ +// contracts/MyCustomAxelarGatewayDuplex.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {AxelarGatewayDuplex, AxelarExecutable} from "../../../crosschain/axelar/AxelarGatewayDuplex.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; + +abstract contract MyCustomAxelarGatewayDuplex is AxelarGatewayDuplex { + /// @dev Initializes the contract with the Axelar gateway and the initial owner. + constructor(IAxelarGateway gateway, address initialOwner) AxelarGatewayDuplex(gateway, initialOwner) {} +} diff --git a/contracts/mocks/docs/crosschain/MyCustomAxelarGatewaySource.sol b/contracts/mocks/docs/crosschain/MyCustomAxelarGatewaySource.sol new file mode 100644 index 00000000..dbeb5ad0 --- /dev/null +++ b/contracts/mocks/docs/crosschain/MyCustomAxelarGatewaySource.sol @@ -0,0 +1,13 @@ +// contracts/MyERC7786ReceiverContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; +import {AxelarGatewaySource} from "../../../crosschain/axelar/AxelarGatewaySource.sol"; +import {AxelarGatewayBase} from "../../../crosschain/axelar/AxelarGatewayBase.sol"; +import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol"; + +abstract contract MyCustomAxelarGatewaySource is AxelarGatewaySource { + /// @dev Initializes the contract with the Axelar gateway and the initial owner. + constructor(IAxelarGateway gateway, address initialOwner) Ownable(initialOwner) AxelarGatewayBase(gateway) {} +} diff --git a/contracts/mocks/docs/crosschain/MyERC7786GatewaySource.sol b/contracts/mocks/docs/crosschain/MyERC7786GatewaySource.sol new file mode 100644 index 00000000..767ba75d --- /dev/null +++ b/contracts/mocks/docs/crosschain/MyERC7786GatewaySource.sol @@ -0,0 +1,47 @@ +// contracts/MyERC7786GatewaySource.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import {CAIP2} from "@openzeppelin/contracts/utils/CAIP2.sol"; +import {CAIP10} from "@openzeppelin/contracts/utils/CAIP10.sol"; +import {Strings} from "@openzeppelin/contracts/utils/Strings.sol"; +import {IERC7786GatewaySource} from "../../../interfaces/IERC7786.sol"; + +abstract contract MyERC7786GatewaySource is IERC7786GatewaySource { + using Strings for address; + + error UnsupportedNativeTransfer(); + + /// @inheritdoc IERC7786GatewaySource + function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) { + return false; + } + + /// @inheritdoc IERC7786GatewaySource + function sendMessage( + string calldata destinationChain, // CAIP-2 chain identifier + string calldata receiver, // CAIP-10 account address (does not include the chain identifier) + bytes calldata payload, + bytes[] calldata attributes + ) external payable returns (bytes32 outboxId) { + require(msg.value == 0, UnsupportedNativeTransfer()); + // Use of `if () revert` syntax to avoid accessing attributes[0] if it's empty + if (attributes.length > 0) + revert UnsupportedAttribute(attributes[0].length < 0x04 ? bytes4(0) : bytes4(attributes[0][0:4])); + + // Emit event + outboxId = bytes32(0); // Explicitly set to 0. Can be used for post-processing + emit MessagePosted( + outboxId, + CAIP10.format(CAIP2.local(), msg.sender.toChecksumHexString()), + CAIP10.format(destinationChain, receiver), + payload, + attributes + ); + + // Optionally: If this is an adapter, send the message to a protocol gateway for processing + // This may require the logic for tracking destination gateway addresses and chain identifiers + + return outboxId; + } +} diff --git a/contracts/mocks/docs/crosschain/MyERC7786ReceiverContract.sol b/contracts/mocks/docs/crosschain/MyERC7786ReceiverContract.sol new file mode 100644 index 00000000..70adcb4f --- /dev/null +++ b/contracts/mocks/docs/crosschain/MyERC7786ReceiverContract.sol @@ -0,0 +1,29 @@ +// contracts/MyERC7786ReceiverContract.sol +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.22; + +import {AccessManaged} from "@openzeppelin/contracts/access/manager/AccessManaged.sol"; +import {ERC7786Receiver} from "../../../crosschain/utils/ERC7786Receiver.sol"; + +contract MyERC7786ReceiverContract is ERC7786Receiver, AccessManaged { + constructor(address initialAuthority) AccessManaged(initialAuthority) {} + + /// @dev Check if the given instance is a known gateway. + function _isKnownGateway(address /* instance */) internal virtual override restricted returns (bool) { + // The restricted modifier ensures that this function is only called by a known authority. + return true; + } + + /// @dev Internal endpoint for receiving cross-chain message. + /// @param sourceChain {CAIP2} chain identifier + /// @param sender {CAIP10} account address (does not include the chain identifier) + function _processMessage( + address gateway, + string calldata sourceChain, + string calldata sender, + bytes calldata payload, + bytes[] calldata attributes + ) internal virtual override restricted { + // Process the message here + } +} diff --git a/docs/modules/ROOT/pages/crosschain.adoc b/docs/modules/ROOT/pages/crosschain.adoc index 2f317ccb..3b1a6be3 100644 --- a/docs/modules/ROOT/pages/crosschain.adoc +++ b/docs/modules/ROOT/pages/crosschain.adoc @@ -14,40 +14,39 @@ To address the lack of composability in a simple and unopinionated way, ERC-7786 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. -* On the **source chain**, the contract implements a standard xref:api:crosschain.adoc#IERC7786GatewaySource#sendMessage[`sendMessage`] function and emits a xref:api:crosschain.adoc#IERC7786GatewaySource#MessagePosted[`MessagePosted`] event to signal that the message should be relayed by the underlying protocol. +* On the **source chain**, the contract implements a standard xref:api:crosschain.adoc#IERC7786GatewaySource-sendMessage-string-string-bytes-bytes---[`sendMessage`] function and emits a xref:api:crosschain.adoc#IERC7786GatewaySource-MessagePosted-bytes32-string-string-bytes-bytes---[`MessagePosted`] event to signal that the message should be relayed by the underlying protocol. -* On the **destination chain**, the gateway that receives the message and passes it to the contract receiver by calling the xref:api:crosschain.adoc#IERC7786Receiver#executeMessage[`executeMessage`] function. +* On the **destination chain**, the gateway that receives the message and passes it to the contract receiver by calling the xref:api:crosschain.adoc#IERC7786Receiver-executeMessage-string-string-bytes-bytes---[`executeMessage`] function. 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. -== Sending a message +=== Getting started with Axelar Network -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[`MessagePosted`] event and deliver it to the receiver on the destination chain. +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 a receive cross-chain messages from other applications or users leveraging automated execution by Axelar relayers on the destination chain. [source,solidity] ---- -include::api:example$crosschain/MyERC7786GatewaySource.sol[] +include::api:example$crosschain/MyCustomAxelarGatewayDuplex.sol[] ---- -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. +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 -=== Axelar Network +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 -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 ERC-7786. +== Cross-chain communication -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: +=== Sending a message -* Keep track of equivalences between Axelar chain names and CAIP-2 identifiers -* Record a destination gateway per network using their CAIP-2 identifier - -The xref:api:crosschain.adoc#AxelarGatewaySource[AxelarGatewaySource] implementation can be used out of the box +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. [source,solidity] ---- -include::api:example$crosschain/MyCustomAxelarGatewaySource.sol[] +include::api:example$crosschain/MyERC7786GatewaySource.sol[] ---- -== Receiving a message +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. + +=== Receiving a message 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. @@ -64,7 +63,21 @@ The standard receiving interface abstracts away the underlying protocol. This wa === Axelar Network -To solve the requirement of 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]. +Aside from the xref:api:crosschain.adoc#AxelarGatewayDuple[AxelarGatewayDuple], 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 + +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: + +* Keep track of equivalences between Axelar chain names and CAIP-2 identifiers +* Record a destination gateway per network using their CAIP-2 identifier + +The xref:api:crosschain.adoc#AxelarGatewaySource[AxelarGatewaySource] implementation can be used out of the box + +[source,solidity] +---- +include::api:example$crosschain/MyCustomAxelarGatewaySource.sol[] +---- + +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]. [source,solidity] ---- From c7b4205046db521cc501d84d9074708af9818787 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Tue, 7 Jan 2025 18:45:29 -0600 Subject: [PATCH 09/10] Revert lib changes --- lib/@openzeppelin-contracts | 2 +- lib/@openzeppelin-contracts-upgradeable | 2 +- lib/forge-std | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/@openzeppelin-contracts b/lib/@openzeppelin-contracts index a2a5dc26..d4ed5f90 160000 --- a/lib/@openzeppelin-contracts +++ b/lib/@openzeppelin-contracts @@ -1 +1 @@ -Subproject commit a2a5dc26a1b3702733715cbee5dc2c2648eb5d83 +Subproject commit d4ed5f9068bb634ecc423417b4f9554f48fae85b diff --git a/lib/@openzeppelin-contracts-upgradeable b/lib/@openzeppelin-contracts-upgradeable index 3837fe4e..94c7b7c8 160000 --- a/lib/@openzeppelin-contracts-upgradeable +++ b/lib/@openzeppelin-contracts-upgradeable @@ -1 +1 @@ -Subproject commit 3837fe4e4506529be67065cc98583b601173a7e9 +Subproject commit 94c7b7c87a69139c4e4d33ac142a1d1820b0d7d5 diff --git a/lib/forge-std b/lib/forge-std index 726a6ee5..83c5d212 160000 --- a/lib/forge-std +++ b/lib/forge-std @@ -1 +1 @@ -Subproject commit 726a6ee5fc8427a0013d6f624e486c9130c0e336 +Subproject commit 83c5d212a01f8950727da4095cdfe2654baccb5b From 0d0e1d85454b83dc4849eab39760b7ac08263d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ernesto=20Garc=C3=ADa?= Date: Wed, 8 Jan 2025 14:32:34 -0600 Subject: [PATCH 10/10] Update contracts/crosschain/README.adoc Co-authored-by: Arr00 <13561405+arr00@users.noreply.github.com> --- contracts/crosschain/README.adoc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/crosschain/README.adoc b/contracts/crosschain/README.adoc index c91402e5..1a5707fa 100644 --- a/contracts/crosschain/README.adoc +++ b/contracts/crosschain/README.adoc @@ -1,7 +1,7 @@ = Crosschain [.readme-notice] -NOTE: This document is better viewed at https://docs.openzeppelin.com/community-contracts/crosschain +NOTE: This document is better viewed at https://docs.openzeppelin.com/community-contracts/api/crosschain Gateways are contracts that enable cross-chain communication. These can either be a message source or a destination according to ERC-7786, which defines the following interfaces: