Skip to content

Add more standard gateway adapters #13

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

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 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
a6bfb2d
forge install: wormhole-solidity-sdk
frangio Sep 11, 2024
c07508f
add wormhole adapter
frangio Sep 11, 2024
a6af4a1
rename WormholeGateway.sol
frangio Sep 11, 2024
ed62ee2
add todos and beginning of wormhole dest gateway
frangio Sep 11, 2024
e1f803e
refactor adapter base
frangio Sep 12, 2024
1c2a043
clean up code
frangio Sep 12, 2024
d0986b9
inline
frangio Sep 12, 2024
0c65ff9
Revert "refactor adapter base"
frangio Sep 12, 2024
8cb52ee
implement wormhole destination gateway
frangio Sep 12, 2024
f9da9d3
lint
frangio Sep 12, 2024
00aae9e
add arbitrum gateway
frangio Sep 12, 2024
3e6ea7e
add arbitrum l2 source
frangio Sep 13, 2024
191a230
lint
frangio Sep 13, 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std.git
[submodule "lib/wormhole-solidity-sdk"]
path = lib/wormhole-solidity-sdk
url = https://github.com/wormhole-foundation/wormhole-solidity-sdk
13 changes: 13 additions & 0 deletions contracts/crosschain/ICAIP2Equivalence.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

/// @dev Equivalence interface between CAIP-2 chain identifiers and protocol-specific chain identifiers.
///
/// See https://chainagnostic.org/CAIPs/caip-2[CAIP2].
interface ICAIP2Equivalence {
error UnsupportedChain(string caip2);

/// @dev Retrieves the protocol-specific chain identifier equivalent to a CAIP-2 chain identifier.
function fromCAIP2(string memory caip2) external view returns (string memory);
}
7 changes: 7 additions & 0 deletions contracts/crosschain/IGatewayDestination.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IGatewayDestination {
event MessageExecuted(bytes32 indexed messageId);
}
15 changes: 15 additions & 0 deletions contracts/crosschain/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 GatewayDestinationPassiveInvalidMessage(bytes32 messageDestinationId);

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

pragma solidity ^0.8.0;

interface IGatewayReceiver {
function receiveMessage(
bytes32 messageId,
string calldata srcChain,
string calldata srcAccount,
bytes calldata payload,
bytes[] calldata attributes
) external payable;
}
22 changes: 22 additions & 0 deletions contracts/crosschain/IGatewaySource.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

interface IGatewaySource {
struct Message {
string source; // CAIP-10 account ID
string destination; // CAIP-10 account ID
bytes payload;
bytes[] attributes;
}

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

function sendMessage(
string calldata destChain, // CAIP-2 chain ID
string calldata destAccount, // i.e. address
bytes calldata payload,
bytes[] calldata attributes
) external payable returns (bytes32 messageId);
}
127 changes: 127 additions & 0 deletions contracts/crosschain/arbitrum/ArbitrumGateway.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {IInbox} from "@arbitrum/nitro-contracts/src/bridge/IInbox.sol";
import {ArbSys} from "@arbitrum/nitro-contracts/src/precompiles/ArbSys.sol";
import {IGatewaySource} from "../IGatewaySource.sol";
import {IGatewayDestination} from "../IGatewayDestination.sol";
import {IGatewayReceiver} from "../IGatewayReceiver.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";

string constant CHAINID_ETH = "eip155:1";
string constant CHAINID_ARB = "eip155:42161";

function addressFromHexString(string memory hexString) pure returns (address) {
return address(0); // TODO
}

interface IArbitrumGatewayL2Destination {
function deliverMessage(address sender, address receiver, bytes calldata payload) external;
}

