diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index a4cb5762..cfdba59b 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -221,14 +221,24 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M } /** - * @dev Allow any token to be approved to the token manager. - * TODO: Move this into a dedicated approve + transfer method to prevent unused approvals to be created that some tokens don't like. + * @dev This will still incur unwanted approvals for mint/burn token managers. */ - function tokenApprove(bytes32 tokenId, uint256 amount) external payable { + function approveAndInterchainTransfer( + bytes32 tokenId, + string calldata destinationChain, + bytes calldata destinationAddress, + uint256 amount, + uint256 gasValue + ) external payable { + if (bytes(destinationChain).length == 0) revert UnsetDestinationChainUnsupported(); + address tokenAddress = service.validTokenAddress(tokenId); IInterchainToken token = IInterchainToken(tokenAddress); address tokenManager = service.tokenManagerAddress(tokenId); token.safeCall(abi.encodeWithSelector(token.approve.selector, tokenManager, amount)); + + // slither-disable-next-line arbitrary-send-eth + service.interchainTransfer{ value: gasValue }(tokenId, destinationChain, destinationAddress, amount, new bytes(0)); } } diff --git a/contracts/interfaces/IInterchainTokenFactory.sol b/contracts/interfaces/IInterchainTokenFactory.sol index 4e720e43..6604e9fa 100644 --- a/contracts/interfaces/IInterchainTokenFactory.sol +++ b/contracts/interfaces/IInterchainTokenFactory.sol @@ -9,6 +9,7 @@ interface IInterchainTokenFactory { error NotOperator(address operator); error NonZeroMintAmount(); error ApproveFailed(); + error UnsetDestinationChainUnsupported(); function chainNameHash() external view returns (bytes32); @@ -58,5 +59,11 @@ interface IInterchainTokenFactory { function tokenTransferFrom(bytes32 tokenId, uint256 amount) external payable; - function tokenApprove(bytes32 tokenId, uint256 amount) external payable; + function approveAndInterchainTransfer( + bytes32 tokenId, + string calldata destinationChain, + bytes calldata destinationAddress, + uint256 amount, + uint256 gasValue + ) external payable; } diff --git a/test/InterchainTokenFactory.js b/test/InterchainTokenFactory.js index 4fa4a055..b160f5ab 100644 --- a/test/InterchainTokenFactory.js +++ b/test/InterchainTokenFactory.js @@ -109,6 +109,8 @@ describe('InterchainTokenFactory', () => { it('Should approve some tokens from the factory to the token manager', async () => { const amount = 123456; + const destinationAddress = '0x57689403'; + const gasValue = 45960; await deployToken(); @@ -120,9 +122,21 @@ describe('InterchainTokenFactory', () => { tokenManagerAddress = await service.validTokenManagerAddress(tokenId); - await expect(tokenFactory.tokenApprove(tokenId, amount)) + await expect(token.transfer(tokenFactory.address, amount)) + .to.emit(token, 'Transfer') + .withArgs(wallet.address, tokenFactory.address, amount); + + await expect( + tokenFactory.approveAndInterchainTransfer(tokenId, destinationChain, destinationAddress, amount, gasValue, { + value: gasValue, + }), + ) .to.emit(token, 'Approval') - .withArgs(tokenFactory.address, tokenManagerAddress, amount); + .withArgs(tokenFactory.address, tokenManagerAddress, amount) + .and.to.emit(token, 'Transfer') + .withArgs(tokenFactory.address, tokenManagerAddress, amount) + .and.to.emit(service, 'InterchainTransfer') + .withArgs(tokenId, destinationChain, destinationAddress, amount); }); it('Should transfer some tokens through the factory as the deployer', async () => { @@ -145,9 +159,14 @@ describe('InterchainTokenFactory', () => { 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 tokenFactory.populateTransaction.approveAndInterchainTransfer( + tokenId, + destinationChain, + destinationAddress, + amount, + gasValue, + ), ); await expect( diff --git a/test/TokenServiceFullFlow.js b/test/TokenServiceFullFlow.js index a869aa79..1ecae377 100644 --- a/test/TokenServiceFullFlow.js +++ b/test/TokenServiceFullFlow.js @@ -85,13 +85,9 @@ describe('Interchain Token Service Full Flow', () => { tx = await factory.populateTransaction.tokenTransferFrom(tokenId, totalMint); calls.push(tx.data); - // Approve total mint amount from the factory to the token manager contract - 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( + tx = await factory.populateTransaction.approveAndInterchainTransfer( tokenId, otherChains[i], wallet.address, @@ -129,7 +125,11 @@ describe('Interchain Token Service Full Flow', () => { .and.to.emit(token, 'Transfer') .withArgs(wallet.address, factory.address, totalMint) .and.to.emit(token, 'Approval') - .withArgs(factory.address, expectedTokenManagerAddress, totalMint) + .withArgs(factory.address, expectedTokenManagerAddress, mintAmount) + .and.to.emit(token, 'Transfer') + .withArgs(factory.address, expectedTokenManagerAddress, mintAmount) + .and.to.emit(token, 'Approval') + .withArgs(factory.address, expectedTokenManagerAddress, mintAmount) .and.to.emit(token, 'Transfer') .withArgs(factory.address, expectedTokenManagerAddress, mintAmount); });