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

feat: added support for gateway tokens #246

Merged
merged 18 commits into from
Jan 22, 2024
12 changes: 12 additions & 0 deletions contracts/InterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,18 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
tokenId = _deployInterchainToken(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, '', gasValue);
}

/**
* @notice Register 'canonical' gateway tokens. The same salt needs to be used for the same gateway token on every chain.
* @param salt The salt to be used for the token registration. Should be the same for all tokens and something that will not have collisions with any of the other salts used by the factory.
* @param symbol The symbol of the token to register.
*/
function registerGatewayToken(bytes32 salt, string calldata symbol) external onlyOwner returns (bytes32 tokenId) {
address tokenAddress = gateway.tokenAddresses(symbol);
if (tokenAddress == address(0)) revert NotGatewayToken(symbol);
bytes memory params = abi.encode('', tokenAddress);
tokenId = interchainTokenService.deployTokenManager(salt, '', TokenManagerType.GATEWAY, params, 0);
}

/**
* @notice Checks if a given token is a gateway token.
* @param token The address of the token to check.
Expand Down
186 changes: 140 additions & 46 deletions contracts/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contract
import { ExpressExecutorTracker } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/express/ExpressExecutorTracker.sol';
import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol';
import { Create3Address } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3Address.sol';
import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';
import { SafeTokenTransferFrom, SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol';
import { AddressBytes } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressBytes.sol';
import { StringToBytes32, Bytes32ToString } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/Bytes32String.sol';
import { Multicall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Multicall.sol';
Expand All @@ -23,6 +23,7 @@ import { IInterchainTokenDeployer } from './interfaces/IInterchainTokenDeployer.
import { IInterchainTokenExecutable } from './interfaces/IInterchainTokenExecutable.sol';
import { IInterchainTokenExpressExecutable } from './interfaces/IInterchainTokenExpressExecutable.sol';
import { ITokenManager } from './interfaces/ITokenManager.sol';
import { IERC20Named } from './interfaces/IERC20Named.sol';

import { Operator } from './utils/Operator.sol';

Expand All @@ -48,6 +49,7 @@ contract InterchainTokenService is
using AddressBytes for bytes;
using AddressBytes for address;
using SafeTokenTransferFrom for IERC20;
using SafeTokenCall for IERC20;

IAxelarGateway public immutable gateway;
IAxelarGasService public immutable gasService;
Expand Down Expand Up @@ -368,7 +370,7 @@ contract InterchainTokenService is
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) external payable whenNotPaused {
) public payable whenNotPaused {
uint256 messageType = abi.decode(payload, (uint256));
if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) {
revert InvalidExpressMessageType(messageType);
Expand Down Expand Up @@ -460,11 +462,22 @@ contract InterchainTokenService is
bytes calldata metadata,
uint256 gasValue
) external payable whenNotPaused {
amount = _takeToken(tokenId, msg.sender, amount, false);
string memory symbol;
(amount, symbol) = _takeToken(tokenId, msg.sender, amount, false);

(MetadataVersion metadataVersion, bytes memory data) = _decodeMetadata(metadata);

_transmitInterchainTransfer(tokenId, msg.sender, destinationChain, destinationAddress, amount, metadataVersion, data, gasValue);
_transmitInterchainTransfer(
tokenId,
msg.sender,
destinationChain,
destinationAddress,
amount,
metadataVersion,
data,
symbol,
gasValue
);
}

/**
Expand All @@ -484,8 +497,8 @@ contract InterchainTokenService is
uint256 gasValue
) external payable whenNotPaused {
if (data.length == 0) revert EmptyData();

amount = _takeToken(tokenId, msg.sender, amount, false);
string memory symbol;
(amount, symbol) = _takeToken(tokenId, msg.sender, amount, false);

_transmitInterchainTransfer(
tokenId,
Expand All @@ -495,6 +508,7 @@ contract InterchainTokenService is
amount,
MetadataVersion.CONTRACT_CALL,
data,
symbol,
gasValue
);
}
Expand All @@ -521,11 +535,22 @@ contract InterchainTokenService is
uint256 amount,
bytes calldata metadata
) external payable whenNotPaused {
amount = _takeToken(tokenId, sourceAddress, amount, true);
string memory symbol;
(amount, symbol) = _takeToken(tokenId, sourceAddress, amount, true);

(MetadataVersion metadataVersion, bytes memory data) = _decodeMetadata(metadata);

_transmitInterchainTransfer(tokenId, sourceAddress, destinationChain, destinationAddress, amount, metadataVersion, data, msg.value);
_transmitInterchainTransfer(
tokenId,
sourceAddress,
destinationChain,
destinationAddress,
amount,
metadataVersion,
data,
symbol,
msg.value
);
}

/*************\
Expand Down Expand Up @@ -612,27 +637,12 @@ contract InterchainTokenService is
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload
) external onlyRemoteService(sourceChain, sourceAddress) whenNotPaused {
) public onlyRemoteService(sourceChain, sourceAddress) whenNotPaused {
bytes32 payloadHash = keccak256(payload);

if (!gateway.validateContractCall(commandId, sourceChain, sourceAddress, payloadHash)) revert NotApprovedByGateway();

uint256 messageType = abi.decode(payload, (uint256));
if (messageType == MESSAGE_TYPE_INTERCHAIN_TRANSFER) {
address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash);

_processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload);

if (expressExecutor != address(0)) {
emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor);
}
} else if (messageType == MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER) {
_processDeployTokenManagerPayload(payload);
} else if (messageType == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) {
_processDeployInterchainTokenPayload(payload);
} else {
revert InvalidMessageType(messageType);
}
_execute(commandId, sourceChain, sourceAddress, payload, payloadHash);
}

function contractCallWithTokenValue(
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
Expand All @@ -646,25 +656,31 @@ contract InterchainTokenService is
}

function expressExecuteWithToken(
bytes32 /*commandId*/,
string calldata /*sourceChain*/,
string calldata /*sourceAddress*/,
bytes calldata /*payload*/,
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload,
string calldata /*tokenSymbol*/,
uint256 /*amount*/
) external payable {
revert ExecuteWithTokenNotSupported();
// It should be ok to ignore the symbol and amount since this info exists on the payload.
expressExecute(commandId, sourceChain, sourceAddress, payload);
milapsheth marked this conversation as resolved.
Show resolved Hide resolved
}

