Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(its): allow its owner to manually migrate tokens #316

Open
wants to merge 6 commits into
base: feat/token-manager-mint-interchain-tokens-2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions contracts/InterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { IInterchainTokenExecutable } from './interfaces/IInterchainTokenExecuta
import { IInterchainTokenExpressExecutable } from './interfaces/IInterchainTokenExpressExecutable.sol';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing JIRA ticket

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';

Expand Down Expand Up @@ -643,6 +644,16 @@ contract InterchainTokenService is
}
}

/**
* @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 {
ITokenManager tokenManager_ = deployedTokenManager(tokenId);
address tokenAddress = tokenManager_.tokenAddress();
IMinter(tokenAddress).transferMintership(address(tokenManager_));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for unit test, also test failure when migrating an already migrated token

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added all tests, please check

}

/****************\
INTERNAL FUNCTIONS
\****************/
Expand Down
6 changes: 6 additions & 0 deletions contracts/interfaces/IInterchainTokenService.sol
Original file line number Diff line number Diff line change
Expand Up @@ -274,4 +274,10 @@ 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.
*/
function migrateInterchainToken(bytes32 tokenId) external;
}
75 changes: 75 additions & 0 deletions test/InterchainTokenService.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Expand Down Expand Up @@ -3235,6 +3236,80 @@ 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);

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);
Expand Down