Skip to content

Commit b91151f

Browse files
authored
Flatten AxelarGatewayAdapter contract (#202)
1 parent 8226253 commit b91151f

10 files changed

+202
-277
lines changed
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.27;
4+
5+
import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol";
6+
import {AxelarExecutable} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/executable/AxelarExecutable.sol";
7+
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
8+
import {InteroperableAddress} from "@openzeppelin/contracts/utils/draft-InteroperableAddress.sol";
9+
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
10+
import {IERC7786GatewaySource} from "../../interfaces/IERC7786.sol";
11+
import {IERC7786Receiver} from "../../interfaces/IERC7786.sol";
12+
13+
/**
14+
* @dev Implementation of an ERC-7786 gateway destination adapter for the Axelar Network in dual mode.
15+
*
16+
* The contract implements AxelarExecutable's {_execute} function to execute the message, converting Axelar's native
17+
* workflow into the standard ERC-7786.
18+
*
19+
* NOTE: While both ERC-7786 and Axelar do support non-evm chains, this adaptor does not. This limitation comes from
20+
* the translation of the ERC-7930 interoperable address (binary objects -- bytes) to strings. This is necessary
21+
* because Axelar uses string to represent addresses. For EVM network, this adapter uses a checksum hex string
22+
* representation. Other networks would require a different encoding. Ideally we would have a single encoding for all
23+
* networks (could be base58, base64, ...) but Axelar doesn't support that.
24+
*/
25+
// slither-disable-next-line locked-ether
26+
contract AxelarGatewayAdapter is IERC7786GatewaySource, Ownable, AxelarExecutable {
27+
using InteroperableAddress for bytes;
28+
using Strings for *;
29+
30+
// Remote gateway.
31+
// `addr` is the isolated address part of ERC-7930. Its not a full ERC-7930 interoperable address.
32+
mapping(bytes2 chainType => mapping(bytes chainReference => bytes addr)) private _remoteGateways;
33+
34+
// chain equivalence ERC-7930 (no address) <> Axelar
35+
mapping(bytes erc7930 => string axelar) private _erc7930ToAxelar;
36+
mapping(string axelar => bytes erc7930) private _axelarToErc7930;
37+
38+
/// @dev A remote gateway has been registered for a chain.
39+
event RegisteredRemoteGateway(bytes remote);
40+
41+
/// @dev A chain equivalence has been registered.
42+
event RegisteredChainEquivalence(bytes erc7930binary, string axelar);
43+
44+
error UnsupportedNativeTransfer();
45+
error InvalidOriginGateway(string axelarSourceChain, string axelarSourceAddress);
46+
error ReceiverExecutionFailed();
47+
error UnsupportedChainType(bytes2 chainType);
48+
error UnsupportedERC7930Chain(bytes erc7930binary);
49+
error UnsupportedAxelarChain(string axelar);
50+
error InvalidChainIdentifier(bytes erc7930binary);
51+
error ChainEquivalenceAlreadyRegistered(bytes erc7930binary, string axelar);
52+
error RemoteGatewayAlreadyRegistered(bytes2 chainType, bytes chainReference);
53+
54+
/// @dev Initializes the contract with the Axelar gateway and the initial owner.
55+
constructor(
56+
IAxelarGateway gateway,
57+
address initialOwner
58+
) Ownable(initialOwner) AxelarExecutable(address(gateway)) {}
59+
60+
/// @dev Returns the Axelar chain identifier for a given binary interoperable chain id.
61+
function getAxelarChain(bytes memory input) public view virtual returns (string memory output) {
62+
output = _erc7930ToAxelar[input];
63+
require(bytes(output).length > 0, UnsupportedERC7930Chain(input));
64+
}
65+
66+
/// @dev Returns the binary interoperable chain id for a given Axelar chain identifier.
67+
function getErc7930Chain(string memory input) public view virtual returns (bytes memory output) {
68+
output = _axelarToErc7930[input];
69+
require(output.length > 0, UnsupportedAxelarChain(input));
70+
}
71+
72+
/// @dev Returns the address of the remote gateway for a given binary interoperable chain id.
73+
function getRemoteGateway(bytes memory chain) public view virtual returns (bytes memory) {
74+
(bytes2 chainType, bytes memory chainReference, ) = chain.parseV1();
75+
return getRemoteGateway(chainType, chainReference);
76+
}
77+
78+
/// @dev Returns the address of the remote gateway for a given chainType and chainReference.
79+
function getRemoteGateway(
80+
bytes2 chainType,
81+
bytes memory chainReference
82+
) public view virtual returns (bytes memory) {
83+
bytes memory addr = _remoteGateways[chainType][chainReference];
84+
if (addr.length == 0)
85+
revert UnsupportedERC7930Chain(InteroperableAddress.formatV1(chainType, chainReference, ""));
86+
return addr;
87+
}
88+
89+
/// @dev Registers a chain equivalence between a binary interoperable chain id and an Axelar chain identifier.
90+
function registerChainEquivalence(bytes calldata chain, string calldata axelar) public virtual onlyOwner {
91+
(, , bytes calldata addr) = chain.parseV1Calldata();
92+
require(addr.length == 0, InvalidChainIdentifier(chain));
93+
require(
94+
bytes(_erc7930ToAxelar[chain]).length == 0 && _axelarToErc7930[axelar].length == 0,
95+
ChainEquivalenceAlreadyRegistered(chain, axelar)
96+
);
97+
98+
_erc7930ToAxelar[chain] = axelar;
99+
_axelarToErc7930[axelar] = chain;
100+
emit RegisteredChainEquivalence(chain, axelar);
101+
}
102+
103+
/// @dev Registers the address of a remote gateway.
104+
function registerRemoteGateway(bytes calldata remote) public virtual onlyOwner {
105+
(bytes2 chainType, bytes calldata chainReference, bytes calldata addr) = remote.parseV1Calldata();
106+
require(
107+
_remoteGateways[chainType][chainReference].length == 0,
108+
RemoteGatewayAlreadyRegistered(chainType, chainReference)
109+
);
110+
_remoteGateways[chainType][chainReference] = addr;
111+
emit RegisteredRemoteGateway(remote);
112+
}
113+
114+
/// @inheritdoc IERC7786GatewaySource
115+
function supportsAttribute(bytes4 /*selector*/) public pure returns (bool) {
116+
return false;
117+
}
118+
119+
/// @inheritdoc IERC7786GatewaySource
120+
function sendMessage(
121+
bytes calldata recipient, // Binary Interoperable Address
122+
bytes calldata payload,
123+
bytes[] calldata attributes
124+
) external payable returns (bytes32) {
125+
require(msg.value == 0, UnsupportedNativeTransfer());
126+
// Use of `if () revert` syntax to avoid accessing attributes[0] if it's empty
127+
if (attributes.length > 0)
128+
revert UnsupportedAttribute(attributes[0].length < 0x04 ? bytes4(0) : bytes4(attributes[0][0:4]));
129+
130+
// Create the package
131+
bytes memory sender = InteroperableAddress.formatEvmV1(block.chainid, msg.sender);
132+
bytes memory adapterPayload = abi.encode(sender, recipient, payload);
133+
134+
// Emit event early (stack too deep)
135+
bytes32 sendId = bytes32(0); // Explicitly set to 0
136+
emit MessageSent(sendId, sender, recipient, payload, 0, attributes);
137+
138+
// Send the message
139+
(bytes2 chainType, bytes calldata chainReference, ) = recipient.parseV1Calldata();
140+
bytes memory remoteGateway = getRemoteGateway(chainType, chainReference);
141+
string memory axelarDestination = getAxelarChain(InteroperableAddress.formatV1(chainType, chainReference, ""));
142+
string memory axelarTarget = _stringifyAddress(chainType, remoteGateway);
143+
144+
gateway().callContract(axelarDestination, axelarTarget, adapterPayload);
145+
146+
return sendId;
147+
}
148+
149+
/**
150+
* @dev Execution of a cross-chain message.
151+
*
152+
* In this function:
153+
*
154+
* - `axelarSourceChain` is in the Axelar format. It should not be expected to be a proper ERC-7930 format
155+
* - `axelarSourceAddress` is the sender of the Axelar message. That should be the remote gateway on the chain
156+
* which the message originates from. It is NOT the sender of the ERC-7786 crosschain message.
157+
*
158+
* Proper ERC-7930 encoding of the crosschain message sender can be found in the message
159+
*/
160+
function _execute(
161+
bytes32 commandId,
162+
string calldata axelarSourceChain, // chain of the remote gateway - axelar format
163+
string calldata axelarSourceAddress, // address of the remote gateway
164+
bytes calldata adapterPayload
165+
) internal override {
166+
// Parse the package
167+
(bytes memory sender, bytes memory recipient, bytes memory payload) = abi.decode(
168+
adapterPayload,
169+
(bytes, bytes, bytes)
170+
);
171+
172+
// variable lifecycle: avoid stack-too-deep
173+
{
174+
// Axelar to ERC-7930 translation
175+
(bytes2 chainType, bytes memory chainReference, ) = getErc7930Chain(axelarSourceChain).parseV1();
176+
bytes memory addr = getRemoteGateway(chainType, chainReference);
177+
178+
// check message validity
179+
// - `axelarSourceAddress` is the remote gateway on the origin chain.
180+
require(
181+
_stringifyAddress(chainType, addr).equal(axelarSourceAddress),
182+
InvalidOriginGateway(axelarSourceChain, axelarSourceAddress)
183+
);
184+
}
185+
186+
(, address target) = recipient.parseEvmV1();
187+
bytes4 result = IERC7786Receiver(target).receiveMessage(commandId, sender, payload);
188+
require(result == IERC7786Receiver.receiveMessage.selector, ReceiverExecutionFailed());
189+
}
190+
191+
/// @dev ERC-7930 to Axelar address translation. Currently only supports EVM chains.
192+
function _stringifyAddress(bytes2 chainType, bytes memory addr) internal virtual returns (string memory) {
193+
if (chainType == 0) {
194+
return address(bytes20(addr)).toChecksumHexString();
195+
} else {
196+
revert UnsupportedChainType(chainType);
197+
}
198+
}
199+
}

contracts/crosschain/axelar/AxelarGatewayBase.sol

Lines changed: 0 additions & 99 deletions
This file was deleted.

contracts/crosschain/axelar/AxelarGatewayDestination.sol

Lines changed: 0 additions & 61 deletions
This file was deleted.

contracts/crosschain/axelar/AxelarGatewayDuplex.sol

Lines changed: 0 additions & 21 deletions
This file was deleted.

0 commit comments

Comments
 (0)