function executeWithToken(
bytes32 /*commandId*/,
string calldata /*sourceChain*/,
string calldata /*sourceAddress*/,
bytes calldata /*payload*/,
string calldata /*tokenSymbol*/,
uint256 /*amount*/
) external pure {
revert ExecuteWithTokenNotSupported();
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload,
string calldata tokenSymbol,
uint256 amount
) external {
bytes32 payloadHash = keccak256(payload);

if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount))
revert NotApprovedByGateway();

_execute(commandId, sourceChain, sourceAddress, payload, payloadHash);
}

/**
Expand Down Expand Up @@ -800,6 +816,77 @@ contract InterchainTokenService is
gateway.callContract(destinationChain, destinationAddress, payload);
}

/**
* @notice Calls a contract on a specific destination chain with the given payload
* @param destinationChain The target chain where the contract will be called.
* @param payload The data payload for the transaction.
* @param gasValue The amount of gas to be paid for the transaction.
Foivos marked this conversation as resolved.
Show resolved Hide resolved
*/
function _callContractWithToken(
string calldata destinationChain,
bytes memory payload,
string memory symbol,
uint256 amount,
MetadataVersion metadataVersion,
uint256 gasValue
) internal {
string memory destinationAddress = trustedAddress(destinationChain);
if (bytes(destinationAddress).length == 0) revert UntrustedChain();

if (gasValue > 0) {
if (metadataVersion == MetadataVersion.CONTRACT_CALL) {
gasService.payNativeGasForContractCallWithToken{ value: gasValue }(
address(this),
destinationChain,
destinationAddress,
payload,
symbol,
amount, // solhint-disable-next-line avoid-tx-origin
tx.origin
);
} else if (metadataVersion == MetadataVersion.EXPRESS_CALL) {
gasService.payNativeGasForExpressCallWithToken{ value: gasValue }(
address(this),
destinationChain,
destinationAddress,
payload,
symbol,
amount, // solhint-disable-next-line avoid-tx-origin
tx.origin
);
} else {
revert InvalidMetadataVersion(uint32(metadataVersion));
}
}

gateway.callContractWithToken(destinationChain, destinationAddress, payload, symbol, amount);
}

