Skip to content

Commit

Permalink
feat(its): add a registerCustomToken function in ITS and add support …
Browse files Browse the repository at this point in the history
…for it in factory (#322)

Co-authored-by: Milap Sheth <[email protected]>
  • Loading branch information
Foivos and milapsheth authored Jan 15, 2025
1 parent 9ada6d4 commit 796c27a
Show file tree
Hide file tree
Showing 9 changed files with 442 additions and 500 deletions.
14 changes: 2 additions & 12 deletions contracts/InterchainTokenFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -403,23 +403,14 @@ contract InterchainTokenFactory is IInterchainTokenFactory, Multicall, Upgradabl
*/
function registerCanonicalInterchainToken(address tokenAddress) external payable returns (bytes32 tokenId) {
bytes32 deploySalt = canonicalInterchainTokenDeploySalt(tokenAddress);
string memory currentChain = '';
// No custom operator is set for canonical token registration
bytes memory linkParams = '';
uint256 gasValue = 0;

// Ensure that the ERC20 token has metadata before registering it
// slither-disable-next-line unused-return
_getTokenMetadata(tokenAddress);

tokenId = interchainTokenService.linkToken(
deploySalt,
currentChain,
tokenAddress.toBytes(),
TokenManagerType.LOCK_UNLOCK,
linkParams,
gasValue
);
tokenId = interchainTokenService.registerCustomToken(deploySalt, tokenAddress, TokenManagerType.LOCK_UNLOCK, linkParams);
}

/**
Expand Down Expand Up @@ -530,13 +521,12 @@ contract InterchainTokenFactory is IInterchainTokenFactory, Multicall, Upgradabl
uint256 gasValue
) external payable returns (bytes32 tokenId) {
bytes32 deploySalt = linkedTokenDeploySalt(msg.sender, salt);
string memory currentChain = '';
bytes memory linkParams = '';
if (operator != address(0)) {
linkParams = operator.toBytes();
}

tokenId = interchainTokenService.linkToken(deploySalt, currentChain, tokenAddress.toBytes(), tokenManagerType, linkParams, 0);
tokenId = interchainTokenService.registerCustomToken(deploySalt, tokenAddress, tokenManagerType, linkParams);

interchainTokenService.registerTokenMetadata{ value: gasValue }(tokenAddress, gasValue);
}
Expand Down
109 changes: 56 additions & 53 deletions contracts/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ contract InterchainTokenService is
_;
}

/**
* @notice This modifier is used to ensure that only a the token factory can call a function.
*/
modifier onlyTokenFactory() {
if (msg.sender != interchainTokenFactory) revert NotInterchainTokenFactory(msg.sender);

_;
}

/*****\
GETTERS
\*****/
Expand Down Expand Up @@ -279,6 +288,30 @@ contract InterchainTokenService is
);
}

/**
* @notice Only to be used by the InterchainTokenFactory to register custom tokens to this chain. Then link token can be used to register those tokens to other chains.
* @param salt A unique salt to derive tokenId from.
* @param tokenManagerType The type of the token manager to use for the token registration.
* @param linkParams The operator for the token.
*/
function registerCustomToken(
bytes32 salt,
address tokenAddress,
TokenManagerType tokenManagerType,
bytes calldata linkParams
) external payable whenNotPaused onlyTokenFactory returns (bytes32 tokenId) {
// 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);

address deployer = TOKEN_FACTORY_DEPLOYER;

tokenId = interchainTokenId(deployer, salt);

emit InterchainTokenIdClaimed(tokenId, deployer, salt);

_deployTokenManager(tokenId, tokenManagerType, tokenAddress, linkParams);
}

