Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Standard cross-chain gateways and Axelar adapters #9

Merged
merged 52 commits into from
Nov 1, 2024
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
eee0853
cross-chain prototype v1
Amxx Jun 21, 2024
50ced4a
split common <> axelar
Amxx Jun 21, 2024
28e88cd
add relay observability
Amxx Jun 23, 2024
948dab4
Update oz to master
ernestognw Jul 30, 2024
e350890
Iterate
Aug 13, 2024
de9da70
Remove salt
Aug 13, 2024
d760742
Iterate
Aug 14, 2024
348ad8d
Add GatewayAxelar specialization
Aug 14, 2024
edfd3ed
Iterate
ernestognw Aug 14, 2024
645794b
Fix GatewayAxelarSource
ernestognw Aug 20, 2024
270c5bd
Remove unnecessary contract
ernestognw Aug 26, 2024
4605a57
Iteration
ernestognw Aug 26, 2024
597bfc2
Remove interfaces
ernestognw Aug 26, 2024
2951fa5
Checkpoint
ernestognw Aug 26, 2024
a7bd130
Add incoming dual mode
ernestognw Aug 26, 2024
2f4588e
Fix compilation
ernestognw Aug 26, 2024
fd010bd
Apply review suggestions
ernestognw Aug 26, 2024
aa731cc
Install axelar contracts
ernestognw Sep 3, 2024
e39ff3a
Apply review sugggestion
ernestognw Sep 3, 2024
b54dda1
Resolve conflcits
ernestognw Sep 3, 2024
0e7d040
Apply suggestions
ernestognw Sep 3, 2024
2402926
wip fixes
frangio Sep 3, 2024
27dcd99
trying to get crosschain to compile
Amxx Sep 5, 2024
01d98fd
fix compilation
Amxx Sep 5, 2024
85ee12a
minor update
Amxx Sep 5, 2024
cdc3c94
make attributes a bytes[]
Amxx Sep 5, 2024
d7ce229
Address comments and add some tests
ernestognw Sep 10, 2024
6dc1c44
refactor and test caip utils
Amxx Sep 10, 2024
4cdf242
up
Amxx Sep 11, 2024
40b33a2
using unmerged version of Strings with parsing
Amxx Sep 16, 2024
a8ee58b
up
Amxx Sep 18, 2024
91cb0ac
workflow testing (active and passive)
Amxx Sep 18, 2024
80b0a59
update
Amxx Sep 19, 2024
b2ac19d
up
Amxx Sep 20, 2024
4f0f1f0
address PR comments
Amxx Sep 30, 2024
5a5b764
renovate
Amxx Sep 30, 2024
46db15d
rename
Amxx Sep 30, 2024
70855f7
fix foundry
Amxx Sep 30, 2024
3f3d6b6
codespell
Amxx Sep 30, 2024
336a56b
use checksumed addresses
Amxx Oct 1, 2024
86caeeb
use @openzepplin/contracts@master
Amxx Oct 22, 2024
426c3fc
add/move files that are no longer planned to be in the main repo
Amxx Oct 22, 2024
12236dd
get submodules when running tests
Amxx Oct 22, 2024
aad8745
Apply suggestions from code review
Amxx Oct 22, 2024
2aa5c15
Apply suggestions from code review
Amxx Oct 22, 2024
98dede1
simplify
Amxx Oct 22, 2024
9bd6a3b
update
Amxx Oct 22, 2024
c926b36
validateReceivedMessage -> setExecutedMessage
ernestognw Oct 29, 2024
a5f1368
Add docs
ernestognw Oct 29, 2024
6089900
Update AxelarGatewayDestination.sol
ernestognw Oct 30, 2024
a38ec99
Update
ernestognw Oct 30, 2024
27fbb93
Update
ernestognw Oct 30, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions contracts/crosschain/GatewayDestination.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {CAIP10} from "../utils/CAIP-10.sol";
import {CAIP2} from "../utils/CAIP-2.sol";
import {IGatewayDestination, IGatewayCommon} from "./IGatewayDestination.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {Set} from "../utils/Set.sol";