function _execute(
bytes32 commandId,
string calldata sourceChain,
string calldata sourceAddress,
bytes calldata payload,
bytes32 payloadHash
) internal {
uint256 messageType = abi.decode(payload, (uint256));
if (messageType == MESSAGE_TYPE_INTERCHAIN_TRANSFER) {
address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash);

if (expressExecutor != address(0)) {
emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor);
}

_processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload);
} else if (messageType == MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER) {
_processDeployTokenManagerPayload(payload);
} else if (messageType == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) {
_processDeployInterchainTokenPayload(payload);
} else {
revert InvalidMessageType(messageType);
}
}

/**
* @notice Deploys a token manager on a destination chain.
* @param tokenId The ID of the token.
Expand Down Expand Up @@ -872,9 +959,10 @@ contract InterchainTokenService is
tokenManager_ := mload(add(returnData, 0x20))
}

if (tokenManagerType == TokenManagerType.LOCK_UNLOCK || tokenManagerType == TokenManagerType.LOCK_UNLOCK_FEE) {
ITokenManager(tokenManager_).approveService();
}
(success, returnData) = tokenHandler.delegatecall(
abi.encodeWithSelector(ITokenHandler.postTokenManagerDeploy.selector, tokenManagerType, tokenManager_)
);
if (!success) revert PostDeployFailed(returnData);

// slither-disable-next-line reentrancy-events
emit TokenManagerDeployed(tokenId, tokenManager_, tokenManagerType, params);
Expand Down Expand Up @@ -962,6 +1050,7 @@ contract InterchainTokenService is
uint256 amount,
MetadataVersion metadataVersion,
bytes memory data,
string memory symbol,
uint256 gasValue
) internal {
// slither-disable-next-line reentrancy-events
Expand All @@ -982,14 +1071,17 @@ contract InterchainTokenService is
amount,
data
);

_callContract(destinationChain, payload, metadataVersion, gasValue);
if (bytes(symbol).length > 0) {
_callContractWithToken(destinationChain, payload, symbol, amount, metadataVersion, gasValue);
} else {
_callContract(destinationChain, payload, metadataVersion, gasValue);
}
}

/**
* @dev Takes token from a sender via the token service. `tokenOnly` indicates if the caller should be restricted to the token only.
*/
function _takeToken(bytes32 tokenId, address from, uint256 amount, bool tokenOnly) internal returns (uint256) {
function _takeToken(bytes32 tokenId, address from, uint256 amount, bool tokenOnly) internal returns (uint256, string memory symbol) {
address tokenManager_ = tokenManagerAddress(tokenId);
uint256 tokenManagerType;
address tokenAddress;
Expand All @@ -1006,8 +1098,10 @@ contract InterchainTokenService is

/// @dev Track the flow amount being sent out as a message
ITokenManager(tokenManager_).addFlowOut(amount);
milapsheth marked this conversation as resolved.
Show resolved Hide resolved

return amount;
if (tokenManagerType == uint256(TokenManagerType.GATEWAY)) {
symbol = IERC20Named(tokenAddress).symbol();
}
return (amount, symbol);
}

/**
Expand Down
Loading
Loading