contract ArbitrumGatewayL1Source is IGatewaySource {
IInbox private _inbox; // TODO
address private _remoteGateway;

struct PendingMessage {
address sender;
address receiver;
uint256 value;
bytes payload;
}

uint256 private _nextOutboxId;
mapping(bytes32 outboxId => PendingMessage) private _pending;

function sendMessage(
string calldata destChain,
string calldata destAccount,
bytes calldata payload,
bytes[] calldata attributes
) external payable override returns (bytes32 outboxId) {
require(Strings.equal(destChain, CHAINID_ARB));
require(attributes.length == 0);

address receiver = addressFromHexString(destAccount);
require(receiver != address(0));

outboxId = bytes32(_nextOutboxId++);
_pending[outboxId] = PendingMessage(msg.sender, receiver, msg.value, payload);
}

function finalizeMessage(
bytes32 outboxId,
uint256 maxSubmissionCost,
address excessFeeRefundAddress,
address callValueRefundAddress,
Copy link
Contributor Author

@frangio frangio Sep 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this value should not be a post-processing parameter, but should be set to the sender. This address will get all the call value back, and it may also be able to cancel the ticket.

uint256 gasLimit,
uint256 maxFeePerGas
) external payable {
PendingMessage storage pmsg = _pending[outboxId];

require(pmsg.receiver != address(0));

bytes memory adapterPayload = abi.encodeCall(
IArbitrumGatewayL2Destination.deliverMessage,
(pmsg.sender, pmsg.receiver, pmsg.payload)
);

_inbox.createRetryableTicket{value: msg.value + pmsg.value}(
_remoteGateway,
pmsg.value,
maxSubmissionCost,
excessFeeRefundAddress,
callValueRefundAddress,
gasLimit,
maxFeePerGas,
adapterPayload
);

delete pmsg.receiver;
delete pmsg.value;
delete pmsg.payload;
}
}

contract ArbitrumGatewayL1Destination is IGatewayDestination, IArbitrumGatewayL2Destination {
using Strings for address;

ArbSys private _arbSys; // TODO
address private _remoteGateway;

function deliverMessage(address sender, address receiver, bytes calldata payload) external {
require(_arbSys.wasMyCallersAddressAliased());
require(_arbSys.myCallersAddressWithoutAliasing() == _remoteGateway);

IGatewayReceiver(receiver).receiveMessage(0, CHAINID_ETH, sender.toHexString(), payload, new bytes[](0));
}
}

contract ArbitrumGatewayL2Source is IGatewaySource {
using Strings for address;

ArbSys private _arbSys; // TODO
address private _remoteGateway;

function sendMessage(
string calldata destChain,
string calldata destAccount,
bytes calldata payload,
bytes[] calldata attributes
) external payable override returns (bytes32) {
require(Strings.equal(destChain, CHAINID_ETH));
require(attributes.length == 0);

address receiver = addressFromHexString(destAccount);

bytes memory receiveMessage = abi.encodeCall(
IGatewayReceiver.receiveMessage,
(0, CHAINID_ARB, msg.sender.toHexString(), payload, new bytes[](0))
);

_arbSys.sendTxToL1(receiver, receiveMessage);

return 0;
}
}
112 changes: 112 additions & 0 deletions contracts/crosschain/axelar/AxelarGatewayBase.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
// 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";
import {ICAIP2Equivalence} from "../ICAIP2Equivalence.sol";

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

IAxelarGateway public immutable localGateway;

mapping(string caip2 => string remoteGateway) private _remoteGateways;
mapping(string caip2 => string destinationChain) private _equivalence;

constructor(IAxelarGateway _gateway) {
localGateway = _gateway;
}

function fromCAIP2(string memory caip2) public view returns (string memory) {
return _equivalence[caip2];
}

function getRemoteGateway(string memory caip2) public view returns (string memory remoteGateway) {
return _remoteGateways[caip2];
}

function registerCAIP2Equivalence(string calldata caip2, string calldata axelarSupported) public onlyOwner {
require(bytes(_equivalence[caip2]).length == 0);
_equivalence[caip2] = axelarSupported;
emit RegisteredCAIP2Equivalence(caip2, axelarSupported);
}

function registerRemoteGateway(string calldata caip2, string calldata remoteGateway) public onlyOwner {
require(bytes(_remoteGateways[caip2]).length == 0);
_remoteGateways[caip2] = remoteGateway;
emit RegisteredRemoteGateway(caip2, remoteGateway);
}
}

// EVM (https://axelarscan.io/resources/chains?type=evm)
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("1"))] = "Ethereum";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("56"))] = "binance";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("137"))] = "Polygon";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("43114"))] = "Avalanche";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("250"))] = "Fantom";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("1284"))] = "Moonbeam";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("1313161554"))] = "aurora";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("42161"))] = "arbitrum";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("10"))] = "optimism";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("8453"))] = "base";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("5000"))] = "mantle";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("42220"))] = "celo";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("2222"))] = "kava";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("314"))] = "filecoin";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("59144"))] = "linea";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("2031"))] = "centrifuge";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("534352"))] = "scroll";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("13371"))] = "immutable";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("252"))] = "fraxtal";
// _equivalence[CAIP2.toString(bytes8("eip155"), bytes32("81457"))] = "blast";

