Skip to content

Commit

Permalink
Update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
ernestognw committed Jan 8, 2025
1 parent 5074e7f commit 3e8620d
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 83 deletions.
2 changes: 1 addition & 1 deletion contracts/crosschain/axelar/AxelarGatewayDestination.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";

/**
Expand Down
4 changes: 2 additions & 2 deletions contracts/crosschain/utils/ERC7786Receiver.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand Down
68 changes: 68 additions & 0 deletions contracts/crosschain/utils/IERC7786.sol
Original file line number Diff line number Diff line change
@@ -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);
}
65 changes: 1 addition & 64 deletions contracts/interfaces/IERC7786.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Original file line number Diff line number Diff line change
@@ -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)) {}
}
11 changes: 11 additions & 0 deletions contracts/mocks/docs/crosschain/MyCustomAxelarGatewayDuplex.sol
Original file line number Diff line number Diff line change
@@ -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) {}
}
13 changes: 13 additions & 0 deletions contracts/mocks/docs/crosschain/MyCustomAxelarGatewaySource.sol
Original file line number Diff line number Diff line change
@@ -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) {}
}
47 changes: 47 additions & 0 deletions contracts/mocks/docs/crosschain/MyERC7786GatewaySource.sol
Original file line number Diff line number Diff line change
@@ -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;
}
}
29 changes: 29 additions & 0 deletions contracts/mocks/docs/crosschain/MyERC7786ReceiverContract.sol
Original file line number Diff line number Diff line change
@@ -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
}
}
45 changes: 29 additions & 16 deletions docs/modules/ROOT/pages/crosschain.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand All @@ -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]
----
Expand Down

0 comments on commit 3e8620d

Please sign in to comment.