From a0e5277f2613414915804cb2b1f964754eceb715 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 11 Dec 2024 17:31:26 +0200 Subject: [PATCH 1/3] stash --- contracts/TokenHandler.sol | 12 ++---------- contracts/interchain-token/InterchainToken.sol | 3 ++- test/ERC20.js | 9 +++++---- test/ERC20Permit.js | 10 +++++----- test/InterchainToken.js | 11 +++++++---- test/InterchainTokenFactory.js | 12 +++--------- 6 files changed, 24 insertions(+), 33 deletions(-) diff --git a/contracts/TokenHandler.sol b/contracts/TokenHandler.sol index 69932021..7249b316 100644 --- a/contracts/TokenHandler.sol +++ b/contracts/TokenHandler.sol @@ -39,7 +39,7 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea ITokenManager(tokenManager).addFlowIn(amount); if (tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN)) { - _giveInterchainToken(tokenAddress, to, amount); + _mintToken(tokenManager, tokenAddress, to, amount); return (amount, tokenAddress); } @@ -77,7 +77,7 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea if (tokenOnly && msg.sender != tokenAddress) revert NotToken(msg.sender, tokenAddress); if (tokenManagerType == uint256(TokenManagerType.NATIVE_INTERCHAIN_TOKEN)) { - _takeInterchainToken(tokenAddress, from, amount); + _burnToken(tokenManager, tokenAddress, from, amount); } else if (tokenManagerType == uint256(TokenManagerType.MINT_BURN)) { _burnToken(tokenManager, tokenAddress, from, amount); } else if (tokenManagerType == uint256(TokenManagerType.MINT_BURN_FROM)) { @@ -160,14 +160,6 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea return diff < amount ? diff : amount; } - function _giveInterchainToken(address tokenAddress, address to, uint256 amount) internal { - IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.mint.selector, to, amount)); - } - - function _takeInterchainToken(address tokenAddress, address from, uint256 amount) internal { - IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20MintableBurnable.burn.selector, from, amount)); - } - function _mintToken(address tokenManager, address tokenAddress, address to, uint256 amount) internal { ITokenManager(tokenManager).mintToken(tokenAddress, to, amount); } diff --git a/contracts/interchain-token/InterchainToken.sol b/contracts/interchain-token/InterchainToken.sol index 03265886..1a3a5c1f 100644 --- a/contracts/interchain-token/InterchainToken.sol +++ b/contracts/interchain-token/InterchainToken.sol @@ -3,6 +3,7 @@ pragma solidity ^0.8.0; import { IInterchainToken } from '../interfaces/IInterchainToken.sol'; +import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol'; import { InterchainTokenStandard } from './InterchainTokenStandard.sol'; import { ERC20Permit } from './ERC20Permit.sol'; @@ -97,7 +98,7 @@ contract InterchainToken is InterchainTokenStandard, ERC20Permit, Minter, IInter * Also add the provided address as a minter. If `address(0)` was provided, * add it as a minter to allow anyone to easily check that no custom minter was set. */ - _addMinter(interchainTokenService_); + _addMinter(IInterchainTokenService(interchainTokenService_).tokenManagerAddress(tokenId_)); _addMinter(minter); _setNameHash(tokenName); diff --git a/test/ERC20.js b/test/ERC20.js index 863ecb9c..86e65f06 100644 --- a/test/ERC20.js +++ b/test/ERC20.js @@ -7,10 +7,10 @@ const { } = ethers; const { expect } = require('chai'); const { getRandomBytes32, expectRevert } = require('./utils'); -const { deployContract } = require('../scripts/deploy'); +const { deployContract, deployAll } = require('../scripts/deploy'); describe('ERC20', () => { - let interchainToken, interchainTokenDeployer; + let interchainTokenDeployer; const name = 'tokenName'; const symbol = 'tokenSymbol'; @@ -26,8 +26,9 @@ describe('ERC20', () => { owner = wallets[0]; user = wallets[1]; - interchainToken = await deployContract(owner, 'InterchainToken', [owner.address]); - interchainTokenDeployer = await deployContract(owner, 'InterchainTokenDeployer', [interchainToken.address]); + ({ + interchainTokenDeployer, + } = await deployAll(owner, 'Test')); const salt = getRandomBytes32(); const tokenId = getRandomBytes32(); diff --git a/test/ERC20Permit.js b/test/ERC20Permit.js index b8438224..35335dd3 100644 --- a/test/ERC20Permit.js +++ b/test/ERC20Permit.js @@ -8,10 +8,10 @@ const { } = ethers; const { expect } = require('chai'); const { getRandomBytes32, expectRevert, getChainId } = require('./utils'); -const { deployContract } = require('../scripts/deploy'); +const { deployContract, deployAll } = require('../scripts/deploy'); describe('ERC20 Permit', () => { - let interchainToken, interchainTokenDeployer; + let interchainTokenDeployer; const name = 'tokenName'; const symbol = 'tokenSymbol'; @@ -27,9 +27,9 @@ describe('ERC20 Permit', () => { owner = wallets[0]; user = wallets[1]; - interchainToken = await deployContract(owner, 'InterchainToken', [owner.address]); - interchainTokenDeployer = await deployContract(owner, 'InterchainTokenDeployer', [interchainToken.address]); - + ({ + interchainTokenDeployer, + } = await deployAll(owner, 'Test')); const salt = getRandomBytes32(); const tokenId = getRandomBytes32(); diff --git a/test/InterchainToken.js b/test/InterchainToken.js index a7ff687a..4f973b8c 100644 --- a/test/InterchainToken.js +++ b/test/InterchainToken.js @@ -8,10 +8,11 @@ const { } = ethers; const { expect } = require('chai'); const { getRandomBytes32, expectRevert, getEVMVersion } = require('./utils'); -const { deployContract } = require('../scripts/deploy'); +const { deployContract, deployAll } = require('../scripts/deploy'); describe('InterchainToken', () => { - let interchainToken, interchainTokenDeployer; + let interchainTokenDeployer; + let interchainToken; const name = 'tokenName'; const symbol = 'tokenSymbol'; @@ -28,8 +29,10 @@ describe('InterchainToken', () => { owner = wallets[0]; user = wallets[1]; - interchainToken = await deployContract(owner, 'InterchainToken', [owner.address]); - interchainTokenDeployer = await deployContract(owner, 'InterchainTokenDeployer', [interchainToken.address]); + ({ + interchainToken, + interchainTokenDeployer, + } = await deployAll(owner, 'Test')); const salt = getRandomBytes32(); const tokenId = getRandomBytes32(); diff --git a/test/InterchainTokenFactory.js b/test/InterchainTokenFactory.js index fa9e687e..0c37b5ae 100644 --- a/test/InterchainTokenFactory.js +++ b/test/InterchainTokenFactory.js @@ -197,7 +197,7 @@ describe('InterchainTokenFactory', () => { const checkRoles = async (tokenManager, minter) => { const token = await getContractAt('InterchainToken', await tokenManager.tokenAddress(), wallet); expect(await token.isMinter(minter)).to.be.true; - expect(await token.isMinter(service.address)).to.be.true; + expect(await token.isMinter(tokenManager.address)).to.be.true; expect(await tokenManager.isOperator(minter)).to.be.true; expect(await tokenManager.isOperator(service.address)).to.be.true; @@ -216,18 +216,12 @@ describe('InterchainTokenFactory', () => { ); }); - it('Should register a token if the mint amount is zero', async () => { + it('Should not register a token if the mint amount is zero and there is no minter', async () => { const salt = keccak256('0x1234'); tokenId = await tokenFactory.interchainTokenId(wallet.address, salt); - const tokenAddress = await service.interchainTokenAddress(tokenId); - const params = defaultAbiCoder.encode(['bytes', 'address'], [minter, tokenAddress]); const tokenManager = await getContractAt('TokenManager', await service.tokenManagerAddress(tokenId), wallet); - await expect(tokenFactory.deployInterchainToken(salt, name, symbol, decimals, 0, minter)) - .to.emit(service, 'InterchainTokenDeployed') - .withArgs(tokenId, tokenAddress, minter, name, symbol, decimals) - .and.to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManager.address, NATIVE_INTERCHAIN_TOKEN, params); + await expectRevert(tokenFactory.deployInterchainToken(salt, name, symbol, decimals, 0, minter), InterchainTokenFactory.abi, 'EmptyInterchainToken()', []); await checkRoles(tokenManager, minter); }); From befb487e6ed5d0642fda6bca40dfb8abe8c0022a Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 11 Dec 2024 18:17:59 +0200 Subject: [PATCH 2/3] Fix tests --- contracts/InterchainTokenFactory.sol | 2 +- contracts/TokenHandler.sol | 3 +-- test/ERC20.js | 4 +--- test/ERC20Permit.js | 6 ++---- test/InterchainToken.js | 7 ++----- test/InterchainTokenFactory.js | 18 ++++++++++++------ test/InterchainTokenService.js | 2 +- test/UtilsTest.js | 17 ++++++++--------- 8 files changed, 28 insertions(+), 31 deletions(-) diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index 31b4422b..a52cfbaa 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -334,7 +334,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M if (!token.isMinter(minter)) revert NotMinter(minter); // Sanity check to prevent accidental use of the current ITS address as the token minter - if (minter == address(interchainTokenService)) revert InvalidMinter(minter); + if (minter == interchainTokenService.tokenManagerAddress(tokenId)) revert InvalidMinter(minter); } /** diff --git a/contracts/TokenHandler.sol b/contracts/TokenHandler.sol index 7249b316..f104b030 100644 --- a/contracts/TokenHandler.sol +++ b/contracts/TokenHandler.sol @@ -4,14 +4,13 @@ pragma solidity ^0.8.0; import { ITokenHandler } from './interfaces/ITokenHandler.sol'; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; -import { SafeTokenTransfer, SafeTokenTransferFrom, SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol'; +import { SafeTokenTransferFrom, SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol'; import { ReentrancyGuard } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/ReentrancyGuard.sol'; import { Create3AddressFixed } from './utils/Create3AddressFixed.sol'; import { ITokenManagerType } from './interfaces/ITokenManagerType.sol'; import { ITokenManager } from './interfaces/ITokenManager.sol'; import { ITokenManagerProxy } from './interfaces/ITokenManagerProxy.sol'; -import { IERC20MintableBurnable } from './interfaces/IERC20MintableBurnable.sol'; import { IERC20BurnableFrom } from './interfaces/IERC20BurnableFrom.sol'; /** diff --git a/test/ERC20.js b/test/ERC20.js index 86e65f06..d03247be 100644 --- a/test/ERC20.js +++ b/test/ERC20.js @@ -26,9 +26,7 @@ describe('ERC20', () => { owner = wallets[0]; user = wallets[1]; - ({ - interchainTokenDeployer, - } = await deployAll(owner, 'Test')); + ({ interchainTokenDeployer } = await deployAll(owner, 'Test')); const salt = getRandomBytes32(); const tokenId = getRandomBytes32(); diff --git a/test/ERC20Permit.js b/test/ERC20Permit.js index 35335dd3..f0825e36 100644 --- a/test/ERC20Permit.js +++ b/test/ERC20Permit.js @@ -8,7 +8,7 @@ const { } = ethers; const { expect } = require('chai'); const { getRandomBytes32, expectRevert, getChainId } = require('./utils'); -const { deployContract, deployAll } = require('../scripts/deploy'); +const { deployAll } = require('../scripts/deploy'); describe('ERC20 Permit', () => { let interchainTokenDeployer; @@ -27,9 +27,7 @@ describe('ERC20 Permit', () => { owner = wallets[0]; user = wallets[1]; - ({ - interchainTokenDeployer, - } = await deployAll(owner, 'Test')); + ({ interchainTokenDeployer } = await deployAll(owner, 'Test')); const salt = getRandomBytes32(); const tokenId = getRandomBytes32(); diff --git a/test/InterchainToken.js b/test/InterchainToken.js index 422b7b65..9e16cc88 100644 --- a/test/InterchainToken.js +++ b/test/InterchainToken.js @@ -29,10 +29,7 @@ describe('InterchainToken', () => { owner = wallets[0]; user = wallets[1]; - ({ - interchainToken, - interchainTokenDeployer, - } = await deployAll(owner, 'Test')); + ({ interchainToken, interchainTokenDeployer } = await deployAll(owner, 'Test')); const salt = getRandomBytes32(); const tokenId = getRandomBytes32(); @@ -150,7 +147,7 @@ describe('InterchainToken', () => { const contractBytecodeHash = keccak256(contractBytecode); const expected = { - london: '0x482146829055f052063003e9cf0ffaf798a12fb58088c2667566a135b9568355', + london: '0x6e99e9cdd22bc7070016c2ca4a88520506fa524a0e91343df4dab44705485991', }[getEVMVersion()]; expect(contractBytecodeHash).to.be.equal(expected); diff --git a/test/InterchainTokenFactory.js b/test/InterchainTokenFactory.js index ed63c544..6238ac48 100644 --- a/test/InterchainTokenFactory.js +++ b/test/InterchainTokenFactory.js @@ -216,12 +216,18 @@ describe('InterchainTokenFactory', () => { ); }); - it('Should not register a token if the mint amount is zero and there is no minter', async () => { + it('Should register a token if the mint amount is zero', async () => { const salt = keccak256('0x1234'); tokenId = await tokenFactory.interchainTokenId(wallet.address, salt); + const tokenAddress = await service.interchainTokenAddress(tokenId); + const params = defaultAbiCoder.encode(['bytes', 'address'], [minter, tokenAddress]); const tokenManager = await getContractAt('TokenManager', await service.tokenManagerAddress(tokenId), wallet); - await expectRevert(tokenFactory.deployInterchainToken(salt, name, symbol, decimals, 0, minter), InterchainTokenFactory.abi, 'EmptyInterchainToken()', []); + await expect(tokenFactory.deployInterchainToken(salt, name, symbol, decimals, 0, minter)) + .to.emit(service, 'InterchainTokenDeployed') + .withArgs(tokenId, tokenAddress, minter, name, symbol, decimals) + .and.to.emit(service, 'TokenManagerDeployed') + .withArgs(tokenId, tokenManager.address, NATIVE_INTERCHAIN_TOKEN, params); await checkRoles(tokenManager, minter); }); @@ -364,7 +370,7 @@ describe('InterchainTokenFactory', () => { tokenFactory['deployRemoteInterchainToken(string,bytes32,address,string,uint256)']( '', salt, - service.address, + tokenManager.address, destinationChain, gasValue, { @@ -374,7 +380,7 @@ describe('InterchainTokenFactory', () => { ), tokenFactory, 'InvalidMinter', - [service.address], + [tokenManager.address], ); await expect( @@ -417,7 +423,7 @@ describe('InterchainTokenFactory', () => { (gasOptions) => tokenFactory['deployRemoteInterchainToken(bytes32,address,string,uint256)']( salt, - service.address, + tokenManager.address, destinationChain, gasValue, { @@ -427,7 +433,7 @@ describe('InterchainTokenFactory', () => { ), tokenFactory, 'InvalidMinter', - [service.address], + [tokenManager.address], ); await expect( diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 2dc561c3..cd931461 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -646,7 +646,7 @@ describe('Interchain Token Service', () => { const token = await getContractAt('InterchainToken', tokenAddress, wallet); expect(await token.isMinter(wallet.address)).to.be.true; - expect(await token.isMinter(service.address)).to.be.true; + expect(await token.isMinter(tokenManager.address)).to.be.true; }); it('Should revert when registering an interchain token as a lock/unlock for a second time', async () => { diff --git a/test/UtilsTest.js b/test/UtilsTest.js index 0b7c64c1..0573f3a9 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -3,14 +3,13 @@ const chai = require('chai'); const { ethers } = require('hardhat'); const { - Wallet, getContractAt, constants: { AddressZero }, } = ethers; const { time } = require('@nomicfoundation/hardhat-network-helpers'); const { expect } = chai; const { getRandomBytes32, expectRevert, isHardhat, waitFor } = require('./utils'); -const { deployContract } = require('../scripts/deploy'); +const { deployContract, deployAll } = require('../scripts/deploy'); let ownerWallet, otherWallet; @@ -257,16 +256,15 @@ describe('FlowLimit', async () => { }); describe('InterchainTokenDeployer', () => { - let interchainToken, interchainTokenDeployer; - const service = new Wallet(getRandomBytes32()).address; + let interchainTokenDeployer; + let service; const name = 'tokenName'; const symbol = 'tokenSymbol'; const decimals = 18; const MINTER_ROLE = 0; before(async () => { - interchainToken = await deployContract(ownerWallet, 'InterchainToken', [service]); - interchainTokenDeployer = await deployContract(ownerWallet, 'InterchainTokenDeployer', [interchainToken.address]); + ({ interchainTokenDeployer, service } = await deployAll(ownerWallet, 'Test')); }); it('Should revert on deployment with invalid implementation address', async () => { @@ -283,20 +281,21 @@ describe('InterchainTokenDeployer', () => { const tokenAddress = await interchainTokenDeployer.deployedAddress(salt); const token = await getContractAt('InterchainToken', tokenAddress, ownerWallet); + const tokenManager = await service.tokenManagerAddress(tokenId); await expect(interchainTokenDeployer.deployInterchainToken(salt, tokenId, ownerWallet.address, name, symbol, decimals)) .to.emit(token, 'RolesAdded') - .withArgs(service, 1 << MINTER_ROLE) + .withArgs(tokenManager, 1 << MINTER_ROLE) .and.to.emit(token, 'RolesAdded') .withArgs(ownerWallet.address, 1 << MINTER_ROLE); expect(await token.name()).to.equal(name); expect(await token.symbol()).to.equal(symbol); expect(await token.decimals()).to.equal(decimals); - expect(await token.hasRole(service, MINTER_ROLE)).to.be.true; + expect(await token.hasRole(tokenManager, MINTER_ROLE)).to.be.true; expect(await token.hasRole(ownerWallet.address, MINTER_ROLE)).to.be.true; expect(await token.interchainTokenId()).to.equal(tokenId); - expect(await token.interchainTokenService()).to.equal(service); + expect(await token.interchainTokenService()).to.equal(service.address); await expectRevert( (gasOptions) => From a4b3962885cef270fec903bb3099ca8b0cfacf46 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 20 Dec 2024 18:05:00 +0200 Subject: [PATCH 3/3] Change an error a bit --- contracts/InterchainTokenFactory.sol | 3 ++- contracts/interfaces/IInterchainTokenFactory.sol | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index a52cfbaa..00c5a190 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -117,6 +117,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M /** * @notice Deploys a new interchain token with specified parameters. + * This will revert with `ZeroSupplyToken()` if trying to deploy a token without a minter or initial supply. * @dev Creates a new token and optionally mints an initial amount to a specified minter. * This function is `payable` because non-payable functions cannot be called in a multicall that calls other `payable` functions. * @param salt The unique salt for deploying the token. @@ -150,7 +151,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M minterBytes = minter.toBytes(); } else { - revert EmptyInterchainToken(); + revert ZeroSupplyToken(); } tokenId = _deployInterchainToken(deploySalt, currentChain, name, symbol, decimals, minterBytes, gasValue); diff --git a/contracts/interfaces/IInterchainTokenFactory.sol b/contracts/interfaces/IInterchainTokenFactory.sol index 0db2aa76..714a73be 100644 --- a/contracts/interfaces/IInterchainTokenFactory.sol +++ b/contracts/interfaces/IInterchainTokenFactory.sol @@ -19,7 +19,7 @@ interface IInterchainTokenFactory is IUpgradable, IMulticall { error NotSupported(); error RemoteDeploymentNotApproved(); error InvalidTokenId(bytes32 tokenId, bytes32 expectedTokenId); - error EmptyInterchainToken(); + error ZeroSupplyToken(); /// @notice Emitted when a minter approves a deployer for a remote interchain token deployment that uses a custom destinationMinter address. event DeployRemoteInterchainTokenApproval( @@ -67,7 +67,8 @@ interface IInterchainTokenFactory is IUpgradable, IMulticall { function interchainTokenId(address deployer, bytes32 salt) external view returns (bytes32 tokenId); /** - * @notice Deploys a new interchain token with specified parameters. + * @notice Deploys a new interchain token with specified parameters. + * This will revert with `ZeroSupplyToken()` if trying to deploy a token without a minter or initial supply. * @param salt The unique salt for deploying the token. * @param name The name of the token. * @param symbol The symbol of the token.