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 40 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
46 changes: 46 additions & 0 deletions contracts/crosschain/axelar/AxelarGatewayBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol";

abstract contract AxelarGatewayBase is Ownable {
event RegisteredRemoteGateway(string caip2, string gatewayAddress);
event RegisteredChainEquivalence(string caip2, string destinationChain);

error UnsupportedChain(string caip2);

IAxelarGateway public immutable localGateway;

// TODO: merge these two (and the corresponding setters?)
Amxx marked this conversation as resolved.
Show resolved Hide resolved
Amxx marked this conversation as resolved.
Show resolved Hide resolved
mapping(string caip2 => string remoteGateway) private _remoteGateways;
mapping(string caip2OrAxelar => string axelarOfCaip2) private _chainEquivalence;

constructor(IAxelarGateway _gateway) {
localGateway = _gateway;
}

function getEquivalentChain(string memory input) public view virtual returns (string memory output) {
output = _chainEquivalence[input];
require(bytes(output).length > 0, UnsupportedChain(input));
}

function getRemoteGateway(string memory caip2) public view virtual returns (string memory remoteGateway) {
remoteGateway = _remoteGateways[caip2];
require(bytes(remoteGateway).length > 0, UnsupportedChain(caip2));
}

function registerChainEquivalence(string calldata caip2, string calldata axelarSupported) public virtual onlyOwner {
require(bytes(_chainEquivalence[caip2]).length == 0);
_chainEquivalence[caip2] = axelarSupported;
_chainEquivalence[axelarSupported] = caip2;
emit RegisteredChainEquivalence(caip2, axelarSupported);
}

function registerRemoteGateway(string calldata caip2, string calldata remoteGateway) public virtual onlyOwner {
require(bytes(_remoteGateways[caip2]).length == 0);
_remoteGateways[caip2] = remoteGateway;
emit RegisteredRemoteGateway(caip2, remoteGateway);
}
}
82 changes: 82 additions & 0 deletions contracts/crosschain/axelar/AxelarGatewayDestination.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
import {StringsUnreleased} from "../../utils/Strings.sol";
import {CAIP2} from "../../utils/CAIP-2.sol";
import {CAIP10} from "../../utils/CAIP-10.sol";
import {AxelarGatewayBase} from "./AxelarGatewayBase.sol";
import {IGatewayDestinationPassive} from "../interfaces/IGatewayDestinationPassive.sol";
import {IGatewayReceiver} from "../interfaces/IGatewayReceiver.sol";

abstract contract AxelarGatewayDestination is IGatewayDestinationPassive, AxelarGatewayBase, AxelarExecutable {
using StringsUnreleased for address;
using StringsUnreleased for string;

/// @dev Passive mode
function validateReceivedMessage(
bytes calldata messageKey,
string calldata source, // CAIP-2
string calldata sender, // CAIP-10
bytes calldata payload,
bytes[] calldata attributes
) external virtual {
// Extract Axelar commandId
bytes32 commandId = abi.decode(messageKey, (bytes32));

// Rebuild expected package
bytes memory adapterPayload = abi.encode(
sender,
msg.sender.toChecksumHexString(), // receiver
payload,
attributes
);

// Check package was received from remote gateway on src chain
require(
gateway.validateContractCall(
commandId,
getEquivalentChain(source),
getRemoteGateway(source),
keccak256(adapterPayload)
),
NotApprovedByGateway()
);
}

/// @dev Active mode
// In this function:
// - `remoteChain` is in the Axelar format. It should not be expected to be a proper CAIP-2 format
// - `remoteAccount` is the sender of the crosschain message. That should be the remote gateway on the chain which
// the message originates from. It is NOT the sender of the crosschain message
//
// Proper CAIP-10 encoding of the message sender (including the CAIP-2 name of the origin chain can be found in
// the message)
function _execute(
string calldata remoteChain, // chain of the remote gateway - axelar format
string calldata remoteAccount, // address of the remote gateway
bytes calldata adapterPayload
) internal virtual override {
// Parse the package
(string memory sender, string memory receiver, bytes memory payload, bytes[] memory attributes) = abi.decode(
adapterPayload,
(string, string, bytes, bytes[])
);
string memory source = getEquivalentChain(remoteChain);

// check message validity
// - `remoteAccount` is the remote gateway on the origin chain.
require(getRemoteGateway(source).equal(remoteAccount), "Invalid origin gateway");

// Active mode
IGatewayReceiver(receiver.parseAddress()).receiveMessage(
address(0), // not needed in active mode
new bytes(0), // not needed in active mode
source,
sender,
payload,
attributes
);
}
}
51 changes: 51 additions & 0 deletions contracts/crosschain/axelar/AxelarGatewaySource.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {AxelarGatewayBase} from "./AxelarGatewayBase.sol";
import {IGatewaySource} from "../interfaces/IGatewaySource.sol";
import {StringsUnreleased} from "../../utils/Strings.sol";
import {CAIP2} from "../../utils/CAIP-2.sol";
import {CAIP10} from "../../utils/CAIP-10.sol";

