diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 6e0d500b..f0a388f9 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -814,29 +814,17 @@ contract InterchainTokenService is destinationAddress = trustedAddress(destinationChain); } - if (gasValue > 0) { - if (metadataVersion == MetadataVersion.CONTRACT_CALL) { - gasService.payNativeGasForContractCall{ value: gasValue }( - address(this), - destinationChain, - destinationAddress, - payload, // solhint-disable-next-line avoid-tx-origin - tx.origin - ); - } else if (metadataVersion == MetadataVersion.EXPRESS_CALL) { - gasService.payNativeGasForExpressCall{ value: gasValue }( - address(this), - destinationChain, - destinationAddress, - payload, // solhint-disable-next-line avoid-tx-origin - tx.origin - ); - } else { - revert InvalidMetadataVersion(uint32(metadataVersion)); - } - } - - gateway.callContract(destinationChain, destinationAddress, payload); + (bool success, ) = tokenHandler.delegatecall( + abi.encodeWithSelector( + ITokenHandler.callContract.selector, + destinationChain, + destinationAddress, + payload, + metadataVersion, + gasValue + ) + ); + if (!success) revert CallContractFailed(); } /** @@ -862,33 +850,19 @@ contract InterchainTokenService is destinationAddress = trustedAddress(destinationChain); } - if (gasValue > 0) { - if (metadataVersion == MetadataVersion.CONTRACT_CALL) { - gasService.payNativeGasForContractCallWithToken{ value: gasValue }( - address(this), - destinationChain, - destinationAddress, - payload, - symbol, - amount, // solhint-disable-next-line avoid-tx-origin - tx.origin - ); - } else if (metadataVersion == MetadataVersion.EXPRESS_CALL) { - gasService.payNativeGasForExpressCallWithToken{ value: gasValue }( - address(this), - destinationChain, - destinationAddress, - payload, - symbol, - amount, // solhint-disable-next-line avoid-tx-origin - tx.origin - ); - } else { - revert InvalidMetadataVersion(uint32(metadataVersion)); - } - } - - gateway.callContractWithToken(destinationChain, destinationAddress, payload, symbol, amount); + (bool success, ) = tokenHandler.delegatecall( + abi.encodeWithSelector( + ITokenHandler.callContractWithToken.selector, + destinationChain, + destinationAddress, + payload, + symbol, + amount, + metadataVersion, + gasValue + ) + ); + if (!success) revert CallContractFailed(); } function _execute( diff --git a/contracts/TokenHandler.sol b/contracts/TokenHandler.sol index 95a82c47..0c8aaf76 100644 --- a/contracts/TokenHandler.sol +++ b/contracts/TokenHandler.sol @@ -6,6 +6,8 @@ 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 { ReentrancyGuard } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/ReentrancyGuard.sol'; +import { IAxelarGateway } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGateway.sol'; +import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IAxelarGasService.sol'; import { Create3AddressFixed } from './utils/Create3AddressFixed.sol'; import { ITokenManagerType } from './interfaces/ITokenManagerType.sol'; @@ -25,12 +27,14 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea using SafeTokenTransfer for IERC20; address public immutable gateway; + address public immutable gasService; uint256 internal constant UINT256_MAX = type(uint256).max; - constructor(address gateway_) { - if (gateway_ == address(0)) revert AddressZero(); + constructor(address gateway_, address gasService_) { + if (gateway_ == address(0) || gasService_ == address(0)) revert AddressZero(); gateway = gateway_; + gasService = gasService_; } /** @@ -173,6 +177,76 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard, Crea } } + function callContract( + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + MetadataVersion metadataVersion, + uint256 gasValue + ) external payable { + if (gasValue > 0) { + if (metadataVersion == MetadataVersion.CONTRACT_CALL) { + IAxelarGasService(gasService).payNativeGasForContractCall{ value: gasValue }( + address(this), + destinationChain, + destinationAddress, + payload, // solhint-disable-next-line avoid-tx-origin + tx.origin + ); + } else if (metadataVersion == MetadataVersion.EXPRESS_CALL) { + IAxelarGasService(gasService).payNativeGasForExpressCall{ value: gasValue }( + address(this), + destinationChain, + destinationAddress, + payload, // solhint-disable-next-line avoid-tx-origin + tx.origin + ); + } else { + revert InvalidMetadataVersion(uint32(metadataVersion)); + } + } + + IAxelarGateway(gateway).callContract(destinationChain, destinationAddress, payload); + } + + function callContractWithToken( + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + string calldata symbol, + uint256 amount, + MetadataVersion metadataVersion, + uint256 gasValue + ) external payable { + if (gasValue > 0) { + if (metadataVersion == MetadataVersion.CONTRACT_CALL) { + IAxelarGasService(gasService).payNativeGasForContractCallWithToken{ value: gasValue }( + address(this), + destinationChain, + destinationAddress, + payload, + symbol, + amount, // solhint-disable-next-line avoid-tx-origin + tx.origin + ); + } else if (metadataVersion == MetadataVersion.EXPRESS_CALL) { + IAxelarGasService(gasService).payNativeGasForExpressCallWithToken{ value: gasValue }( + address(this), + destinationChain, + destinationAddress, + payload, + symbol, + amount, // solhint-disable-next-line avoid-tx-origin + tx.origin + ); + } else { + revert InvalidMetadataVersion(uint32(metadataVersion)); + } + } + + IAxelarGateway(gateway).callContractWithToken(destinationChain, destinationAddress, payload, symbol, amount); + } + function _transferTokenFrom(address tokenAddress, address from, address to, uint256 amount) internal { // slither-disable-next-line arbitrary-send-erc20 IERC20(tokenAddress).safeTransferFrom(from, to, amount); diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 33787cca..d25bc570 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -49,6 +49,7 @@ interface IInterchainTokenService is error ZeroAmount(); error CannotDeploy(TokenManagerType); error InvalidGatewayTokenTransfer(bytes32 tokenId, bytes payload, string tokenSymbol, uint256 amount); + error CallContractFailed(); event InterchainTransfer( bytes32 indexed tokenId, diff --git a/contracts/interfaces/ITokenHandler.sol b/contracts/interfaces/ITokenHandler.sol index 992079c7..7e4faf1d 100644 --- a/contracts/interfaces/ITokenHandler.sol +++ b/contracts/interfaces/ITokenHandler.sol @@ -10,6 +10,12 @@ interface ITokenHandler { error UnsupportedTokenManagerType(uint256 tokenManagerType); error AddressZero(); error NotToken(address caller, address token); + error InvalidMetadataVersion(uint32 version); + + enum MetadataVersion { + CONTRACT_CALL, + EXPRESS_CALL + } /** * @notice Returns the address of the axelar gateway on this chain. @@ -62,4 +68,40 @@ interface ITokenHandler { * @param tokenManager The address of the token manager. */ function postTokenManagerDeploy(uint256 tokenManagerType, address tokenManager) external payable; + + /** + * @notice This function pays gas to the gas service and calls the gateway to transmit a CallContract. + * @param destinationChain The destination chain. + * @param destinationAddress The destination address. + * @param payload The payload to transmit. + * @param metadataVersion The metadata version is used to determine how to pay for gas. + * @param gasValue how much gas to pay. + */ + function callContract( + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + MetadataVersion metadataVersion, + uint256 gasValue + ) external payable; + + /** + * @notice This function pays gas to the gas service and calls the gateway to transmit a CallContractWithToken. + * @param destinationChain The destination chain. + * @param destinationAddress The destination address. + * @param payload The payload to transmit. + * @param metadataVersion The metadata version is used to determine how to pay for gas. + * @param symbol The gateway symbol. + * @param amount The amount of token to send. + * @param gasValue how much gas to pay. + */ + function callContractWithToken( + string calldata destinationChain, + string calldata destinationAddress, + bytes calldata payload, + string calldata symbol, + uint256 amount, + MetadataVersion metadataVersion, + uint256 gasValue + ) external payable; } diff --git a/hardhat.config.js b/hardhat.config.js index 88ea8008..aa8ac303 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -40,7 +40,7 @@ const itsCompilerSettings = { evmVersion: process.env.EVM_VERSION || 'london', optimizer: { ...optimizerSettings, - runs: 1, // Reduce runs to keep bytecode size under limit + runs: 1000, // Reduce runs to keep bytecode size under limit }, }, }; diff --git a/scripts/deploy.js b/scripts/deploy.js index b9708a4b..b4c9c8ed 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -93,7 +93,7 @@ async function deployAll( const interchainToken = await deployContract(wallet, 'InterchainToken', [interchainTokenServiceAddress]); const interchainTokenDeployer = await deployContract(wallet, 'InterchainTokenDeployer', [interchainToken.address]); const tokenManager = await deployContract(wallet, 'TokenManager', [interchainTokenServiceAddress]); - const tokenHandler = await deployContract(wallet, 'TokenHandler', [gateway.address]); + const tokenHandler = await deployContract(wallet, 'TokenHandler', [gateway.address, gasService.address]); const interchainTokenFactoryAddress = await getCreate3Address(create3Deployer.address, wallet, factoryDeploymentKey); diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 79da4587..d8bbc8bc 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -235,9 +235,15 @@ describe('Interchain Token Service', () => { }); describe('Interchain Token Service Deployment', () => { - it('Should revert on token handler deployment with invalid gateway address', async () => { + it('Should revert on token handler deployment with invalid gateway or gas service address', async () => { await expectRevert( - (gasOptions) => deployContract(wallet, 'TokenHandler', [AddressZero, gasOptions]), + (gasOptions) => deployContract(wallet, 'TokenHandler', [AddressZero, gasService.address, gasOptions]), + tokenHandler, + 'AddressZero', + ); + + await expectRevert( + (gasOptions) => deployContract(wallet, 'TokenHandler', [gateway.address, AddressZero, gasOptions]), tokenHandler, 'AddressZero', ); diff --git a/test/InterchainTokenServiceUpgradeFlow.js b/test/InterchainTokenServiceUpgradeFlow.js index 86c5f44e..c031f260 100644 --- a/test/InterchainTokenServiceUpgradeFlow.js +++ b/test/InterchainTokenServiceUpgradeFlow.js @@ -75,7 +75,7 @@ describe('Interchain Token Service Upgrade Flow', () => { tokenManagerDeployer = await deployContract(wallet, 'TokenManagerDeployer', []); interchainTokenDeployer = await deployContract(wallet, 'InterchainTokenDeployer', [interchainToken.address]); tokenManager = await deployContract(wallet, 'TokenManager', [interchainTokenServiceAddress]); - tokenHandler = await deployContract(wallet, 'TokenHandler', [gateway.address]); + tokenHandler = await deployContract(wallet, 'TokenHandler', [gateway.address, gasService.address]); interchainTokenFactoryAddress = await getCreate3Address(create3Deployer.address, wallet, deploymentKey + 'Factory'); axelarServiceGovernanceFactory = await ethers.getContractFactory(