diff --git a/contracts/examples/InterchainTokenExecutable.sol b/contracts/executable/InterchainTokenExecutable.sol similarity index 86% rename from contracts/examples/InterchainTokenExecutable.sol rename to contracts/executable/InterchainTokenExecutable.sol index 50124de8..67a6e35d 100644 --- a/contracts/examples/InterchainTokenExecutable.sol +++ b/contracts/executable/InterchainTokenExecutable.sol @@ -9,6 +9,8 @@ abstract contract InterchainTokenExecutable is IInterchainTokenExecutable { address public immutable interchainTokenService; + bytes32 internal constant EXECUTE_SUCCESS = keccak256('its-execute-success'); + constructor(address interchainTokenService_) { interchainTokenService = interchainTokenService_; } @@ -25,8 +27,9 @@ abstract contract InterchainTokenExecutable is IInterchainTokenExecutable { bytes32 tokenId, address token, uint256 amount - ) external onlyService { + ) external virtual onlyService returns (bytes32) { _executeWithInterchainToken(sourceChain, sourceAddress, data, tokenId, token, amount); + return EXECUTE_SUCCESS; } function _executeWithInterchainToken( diff --git a/contracts/examples/InterchainTokenExpressExecutable.sol b/contracts/executable/InterchainTokenExpressExecutable.sol similarity index 80% rename from contracts/examples/InterchainTokenExpressExecutable.sol rename to contracts/executable/InterchainTokenExpressExecutable.sol index d93eacac..9eb0f7ab 100644 --- a/contracts/examples/InterchainTokenExpressExecutable.sol +++ b/contracts/executable/InterchainTokenExpressExecutable.sol @@ -6,6 +6,8 @@ import { IInterchainTokenExpressExecutable } from '../interfaces/IInterchainToke import { InterchainTokenExecutable } from './InterchainTokenExecutable.sol'; abstract contract InterchainTokenExpressExecutable is IInterchainTokenExpressExecutable, InterchainTokenExecutable { + bytes32 internal constant EXPRESS_EXECUTE_SUCCESS = keccak256('its-express-execute-success'); + constructor(address interchainTokenService_) InterchainTokenExecutable(interchainTokenService_) {} function expressExecuteWithInterchainToken( @@ -15,7 +17,8 @@ abstract contract InterchainTokenExpressExecutable is IInterchainTokenExpressExe bytes32 tokenId, address token, uint256 amount - ) external onlyService { + ) external virtual onlyService returns (bytes32) { _executeWithInterchainToken(sourceChain, sourceAddress, data, tokenId, token, amount); + return EXPRESS_EXECUTE_SUCCESS; } } diff --git a/contracts/interchain-token-service/InterchainTokenService.sol b/contracts/interchain-token-service/InterchainTokenService.sol index 57320451..3f5e1582 100644 --- a/contracts/interchain-token-service/InterchainTokenService.sol +++ b/contracts/interchain-token-service/InterchainTokenService.sol @@ -5,6 +5,7 @@ pragma solidity ^0.8.0; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol'; import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; +import { ExpressExecutorTracker } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/express/ExpressExecutorTracker.sol'; import { Upgradable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/upgradable/Upgradable.sol'; import { Create3Address } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3Address.sol'; import { SafeTokenTransferFrom } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol'; @@ -21,7 +22,6 @@ import { ITokenManagerProxy } from '../interfaces/ITokenManagerProxy.sol'; import { IERC20Named } from '../interfaces/IERC20Named.sol'; import { AddressBytesUtils } from '../libraries/AddressBytesUtils.sol'; -import { ExpressCallHandler } from '../utils/ExpressCallHandler.sol'; import { Pausable } from '../utils/Pausable.sol'; import { Operatable } from '../utils/Operatable.sol'; import { Multicall } from '../utils/Multicall.sol'; @@ -33,13 +33,13 @@ import { Multicall } from '../utils/Multicall.sol'; * @dev The only storage used here is for ExpressCalls */ contract InterchainTokenService is - IInterchainTokenService, Upgradable, Operatable, - ExpressCallHandler, Pausable, Multicall, - Create3Address + Create3Address, + ExpressExecutorTracker, + IInterchainTokenService { using StringToBytes32 for string; using Bytes32ToString for bytes32; @@ -68,6 +68,8 @@ contract InterchainTokenService is uint256 private constant SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN = 4; bytes32 private constant CONTRACT_ID = keccak256('interchain-token-service'); + bytes32 private constant EXECUTE_SUCCESS = keccak256('its-execute-success'); + bytes32 private constant EXPRESS_EXECUTE_SUCCESS = keccak256('its-express-execute-success'); /** * @dev All of the variables passed here are stored as immutable variables. @@ -398,33 +400,69 @@ contract InterchainTokenService is ); } + // Returns the amount of token that this call is worth. If `tokenAddress` is `0`, then value is in terms of the native token, otherwise it's in terms of the token address. + function contractCallValue( + string calldata sourceChain, + string calldata sourceAddress, + bytes calldata payload + ) public view virtual onlyRemoteService(sourceChain, sourceAddress) notPaused returns (address, uint256) { + (uint256 selector, bytes32 tokenId, , uint256 amount) = abi.decode(payload, (uint256, bytes32, bytes, uint256)); + + if (selector != SELECTOR_RECEIVE_TOKEN && selector != SELECTOR_RECEIVE_TOKEN_WITH_DATA) { + revert InvalidExpressSelector(); + } + + ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); + return (tokenManager.tokenAddress(), amount); + } + + function expressExecute( + bytes32 commandId, + string calldata sourceChain, + string calldata sourceAddress, + bytes calldata payload + ) external payable notPaused { + uint256 selector = abi.decode(payload, (uint256)); + if (selector != SELECTOR_RECEIVE_TOKEN && selector != SELECTOR_RECEIVE_TOKEN_WITH_DATA) { + revert InvalidExpressSelector(); + } + if (gateway.isCommandExecuted(commandId)) revert AlreadyExecuted(); + + address expressExecutor = msg.sender; + bytes32 payloadHash = keccak256(payload); + + _setExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); + _expressExecute(sourceChain, payload); + + emit ExpressExecuted(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); + } + /** * @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing * interchainTransfer that matches the parameters passed here. * @dev This is not to be used with fee on transfer tokens as it will incur losses for the express caller. + * @param sourceChain the name of the chain where the interchainTransfer originated from. * @param payload the payload of the receive token - * @param commandId the sendHash detected at the sourceChain. */ - function expressReceiveToken(bytes calldata payload, bytes32 commandId, string calldata sourceChain) external { - if (gateway.isCommandExecuted(commandId)) revert AlreadyExecuted(commandId); - - address caller = msg.sender; - _setExpressReceiveToken(payload, commandId, caller); - - (uint256 selector, bytes32 tokenId, bytes memory destinationAddressBytes, uint256 amount) = abi.decode( + function _expressExecute(string calldata sourceChain, bytes calldata payload) internal { + (uint256 selector, bytes32 tokenId, bytes memory sourceAddress, bytes memory destinationAddressBytes, uint256 amount) = abi.decode( payload, - (uint256, bytes32, bytes, uint256) + (uint256, bytes32, bytes, bytes, uint256) ); address destinationAddress = destinationAddressBytes.toAddress(); - ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); - IERC20 token = IERC20(tokenManager.tokenAddress()); + IERC20 token; + { + ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); + token = IERC20(tokenManager.tokenAddress()); + } - token.safeTransferFrom(caller, destinationAddress, amount); + token.safeTransferFrom(msg.sender, destinationAddress, amount); if (selector == SELECTOR_RECEIVE_TOKEN_WITH_DATA) { - (, , , , bytes memory sourceAddress, bytes memory data) = abi.decode(payload, (uint256, bytes32, bytes, uint256, bytes, bytes)); - IInterchainTokenExpressExecutable(destinationAddress).expressExecuteWithInterchainToken( + (, , , , , bytes memory data) = abi.decode(payload, (uint256, bytes32, bytes, bytes, uint256, bytes)); + + bytes32 result = IInterchainTokenExpressExecutable(destinationAddress).expressExecuteWithInterchainToken( sourceChain, sourceAddress, data, @@ -432,8 +470,8 @@ contract InterchainTokenService is address(token), amount ); - } else if (selector != SELECTOR_RECEIVE_TOKEN) { - revert InvalidExpressSelector(); + + if (result != EXPRESS_EXECUTE_SUCCESS) revert ExpressExecuteWithInterchainTokenFailed(destinationAddress); } } @@ -443,7 +481,7 @@ contract InterchainTokenService is bytes calldata destinationAddress, uint256 amount, bytes calldata metadata - ) external { + ) external notPaused { ITokenManager tokenManager = ITokenManager(getTokenManagerAddress(tokenId)); amount = tokenManager.takeToken(msg.sender, amount); _transmitSendToken(tokenId, msg.sender, destinationChain, destinationAddress, amount, metadata); @@ -455,7 +493,7 @@ contract InterchainTokenService is bytes calldata destinationAddress, uint256 amount, bytes calldata data - ) external { + ) external notPaused { ITokenManager tokenManager = ITokenManager(getTokenManagerAddress(tokenId)); amount = tokenManager.takeToken(msg.sender, amount); uint32 prefix = 0; @@ -533,7 +571,7 @@ contract InterchainTokenService is /** * @notice Executes operations based on the payload and selector. * @param sourceChain The chain where the transaction originates from - * @param sourceAddress The address where the transaction originates from + * @param sourceAddress The address of the remote ITS where the transaction originates from * @param payload The encoded data payload for the transaction */ function execute( @@ -547,14 +585,43 @@ contract InterchainTokenService is if (!gateway.validateContractCall(commandId, sourceChain, sourceAddress, payloadHash)) revert NotApprovedByGateway(); uint256 selector = abi.decode(payload, (uint256)); - if (selector == SELECTOR_RECEIVE_TOKEN || selector == SELECTOR_RECEIVE_TOKEN_WITH_DATA) - return _processReceiveTokenPayload(commandId, sourceChain, payload, selector); + if (selector == SELECTOR_RECEIVE_TOKEN || selector == SELECTOR_RECEIVE_TOKEN_WITH_DATA) { + address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); + _processReceiveTokenPayload(expressExecutor, sourceChain, payload, selector); + + if (expressExecutor != address(0)) + emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); + + return; + } + if (selector == SELECTOR_DEPLOY_TOKEN_MANAGER) return _processDeployTokenManagerPayload(payload); if (selector == SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN) return _processDeployStandardizedTokenAndManagerPayload(payload); revert SelectorUnknown(); } + function contractCallWithTokenValue( + string calldata /*sourceChain*/, + string calldata /*sourceAddress*/, + bytes calldata /*payload*/, + string calldata /*symbol*/, + uint256 /*amount*/ + ) public view virtual returns (address, uint256) { + revert ExecuteWithTokenNotSupported(); + } + + function expressExecuteWithToken( + bytes32 /*commandId*/, + string calldata /*sourceChain*/, + string calldata /*sourceAddress*/, + bytes calldata /*payload*/, + string calldata /*tokenSymbol*/, + uint256 /*amount*/ + ) external payable { + revert ExecuteWithTokenNotSupported(); + } + function executeWithToken( bytes32 /*commandId*/, string calldata /*sourceChain*/, @@ -572,39 +639,40 @@ contract InterchainTokenService is * @param payload The encoded data payload to be processed */ function _processReceiveTokenPayload( - bytes32 commandId, + address expressExecutor, string calldata sourceChain, bytes calldata payload, uint256 selector ) internal { bytes32 tokenId; + bytes memory sourceAddress; address destinationAddress; uint256 amount; { bytes memory destinationAddressBytes; - (, tokenId, destinationAddressBytes, amount) = abi.decode(payload, (uint256, bytes32, bytes, uint256)); + (, tokenId, sourceAddress, destinationAddressBytes, amount) = abi.decode(payload, (uint256, bytes32, bytes, bytes, uint256)); destinationAddress = destinationAddressBytes.toAddress(); } ITokenManager tokenManager = ITokenManager(getValidTokenManagerAddress(tokenId)); - { - address expressCaller = _popExpressReceiveToken(payload, commandId); - if (expressCaller != address(0)) { - amount = tokenManager.giveToken(expressCaller, amount); - return; - } + + // Return token to the existing express caller + if (expressExecutor != address(0)) { + // slither-disable-next-line unused-return + tokenManager.giveToken(expressExecutor, amount); + return; } + amount = tokenManager.giveToken(destinationAddress, amount); if (selector == SELECTOR_RECEIVE_TOKEN_WITH_DATA) { - bytes memory sourceAddress; bytes memory data; - (, , , , sourceAddress, data) = abi.decode(payload, (uint256, bytes32, bytes, uint256, bytes, bytes)); + (, , , , , data) = abi.decode(payload, (uint256, bytes32, bytes, bytes, uint256, bytes)); // slither-disable-next-line reentrancy-events - emit TokenReceivedWithData(tokenId, sourceChain, destinationAddress, amount, sourceAddress, data); + emit TokenReceivedWithData(tokenId, sourceChain, sourceAddress, destinationAddress, amount); - IInterchainTokenExecutable(destinationAddress).executeWithInterchainToken( + bytes32 result = IInterchainTokenExecutable(destinationAddress).executeWithInterchainToken( sourceChain, sourceAddress, data, @@ -612,9 +680,11 @@ contract InterchainTokenService is tokenManager.tokenAddress(), amount ); + + if (result != EXECUTE_SUCCESS) revert ExecuteWithInterchainTokenFailed(destinationAddress); } else { // slither-disable-next-line reentrancy-events - emit TokenReceived(tokenId, sourceChain, destinationAddress, amount); + emit TokenReceived(tokenId, sourceChain, sourceAddress, destinationAddress, amount); } } @@ -878,7 +948,7 @@ contract InterchainTokenService is // slither-disable-next-line reentrancy-events emit TokenSent(tokenId, destinationChain, destinationAddress, amount); - payload = abi.encode(SELECTOR_RECEIVE_TOKEN, tokenId, destinationAddress, amount); + payload = abi.encode(SELECTOR_RECEIVE_TOKEN, tokenId, sourceAddress.toBytes(), destinationAddress, amount); _callContract(destinationChain, payload, msg.value); return; @@ -890,7 +960,7 @@ contract InterchainTokenService is // slither-disable-next-line reentrancy-events emit TokenSentWithData(tokenId, destinationChain, destinationAddress, amount, sourceAddress, metadata); - payload = abi.encode(SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, destinationAddress, amount, sourceAddress.toBytes(), metadata); + payload = abi.encode(SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddress.toBytes(), destinationAddress, amount, metadata); _callContract(destinationChain, payload, msg.value); } diff --git a/contracts/interfaces/IExpressCallHandler.sol b/contracts/interfaces/IExpressCallHandler.sol deleted file mode 100644 index 601fdb6e..00000000 --- a/contracts/interfaces/IExpressCallHandler.sol +++ /dev/null @@ -1,19 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -interface IExpressCallHandler { - error AlreadyExpressCalled(); - error SameDestinationAsCaller(); - - event ExpressReceive(bytes payload, bytes32 indexed sendHash, address indexed expressCaller); - event ExpressExecutionFulfilled(bytes payload, bytes32 indexed sendHash, address indexed expressCaller); - - /** - * @notice Gets the address of the express caller for a specific token transfer - * @param payload the payload for the receive token - * @param commandId The unique hash for this token transfer - * @return expressCaller The address of the express caller for this token transfer - */ - function getExpressReceiveToken(bytes calldata payload, bytes32 commandId) external view returns (address expressCaller); -} diff --git a/contracts/interfaces/IInterchainTokenExecutable.sol b/contracts/interfaces/IInterchainTokenExecutable.sol index 319c84d6..208d2e67 100644 --- a/contracts/interfaces/IInterchainTokenExecutable.sol +++ b/contracts/interfaces/IInterchainTokenExecutable.sol @@ -24,5 +24,5 @@ interface IInterchainTokenExecutable { bytes32 tokenId, address token, uint256 amount - ) external; + ) external returns (bytes32); } diff --git a/contracts/interfaces/IInterchainTokenExpressExecutable.sol b/contracts/interfaces/IInterchainTokenExpressExecutable.sol index 3b78a26d..b8673942 100644 --- a/contracts/interfaces/IInterchainTokenExpressExecutable.sol +++ b/contracts/interfaces/IInterchainTokenExpressExecutable.sol @@ -26,5 +26,5 @@ interface IInterchainTokenExpressExecutable is IInterchainTokenExecutable { bytes32 tokenId, address token, uint256 amount - ) external; + ) external returns (bytes32); } diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 91ff49e0..7656c3da 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -2,15 +2,14 @@ pragma solidity ^0.8.0; -import { IAxelarExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarExecutable.sol'; +import { IAxelarValuedExpressExecutable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarValuedExpressExecutable.sol'; import { IContractIdentifier } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IContractIdentifier.sol'; -import { IExpressCallHandler } from './IExpressCallHandler.sol'; import { ITokenManagerType } from './ITokenManagerType.sol'; import { IPausable } from './IPausable.sol'; import { IMulticall } from './IMulticall.sol'; -interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAxelarExecutable, IPausable, IMulticall, IContractIdentifier { +interface IInterchainTokenService is ITokenManagerType, IAxelarValuedExpressExecutable, IPausable, IMulticall, IContractIdentifier { error ZeroAddress(); error LengthMismatch(); error InvalidTokenManagerImplementation(); @@ -18,6 +17,7 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx error TokenManagerDoesNotExist(bytes32 tokenId); error NotTokenManager(); error ExecuteWithInterchainTokenFailed(address contractAddress); + error ExpressExecuteWithInterchainTokenFailed(address contractAddress); error NotCanonicalTokenManager(); error GatewayToken(); error TokenManagerDeploymentFailed(); @@ -25,7 +25,6 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx error DoesNotAcceptExpressExecute(address contractAddress); error SelectorUnknown(); error InvalidMetadataVersion(uint32 version); - error AlreadyExecuted(bytes32 commandId); error ExecuteWithTokenNotSupported(); error InvalidExpressSelector(); @@ -38,14 +37,19 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx address indexed sourceAddress, bytes data ); - event TokenReceived(bytes32 indexed tokenId, string sourceChain, address indexed destinationAddress, uint256 indexed amount); - event TokenReceivedWithData( + event TokenReceived( bytes32 indexed tokenId, string sourceChain, + bytes sourceAddress, address indexed destinationAddress, - uint256 indexed amount, + uint256 indexed amount + ); + event TokenReceivedWithData( + bytes32 indexed tokenId, + string sourceChain, bytes sourceAddress, - bytes data + address indexed destinationAddress, + uint256 indexed amount ); event RemoteTokenManagerDeploymentInitialized( bytes32 indexed tokenId, @@ -295,11 +299,4 @@ interface IInterchainTokenService is ITokenManagerType, IExpressCallHandler, IAx * @param paused The boolean value indicating whether the contract is paused or not. */ function setPaused(bool paused) external; - - /** - * @notice Uses the caller's tokens to fullfill a sendCall ahead of time. Use this only if you have detected an outgoing interchainTransfer that matches the parameters passed here. - * @param payload the payload of the receive token - * @param commandId the commandId calculated from the event at the sourceChain. - */ - function expressReceiveToken(bytes calldata payload, bytes32 commandId, string calldata sourceChain) external; } diff --git a/contracts/test/InterchainExecutableTest.sol b/contracts/test/InterchainExecutableTest.sol index 21c4b0d3..5ea53999 100644 --- a/contracts/test/InterchainExecutableTest.sol +++ b/contracts/test/InterchainExecutableTest.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import { InterchainTokenExpressExecutable } from '../examples/InterchainTokenExpressExecutable.sol'; +import { InterchainTokenExpressExecutable } from '../executable/InterchainTokenExpressExecutable.sol'; import { IInterchainTokenService } from '../interfaces/IInterchainTokenService.sol'; import { IERC20 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IERC20.sol'; diff --git a/contracts/test/utils/ExpressCallHandlerTest.sol b/contracts/test/utils/ExpressCallHandlerTest.sol deleted file mode 100644 index 92cb8b12..00000000 --- a/contracts/test/utils/ExpressCallHandlerTest.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { ExpressCallHandler } from '../../utils/ExpressCallHandler.sol'; - -contract ExpressCallHandlerTest is ExpressCallHandler { - address public lastPoppedExpressCaller; - - function setExpressReceiveToken(bytes calldata payload, bytes32 commandId, address expressCaller) external { - _setExpressReceiveToken(payload, commandId, expressCaller); - } - - function popExpressReceiveToken(bytes calldata payload, bytes32 commandId) external { - lastPoppedExpressCaller = _popExpressReceiveToken(payload, commandId); - } -} diff --git a/contracts/utils/ExpressCallHandler.sol b/contracts/utils/ExpressCallHandler.sol deleted file mode 100644 index baab3951..00000000 --- a/contracts/utils/ExpressCallHandler.sol +++ /dev/null @@ -1,81 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity ^0.8.0; - -import { IExpressCallHandler } from '../interfaces/IExpressCallHandler.sol'; - -/** - * @title ExpressCallHandler - * @dev Integrates the interchain token service with the GMP express service by providing methods to handle express calls for - * token transfers and token transfers with contract calls between chains. Implements the IExpressCallHandler interface. - */ -contract ExpressCallHandler is IExpressCallHandler { - // uint256(keccak256('prefix-express-give-token')); - uint256 internal constant PREFIX_EXPRESS_RECEIVE_TOKEN = 0x67c7b41c1cb0375e36084c4ec399d005168e83425fa471b9224f6115af865619; - - /** - * @notice Calculates the unique slot for a given express token transfer. - * @param payload the payload of the receive token - * @param commandId The unique hash for this token transfer - * @return slot The calculated slot for this token transfer - */ - function _getExpressReceiveTokenSlot(bytes calldata payload, bytes32 commandId) internal pure returns (uint256 slot) { - slot = uint256(keccak256(abi.encode(PREFIX_EXPRESS_RECEIVE_TOKEN, payload, commandId))); - } - - /** - * @notice Stores the address of the express caller at the storage slot determined by _getExpressSendTokenSlot - * @param payload The payload for the receive token - * @param commandId The unique hash for this token transfer - * @param expressCaller The address of the express caller - */ - function _setExpressReceiveToken(bytes calldata payload, bytes32 commandId, address expressCaller) internal { - uint256 slot = _getExpressReceiveTokenSlot(payload, commandId); - address prevExpressCaller; - assembly { - prevExpressCaller := sload(slot) - } - - if (prevExpressCaller != address(0)) revert AlreadyExpressCalled(); - - assembly { - sstore(slot, expressCaller) - } - - emit ExpressReceive(payload, commandId, expressCaller); - } - - /** - * @notice Gets the address of the express caller for a specific token transfer - * @param payload The payload for the receive token - * @param commandId The unique hash for this token transfer - * @return expressCaller The address of the express caller for this token transfer - */ - function getExpressReceiveToken(bytes calldata payload, bytes32 commandId) public view returns (address expressCaller) { - uint256 slot = _getExpressReceiveTokenSlot(payload, commandId); - assembly { - expressCaller := sload(slot) - } - } - - /** - * @notice Removes the express caller from storage for a specific token transfer, if it exists. - * @param payload the payload for the receive token - * @param commandId The unique hash for this token transfer - * @return expressCaller The address of the express caller for this token transfer - */ - function _popExpressReceiveToken(bytes calldata payload, bytes32 commandId) internal returns (address expressCaller) { - uint256 slot = _getExpressReceiveTokenSlot(payload, commandId); - assembly { - expressCaller := sload(slot) - } - - if (expressCaller != address(0)) { - assembly { - sstore(slot, 0) - } - - emit ExpressExecutionFulfilled(payload, commandId, expressCaller); - } - } -} diff --git a/hardhat.config.js b/hardhat.config.js index 80120b46..b63513e8 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -20,7 +20,7 @@ module.exports = { evmVersion: process.env.EVM_VERSION || 'london', optimizer: { enabled: true, - runs: 99999, + runs: 1000, details: { peephole: process.env.COVERAGE === undefined, inliner: process.env.COVERAGE === undefined, diff --git a/test/TokenService.js b/test/TokenService.js index 693bd8ca..16b61fea 100644 --- a/test/TokenService.js +++ b/test/TokenService.js @@ -5,7 +5,7 @@ const { expect } = chai; require('dotenv').config(); const { ethers } = require('hardhat'); const { AddressZero, MaxUint256 } = ethers.constants; -const { defaultAbiCoder, solidityPack, keccak256, arrayify } = ethers.utils; +const { defaultAbiCoder, solidityPack, keccak256, arrayify, hexlify } = ethers.utils; const { Contract, Wallet } = ethers; const TokenManager = require('../artifacts/contracts/token-manager/TokenManager.sol/TokenManager.json'); const Token = require('../artifacts/contracts/interfaces/IStandardizedToken.sol/IStandardizedToken.json'); @@ -22,8 +22,8 @@ const { deployTokenManagerImplementations, } = require('../scripts/deploy'); -const SELECTOR_SEND_TOKEN = 1; -const SELECTOR_SEND_TOKEN_WITH_DATA = 2; +const SELECTOR_RECEIVE_TOKEN = 1; +const SELECTOR_RECEIVE_TOKEN_WITH_DATA = 2; const SELECTOR_DEPLOY_TOKEN_MANAGER = 3; const SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN = 4; const INVALID_SELECTOR = 5; @@ -1217,8 +1217,8 @@ describe('Interchain Token Service', () => { const [token, tokenManager, tokenId] = await deployFunctions[type](`Test Token ${type}`, 'TT', 12, amount); const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, sendAmount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, sendAmount], ); const payloadHash = keccak256(payload); @@ -1288,8 +1288,8 @@ describe('Interchain Token Service', () => { (await await token.transfer(tokenManager.address, amount)).wait(); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const commandId = await approveContractCall(gateway, sourceChain, wallet.address, service.address, payload); @@ -1305,8 +1305,8 @@ describe('Interchain Token Service', () => { (await await token.transfer(tokenManager.address, amount)).wait(); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -1356,8 +1356,8 @@ describe('Interchain Token Service', () => { (await await token.transfer(tokenManager.address, amount)).wait(); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -1365,15 +1365,15 @@ describe('Interchain Token Service', () => { .to.emit(token, 'Transfer') .withArgs(tokenManager.address, destAddress, amount) .and.to.emit(service, 'TokenReceived') - .withArgs(tokenId, sourceChain, destAddress, amount); + .withArgs(tokenId, sourceChain, hexlify(wallet.address), destAddress, amount); }); it('Should be able to receive mint/burn token', async () => { const [token, , tokenId] = await deployFunctions.mintBurn(`Test Token Mint Burn`, 'TT', 12, 0); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -1381,7 +1381,7 @@ describe('Interchain Token Service', () => { .to.emit(token, 'Transfer') .withArgs(AddressZero, destAddress, amount) .and.to.emit(service, 'TokenReceived') - .withArgs(tokenId, sourceChain, destAddress, amount); + .withArgs(tokenId, sourceChain, hexlify(wallet.address), destAddress, amount); }); it('Should be able to receive lock/unlock with fee on transfer token', async () => { @@ -1389,8 +1389,8 @@ describe('Interchain Token Service', () => { (await await token.transfer(tokenManager.address, amount + 10)).wait(); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -1398,7 +1398,7 @@ describe('Interchain Token Service', () => { .to.emit(token, 'Transfer') .withArgs(tokenManager.address, destAddress, amount) .and.to.emit(service, 'TokenReceived') - .withArgs(tokenId, sourceChain, destAddress, amount - 10); + .withArgs(tokenId, sourceChain, hexlify(wallet.address), destAddress, amount - 10); }); }); @@ -1418,8 +1418,8 @@ describe('Interchain Token Service', () => { const [token, tokenManager, tokenId] = await deployFunctions[type](`Test Token ${type}`, 'TT', 12, amount); const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, sendAmount, sourceAddress, data], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddress, destAddress, sendAmount, data], ); const payloadHash = keccak256(payload); @@ -1447,8 +1447,8 @@ describe('Interchain Token Service', () => { const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const metadata = '0x00000000'; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, sendAmount, sourceAddress, '0x'], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddress, destAddress, sendAmount, '0x'], ); const payloadHash = keccak256(payload); @@ -1473,8 +1473,8 @@ describe('Interchain Token Service', () => { const [token, tokenManager, tokenId] = await deployFunctions[type](`Test Token ${type}`, 'TT', 12, amount); const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, sendAmount, sourceAddress, data], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddress, destAddress, sendAmount, data], ); const payloadHash = keccak256(payload); @@ -1526,8 +1526,8 @@ describe('Interchain Token Service', () => { const msg = `lock/unlock`; const data = defaultAbiCoder.encode(['address', 'string'], [wallet.address, msg]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, amount, sourceAddressForService, data], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddressForService, destAddress, amount, data], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -1537,7 +1537,7 @@ describe('Interchain Token Service', () => { .to.emit(token, 'Transfer') .withArgs(destAddress, wallet.address, amount) .and.to.emit(service, 'TokenReceivedWithData') - .withArgs(tokenId, sourceChain, destAddress, amount, sourceAddressForService, data) + .withArgs(tokenId, sourceChain, sourceAddressForService, destAddress, amount) .and.to.emit(executable, 'MessageReceived') .withArgs(sourceChain, sourceAddressForService, wallet.address, msg, tokenId, amount); @@ -1550,8 +1550,8 @@ describe('Interchain Token Service', () => { const msg = `mint/burn`; const data = defaultAbiCoder.encode(['address', 'string'], [wallet.address, msg]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, amount, sourceAddressForService, data], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddressForService, destAddress, amount, data], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -1561,7 +1561,7 @@ describe('Interchain Token Service', () => { .to.emit(token, 'Transfer') .withArgs(destAddress, wallet.address, amount) .and.to.emit(service, 'TokenReceivedWithData') - .withArgs(tokenId, sourceChain, destAddress, amount, sourceAddressForService, data) + .withArgs(tokenId, sourceChain, sourceAddressForService, destAddress, amount) .and.to.emit(executable, 'MessageReceived') .withArgs(sourceChain, sourceAddressForService, wallet.address, msg, tokenId, amount); @@ -1574,8 +1574,8 @@ describe('Interchain Token Service', () => { const msg = `lock/unlock`; const data = defaultAbiCoder.encode(['address', 'string'], [wallet.address, msg]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, amount, sourceAddressForService, data], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddressForService, destAddress, amount, data], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); @@ -1585,7 +1585,7 @@ describe('Interchain Token Service', () => { .to.emit(token, 'Transfer') .withArgs(destAddress, wallet.address, amount - 10) .and.to.emit(service, 'TokenReceivedWithData') - .withArgs(tokenId, sourceChain, destAddress, amount - 10, sourceAddressForService, data) + .withArgs(tokenId, sourceChain, sourceAddressForService, destAddress, amount - 10) .and.to.emit(executable, 'MessageReceived') .withArgs(sourceChain, sourceAddressForService, wallet.address, msg, tokenId, amount - 10); @@ -1604,8 +1604,8 @@ describe('Interchain Token Service', () => { const [token, tokenManager, tokenId] = await deployFunctions[type](`Test Token ${type}`, 'TT', 12, amount, true); const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, sendAmount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, sendAmount], ); const payloadHash = keccak256(payload); @@ -1630,8 +1630,8 @@ describe('Interchain Token Service', () => { const [token, tokenManager, tokenId] = await deployFunctions[type](`Test Token ${type}`, 'TT', 12, amount, true); const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, sendAmount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, sendAmount], ); const payloadHash = keccak256(payload); @@ -1665,8 +1665,8 @@ describe('Interchain Token Service', () => { const [token, tokenManager, tokenId] = await deployFunctions.lockUnlock(`Test Token LockUnlock`, 'TT', 12, amount, true); const sendAmount = amount; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, sendAmount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, sendAmount], ); const payloadHash = keccak256(payload); @@ -1709,8 +1709,8 @@ describe('Interchain Token Service', () => { const sendAmount = type === 'lockUnlockFee' ? amount - 10 : amount; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, sendAmount, sourceAddress, data], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddress, destAddress, sendAmount, data], ); const payloadHash = keccak256(payload); @@ -1757,24 +1757,24 @@ describe('Interchain Token Service', () => { it('Should express execute', async () => { const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destinationAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destinationAddress, amount], ); - await expect(service.expressReceiveToken(payload, commandId, sourceChain)) - .to.emit(service, 'ExpressReceive') - .withArgs(payload, commandId, wallet.address) + await expect(service.expressExecute(commandId, sourceChain, sourceAddress, payload)) + .to.emit(service, 'ExpressExecuted') + .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address) .and.to.emit(token, 'Transfer') .withArgs(wallet.address, destinationAddress, amount); }); it('Should express execute with token', async () => { const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', ' bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, executable.address, amount, sourceAddress, data], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', ' bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddress, executable.address, amount, data], ); - await expect(service.expressReceiveToken(payload, commandId, sourceChain)) - .to.emit(service, 'ExpressReceive') - .withArgs(payload, commandId, wallet.address) + await expect(service.expressExecute(commandId, sourceChain, sourceAddress, payload)) + .to.emit(service, 'ExpressExecuted') + .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address) .and.to.emit(token, 'Transfer') .withArgs(wallet.address, executable.address, amount) .and.to.emit(token, 'Transfer') @@ -1799,15 +1799,15 @@ describe('Interchain Token Service', () => { await (await token.approve(service.address, amount)).wait(); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); await gateway.setCommandExecuted(commandId, true).then((tx) => tx.wait()); await expectRevert( - (gasOptions) => service.expressReceiveToken(payload, commandId, sourceChain, gasOptions), + (gasOptions) => service.expressExecute(commandId, sourceChain, sourceAddress, payload, gasOptions), service, 'AlreadyExecuted', ); @@ -1825,7 +1825,7 @@ describe('Interchain Token Service', () => { const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); await expectRevert( - (gasOptions) => service.expressReceiveToken(payload, commandId, sourceChain, gasOptions), + (gasOptions) => service.expressExecute(commandId, sourceChain, sourceAddress, payload, gasOptions), service, 'InvalidExpressSelector', ); @@ -1837,18 +1837,18 @@ describe('Interchain Token Service', () => { await (await token.approve(service.address, amount)).wait(); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); + await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') .withArgs(tokenManager.address, wallet.address, amount) .and.to.emit(service, 'ExpressExecutionFulfilled') - .withArgs(payload, commandId, wallet.address); + .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address); }); it('Should be able to receive mint/burn token', async () => { @@ -1857,18 +1857,18 @@ describe('Interchain Token Service', () => { await (await token.approve(service.address, amount)).wait(); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); + await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') .withArgs(AddressZero, wallet.address, amount) .and.to.emit(service, 'ExpressExecutionFulfilled') - .withArgs(payload, commandId, wallet.address); + .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address); }); it('Should be able to receive lock/unlock with fee on transfer token', async () => { @@ -1877,18 +1877,18 @@ describe('Interchain Token Service', () => { await (await token.approve(service.address, amount)).wait(); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); + await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') .withArgs(tokenManager.address, wallet.address, amount) .and.to.emit(service, 'ExpressExecutionFulfilled') - .withArgs(payload, commandId, wallet.address); + .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address); }); }); @@ -1913,19 +1913,19 @@ describe('Interchain Token Service', () => { const msg = `lock/unlock`; const data = defaultAbiCoder.encode(['address', 'string'], [wallet.address, msg]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, amount, sourceAddressForService, data], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddressForService, destAddress, amount, data], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); + await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); const tx = service.execute(commandId, sourceChain, sourceAddress, payload); await expect(tx) .to.emit(token, 'Transfer') .withArgs(tokenManager.address, wallet.address, amount) .and.to.emit(service, 'ExpressExecutionFulfilled') - .withArgs(payload, commandId, wallet.address); + .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address); expect(await executable.lastMessage()).to.equal(msg); }); @@ -1937,19 +1937,19 @@ describe('Interchain Token Service', () => { const msg = `mint/burn`; const data = defaultAbiCoder.encode(['address', 'string'], [wallet.address, msg]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, amount, sourceAddressForService, data], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddressForService, destAddress, amount, data], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await (await service.expressReceiveToken(payload, commandId, sourceChain)).wait(); + await (await service.expressExecute(commandId, sourceChain, sourceAddress, payload)).wait(); await expect(service.execute(commandId, sourceChain, sourceAddress, payload)) .to.emit(token, 'Transfer') .withArgs(AddressZero, wallet.address, amount) .and.to.emit(service, 'ExpressExecutionFulfilled') - .withArgs(payload, commandId, wallet.address); + .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address); expect(await executable.lastMessage()).to.equal(msg); }); @@ -1962,12 +1962,12 @@ describe('Interchain Token Service', () => { const msg = `lock/unlock`; const data = defaultAbiCoder.encode(['address', 'string'], [wallet.address, msg]); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256', 'bytes', 'bytes'], - [SELECTOR_SEND_TOKEN_WITH_DATA, tokenId, destAddress, amount, sourceAddressForService, data], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [SELECTOR_RECEIVE_TOKEN_WITH_DATA, tokenId, sourceAddressForService, destAddress, amount, data], ); const commandId = await approveContractCall(gateway, sourceChain, sourceAddress, service.address, payload); - await expect(service.expressReceiveToken(payload, commandId, sourceChain)).to.be.reverted; + await expect(service.expressExecute(commandId, sourceChain, sourceAddress, payload)).to.be.reverted; }); }); @@ -2004,8 +2004,8 @@ describe('Interchain Token Service', () => { async function receiveToken(sendAmount) { const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, wallet.address, sendAmount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), wallet.address, sendAmount], ); const commandId = await approveContractCall(gateway, destinationChain, service.address, service.address, payload); diff --git a/test/TokenServiceFullFlow.js b/test/TokenServiceFullFlow.js index a02bc4ce..9f98cd43 100644 --- a/test/TokenServiceFullFlow.js +++ b/test/TokenServiceFullFlow.js @@ -5,7 +5,7 @@ const { expect } = chai; require('dotenv').config(); const { ethers } = require('hardhat'); const { AddressZero } = ethers.constants; -const { defaultAbiCoder, keccak256 } = ethers.utils; +const { defaultAbiCoder, keccak256, hexlify } = ethers.utils; const { Contract, Wallet } = ethers; const StandardizedToken = require('../artifacts/contracts/token-implementations/StandardizedToken.sol/StandardizedToken.json'); @@ -15,7 +15,7 @@ const ITokenManagerMintBurn = require('../artifacts/contracts/interfaces/ITokenM const { getRandomBytes32, expectRevert } = require('./utils'); const { deployAll, deployContract } = require('../scripts/deploy'); -const SELECTOR_SEND_TOKEN = 1; +const SELECTOR_RECEIVE_TOKEN = 1; const SELECTOR_DEPLOY_TOKEN_MANAGER = 3; const SELECTOR_DEPLOY_AND_REGISTER_STANDARDIZED_TOKEN = 4; @@ -93,8 +93,8 @@ describe('Interchain Token Service Full Flow', () => { const gasValue = 6789; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const payloadHash = keccak256(payload); @@ -209,8 +209,8 @@ describe('Interchain Token Service Full Flow', () => { const gasValue = 6789; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const payloadHash = keccak256(payload); @@ -329,8 +329,8 @@ describe('Interchain Token Service Full Flow', () => { const gasValue = 6789; const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [SELECTOR_SEND_TOKEN, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [SELECTOR_RECEIVE_TOKEN, tokenId, hexlify(wallet.address), destAddress, amount], ); const payloadHash = keccak256(payload); diff --git a/test/UtilsTest.js b/test/UtilsTest.js index ac366763..4354c048 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -155,56 +155,6 @@ describe('Distributable', () => { }); }); -describe('ExpressCallHandler', () => { - let handler; - const expressCaller = new Wallet(getRandomBytes32()).address; - const payload = '0x5678'; - - before(async () => { - handler = await deployContract(ownerWallet, 'ExpressCallHandlerTest'); - }); - - it('Should be able to set an express receive token', async () => { - const commandId = getRandomBytes32(); - - await expect(handler.setExpressReceiveToken(payload, commandId, expressCaller)) - .to.emit(handler, 'ExpressReceive') - .withArgs(payload, commandId, expressCaller); - - expect(await handler.getExpressReceiveToken(payload, commandId)).to.equal(expressCaller); - }); - - it('Should not be able to set an express receive token if it is already set', async () => { - const commandId = getRandomBytes32(); - await expect(handler.setExpressReceiveToken(payload, commandId, expressCaller)) - .to.emit(handler, 'ExpressReceive') - .withArgs(payload, commandId, expressCaller); - expect(await handler.getExpressReceiveToken(payload, commandId)).to.equal(expressCaller); - - const newExpressCaller = new Wallet(getRandomBytes32()).address; - await expectRevert( - (gasOptions) => handler.setExpressReceiveToken(payload, commandId, newExpressCaller, gasOptions), - handler, - 'AlreadyExpressCalled', - ); - }); - - it('Should properly pop an express receive token', async () => { - const commandId = getRandomBytes32(); - await expect(handler.popExpressReceiveToken(payload, commandId)).to.not.emit(handler, 'ExpressExecutionFulfilled'); - - expect(await handler.lastPoppedExpressCaller()).to.equal(AddressZero); - - await (await handler.setExpressReceiveToken(payload, commandId, expressCaller)).wait(); - - await expect(handler.popExpressReceiveToken(payload, commandId)) - .to.emit(handler, 'ExpressExecutionFulfilled') - .withArgs(payload, commandId, expressCaller); - - expect(await handler.lastPoppedExpressCaller()).to.equal(expressCaller); - }); -}); - describe('FlowLimit', async () => { let test; const flowLimit = isHardhat ? 5 : 2; @@ -312,7 +262,7 @@ describe('Mutlicall', () => { await expect(test.multicall([function1Data, function2Data, function2Data, function1Data])) .to.emit(test, 'Function1Called') - .withArgs(nonce + 0) + .withArgs(nonce) .and.to.emit(test, 'Function2Called') .withArgs(nonce + 1) .and.to.emit(test, 'Function2Called') @@ -326,7 +276,7 @@ describe('Mutlicall', () => { await expect(test.multicallTest([function2Data, function1Data, function2Data, function2Data])) .to.emit(test, 'Function2Called') - .withArgs(nonce + 0) + .withArgs(nonce) .and.to.emit(test, 'Function1Called') .withArgs(nonce + 1) .and.to.emit(test, 'Function2Called') @@ -347,7 +297,7 @@ describe('Mutlicall', () => { await expect(test.multicall([function1Data, function2Data, function3Data, function1Data])) .to.emit(test, 'Function1Called') - .withArgs(nonce + 0) + .withArgs(nonce) .and.to.emit(test, 'Function2Called') .withArgs(nonce + 1).to.be.reverted; });