Skip to content

Commit

Permalink
feat(its): add custom token support with auto scaling
Browse files Browse the repository at this point in the history
  • Loading branch information
milapsheth committed Dec 10, 2024
1 parent a7439cd commit 5dad96a
Show file tree
Hide file tree
Showing 5 changed files with 125 additions and 33 deletions.
29 changes: 28 additions & 1 deletion contracts/InterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { IERC20Named } from './interfaces/IERC20Named.sol';
* @title InterchainTokenFactory
* @notice This contract is responsible for deploying new interchain tokens and managing their token managers.
*/
contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, Multicall, Upgradable {
contract InterchainTokenFactory is IInterchainTokenFactory, Multicall, Upgradable {
using AddressBytes for address;

/// @dev This slot contains the storage for this contract in an upgrade-compatible manner
Expand All @@ -26,6 +26,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
bytes32 private constant CONTRACT_ID = keccak256('interchain-token-factory');
bytes32 internal constant PREFIX_CANONICAL_TOKEN_SALT = keccak256('canonical-token-salt');
bytes32 internal constant PREFIX_INTERCHAIN_TOKEN_SALT = keccak256('interchain-token-salt');
bytes32 internal constant PREFIX_CUSTOM_TOKEN_SALT = keccak256('custom-token-salt');
bytes32 internal constant PREFIX_DEPLOY_APPROVAL = keccak256('deploy-approval');
address private constant TOKEN_FACTORY_DEPLOYER = address(0);

Expand Down Expand Up @@ -446,6 +447,32 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M
tokenId = deployRemoteCanonicalInterchainToken(originalTokenAddress, destinationChain, gasValue);
}

// TODO: Should we reuse interchainTokenDeploySalt?
function linkedTokenDeploySalt(address deployer, bytes32 salt) public view returns (bytes32 deploySalt) {
deploySalt = keccak256(abi.encode(PREFIX_CUSTOM_TOKEN_SALT, chainNameHash, deployer, salt));
}

function linkedTokenId(address deployer, bytes32 salt) external view returns (bytes32 tokenId) {
bytes32 deploySalt = linkedTokenDeploySalt(deployer, salt);
tokenId = _interchainTokenId(deploySalt);
}

function registerCustomToken(bytes32 salt, address tokenAddress, TokenManagerType tokenManagerType, address operator) external payable returns (bytes32 tokenId) {
bytes32 deploySalt = linkedTokenDeploySalt(msg.sender, salt);
bytes memory operatorBytes = '';
string memory currentChain = '';
if (operator != address(0)) {
operatorBytes = operator.toBytes();
}

tokenId = interchainTokenService.linkToken(deploySalt, currentChain, tokenAddress.toBytes(), tokenManagerType, false, operatorBytes, 0);
}

function linkToken(bytes32 salt, string calldata destinationChain, bytes calldata destinationTokenAddress, TokenManagerType tokenManagerType, bool autoScaling, bytes calldata linkParams, uint256 gasValue) external payable returns (bytes32 tokenId) {
bytes32 deploySalt = linkedTokenDeploySalt(msg.sender, salt);
tokenId = interchainTokenService.linkToken(deploySalt, destinationChain, destinationTokenAddress, tokenManagerType, autoScaling, linkParams, gasValue);
}

/********************\
|* Pure Key Getters *|
\********************/
Expand Down
85 changes: 54 additions & 31 deletions contracts/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { IInterchainTokenExecutable } from './interfaces/IInterchainTokenExecuta
import { IInterchainTokenExpressExecutable } from './interfaces/IInterchainTokenExpressExecutable.sol';
import { ITokenManager } from './interfaces/ITokenManager.sol';
import { IGatewayCaller } from './interfaces/IGatewayCaller.sol';
import { IERC20Named } from './interfaces/IERC20Named.sol';
import { Create3AddressFixed } from './utils/Create3AddressFixed.sol';
import { Operator } from './utils/Operator.sol';

Expand Down Expand Up @@ -79,9 +80,12 @@ contract InterchainTokenService is

uint256 private constant MESSAGE_TYPE_INTERCHAIN_TRANSFER = 0;
uint256 private constant MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN = 1;
uint256 private constant MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER = 2;
// Deprecated
// uint256 private constant MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER = 2;
uint256 private constant MESSAGE_TYPE_SEND_TO_HUB = 3;
uint256 private constant MESSAGE_TYPE_RECEIVE_FROM_HUB = 4;
uint256 private constant MESSAGE_TYPE_LINK_TOKEN = 5;
uint256 private constant MESSAGE_TYPE_REGISTER_TOKEN_METADATA = 6;

/**
* @dev Tokens and token managers deployed via the Token Factory contract use a special deployer address.
Expand Down Expand Up @@ -279,6 +283,15 @@ contract InterchainTokenService is
USER FUNCTIONS
\************/

function registerTokenMetadata(address tokenAddress, uint256 gasValue) external payable {
if (tokenAddress == address(0)) revert ZeroAddress();

uint8 decimals = IERC20Named(tokenAddress).decimals();

bytes memory payload = abi.encode(MESSAGE_TYPE_REGISTER_TOKEN_METADATA, tokenAddress.toBytes(), decimals);

_callContract(ITS_HUB_CHAIN_NAME, payload, IGatewayCaller.MetadataVersion.CONTRACT_CALL, gasValue);
}
/**
* @notice Used to deploy remote custom TokenManagers.
* @dev At least the `gasValue` amount of native token must be passed to the function call. `gasValue` exists because this function can be
Expand All @@ -301,18 +314,26 @@ contract InterchainTokenService is
TokenManagerType tokenManagerType,
bytes calldata params,
uint256 gasValue
) external payable whenNotPaused returns (bytes32 tokenId) {
if (bytes(params).length == 0) revert EmptyParams();
) external payable returns (bytes32 tokenId) {
(bytes memory linkParams, address destinationTokenAddress) = abi.decode(params, (bytes, address));

tokenId = linkToken(salt, destinationChain, destinationTokenAddress.toBytes(), tokenManagerType, false, linkParams, gasValue);
}

function linkToken(bytes32 salt, string calldata destinationChain, bytes memory destinationTokenAddress, TokenManagerType tokenManagerType, bool autoScaling, bytes memory linkParams, uint256 gasValue) public payable whenNotPaused returns (bytes32 tokenId) {
if (destinationTokenAddress.length == 0) revert EmptyDestinationAddress();
if (linkParams.length == 0) revert EmptyParams();

// TODO: Should we only mint/burn or lock/unlock for remote linking for simplicity? Makes it easier for external chains
// Custom token managers can't be deployed with native interchain token type, which is reserved for interchain tokens
if (tokenManagerType == TokenManagerType.NATIVE_INTERCHAIN_TOKEN) revert CannotDeploy(tokenManagerType);

// TODO: Support linking existing custom tokens on remote chains
address deployer = msg.sender;

if (deployer == interchainTokenFactory) {
if (msg.sender == interchainTokenFactory) {
deployer = TOKEN_FACTORY_DEPLOYER;
} else if (bytes(destinationChain).length == 0 && trustedAddressHash(chainName()) == ITS_HUB_ROUTING_IDENTIFIER_HASH) {
// Restricted on ITS contracts deployed to Amplifier chains until ITS Hub adds support
} else if (bytes(destinationChain).length == 0) {
revert NotSupported();
}

Expand All @@ -321,11 +342,11 @@ contract InterchainTokenService is
emit InterchainTokenIdClaimed(tokenId, deployer, salt);

if (bytes(destinationChain).length == 0) {
_deployTokenManager(tokenId, tokenManagerType, params);
_deployTokenManager(tokenId, tokenManagerType, destinationTokenAddress.toAddress(), linkParams);
} else {
if (chainNameHash == keccak256(bytes(destinationChain))) revert CannotDeployRemotelyToSelf();

_deployRemoteTokenManager(tokenId, destinationChain, gasValue, tokenManagerType, params);
_linkToken(tokenId, destinationChain, destinationTokenAddress, tokenManagerType, autoScaling, linkParams, gasValue);
}
}

Expand Down Expand Up @@ -370,7 +391,7 @@ contract InterchainTokenService is
if (bytes(destinationChain).length == 0) {
address tokenAddress = _deployInterchainToken(tokenId, minter, name, symbol, decimals);

_deployTokenManager(tokenId, TokenManagerType.NATIVE_INTERCHAIN_TOKEN, abi.encode(minter, tokenAddress));
_deployTokenManager(tokenId, TokenManagerType.NATIVE_INTERCHAIN_TOKEN, tokenAddress, minter);
} else {
if (chainNameHash == keccak256(bytes(destinationChain))) revert CannotDeployRemotelyToSelf();

Expand Down Expand Up @@ -731,15 +752,15 @@ contract InterchainTokenService is
/**
* @notice Processes a deploy token manager payload.
*/
function _processDeployTokenManagerPayload(bytes memory payload) internal {
(, bytes32 tokenId, TokenManagerType tokenManagerType, bytes memory params) = abi.decode(
function _processLinkTokenPayload(bytes memory payload) internal {
(, bytes32 tokenId, TokenManagerType tokenManagerType, , bytes memory destinationTokenAddress, , bytes memory linkParams) = abi.decode(
payload,
(uint256, bytes32, TokenManagerType, bytes)
(uint256, bytes32, TokenManagerType, bytes, bytes, bool, bytes)
);

if (tokenManagerType == TokenManagerType.NATIVE_INTERCHAIN_TOKEN) revert CannotDeploy(tokenManagerType);

_deployTokenManager(tokenId, tokenManagerType, params);
_deployTokenManager(tokenId, tokenManagerType, destinationTokenAddress.toAddress(), linkParams);
}

/**
Expand All @@ -755,7 +776,7 @@ contract InterchainTokenService is

tokenAddress = _deployInterchainToken(tokenId, minterBytes, name, symbol, decimals);

_deployTokenManager(tokenId, TokenManagerType.NATIVE_INTERCHAIN_TOKEN, abi.encode(minterBytes, tokenAddress));
_deployTokenManager(tokenId, TokenManagerType.NATIVE_INTERCHAIN_TOKEN, tokenAddress, minterBytes);
}

/**
Expand Down Expand Up @@ -804,9 +825,6 @@ contract InterchainTokenService is

// Check whether the ITS call should be routed via ITS hub for this destination chain
if (keccak256(abi.encodePacked(destinationAddress)) == ITS_HUB_ROUTING_IDENTIFIER_HASH) {
// Prevent deploy token manager to be usable on ITS hub
if (_getMessageType(payload) == MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER) revert NotSupported();

// Wrap ITS message in an ITS Hub message
payload = abi.encode(MESSAGE_TYPE_SEND_TO_HUB, destinationChain, payload);
destinationChain = ITS_HUB_CHAIN_NAME;
Expand All @@ -833,10 +851,10 @@ contract InterchainTokenService is
if (messageType == MESSAGE_TYPE_INTERCHAIN_TRANSFER) {
address expressExecutor = _getExpressExecutorAndEmitEvent(commandId, sourceChain, sourceAddress, payloadHash);
_processInterchainTransferPayload(commandId, expressExecutor, originalSourceChain, payload);
} else if (messageType == MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER) {
_processDeployTokenManagerPayload(payload);
} else if (messageType == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) {
_processDeployInterchainTokenPayload(payload);
} else if (messageType == MESSAGE_TYPE_LINK_TOKEN) {
_processLinkTokenPayload(payload);
} else {
revert InvalidMessageType(messageType);
}
Expand Down Expand Up @@ -875,9 +893,6 @@ contract InterchainTokenService is

// Get message type of the inner ITS message
messageType = _getMessageType(payload);

// Prevent deploy token manager to be usable on ITS hub
if (messageType == MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER) revert NotSupported();
} else {
// Prevent receiving a direct message from the ITS Hub. This is not supported yet.
if (keccak256(abi.encodePacked(sourceChain)) == ITS_HUB_CHAIN_NAME_HASH) revert UntrustedChain();
Expand All @@ -890,23 +905,27 @@ contract InterchainTokenService is
* @notice Deploys a token manager on a destination chain.
* @param tokenId The ID of the token.
* @param destinationChain The chain where the token manager will be deployed.
* @param gasValue The amount of gas to be paid for the transaction.
* @param destinationTokenAddress The address of the token on the destination chain.
* @param tokenManagerType The type of token manager to be deployed.
* @param params Additional parameters for the token manager deployment.
* @param autoScaling Whether to enable auto scaling of decimals for the interchain token.
* @param params Additional parameters for the token linking.
* @param gasValue The amount of gas to be paid for the transaction.
*/
function _deployRemoteTokenManager(
function _linkToken(
bytes32 tokenId,
string calldata destinationChain,
uint256 gasValue,
bytes memory destinationTokenAddress,
TokenManagerType tokenManagerType,
bytes calldata params
bool autoScaling,
bytes memory params,
uint256 gasValue
) internal {
// slither-disable-next-line unused-return
deployedTokenManager(tokenId);
bytes memory sourceTokenAddress = registeredTokenAddress(tokenId).toBytes();

emit TokenManagerDeploymentStarted(tokenId, destinationChain, tokenManagerType, params);

bytes memory payload = abi.encode(MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, tokenManagerType, params);
bytes memory payload = abi.encode(MESSAGE_TYPE_LINK_TOKEN, tokenId, tokenManagerType, sourceTokenAddress, destinationTokenAddress, autoScaling, params);

_callContract(destinationChain, payload, IGatewayCaller.MetadataVersion.CONTRACT_CALL, gasValue);
}
Expand Down Expand Up @@ -948,9 +967,13 @@ contract InterchainTokenService is
* @notice Deploys a token manager.
* @param tokenId The ID of the token.
* @param tokenManagerType The type of the token manager to be deployed.
* @param params Additional parameters for the token manager deployment.
* @param tokenAddress The address of the token to be managed.
* @param operator The operator of the token manager.
*/
function _deployTokenManager(bytes32 tokenId, TokenManagerType tokenManagerType, bytes memory params) internal {
function _deployTokenManager(bytes32 tokenId, TokenManagerType tokenManagerType, address tokenAddress, bytes memory operator) internal {
// TokenManagerProxy params
bytes memory params = abi.encode(operator, tokenAddress);

(bool success, bytes memory returnData) = tokenManagerDeployer.delegatecall(
abi.encodeWithSelector(ITokenManagerDeployer.deployTokenManager.selector, tokenId, tokenManagerType, params)
);
Expand Down
23 changes: 22 additions & 1 deletion contracts/interfaces/IInterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@ import { IMulticall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/in
import { IUpgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IUpgradable.sol';

import { IInterchainTokenService } from './IInterchainTokenService.sol';
import { ITokenManagerType } from './ITokenManagerType.sol';

/**
* @title IInterchainTokenFactory Interface
* @notice This interface defines functions for deploying new interchain tokens and managing their token managers.
*/
interface IInterchainTokenFactory is IUpgradable, IMulticall {
interface IInterchainTokenFactory is ITokenManagerType, IUpgradable, IMulticall {
error ZeroAddress();
error InvalidChainName();
error InvalidMinter(address minter);
Expand Down Expand Up @@ -215,4 +216,24 @@ interface IInterchainTokenFactory is IUpgradable, IMulticall {
string calldata destinationChain,
uint256 gasValue
) external payable returns (bytes32 tokenId);

/**
* @notice Computes the deploy salt for a linked interchain token.
* @param deployer The address of the deployer.
* @param salt The unique salt for deploying the token.
* @return deploySalt The deploy salt for the interchain token.
*/
function linkedTokenDeploySalt(address deployer, bytes32 salt) external view returns (bytes32 deploySalt);

/**
* @notice Computes the ID for a canonical interchain token based on its address.
* @param deployer The address of the deployer.
* @param salt The unique salt for deploying the token.
* @return tokenId The ID of the canonical interchain token.
*/
function linkedTokenId(address deployer, bytes32 salt) external view returns (bytes32 tokenId);

function registerCustomToken(bytes32 salt, address tokenAddress, TokenManagerType tokenManagerType, address operator) external payable returns (bytes32 tokenId);

function linkToken(bytes32 salt, string calldata destinationChain, bytes calldata destinationTokenAddress, TokenManagerType tokenManagerType, bool autoScaling, bytes calldata linkParams, uint256 gasValue) external payable returns (bytes32 tokenId);
}
20 changes: 20 additions & 0 deletions contracts/interfaces/IInterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,13 @@ interface IInterchainTokenService is
*/
function interchainTokenId(address operator_, bytes32 salt) external view returns (bytes32 tokenId);

/**
* @notice Registers metadata for a token on the ITS Hub. This metadata is used for scaling linked tokens.
* @param tokenAddress The address of the token.
* @param gasValue The gas value for deployment.
*/
function registerTokenMetadata(address tokenAddress, uint256 gasValue) external payable;

/**
* @notice Deploys a custom token manager contract on a remote chain.
* @param salt The salt used for token manager deployment.
Expand All @@ -187,6 +194,19 @@ interface IInterchainTokenService is
uint256 gasValue
) external payable returns (bytes32 tokenId);

/**
* @notice Links a source token to a destination token on a remote chain.
* @param salt The salt used for token manager deployment.
* @param destinationChain The name of the destination chain.
* @param destinationTokenAddress The address of the token on the destination chain.
* @param tokenManagerType The type of token manager. Cannot be NATIVE_INTERCHAIN_TOKEN.
* @param autoScaling Whether to enable auto scaling of decimals for the interchain token.
* @param linkParams The link parameters.
* @param gasValue The gas value for deployment.
* @return tokenId The tokenId associated with the token manager.
*/
function linkToken(bytes32 salt, string calldata destinationChain, bytes memory destinationTokenAddress, TokenManagerType tokenManagerType, bool autoScaling, bytes memory linkParams, uint256 gasValue) external payable returns (bytes32 tokenId);

/**
* @notice Deploys and registers an interchain token on a remote chain.
* @param salt The salt used for token deployment.
Expand Down
1 change: 1 addition & 0 deletions contracts/proxies/TokenManagerProxy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,4 @@ contract TokenManagerProxy is BaseProxy, ITokenManagerProxy {
implementation_ = ITokenManagerImplementation(interchainTokenService_).tokenManagerImplementation(implementationType_);
}
}

0 comments on commit 5dad96a

Please sign in to comment.