abstract contract AxelarGatewaySource is IGatewaySource, AxelarGatewayBase {
using StringsUnreleased for address;

function supportsAttribute(bytes4 /*selector*/) public view virtual returns (bool) {
return false;
}

function sendMessage(
string calldata destination, // CAIP-2 chain ID
string calldata receiver, // i.e. address
bytes calldata payload,
bytes[] calldata attributes
) external payable virtual returns (bytes32) {
// TODO: add support for value and attributes ?
Amxx marked this conversation as resolved.
Show resolved Hide resolved
require(msg.value == 0, "Value not supported");
for (uint256 i = 0; i < attributes.length; ++i) {
bytes4 selector = bytes4(attributes[i][0:4]);
require(supportsAttribute(selector), UnsuportedAttribute(selector));
}
Amxx marked this conversation as resolved.
Show resolved Hide resolved

// Create the package
string memory sender = msg.sender.toChecksumHexString();
bytes memory adapterPayload = abi.encode(sender, receiver, payload, attributes);

// Emit event
emit MessageCreated(
0,
CAIP10.format(CAIP2.local(), sender),
CAIP10.format(destination, receiver),
payload,
attributes
);

// Send the message
string memory axelarDestination = getEquivalentChain(destination);
string memory remoteGateway = getRemoteGateway(destination);
localGateway.callContract(axelarDestination, remoteGateway, adapterPayload);

return 0;
}
}
15 changes: 15 additions & 0 deletions contracts/crosschain/interfaces/IGatewayDestinationPassive.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IGatewayDestinationPassive {
error InvalidMessageKey(bytes messageKey);

function validateReceivedMessage(
bytes calldata messageKey,
string calldata source,
string calldata sender,
bytes calldata payload,
bytes[] calldata attributes
) external;
}
14 changes: 14 additions & 0 deletions contracts/crosschain/interfaces/IGatewayReceiver.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IGatewayReceiver {
function receiveMessage(
address gateway,
bytes calldata gatewayMessageKey,
string calldata source,
string calldata sender,
bytes calldata payload,
bytes[] calldata attributes
) external payable;
}
26 changes: 26 additions & 0 deletions contracts/crosschain/interfaces/IGatewaySource.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IGatewaySource {
event MessageCreated(
bytes32 outboxId,
string sender, // CAIP-10 account ID
string receiver, // CAIP-10 account ID
bytes payload,
bytes[] attributes
);

event MessageSent(bytes32 indexed outboxId);

error UnsuportedAttribute(bytes4 signature);
Amxx marked this conversation as resolved.
Show resolved Hide resolved

function supportsAttribute(bytes4 signature) external view returns (bool);

function sendMessage(
string calldata destination, // CAIP-2 chain ID
string calldata receiver, // CAIP-10 account ID
bytes calldata payload,
bytes[] calldata attributes
) external payable returns (bytes32 outboxId);
}
82 changes: 82 additions & 0 deletions contracts/crosschain/mocks/AxelarGatewayMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol";
import {IAxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarExecutable.sol";
import {BitMaps} from "@openzeppelin/contracts/utils/structs/BitMaps.sol";
import {StringsUnreleased} from "../../utils/Strings.sol";

contract AxelarGatewayMock {
using StringsUnreleased for address;
using StringsUnreleased for string;
using BitMaps for BitMaps.BitMap;

bool private activeMode;
BitMaps.BitMap private pendingCommandIds;

event CommandIdPending(
bytes32 indexed commandId,
string destinationChain,
string destinationContractAddress,
bytes payload
);

function setActive(bool enabled) public {
activeMode = enabled;
}

function callContract(
string calldata destinationChain,
string calldata destinationContractAddress,
bytes calldata payload
) external {
// TODO: check that destination chain is local

emit IAxelarGateway.ContractCall(
msg.sender,
destinationChain,
destinationContractAddress,
keccak256(payload),
payload
);

bytes32 commandId = keccak256(
abi.encode(
destinationChain,
msg.sender.toChecksumHexString(),
destinationContractAddress,
keccak256(payload)
)
);

require(!pendingCommandIds.get(uint256(commandId)));
pendingCommandIds.set(uint256(commandId));

emit CommandIdPending(commandId, destinationChain, destinationContractAddress, payload);

if (activeMode) {
// NOTE:
// - source chain and destination chain are the same in this mock
address target = destinationContractAddress.parseAddress();
IAxelarExecutable(target).execute(commandId, destinationChain, msg.sender.toChecksumHexString(), payload);
}
}

function validateContractCall(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes32 payloadHash
) external returns (bool) {
if (pendingCommandIds.get(uint256(commandId))) {
pendingCommandIds.unset(uint256(commandId));

emit IAxelarGateway.ContractCallExecuted(commandId);

return
commandId ==
keccak256(abi.encode(sourceChain, sourceAddress, msg.sender.toChecksumHexString(), payloadHash));
} else return false;
}
}
46 changes: 46 additions & 0 deletions contracts/crosschain/mocks/GatewayReceiverMock.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {IGatewayDestinationPassive} from "../interfaces/IGatewayDestinationPassive.sol";
import {IGatewayReceiver} from "../interfaces/IGatewayReceiver.sol";

contract GatewayReceiverMock is IGatewayReceiver {
address public immutable GATEWAY;

event MessageReceived(bytes gatewayMessageKey, string source, string sender, bytes payload, bytes[] attributes);

constructor(address _gateway) {
GATEWAY = _gateway;
}

function isGateway(address instance) public view returns (bool) {
return instance == GATEWAY;
}

function receiveMessage(
address gateway,
bytes calldata gatewayMessageKey,
string calldata source,
string calldata sender,
bytes calldata payload,
bytes[] calldata attributes
) public payable {
if (isGateway(msg.sender)) {
// Active mode
// no extra check
} else if (isGateway(gateway)) {
// Passive mode
IGatewayDestinationPassive(gateway).validateReceivedMessage(
gatewayMessageKey,
source,
sender,
payload,
attributes
);
} else {
revert("invalid call");
}
emit MessageReceived(gatewayMessageKey, source, sender, payload, attributes);
}
}
Loading