/// @dev A gateway destination that receives messages from a source chain.
///
/// This contract allows for 2 main operations:
/// - Message delivery (i.e. making it available for execution)
/// - Message execution (i.e. marking it as executed)
///
/// Message delivery is permissioned through {_authorizeMessageDelivered} and it's usually set
/// to an authority that can validate the message and make it available for execution.
///
/// Message execution is permissioned through {_authorizeMessageExecuted} and it checks if the
/// destination account is the one marking the message as executed.
abstract contract GatewayDestination is IGatewayDestination {
/// @inheritdoc IGatewayCommon
function messageId(
string memory source,
string memory destination,
Message memory message
) public pure virtual override returns (bytes32);

/// @inheritdoc IGatewayDestination
function destinationStatus(bytes32 id) public view virtual override returns (MessageDestinationStatus);

/// @inheritdoc IGatewayDestination
function deliverMessage(
string memory source,
string memory destination,
Message memory message
) external returns (bytes32) {
bytes32 id = messageId(source, destination, message);
MessageDestinationStatus status = destinationStatus(id);
_validateCurrentChain(destination);
_authorizeMessageDelivered(destination, message);
return _deliverMessage(id, status, message);
}

/// @inheritdoc IGatewayDestination
function setMessageExecuted(
string memory source,
string memory destination,
Message memory message
) external returns (bytes32) {
bytes32 id = messageId(source, destination, message);
MessageDestinationStatus status = destinationStatus(id);
_authorizeMessageExecuted(destination, message);
return _setMessageExecuted(id, status, message);
}

/// @dev Internal version of {deliverMessage} without access control.
function _deliverMessage(
bytes32 id,
MessageDestinationStatus status,
Message memory message
) internal virtual returns (bytes32) {
// Check if the message was not delivered or executed before. NOOP otherwise.
if (status == MessageDestinationStatus.Unknown) {
emit MessageDelivered(id, message);
}
return id;
}

/// @dev Internal version of {setMessageExecuted} without access control.
function _setMessageExecuted(
bytes32 id,
MessageDestinationStatus status,
Message memory message
) internal virtual returns (bytes32) {
// Check if the message was not executed already. NOOP otherwise.
if (status != MessageDestinationStatus.Executed) {
emit MessageExecuted(id, message);
}
return id;
}

/// @dev Authorizes the delivery of a message to the destination chain.
function _authorizeMessageDelivered(string memory destination, Message memory message) internal virtual;

/// @dev Validates a message submitted as executed.
///
/// Requirements:
/// - The destination must be the `msg.sender`
function _authorizeMessageExecuted(string memory destination, Message memory message) internal virtual {
CAIP10.Account memory destinationAccount = CAIP10.fromString(destination);

if (CAIP10.getAddress(destinationAccount) != msg.sender) {
revert UnauthorizedDestinationMessage(destination, msg.sender, message);
}
}

/// @dev Validates that the destination chain is the current chain.
function _validateCurrentChain(string memory destination) private view {
CAIP10.Account memory destinationAccount = CAIP10.fromString(destination);
if (!CAIP2.isCurrentEVMChain(destinationAccount._chainId)) {
revert MismatchedDestinationChain(destination);
}
}
}
145 changes: 145 additions & 0 deletions contracts/crosschain/GatewaySource.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {CAIP10} from "../utils/CAIP-10.sol";
import {CAIP2} from "../utils/CAIP-2.sol";
import {IGatewaySource, IGatewayCommon} from "./IGatewaySource.sol";
import {Set} from "../utils/Set.sol";

