Skip to content

Merge AxelarGatewaySource and AxelarGatewayDestination in AxelarGatewayAdaptor #202

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
151 changes: 151 additions & 0 deletions contracts/crosschain/axelar/AxelarGatewayAdaptor.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol";
import {IAxelarGasService} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol";
import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {InteroperableAddress} from "@openzeppelin/contracts/utils/draft-InteroperableAddress.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {IERC7786GatewaySource} from "../../interfaces/IERC7786.sol";
import {IERC7786Attributes} from "../../interfaces/IERC7786Attributes.sol";
import {IERC7786Receiver} from "../../interfaces/IERC7786.sol";
import {ERC7786Attributes} from "../utils/ERC7786Attributes.sol";
import {AxelarGatewayBase} from "./AxelarGatewayBase.sol";

/**
* @dev Implementation of an ERC-7786 gateway destination adapter for the Axelar Network in dual mode.
*
* The contract implements AxelarExecutable's {_execute} function to execute the message, converting Axelar's native
* workflow into the standard ERC-7786.
*/
contract AxelarGatewayAdaptor is IERC7786GatewaySource, AxelarGatewayBase, AxelarExecutable {
using InteroperableAddress for bytes;
using Strings for *;

struct MessageDetails {
string destination;
string target;
bytes payload;
}

uint256 private _sendId;
mapping(bytes32 => MessageDetails) private _details;

error UnsupportedNativeTransfer();
error InvalidOriginGateway(string axelarSourceChain, string axelarSourceAddress);
error ReceiverExecutionFailed();

/// @dev Initializes the contract with the Axelar gateway and the initial owner.
constructor(
IAxelarGateway gateway,
IAxelarGasService gasService,
address initialOwner
) Ownable(initialOwner) AxelarGatewayBase(gateway, gasService) AxelarExecutable(address(gateway)) {}

/// @inheritdoc IERC7786GatewaySource
function supportsAttribute(bytes4 selector) public pure returns (bool) {
return selector == IERC7786Attributes.requestRelay.selector;
}

/// @inheritdoc IERC7786GatewaySource
function sendMessage(
bytes calldata recipient, // Binary Interoperable Address
bytes calldata payload,
bytes[] calldata attributes
) external payable returns (bytes32 sendId) {
// Process attributes (relay)
bool withRelay = false;
uint256 value = 0;
address refundRecipient = address(0);

for (uint256 i = 0; i < attributes.length; ++i) {
(withRelay, value, , refundRecipient) = ERC7786Attributes.tryDecodeRequestRelayCalldata(attributes[i]);
require(withRelay, UnsupportedAttribute(attributes[i].length < 0x04 ? bytes4(0) : bytes4(attributes[i])));
}
if (!withRelay) {
sendId = bytes32(++_sendId);
}
require(msg.value == value, UnsupportedNativeTransfer());

// Create the package
bytes memory sender = InteroperableAddress.formatEvmV1(block.chainid, msg.sender);
bytes memory adapterPayload = abi.encode(sender, recipient, payload);

// Emit event early (stack too deep)
emit MessageSent(sendId, sender, recipient, payload, 0, attributes);

// Send the message
(bytes2 chainType, bytes calldata chainReference, ) = recipient.parseV1Calldata();
bytes memory remoteGateway = getRemoteGateway(chainType, chainReference);
string memory axelarDestination = getAxelarChain(InteroperableAddress.formatV1(chainType, chainReference, ""));
string memory axelarTarget = address(bytes20(remoteGateway)).toChecksumHexString(); // TODO non-evm chains?

_axelarGateway.callContract(axelarDestination, axelarTarget, adapterPayload);

if (withRelay) {
_axelarGasService.payNativeGasForContractCall{value: msg.value}(
address(this),
axelarDestination,
axelarTarget,
adapterPayload,
refundRecipient
);
} else {
_details[sendId] = MessageDetails(axelarDestination, axelarTarget, adapterPayload);
}
}

// TODO inheritdoc from interface when that is standardized
function requestRelay(bytes32 sendId, uint256 /*gasLimit*/, address refundRecipient) external payable {
MessageDetails storage details = _details[sendId];
require(details.payload.length > 0);

_axelarGasService.payNativeGasForContractCall{value: msg.value}(
address(this),
details.destination,
details.target,
details.payload,
refundRecipient
);
}

/**
* @dev Execution of a cross-chain message.
*
* In this function:
*
* - `axelarSourceChain` is in the Axelar format. It should not be expected to be a proper ERC-7930 format
* - `axelarSourceAddress` is the sender of the Axelar message. That should be the remote gateway on the chain
* which the message originates from. It is NOT the sender of the ERC-7786 crosschain message.
*
* Proper ERC-7930 encoding of the crosschain message sender can be found in the message
*/
function _execute(
bytes32 commandId,
string calldata axelarSourceChain, // chain of the remote gateway - axelar format
string calldata axelarSourceAddress, // address of the remote gateway
bytes calldata adapterPayload
) internal override {
// Parse the package
(bytes memory sender, bytes memory recipient, bytes memory payload) = abi.decode(
adapterPayload,
(bytes, bytes, bytes)
);

// Axelar to ERC-7930 translation
bytes memory addr = getRemoteGateway(getErc7930Chain(axelarSourceChain));

// check message validity
// - `axelarSourceAddress` is the remote gateway on the origin chain.
require(
address(bytes20(addr)).toChecksumHexString().equal(axelarSourceAddress), // TODO non-evm chains?
InvalidOriginGateway(axelarSourceChain, axelarSourceAddress)
);

(, address target) = recipient.parseEvmV1();
bytes4 result = IERC7786Receiver(target).receiveMessage(commandId, sender, payload);
require(result == IERC7786Receiver.receiveMessage.selector, ReceiverExecutionFailed());
}
}
15 changes: 14 additions & 1 deletion contracts/crosschain/axelar/AxelarGatewayBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
pragma solidity ^0.8.27;

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

