diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index 3700e78b..f1ffd6cc 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -32,7 +32,6 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M bytes32 private constant CONTRACT_ID = keccak256('interchain-token-factory'); 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_DEPLOYER_BALANCE = keccak256('deployer-balance'); address private constant TOKEN_FACTORY_DEPLOYER = address(0); /** @@ -105,13 +104,6 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M tokenAddress = service.interchainTokenAddress(interchainTokenId(deployer, salt)); } - function deployerTokenBalance(bytes32 tokenId, address deployer) public view returns (uint256 deployerBalance) { - bytes32 balanceKey = keccak256(abi.encode(PREFIX_DEPLOYER_BALANCE, tokenId, deployer)); - assembly { - deployerBalance := sload(balanceKey) - } - } - /** * @notice Deploys a new interchain token with specified parameters. * @dev Creates a new token and optionally mints an initial amount to a specified minter. @@ -147,11 +139,9 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M IInterchainToken token = IInterchainToken(service.interchainTokenAddress(tokenId)); ITokenManager tokenManager = ITokenManager(service.tokenManagerAddress(tokenId)); - _setDeployerTokenBalance(tokenId, sender, initialSupply); - token.mint(address(this), initialSupply); + token.mint(minter != address(0) ? minter : sender, initialSupply); token.transferMintership(minter); - tokenManager.removeFlowLimiter(address(this)); // If minter == address(0), we still set it as a flow limiter for consistency with the remote token manager. @@ -283,64 +273,6 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M _deployInterchainToken(salt, destinationChain, tokenName, tokenSymbol, tokenDecimals, '', gasValue); } - /** - * @notice Transfers an interchain token to a specified destination chain and address. - * @param tokenId The identifier of the interchain token. - * @param destinationChain The name of the destination chain. - * @param destinationAddress The address on the destination chain to receive the token. - * @param amount The amount of tokens to transfer. - * @param gasValue The amount of gas to send for the transfer. - */ - function interchainTransfer( - bytes32 tokenId, - string calldata destinationChain, - bytes calldata destinationAddress, - uint256 amount, - uint256 gasValue - ) external payable { - address sender = msg.sender; - uint256 balance = deployerTokenBalance(tokenId, sender); - - if (amount > balance) revert InsufficientBalance(tokenId, sender, balance); - - _setDeployerTokenBalance(tokenId, sender, balance - amount); - - if (bytes(destinationChain).length == 0) { - address tokenAddress = service.interchainTokenAddress(tokenId); - IInterchainToken token = IInterchainToken(tokenAddress); - token.safeTransfer(destinationAddress.toAddress(), amount); - } else { - // slither-disable-next-line arbitrary-send-eth - service.interchainTransfer{ value: gasValue }(tokenId, destinationChain, destinationAddress, amount, new bytes(0)); - } - } - - /** - * @notice Allows tokens to be transferred from the sender to the contract. - * @param tokenId The identifier of the interchain token. - * @param amount The amount of tokens to transfer. - */ - function tokenTransferFrom(bytes32 tokenId, uint256 amount) external payable { - address tokenAddress = service.validTokenAddress(tokenId); - IInterchainToken token = IInterchainToken(tokenAddress); - - _setDeployerTokenBalance(tokenId, msg.sender, deployerTokenBalance(tokenId, msg.sender) + amount); - - token.safeTransferFrom(msg.sender, address(this), amount); - } - - /** - * @notice Approves a specified amount of tokens to the service. - * @param tokenId The identifier of the interchain token. - * @param amount The amount of tokens to approve. - */ - function tokenApprove(bytes32 tokenId, uint256 amount) external payable { - address tokenAddress = service.validTokenAddress(tokenId); - IInterchainToken token = IInterchainToken(tokenAddress); - - token.safeCall(abi.encodeWithSelector(token.approve.selector, service, amount)); - } - /** * @notice Checks if a given token is a gateway token. * @param token The address of the token to check. @@ -350,11 +282,4 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M string memory symbol = IInterchainToken(token).symbol(); return token == gateway.tokenAddresses(symbol); } - - function _setDeployerTokenBalance(bytes32 tokenId, address deployer, uint256 deployerBalance) internal { - bytes32 balanceKey = keccak256(abi.encode(PREFIX_DEPLOYER_BALANCE, tokenId, deployer)); - assembly { - sstore(balanceKey, deployerBalance) - } - } } diff --git a/contracts/interfaces/IInterchainTokenFactory.sol b/contracts/interfaces/IInterchainTokenFactory.sol index b272caae..2e789394 100644 --- a/contracts/interfaces/IInterchainTokenFactory.sol +++ b/contracts/interfaces/IInterchainTokenFactory.sol @@ -11,8 +11,6 @@ interface IInterchainTokenFactory { error InvalidChainName(); error NotMinter(address minter); error NotOperator(address operator); - error InsufficientBalance(bytes32 tokenId, address deployer, uint256 balance); - error ApproveFailed(); error GatewayToken(address tokenAddress); /** @@ -115,34 +113,4 @@ interface IInterchainTokenFactory { string calldata destinationChain, uint256 gasValue ) external payable; - - /** - * @notice Transfers an interchain token to a specified destination chain and address. - * @param tokenId The identifier of the interchain token. - * @param destinationChain The name of the destination chain. - * @param destinationAddress The address on the destination chain to receive the token. - * @param amount The amount of tokens to transfer. - * @param gasValue The amount of gas to send for the transfer. - */ - function interchainTransfer( - bytes32 tokenId, - string calldata destinationChain, - bytes calldata destinationAddress, - uint256 amount, - uint256 gasValue - ) external payable; - - /** - * @notice Allows tokens to be transferred from the sender to the contract. - * @param tokenId The identifier of the interchain token. - * @param amount The amount of tokens to transfer. - */ - function tokenTransferFrom(bytes32 tokenId, uint256 amount) external payable; - - /** - * @notice Approves a specified amount of tokens to the token manager. - * @param tokenId The identifier of the interchain token. - * @param amount The amount of tokens to approve. - */ - function tokenApprove(bytes32 tokenId, uint256 amount) external payable; } diff --git a/test/InterchainTokenFactory.js b/test/InterchainTokenFactory.js index bb2f3c17..105ea2f5 100644 --- a/test/InterchainTokenFactory.js +++ b/test/InterchainTokenFactory.js @@ -6,7 +6,7 @@ const { ethers } = require('hardhat'); const { getContractAt, Wallet, - constants: { AddressZero, HashZero }, + constants: { AddressZero }, utils: { defaultAbiCoder, keccak256, toUtf8Bytes }, } = ethers; @@ -133,8 +133,6 @@ describe('InterchainTokenFactory', () => { }); it('Should transfer some tokens to the factory', async () => { - const amount = 123456; - await deployToken(); const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); @@ -142,71 +140,6 @@ describe('InterchainTokenFactory', () => { await expect(tokenFactory.registerCanonicalInterchainToken(token.address)) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManagerAddress, LOCK_UNLOCK, params); - - await token.approve(tokenFactory.address, amount).then((tx) => tx.wait()); - - await expect(tokenFactory.tokenTransferFrom(tokenId, amount)) - .to.emit(token, 'Transfer') - .withArgs(wallet.address, tokenFactory.address, amount); - }); - - it('Should approve some tokens from the factory to the token manager', async () => { - const amount = 123456; - - await deployToken(); - - const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); - - await expect(tokenFactory.registerCanonicalInterchainToken(token.address)) - .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManagerAddress, LOCK_UNLOCK, params); - - tokenManagerAddress = await service.validTokenManagerAddress(tokenId); - - await expect(tokenFactory.tokenApprove(tokenId, amount)) - .to.emit(token, 'Approval') - .withArgs(tokenFactory.address, service.address, amount); - }); - - it('Should transfer some tokens through the factory as the deployer', async () => { - const amount = 123456; - const destinationAddress = '0x57689403'; - const gasValue = 45960; - - await deployToken(); - - const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); - - await expect(tokenFactory.registerCanonicalInterchainToken(token.address)) - .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, tokenManagerAddress, LOCK_UNLOCK, params); - - await token.approve(tokenFactory.address, amount).then((tx) => tx.wait()); - - tokenManagerAddress = await service.validTokenManagerAddress(tokenId); - - const txs = []; - - txs.push(await tokenFactory.populateTransaction.tokenTransferFrom(tokenId, amount)); - txs.push(await tokenFactory.populateTransaction.tokenApprove(tokenId, amount)); - txs.push( - await tokenFactory.populateTransaction.interchainTransfer(tokenId, destinationChain, destinationAddress, amount, gasValue), - ); - - await expect( - tokenFactory.multicall( - txs.map((tx) => tx.data), - { value: gasValue }, - ), - ) - .to.emit(token, 'Transfer') - .withArgs(wallet.address, tokenFactory.address, amount) - .and.to.emit(token, 'Approval') - .withArgs(tokenFactory.address, service.address, amount) - .and.to.emit(token, 'Transfer') - .withArgs(tokenFactory.address, tokenManagerAddress, amount) - .and.to.emit(service, 'InterchainTransfer') - .withArgs(tokenId, tokenFactory.address, destinationChain, destinationAddress, amount, HashZero); }); it('Should revert when trying to register a canonical lock/unlock gateway token', async () => { @@ -332,7 +265,7 @@ describe('InterchainTokenFactory', () => { .and.to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManager.address, MINT_BURN, params) .and.to.emit(token, 'Transfer') - .withArgs(AddressZero, tokenFactory.address, mintAmount) + .withArgs(AddressZero, minter, mintAmount) .and.to.emit(tokenManager, 'RolesAdded') .withArgs(minter, 1 << FLOW_LIMITER_ROLE) .and.to.emit(tokenManager, 'RolesAdded') @@ -346,10 +279,6 @@ describe('InterchainTokenFactory', () => { .and.to.emit(tokenManager, 'RolesRemoved') .withArgs(tokenFactory.address, 1 << FLOW_LIMITER_ROLE); - await expect(tokenFactory.interchainTransfer(tokenId, '', minter, mintAmount, 0)) - .to.emit(token, 'Transfer') - .withArgs(tokenFactory.address, minter, mintAmount); - expect(await token.balanceOf(tokenFactory.address)).to.equal(0); expect(await token.balanceOf(minter)).to.equal(mintAmount); @@ -363,7 +292,7 @@ describe('InterchainTokenFactory', () => { const salt = keccak256('0x12'); tokenId = await tokenFactory.interchainTokenId(wallet.address, salt); const tokenAddress = await tokenFactory.interchainTokenAddress(wallet.address, salt); - let params = defaultAbiCoder.encode(['bytes', 'address'], [tokenFactory.address, tokenAddress]); + const params = defaultAbiCoder.encode(['bytes', 'address'], [tokenFactory.address, tokenAddress]); const tokenManager = await getContractAt('TokenManager', await service.tokenManagerAddress(tokenId), wallet); const token = await getContractAt('InterchainToken', tokenAddress, wallet); @@ -373,7 +302,7 @@ describe('InterchainTokenFactory', () => { .and.to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManager.address, MINT_BURN, params) .and.to.emit(token, 'Transfer') - .withArgs(AddressZero, tokenFactory.address, mintAmount) + .withArgs(AddressZero, wallet.address, mintAmount) .and.to.emit(token, 'RolesAdded') .withArgs(wallet.address, 1 << MINTER_ROLE) .and.to.emit(tokenManager, 'RolesAdded') @@ -387,7 +316,6 @@ describe('InterchainTokenFactory', () => { .and.to.emit(tokenManager, 'RolesRemoved') .withArgs(tokenFactory.address, 1 << FLOW_LIMITER_ROLE); - params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes'], [MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, tokenId, name, symbol, decimals, wallet.address.toLowerCase()], @@ -435,7 +363,7 @@ describe('InterchainTokenFactory', () => { const salt = keccak256('0x1245'); tokenId = await tokenFactory.interchainTokenId(wallet.address, salt); const tokenAddress = await tokenFactory.interchainTokenAddress(wallet.address, salt); - let params = defaultAbiCoder.encode(['bytes', 'address'], [tokenFactory.address, tokenAddress]); + const params = defaultAbiCoder.encode(['bytes', 'address'], [tokenFactory.address, tokenAddress]); const tokenManager = await getContractAt('TokenManager', await service.tokenManagerAddress(tokenId), wallet); const token = await getContractAt('InterchainToken', tokenAddress, wallet); @@ -445,7 +373,7 @@ describe('InterchainTokenFactory', () => { .and.to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, tokenManager.address, MINT_BURN, params) .and.to.emit(token, 'Transfer') - .withArgs(AddressZero, tokenFactory.address, mintAmount) + .withArgs(AddressZero, wallet.address, mintAmount) .and.to.emit(token, 'RolesAdded') .withArgs(wallet.address, 1 << MINTER_ROLE) .and.to.emit(tokenManager, 'RolesAdded') @@ -459,7 +387,6 @@ describe('InterchainTokenFactory', () => { .and.to.emit(tokenManager, 'RolesRemoved') .withArgs(tokenFactory.address, 1 << FLOW_LIMITER_ROLE); - params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes'], [MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN, tokenId, name, symbol, decimals, '0x'], diff --git a/test/InterchainTokenServiceFullFlow.js b/test/InterchainTokenServiceFullFlow.js index 96847dc8..a8dcb497 100644 --- a/test/InterchainTokenServiceFullFlow.js +++ b/test/InterchainTokenServiceFullFlow.js @@ -64,11 +64,6 @@ describe('Interchain Token Service Full Flow', () => { }); it('Should register the token and initiate its deployment on other chains', async () => { - const mintAmount = Math.floor(tokenCap / otherChains.length); - const totalMint = mintAmount * otherChains.length; - - await token.approve(factory.address, totalMint).then((tx) => tx.wait()); - tokenId = await factory.canonicalInterchainTokenId(token.address); let tx = await factory.populateTransaction.registerCanonicalInterchainToken(token.address); @@ -86,31 +81,6 @@ describe('Interchain Token Service Full Flow', () => { value += gasValues[i]; } - // Transfer total mint amount to the factory contract - tx = await factory.populateTransaction.tokenTransferFrom(tokenId, totalMint); - calls.push(tx.data); - - // Optional. Reset approval from the factory to ITS. This is only needed for tokens like USDT that don't allow overriding existing approvals. - tx = await factory.populateTransaction.tokenApprove(tokenId, 0); - calls.push(tx.data); - - // Approve total mint amount from the factory to ITS - tx = await factory.populateTransaction.tokenApprove(tokenId, totalMint); - calls.push(tx.data); - - // Transfer tokens from factory contract to the user on remote chains. - for (const i in otherChains) { - tx = await factory.populateTransaction.interchainTransfer( - tokenId, - otherChains[i], - wallet.address, - mintAmount, - gasValues[i], - ); - calls.push(tx.data); - value += gasValues[i]; - } - const params = defaultAbiCoder.encode(['bytes', 'address'], ['0x', token.address]); const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes'], @@ -134,13 +104,7 @@ describe('Interchain Token Service Full Flow', () => { .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, otherChains[1], service.address, keccak256(payload), gasValues[1], wallet.address) .and.to.emit(gateway, 'ContractCall') - .withArgs(service.address, otherChains[1], service.address, keccak256(payload), payload) - .and.to.emit(token, 'Transfer') - .withArgs(wallet.address, factory.address, totalMint) - .and.to.emit(token, 'Approval') - .withArgs(factory.address, service.address, totalMint) - .and.to.emit(token, 'Transfer') - .withArgs(factory.address, expectedTokenManagerAddress, mintAmount); + .withArgs(service.address, otherChains[1], service.address, keccak256(payload), payload); }); describe('Interchain transfer', () => { @@ -202,8 +166,7 @@ describe('Interchain Token Service Full Flow', () => { }); it('Should register the token and initiate its deployment on other chains', async () => { - const mintAmount = Math.floor(tokenCap / (otherChains.length + 1)); - const totalMint = mintAmount * (otherChains.length + 1); + const totalMint = tokenCap; // Deploy a new Interchain token on the local chain. // The initial mint occurs on the factory contract, so it can be moved to other chains within the same multicall. @@ -224,23 +187,6 @@ describe('Interchain Token Service Full Flow', () => { value += gasValues[i]; } - // Transfer tokens from factory contract to the user on local chain. - tx = await factory.populateTransaction.interchainTransfer(tokenId, '', wallet.address, mintAmount, 0); - calls.push(tx.data); - - // Transfer tokens from factory contract to the user on remote chains. - for (const i in otherChains) { - tx = await factory.populateTransaction.interchainTransfer( - tokenId, - otherChains[i], - wallet.address, - mintAmount, - gasValues[i], - ); - calls.push(tx.data); - value += gasValues[i]; - } - const params = defaultAbiCoder.encode(['bytes', 'address'], [factory.address, token.address]); const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'string', 'string', 'uint8', 'bytes'], @@ -266,16 +212,10 @@ describe('Interchain Token Service Full Flow', () => { .and.to.emit(gasService, 'NativeGasPaidForContractCall') .withArgs(service.address, otherChains[1], service.address, keccak256(payload), gasValues[1], wallet.address) .and.to.emit(gateway, 'ContractCall') - .withArgs(service.address, otherChains[1], service.address, keccak256(payload), payload) - .and.to.emit(token, 'Transfer') - .withArgs(AddressZero, factory.address, totalMint) - .and.to.emit(token, 'Transfer') - .withArgs(factory.address, wallet.address, mintAmount) - .and.to.emit(token, 'Transfer') - .withArgs(factory.address, AddressZero, mintAmount); + .withArgs(service.address, otherChains[1], service.address, keccak256(payload), payload); // Only tokens minted for the local chain should be left, remaining should be burned. - expect(await token.balanceOf(wallet.address)).to.equal(mintAmount); + expect(await token.balanceOf(wallet.address)).to.equal(totalMint); expect(await service.validTokenManagerAddress(tokenId)).to.equal(expectedTokenManagerAddress); });