From 8e0c2904b900ea6aaf60f62869031b177355e9c3 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 6 Jan 2025 16:09:59 +0200 Subject: [PATCH 1/4] Added some functions to migrate tokens --- contracts/InterchainTokenService.sol | 24 +++++++++++++++++++ .../interfaces/IInterchainTokenService.sol | 14 +++++++++++ 2 files changed, 38 insertions(+) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 7c83b429..f376ca20 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -20,6 +20,7 @@ import { IInterchainTokenExecutable } from './interfaces/IInterchainTokenExecuta import { IInterchainTokenExpressExecutable } from './interfaces/IInterchainTokenExpressExecutable.sol'; import { ITokenManager } from './interfaces/ITokenManager.sol'; import { IGatewayCaller } from './interfaces/IGatewayCaller.sol'; +import { IMinter } from './interfaces/IMinter.sol'; import { Create3AddressFixed } from './utils/Create3AddressFixed.sol'; import { Operator } from './utils/Operator.sol'; @@ -623,6 +624,29 @@ contract InterchainTokenService is } } + /** + * @notice Allows the owner to migrate legacy tokens that cannot be migrated automatically. + * @param tokenId the tokenId of the registered token. + * @param data the data to pass to migrate the token. + */ + function migrateLegacyToken(bytes32 tokenId, bytes calldata data) external onlyOwner { + address tokenAddress = registeredTokenAddress(tokenId); + (bool success, bytes memory returnData) = tokenAddress.call(data); + if (!success) { + revert TokenMigrateFailed(returnData); + } + } + + /** + * @notice Allows the owner to migrate legacy tokens that cannot be migrated automatically. + * @param tokenId the tokenId of the registered token. + */ + function migrateInterchainToken(bytes32 tokenId) external onlyOwner { + ITokenManager tokenManager_ = deployedTokenManager(tokenId); + address tokenAddress = tokenManager_.tokenAddress(); + IMinter(tokenAddress).transferMintership(address(tokenManager_)); + } + /****************\ INTERNAL FUNCTIONS \****************/ diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index b3f5fc12..65d7b011 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -55,6 +55,7 @@ interface IInterchainTokenService is error EmptyParams(); error EmptyDestinationAddress(); error NotSupported(); + error TokenMigrateFailed(bytes returnData); event InterchainTransfer( bytes32 indexed tokenId, @@ -275,4 +276,17 @@ interface IInterchainTokenService is * @param paused whether to pause or unpause. */ function setPauseStatus(bool paused) external; + + /** + * @notice Allows the owner to migrate legacy tokens that cannot be migrated automatically. + * @param tokenId the tokenId of the registered token. + * @param data the data to pass to migrate the token. + */ + function migrateLegacyToken(bytes32 tokenId, bytes calldata data) external; + + /** + * @notice Allows the owner to migrate legacy tokens that cannot be migrated automatically. + * @param tokenId the tokenId of the registered token. + */ + function migrateInterchainToken(bytes32 tokenId) external; } From a8338448bafd10446cf0b2acb7a7a020a15e5f84 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 9 Jan 2025 16:13:06 +0200 Subject: [PATCH 2/4] 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 9857b945..bf6f205e 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -658,7 +658,7 @@ contract InterchainTokenService is } /** - * @notice Allows the owner to migrate legacy tokens that cannot be migrated automatically. + * @notice Allows the owner to migrate minter of native interchain tokens from ITS to the corresponding token manager. * @param tokenId the tokenId of the registered token. */ function migrateInterchainToken(bytes32 tokenId) external onlyOwner { From 123c9c10abed4a8dbeb9a6c587b14577b2e1cce5 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 9 Jan 2025 17:24:29 +0200 Subject: [PATCH 3/4] removed migrate legacy token and added tests --- contracts/InterchainTokenService.sol | 13 ---- .../interfaces/IInterchainTokenService.sol | 8 --- test/InterchainTokenService.js | 67 +++++++++++++++++++ 3 files changed, 67 insertions(+), 21 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index bf6f205e..9cfaa047 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -644,19 +644,6 @@ contract InterchainTokenService is } } - /** - * @notice Allows the owner to migrate legacy tokens that cannot be migrated automatically. - * @param tokenId the tokenId of the registered token. - * @param data the data to pass to migrate the token. - */ - function migrateLegacyToken(bytes32 tokenId, bytes calldata data) external onlyOwner { - address tokenAddress = registeredTokenAddress(tokenId); - (bool success, bytes memory returnData) = tokenAddress.call(data); - if (!success) { - revert TokenMigrateFailed(returnData); - } - } - /** * @notice Allows the owner to migrate minter of native interchain tokens from ITS to the corresponding token manager. * @param tokenId the tokenId of the registered token. diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index e7474889..c10dffb7 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -54,7 +54,6 @@ interface IInterchainTokenService is error EmptyParams(); error EmptyDestinationAddress(); error NotSupported(); - error TokenMigrateFailed(bytes returnData); event InterchainTransfer( bytes32 indexed tokenId, @@ -276,13 +275,6 @@ interface IInterchainTokenService is */ function setPauseStatus(bool paused) external; - /** - * @notice Allows the owner to migrate legacy tokens that cannot be migrated automatically. - * @param tokenId the tokenId of the registered token. - * @param data the data to pass to migrate the token. - */ - function migrateLegacyToken(bytes32 tokenId, bytes calldata data) external; - /** * @notice Allows the owner to migrate legacy tokens that cannot be migrated automatically. * @param tokenId the tokenId of the registered token. diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 401edf33..93eed02f 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -30,6 +30,7 @@ const { ITS_HUB_CHAIN_NAME, ITS_HUB_ROUTING_IDENTIFIER, ITS_HUB_ADDRESS, + MINTER_ROLE, } = require('./constants'); const reportGas = gasReporter('Interchain Token Service'); @@ -3235,6 +3236,72 @@ describe('Interchain Token Service', () => { }); }); + describe('Interchain Token Migration', () => { + it('Should migrate a token succesfully', async () => { + const salt = getRandomBytes32(); + const name = 'migrated token'; + const symbol = 'MT'; + const decimals = 53; + const tokenId = await service.interchainTokenId(wallet.address, salt); + const tokenManagerAddress = await service.tokenManagerAddress(tokenId); + + await interchainTokenDeployer.deployInterchainToken(salt, tokenId, service.address, name, symbol, decimals).then((tx) => tx.wait); + const tokenAddress = await interchainTokenDeployer.deployedAddress(salt); + const token = await getContractAt('InterchainToken', tokenAddress, wallet); + + const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); + + await service.deployTokenManager(salt, '', MINT_BURN, params, 0).then((tx) => tx.wait); + + await expect(service.migrateInterchainToken(tokenId)) + .to.emit(token, 'RolesRemoved') + .withArgs(service.address, 1 << MINTER_ROLE) + .to.emit(token, 'RolesAdded') + .withArgs(tokenManagerAddress, 1 << MINTER_ROLE); + }); + + it('Should not be able to migrate a token twice', async () => { + const salt = getRandomBytes32(); + const name = 'migrated token'; + const symbol = 'MT'; + const decimals = 53; + const tokenId = await service.interchainTokenId(wallet.address, salt); + const tokenManagerAddress = await service.tokenManagerAddress(tokenId); + + await interchainTokenDeployer.deployInterchainToken(salt, tokenId, service.address, name, symbol, decimals).then((tx) => tx.wait); + const tokenAddress = await interchainTokenDeployer.deployedAddress(salt); + const token = await getContractAt('InterchainToken', tokenAddress, wallet); + + const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); + + await service.deployTokenManager(salt, '', MINT_BURN, params, 0).then((tx) => tx.wait); + + await expect(service.migrateInterchainToken(tokenId)) + .to.emit(token, 'RolesRemoved') + .withArgs(service.address, 1 << MINTER_ROLE) + .to.emit(token, 'RolesAdded') + .withArgs(tokenManagerAddress, 1 << MINTER_ROLE); + + await expectRevert((gasOptions) => service.migrateInterchainToken(tokenId, { gasOptions} ), token, 'MissingRole', [service.address, MINTER_ROLE]); + }); + + it('Should not be able to migrate a token deployed after this upgrade', async () => { + const salt = getRandomBytes32(); + const name = 'migrated token'; + const symbol = 'MT'; + const decimals = 53; + const tokenId = await service.interchainTokenId(wallet.address, salt); + const tokenManagerAddress = await service.tokenManagerAddress(tokenId); + + + await service.deployInterchainToken(salt, '', name, symbol, decimals, AddressZero, 0).then((tx) => tx.wait); + const tokenAddress = await service.interchainTokenAddress(tokenId); + const token = await getContractAt('InterchainToken', tokenAddress, wallet); + + await expectRevert((gasOptions) => service.migrateInterchainToken(tokenId, { gasOptions} ), token, 'MissingRole', [service.address, MINTER_ROLE]); + }); + }) + describe('Bytecode checks [ @skip-on-coverage ]', () => { it('Should preserve the same proxy bytecode for each EVM', async () => { const proxyFactory = await ethers.getContractFactory('InterchainProxy', wallet); From 716fb46b2698b191c4b38d2b1ee94903bbaeb7e4 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 9 Jan 2025 18:19:49 +0200 Subject: [PATCH 4/4] made lint happy --- test/InterchainTokenService.js | 26 +++++++++++++++++--------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 93eed02f..9ac64317 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -3245,12 +3245,14 @@ describe('Interchain Token Service', () => { const tokenId = await service.interchainTokenId(wallet.address, salt); const tokenManagerAddress = await service.tokenManagerAddress(tokenId); - await interchainTokenDeployer.deployInterchainToken(salt, tokenId, service.address, name, symbol, decimals).then((tx) => tx.wait); + await interchainTokenDeployer + .deployInterchainToken(salt, tokenId, service.address, name, symbol, decimals) + .then((tx) => tx.wait); const tokenAddress = await interchainTokenDeployer.deployedAddress(salt); const token = await getContractAt('InterchainToken', tokenAddress, wallet); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); - + await service.deployTokenManager(salt, '', MINT_BURN, params, 0).then((tx) => tx.wait); await expect(service.migrateInterchainToken(tokenId)) @@ -3268,12 +3270,14 @@ describe('Interchain Token Service', () => { const tokenId = await service.interchainTokenId(wallet.address, salt); const tokenManagerAddress = await service.tokenManagerAddress(tokenId); - await interchainTokenDeployer.deployInterchainToken(salt, tokenId, service.address, name, symbol, decimals).then((tx) => tx.wait); + await interchainTokenDeployer + .deployInterchainToken(salt, tokenId, service.address, name, symbol, decimals) + .then((tx) => tx.wait); const tokenAddress = await interchainTokenDeployer.deployedAddress(salt); const token = await getContractAt('InterchainToken', tokenAddress, wallet); const params = defaultAbiCoder.encode(['bytes', 'address'], [wallet.address, tokenAddress]); - + await service.deployTokenManager(salt, '', MINT_BURN, params, 0).then((tx) => tx.wait); await expect(service.migrateInterchainToken(tokenId)) @@ -3282,7 +3286,10 @@ describe('Interchain Token Service', () => { .to.emit(token, 'RolesAdded') .withArgs(tokenManagerAddress, 1 << MINTER_ROLE); - await expectRevert((gasOptions) => service.migrateInterchainToken(tokenId, { gasOptions} ), token, 'MissingRole', [service.address, MINTER_ROLE]); + await expectRevert((gasOptions) => service.migrateInterchainToken(tokenId, { gasOptions }), token, 'MissingRole', [ + service.address, + MINTER_ROLE, + ]); }); it('Should not be able to migrate a token deployed after this upgrade', async () => { @@ -3291,16 +3298,17 @@ describe('Interchain Token Service', () => { const symbol = 'MT'; const decimals = 53; const tokenId = await service.interchainTokenId(wallet.address, salt); - const tokenManagerAddress = await service.tokenManagerAddress(tokenId); - await service.deployInterchainToken(salt, '', name, symbol, decimals, AddressZero, 0).then((tx) => tx.wait); const tokenAddress = await service.interchainTokenAddress(tokenId); const token = await getContractAt('InterchainToken', tokenAddress, wallet); - await expectRevert((gasOptions) => service.migrateInterchainToken(tokenId, { gasOptions} ), token, 'MissingRole', [service.address, MINTER_ROLE]); + await expectRevert((gasOptions) => service.migrateInterchainToken(tokenId, { gasOptions }), token, 'MissingRole', [ + service.address, + MINTER_ROLE, + ]); }); - }) + }); describe('Bytecode checks [ @skip-on-coverage ]', () => { it('Should preserve the same proxy bytecode for each EVM', async () => {