// Cosmos (https://axelarscan.io/resources/chains?type=cosmos)
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('axelar-dojo-1'))] = 'Axelarnet';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('osmosis-1'))] = 'osmosis';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('cosmoshub-4'))] = 'cosmoshub';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('juno-1'))] = 'juno';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('emoney-3'))] = 'e-money';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('injective-1'))] = 'injective';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('crescent-1'))] = 'crescent';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('kaiyo-1'))] = 'kujira';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('secret-4'))] = 'secret-snip';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('secret-4'))] = 'secret';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('pacific-1'))] = 'sei';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('stargaze-1'))] = 'stargaze';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('mantle-1'))] = 'assetmantle';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('fetchhub-4'))] = 'fetch';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('kichain-2'))] = 'ki';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('evmos_9001-2'))] = 'evmos';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('xstaxy-1'))] = 'aura';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('comdex-1'))] = 'comdex';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('core-1'))] = 'persistence';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('regen-1'))] = 'regen';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('umee-1'))] = 'umee';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('agoric-3'))] = 'agoric';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('dimension_37-1'))] = 'xpla';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('acre_9052-1'))] = 'acre';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('stride-1'))] = 'stride';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('carbon-1'))] = 'carbon';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('sommelier-3'))] = 'sommelier';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('neutron-1'))] = 'neutron';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('reb_1111-1'))] = 'rebus';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('archway-1'))] = 'archway';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('pio-mainnet-1'))] = 'provenance';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('ixo-5'))] = 'ixo';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('migaloo-1'))] = 'migaloo';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('teritori-1'))] = 'teritori';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('haqq_11235-1'))] = 'haqq';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('celestia'))] = 'celestia';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('agamotto'))] = 'ojo';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('chihuahua-1'))] = 'chihuahua';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('ssc-1'))] = 'saga';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('dymension_1100-1'))] = 'dymension';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('fxcore'))] = 'fxcore';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('perun-1'))] = 'c4e';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('bitsong-2b'))] = 'bitsong';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('pirin-1'))] = 'nolus';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('lava-mainnet-1'))] = 'lava';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('phoenix-1'))] = 'terra-2';
// _equivalence[CAIP2.toString(bytes8('cosmos'), bytes32('columbus-5'))] = 'terra';"
68 changes: 68 additions & 0 deletions contracts/crosschain/axelar/AxelarGatewayDestination.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

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

abstract contract AxelarGatewayDestination is
IGatewayDestination,
// IGatewayDestinationPassive, // TODO
AxelarGatewayBase,
AxelarExecutable
{
using Strings for string;

// In this function:
// - `srcChain` is in the Axelar format. It should not be expected to be a proper CAIP-2 format
// - `srcAccount` 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 srcChain,
string calldata srcAccount,
bytes calldata package
) internal virtual override {
// Parse the message package
// - message identifier (from the source, not unique ?)
// - source account (caller of this gateway)
// - destination account
// - payload
// - attributes
(
bytes32 messageId,
string memory caip10Src,
string memory caip10Dst,
bytes memory payload,
bytes[] memory attributes
) = abi.decode(package, (bytes32, string, string, bytes, bytes[]));

(string memory originChain, string memory originAccount) = CAIP10.parse(caip10Src);
(string memory targetChain, string memory targetAccount) = CAIP10.parse(caip10Dst);

// check message validity
// - `srcChain` matches origin chain in the message (in caip2)
// - `srcAccount` is the remote gateway on the origin chain.
require(fromCAIP2(originChain).equal(srcChain), "Invalid origin chain");
require(getRemoteGateway(originChain).equal(srcAccount), "Invalid origin gateway");
// This check is not required for security. That is enforced by axelar (+ source gateway)
require(CAIP2.format().equal(targetChain), "Invalid tardet chain");

// TODO: not available yet
// address destination = address(uint160(Strings.toUint(targetAccount)));
targetAccount;
address destination = address(0);

IGatewayReceiver(destination).receiveMessage(messageId, originChain, originAccount, payload, attributes);

emit MessageExecuted(messageId);
}
}
Loading
Loading