/// @dev Gateway contract on the source chain according to ERC-XXXX definitions.
///
/// This is a generic implementation of an asynchronous message-passing system between accounts on different chains.
abstract contract GatewaySource is IGatewaySource {
/// @inheritdoc IGatewayCommon
function messageId(
string memory source,
string memory destination,
Message memory message
) public pure virtual returns (bytes32);

/// @inheritdoc IGatewaySource
function sendingStatus(bytes32 id) public view virtual returns (MessageSourceStatus);

/// @inheritdoc IGatewaySource
function sendMessage(
string memory source,
string memory destination,
Message memory message
) external payable virtual returns (bytes32) {
bytes32 id = messageId(source, destination, message);
_validateCurrentChain(source);
_authorizeMessageCreated(source, message);
return _sendMessage(id, sendingStatus(id), message);
}

/// @inheritdoc IGatewaySource
function createMessage(
string memory source,
string memory destination,
Message memory message
) external payable virtual returns (bytes32) {
bytes32 id = messageId(source, destination, message);
_validateCurrentChain(source);
_authorizeMessageCreated(source, message);
return _createMessage(id, sendingStatus(id), message);
}

/// @inheritdoc IGatewaySource
function forwardMessage(
string memory source,
string memory destination,
Message memory message
) external payable virtual returns (bytes32) {
bytes32 id = messageId(source, destination, message);
_authorizeMessageForwarded(destination, message);
return _forwardMessage(id, sendingStatus(id), message);
}

/// @dev Internal version of {createMessage} without access control.
function _createMessage(
bytes32 id,
MessageSourceStatus status,
Message memory message
) internal virtual returns (bytes32) {
// Check if the message was not created or sent before. NOOP otherwise.
if (status == MessageSourceStatus.Unknown) {
emit MessageCreated(id, message);
}
return id;
}

/// @dev Internal version of {sendMessage} without access control.
function _sendMessage(
bytes32 id,
MessageSourceStatus status,
Message memory message
) internal virtual returns (bytes32) {
/// Check if the message hwas not sent before. NOOP otherwise.
if (status != MessageSourceStatus.Sent) {
emit MessageSent(id, message);
}
return id;
}

/// @dev Internal version of {forwardMessage} without access control.
function _forwardMessage(
bytes32 id,
MessageSourceStatus status,
Message memory message
) internal virtual returns (bytes32) {
// Check if the message was created first. NOOP otherwise.
if (status == MessageSourceStatus.Created) {
_sendMessage(id, status, message);
}
return id;
}

/// @dev Authorizes the creation of a message on the source chain.
///
/// Requirements:
/// - The source chain must match `msg.sender`
function _authorizeMessageCreated(string memory source, Message memory message) internal virtual {
CAIP10.Account memory sourceAccount = CAIP10.fromString(source);

if (CAIP10.getAddress(sourceAccount) != msg.sender) {
revert UnauthorizedSourceMessage(source, msg.sender, message);
}
}

/// @dev Authorizes the forwarding of a message to the destination chain.
function _authorizeMessageForwarded(string memory destination, Message memory message) internal virtual;

/// @dev Validates that the source chain is the current chain.
function _validateCurrentChain(string memory source) private view {
CAIP10.Account memory sourceAccount = CAIP10.fromString(source);
if (!CAIP2.isCurrentEVMChain(sourceAccount._chainId)) {
revert MismatchedSourceChain(source);
}
}
}

abstract contract GatewaySourceGeneric is GatewaySource {
using Set for Set.Bytes32Set;
using CAIP10 for CAIP10.Account;

Set.Bytes32Set private _createdBox;
Set.Bytes32Set private _sentBox;

/// @inheritdoc IGatewayCommon
function messageId(
string memory source,
string memory destination,
Message memory message
) public pure override returns (bytes32) {
return keccak256(abi.encode(source, destination, message));
}

/// @inheritdoc IGatewaySource
function sendingStatus(bytes32 id) public view virtual override returns (MessageSourceStatus) {
if (_sentBox.contains(id)) return MessageSourceStatus.Sent;
if (_createdBox.contains(id)) return MessageSourceStatus.Created;
return MessageSourceStatus.Unknown;
}
}
21 changes: 21 additions & 0 deletions contracts/crosschain/ICAIP2Equivalence.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IAxelarGateway} from "./vendor/axelar/interfaces/IAxelarGateway.sol";
import {IAxelarGasService} from "./vendor/axelar/interfaces/IAxelarGasService.sol";
import {CAIP2} from "../utils/CAIP-2.sol";