Expand All @@ -18,6 +19,7 @@ abstract contract AxelarGatewayBase is Ownable {

/// @dev Axelar's official gateway for the current chain.
IAxelarGateway internal immutable _axelarGateway;
IAxelarGasService internal immutable _axelarGasService;

// Remote gateway.
// `addr` is the isolated address part of ERC-7930. Its not a full ERC-7930 interoperable address.
Expand All @@ -41,8 +43,19 @@ abstract contract AxelarGatewayBase is Ownable {
error RemoteGatewayAlreadyRegistered(bytes2 chainType, bytes chainReference);

/// @dev Sets the local gateway address (i.e. Axelar's official gateway for the current chain).
constructor(IAxelarGateway _gateway) {
constructor(IAxelarGateway _gateway, IAxelarGasService _gasService) {
_axelarGateway = _gateway;
_axelarGasService = _gasService;
}

// This is already exposed by AxelarExecutable which AxelarDestinationGateway inherit from. Because its not
// virtual, resolution is not possible. Therefore, we should not expose it.
// function gateway() public view virtual returns (IAxelarGateway) {
// return _axelarGateway;
// }

function gasService() public view virtual returns (IAxelarGasService) {
return _axelarGasService;
}

/// @dev Returns the equivalent chain given an id that can be either either a binary interoperable address or an Axelar network identifier.
Expand Down
61 changes: 0 additions & 61 deletions contracts/crosschain/axelar/AxelarGatewayDestination.sol

This file was deleted.

21 changes: 0 additions & 21 deletions contracts/crosschain/axelar/AxelarGatewayDuplex.sol

This file was deleted.

58 changes: 0 additions & 58 deletions contracts/crosschain/axelar/AxelarGatewaySource.sol

This file was deleted.

33 changes: 33 additions & 0 deletions contracts/crosschain/utils/ERC7786Attributes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {IERC7786Attributes} from "../../interfaces/IERC7786Attributes.sol";

library ERC7786Attributes {
function tryDecodeRequestRelay(
bytes memory attribute
) internal pure returns (bool success, uint256 value, uint256 gasLimit, address refundRecipient) {
success = bytes4(attribute) == IERC7786Attributes.requestRelay.selector && attribute.length >= 0x64;
if (success) {
assembly ("memory-safe") {
value := mload(add(attribute, 0x24))
gasLimit := mload(add(attribute, 0x44))
refundRecipient := mload(add(attribute, 0x64))
}
}
}

function tryDecodeRequestRelayCalldata(
bytes calldata attribute
) internal pure returns (bool success, uint256 value, uint256 gasLimit, address refundRecipient) {
success = bytes4(attribute) == IERC7786Attributes.requestRelay.selector && attribute.length >= 0x64;
if (success) {
assembly ("memory-safe") {
value := calldataload(add(attribute.offset, 0x04))
gasLimit := calldataload(add(attribute.offset, 0x24))
refundRecipient := calldataload(add(attribute.offset, 0x44))
}
}
}
}
10 changes: 10 additions & 0 deletions contracts/interfaces/IERC7786Attributes.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.4;

/**
* @dev Standard attributes for ERC-7786. These attributes may be standardized in different ERC.
*/
interface IERC7786Attributes {
function requestRelay(uint256 value, uint256 gasLimit, address refundRecipient) external;
}
Loading
Loading