diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index 51c0e439..f814972c 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -402,7 +402,6 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M * @return tokenId The tokenId corresponding to the registered canonical token. */ function registerCanonicalInterchainToken(address tokenAddress) external payable returns (bytes32 tokenId) { - bytes memory params = abi.encode('', tokenAddress); bytes32 deploySalt = canonicalInterchainTokenDeploySalt(tokenAddress); string memory currentChain = ''; uint256 gasValue = 0; @@ -411,7 +410,15 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M // slither-disable-next-line unused-return _getTokenMetadata(tokenAddress); - tokenId = interchainTokenService.deployTokenManager(deploySalt, currentChain, TokenManagerType.LOCK_UNLOCK, params, gasValue); + tokenId = interchainTokenService.linkToken( + deploySalt, + currentChain, + tokenAddress.toBytes(), + TokenManagerType.LOCK_UNLOCK, + true, + '', + gasValue + ); } /** diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 84053af1..240bf101 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -19,6 +19,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 { IGatewayCaller } from './interfaces/IGatewayCaller.sol'; import { Create3AddressFixed } from './utils/Create3AddressFixed.sol'; import { Operator } from './utils/Operator.sol'; @@ -79,9 +80,11 @@ 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; + //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. @@ -280,40 +283,58 @@ contract InterchainTokenService is \************/ /** - * @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 - * part of a multicall involving multiple functions that could make remote contract calls. - * This method is temporarily restricted in the following scenarios: - * - Deploying to a remote chain and the destination chain is connected via ITS Hub - * - Deploying to the current chain, if connected as an Amplifier chain, i.e existing ITS contracts on consensus chains aren't affected. - * Once ITS Hub adds support for deploy token manager msg, the restriction will be lifted. - * Note that the factory contract can still call `deployTokenManager` to facilitate canonical token registration. - * @param salt The salt to be used during deployment. - * @param destinationChain The name of the chain to deploy the TokenManager and standardized token to. - * @param tokenManagerType The type of token manager to be deployed. Cannot be NATIVE_INTERCHAIN_TOKEN. - * @param params The params that will be used to initialize the TokenManager. - * @param gasValue The amount of native tokens to be used to pay for gas for the remote deployment. - * @return tokenId The tokenId corresponding to the deployed TokenManager. + * @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 deployTokenManager( + 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); + + string memory destinationAddress = trustedAddress(ITS_HUB_CHAIN_NAME); + // Check whether no trusted address was set for ITS Hub chain + if (bytes(destinationAddress).length == 0) revert UntrustedChain(); + + (bool success, bytes memory returnData) = gatewayCaller.delegatecall( + abi.encodeWithSelector( + IGatewayCaller.callContract.selector, + ITS_HUB_CHAIN_NAME, + destinationAddress, + payload, + IGatewayCaller.MetadataVersion.CONTRACT_CALL, + gasValue + ) + ); + + if (!success) revert GatewayCallFailed(returnData); + } + + function linkToken( bytes32 salt, string calldata destinationChain, + bytes memory destinationTokenAddress, TokenManagerType tokenManagerType, - bytes calldata params, + bool autoScaling, + bytes memory linkParams, uint256 gasValue - ) external payable whenNotPaused returns (bytes32 tokenId) { - if (bytes(params).length == 0) revert EmptyParams(); + ) public payable whenNotPaused returns (bytes32 tokenId) { + if (destinationTokenAddress.length == 0) revert EmptyDestinationAddress(); + // TODO: Should we only allow 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); 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 - revert NotSupported(); + } 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); @@ -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); } } @@ -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(); @@ -731,15 +752,13 @@ 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( - payload, - (uint256, bytes32, TokenManagerType, bytes) - ); + function _processLinkTokenPayload(bytes memory payload) internal { + (, bytes32 tokenId, TokenManagerType tokenManagerType, , bytes memory destinationTokenAddress, , bytes memory linkParams) = abi + .decode(payload, (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); } /** @@ -755,7 +774,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); } /** @@ -804,9 +823,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; @@ -833,10 +849,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); } @@ -876,8 +892,8 @@ 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(); + // Prevent link token to be usable on ITS HUB. + if (messageType == MESSAGE_TYPE_LINK_TOKEN) 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(); @@ -890,23 +906,43 @@ 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); + emit LinkTokenStarted( + tokenId, + destinationChain, + sourceTokenAddress, + destinationTokenAddress, + tokenManagerType, + autoScaling, + 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); } @@ -944,13 +980,17 @@ contract InterchainTokenService is _callContract(destinationChain, payload, IGatewayCaller.MetadataVersion.CONTRACT_CALL, gasValue); } - /** + /* * @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) ); diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index f6aef9cd..c38b2f2e 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -72,10 +72,13 @@ interface IInterchainTokenService is uint256 amount, bytes32 dataHash ); - event TokenManagerDeploymentStarted( + event LinkTokenStarted( bytes32 indexed tokenId, string destinationChain, + bytes sourceTokenAddress, + bytes destinationTokenAddress, TokenManagerType indexed tokenManagerType, + bool autoScaling, bytes params ); event InterchainTokenDeploymentStarted( @@ -170,19 +173,30 @@ interface IInterchainTokenService is function interchainTokenId(address operator_, bytes32 salt) external view returns (bytes32 tokenId); /** - * @notice Deploys a custom token manager contract on a remote chain. + * @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 cross-chain gas value for sending the registration message to ITS Hub. + */ + function registerTokenMetadata(address tokenAddress, uint256 gasValue) external payable; + + /** + * @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 params The deployment parameters. + * @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 deployTokenManager( + function linkToken( bytes32 salt, string calldata destinationChain, + bytes memory destinationTokenAddress, TokenManagerType tokenManagerType, - bytes calldata params, + bool autoScaling, + bytes memory linkParams, uint256 gasValue ) external payable returns (bytes32 tokenId); diff --git a/scripts/deploy.js b/scripts/deploy.js index a1db9206..aa29904c 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -42,7 +42,6 @@ async function deployInterchainTokenService( operatorAddress = wallet.address, ) { const interchainTokenServiceAddress = await getCreate3Address(create3DeployerAddress, wallet, deploymentKey); - const implementation = await deployContract(wallet, 'InterchainTokenService', [ tokenManagerDeployerAddress, interchainTokenDeployerAddress, diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 2cb75946..868d0013 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -19,6 +19,7 @@ const { MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, MESSAGE_TYPE_RECEIVE_FROM_HUB, + MESSAGE_TYPE_LINK_TOKEN, INVALID_MESSAGE_TYPE, NATIVE_INTERCHAIN_TOKEN, MINT_BURN_FROM, @@ -59,6 +60,7 @@ describe('Interchain Token Service', () => { const salt = getRandomBytes32(); const tokenId = await service.interchainTokenId(wallet.address, salt); const tokenManager = await getContractAt('TokenManager', await service.tokenManagerAddress(tokenId), wallet); + const autoScaling = false; const token = await deployContract(wallet, 'TestInterchainTokenStandard', [ tokenName, @@ -67,9 +69,8 @@ describe('Interchain Token Service', () => { service.address, tokenId, ]); - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - await service.deployTokenManager(salt, '', LOCK_UNLOCK, params, 0).then((tx) => tx.wait); + await service.linkToken(salt, '', token.address, LOCK_UNLOCK, autoScaling, wallet.address, 0).then((tx) => tx.wait); if (mintAmount > 0) { await token.mint(wallet.address, mintAmount).then((tx) => tx.wait); @@ -91,6 +92,7 @@ describe('Interchain Token Service', () => { const salt = getRandomBytes32(); const tokenId = await service.interchainTokenId(wallet.address, salt); const tokenManager = await getContractAt('TokenManager', await service.tokenManagerAddress(tokenId), wallet); + const autoScaling = false; let token; @@ -120,9 +122,7 @@ describe('Interchain Token Service', () => { ]); } - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - - await service.deployTokenManager(salt, '', LOCK_UNLOCK_FEE_ON_TRANSFER, params, 0).then((tx) => tx.wait); + await service.linkToken(salt, '', token.address, LOCK_UNLOCK_FEE_ON_TRANSFER, autoScaling, wallet.address, 0).then((tx) => tx.wait); if (mintAmount > 0) { await token.mint(wallet.address, mintAmount).then((tx) => tx.wait); @@ -146,6 +146,7 @@ describe('Interchain Token Service', () => { service.address, tokenId, ]); + const autoScaling = false; const tokenManager = await getContractAt('TokenManager', await service.tokenManagerAddress(tokenId), wallet); @@ -155,8 +156,7 @@ describe('Interchain Token Service', () => { await token.transferMintership(tokenManager.address).then((tx) => tx.wait); - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - await service.deployTokenManager(salt, '', type, params, 0).then((tx) => tx.wait); + await service.linkToken(salt, '', token.address, type, autoScaling, wallet.address, 0).then((tx) => tx.wait); return [token, tokenManager, tokenId]; }; @@ -542,15 +542,7 @@ describe('Interchain Token Service', () => { const salt = getRandomBytes32(); await expectRevert( - (gasOptions) => - serviceTest.deployTokenManager( - salt, - chainName, - LOCK_UNLOCK, - defaultAbiCoder.encode(['bytes', 'address'], ['0x', testToken.address]), - 0, - gasOptions, - ), + (gasOptions) => serviceTest.linkToken(salt, chainName, testToken.address, LOCK_UNLOCK, true, '0x', 0, gasOptions), serviceTest, 'CannotDeployRemotelyToSelf', ); @@ -721,9 +713,7 @@ describe('Interchain Token Service', () => { before(async () => { salt = getRandomBytes32(); - await service - .deployTokenManager(salt, '', LOCK_UNLOCK, defaultAbiCoder.encode(['bytes', 'address'], ['0x', testToken.address]), 0) - .then((tx) => tx.wait); + await service.linkToken(salt, '', testToken.address, LOCK_UNLOCK, true, '0x', 0).then((tx) => tx.wait); }); it('Should initialize a remote interchain token deployment', async () => { @@ -858,6 +848,7 @@ describe('Interchain Token Service', () => { const tokenName = 'Token Name'; const tokenSymbol = 'TN'; const tokenDecimals = 13; + const autoScaling = false; let token, salt, tokenId; let tokenManagerProxy; @@ -874,24 +865,21 @@ describe('Interchain Token Service', () => { }); it('Should revert on deploying an invalid token manager', async () => { - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - - await expectRevert((gasOptions) => service.deployTokenManager(salt, '', 6, params, 0, gasOptions)); + await expectRevert((gasOptions) => service.linkToken(salt, '', token.address, 6, autoScaling, wallet.address, 0, gasOptions)); }); it('Should revert on deploying a local token manager with invalid params', async () => { await expectRevert( - (gasOptions) => service.deployTokenManager(salt, '', NATIVE_INTERCHAIN_TOKEN, '0x', 0, gasOptions), + (gasOptions) => service.linkToken(salt, '', token.address, NATIVE_INTERCHAIN_TOKEN, autoScaling, '0x', 0, gasOptions), service, - 'EmptyParams', + 'CannotDeploy', ); }); it('Should revert on deploying a local token manager with interchain token manager type', async () => { - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - await expectRevert( - (gasOptions) => service.deployTokenManager(salt, '', NATIVE_INTERCHAIN_TOKEN, params, 0, gasOptions), + (gasOptions) => + service.linkToken(salt, '', token.address, NATIVE_INTERCHAIN_TOKEN, autoScaling, wallet.address, 0, gasOptions), service, 'CannotDeploy', [NATIVE_INTERCHAIN_TOKEN], @@ -899,10 +887,18 @@ describe('Interchain Token Service', () => { }); it('Should revert on deploying a remote token manager with interchain token manager type', async () => { - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - await expectRevert( - (gasOptions) => service.deployTokenManager(salt, destinationChain, NATIVE_INTERCHAIN_TOKEN, params, 0, gasOptions), + (gasOptions) => + service.linkToken( + salt, + destinationChain, + token.address, + NATIVE_INTERCHAIN_TOKEN, + autoScaling, + wallet.address, + 0, + gasOptions, + ), service, 'CannotDeploy', [NATIVE_INTERCHAIN_TOKEN], @@ -910,10 +906,8 @@ describe('Interchain Token Service', () => { }); it('Should revert on deploying a token manager if token handler post deploy fails', async () => { - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, AddressZero]); - await expectRevert( - (gasOptions) => service.deployTokenManager(salt, '', LOCK_UNLOCK, params, 0, gasOptions), + (gasOptions) => service.linkToken(salt, '', AddressZero, LOCK_UNLOCK, autoScaling, wallet.address, 0, gasOptions), service, 'PostDeployFailed', ); @@ -923,7 +917,12 @@ describe('Interchain Token Service', () => { const tokenManagerAddress = await service.tokenManagerAddress(tokenId); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - await expect(reportGas(service.deployTokenManager(salt, '', LOCK_UNLOCK, params, 0), 'Call deployTokenManager on source chain')) + await expect( + reportGas( + service.linkToken(salt, '', token.address, LOCK_UNLOCK, autoScaling, wallet.address, 0), + 'Call deployTokenManager on source chain', + ), + ) .to.emit(service, 'InterchainTokenIdClaimed') .withArgs(tokenId, wallet.address, salt) .to.emit(service, 'TokenManagerDeployed') @@ -948,10 +947,9 @@ describe('Interchain Token Service', () => { }); it('Should revert when deploying a custom token manager twice', async () => { - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const revertData = keccak256(toUtf8Bytes('AlreadyDeployed()')).substring(0, 10); await expectRevert( - (gasOptions) => service.deployTokenManager(salt, '', LOCK_UNLOCK, params, 0, gasOptions), + (gasOptions) => service.linkToken(salt, '', token.address, LOCK_UNLOCK, autoScaling, wallet.address, 0, gasOptions), service, 'TokenManagerDeploymentFailed', [revertData], @@ -987,7 +985,7 @@ describe('Interchain Token Service', () => { ]); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - const tx = service.deployTokenManager(salt, '', MINT_BURN, params, 0); + const tx = service.linkToken(salt, '', token.address, MINT_BURN, autoScaling, wallet.address, 0); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); await expect(tx).to.emit(service, 'TokenManagerDeployed').withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); @@ -1022,7 +1020,7 @@ describe('Interchain Token Service', () => { ]); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - const tx = service.deployTokenManager(salt, '', MINT_BURN_FROM, params, 0); + const tx = service.linkToken(salt, '', token.address, MINT_BURN_FROM, autoScaling, wallet.address, 0); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); await expect(tx) .to.emit(service, 'TokenManagerDeployed') @@ -1059,7 +1057,7 @@ describe('Interchain Token Service', () => { ]); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - const tx = service.deployTokenManager(salt, '', LOCK_UNLOCK_FEE_ON_TRANSFER, params, 0); + const tx = service.linkToken(salt, '', token.address, LOCK_UNLOCK_FEE_ON_TRANSFER, autoScaling, wallet.address, 0); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); await expect(tx) .to.emit(service, 'TokenManagerDeployed') @@ -1086,50 +1084,32 @@ describe('Interchain Token Service', () => { it('Should revert when deploying a custom token manager if paused', async () => { await service.setPauseStatus(true).then((tx) => tx.wait); - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - - await expectRevert((gasOptions) => service.deployTokenManager(salt, '', LOCK_UNLOCK, params, 0, gasOptions), service, 'Pause'); - - await service.setPauseStatus(false).then((tx) => tx.wait); - }); - - it('Should revert with NotSupported when deploying a token manager on its hub chain', async () => { - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - - await expect(service.setTrustedAddress(chainName, ITS_HUB_ROUTING_IDENTIFIER)) - .to.emit(service, 'TrustedAddressSet') - .withArgs(chainName, ITS_HUB_ROUTING_IDENTIFIER); - await expectRevert( - (gasOptions) => service.deployTokenManager(salt, '', LOCK_UNLOCK, params, 0, gasOptions), + (gasOptions) => service.linkToken(salt, '', token.address, LOCK_UNLOCK, autoScaling, wallet.address, 0, gasOptions), service, - 'NotSupported', + 'Pause', ); - await expect(service.removeTrustedAddress(chainName)).to.emit(service, 'TrustedAddressRemoved').withArgs(chainName); + await service.setPauseStatus(false).then((tx) => tx.wait); }); }); describe('Initialize remote custom token manager deployment', () => { + const autoScaling = false; + it('Should initialize a remote custom token manager deployment', async () => { const salt = getRandomBytes32(); + const tokenAddress = wallet.address; - await ( - await service.deployTokenManager( - salt, - '', - MINT_BURN, - defaultAbiCoder.encode(['bytes', 'address'], ['0x', wallet.address]), - 0, - ) - ).wait(); + await (await service.linkToken(salt, '', tokenAddress, MINT_BURN, autoScaling, '0x', 0)).wait(); const tokenId = await service.interchainTokenId(wallet.address, salt); - const params = '0x1234'; + const remoteTokenAddress = '0x1234'; + const minter = '0x5789'; const type = LOCK_UNLOCK; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes'], - [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, type, params], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, type, tokenAddress, remoteTokenAddress, autoScaling, minter], ); const tokenManager = await getContractAt('TokenManager', await service.deployedTokenManager(tokenId), wallet); @@ -1140,14 +1120,22 @@ describe('Interchain Token Service', () => { await expect( reportGas( - service.deployTokenManager(salt, destinationChain, type, params, gasValue, { value: gasValue }), + service.linkToken(salt, destinationChain, remoteTokenAddress, type, autoScaling, minter, gasValue, { value: gasValue }), 'Send deployTokenManager to remote chain', ), ) .to.emit(service, 'InterchainTokenIdClaimed') .withArgs(tokenId, wallet.address, salt) - .to.emit(service, 'TokenManagerDeploymentStarted') - .withArgs(tokenId, destinationChain, type, params) + .to.emit(service, 'LinkTokenStarted') + .withArgs( + tokenId, + destinationChain, + tokenAddress.toLowerCase(), + remoteTokenAddress.toLowerCase(), + type, + autoScaling, + minter.toLowerCase(), + ) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, destinationChain, service.address, keccak256(payload), gasValue, wallet.address) .and.to.emit(gateway, 'ContractCall') @@ -1157,11 +1145,13 @@ describe('Interchain Token Service', () => { it('Should revert on a remote custom token manager deployment if the token manager does does not exist', async () => { const salt = getRandomBytes32(); const tokenId = await service.interchainTokenId(wallet.address, salt); - const params = '0x1234'; + const tokenAddress = '0x1234'; + const minter = '0x5678'; + const autoScaling = true; const type = LOCK_UNLOCK; await expect( - service.deployTokenManager(salt, destinationChain, type, params, gasValue, { value: gasValue }), + service.linkToken(salt, destinationChain, tokenAddress, type, autoScaling, minter, gasValue, { value: gasValue }), ).to.be.revertedWithCustomError(service, 'TokenManagerDoesNotExist', [tokenId]); }); @@ -1169,12 +1159,14 @@ describe('Interchain Token Service', () => { await service.setPauseStatus(true).then((tx) => tx.wait); const salt = getRandomBytes32(); - const params = '0x1234'; + const tokenAddress = '0x1234'; + const minter = '0x5678'; + const autoScaling = true; const type = LOCK_UNLOCK; await expectRevert( (gasOptions) => - service.deployTokenManager(salt, destinationChain, type, params, gasValue, { + service.linkToken(salt, destinationChain, tokenAddress, type, autoScaling, minter, gasValue, { ...gasOptions, value: gasValue, }), @@ -1184,34 +1176,6 @@ describe('Interchain Token Service', () => { await service.setPauseStatus(false).then((tx) => tx.wait); }); - - it('Should revert with NotSupported on deploying a remote custom token manager via its hub', async () => { - const salt = getRandomBytes32(); - - await ( - await service.deployTokenManager( - salt, - '', - MINT_BURN, - defaultAbiCoder.encode(['bytes', 'address'], ['0x', wallet.address]), - 0, - ) - ).wait(); - - const params = '0x1234'; - const type = LOCK_UNLOCK; - const destinationChainItsHub = 'hub chain 1'; - - await expect(service.setTrustedAddress(destinationChainItsHub, ITS_HUB_ROUTING_IDENTIFIER)) - .to.emit(service, 'TrustedAddressSet') - .withArgs(destinationChainItsHub, ITS_HUB_ROUTING_IDENTIFIER); - - await expectRevert( - (gasOptions) => service.deployTokenManager(salt, destinationChainItsHub, type, params, gasValue, gasOptions), - service, - 'NotSupported', - ); - }); }); describe('Receive Remote Token Manager Deployment', () => { @@ -1227,6 +1191,11 @@ describe('Interchain Token Service', () => { it('Should be able to receive a remote lock/unlock token manager deployment', async () => { const tokenId = getRandomBytes32(); const tokenManagerAddress = await service.tokenManagerAddress(tokenId); + const tokenManagerType = LOCK_UNLOCK; + const sourceTokenAddress = '0x1234'; + const autoScaling = true; + const minter = wallet.address; + const token = await deployContract(wallet, 'TestInterchainTokenStandard', [ tokenName, tokenSymbol, @@ -1237,15 +1206,15 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes'], - [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, LOCK_UNLOCK, params], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, tokenManagerType, sourceTokenAddress, token.address, autoScaling, minter], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); await expect(reportGas(service.execute(commandId, sourceChain, sourceAddress, payload), 'Receive GMP DEPLOY_TOKEN_MANAGER')) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, expectedTokenManagerAddress, LOCK_UNLOCK, params); + .withArgs(tokenId, expectedTokenManagerAddress, tokenManagerType, params); const tokenManager = await getContractAt('TokenManager', tokenManagerAddress, wallet); expect(await tokenManager.tokenAddress()).to.equal(token.address); @@ -1255,6 +1224,11 @@ describe('Interchain Token Service', () => { it('Should be able to receive a remote mint/burn token manager deployment', async () => { const tokenId = getRandomBytes32(); const tokenManagerAddress = await service.tokenManagerAddress(tokenId); + const tokenManagerType = MINT_BURN; + const sourceTokenAddress = '0x1234'; + const autoScaling = true; + const minter = wallet.address; + const token = await deployContract(wallet, 'TestInterchainTokenStandard', [ tokenName, tokenSymbol, @@ -1265,15 +1239,15 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes'], - [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, MINT_BURN, params], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, tokenManagerType, sourceTokenAddress, token.address, autoScaling, minter], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); + .withArgs(tokenId, expectedTokenManagerAddress, tokenManagerType, params); const tokenManager = await getContractAt('TokenManager', tokenManagerAddress, wallet); expect(await tokenManager.tokenAddress()).to.equal(token.address); expect(await tokenManager.hasRole(wallet.address, OPERATOR_ROLE)).to.be.true; @@ -1281,6 +1255,11 @@ describe('Interchain Token Service', () => { it('Should not be able to receive a remote interchain token manager deployment', async () => { const tokenId = getRandomBytes32(); + const tokenManagerType = NATIVE_INTERCHAIN_TOKEN; + const sourceTokenAddress = '0x1234'; + const autoScaling = true; + const minter = wallet.address; + const token = await deployContract(wallet, 'TestInterchainTokenStandard', [ tokenName, tokenSymbol, @@ -1289,10 +1268,9 @@ describe('Interchain Token Service', () => { tokenId, ]); - const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes'], - [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, NATIVE_INTERCHAIN_TOKEN, params], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, tokenManagerType, sourceTokenAddress, token.address, autoScaling, minter], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -2162,24 +2140,19 @@ describe('Interchain Token Service', () => { it('Should revert with NotSupported when the message type is RECEIVE_FROM_HUB and has MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER type.', async () => { const salt = getRandomBytes32(); + const tokenAddress = wallet.address; + const autoScaling = false; - await ( - await service.deployTokenManager( - salt, - '', - MINT_BURN, - defaultAbiCoder.encode(['bytes', 'address'], ['0x', wallet.address]), - 0, - ) - ).wait(); + await (await service.linkToken(salt, '', tokenAddress, MINT_BURN, autoScaling, wallet.address, 0)).wait(); const tokenId = await service.interchainTokenId(wallet.address, salt); - const params = '0x1234'; + const remoteTokenAddress = '0x1234'; + const minter = '0x5678'; const type = LOCK_UNLOCK; const sourceChain = 'hub chain 1'; const itsMessage = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes'], - [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, type, params], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, type, tokenAddress, remoteTokenAddress, autoScaling, minter], ); const payload = defaultAbiCoder.encode( ['uint256', 'string', 'bytes'], diff --git a/test/InterchainTokenServiceFullFlow.js b/test/InterchainTokenServiceFullFlow.js index 2d177f28..2722e5ce 100644 --- a/test/InterchainTokenServiceFullFlow.js +++ b/test/InterchainTokenServiceFullFlow.js @@ -18,7 +18,7 @@ const { approveContractCall } = require('../scripts/utils'); const { MESSAGE_TYPE_INTERCHAIN_TRANSFER, MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, - MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, + MESSAGE_TYPE_LINK_TOKEN, MESSAGE_TYPE_SEND_TO_HUB, MESSAGE_TYPE_RECEIVE_FROM_HUB, NATIVE_INTERCHAIN_TOKEN, @@ -342,6 +342,7 @@ describe('Interchain Token Service Full Flow', () => { const gasValues = [1234, 5678]; const tokenCap = 1e9; const salt = keccak256('0x697858'); + const autoScaling = true; before(async () => { token = await deployContract(wallet, 'TestMintableBurnableERC20', [name, symbol, decimals]); @@ -355,7 +356,7 @@ describe('Interchain Token Service Full Flow', () => { const tokenManagerImplementation = await getContractAt('TokenManager', tokenManagerImplementationAddress, wallet); const params = await tokenManagerImplementation.params(wallet.address, token.address); - let tx = await service.populateTransaction.deployTokenManager(salt, '', MINT_BURN, params, 0); + let tx = await service.populateTransaction.linkToken(salt, '', token.address, MINT_BURN, autoScaling, wallet.address, 0); const calls = [tx.data]; let value = 0; @@ -363,29 +364,53 @@ describe('Interchain Token Service Full Flow', () => { for (const i in otherChains) { // This should be replaced with the existing token address on each chain being linked const remoteTokenAddress = token.address; - const params = await tokenManagerImplementation.params(wallet.address, remoteTokenAddress); - tx = await service.populateTransaction.deployTokenManager(salt, otherChains[i], MINT_BURN, params, gasValues[i]); + tx = await service.populateTransaction.linkToken( + salt, + otherChains[i], + remoteTokenAddress, + MINT_BURN, + autoScaling, + wallet.address, + gasValues[i], + ); calls.push(tx.data); value += gasValues[i]; } const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes'], - [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, MINT_BURN, params], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, MINT_BURN, token.address, token.address, autoScaling, wallet.address], ); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); + await expect(service.multicall(calls, { value })) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params) - .and.to.emit(service, 'TokenManagerDeploymentStarted') - .withArgs(tokenId, otherChains[0], MINT_BURN, params) + .and.to.emit(service, 'LinkTokenStarted') + .withArgs( + tokenId, + otherChains[0], + token.address.toLowerCase(), + token.address.toLowerCase(), + MINT_BURN, + autoScaling, + wallet.address.toLowerCase(), + ) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, otherChains[0], service.address, keccak256(payload), gasValues[0], wallet.address) .and.to.emit(gateway, 'ContractCall') .withArgs(service.address, otherChains[0], service.address, keccak256(payload), payload) - .and.to.emit(service, 'TokenManagerDeploymentStarted') - .withArgs(tokenId, otherChains[1], MINT_BURN, params) + .and.to.emit(service, 'LinkTokenStarted') + .withArgs( + tokenId, + otherChains[1], + token.address.toLowerCase(), + token.address.toLowerCase(), + MINT_BURN, + autoScaling, + wallet.address.toLowerCase(), + ) .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, otherChains[1], service.address, keccak256(payload), gasValues[1], wallet.address) .and.to.emit(gateway, 'ContractCall') diff --git a/test/InterchainTokenServiceUpgradeFlow.js b/test/InterchainTokenServiceUpgradeFlow.js index ce5a2965..670df9c3 100644 --- a/test/InterchainTokenServiceUpgradeFlow.js +++ b/test/InterchainTokenServiceUpgradeFlow.js @@ -52,7 +52,7 @@ describe('Interchain Token Service Upgrade Flow', () => { ]); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - await expect(service.deployTokenManager(salt, '', MINT_BURN, params, 0)) + await expect(service.linkToken(salt, '', token.address, MINT_BURN, true, wallet.address, 0)) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManager.address, MINT_BURN, params); } diff --git a/test/constants.js b/test/constants.js index a4fef332..ccc90864 100644 --- a/test/constants.js +++ b/test/constants.js @@ -5,7 +5,9 @@ const MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN = 1; const MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER = 2; const MESSAGE_TYPE_SEND_TO_HUB = 3; const MESSAGE_TYPE_RECEIVE_FROM_HUB = 4; -const INVALID_MESSAGE_TYPE = 5; +const MESSAGE_TYPE_LINK_TOKEN = 5; +const MESSAGE_TYPE_REGISTER_TOKEN_METADATA = 6; +const INVALID_MESSAGE_TYPE = 7; const NATIVE_INTERCHAIN_TOKEN = 0; const MINT_BURN_FROM = 1; @@ -28,6 +30,8 @@ module.exports = { MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, MESSAGE_TYPE_SEND_TO_HUB, MESSAGE_TYPE_RECEIVE_FROM_HUB, + MESSAGE_TYPE_LINK_TOKEN, + MESSAGE_TYPE_REGISTER_TOKEN_METADATA, INVALID_MESSAGE_TYPE, NATIVE_INTERCHAIN_TOKEN, MINT_BURN_FROM,