diff --git a/contracts/TokenHandler.sol b/contracts/TokenHandler.sol index 14fac5a9..9ec396e2 100644 --- a/contracts/TokenHandler.sol +++ b/contracts/TokenHandler.sol @@ -43,7 +43,7 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea */ // slither-disable-next-line locked-ether function giveToken(bytes32 tokenId, address to, uint256 amount) external payable returns (uint256, address) { - address tokenManager = _create3Address(tokenId); + address tokenManager = _validTokenManagerAddress(tokenId); (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager).getImplementationTypeAndTokenAddress(); @@ -94,7 +94,8 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea address from, uint256 amount ) external payable returns (uint256, string memory symbol) { - address tokenManager = _create3Address(tokenId); + address tokenManager = _validTokenManagerAddress(tokenId); + (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager).getImplementationTypeAndTokenAddress(); if (tokenOnly && msg.sender != tokenAddress) revert NotToken(msg.sender, tokenAddress); @@ -133,7 +134,8 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea */ // slither-disable-next-line locked-ether function transferTokenFrom(bytes32 tokenId, address from, address to, uint256 amount) external payable returns (uint256, address) { - address tokenManager = _create3Address(tokenId); + address tokenManager = _validTokenManagerAddress(tokenId); + (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager).getImplementationTypeAndTokenAddress(); if ( @@ -228,4 +230,15 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea IERC20(tokenAddress).safeCall(abi.encodeWithSelector(IERC20.approve.selector, gateway, amount)); } } + + /** + * @notice Returns the address of a TokenManager from a specific tokenId. + * @dev The TokenManager needs to exist already. + * @param tokenId The tokenId. + * @return tokenManagerAddress_ The deployment address of the TokenManager. + */ + function _validTokenManagerAddress(bytes32 tokenId) internal view returns (address tokenManagerAddress_) { + tokenManagerAddress_ = _create3Address(tokenId); + if (tokenManagerAddress_.code.length == 0) revert TokenManagerDoesNotExist(tokenId); + } } diff --git a/contracts/interfaces/ITokenHandler.sol b/contracts/interfaces/ITokenHandler.sol index 992079c7..06ba8e10 100644 --- a/contracts/interfaces/ITokenHandler.sol +++ b/contracts/interfaces/ITokenHandler.sol @@ -10,6 +10,7 @@ interface ITokenHandler { error UnsupportedTokenManagerType(uint256 tokenManagerType); error AddressZero(); error NotToken(address caller, address token); + error TokenManagerDoesNotExist(bytes32 tokenId); /** * @notice Returns the address of the axelar gateway on this chain. diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index cb4e8c09..7eefe27a 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -1289,6 +1289,21 @@ describe('Interchain Token Service', () => { ); }); + it('Should revert on initiating an interchain token transfer for lockUnlock with invalid token id', async () => { + const [, , tokenId] = await deployFunctions.lockUnlock(service, 'Test Token lockUnlock', 'TT', 12, amount); + const invalidTokenId = tokenId.slice(0, -4) + '1111'; + + await expectRevert( + (gasOptions) => + service.interchainTransfer(invalidTokenId, destinationChain, destAddress, amount, '0x', gasValue, { + ...gasOptions, + value: gasValue, + }), + service, + 'TakeTokenFailed', + ); + }); + it('Should revert on initiate interchain token transfer with zero amount', async () => { await expectRevert( (gasOptions) => @@ -1698,6 +1713,35 @@ describe('Interchain Token Service', () => { .and.to.emit(service, 'InterchainTransferReceived') .withArgs(commandId, tokenId, sourceChain, hexlify(wallet.address), destAddress, amount, HashZero); }); + + it('Should revert with invalid token id', async () => { + const symbol = 'TT6'; + const [token, , tokenId] = await deployFunctions.gateway(service, 'Test Token Lock Unlock', symbol, 12, amount); + await token.transfer(gateway.address, amount).then((tx) => tx.wait); + const invalidTokenId = tokenId.slice(0, -4) + '1111'; + + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [MESSAGE_TYPE_INTERCHAIN_TRANSFER, invalidTokenId, hexlify(wallet.address), destAddress, amount, '0x'], + ); + + const commandId = await approveContractCallWithMint( + gateway, + sourceChain, + sourceAddress, + service.address, + payload, + symbol, + amount, + ); + + await expectRevert( + (gasOptions) => service.executeWithToken(commandId, sourceChain, sourceAddress, payload, symbol, amount, gasOptions), + service, + 'TokenManagerDoesNotExist', + [invalidTokenId], + ); + }); }); describe('Send Token With Data', () => { @@ -2560,6 +2604,21 @@ describe('Interchain Token Service', () => { .withArgs(wallet.address, destinationAddress, amount); }); + it('Should revert on express execute with invalid token id', async () => { + const invalidTokenId = tokenId.slice(0, -4) + '1111'; + + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', ' bytes'], + [MESSAGE_TYPE_INTERCHAIN_TRANSFER, invalidTokenId, sourceAddress, AddressZero, amount, data], + ); + + await expectRevert( + (gasOptions) => service.expressExecute(commandId, sourceChain, sourceAddress, payload, gasOptions), + service, + 'TokenHandlerFailed', + ); + }); + it('Should revert on express execute if token handler transfer token from fails', async () => { const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', ' bytes'],