/**
* @notice If `destinationChain` is an empty string, this function will register the token address on the current chain.
* Otherwise, it will link the token address on the destination chain with the token corresponding to the tokenId on the current chain.
Expand All @@ -305,27 +338,38 @@ contract InterchainTokenService is
// 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);

// Cannot deploy to this chain using linkToken anymore
if (bytes(destinationChain).length == 0) {
revert NotSupported();
}

// Cannot deploy to this chain using linkToken anymore
if (chainNameHash == keccak256(bytes(destinationChain))) revert CannotDeployRemotelyToSelf();

address deployer = msg.sender;

if (deployer == interchainTokenFactory) {
deployer = TOKEN_FACTORY_DEPLOYER;
} else if (bytes(destinationChain).length == 0) {
// TODO: Only support linking new tokens via ITS factory, to include chain name in token id derivation
// Custom token usage needs to be moved to ITS factory tests
// revert NotSupported();
}

tokenId = interchainTokenId(deployer, salt);

emit InterchainTokenIdClaimed(tokenId, deployer, salt);

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

_linkToken(tokenId, destinationChain, destinationTokenAddress, tokenManagerType, linkParams, gasValue);
}
emit LinkTokenStarted(tokenId, destinationChain, sourceTokenAddress, destinationTokenAddress, tokenManagerType, linkParams);

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

_routeMessage(destinationChain, payload, IGatewayCaller.MetadataVersion.CONTRACT_CALL, gasValue);
}

/**
Expand All @@ -352,15 +396,8 @@ contract InterchainTokenService is
uint8 decimals,
bytes memory minter,
uint256 gasValue
) external payable whenNotPaused returns (bytes32 tokenId) {
address deployer = msg.sender;

if (deployer == interchainTokenFactory) {
deployer = TOKEN_FACTORY_DEPLOYER;
} else if (trustedAddressHash(chainName()) == ITS_HUB_ROUTING_IDENTIFIER_HASH) {
// Currently, deployments directly on ITS contract (instead of ITS Factory) are restricted for ITS contracts deployed on Amplifier, i.e registered with the Hub
revert NotSupported();
}
) external payable whenNotPaused onlyTokenFactory returns (bytes32 tokenId) {
address deployer = TOKEN_FACTORY_DEPLOYER;

tokenId = interchainTokenId(deployer, salt);

Expand Down Expand Up @@ -863,40 +900,6 @@ contract InterchainTokenService is
return (messageType, originalSourceChain, payload);
}

/**
* @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 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 linking.
* @param gasValue The amount of gas to be paid for the transaction.
*/
function _linkToken(
bytes32 tokenId,
string calldata destinationChain,
bytes calldata destinationTokenAddress,
TokenManagerType tokenManagerType,
bytes calldata params,
uint256 gasValue
) internal {
// slither-disable-next-line unused-return
bytes memory sourceTokenAddress = registeredTokenAddress(tokenId).toBytes();

emit LinkTokenStarted(tokenId, destinationChain, sourceTokenAddress, destinationTokenAddress, tokenManagerType, params);

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

_routeMessage(destinationChain, payload, IGatewayCaller.MetadataVersion.CONTRACT_CALL, gasValue);
}

/**
* @notice Deploys an interchain token on a destination chain.
* @param tokenId The ID of the token.
Expand Down
14 changes: 14 additions & 0 deletions contracts/interfaces/IInterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ interface IInterchainTokenService is
error EmptyDestinationAddress();
error EmptyTokenAddress();
error NotSupported();
error NotInterchainTokenFactory(address sender);

event InterchainTransfer(
bytes32 indexed tokenId,
Expand Down Expand Up @@ -181,6 +182,19 @@ interface IInterchainTokenService is
*/
function registerTokenMetadata(address tokenAddress, uint256 gasValue) external payable;

/**
* @notice Only to be used by the InterchainTokenFactory to register custom tokens to this chain. Then link token can be used to register those tokens to other chains.
* @param salt A unique salt to derive tokenId from.
* @param tokenManagerType The type of the token manager to use for the token registration.
* @param linkParams The operator for the token.
*/
function registerCustomToken(
bytes32 salt,
address tokenAddress,
TokenManagerType tokenManagerType,
bytes calldata linkParams
) external payable returns (bytes32 tokenId);

/**
* @notice If `destinationChain` is an empty string, this function will register the token address on the current chain.
* Otherwise, it will link the token address on the destination chain with the token corresponding to the tokenId on the current chain.
Expand Down
1 change: 1 addition & 0 deletions scripts/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -142,5 +142,6 @@ module.exports = {
deployMockGateway,
deployGasService,
deployInterchainTokenService,
deployInterchainTokenFactory,
deployAll,
};
16 changes: 0 additions & 16 deletions test/AddressDerivation.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,6 @@ if (isHardhat) {
});

describe('Interchain Token Service Deployments', () => {
it('Should derive the correct token address for interchain token deployment on source chain', async () => {
const salt = getSaltFromKey('deployInterchainToken');
const tokenId = await service.interchainTokenId(wallet.address, salt);

const expectedTokenAddress = '0x2b7c2c1f7297BB9a573Fb970D086F0d113722Ceb';
const expectedTokenManagerAddress = '0x1248d7831b5B231147bfbDb5e2b29B0110EeC9C8';

const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, expectedTokenAddress]);

await expect(service.deployInterchainToken(salt, '', tokenName, tokenSymbol, tokenDecimals, wallet.address, 0))
.to.emit(service, 'InterchainTokenDeployed')
.withArgs(tokenId, expectedTokenAddress, wallet.address, tokenName, tokenSymbol, tokenDecimals)
.to.emit(service, 'TokenManagerDeployed')
.withArgs(tokenId, expectedTokenManagerAddress, NATIVE_INTERCHAIN_TOKEN, params);
});

it('Should derive the correct token address for remote interchain token deployment', async () => {
const salt = getSaltFromKey('deployRemoteInterchainToken');
const tokenId = await service.interchainTokenId(wallet.address, salt);
Expand Down
Loading

0 comments on commit 796c27a

Please sign in to comment.