/// @dev Equivalence interface between CAIP-2 chain identifiers and Gateway-specific chain identifiers.
///
/// See https://chainagnostic.org/CAIPs/caip-2[CAIP2].
interface ICAIP2Equivalence {
frangio marked this conversation as resolved.
Show resolved Hide resolved
/// @dev Sets a CAIP-2 chain identifier as equivalent to a Gateway-specific chain identifier.
function setCAIP2Equivalence(CAIP2.ChainId memory chain, bytes memory custom) external;

/// @dev Checks if a CAIP-2 chain identifier is registered as equivalent to a Gateway-specific chain identifier.
function exists(CAIP2.ChainId memory chain) external view returns (bool);

/// @dev Retrieves the Gateway-specific chain identifier equivalent to a CAIP-2 chain identifier.
function fromCAIP2(CAIP2.ChainId memory chain) external pure returns (bytes memory);
}
26 changes: 26 additions & 0 deletions contracts/crosschain/IGatewayCommon.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @dev Common for a generic cross-chain gateway.
///
/// Gateways are split in two parts: source and destination. Respectively, they are responsible for
/// sending and receiving messages between different blockchains.
interface IGatewayCommon {
/// @dev Represents a cross-chain message.
struct Message {
// Arbitrary data to be sent with the message.
bytes payload;
// Extra parameters to be used by the gateway specialization.
bytes extraParams;
}

/// @dev Uniquely identifies a message.
/// @param source CAIP-10 account ID of the source chain.
/// @param destination CAIP-10 account ID of the destination chain.
function messageId(
string memory source,
string memory destination,
Message memory message
) external pure returns (bytes32);
}
44 changes: 44 additions & 0 deletions contracts/crosschain/IGatewayDestination.sol
ernestognw marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IGatewayCommon} from "./IGatewayCommon.sol";

/// @dev Interface for a cross-chain gateway that receives messages.
///
/// Allows to check the status of a message and to mark it as delivered or executed.
interface IGatewayDestination is IGatewayCommon {
enum MessageDestinationStatus {
Unknown,
Delivered,
Executed
}

event MessageDelivered(bytes32 indexed id, Message message);
event MessageExecuted(bytes32 indexed id, Message message);

error UnauthorizedDestinationMessage(string destination, address sender, Message message);
error MismatchedDestinationChain(string destination);

/// @dev Returns the status of a received cross-chain message.
function destinationStatus(bytes32 id) external view returns (MessageDestinationStatus);

/// @dev Sets a cross-chain message as delivered, ready for execution.
/// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Delivered
/// NOTE: Should only be called by an authorized gateway operator and validate that destination is the current chain.
function deliverMessage(
string memory source,
string memory destination,
Message memory message
) external returns (bytes32);

/// @dev Sets a cross-chain message as executed.
/// MessageDestinationStatus.Unknown -> MessageDestinationStatus.Executed.
/// MessageDestinationStatus.Delivered -> MessageDestinationStatus.Executed.
/// NOTE: Should only be called by the destination account.
function setMessageExecuted(
string memory source,
string memory destination,
Message memory message
) external returns (bytes32);
}
52 changes: 52 additions & 0 deletions contracts/crosschain/IGatewaySource.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {IGatewayCommon} from "./IGatewayCommon.sol";

/// @dev Interface for a cross-chain gateway that sends messages.
///
/// Allows for 2 sending modes: {sendMessage} and {createMessage} + {forwardMessage},
/// where the latter allows to hook logic before sending the message.
interface IGatewaySource is IGatewayCommon {
enum MessageSourceStatus {
Unknown,
Created,
Sent
}

event MessageCreated(bytes32 indexed id, Message message);
event MessageSent(bytes32 indexed id, Message message);

error UnauthorizedSourceMessage(string source, address sender, Message message);
error MismatchedSourceChain(string source);

/// @dev Returns the status of a sent cross-chain message.
function sendingStatus(bytes32 id) external view returns (MessageSourceStatus);

/// @dev Send a cross-chain message to the target chain.
/// MessageSourceStatus.Unknown -> MessageSourceStatus.Sent
/// NOTE: Must validate that source is the current chain.
function sendMessage(
string memory source,
string memory destination,
Message memory message
) external payable returns (bytes32);
ernestognw marked this conversation as resolved.
Show resolved Hide resolved

/// @dev Create a cross-chain message to the target chain. See {forwardMessage} to send it.
/// MessageSourceStatus.Unknown -> MessageSourceStatus.Created
/// NOTE: Must validate that source is the current chain.
function createMessage(
string memory source,
string memory destination,
Message memory message
) external payable returns (bytes32);

/// @dev Forwards a previously created cross-chain message to the target chain. See {createMessage} to create it.
/// MessageSourceStatus.Created -> MessageSourceStatus.Sent
function forwardMessage(
string memory source,
string memory destination,
Message memory message
) external payable returns (bytes32);
}
Loading
Loading