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

Add ERC20 Token bridge #63

Draft
wants to merge 1 commit 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
103 changes: 103 additions & 0 deletions contracts/crosschain/ERC20Bridge.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// SPDX-License-Identifier: MIT

pragma solidity ^0.8.27;

import {ERC7786Receiver} from "./utils/ERC7786Receiver.sol";
import {IERC7786GatewaySource} from "../interfaces/IERC7786.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {CAIP2} from "@openzeppelin/contracts/utils/CAIP2.sol";
import {CAIP10} from "@openzeppelin/contracts/utils/CAIP10.sol";
import {IERC7802} from "../interfaces/IERC7802.sol";

abstract contract ERC20Bridge is ERC7786Receiver {
using Strings for *;
using CAIP2 for string;
using CAIP10 for uint256;

mapping(address token => mapping(string caip2 => string caip10)) private _tokenEquivalents;
mapping(string caip2 => string caip10) private _bridgeEquivalents;

function getTokenEquivalent(
address _tokenAddress,
string memory _chainId
) public view virtual returns (string memory) {
return _tokenEquivalents[_tokenAddress][_chainId];
}

function getBridgeEquivalent(string memory _chainId) public view virtual returns (string memory) {
return _bridgeEquivalents[_chainId];
}

function registerTokenEquivalent(
address _tokenAddress,
string memory _chainId,
string memory _remoteTokenAddress
) public virtual {
_authorizeRegister(msg.sender);
_tokenEquivalents[_tokenAddress][_chainId] = _remoteTokenAddress;
}

function registerBridgeEquivalent(string memory _chainId, string memory _remoteChainId) public virtual {
_authorizeRegister(msg.sender);
_bridgeEquivalents[_chainId] = _remoteChainId;
}

function crossChainTransfer(
IERC7786GatewaySource gateway,
IERC7802 token,
string memory destinationChain, // CAIP-2 chain identifier
string memory to, // CAIP-10 account address (does not include the chain identifier)
uint256 _amount
) public virtual {
require(_isKnownGateway(address(gateway)));

// Burn the tokens
token.crosschainBurn(msg.sender, _amount);

// Send the message
gateway.sendMessage(
getBridgeEquivalent(destinationChain),
to,
_encodePayload(getTokenEquivalent(address(token), destinationChain), to, _amount),
new bytes[](0)
);
}

/// @dev Virtual function that should contain the logic to execute when a cross-chain message is received.
function _processMessage(
address /* gateway */,
string calldata sourceChain,
string calldata sender,
bytes calldata payload,
bytes[] calldata /* attributes */
) internal virtual override {
// Gateway is already validated

require(getBridgeEquivalent(sourceChain).equal(sender));

(string memory tokenCaip10, string memory to, uint256 amount) = _decodePayload(payload);
address tokenAddr = tokenCaip10.parseAddress();

// Mint the tokens
IERC7802 token = IERC7802(tokenAddr);
token.crosschainMint(to.parseAddress(), amount);
}

function _encodePayload(
string memory _tokenAddress,
string memory _to,
uint256 _amount
) internal pure returns (bytes memory) {
return abi.encode(_tokenAddress, _to, _amount);
}

function _decodePayload(
bytes memory _payload
) internal pure returns (string memory tokenAddress_, string memory to_, uint256 amount_) {
bytes4 selector;
(selector, tokenAddress_, to_, amount_) = abi.decode(_payload, (bytes4, string, string, uint256));
}

/// @dev Modifier to check if the caller is allowed to register token and bridge equivalents.
function _authorizeRegister(address register) internal virtual;
}
12 changes: 8 additions & 4 deletions contracts/crosschain/axelar/AxelarGatewayBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

pragma solidity ^0.8.27;

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

/**
Expand All @@ -12,7 +11,7 @@ import {IAxelarGateway} from "@axelar-network/axelar-gmp-sdk-solidity/contracts/
* to Axelar chain identifiers) and remote gateways (i.e. gateways on other chains) to
* facilitate cross-chain communication.
*/
abstract contract AxelarGatewayBase is Ownable {
abstract contract AxelarGatewayBase {
/// @dev A remote gateway has been registered for a chain.
event RegisteredRemoteGateway(string caip2, string gatewayAddress);

Expand Down Expand Up @@ -49,17 +48,22 @@ abstract contract AxelarGatewayBase is Ownable {
}

/// @dev Registers a chain equivalence between a CAIP-2 chain identifier and an Axelar network identifier.
function registerChainEquivalence(string calldata caip2, string calldata axelarSupported) public virtual onlyOwner {
function registerChainEquivalence(string calldata caip2, string calldata axelarSupported) public virtual {
_authorizeRegister(msg.sender);
require(bytes(_chainEquivalence[caip2]).length == 0, ChainEquivalenceAlreadyRegistered(caip2));
_chainEquivalence[caip2] = axelarSupported;
_chainEquivalence[axelarSupported] = caip2;
emit RegisteredChainEquivalence(caip2, axelarSupported);
}

/// @dev Registers the address string of the remote gateway for a given CAIP-2 chain identifier.
function registerRemoteGateway(string calldata caip2, string calldata remoteGateway) public virtual onlyOwner {
function registerRemoteGateway(string calldata caip2, string calldata remoteGateway) public virtual {
_authorizeRegister(msg.sender);
require(bytes(_remoteGateways[caip2]).length == 0, RemoteGatewayAlreadyRegistered(caip2));
_remoteGateways[caip2] = remoteGateway;
emit RegisteredRemoteGateway(caip2, remoteGateway);
}

/// @dev Modifier to check if the caller is allowed to register remote gateways and chain equivalences.
function _authorizeRegister(address register) internal virtual;
}
13 changes: 10 additions & 3 deletions contracts/crosschain/axelar/AxelarGatewaySource.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,7 @@ abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBas
bytes[] calldata attributes
) external payable returns (bytes32 outboxId) {
require(msg.value == 0, UnsupportedNativeTransfer());
// Use of `if () revert` syntax to avoid accessing attributes[0] if it's empty
if (attributes.length > 0)
revert UnsupportedAttribute(attributes[0].length < 0x04 ? bytes4(0) : bytes4(attributes[0][0:4]));
_checkAttributes(attributes);

// Create the package
string memory sender = msg.sender.toChecksumHexString();
Expand All @@ -57,4 +55,13 @@ abstract contract AxelarGatewaySource is IERC7786GatewaySource, AxelarGatewayBas

return outboxId;
}

/// @dev Checks whether the attributes are supported. Reverts if at least one is not.
function _checkAttributes(bytes[] calldata attributes) private pure {
for (uint256 i = 0; i < attributes.length; i++) {
uint8 tooShort = attributes[i].length < 0x04;
bytes4 selector = tooShort ? bytes4(0) : bytes4(attributes[i][0:4]);
require(!tooShort && supportsAttribute(selector), UnsupportedAttribute(selector));
}
}
}
Loading