From 98365e4631fe044a052e56589ab0c2a74c909eee Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 8 Jan 2025 19:02:41 +0200 Subject: [PATCH 01/15] Tests are passing. --- contracts/InterchainTokenFactory.sol | 11 +- contracts/InterchainTokenService.sol | 142 +++++++---- .../interfaces/IInterchainTokenService.sol | 24 +- scripts/deploy.js | 1 - test/InterchainTokenService.js | 221 ++++++++---------- test/InterchainTokenServiceFullFlow.js | 45 +++- test/InterchainTokenServiceUpgradeFlow.js | 2 +- test/constants.js | 6 +- 8 files changed, 257 insertions(+), 195 deletions(-) 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, From fb0464fcc865b8987045adc73bd97f2b6cf6649b Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 9 Jan 2025 13:48:36 +0200 Subject: [PATCH 02/15] stash to remove auto scaling --- contracts/InterchainTokenFactory.sol | 48 ++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index f814972c..fc1b3894 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -27,6 +27,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M 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_DEPLOY_APPROVAL = keccak256('deploy-approval'); + bytes32 internal constant PREFIX_CUSTOM_TOKEN_SALT = keccak256('custom-token-salt'); address private constant TOKEN_FACTORY_DEPLOYER = address(0); IInterchainTokenService public immutable interchainTokenService; @@ -491,6 +492,53 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M tokenId = deployRemoteCanonicalInterchainToken(originalTokenAddress, destinationChain, gasValue); } + 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, + uint256 gasValue + ) 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, operatorBytes, 0); + + interchainTokenService.registerTokenMetadata{ value: gasValue }(tokenAddress, gasValue); + } + + function linkToken( + bytes32 salt, + string calldata destinationChain, + bytes calldata destinationTokenAddress, + TokenManagerType tokenManagerType, + bytes calldata linkParams, + uint256 gasValue + ) external payable returns (bytes32 tokenId) { + bytes32 deploySalt = linkedTokenDeploySalt(msg.sender, salt); + tokenId = interchainTokenService.linkToken( + deploySalt, + destinationChain, + destinationTokenAddress, + tokenManagerType, + linkParams, + gasValue + ); + } + /********************\ |* Pure Key Getters *| \********************/ From 873020e9dbe8448510a6c4f8c0f7554e525e9530 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 9 Jan 2025 14:03:45 +0200 Subject: [PATCH 03/15] remove autoScaling --- contracts/InterchainTokenFactory.sol | 49 ------------ contracts/InterchainTokenService.sol | 11 +-- .../interfaces/IInterchainTokenService.sol | 3 - test/InterchainTokenService.js | 74 ++++++++----------- test/InterchainTokenServiceFullFlow.js | 10 +-- test/InterchainTokenServiceUpgradeFlow.js | 2 +- 6 files changed, 37 insertions(+), 112 deletions(-) diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index fc1b3894..12248b91 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -27,7 +27,6 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M 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_DEPLOY_APPROVAL = keccak256('deploy-approval'); - bytes32 internal constant PREFIX_CUSTOM_TOKEN_SALT = keccak256('custom-token-salt'); address private constant TOKEN_FACTORY_DEPLOYER = address(0); IInterchainTokenService public immutable interchainTokenService; @@ -416,7 +415,6 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M currentChain, tokenAddress.toBytes(), TokenManagerType.LOCK_UNLOCK, - true, '', gasValue ); @@ -492,53 +490,6 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M tokenId = deployRemoteCanonicalInterchainToken(originalTokenAddress, destinationChain, gasValue); } - 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, - uint256 gasValue - ) 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, operatorBytes, 0); - - interchainTokenService.registerTokenMetadata{ value: gasValue }(tokenAddress, gasValue); - } - - function linkToken( - bytes32 salt, - string calldata destinationChain, - bytes calldata destinationTokenAddress, - TokenManagerType tokenManagerType, - bytes calldata linkParams, - uint256 gasValue - ) external payable returns (bytes32 tokenId) { - bytes32 deploySalt = linkedTokenDeploySalt(msg.sender, salt); - tokenId = interchainTokenService.linkToken( - deploySalt, - destinationChain, - destinationTokenAddress, - tokenManagerType, - linkParams, - gasValue - ); - } - /********************\ |* Pure Key Getters *| \********************/ diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 240bf101..c4d58c98 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -317,7 +317,6 @@ contract InterchainTokenService is string calldata destinationChain, bytes memory destinationTokenAddress, TokenManagerType tokenManagerType, - bool autoScaling, bytes memory linkParams, uint256 gasValue ) public payable whenNotPaused returns (bytes32 tokenId) { @@ -346,7 +345,7 @@ contract InterchainTokenService is } else { if (chainNameHash == keccak256(bytes(destinationChain))) revert CannotDeployRemotelyToSelf(); - _linkToken(tokenId, destinationChain, destinationTokenAddress, tokenManagerType, autoScaling, linkParams, gasValue); + _linkToken(tokenId, destinationChain, destinationTokenAddress, tokenManagerType, linkParams, gasValue); } } @@ -753,8 +752,8 @@ contract InterchainTokenService is * @notice Processes a deploy token manager payload. */ 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)); + (, bytes32 tokenId, TokenManagerType tokenManagerType, , bytes memory destinationTokenAddress, bytes memory linkParams) = abi + .decode(payload, (uint256, bytes32, TokenManagerType, bytes, bytes, bytes)); if (tokenManagerType == TokenManagerType.NATIVE_INTERCHAIN_TOKEN) revert CannotDeploy(tokenManagerType); @@ -908,7 +907,6 @@ contract InterchainTokenService is * @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 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. */ @@ -917,7 +915,6 @@ contract InterchainTokenService is string calldata destinationChain, bytes memory destinationTokenAddress, TokenManagerType tokenManagerType, - bool autoScaling, bytes memory params, uint256 gasValue ) internal { @@ -930,7 +927,6 @@ contract InterchainTokenService is sourceTokenAddress, destinationTokenAddress, tokenManagerType, - autoScaling, params ); @@ -940,7 +936,6 @@ contract InterchainTokenService is tokenManagerType, sourceTokenAddress, destinationTokenAddress, - autoScaling, params ); diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index c38b2f2e..5b40518f 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -78,7 +78,6 @@ interface IInterchainTokenService is bytes sourceTokenAddress, bytes destinationTokenAddress, TokenManagerType indexed tokenManagerType, - bool autoScaling, bytes params ); event InterchainTokenDeploymentStarted( @@ -185,7 +184,6 @@ interface IInterchainTokenService is * @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. @@ -195,7 +193,6 @@ interface IInterchainTokenService is string calldata destinationChain, bytes memory destinationTokenAddress, TokenManagerType tokenManagerType, - bool autoScaling, bytes memory linkParams, uint256 gasValue ) external payable returns (bytes32 tokenId); diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 868d0013..cda9f10e 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -60,7 +60,6 @@ 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, @@ -70,7 +69,7 @@ describe('Interchain Token Service', () => { tokenId, ]); - await service.linkToken(salt, '', token.address, LOCK_UNLOCK, autoScaling, wallet.address, 0).then((tx) => tx.wait); + await service.linkToken(salt, '', token.address, LOCK_UNLOCK, wallet.address, 0).then((tx) => tx.wait); if (mintAmount > 0) { await token.mint(wallet.address, mintAmount).then((tx) => tx.wait); @@ -92,7 +91,6 @@ 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; @@ -122,7 +120,7 @@ describe('Interchain Token Service', () => { ]); } - await service.linkToken(salt, '', token.address, LOCK_UNLOCK_FEE_ON_TRANSFER, autoScaling, wallet.address, 0).then((tx) => tx.wait); + await service.linkToken(salt, '', token.address, LOCK_UNLOCK_FEE_ON_TRANSFER, wallet.address, 0).then((tx) => tx.wait); if (mintAmount > 0) { await token.mint(wallet.address, mintAmount).then((tx) => tx.wait); @@ -146,7 +144,6 @@ describe('Interchain Token Service', () => { service.address, tokenId, ]); - const autoScaling = false; const tokenManager = await getContractAt('TokenManager', await service.tokenManagerAddress(tokenId), wallet); @@ -156,7 +153,7 @@ describe('Interchain Token Service', () => { await token.transferMintership(tokenManager.address).then((tx) => tx.wait); - await service.linkToken(salt, '', token.address, type, autoScaling, wallet.address, 0).then((tx) => tx.wait); + await service.linkToken(salt, '', token.address, type, wallet.address, 0).then((tx) => tx.wait); return [token, tokenManager, tokenId]; }; @@ -542,7 +539,7 @@ describe('Interchain Token Service', () => { const salt = getRandomBytes32(); await expectRevert( - (gasOptions) => serviceTest.linkToken(salt, chainName, testToken.address, LOCK_UNLOCK, true, '0x', 0, gasOptions), + (gasOptions) => serviceTest.linkToken(salt, chainName, testToken.address, LOCK_UNLOCK, '0x', 0, gasOptions), serviceTest, 'CannotDeployRemotelyToSelf', ); @@ -713,7 +710,7 @@ describe('Interchain Token Service', () => { before(async () => { salt = getRandomBytes32(); - await service.linkToken(salt, '', testToken.address, LOCK_UNLOCK, true, '0x', 0).then((tx) => tx.wait); + await service.linkToken(salt, '', testToken.address, LOCK_UNLOCK, '0x', 0).then((tx) => tx.wait); }); it('Should initialize a remote interchain token deployment', async () => { @@ -848,7 +845,6 @@ describe('Interchain Token Service', () => { const tokenName = 'Token Name'; const tokenSymbol = 'TN'; const tokenDecimals = 13; - const autoScaling = false; let token, salt, tokenId; let tokenManagerProxy; @@ -865,12 +861,12 @@ describe('Interchain Token Service', () => { }); it('Should revert on deploying an invalid token manager', async () => { - await expectRevert((gasOptions) => service.linkToken(salt, '', token.address, 6, autoScaling, wallet.address, 0, gasOptions)); + await expectRevert((gasOptions) => service.linkToken(salt, '', token.address, 6, wallet.address, 0, gasOptions)); }); it('Should revert on deploying a local token manager with invalid params', async () => { await expectRevert( - (gasOptions) => service.linkToken(salt, '', token.address, NATIVE_INTERCHAIN_TOKEN, autoScaling, '0x', 0, gasOptions), + (gasOptions) => service.linkToken(salt, '', token.address, NATIVE_INTERCHAIN_TOKEN, '0x', 0, gasOptions), service, 'CannotDeploy', ); @@ -879,7 +875,7 @@ describe('Interchain Token Service', () => { it('Should revert on deploying a local token manager with interchain token manager type', async () => { await expectRevert( (gasOptions) => - service.linkToken(salt, '', token.address, NATIVE_INTERCHAIN_TOKEN, autoScaling, wallet.address, 0, gasOptions), + service.linkToken(salt, '', token.address, NATIVE_INTERCHAIN_TOKEN, wallet.address, 0, gasOptions), service, 'CannotDeploy', [NATIVE_INTERCHAIN_TOKEN], @@ -894,7 +890,6 @@ describe('Interchain Token Service', () => { destinationChain, token.address, NATIVE_INTERCHAIN_TOKEN, - autoScaling, wallet.address, 0, gasOptions, @@ -907,7 +902,7 @@ describe('Interchain Token Service', () => { it('Should revert on deploying a token manager if token handler post deploy fails', async () => { await expectRevert( - (gasOptions) => service.linkToken(salt, '', AddressZero, LOCK_UNLOCK, autoScaling, wallet.address, 0, gasOptions), + (gasOptions) => service.linkToken(salt, '', AddressZero, LOCK_UNLOCK, wallet.address, 0, gasOptions), service, 'PostDeployFailed', ); @@ -919,7 +914,7 @@ describe('Interchain Token Service', () => { await expect( reportGas( - service.linkToken(salt, '', token.address, LOCK_UNLOCK, autoScaling, wallet.address, 0), + service.linkToken(salt, '', token.address, LOCK_UNLOCK, wallet.address, 0), 'Call deployTokenManager on source chain', ), ) @@ -949,7 +944,7 @@ describe('Interchain Token Service', () => { it('Should revert when deploying a custom token manager twice', async () => { const revertData = keccak256(toUtf8Bytes('AlreadyDeployed()')).substring(0, 10); await expectRevert( - (gasOptions) => service.linkToken(salt, '', token.address, LOCK_UNLOCK, autoScaling, wallet.address, 0, gasOptions), + (gasOptions) => service.linkToken(salt, '', token.address, LOCK_UNLOCK, wallet.address, 0, gasOptions), service, 'TokenManagerDeploymentFailed', [revertData], @@ -985,7 +980,7 @@ describe('Interchain Token Service', () => { ]); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - const tx = service.linkToken(salt, '', token.address, MINT_BURN, autoScaling, wallet.address, 0); + const tx = service.linkToken(salt, '', token.address, MINT_BURN, wallet.address, 0); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); await expect(tx).to.emit(service, 'TokenManagerDeployed').withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); @@ -1020,7 +1015,7 @@ describe('Interchain Token Service', () => { ]); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - const tx = service.linkToken(salt, '', token.address, MINT_BURN_FROM, autoScaling, wallet.address, 0); + const tx = service.linkToken(salt, '', token.address, MINT_BURN_FROM, wallet.address, 0); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); await expect(tx) .to.emit(service, 'TokenManagerDeployed') @@ -1057,7 +1052,7 @@ describe('Interchain Token Service', () => { ]); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - const tx = service.linkToken(salt, '', token.address, LOCK_UNLOCK_FEE_ON_TRANSFER, autoScaling, wallet.address, 0); + const tx = service.linkToken(salt, '', token.address, LOCK_UNLOCK_FEE_ON_TRANSFER, wallet.address, 0); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); await expect(tx) .to.emit(service, 'TokenManagerDeployed') @@ -1085,7 +1080,7 @@ describe('Interchain Token Service', () => { await service.setPauseStatus(true).then((tx) => tx.wait); await expectRevert( - (gasOptions) => service.linkToken(salt, '', token.address, LOCK_UNLOCK, autoScaling, wallet.address, 0, gasOptions), + (gasOptions) => service.linkToken(salt, '', token.address, LOCK_UNLOCK, wallet.address, 0, gasOptions), service, 'Pause', ); @@ -1095,21 +1090,20 @@ describe('Interchain Token Service', () => { }); 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.linkToken(salt, '', tokenAddress, MINT_BURN, autoScaling, '0x', 0)).wait(); + await (await service.linkToken(salt, '', tokenAddress, MINT_BURN, '0x', 0)).wait(); const tokenId = await service.interchainTokenId(wallet.address, salt); const remoteTokenAddress = '0x1234'; const minter = '0x5789'; const type = LOCK_UNLOCK; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], - [MESSAGE_TYPE_LINK_TOKEN, tokenId, type, tokenAddress, remoteTokenAddress, autoScaling, minter], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, type, tokenAddress, remoteTokenAddress, minter], ); const tokenManager = await getContractAt('TokenManager', await service.deployedTokenManager(tokenId), wallet); @@ -1120,7 +1114,7 @@ describe('Interchain Token Service', () => { await expect( reportGas( - service.linkToken(salt, destinationChain, remoteTokenAddress, type, autoScaling, minter, gasValue, { value: gasValue }), + service.linkToken(salt, destinationChain, remoteTokenAddress, type, minter, gasValue, { value: gasValue }), 'Send deployTokenManager to remote chain', ), ) @@ -1133,7 +1127,6 @@ describe('Interchain Token Service', () => { tokenAddress.toLowerCase(), remoteTokenAddress.toLowerCase(), type, - autoScaling, minter.toLowerCase(), ) .and.to.emit(gasService, 'NativeGasPaidForContractCall') @@ -1147,11 +1140,10 @@ describe('Interchain Token Service', () => { const tokenId = await service.interchainTokenId(wallet.address, salt); const tokenAddress = '0x1234'; const minter = '0x5678'; - const autoScaling = true; const type = LOCK_UNLOCK; await expect( - service.linkToken(salt, destinationChain, tokenAddress, type, autoScaling, minter, gasValue, { value: gasValue }), + service.linkToken(salt, destinationChain, tokenAddress, type, minter, gasValue, { value: gasValue }), ).to.be.revertedWithCustomError(service, 'TokenManagerDoesNotExist', [tokenId]); }); @@ -1161,12 +1153,11 @@ describe('Interchain Token Service', () => { const salt = getRandomBytes32(); const tokenAddress = '0x1234'; const minter = '0x5678'; - const autoScaling = true; const type = LOCK_UNLOCK; await expectRevert( (gasOptions) => - service.linkToken(salt, destinationChain, tokenAddress, type, autoScaling, minter, gasValue, { + service.linkToken(salt, destinationChain, tokenAddress, type, minter, gasValue, { ...gasOptions, value: gasValue, }), @@ -1193,7 +1184,6 @@ describe('Interchain Token Service', () => { 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', [ @@ -1206,8 +1196,8 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], - [MESSAGE_TYPE_LINK_TOKEN, tokenId, tokenManagerType, sourceTokenAddress, token.address, autoScaling, minter], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, tokenManagerType, sourceTokenAddress, token.address, minter], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); @@ -1226,7 +1216,6 @@ describe('Interchain Token Service', () => { 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', [ @@ -1239,8 +1228,8 @@ describe('Interchain Token Service', () => { const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], - [MESSAGE_TYPE_LINK_TOKEN, tokenId, tokenManagerType, sourceTokenAddress, token.address, autoScaling, minter], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, tokenManagerType, sourceTokenAddress, token.address, minter], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -1257,7 +1246,6 @@ describe('Interchain Token Service', () => { const tokenId = getRandomBytes32(); const tokenManagerType = NATIVE_INTERCHAIN_TOKEN; const sourceTokenAddress = '0x1234'; - const autoScaling = true; const minter = wallet.address; const token = await deployContract(wallet, 'TestInterchainTokenStandard', [ @@ -1269,8 +1257,8 @@ describe('Interchain Token Service', () => { ]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], - [MESSAGE_TYPE_LINK_TOKEN, tokenId, tokenManagerType, sourceTokenAddress, token.address, autoScaling, minter], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, tokenManagerType, sourceTokenAddress, token.address, minter], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -1330,7 +1318,6 @@ describe('Interchain Token Service', () => { 'TT', 12, amount, - false, 'reentrant', ); @@ -2141,9 +2128,8 @@ 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.linkToken(salt, '', tokenAddress, MINT_BURN, autoScaling, wallet.address, 0)).wait(); + await (await service.linkToken(salt, '', tokenAddress, MINT_BURN, wallet.address, 0)).wait(); const tokenId = await service.interchainTokenId(wallet.address, salt); const remoteTokenAddress = '0x1234'; @@ -2151,8 +2137,8 @@ describe('Interchain Token Service', () => { const type = LOCK_UNLOCK; const sourceChain = 'hub chain 1'; const itsMessage = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], - [MESSAGE_TYPE_LINK_TOKEN, tokenId, type, tokenAddress, remoteTokenAddress, autoScaling, minter], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, type, tokenAddress, remoteTokenAddress, minter], ); const payload = defaultAbiCoder.encode( ['uint256', 'string', 'bytes'], diff --git a/test/InterchainTokenServiceFullFlow.js b/test/InterchainTokenServiceFullFlow.js index 2722e5ce..3dbf4ac2 100644 --- a/test/InterchainTokenServiceFullFlow.js +++ b/test/InterchainTokenServiceFullFlow.js @@ -342,7 +342,6 @@ 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]); @@ -356,7 +355,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.linkToken(salt, '', token.address, MINT_BURN, autoScaling, wallet.address, 0); + let tx = await service.populateTransaction.linkToken(salt, '', token.address, MINT_BURN, wallet.address, 0); const calls = [tx.data]; let value = 0; @@ -370,7 +369,6 @@ describe('Interchain Token Service Full Flow', () => { otherChains[i], remoteTokenAddress, MINT_BURN, - autoScaling, wallet.address, gasValues[i], ); @@ -379,8 +377,8 @@ describe('Interchain Token Service Full Flow', () => { } const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bool', 'bytes'], - [MESSAGE_TYPE_LINK_TOKEN, tokenId, MINT_BURN, token.address, token.address, autoScaling, wallet.address], + ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bytes'], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, MINT_BURN, token.address, token.address, wallet.address], ); const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); @@ -394,7 +392,6 @@ describe('Interchain Token Service Full Flow', () => { token.address.toLowerCase(), token.address.toLowerCase(), MINT_BURN, - autoScaling, wallet.address.toLowerCase(), ) .and.to.emit(gasService, 'NativeGasPaidForContractCall') @@ -408,7 +405,6 @@ describe('Interchain Token Service Full Flow', () => { token.address.toLowerCase(), token.address.toLowerCase(), MINT_BURN, - autoScaling, wallet.address.toLowerCase(), ) .and.to.emit(gasService, 'NativeGasPaidForContractCall') diff --git a/test/InterchainTokenServiceUpgradeFlow.js b/test/InterchainTokenServiceUpgradeFlow.js index 670df9c3..69befd4e 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.linkToken(salt, '', token.address, MINT_BURN, true, wallet.address, 0)) + await expect(service.linkToken(salt, '', token.address, MINT_BURN, wallet.address, 0)) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManager.address, MINT_BURN, params); } From fadcba7f3cb7e4a105a3ade24881a7fbdf01fd8f Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 9 Jan 2025 14:04:25 +0200 Subject: [PATCH 04/15] prettier --- contracts/InterchainTokenService.sol | 9 +-------- test/InterchainTokenService.js | 23 +++-------------------- 2 files changed, 4 insertions(+), 28 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index c4d58c98..08d0183e 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -921,14 +921,7 @@ contract InterchainTokenService is // slither-disable-next-line unused-return bytes memory sourceTokenAddress = registeredTokenAddress(tokenId).toBytes(); - emit LinkTokenStarted( - tokenId, - destinationChain, - sourceTokenAddress, - destinationTokenAddress, - tokenManagerType, - params - ); + emit LinkTokenStarted(tokenId, destinationChain, sourceTokenAddress, destinationTokenAddress, tokenManagerType, params); bytes memory payload = abi.encode( MESSAGE_TYPE_LINK_TOKEN, diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index cda9f10e..67987e60 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -874,8 +874,7 @@ describe('Interchain Token Service', () => { it('Should revert on deploying a local token manager with interchain token manager type', async () => { await expectRevert( - (gasOptions) => - service.linkToken(salt, '', token.address, NATIVE_INTERCHAIN_TOKEN, wallet.address, 0, gasOptions), + (gasOptions) => service.linkToken(salt, '', token.address, NATIVE_INTERCHAIN_TOKEN, wallet.address, 0, gasOptions), service, 'CannotDeploy', [NATIVE_INTERCHAIN_TOKEN], @@ -885,15 +884,7 @@ describe('Interchain Token Service', () => { it('Should revert on deploying a remote token manager with interchain token manager type', async () => { await expectRevert( (gasOptions) => - service.linkToken( - salt, - destinationChain, - token.address, - NATIVE_INTERCHAIN_TOKEN, - wallet.address, - 0, - gasOptions, - ), + service.linkToken(salt, destinationChain, token.address, NATIVE_INTERCHAIN_TOKEN, wallet.address, 0, gasOptions), service, 'CannotDeploy', [NATIVE_INTERCHAIN_TOKEN], @@ -1090,7 +1081,6 @@ describe('Interchain Token Service', () => { }); describe('Initialize remote custom token manager deployment', () => { - it('Should initialize a remote custom token manager deployment', async () => { const salt = getRandomBytes32(); const tokenAddress = wallet.address; @@ -1312,14 +1302,7 @@ describe('Interchain Token Service', () => { }); it('Should revert on initiating an interchain token transfer for lockUnlockFee with reentrant token', async () => { - const [, , tokenId] = await deployFunctions.lockUnlockFee( - service, - 'Test Token lockUnlockFee', - 'TT', - 12, - amount, - 'reentrant', - ); + const [, , tokenId] = await deployFunctions.lockUnlockFee(service, 'Test Token lockUnlockFee', 'TT', 12, amount, 'reentrant'); const revertData = keccak256(toUtf8Bytes('TokenTransferFailed()')).substring(0, 10); From 90599c5a37694f46f3cc535138b030400b263cdf Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 10 Jan 2025 17:55:54 +0200 Subject: [PATCH 05/15] Update contracts/InterchainTokenService.sol Co-authored-by: Milap Sheth --- contracts/InterchainTokenService.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 08d0183e..b0cda7ed 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -80,7 +80,7 @@ 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; From bc83522d804b0cf11eb365efd7547feb910490c8 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 10 Jan 2025 18:01:58 +0200 Subject: [PATCH 06/15] add some docstrings --- contracts/InterchainTokenService.sol | 11 +++++++++++ contracts/interfaces/IInterchainTokenService.sol | 15 ++++++++------- 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index b0cda7ed..d2e9f46b 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -312,6 +312,17 @@ contract InterchainTokenService is if (!success) revert GatewayCallFailed(returnData); } + /** + * @notice This replaces the old deployTokenManager function. + * It can either deploy token managers on this chain, if an empty string is provided as the destinationChain, or link an existing token registered to another chain. + * @param salt A unique identifier to allow for multiple tokens registered per deployer. + * @param destinationChain The chain to link the token to. Pass an empty string for this chain. + * @param destinationTokenAddress The token address to link, as bytes. + * @param tokenManagerType The type of the token manager to use to send and receive tokens. + * @param linkParams Additional parameteres to use to link the token. Fow not it is just the address of the operator. + * @param gasValue Pass a non-zero value only for remote linking, which should be the gas to use to pay for the contract call. + * @return tokenId The tokenId associated with the token manager. + */ function linkToken( bytes32 salt, string calldata destinationChain, diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 5b40518f..7de35e07 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -179,13 +179,14 @@ interface IInterchainTokenService is 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 linkParams The link parameters. - * @param gasValue The gas value for deployment. + * @notice This replaces the old deployTokenManager function. + * It can either deploy token managers on this chain, if an empty string is provided as the destinationChain, or link an existing token registered to another chain. + * @param salt A unique identifier to allow for multiple tokens registered per deployer. + * @param destinationChain The chain to link the token to. Pass an empty string for this chain. + * @param destinationTokenAddress The token address to link, as bytes. + * @param tokenManagerType The type of the token manager to use to send and receive tokens. + * @param linkParams Additional parameteres to use to link the token. Fow not it is just the address of the operator. + * @param gasValue Pass a non-zero value only for remote linking, which should be the gas to use to pay for the contract call. * @return tokenId The tokenId associated with the token manager. */ function linkToken( From c387d9819cffc400d8bc96df112e04dcf344bc5e Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 10 Jan 2025 18:02:30 +0200 Subject: [PATCH 07/15] Update contracts/InterchainTokenService.sol Co-authored-by: Milap Sheth --- contracts/InterchainTokenService.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index d2e9f46b..1522ec99 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -333,7 +333,6 @@ contract InterchainTokenService is ) 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); From bc063392753fb1abb2e051b94be61319fa1b21f3 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 10 Jan 2025 18:11:07 +0200 Subject: [PATCH 08/15] addressed most comments --- contracts/InterchainTokenService.sol | 15 ++++++--------- hardhat.config.js | 13 ++++++++++++- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 1522ec99..788f54dc 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -326,9 +326,9 @@ contract InterchainTokenService is function linkToken( bytes32 salt, string calldata destinationChain, - bytes memory destinationTokenAddress, + bytes calldata destinationTokenAddress, TokenManagerType tokenManagerType, - bytes memory linkParams, + bytes calldata linkParams, uint256 gasValue ) public payable whenNotPaused returns (bytes32 tokenId) { if (destinationTokenAddress.length == 0) revert EmptyDestinationAddress(); @@ -338,7 +338,7 @@ contract InterchainTokenService is address deployer = msg.sender; - if (msg.sender == interchainTokenFactory) { + 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 @@ -900,9 +900,6 @@ contract InterchainTokenService is // Get message type of the inner ITS message messageType = _getMessageType(payload); - - // 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(); @@ -923,9 +920,9 @@ contract InterchainTokenService is function _linkToken( bytes32 tokenId, string calldata destinationChain, - bytes memory destinationTokenAddress, + bytes calldata destinationTokenAddress, TokenManagerType tokenManagerType, - bytes memory params, + bytes calldata params, uint256 gasValue ) internal { // slither-disable-next-line unused-return @@ -978,7 +975,7 @@ 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. diff --git a/hardhat.config.js b/hardhat.config.js index 562c8142..38c2169e 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -45,6 +45,16 @@ const compilerSettings = { optimizer: optimizerSettings, }, }; +const itsCompilerService = { + version: '0.8.21', + settings: { + evmVersion: process.env.EVM_VERSION || 'london', + optimizer: { + ...optimizerSettings, + runs: 100, + }, + }, +}; /** * @type import('hardhat/config').HardhatUserConfig @@ -59,7 +69,8 @@ module.exports = { 'contracts/proxies/InterchainProxy.sol': fixedContractCompilerSettings, 'contracts/proxies/TokenManagerProxy.sol': fixedContractCompilerSettings, 'contracts/interchain-token/InterchainToken.sol': fixedContractCompilerSettings, - 'contracts/test/TestInterchainTokenService.sol': fixedContractCompilerSettings, + 'contracts/test/TestInterchainTokenService.sol': itsCompilerService, + 'contracts/InterchainTokenService.sol': itsCompilerService, }, }, defaultNetwork: 'hardhat', From f088c83afdbdf47d39c1b5b85b7c60814a07e986 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 10 Jan 2025 18:11:18 +0200 Subject: [PATCH 09/15] Update test/InterchainTokenService.js Co-authored-by: Milap Sheth --- test/InterchainTokenService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 67987e60..f87bbd00 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -1085,7 +1085,7 @@ describe('Interchain Token Service', () => { const salt = getRandomBytes32(); const tokenAddress = wallet.address; - await (await service.linkToken(salt, '', tokenAddress, MINT_BURN, '0x', 0)).wait(); + await service.linkToken(salt, '', tokenAddress, MINT_BURN, '0x', 0).then((tx) => tx.wait); const tokenId = await service.interchainTokenId(wallet.address, salt); const remoteTokenAddress = '0x1234'; From 46c4ab175a46c0dd0e1d9dcdb7d5e7e9f02c8b9c Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 10 Jan 2025 18:30:43 +0200 Subject: [PATCH 10/15] fixed tests --- test/InterchainTokenService.js | 44 ++++++++++++++++++++++++---------- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index f87bbd00..b17e3813 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -2108,38 +2108,58 @@ 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; + it('Should receive a message wrapped with RECEIVE_FROM_HUB and has MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER type.', async () => { + const tokenName = 'Token Name'; + const tokenSymbol = 'TS'; + const tokenDecimals = 53; + const tokenId = getRandomBytes32(); - await (await service.linkToken(salt, '', tokenAddress, MINT_BURN, wallet.address, 0)).wait(); + const token = await deployContract(wallet, 'TestInterchainTokenStandard', [ + tokenName, + tokenSymbol, + tokenDecimals, + service.address, + tokenId, + ]); + + const tokenManagerAddress = await service.tokenManagerAddress(tokenId); + const tokenManagerType = LOCK_UNLOCK; + const minter = wallet.address; + + const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, token.address]); - const tokenId = await service.interchainTokenId(wallet.address, salt); const remoteTokenAddress = '0x1234'; - const minter = '0x5678'; const type = LOCK_UNLOCK; const sourceChain = 'hub chain 1'; const itsMessage = defaultAbiCoder.encode( ['uint256', 'bytes32', 'uint256', 'bytes', 'bytes', 'bytes'], - [MESSAGE_TYPE_LINK_TOKEN, tokenId, type, tokenAddress, remoteTokenAddress, minter], + [MESSAGE_TYPE_LINK_TOKEN, tokenId, type, remoteTokenAddress, token.address, minter], ); const payload = defaultAbiCoder.encode( ['uint256', 'string', 'bytes'], [MESSAGE_TYPE_RECEIVE_FROM_HUB, sourceChain, itsMessage], ); const commandId = await approveContractCall(gateway, ITS_HUB_CHAIN_NAME, ITS_HUB_ADDRESS, service.address, payload); + const expectedTokenManagerAddress = await service.tokenManagerAddress(tokenId); + + await expect(service.setTrustedAddress(ITS_HUB_CHAIN_NAME, ITS_HUB_ADDRESS)) + .to.emit(service, 'TrustedAddressSet') + .withArgs(ITS_HUB_CHAIN_NAME, ITS_HUB_ADDRESS); await expect(service.setTrustedAddress(sourceChain, ITS_HUB_ROUTING_IDENTIFIER)) .to.emit(service, 'TrustedAddressSet') .withArgs(sourceChain, ITS_HUB_ROUTING_IDENTIFIER); - await expectRevert( - (gasOptions) => service.execute(commandId, ITS_HUB_CHAIN_NAME, ITS_HUB_ADDRESS, payload, gasOptions), - service, - 'NotSupported', - ); + await expect(reportGas(service.execute(commandId, ITS_HUB_CHAIN_NAME, ITS_HUB_ADDRESS, payload), 'Receive GMP DEPLOY_TOKEN_MANAGER')) + .to.emit(service, 'TokenManagerDeployed') + .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; }); + it('Should revert with UntrustedChain when receiving a direct message from the ITS Hub. Not supported yet', async () => { const data = '0x'; const payload = defaultAbiCoder.encode(['uint256', 'bytes'], [MESSAGE_TYPE_INTERCHAIN_TRANSFER, data]); From 106c02dbdb0e854c80ce7471f055c31641159f36 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 10 Jan 2025 18:30:55 +0200 Subject: [PATCH 11/15] prettier --- test/InterchainTokenService.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index b17e3813..b560c697 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -2150,7 +2150,9 @@ describe('Interchain Token Service', () => { .to.emit(service, 'TrustedAddressSet') .withArgs(sourceChain, ITS_HUB_ROUTING_IDENTIFIER); - await expect(reportGas(service.execute(commandId, ITS_HUB_CHAIN_NAME, ITS_HUB_ADDRESS, payload), 'Receive GMP DEPLOY_TOKEN_MANAGER')) + await expect( + reportGas(service.execute(commandId, ITS_HUB_CHAIN_NAME, ITS_HUB_ADDRESS, payload), 'Receive GMP DEPLOY_TOKEN_MANAGER'), + ) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, expectedTokenManagerAddress, tokenManagerType, params); @@ -2159,7 +2161,6 @@ describe('Interchain Token Service', () => { expect(await tokenManager.hasRole(wallet.address, OPERATOR_ROLE)).to.be.true; }); - it('Should revert with UntrustedChain when receiving a direct message from the ITS Hub. Not supported yet', async () => { const data = '0x'; const payload = defaultAbiCoder.encode(['uint256', 'bytes'], [MESSAGE_TYPE_INTERCHAIN_TRANSFER, data]); From b1ea878749e753d2f17b8be4340b8f0fb1faca99 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Sun, 12 Jan 2025 19:38:55 -0500 Subject: [PATCH 12/15] Apply suggestions from code review --- hardhat.config.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/hardhat.config.js b/hardhat.config.js index 38c2169e..992fe52b 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -45,8 +45,8 @@ const compilerSettings = { optimizer: optimizerSettings, }, }; -const itsCompilerService = { - version: '0.8.21', +const itsCompilerSettings = { + version: '0.8.27', settings: { evmVersion: process.env.EVM_VERSION || 'london', optimizer: { @@ -69,8 +69,8 @@ module.exports = { 'contracts/proxies/InterchainProxy.sol': fixedContractCompilerSettings, 'contracts/proxies/TokenManagerProxy.sol': fixedContractCompilerSettings, 'contracts/interchain-token/InterchainToken.sol': fixedContractCompilerSettings, - 'contracts/test/TestInterchainTokenService.sol': itsCompilerService, - 'contracts/InterchainTokenService.sol': itsCompilerService, + 'contracts/test/TestInterchainTokenService.sol': itsCompilerSettings, + 'contracts/InterchainTokenService.sol': itsCompilerSettings, }, }, defaultNetwork: 'hardhat', From ef617861a4b2386619dde00fa50700ed96be9e8f Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 13 Jan 2025 01:22:03 -0500 Subject: [PATCH 13/15] address comments --- contracts/InterchainTokenFactory.sol | 4 +++- contracts/InterchainTokenService.sol | 9 ++++++--- contracts/interfaces/IInterchainTokenService.sol | 7 +++++-- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index 12248b91..238fab0e 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -404,6 +404,8 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M 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 @@ -415,7 +417,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M currentChain, tokenAddress.toBytes(), TokenManagerType.LOCK_UNLOCK, - '', + linkParams, gasValue ); } diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 788f54dc..5fd476f2 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -284,8 +284,9 @@ contract InterchainTokenService is /** * @notice Registers metadata for a token on the ITS Hub. This metadata is used for scaling linked tokens. + * The token metadata must be registered before linkToken can be called for the corresponding token. * @param tokenAddress The address of the token. - * @param gasValue The gas value for deployment. + * @param gasValue The cross-chain gas value for sending the registration message to ITS Hub. */ function registerTokenMetadata(address tokenAddress, uint256 gasValue) external payable { if (tokenAddress == address(0)) revert ZeroAddress(); @@ -313,8 +314,10 @@ contract InterchainTokenService is } /** - * @notice This replaces the old deployTokenManager function. - * It can either deploy token managers on this chain, if an empty string is provided as the destinationChain, or link an existing token registered to another chain. + * @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. + * A token manager is deployed on EVM chains that's responsible for managing the linked token. + * @dev This function replaces the prior `deployTokenManager` function. * @param salt A unique identifier to allow for multiple tokens registered per deployer. * @param destinationChain The chain to link the token to. Pass an empty string for this chain. * @param destinationTokenAddress The token address to link, as bytes. diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 7de35e07..558195cb 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -173,14 +173,17 @@ interface IInterchainTokenService is /** * @notice Registers metadata for a token on the ITS Hub. This metadata is used for scaling linked tokens. + * The token metadata must be registered before linkToken can be called for the corresponding token. * @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 This replaces the old deployTokenManager function. - * It can either deploy token managers on this chain, if an empty string is provided as the destinationChain, or link an existing token registered to another chain. + * @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. + * A token manager is deployed on EVM chains that's responsible for managing the linked token. + * @dev This function replaces the prior `deployTokenManager` function. * @param salt A unique identifier to allow for multiple tokens registered per deployer. * @param destinationChain The chain to link the token to. Pass an empty string for this chain. * @param destinationTokenAddress The token address to link, as bytes. From 7ce08866f686dbf75bf414dbbdce9a0dff9133f5 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 13 Jan 2025 02:02:09 -0500 Subject: [PATCH 14/15] add tests for register token metadata --- contracts/InterchainTokenService.sol | 6 ++- .../interfaces/IInterchainTokenService.sol | 2 + test/InterchainTokenService.js | 42 +++++++++++++++++++ 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 5fd476f2..7be75417 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -289,7 +289,7 @@ contract InterchainTokenService is * @param gasValue The cross-chain gas value for sending the registration message to ITS Hub. */ function registerTokenMetadata(address tokenAddress, uint256 gasValue) external payable { - if (tokenAddress == address(0)) revert ZeroAddress(); + if (tokenAddress == address(0)) revert EmptyTokenAddress(); uint8 decimals = IERC20Named(tokenAddress).decimals(); @@ -299,6 +299,8 @@ contract InterchainTokenService is // Check whether no trusted address was set for ITS Hub chain if (bytes(destinationAddress).length == 0) revert UntrustedChain(); + emit TokenMetadataRegistered(tokenAddress, decimals); + (bool success, bytes memory returnData) = gatewayCaller.delegatecall( abi.encodeWithSelector( IGatewayCaller.callContract.selector, @@ -334,7 +336,7 @@ contract InterchainTokenService is bytes calldata linkParams, uint256 gasValue ) public payable whenNotPaused returns (bytes32 tokenId) { - if (destinationTokenAddress.length == 0) revert EmptyDestinationAddress(); + if (destinationTokenAddress.length == 0) revert EmptyTokenAddress(); // 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); diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 558195cb..7cc70e2c 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -53,6 +53,7 @@ interface IInterchainTokenService is error EmptyTokenSymbol(); error EmptyParams(); error EmptyDestinationAddress(); + error EmptyTokenAddress(); error NotSupported(); event InterchainTransfer( @@ -72,6 +73,7 @@ interface IInterchainTokenService is uint256 amount, bytes32 dataHash ); + event TokenMetadataRegistered(address indexed tokenAddress, uint8 decimals); event LinkTokenStarted( bytes32 indexed tokenId, string destinationChain, diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index b560c697..ab47c5e0 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -31,6 +31,7 @@ const { ITS_HUB_CHAIN_NAME, ITS_HUB_ROUTING_IDENTIFIER, ITS_HUB_ADDRESS, + MESSAGE_TYPE_REGISTER_TOKEN_METADATA, } = require('./constants'); const reportGas = gasReporter('Interchain Token Service'); @@ -841,6 +842,39 @@ describe('Interchain Token Service', () => { }); }); + describe('Register Token Metadata', () => { + const decimals = 18; + let token; + + before(async () => { + token = await deployContract(wallet, 'TestInterchainTokenStandard', ['Test', 'TEST', decimals, service.address, HashZero]); + }); + + it('Should revert on registering token metadata with empty token address', async () => { + await expectRevert((gasOptions) => service.registerTokenMetadata(AddressZero, 0, gasOptions), service, 'EmptyTokenAddress'); + }); + + it('Should revert if ITS Hub chain is not trusted', async () => { + await expectRevert((gasOptions) => service.registerTokenMetadata(token.address, 0, gasOptions), service, 'UntrustedChain'); + }); + + it('Should successfully register token metadata', async () => { + const gasValue = 0; + const expectedPayload = defaultAbiCoder.encode( + ['uint256', 'bytes', 'uint8'], + [MESSAGE_TYPE_REGISTER_TOKEN_METADATA, token.address, decimals], + ); + + await service.setTrustedAddress(ITS_HUB_CHAIN_NAME, ITS_HUB_ADDRESS).then((tx) => tx.wait); + + await expect(reportGas(service.registerTokenMetadata(token.address, gasValue), 'registerTokenMetadata')) + .to.emit(service, 'TokenMetadataRegistered') + .withArgs(token.address, decimals) + .to.emit(gateway, 'ContractCall') + .withArgs(service.address, ITS_HUB_CHAIN_NAME, ITS_HUB_ADDRESS, keccak256(expectedPayload), expectedPayload); + }); + }); + describe('Custom Token Manager Deployment', () => { const tokenName = 'Token Name'; const tokenSymbol = 'TN'; @@ -881,6 +915,14 @@ describe('Interchain Token Service', () => { ); }); + it('Should revert on linking a token with empty token address', async () => { + await expectRevert( + (gasOptions) => service.linkToken(salt, '', '0x', MINT_BURN, wallet.address, 0, gasOptions), + service, + 'EmptyTokenAddress', + ); + }); + it('Should revert on deploying a remote token manager with interchain token manager type', async () => { await expectRevert( (gasOptions) => From 537adce1d171a919596989702167ada5e888e048 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Mon, 13 Jan 2025 02:11:52 -0500 Subject: [PATCH 15/15] cleanup call contract helper --- contracts/InterchainTokenService.sol | 55 ++++++++++++++++------------ 1 file changed, 32 insertions(+), 23 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 7be75417..7fdd4b7f 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -295,24 +295,15 @@ contract InterchainTokenService is 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(); - emit TokenMetadataRegistered(tokenAddress, decimals); - (bool success, bytes memory returnData) = gatewayCaller.delegatecall( - abi.encodeWithSelector( - IGatewayCaller.callContract.selector, - ITS_HUB_CHAIN_NAME, - destinationAddress, - payload, - IGatewayCaller.MetadataVersion.CONTRACT_CALL, - gasValue - ) + _callContract( + ITS_HUB_CHAIN_NAME, + trustedAddress(ITS_HUB_CHAIN_NAME), + payload, + IGatewayCaller.MetadataVersion.CONTRACT_CALL, + gasValue ); - - if (!success) revert GatewayCallFailed(returnData); } /** @@ -792,14 +783,14 @@ contract InterchainTokenService is } /** - * @notice Calls a contract on a specific destination chain with the given payload + * @notice Route the ITS message to the destination chain with the given payload * @dev This method also determines whether the ITS call should be routed via the ITS Hub. * If the `trustedAddress(destinationChain) == 'hub'`, then the call is wrapped and routed to the ITS Hub destination. * @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. */ - function _callContract( + function _routeMessage( string memory destinationChain, bytes memory payload, IGatewayCaller.MetadataVersion metadataVersion, @@ -809,6 +800,27 @@ contract InterchainTokenService is (destinationChain, destinationAddress, payload) = _getCallParams(destinationChain, payload); + _callContract(destinationChain, destinationAddress, payload, metadataVersion, gasValue); + } + + /** + * @notice Calls a contract on a destination chain via the gateway caller. + * @param destinationChain The chain where the contract will be called. + * @param destinationAddress The address of the contract to call. + * @param payload The data payload for the transaction. + * @param metadataVersion The version of the metadata. + * @param gasValue The amount of gas to be paid for the transaction. + */ + function _callContract( + string memory destinationChain, + string memory destinationAddress, + bytes memory payload, + IGatewayCaller.MetadataVersion metadataVersion, + uint256 gasValue + ) internal { + // Check whether no trusted address was set for the destination chain + if (bytes(destinationAddress).length == 0) revert UntrustedChain(); + (bool success, bytes memory returnData) = gatewayCaller.delegatecall( abi.encodeWithSelector( IGatewayCaller.callContract.selector, @@ -843,9 +855,6 @@ contract InterchainTokenService is destinationAddress = trustedAddress(ITS_HUB_CHAIN_NAME); } - // Check whether no trusted address was set for the destination chain - if (bytes(destinationAddress).length == 0) revert UntrustedChain(); - return (destinationChain, destinationAddress, payload); } @@ -944,7 +953,7 @@ contract InterchainTokenService is params ); - _callContract(destinationChain, payload, IGatewayCaller.MetadataVersion.CONTRACT_CALL, gasValue); + _routeMessage(destinationChain, payload, IGatewayCaller.MetadataVersion.CONTRACT_CALL, gasValue); } /** @@ -977,7 +986,7 @@ contract InterchainTokenService is bytes memory payload = abi.encode(MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, tokenId, name, symbol, decimals, minter); - _callContract(destinationChain, payload, IGatewayCaller.MetadataVersion.CONTRACT_CALL, gasValue); + _routeMessage(destinationChain, payload, IGatewayCaller.MetadataVersion.CONTRACT_CALL, gasValue); } /** @@ -1120,7 +1129,7 @@ contract InterchainTokenService is data ); - _callContract(destinationChain, payload, metadataVersion, gasValue); + _routeMessage(destinationChain, payload, metadataVersion, gasValue); } /**