From 48f60e5582b91f6c19e485622429ea59ead9c1d2 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 6 Feb 2024 17:36:28 +0200 Subject: [PATCH 01/38] Added additional checks but code size is too large --- contracts/InterchainTokenService.sol | 37 +++++++++++++++++-- .../interfaces/IInterchainTokenService.sol | 1 + 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index a0e41ac5..d1f80d1c 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -655,9 +655,10 @@ contract InterchainTokenService is string calldata sourceChain, string calldata sourceAddress, bytes calldata payload, - string calldata /*tokenSymbol*/, - uint256 /*amount*/ + string calldata tokenSymbol, + uint256 amount ) external payable { + _checkPayloadAgainstGatewayData(payload, tokenSymbol, amount); // It should be ok to ignore the symbol and amount since this info exists on the payload. expressExecute(commandId, sourceChain, sourceAddress, payload); } @@ -675,7 +676,37 @@ contract InterchainTokenService is if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) revert NotApprovedByGateway(); - _execute(commandId, sourceChain, sourceAddress, payload, payloadHash); + uint256 messageType = abi.decode(payload, (uint256)); + if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) { + revert InvalidExpressMessageType(messageType); + } + + _checkPayloadAgainstGatewayData(payload, tokenSymbol, amount); + + address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); + + if (expressExecutor != address(0)) { + emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); + } + + _processInterchainTransferPayload( + commandId, + expressExecutor, + sourceChain, + payload + ); + } + + function _checkPayloadAgainstGatewayData(bytes calldata payload, string calldata tokenSymbol, uint256 amount) internal view { + (, bytes32 tokenId, , , uint256 amountInPayload) = abi.decode( + payload, + (uint256, bytes32, uint256, uint256, uint256) + ); + + if( + validTokenAddress(tokenId) != gateway.tokenAddresses(tokenSymbol) || + amount != amountInPayload + ) revert CallWithTokenMissmatch(payload, tokenSymbol, amount); } /** diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 6913b827..057f445d 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -47,6 +47,7 @@ interface IInterchainTokenService is error TokenHandlerFailed(bytes data); error EmptyData(); error PostDeployFailed(bytes data); + error CallWithTokenMissmatch(bytes payload, string tokenSymbol, uint256 amount); event InterchainTransfer( bytes32 indexed tokenId, From fc8cb1aae0b3e8571ea1c0643cfca3600c4e52bd Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 7 Feb 2024 15:02:37 +0200 Subject: [PATCH 02/38] Added value for contractCallWithToken and optimized contract size --- contracts/InterchainTokenService.sol | 31 ++++++++++------------------ hardhat.config.js | 26 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 20 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index d1f80d1c..119e0695 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -641,13 +641,14 @@ contract InterchainTokenService is } function contractCallWithTokenValue( - string calldata /*sourceChain*/, - string calldata /*sourceAddress*/, - bytes calldata /*payload*/, - string calldata /*symbol*/, - uint256 /*amount*/ + string calldata sourceChain, + string calldata sourceAddress, + bytes calldata payload, + string calldata symbol, + uint256 amount ) public view virtual returns (address, uint256) { - revert ExecuteWithTokenNotSupported(); + _checkPayloadAgainstGatewayData(payload, symbol, amount); + return contractCallValue(sourceChain, sourceAddress, payload); } function expressExecuteWithToken( @@ -689,24 +690,14 @@ contract InterchainTokenService is emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); } - _processInterchainTransferPayload( - commandId, - expressExecutor, - sourceChain, - payload - ); + _processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload); } function _checkPayloadAgainstGatewayData(bytes calldata payload, string calldata tokenSymbol, uint256 amount) internal view { - (, bytes32 tokenId, , , uint256 amountInPayload) = abi.decode( - payload, - (uint256, bytes32, uint256, uint256, uint256) - ); + (, bytes32 tokenId, , , uint256 amountInPayload) = abi.decode(payload, (uint256, bytes32, uint256, uint256, uint256)); - if( - validTokenAddress(tokenId) != gateway.tokenAddresses(tokenSymbol) || - amount != amountInPayload - ) revert CallWithTokenMissmatch(payload, tokenSymbol, amount); + if (validTokenAddress(tokenId) != gateway.tokenAddresses(tokenSymbol) || amount != amountInPayload) + revert CallWithTokenMissmatch(payload, tokenSymbol, amount); } /** diff --git a/hardhat.config.js b/hardhat.config.js index 49296ec8..4b56f1e3 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -27,6 +27,23 @@ const optimizerSettings = { }, }, }; +const itsOptimizerSettings = { + enabled: true, + runs: 750, + details: { + peephole: process.env.COVERAGE === undefined, + inliner: process.env.COVERAGE === undefined, + jumpdestRemover: true, + orderLiterals: true, + deduplicate: true, + cse: process.env.COVERAGE === undefined, + constantOptimizer: true, + yul: true, + yulDetails: { + stackAllocation: true, + }, + }, +}; const compilerSettings = { version: '0.8.21', settings: { @@ -34,6 +51,13 @@ const compilerSettings = { optimizer: optimizerSettings, }, }; +const itsCompilerSettings = { + version: '0.8.21', + settings: { + evmVersion: process.env.EVM_VERSION || 'london', + optimizer: itsOptimizerSettings, + }, +}; /** * @type import('hardhat/config').HardhatUserConfig @@ -45,6 +69,8 @@ module.exports = { overrides: { 'contracts/proxies/Proxy.sol': compilerSettings, 'contracts/proxies/TokenManagerProxy.sol': compilerSettings, + 'contracts/InterchainTokenService.sol': itsCompilerSettings, + 'contracts/test/TestInterchainTokenService.sol': itsCompilerSettings, }, }, defaultNetwork: 'hardhat', From ead719ddf9ee63fdc59e165419c8317dae2c2d94 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 7 Feb 2024 17:34:48 +0200 Subject: [PATCH 03/38] trying to fix tests --- contracts/InterchainTokenFactory.sol | 4 +++- test/AddressDerivation.js | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index a5b90129..12edfeb9 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -140,9 +140,11 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M tokenId = _deployInterchainToken(salt, '', name, symbol, decimals, minterBytes, 0); if (initialSupply > 0) { + if(tokenId != 0x108b8ecf67eebbbdfbd5a048cf4f0122c577a636fcae935511aae85c8701849a) revert('asdasd'); + return tokenId; IInterchainToken token = IInterchainToken(interchainTokenService.interchainTokenAddress(tokenId)); ITokenManager tokenManager = ITokenManager(interchainTokenService.tokenManagerAddress(tokenId)); - + token.mint(sender, initialSupply); token.transferMintership(minter); diff --git a/test/AddressDerivation.js b/test/AddressDerivation.js index da77dbeb..23ce8b7d 100644 --- a/test/AddressDerivation.js +++ b/test/AddressDerivation.js @@ -111,7 +111,7 @@ if (isHardhat) { }); }); - describe('Interchain Token Factory Deployments', () => { + describe.only('Interchain Token Factory Deployments', () => { const initialSupply = 100; it('Should derive the correct token address for interchain token deployment on source chain', async () => { @@ -128,6 +128,9 @@ if (isHardhat) { .withArgs(tokenId, expectedTokenAddress, tokenFactory.address, tokenName, tokenSymbol, tokenDecimals) .to.emit(service, 'TokenManagerDeployed') .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); + console.log(await service.interchainTokenAddress(tokenId), expectedTokenAddress); + console.log(await service.tokenManagerAddress(tokenId), expectedTokenManagerAddress); + console.log(tokenId); }); it('Should derive the correct token address for remote interchain token deployment', async () => { From 800ae3d1f3d6d446498f7a98af5e027c98dd85ee Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 9 Feb 2024 17:40:24 +0200 Subject: [PATCH 04/38] Update the create3address of ITS to use a custom bytecodehash. --- contracts/InterchainTokenFactory.sol | 2 -- contracts/InterchainTokenService.sol | 5 ++-- .../interfaces/ITokenManagerDeployer.sol | 2 ++ contracts/utils/Create3Address.sol | 29 +++++++++++++++++++ contracts/utils/TokenManagerDeployer.sol | 4 +++ test/AddressDerivation.js | 8 ++--- 6 files changed, 40 insertions(+), 10 deletions(-) create mode 100644 contracts/utils/Create3Address.sol diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index 12edfeb9..06c42053 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -140,8 +140,6 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M tokenId = _deployInterchainToken(salt, '', name, symbol, decimals, minterBytes, 0); if (initialSupply > 0) { - if(tokenId != 0x108b8ecf67eebbbdfbd5a048cf4f0122c577a636fcae935511aae85c8701849a) revert('asdasd'); - return tokenId; IInterchainToken token = IInterchainToken(interchainTokenService.interchainTokenAddress(tokenId)); ITokenManager tokenManager = ITokenManager(interchainTokenService.tokenManagerAddress(tokenId)); diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 119e0695..4b57802b 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -7,7 +7,6 @@ import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contr 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, SafeTokenCall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/SafeTransfer.sol'; import { AddressBytes } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressBytes.sol'; import { StringToBytes32, Bytes32ToString } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/Bytes32String.sol'; @@ -24,6 +23,8 @@ import { IInterchainTokenExecutable } from './interfaces/IInterchainTokenExecuta import { IInterchainTokenExpressExecutable } from './interfaces/IInterchainTokenExpressExecutable.sol'; import { ITokenManager } from './interfaces/ITokenManager.sol'; import { IERC20Named } from './interfaces/IERC20Named.sol'; +import { Create3Address } from './utils/Create3Address.sol'; + import { Operator } from './utils/Operator.sol'; @@ -118,7 +119,7 @@ contract InterchainTokenService is string memory chainName_, address tokenManagerImplementation_, address tokenHandler_ - ) { + ) Create3Address(ITokenManagerDeployer(tokenManagerDeployer_).createDeployHash()) { if ( gasService_ == address(0) || tokenManagerDeployer_ == address(0) || diff --git a/contracts/interfaces/ITokenManagerDeployer.sol b/contracts/interfaces/ITokenManagerDeployer.sol index 8e9acdd6..7d72abb5 100644 --- a/contracts/interfaces/ITokenManagerDeployer.sol +++ b/contracts/interfaces/ITokenManagerDeployer.sol @@ -22,4 +22,6 @@ interface ITokenManagerDeployer { uint256 implementationType, bytes calldata params ) external payable returns (address tokenManager); + + function createDeployHash() external view returns (bytes32 createDeployBytecodeHash_); } diff --git a/contracts/utils/Create3Address.sol b/contracts/utils/Create3Address.sol new file mode 100644 index 00000000..100760e1 --- /dev/null +++ b/contracts/utils/Create3Address.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +/** + * @title Create3Address contract + * @notice This contract can be used to predict the deterministic deployment address of a contract deployed with the `CREATE3` technique. + */ +contract Create3Address { + /// @dev bytecode hash of the CreateDeploy helper contract + bytes32 internal immutable createDeployBytecodeHash; + + constructor(bytes32 createDeployBytecodeHash_) { + createDeployBytecodeHash = createDeployBytecodeHash_; + } + + /** + * @notice Compute the deployed address that will result from the `CREATE3` method. + * @param deploySalt A salt to influence the contract address + * @return deployed The deterministic contract address if it was deployed + */ + function _create3Address(bytes32 deploySalt) internal view returns (address deployed) { + address deployer = address( + uint160(uint256(keccak256(abi.encodePacked(hex'ff', address(this), deploySalt, createDeployBytecodeHash)))) + ); + + deployed = address(uint160(uint256(keccak256(abi.encodePacked(hex'd6_94', deployer, hex'01'))))); + } +} diff --git a/contracts/utils/TokenManagerDeployer.sol b/contracts/utils/TokenManagerDeployer.sol index b46f6e26..21a20f74 100644 --- a/contracts/utils/TokenManagerDeployer.sol +++ b/contracts/utils/TokenManagerDeployer.sol @@ -34,4 +34,8 @@ contract TokenManagerDeployer is ITokenManagerDeployer, Create3 { if (tokenManager.code.length == 0) revert TokenManagerDeploymentFailed(); } + + function createDeployHash() external view returns (bytes32 createDeployBytecodeHash_) { + createDeployBytecodeHash_ = createDeployBytecodeHash; + } } diff --git a/test/AddressDerivation.js b/test/AddressDerivation.js index 23ce8b7d..acbd91a9 100644 --- a/test/AddressDerivation.js +++ b/test/AddressDerivation.js @@ -111,7 +111,7 @@ if (isHardhat) { }); }); - describe.only('Interchain Token Factory Deployments', () => { + describe('Interchain Token Factory Deployments', () => { const initialSupply = 100; it('Should derive the correct token address for interchain token deployment on source chain', async () => { @@ -126,11 +126,7 @@ if (isHardhat) { await expect(tokenFactory.deployInterchainToken(salt, tokenName, tokenSymbol, tokenDecimals, initialSupply, wallet.address)) .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, expectedTokenAddress, tokenFactory.address, tokenName, tokenSymbol, tokenDecimals) - .to.emit(service, 'TokenManagerDeployed') - .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); - console.log(await service.interchainTokenAddress(tokenId), expectedTokenAddress); - console.log(await service.tokenManagerAddress(tokenId), expectedTokenManagerAddress); - console.log(tokenId); + .to.emit(service, 'TokenManagerDeployed'); }); it('Should derive the correct token address for remote interchain token deployment', async () => { From cf016b0c45b17020164e37354dde1d8649798f70 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 12 Feb 2024 16:33:12 +0200 Subject: [PATCH 05/38] prettier --- contracts/InterchainTokenFactory.sol | 2 +- contracts/InterchainTokenService.sol | 1 - test/AddressDerivation.js | 3 ++- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index 06c42053..a5b90129 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -142,7 +142,7 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M if (initialSupply > 0) { IInterchainToken token = IInterchainToken(interchainTokenService.interchainTokenAddress(tokenId)); ITokenManager tokenManager = ITokenManager(interchainTokenService.tokenManagerAddress(tokenId)); - + token.mint(sender, initialSupply); token.transferMintership(minter); diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 4b57802b..ef1639f4 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -25,7 +25,6 @@ import { ITokenManager } from './interfaces/ITokenManager.sol'; import { IERC20Named } from './interfaces/IERC20Named.sol'; import { Create3Address } from './utils/Create3Address.sol'; - import { Operator } from './utils/Operator.sol'; /** diff --git a/test/AddressDerivation.js b/test/AddressDerivation.js index acbd91a9..da77dbeb 100644 --- a/test/AddressDerivation.js +++ b/test/AddressDerivation.js @@ -126,7 +126,8 @@ if (isHardhat) { await expect(tokenFactory.deployInterchainToken(salt, tokenName, tokenSymbol, tokenDecimals, initialSupply, wallet.address)) .to.emit(service, 'InterchainTokenDeployed') .withArgs(tokenId, expectedTokenAddress, tokenFactory.address, tokenName, tokenSymbol, tokenDecimals) - .to.emit(service, 'TokenManagerDeployed'); + .to.emit(service, 'TokenManagerDeployed') + .withArgs(tokenId, expectedTokenManagerAddress, MINT_BURN, params); }); it('Should derive the correct token address for remote interchain token deployment', async () => { From ad53bc2666e988745ee65a8c0b030f53305df03f Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 12 Feb 2024 16:35:20 +0200 Subject: [PATCH 06/38] fix tests --- test/InterchainTokenService.js | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 94f38ba3..b2ebd6cb 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -370,9 +370,7 @@ describe('Interchain Token Service', () => { [], deploymentKey, gasOptions, - ), - service, - 'ZeroAddress', + ) ); }); @@ -2711,23 +2709,7 @@ describe('Interchain Token Service', () => { expect(returnedAmount).to.eq(amount); }); }); - - describe('Unsupported functions', () => { - const sourceChain = 'Source chain'; - const sourceAddress = 'Source address'; - const payload = '0x'; - const symbol = 'ABC'; - const amount = 100; - - it('Should revert on contractCallWithTokenValue', async () => { - await expectRevert( - (gasOptions) => service.contractCallWithTokenValue(sourceChain, sourceAddress, payload, symbol, amount, gasOptions), - service, - 'ExecuteWithTokenNotSupported', - ); - }); - }); - + describe('Bytecode checks [ @skip-on-coverage ]', () => { it('Should preserve the same proxy bytecode for each EVM', async () => { const proxyFactory = await ethers.getContractFactory('InterchainProxy', wallet); From 7f610e78da5af84653aeaf751d9b19c7ae2c99f5 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 12 Feb 2024 18:27:21 +0200 Subject: [PATCH 07/38] Added a few tests --- contracts/InterchainTokenService.sol | 32 +++++++--- test/InterchainTokenService.js | 95 ++++++++++++++++++++++++++-- 2 files changed, 113 insertions(+), 14 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index ef1639f4..6777a53b 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -349,13 +349,7 @@ contract InterchainTokenService is string calldata sourceAddress, bytes calldata payload ) public view virtual onlyRemoteService(sourceChain, sourceAddress) whenNotPaused returns (address, uint256) { - (uint256 messageType, bytes32 tokenId, , uint256 amount) = abi.decode(payload, (uint256, bytes32, bytes, uint256)); - - if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) { - revert InvalidExpressMessageType(messageType); - } - - return (validTokenAddress(tokenId), amount); + return _contractCallValue(payload); } /** @@ -646,9 +640,9 @@ contract InterchainTokenService is bytes calldata payload, string calldata symbol, uint256 amount - ) public view virtual returns (address, uint256) { + ) public view virtual onlyRemoteService(sourceChain, sourceAddress) whenNotPaused returns (address, uint256) { _checkPayloadAgainstGatewayData(payload, symbol, amount); - return contractCallValue(sourceChain, sourceAddress, payload); + return _contractCallValue(payload); } function expressExecuteWithToken( @@ -1140,4 +1134,24 @@ contract InterchainTokenService is return (amount, tokenAddress); } + /** + * @notice Returns the amount of token that this call is worth. + * @dev If `tokenAddress` is `0`, then value is in terms of the native token, otherwise it's in terms of the token address. + * @param payload The payload sent with the call. + * @return address The token address. + * @return uint256 The value the call is worth. + */ + function _contractCallValue( + bytes calldata payload + ) internal view returns (address, uint256) { + (uint256 messageType, bytes32 tokenId, , , uint256 amount) = abi.decode( + payload, + (uint256, bytes32, bytes, bytes, uint256) + ); + if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) { + revert InvalidExpressMessageType(messageType); + } + + return (validTokenAddress(tokenId), amount); + } } diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index b2ebd6cb..155dfa6d 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -82,7 +82,7 @@ describe('Interchain Token Service', () => { return [token, tokenManager, tokenId]; }; - deployFunctions.gateway = async function deployNewLockUnlock( + deployFunctions.gateway = async function deployNewGateway( tokenName, tokenSymbol, tokenDecimals, @@ -2686,7 +2686,7 @@ describe('Interchain Token Service', () => { const message = 10; const tokenId = HashZero; const amount = 100; - const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'uint256'], [message, tokenId, '0x', amount]); + const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); await expectRevert( (gasOptions) => service.contractCallValue(sourceChain, trustedAddress, payload, gasOptions), @@ -2701,15 +2701,100 @@ describe('Interchain Token Service', () => { const [token, , tokenId] = await deployFunctions.lockUnlock(`Test Token Lock Unlock`, 'TT', 12, mintAmount); const message = 0; const amount = 100; - const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'uint256'], [message, tokenId, '0x', amount]); - + const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); + const [tokenAddress, returnedAmount] = await service.contractCallValue(sourceChain, trustedAddress, payload); expect(tokenAddress).to.eq(token.address); expect(returnedAmount).to.eq(amount); }); }); - + + describe.only('Call contract with token value', () => { + const trustedAddress = 'Trusted address with token'; + const name = 'Gateway Token' + const symbol = 'GT'; + const decimals = 18; + let tokenId; + + before(async () => { + [, , tokenId] = await deployFunctions.gateway(name, symbol, decimals); + }); + + it('Should revert on contractCallValue if not called by remote service', async () => { + const payload = '0x'; + + await expectRevert( + (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, 0, gasOptions), + service, + 'NotRemoteService', + ); + }); + + it('Should revert on contractCallValue if service is paused', async () => { + const payload = '0x'; + + await service.setTrustedAddress(sourceChain, trustedAddress).then((tx) => tx.wait); + + await service.setPauseStatus(true).then((tx) => tx.wait); + + await expectRevert( + (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, 0, gasOptions), + service, + 'Pause', + ); + + await service.setPauseStatus(false).then((tx) => tx.wait); + }); + + it('Should revert on invalid express message type', async () => { + const message = 10; + const amount = 100; + const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); + await expectRevert( + (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, amount, gasOptions), + service, + 'InvalidExpressMessageType', + [message], + ); + }); + + it('Should revert on token missmatch', async () => { + const message = 10; + const amount = 100; + const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); + await expectRevert( + (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, 'wrong symbol', amount, gasOptions), + service, + 'CallWithTokenMissmatch', + [payload, symbol, amount], + ); + }); + + it('Should revert on amount missmatch', async () => { + const message = 10; + const amount = 100; + const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); + await expectRevert( + (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, amount + 1, gasOptions), + service, + 'CallWithTokenMissmatch', + [payload, symbol, amount], + ); + }); + + it('Should return correct token address and amount', async () => { + const message = 0; + const amount = 100; + const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); + + const [tokenAddress, returnedAmount] = await service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, amount); + + expect(tokenAddress).to.eq(await service.validTokenAddress(tokenId)); + expect(returnedAmount).to.eq(amount); + }); + }); + describe('Bytecode checks [ @skip-on-coverage ]', () => { it('Should preserve the same proxy bytecode for each EVM', async () => { const proxyFactory = await ethers.getContractFactory('InterchainProxy', wallet); From 7a926fce609871dfbe807e249737b0301db57642 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 12 Feb 2024 18:28:05 +0200 Subject: [PATCH 08/38] fixed one more test --- test/InterchainTokenService.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 155dfa6d..edea87de 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -2710,7 +2710,7 @@ describe('Interchain Token Service', () => { }); }); - describe.only('Call contract with token value', () => { + describe('Call contract with token value', () => { const trustedAddress = 'Trusted address with token'; const name = 'Gateway Token' const symbol = 'GT'; @@ -2767,7 +2767,7 @@ describe('Interchain Token Service', () => { (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, 'wrong symbol', amount, gasOptions), service, 'CallWithTokenMissmatch', - [payload, symbol, amount], + [payload, 'wrong symbol', amount], ); }); @@ -2779,7 +2779,7 @@ describe('Interchain Token Service', () => { (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, amount + 1, gasOptions), service, 'CallWithTokenMissmatch', - [payload, symbol, amount], + [payload, symbol, amount + 1], ); }); From 83a396a921d0fa8ded6b80e7409df8bb5ec787f2 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 13 Feb 2024 14:12:29 +0200 Subject: [PATCH 09/38] fixed all tests --- contracts/utils/Create3Fixed.sol | 48 +++++++++++++++++++++ contracts/utils/InterchainTokenDeployer.sol | 4 +- contracts/utils/TokenManagerDeployer.sol | 4 +- test/InterchainTokenService.js | 2 +- 4 files changed, 53 insertions(+), 5 deletions(-) create mode 100644 contracts/utils/Create3Fixed.sol diff --git a/contracts/utils/Create3Fixed.sol b/contracts/utils/Create3Fixed.sol new file mode 100644 index 00000000..9bb6bc7f --- /dev/null +++ b/contracts/utils/Create3Fixed.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { IDeploy } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IDeploy.sol'; +import { ContractAddress } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/ContractAddress.sol'; +import { CreateDeploy } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/CreateDeploy.sol'; +import { Create3Address } from './Create3Address.sol'; + +/** + * @title Create3 contract + * @notice This contract can be used to deploy a contract with a deterministic address that depends only on + * the deployer address and deployment salt, not the contract bytecode and constructor parameters. + */ +contract Create3Fixed is Create3Address, IDeploy { + using ContractAddress for address; + + bytes internal constant createDeployBytecode = hex'608060405234801561001057600080fd5b50610162806100206000396000f3fe60806040526004361061001d5760003560e01c806277436014610022575b600080fd5b61003561003036600461007b565b610037565b005b8051602082016000f061004957600080fd5b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561008d57600080fd5b813567ffffffffffffffff808211156100a557600080fd5b818401915084601f8301126100b957600080fd5b8135818111156100cb576100cb61004c565b604051601f8201601f19908116603f011681019083821181831017156100f3576100f361004c565b8160405282815287602084870101111561010c57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122094780ce55d28f1d568f4e0ab1b9dc230b96e952b73d2e06456fbff2289fa27f464736f6c63430008150033'; + + constructor() Create3Address(keccak256(createDeployBytecode)) {} + /** + * @notice Deploys a new contract using the `CREATE3` method. + * @dev This function first deploys the CreateDeploy contract using + * the `CREATE2` opcode and then utilizes the CreateDeploy to deploy the + * new contract with the `CREATE` opcode. + * @param bytecode The bytecode of the contract to be deployed + * @param deploySalt A salt to influence the contract address + * @return deployed The address of the deployed contract + */ + function _create3(bytes memory bytecode, bytes32 deploySalt) internal returns (address deployed) { + deployed = _create3Address(deploySalt); + + if (bytecode.length == 0) revert EmptyBytecode(); + if (deployed.isContract()) revert AlreadyDeployed(); + + // Deploy using create2 + CreateDeploy createDeploy; + bytes memory createDeployBytecode_ = createDeployBytecode; + uint256 length = createDeployBytecode_.length; + assembly { + createDeploy := create2(0, add(createDeployBytecode_, 0x20), length, deploySalt) + } + + if (address(createDeploy) == address(0)) revert DeployFailed(); + // Deploy using create + createDeploy.deploy(bytecode); + } +} diff --git a/contracts/utils/InterchainTokenDeployer.sol b/contracts/utils/InterchainTokenDeployer.sol index 1fabe688..f39eed8c 100644 --- a/contracts/utils/InterchainTokenDeployer.sol +++ b/contracts/utils/InterchainTokenDeployer.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import { Create3 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3.sol'; +import { Create3Fixed } from './Create3Fixed.sol'; import { IInterchainTokenDeployer } from '../interfaces/IInterchainTokenDeployer.sol'; import { IInterchainToken } from '../interfaces/IInterchainToken.sol'; @@ -11,7 +11,7 @@ import { IInterchainToken } from '../interfaces/IInterchainToken.sol'; * @title InterchainTokenDeployer * @notice This contract is used to deploy new instances of the InterchainTokenProxy contract. */ -contract InterchainTokenDeployer is IInterchainTokenDeployer, Create3 { +contract InterchainTokenDeployer is IInterchainTokenDeployer, Create3Fixed { address public immutable implementationAddress; /** diff --git a/contracts/utils/TokenManagerDeployer.sol b/contracts/utils/TokenManagerDeployer.sol index 21a20f74..adfcb8ad 100644 --- a/contracts/utils/TokenManagerDeployer.sol +++ b/contracts/utils/TokenManagerDeployer.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.0; -import { Create3 } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/Create3.sol'; +import { Create3Fixed } from './Create3Fixed.sol'; import { ITokenManagerDeployer } from '../interfaces/ITokenManagerDeployer.sol'; @@ -12,7 +12,7 @@ import { TokenManagerProxy } from '../proxies/TokenManagerProxy.sol'; * @title TokenManagerDeployer * @notice This contract is used to deploy new instances of the TokenManagerProxy contract. */ -contract TokenManagerDeployer is ITokenManagerDeployer, Create3 { +contract TokenManagerDeployer is ITokenManagerDeployer, Create3Fixed { /** * @notice Deploys a new instance of the TokenManagerProxy contract * @param tokenId The unique identifier for the token diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index edea87de..95654eed 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -30,7 +30,7 @@ const FLOW_LIMITER_ROLE = 2; const reportGas = gasReporter('Interchain Token Service'); -describe('Interchain Token Service', () => { +describe.only('Interchain Token Service', () => { let wallet, otherWallet; let service, gateway, gasService, testToken; From c326d57d148c20337be19fd070687b46df73f7f5 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 13 Feb 2024 14:33:17 +0200 Subject: [PATCH 10/38] prettier --- contracts/InterchainTokenService.sol | 10 +--- contracts/utils/Create3Fixed.sol | 4 +- test/InterchainTokenService.js | 86 +++++++++++++++++----------- 3 files changed, 58 insertions(+), 42 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 6777a53b..f9819d31 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -1134,6 +1134,7 @@ contract InterchainTokenService is return (amount, tokenAddress); } + /** * @notice Returns the amount of token that this call is worth. * @dev If `tokenAddress` is `0`, then value is in terms of the native token, otherwise it's in terms of the token address. @@ -1141,13 +1142,8 @@ contract InterchainTokenService is * @return address The token address. * @return uint256 The value the call is worth. */ - function _contractCallValue( - bytes calldata payload - ) internal view returns (address, uint256) { - (uint256 messageType, bytes32 tokenId, , , uint256 amount) = abi.decode( - payload, - (uint256, bytes32, bytes, bytes, uint256) - ); + function _contractCallValue(bytes calldata payload) internal view returns (address, uint256) { + (uint256 messageType, bytes32 tokenId, , , uint256 amount) = abi.decode(payload, (uint256, bytes32, bytes, bytes, uint256)); if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) { revert InvalidExpressMessageType(messageType); } diff --git a/contracts/utils/Create3Fixed.sol b/contracts/utils/Create3Fixed.sol index 9bb6bc7f..46f27767 100644 --- a/contracts/utils/Create3Fixed.sol +++ b/contracts/utils/Create3Fixed.sol @@ -15,9 +15,11 @@ import { Create3Address } from './Create3Address.sol'; contract Create3Fixed is Create3Address, IDeploy { using ContractAddress for address; - bytes internal constant createDeployBytecode = hex'608060405234801561001057600080fd5b50610162806100206000396000f3fe60806040526004361061001d5760003560e01c806277436014610022575b600080fd5b61003561003036600461007b565b610037565b005b8051602082016000f061004957600080fd5b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561008d57600080fd5b813567ffffffffffffffff808211156100a557600080fd5b818401915084601f8301126100b957600080fd5b8135818111156100cb576100cb61004c565b604051601f8201601f19908116603f011681019083821181831017156100f3576100f361004c565b8160405282815287602084870101111561010c57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122094780ce55d28f1d568f4e0ab1b9dc230b96e952b73d2e06456fbff2289fa27f464736f6c63430008150033'; + bytes internal constant createDeployBytecode = + hex'608060405234801561001057600080fd5b50610162806100206000396000f3fe60806040526004361061001d5760003560e01c806277436014610022575b600080fd5b61003561003036600461007b565b610037565b005b8051602082016000f061004957600080fd5b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561008d57600080fd5b813567ffffffffffffffff808211156100a557600080fd5b818401915084601f8301126100b957600080fd5b8135818111156100cb576100cb61004c565b604051601f8201601f19908116603f011681019083821181831017156100f3576100f361004c565b8160405282815287602084870101111561010c57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122094780ce55d28f1d568f4e0ab1b9dc230b96e952b73d2e06456fbff2289fa27f464736f6c63430008150033'; constructor() Create3Address(keccak256(createDeployBytecode)) {} + /** * @notice Deploys a new contract using the `CREATE3` method. * @dev This function first deploys the CreateDeploy contract using diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 95654eed..9da6b392 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -82,13 +82,7 @@ describe.only('Interchain Token Service', () => { return [token, tokenManager, tokenId]; }; - deployFunctions.gateway = async function deployNewGateway( - tokenName, - tokenSymbol, - tokenDecimals, - mintAmount = 0, - skipApprove = false, - ) { + deployFunctions.gateway = async function deployNewGateway(tokenName, tokenSymbol, tokenDecimals, mintAmount = 0, skipApprove = false) { const salt = getRandomBytes32(); const tokenId = await service.interchainTokenId(wallet.address, salt); const tokenManager = await getContractAt('TokenManager', await service.tokenManagerAddress(tokenId), wallet); @@ -354,23 +348,22 @@ describe.only('Interchain Token Service', () => { }); it('Should revert on invalid token manager deployer', async () => { - await expectRevert( - (gasOptions) => - deployInterchainTokenService( - wallet, - create3Deployer.address, - AddressZero, - interchainTokenDeployer.address, - gateway.address, - gasService.address, - interchainTokenFactoryAddress, - tokenManager.address, - tokenHandler.address, - chainName, - [], - deploymentKey, - gasOptions, - ) + await expectRevert((gasOptions) => + deployInterchainTokenService( + wallet, + create3Deployer.address, + AddressZero, + interchainTokenDeployer.address, + gateway.address, + gasService.address, + interchainTokenFactoryAddress, + tokenManager.address, + tokenHandler.address, + chainName, + [], + deploymentKey, + gasOptions, + ), ); }); @@ -2686,7 +2679,10 @@ describe.only('Interchain Token Service', () => { const message = 10; const tokenId = HashZero; const amount = 100; - const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [message, tokenId, '0x', '0x', amount, '0x'], + ); await expectRevert( (gasOptions) => service.contractCallValue(sourceChain, trustedAddress, payload, gasOptions), @@ -2701,8 +2697,11 @@ describe.only('Interchain Token Service', () => { const [token, , tokenId] = await deployFunctions.lockUnlock(`Test Token Lock Unlock`, 'TT', 12, mintAmount); const message = 0; const amount = 100; - const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); - + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [message, tokenId, '0x', '0x', amount, '0x'], + ); + const [tokenAddress, returnedAmount] = await service.contractCallValue(sourceChain, trustedAddress, payload); expect(tokenAddress).to.eq(token.address); @@ -2712,7 +2711,7 @@ describe.only('Interchain Token Service', () => { describe('Call contract with token value', () => { const trustedAddress = 'Trusted address with token'; - const name = 'Gateway Token' + const name = 'Gateway Token'; const symbol = 'GT'; const decimals = 18; let tokenId; @@ -2750,7 +2749,10 @@ describe.only('Interchain Token Service', () => { it('Should revert on invalid express message type', async () => { const message = 10; const amount = 100; - const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [message, tokenId, '0x', '0x', amount, '0x'], + ); await expectRevert( (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, amount, gasOptions), service, @@ -2762,9 +2764,13 @@ describe.only('Interchain Token Service', () => { it('Should revert on token missmatch', async () => { const message = 10; const amount = 100; - const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [message, tokenId, '0x', '0x', amount, '0x'], + ); await expectRevert( - (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, 'wrong symbol', amount, gasOptions), + (gasOptions) => + service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, 'wrong symbol', amount, gasOptions), service, 'CallWithTokenMissmatch', [payload, 'wrong symbol', amount], @@ -2774,7 +2780,10 @@ describe.only('Interchain Token Service', () => { it('Should revert on amount missmatch', async () => { const message = 10; const amount = 100; - const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [message, tokenId, '0x', '0x', amount, '0x'], + ); await expectRevert( (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, amount + 1, gasOptions), service, @@ -2786,9 +2795,18 @@ describe.only('Interchain Token Service', () => { it('Should return correct token address and amount', async () => { const message = 0; const amount = 100; - const payload = defaultAbiCoder.encode(['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x']); + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [message, tokenId, '0x', '0x', amount, '0x'], + ); - const [tokenAddress, returnedAmount] = await service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, amount); + const [tokenAddress, returnedAmount] = await service.contractCallWithTokenValue( + sourceChain, + trustedAddress, + payload, + symbol, + amount, + ); expect(tokenAddress).to.eq(await service.validTokenAddress(tokenId)); expect(returnedAmount).to.eq(amount); From ca2bd917b9def354908afb0f74f51f1c595b6ed5 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 13 Feb 2024 14:36:20 +0200 Subject: [PATCH 11/38] made lint happy --- test/InterchainTokenService.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 9da6b392..61b419fb 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -30,7 +30,7 @@ const FLOW_LIMITER_ROLE = 2; const reportGas = gasReporter('Interchain Token Service'); -describe.only('Interchain Token Service', () => { +describe('Interchain Token Service', () => { let wallet, otherWallet; let service, gateway, gasService, testToken; From 1468d7e7e1b3749b8694a4e44e5668342b239a60 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 13 Feb 2024 19:04:25 +0200 Subject: [PATCH 12/38] working on slither --- contracts/InterchainTokenService.sol | 5 +++-- contracts/utils/Create3Fixed.sol | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index f9819d31..c88ad919 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -668,8 +668,6 @@ contract InterchainTokenService is ) external { bytes32 payloadHash = keccak256(payload); - if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) - revert NotApprovedByGateway(); uint256 messageType = abi.decode(payload, (uint256)); if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) { @@ -684,6 +682,9 @@ contract InterchainTokenService is emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); } + if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) + revert NotApprovedByGateway(); + _processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload); } diff --git a/contracts/utils/Create3Fixed.sol b/contracts/utils/Create3Fixed.sol index 46f27767..17bb9d47 100644 --- a/contracts/utils/Create3Fixed.sol +++ b/contracts/utils/Create3Fixed.sol @@ -15,10 +15,10 @@ import { Create3Address } from './Create3Address.sol'; contract Create3Fixed is Create3Address, IDeploy { using ContractAddress for address; - bytes internal constant createDeployBytecode = + bytes internal constant CREATE_DEPLOY_BYTECODE = hex'608060405234801561001057600080fd5b50610162806100206000396000f3fe60806040526004361061001d5760003560e01c806277436014610022575b600080fd5b61003561003036600461007b565b610037565b005b8051602082016000f061004957600080fd5b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561008d57600080fd5b813567ffffffffffffffff808211156100a557600080fd5b818401915084601f8301126100b957600080fd5b8135818111156100cb576100cb61004c565b604051601f8201601f19908116603f011681019083821181831017156100f3576100f361004c565b8160405282815287602084870101111561010c57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122094780ce55d28f1d568f4e0ab1b9dc230b96e952b73d2e06456fbff2289fa27f464736f6c63430008150033'; - constructor() Create3Address(keccak256(createDeployBytecode)) {} + constructor() Create3Address(keccak256(CREATE_DEPLOY_BYTECODE)) {} /** * @notice Deploys a new contract using the `CREATE3` method. @@ -37,7 +37,7 @@ contract Create3Fixed is Create3Address, IDeploy { // Deploy using create2 CreateDeploy createDeploy; - bytes memory createDeployBytecode_ = createDeployBytecode; + bytes memory createDeployBytecode_ = CREATE_DEPLOY_BYTECODE; uint256 length = createDeployBytecode_.length; assembly { createDeploy := create2(0, add(createDeployBytecode_, 0x20), length, deploySalt) From dccaf3c4c784f56b10cedd2a0ab5290ee4e142b7 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 14 Feb 2024 13:24:34 +0200 Subject: [PATCH 13/38] made slither happy --- contracts/utils/Create3Fixed.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/utils/Create3Fixed.sol b/contracts/utils/Create3Fixed.sol index 17bb9d47..155c986f 100644 --- a/contracts/utils/Create3Fixed.sol +++ b/contracts/utils/Create3Fixed.sol @@ -14,7 +14,8 @@ import { Create3Address } from './Create3Address.sol'; */ contract Create3Fixed is Create3Address, IDeploy { using ContractAddress for address; - + + // slither-disable-next-line too-many-digits bytes internal constant CREATE_DEPLOY_BYTECODE = hex'608060405234801561001057600080fd5b50610162806100206000396000f3fe60806040526004361061001d5760003560e01c806277436014610022575b600080fd5b61003561003036600461007b565b610037565b005b8051602082016000f061004957600080fd5b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561008d57600080fd5b813567ffffffffffffffff808211156100a557600080fd5b818401915084601f8301126100b957600080fd5b8135818111156100cb576100cb61004c565b604051601f8201601f19908116603f011681019083821181831017156100f3576100f361004c565b8160405282815287602084870101111561010c57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122094780ce55d28f1d568f4e0ab1b9dc230b96e952b73d2e06456fbff2289fa27f464736f6c63430008150033'; From 927e1ceb8719aea50118ca07f7681049fa45fa3e Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 14 Feb 2024 13:51:10 +0200 Subject: [PATCH 14/38] prettier --- contracts/InterchainTokenService.sol | 5 ++--- contracts/utils/Create3Fixed.sol | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index c88ad919..1be037c5 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -668,7 +668,6 @@ contract InterchainTokenService is ) external { bytes32 payloadHash = keccak256(payload); - uint256 messageType = abi.decode(payload, (uint256)); if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) { revert InvalidExpressMessageType(messageType); @@ -682,8 +681,8 @@ contract InterchainTokenService is emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); } - if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) - revert NotApprovedByGateway(); + if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) + revert NotApprovedByGateway(); _processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload); } diff --git a/contracts/utils/Create3Fixed.sol b/contracts/utils/Create3Fixed.sol index 155c986f..a6e713b4 100644 --- a/contracts/utils/Create3Fixed.sol +++ b/contracts/utils/Create3Fixed.sol @@ -14,7 +14,7 @@ import { Create3Address } from './Create3Address.sol'; */ contract Create3Fixed is Create3Address, IDeploy { using ContractAddress for address; - + // slither-disable-next-line too-many-digits bytes internal constant CREATE_DEPLOY_BYTECODE = hex'608060405234801561001057600080fd5b50610162806100206000396000f3fe60806040526004361061001d5760003560e01c806277436014610022575b600080fd5b61003561003036600461007b565b610037565b005b8051602082016000f061004957600080fd5b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561008d57600080fd5b813567ffffffffffffffff808211156100a557600080fd5b818401915084601f8301126100b957600080fd5b8135818111156100cb576100cb61004c565b604051601f8201601f19908116603f011681019083821181831017156100f3576100f361004c565b8160405282815287602084870101111561010c57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122094780ce55d28f1d568f4e0ab1b9dc230b96e952b73d2e06456fbff2289fa27f464736f6c63430008150033'; From 1fc47a813f4c740cae661a5ea8bf7832597d7bc6 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 14 Feb 2024 15:40:23 +0200 Subject: [PATCH 15/38] Using constant for the hash as well --- contracts/InterchainTokenService.sol | 2 +- contracts/interfaces/ITokenManagerDeployer.sol | 2 -- contracts/utils/Create3Address.sol | 12 +++++------- contracts/utils/Create3Fixed.sol | 6 ------ contracts/utils/TokenManagerDeployer.sol | 4 ---- 5 files changed, 6 insertions(+), 20 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 1be037c5..c93b5715 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -118,7 +118,7 @@ contract InterchainTokenService is string memory chainName_, address tokenManagerImplementation_, address tokenHandler_ - ) Create3Address(ITokenManagerDeployer(tokenManagerDeployer_).createDeployHash()) { + ) { if ( gasService_ == address(0) || tokenManagerDeployer_ == address(0) || diff --git a/contracts/interfaces/ITokenManagerDeployer.sol b/contracts/interfaces/ITokenManagerDeployer.sol index 7d72abb5..8e9acdd6 100644 --- a/contracts/interfaces/ITokenManagerDeployer.sol +++ b/contracts/interfaces/ITokenManagerDeployer.sol @@ -22,6 +22,4 @@ interface ITokenManagerDeployer { uint256 implementationType, bytes calldata params ) external payable returns (address tokenManager); - - function createDeployHash() external view returns (bytes32 createDeployBytecodeHash_); } diff --git a/contracts/utils/Create3Address.sol b/contracts/utils/Create3Address.sol index 100760e1..3269f549 100644 --- a/contracts/utils/Create3Address.sol +++ b/contracts/utils/Create3Address.sol @@ -7,12 +7,10 @@ pragma solidity ^0.8.0; * @notice This contract can be used to predict the deterministic deployment address of a contract deployed with the `CREATE3` technique. */ contract Create3Address { - /// @dev bytecode hash of the CreateDeploy helper contract - bytes32 internal immutable createDeployBytecodeHash; - - constructor(bytes32 createDeployBytecodeHash_) { - createDeployBytecodeHash = createDeployBytecodeHash_; - } + // slither-disable-next-line too-many-digits + bytes internal constant CREATE_DEPLOY_BYTECODE = + hex'608060405234801561001057600080fd5b50610162806100206000396000f3fe60806040526004361061001d5760003560e01c806277436014610022575b600080fd5b61003561003036600461007b565b610037565b005b8051602082016000f061004957600080fd5b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561008d57600080fd5b813567ffffffffffffffff808211156100a557600080fd5b818401915084601f8301126100b957600080fd5b8135818111156100cb576100cb61004c565b604051601f8201601f19908116603f011681019083821181831017156100f3576100f361004c565b8160405282815287602084870101111561010c57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122094780ce55d28f1d568f4e0ab1b9dc230b96e952b73d2e06456fbff2289fa27f464736f6c63430008150033'; + bytes32 internal constant CREATE_DEPLOY_BYTECODE_HASH = keccak256(CREATE_DEPLOY_BYTECODE); /** * @notice Compute the deployed address that will result from the `CREATE3` method. @@ -21,7 +19,7 @@ contract Create3Address { */ function _create3Address(bytes32 deploySalt) internal view returns (address deployed) { address deployer = address( - uint160(uint256(keccak256(abi.encodePacked(hex'ff', address(this), deploySalt, createDeployBytecodeHash)))) + uint160(uint256(keccak256(abi.encodePacked(hex'ff', address(this), deploySalt, CREATE_DEPLOY_BYTECODE_HASH)))) ); deployed = address(uint160(uint256(keccak256(abi.encodePacked(hex'd6_94', deployer, hex'01'))))); diff --git a/contracts/utils/Create3Fixed.sol b/contracts/utils/Create3Fixed.sol index a6e713b4..b4694473 100644 --- a/contracts/utils/Create3Fixed.sol +++ b/contracts/utils/Create3Fixed.sol @@ -15,12 +15,6 @@ import { Create3Address } from './Create3Address.sol'; contract Create3Fixed is Create3Address, IDeploy { using ContractAddress for address; - // slither-disable-next-line too-many-digits - bytes internal constant CREATE_DEPLOY_BYTECODE = - hex'608060405234801561001057600080fd5b50610162806100206000396000f3fe60806040526004361061001d5760003560e01c806277436014610022575b600080fd5b61003561003036600461007b565b610037565b005b8051602082016000f061004957600080fd5b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561008d57600080fd5b813567ffffffffffffffff808211156100a557600080fd5b818401915084601f8301126100b957600080fd5b8135818111156100cb576100cb61004c565b604051601f8201601f19908116603f011681019083821181831017156100f3576100f361004c565b8160405282815287602084870101111561010c57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122094780ce55d28f1d568f4e0ab1b9dc230b96e952b73d2e06456fbff2289fa27f464736f6c63430008150033'; - - constructor() Create3Address(keccak256(CREATE_DEPLOY_BYTECODE)) {} - /** * @notice Deploys a new contract using the `CREATE3` method. * @dev This function first deploys the CreateDeploy contract using diff --git a/contracts/utils/TokenManagerDeployer.sol b/contracts/utils/TokenManagerDeployer.sol index adfcb8ad..cdc8349b 100644 --- a/contracts/utils/TokenManagerDeployer.sol +++ b/contracts/utils/TokenManagerDeployer.sol @@ -34,8 +34,4 @@ contract TokenManagerDeployer is ITokenManagerDeployer, Create3Fixed { if (tokenManager.code.length == 0) revert TokenManagerDeploymentFailed(); } - - function createDeployHash() external view returns (bytes32 createDeployBytecodeHash_) { - createDeployBytecodeHash_ = createDeployBytecodeHash; - } } From f5823ccb6a4759a645e8128929db3a1780e1292f Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 14 Feb 2024 17:03:48 +0200 Subject: [PATCH 16/38] addressed comments --- hardhat.config.js | 17 ++--------------- test/InterchainTokenService.js | 17 +++++------------ 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/hardhat.config.js b/hardhat.config.js index 4b56f1e3..2bd328c3 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -28,21 +28,8 @@ const optimizerSettings = { }, }; const itsOptimizerSettings = { - enabled: true, - runs: 750, - details: { - peephole: process.env.COVERAGE === undefined, - inliner: process.env.COVERAGE === undefined, - jumpdestRemover: true, - orderLiterals: true, - deduplicate: true, - cse: process.env.COVERAGE === undefined, - constantOptimizer: true, - yul: true, - yulDetails: { - stackAllocation: true, - }, - }, + ...optimizerSettings, + runs: 750, // Reduce runs to keep bytecode size under limit }; const compilerSettings = { version: '0.8.21', diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 61b419fb..842f6c03 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -2648,6 +2648,7 @@ describe('Interchain Token Service', () => { describe('Call contract value', () => { const trustedAddress = 'Trusted address'; + const amount = 100; it('Should revert on contractCallValue if not called by remote service', async () => { const payload = '0x'; @@ -2678,7 +2679,6 @@ describe('Interchain Token Service', () => { it('Should revert on invalid express message type', async () => { const message = 10; const tokenId = HashZero; - const amount = 100; const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x'], @@ -2696,7 +2696,6 @@ describe('Interchain Token Service', () => { const mintAmount = 1234; const [token, , tokenId] = await deployFunctions.lockUnlock(`Test Token Lock Unlock`, 'TT', 12, mintAmount); const message = 0; - const amount = 100; const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x'], @@ -2714,6 +2713,8 @@ describe('Interchain Token Service', () => { const name = 'Gateway Token'; const symbol = 'GT'; const decimals = 18; + const message = 0; + const amount = 100; let tokenId; before(async () => { @@ -2747,23 +2748,19 @@ describe('Interchain Token Service', () => { }); it('Should revert on invalid express message type', async () => { - const message = 10; - const amount = 100; const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], - [message, tokenId, '0x', '0x', amount, '0x'], + [message + 1, tokenId, '0x', '0x', amount, '0x'], ); await expectRevert( (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, amount, gasOptions), service, 'InvalidExpressMessageType', - [message], + [message + 1], ); }); it('Should revert on token missmatch', async () => { - const message = 10; - const amount = 100; const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x'], @@ -2778,8 +2775,6 @@ describe('Interchain Token Service', () => { }); it('Should revert on amount missmatch', async () => { - const message = 10; - const amount = 100; const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x'], @@ -2793,8 +2788,6 @@ describe('Interchain Token Service', () => { }); it('Should return correct token address and amount', async () => { - const message = 0; - const amount = 100; const payload = defaultAbiCoder.encode( ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], [message, tokenId, '0x', '0x', amount, '0x'], From c562c6a704493e6ddde48db135a31e910ffd02ff Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 14 Feb 2024 18:02:28 +0200 Subject: [PATCH 17/38] added some tests --- contracts/InterchainTokenService.sol | 2 +- .../interfaces/IInterchainTokenService.sol | 2 +- contracts/test/utils/TestCreate3Fixed.sol | 19 ++++++ test/InterchainTokenService.js | 4 +- test/UtilsTest.js | 59 +++++++++++++++++++ 5 files changed, 82 insertions(+), 4 deletions(-) create mode 100644 contracts/test/utils/TestCreate3Fixed.sol diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index c93b5715..6670d2c1 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -691,7 +691,7 @@ contract InterchainTokenService is (, bytes32 tokenId, , , uint256 amountInPayload) = abi.decode(payload, (uint256, bytes32, uint256, uint256, uint256)); if (validTokenAddress(tokenId) != gateway.tokenAddresses(tokenSymbol) || amount != amountInPayload) - revert CallWithTokenMissmatch(payload, tokenSymbol, amount); + revert InvalidGatewayTokenTransfer(payload, tokenSymbol, amount); } /** diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 057f445d..41d5b8a9 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -47,7 +47,7 @@ interface IInterchainTokenService is error TokenHandlerFailed(bytes data); error EmptyData(); error PostDeployFailed(bytes data); - error CallWithTokenMissmatch(bytes payload, string tokenSymbol, uint256 amount); + error InvalidGatewayTokenTransfer(bytes payload, string tokenSymbol, uint256 amount); event InterchainTransfer( bytes32 indexed tokenId, diff --git a/contracts/test/utils/TestCreate3Fixed.sol b/contracts/test/utils/TestCreate3Fixed.sol new file mode 100644 index 00000000..3b31656d --- /dev/null +++ b/contracts/test/utils/TestCreate3Fixed.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.0; + +import { Create3Fixed } from '../../utils/Create3Fixed.sol'; + +contract TestCreate3Fixed is Create3Fixed { + event Deployed(address addr); + + function deploy(bytes memory code, bytes32 salt) public payable returns (address addr) { + addr = _create3(code, salt); + + emit Deployed(addr); + } + + function deployedAddress(bytes32 salt) public view returns (address addr) { + addr = _create3Address(salt); + } +} diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 842f6c03..363f87f4 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -2769,7 +2769,7 @@ describe('Interchain Token Service', () => { (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, 'wrong symbol', amount, gasOptions), service, - 'CallWithTokenMissmatch', + 'InvalidGatewayTokenTransfer', [payload, 'wrong symbol', amount], ); }); @@ -2782,7 +2782,7 @@ describe('Interchain Token Service', () => { await expectRevert( (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, amount + 1, gasOptions), service, - 'CallWithTokenMissmatch', + 'InvalidGatewayTokenTransfer', [payload, symbol, amount + 1], ); }); diff --git a/test/UtilsTest.js b/test/UtilsTest.js index f368431d..eee3c583 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -6,11 +6,13 @@ const { Wallet, getContractAt, constants: { AddressZero }, + ContractFactory, } = ethers; const { time } = require('@nomicfoundation/hardhat-network-helpers'); const { expect } = chai; const { getRandomBytes32, expectRevert, isHardhat, waitFor } = require('./utils'); const { deployContract } = require('../scripts/deploy'); +const BurnableMintableCappedERC20 = require('../artifacts/contracts/test/TestMintableBurnableERC20.sol/TestMintableBurnableERC20.json'); let ownerWallet, otherWallet; @@ -306,3 +308,60 @@ describe('InterchainTokenDeployer', () => { ); }); }); + +describe('Create3Deployer', () => { + let deployerWallet; + let userWallet; + + let deployerFactory; + let deployer; + const name = 'test'; + const symbol = 'test'; + const decimals = 16; + + before(async () => { + [deployerWallet, userWallet] = await ethers.getSigners(); + + deployerFactory = await ethers.getContractFactory('TestCreate3Fixed', deployerWallet); + }); + + beforeEach(async () => { + deployer = await deployerFactory.deploy().then((d) => d.deployed()); + }); + + describe('deploy', () => { + it('should revert on deploy with empty bytecode', async () => { + const salt = getRandomBytes32(); + const bytecode = '0x'; + + await expect(deployer.connect(userWallet).deploy(bytecode, salt)).to.be.revertedWithCustomError(deployer, 'EmptyBytecode'); + }); + + it('should deploy to the predicted address', async () => { + const salt = getRandomBytes32(); + + const address = await deployer.deployedAddress(salt); + + const factory = new ContractFactory(BurnableMintableCappedERC20.abi, BurnableMintableCappedERC20.bytecode); + const bytecode = factory.getDeployTransaction(name, symbol, decimals).data; + + await expect(deployer.deploy(bytecode, salt)).to.emit(deployer, 'Deployed').withArgs(address); + }); + + it('should not forward native value', async () => { + const salt = getRandomBytes32(); + + const address = await deployer.deployedAddress(salt); + + const factory = new ContractFactory(BurnableMintableCappedERC20.abi, BurnableMintableCappedERC20.bytecode); + const bytecode = factory.getDeployTransaction(name, symbol, decimals).data; + + await expect(deployer.deploy(bytecode, salt, { value: 10 })) + .to.emit(deployer, 'Deployed') + .withArgs(address); + + expect(await ethers.provider.getBalance(address)).to.equal(0); + expect(await ethers.provider.getBalance(deployer.address)).to.equal(10); + }); + }); +}); From f133cc973ae6a4ff84f509093e15987826712368 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 15 Feb 2024 17:37:39 +0200 Subject: [PATCH 18/38] added some coverage tests, found a bug too! --- contracts/InterchainTokenService.sol | 60 +++++++++++++-------- hardhat.config.js | 2 +- test/InterchainTokenService.js | 81 ++++++++++++++++++++++++++++ 3 files changed, 120 insertions(+), 23 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 6670d2c1..5ac409a8 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -626,7 +626,12 @@ contract InterchainTokenService is * @param sourceAddress The address of the remote ITS where the transaction originates from. * @param payload The encoded data payload for the transaction. */ - function execute(bytes32 commandId, string calldata sourceChain, string calldata sourceAddress, bytes calldata payload) public { + function execute( + bytes32 commandId, + string calldata sourceChain, + string calldata sourceAddress, + bytes calldata payload + ) public onlyRemoteService(sourceChain, sourceAddress) whenNotPaused { bytes32 payloadHash = keccak256(payload); if (!gateway.validateContractCall(commandId, sourceChain, sourceAddress, payloadHash)) revert NotApprovedByGateway(); @@ -665,26 +670,8 @@ contract InterchainTokenService is bytes calldata payload, string calldata tokenSymbol, uint256 amount - ) external { - bytes32 payloadHash = keccak256(payload); - - uint256 messageType = abi.decode(payload, (uint256)); - if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) { - revert InvalidExpressMessageType(messageType); - } - - _checkPayloadAgainstGatewayData(payload, tokenSymbol, amount); - - address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); - - if (expressExecutor != address(0)) { - emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); - } - - if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) - revert NotApprovedByGateway(); - - _processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload); + ) external onlyRemoteService(sourceChain, sourceAddress) whenNotPaused { + _executeWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount); } function _checkPayloadAgainstGatewayData(bytes calldata payload, string calldata tokenSymbol, uint256 amount) internal view { @@ -879,7 +866,7 @@ contract InterchainTokenService is string calldata sourceAddress, bytes calldata payload, bytes32 payloadHash - ) internal onlyRemoteService(sourceChain, sourceAddress) whenNotPaused { + ) internal { uint256 messageType = abi.decode(payload, (uint256)); if (messageType == MESSAGE_TYPE_INTERCHAIN_TRANSFER) { address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); @@ -898,6 +885,35 @@ contract InterchainTokenService is } } + function _executeWithToken( + bytes32 commandId, + string calldata sourceChain, + string calldata sourceAddress, + bytes calldata payload, + string calldata tokenSymbol, + uint256 amount + ) internal { + bytes32 payloadHash = keccak256(payload); + + uint256 messageType = abi.decode(payload, (uint256)); + if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) { + revert InvalidMessageType(messageType); + } + + _checkPayloadAgainstGatewayData(payload, tokenSymbol, amount); + + address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); + + if (expressExecutor != address(0)) { + emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); + } + + if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) + revert NotApprovedByGateway(); + + _processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload); + } + /** * @notice Deploys a token manager on a destination chain. * @param tokenId The ID of the token. diff --git a/hardhat.config.js b/hardhat.config.js index 2bd328c3..2acbe3e3 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -29,7 +29,7 @@ const optimizerSettings = { }; const itsOptimizerSettings = { ...optimizerSettings, - runs: 750, // Reduce runs to keep bytecode size under limit + runs: 600, // Reduce runs to keep bytecode size under limit }; const compilerSettings = { version: '0.8.21', diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 1e8d6ebd..15090b41 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -1340,6 +1340,87 @@ describe('Interchain Token Service', () => { }); }); + describe('Execute with token checks', () => { + const sourceChain = 'source chain'; + let sourceAddress; + const amount = 1234; + let destAddress; + const tokenName = 'Token Name'; + const tokenSymbol = 'TS'; + const tokenDecimals = 16; + + before(async () => { + sourceAddress = service.address; + destAddress = wallet.address; + await deployFunctions.gateway(tokenName, tokenSymbol, tokenDecimals); + }); + + it('Should revert on execute if remote address validation fails', async () => { + const commandId = await approveContractCallWithMint( + gateway, + sourceChain, + wallet.address, + service.address, + '0x', + tokenSymbol, + amount, + ); + + await expectRevert( + (gasOptions) => service.executeWithToken(commandId, sourceChain, wallet.address, '0x', tokenSymbol, amount, gasOptions), + service, + 'NotRemoteService', + ); + }); + + it('Should revert on execute if the service is paused', async () => { + await service.setPauseStatus(true).then((tx) => tx.wait); + + const commandId = await approveContractCallWithMint( + gateway, + sourceChain, + sourceAddress, + service.address, + '0x', + tokenSymbol, + amount, + ); + + await expectRevert( + (gasOptions) => service.executeWithToken(commandId, sourceChain, sourceAddress, '0x', tokenSymbol, amount, gasOptions), + service, + 'Pause', + ); + + await service.setPauseStatus(false).then((tx) => tx.wait); + }); + + it('Should revert on execute with invalid messageType', async () => { + const tokenId = getRandomBytes32(); + + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'uint256'], + [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, destAddress, amount], + ); + const commandId = await approveContractCallWithMint( + gateway, + sourceChain, + wallet.address, + service.address, + payload, + tokenSymbol, + amount, + ); + + await expectRevert( + (gasOptions) => service.executeWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), + service, + 'InvalidMessageType', + [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER], + ); + }); + }); + describe('Receive Remote Tokens', () => { let sourceAddress; const amount = 1234; From 76a2545aef0c6fda53b6fa62c5d06190b4d61fb8 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 16 Feb 2024 12:18:07 +0200 Subject: [PATCH 19/38] a small style fix --- contracts/InterchainTokenFactory.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index a5b90129..cf05ef2b 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -62,8 +62,8 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M * @param salt A unique identifier to generate the salt. * @return bytes32 The calculated salt for the interchain token. */ - function interchainTokenSalt(bytes32 chainNameHash_, address deployer, bytes32 salt) public pure returns (bytes32) { - return keccak256(abi.encode(PREFIX_INTERCHAIN_TOKEN_SALT, chainNameHash_, deployer, salt)); + function interchainTokenSalt(bytes32 chainNameHash_, address deployer, bytes32 salt) public pure returns (bytes32 salt) { + salt = keccak256(abi.encode(PREFIX_INTERCHAIN_TOKEN_SALT, chainNameHash_, deployer, salt)); } /** From 74f09c6c4a5331697786ba910d40976535cfb7bb Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 16 Feb 2024 18:14:37 +0200 Subject: [PATCH 20/38] fixed a bug --- contracts/InterchainTokenFactory.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/InterchainTokenFactory.sol b/contracts/InterchainTokenFactory.sol index cf05ef2b..b8bd7ba0 100644 --- a/contracts/InterchainTokenFactory.sol +++ b/contracts/InterchainTokenFactory.sol @@ -60,10 +60,10 @@ contract InterchainTokenFactory is IInterchainTokenFactory, ITokenManagerType, M * @param chainNameHash_ The hash of the chain name. * @param deployer The address of the deployer. * @param salt A unique identifier to generate the salt. - * @return bytes32 The calculated salt for the interchain token. + * @return tokenSalt The calculated salt for the interchain token. */ - function interchainTokenSalt(bytes32 chainNameHash_, address deployer, bytes32 salt) public pure returns (bytes32 salt) { - salt = keccak256(abi.encode(PREFIX_INTERCHAIN_TOKEN_SALT, chainNameHash_, deployer, salt)); + function interchainTokenSalt(bytes32 chainNameHash_, address deployer, bytes32 salt) public pure returns (bytes32 tokenSalt) { + tokenSalt = keccak256(abi.encode(PREFIX_INTERCHAIN_TOKEN_SALT, chainNameHash_, deployer, salt)); } /** From e896028809e898ce59a8f5b1020b220f9159edcd Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 20 Feb 2024 14:31:38 +0200 Subject: [PATCH 21/38] addressed some comments --- contracts/InterchainTokenService.sol | 57 +++--- .../interfaces/IInterchainTokenFactory.sol | 8 +- .../interfaces/IInterchainTokenService.sol | 2 +- ...te3Address.sol => Create3AddressFixed.sol} | 6 +- contracts/utils/Create3Fixed.sol | 9 +- hardhat.config.js | 9 +- test/InterchainTokenService.js | 168 ++++++++++++++++-- test/UtilsTest.js | 13 ++ 8 files changed, 222 insertions(+), 50 deletions(-) rename contracts/utils/{Create3Address.sol => Create3AddressFixed.sol} (85%) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 5ac409a8..742d7d17 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -23,7 +23,7 @@ import { IInterchainTokenExecutable } from './interfaces/IInterchainTokenExecuta import { IInterchainTokenExpressExecutable } from './interfaces/IInterchainTokenExpressExecutable.sol'; import { ITokenManager } from './interfaces/ITokenManager.sol'; import { IERC20Named } from './interfaces/IERC20Named.sol'; -import { Create3Address } from './utils/Create3Address.sol'; +import { Create3AddressFixed } from './utils/Create3AddressFixed.sol'; import { Operator } from './utils/Operator.sol'; @@ -39,7 +39,7 @@ contract InterchainTokenService is Operator, Pausable, Multicall, - Create3Address, + Create3AddressFixed, ExpressExecutorTracker, InterchainAddressTracker, IInterchainTokenService @@ -631,7 +631,7 @@ contract InterchainTokenService is string calldata sourceChain, string calldata sourceAddress, bytes calldata payload - ) public onlyRemoteService(sourceChain, sourceAddress) whenNotPaused { + ) external onlyRemoteService(sourceChain, sourceAddress) whenNotPaused { bytes32 payloadHash = keccak256(payload); if (!gateway.validateContractCall(commandId, sourceChain, sourceAddress, payloadHash)) revert NotApprovedByGateway(); @@ -639,6 +639,17 @@ contract InterchainTokenService is _execute(commandId, sourceChain, sourceAddress, payload, payloadHash); } + /** + * @notice Returns the amount of token that this call is worth. + * @dev If `tokenAddress` is `0`, then value is in terms of the native token, otherwise it's in terms of the token address. + * @param sourceChain The source chain. + * @param sourceAddress The source address on the source chain. + * @param payload The payload sent with the call. + * @param symbol The symbol symbol for the call. + * @param amount The amount for the call. + * @return address The token address. + * @return uint256 The value the call is worth. + */ function contractCallWithTokenValue( string calldata sourceChain, string calldata sourceAddress, @@ -674,11 +685,18 @@ contract InterchainTokenService is _executeWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount); } + /** + * @notice Check that the tokenId is has a token that is registered in the gateway with the proper tokenSymbol. + * Also check that the amount in the payload matches the one for the call. + * @param payload The payload for the call contract with token. + * @param tokenSymbol The tokenSymbol for the call contract with token. + * @param amount The amount for the call contract with token. + */ function _checkPayloadAgainstGatewayData(bytes calldata payload, string calldata tokenSymbol, uint256 amount) internal view { (, bytes32 tokenId, , , uint256 amountInPayload) = abi.decode(payload, (uint256, bytes32, uint256, uint256, uint256)); if (validTokenAddress(tokenId) != gateway.tokenAddresses(tokenSymbol) || amount != amountInPayload) - revert InvalidGatewayTokenTransfer(payload, tokenSymbol, amount); + revert InvalidGatewayTokenTransfer(tokenId, payload, tokenSymbol, amount); } /** @@ -869,12 +887,7 @@ contract InterchainTokenService is ) internal { uint256 messageType = abi.decode(payload, (uint256)); if (messageType == MESSAGE_TYPE_INTERCHAIN_TRANSFER) { - address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); - - if (expressExecutor != address(0)) { - emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); - } - + address expressExecutor = _getExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); _processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload); } else if (messageType == MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER) { _processDeployTokenManagerPayload(payload); @@ -895,22 +908,18 @@ contract InterchainTokenService is ) internal { bytes32 payloadHash = keccak256(payload); + address expressExecutor = _getExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); + + if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) + revert NotApprovedByGateway(); + uint256 messageType = abi.decode(payload, (uint256)); if (messageType != MESSAGE_TYPE_INTERCHAIN_TRANSFER) { revert InvalidMessageType(messageType); } _checkPayloadAgainstGatewayData(payload, tokenSymbol, amount); - - address expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); - - if (expressExecutor != address(0)) { - emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); - } - - if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) - revert NotApprovedByGateway(); - + _processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload); } @@ -1166,4 +1175,12 @@ contract InterchainTokenService is return (validTokenAddress(tokenId), amount); } + + function _getExpressExecutor(bytes32 commandId, string calldata sourceChain, string calldata sourceAddress, bytes32 payloadHash) internal returns (address expressExecutor) { + expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); + + if (expressExecutor != address(0)) { + emit ExpressExecutionFulfilled(commandId, sourceChain, sourceAddress, payloadHash, expressExecutor); + } + } } diff --git a/contracts/interfaces/IInterchainTokenFactory.sol b/contracts/interfaces/IInterchainTokenFactory.sol index 7e55efbf..65301562 100644 --- a/contracts/interfaces/IInterchainTokenFactory.sol +++ b/contracts/interfaces/IInterchainTokenFactory.sol @@ -37,9 +37,9 @@ interface IInterchainTokenFactory is IUpgradable, IMulticall { * @param chainNameHash_ The hash of the chain name. * @param deployer The address of the deployer. * @param salt A unique identifier to generate the salt. - * @return bytes32 The calculated salt for the interchain token. + * @return tokenSalt The calculated salt for the interchain token. */ - function interchainTokenSalt(bytes32 chainNameHash_, address deployer, bytes32 salt) external view returns (bytes32); + function interchainTokenSalt(bytes32 chainNameHash_, address deployer, bytes32 salt) external view returns (bytes32 tokenSalt); /** * @notice Computes the ID for an interchain token based on the deployer and a salt. @@ -97,9 +97,9 @@ interface IInterchainTokenFactory is IUpgradable, IMulticall { * @notice Calculates the salt for a canonical interchain token. * @param chainNameHash_ The hash of the chain name. * @param tokenAddress The address of the token. - * @return salt The calculated salt for the interchain token. + * @return tokenSalt The calculated salt for the interchain token. */ - function canonicalInterchainTokenSalt(bytes32 chainNameHash_, address tokenAddress) external view returns (bytes32 salt); + function canonicalInterchainTokenSalt(bytes32 chainNameHash_, address tokenAddress) external view returns (bytes32 tokenSalt); /** * @notice Computes the ID for a canonical interchain token based on its address. diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 41d5b8a9..5ea627c5 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -47,7 +47,7 @@ interface IInterchainTokenService is error TokenHandlerFailed(bytes data); error EmptyData(); error PostDeployFailed(bytes data); - error InvalidGatewayTokenTransfer(bytes payload, string tokenSymbol, uint256 amount); + error InvalidGatewayTokenTransfer(bytes32 tokenId, bytes payload, string tokenSymbol, uint256 amount); event InterchainTransfer( bytes32 indexed tokenId, diff --git a/contracts/utils/Create3Address.sol b/contracts/utils/Create3AddressFixed.sol similarity index 85% rename from contracts/utils/Create3Address.sol rename to contracts/utils/Create3AddressFixed.sol index 3269f549..faaf8bf9 100644 --- a/contracts/utils/Create3Address.sol +++ b/contracts/utils/Create3AddressFixed.sol @@ -3,10 +3,12 @@ pragma solidity ^0.8.0; /** - * @title Create3Address contract + * @title Create3AddressFixed contract * @notice This contract can be used to predict the deterministic deployment address of a contract deployed with the `CREATE3` technique. + * It is equivalent to the Create3Address found in axelar-sdk-solidity but uses a fixed bytecode for CreateDeploy, + * which allows changing compilation options (like number of runs) without affecting the future deployment addresses. */ -contract Create3Address { +contract Create3AddressFixed { // slither-disable-next-line too-many-digits bytes internal constant CREATE_DEPLOY_BYTECODE = hex'608060405234801561001057600080fd5b50610162806100206000396000f3fe60806040526004361061001d5760003560e01c806277436014610022575b600080fd5b61003561003036600461007b565b610037565b005b8051602082016000f061004957600080fd5b50565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b60006020828403121561008d57600080fd5b813567ffffffffffffffff808211156100a557600080fd5b818401915084601f8301126100b957600080fd5b8135818111156100cb576100cb61004c565b604051601f8201601f19908116603f011681019083821181831017156100f3576100f361004c565b8160405282815287602084870101111561010c57600080fd5b82602086016020830137600092810160200192909252509594505050505056fea264697066735822122094780ce55d28f1d568f4e0ab1b9dc230b96e952b73d2e06456fbff2289fa27f464736f6c63430008150033'; diff --git a/contracts/utils/Create3Fixed.sol b/contracts/utils/Create3Fixed.sol index b4694473..f2bf5907 100644 --- a/contracts/utils/Create3Fixed.sol +++ b/contracts/utils/Create3Fixed.sol @@ -5,14 +5,15 @@ pragma solidity ^0.8.0; import { IDeploy } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/interfaces/IDeploy.sol'; import { ContractAddress } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/ContractAddress.sol'; import { CreateDeploy } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/deploy/CreateDeploy.sol'; -import { Create3Address } from './Create3Address.sol'; +import { Create3AddressFixed } from './Create3AddressFixed.sol'; /** - * @title Create3 contract + * @title Create3Fixed contract * @notice This contract can be used to deploy a contract with a deterministic address that depends only on - * the deployer address and deployment salt, not the contract bytecode and constructor parameters. + * the deployer address and deployment salt, not the contract bytecode and constructor parameters. + * It uses a fixed bytecode to allow changing the compilation settings without affecting the deployment address in the future. */ -contract Create3Fixed is Create3Address, IDeploy { +contract Create3Fixed is Create3AddressFixed, IDeploy { using ContractAddress for address; /** diff --git a/hardhat.config.js b/hardhat.config.js index 2acbe3e3..c55dd8d0 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -27,10 +27,6 @@ const optimizerSettings = { }, }, }; -const itsOptimizerSettings = { - ...optimizerSettings, - runs: 600, // Reduce runs to keep bytecode size under limit -}; const compilerSettings = { version: '0.8.21', settings: { @@ -42,7 +38,10 @@ const itsCompilerSettings = { version: '0.8.21', settings: { evmVersion: process.env.EVM_VERSION || 'london', - optimizer: itsOptimizerSettings, + optimizer: { + ...optimizerSettings, + runs: 600, // Reduce runs to keep bytecode size under limit + }, }, }; diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 15090b41..3c83519c 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -1019,7 +1019,6 @@ describe('Interchain Token Service', () => { const symbol = 'GTA'; const decimals = 18; const [token] = await deployFunctions.gateway(name, symbol, decimals); - console.log(await token.allowance(service.address, gateway.address)); const salt = getRandomBytes32(); const tokenId = await service.interchainTokenId(wallet.address, salt); @@ -1355,7 +1354,7 @@ describe('Interchain Token Service', () => { await deployFunctions.gateway(tokenName, tokenSymbol, tokenDecimals); }); - it('Should revert on execute if remote address validation fails', async () => { + it('Should revert on execute with token if remote address validation fails', async () => { const commandId = await approveContractCallWithMint( gateway, sourceChain, @@ -1373,7 +1372,7 @@ describe('Interchain Token Service', () => { ); }); - it('Should revert on execute if the service is paused', async () => { + it('Should revert on execute with token if the service is paused', async () => { await service.setPauseStatus(true).then((tx) => tx.wait); const commandId = await approveContractCallWithMint( @@ -1395,25 +1394,28 @@ describe('Interchain Token Service', () => { await service.setPauseStatus(false).then((tx) => tx.wait); }); - it('Should revert on execute with invalid messageType', async () => { - const tokenId = getRandomBytes32(); + it('Should revert on execute with token with invalid messageType', async () => { + const symbol = 'TS3'; + const [token, , tokenId] = await deployFunctions.gateway('Name', symbol, 15, amount); + + await token.transfer(gateway.address, amount).then((tx) => tx.wait); const payload = defaultAbiCoder.encode( - ['uint256', 'bytes32', 'bytes', 'uint256'], - [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, destAddress, amount], + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256'], + [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER, tokenId, sourceAddress, destAddress, amount], ); const commandId = await approveContractCallWithMint( gateway, sourceChain, - wallet.address, + sourceAddress, service.address, payload, - tokenSymbol, + symbol, amount, ); await expectRevert( - (gasOptions) => service.executeWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), + (gasOptions) => service.executeWithToken(commandId, sourceChain, sourceAddress, payload, symbol, amount, gasOptions), service, 'InvalidMessageType', [MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER], @@ -2348,6 +2350,144 @@ describe('Interchain Token Service', () => { }); }); + describe('Express Execute With Token', () => { + const commandId = getRandomBytes32(); + const sourceAddress = '0x1234'; + const amount = 1234; + const destinationAddress = new Wallet(getRandomBytes32()).address; + const tokenName = 'name'; + const tokenSymbol = 'symbol'; + const tokenDecimals = 16; + const message = 'message'; + let data; + let tokenId; + let executable; + let invalidExecutable; + let token; + + before(async () => { + [token, , tokenId] = await deployFunctions.gateway(tokenName, tokenSymbol, tokenDecimals, amount * 2, true); + await token.approve(service.address, amount * 2).then((tx) => tx.wait); + data = defaultAbiCoder.encode(['address', 'string'], [destinationAddress, message]); + executable = await deployContract(wallet, 'TestInterchainExecutable', [service.address]); + invalidExecutable = await deployContract(wallet, 'TestInvalidInterchainExecutable', [service.address]); + }); + + it('Should revert on executeWithInterchainToken when not called by the service', async () => { + await expectRevert( + (gasOptions) => + executable.executeWithInterchainToken( + commandId, + sourceChain, + sourceAddress, + data, + tokenId, + token.address, + amount, + gasOptions, + ), + executable, + 'NotService', + [wallet.address], + ); + }); + + it('Should revert on expressExecuteWithInterchainToken when not called by the service', async () => { + await expectRevert( + (gasOptions) => + executable.expressExecuteWithInterchainToken( + commandId, + sourceChain, + sourceAddress, + data, + tokenId, + token.address, + amount, + gasOptions, + ), + executable, + 'NotService', + [wallet.address], + ); + }); + + it('Should revert on express execute with token when service is paused', async () => { + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [MESSAGE_TYPE_INTERCHAIN_TRANSFER, tokenId, hexlify(wallet.address), destinationAddress, amount, '0x'], + ); + + await service.setPauseStatus(true).then((tx) => tx.wait); + + await expectRevert( + (gasOptions) => service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), + service, + 'Pause', + ); + + await service.setPauseStatus(false).then((tx) => tx.wait); + }); + + it('Should express execute with token', async () => { + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', 'bytes'], + [MESSAGE_TYPE_INTERCHAIN_TRANSFER, tokenId, hexlify(wallet.address), destinationAddress, amount, '0x'], + ); + await expect(service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount)) + .to.emit(service, 'ExpressExecuted') + .withArgs(commandId, sourceChain, sourceAddress, keccak256(payload), wallet.address) + .and.to.emit(token, 'Transfer') + .withArgs(wallet.address, destinationAddress, amount); + }); + + it('Should revert on express execute if token handler transfer token from fails', async () => { + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', ' bytes'], + [MESSAGE_TYPE_INTERCHAIN_TRANSFER, tokenId, sourceAddress, AddressZero, amount, data], + ); + + const errorSignatureHash = id('TokenTransferFailed()'); + const errorData = errorSignatureHash.substring(0, 10); + + await expectRevert( + (gasOptions) => service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), + service, + 'TokenHandlerFailed', + [errorData], + ); + }); + + it('Should revert on express execute with token if token transfer fails on destination chain', async () => { + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', ' bytes'], + [MESSAGE_TYPE_INTERCHAIN_TRANSFER, tokenId, sourceAddress, invalidExecutable.address, amount, data], + ); + + await expectRevert( + (gasOptions) => service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), + service, + 'ExpressExecuteWithInterchainTokenFailed', + [invalidExecutable.address], + ); + }); + + it('Should express execute with token', async () => { + const payload = defaultAbiCoder.encode( + ['uint256', 'bytes32', 'bytes', 'bytes', 'uint256', ' bytes'], + [MESSAGE_TYPE_INTERCHAIN_TRANSFER, tokenId, sourceAddress, executable.address, amount, data], + ); + await expect(service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount)) + .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') + .withArgs(executable.address, destinationAddress, amount) + .and.to.emit(executable, 'MessageReceived') + .withArgs(commandId, sourceChain, sourceAddress, destinationAddress, message, tokenId, amount); + }); + }); + describe('Express Receive Remote Token', () => { let sourceAddress; const amount = 1234; @@ -2840,7 +2980,7 @@ describe('Interchain Token Service', () => { [, , tokenId] = await deployFunctions.gateway(name, symbol, decimals); }); - it('Should revert on contractCallValue if not called by remote service', async () => { + it('Should revert on contractCallWithTokenValue if not called by remote service', async () => { const payload = '0x'; await expectRevert( @@ -2850,7 +2990,7 @@ describe('Interchain Token Service', () => { ); }); - it('Should revert on contractCallValue if service is paused', async () => { + it('Should revert on contractCallWithTokenValue if service is paused', async () => { const payload = '0x'; await service.setTrustedAddress(sourceChain, trustedAddress).then((tx) => tx.wait); @@ -2889,7 +3029,7 @@ describe('Interchain Token Service', () => { service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, 'wrong symbol', amount, gasOptions), service, 'InvalidGatewayTokenTransfer', - [payload, 'wrong symbol', amount], + [tokenId, payload, 'wrong symbol', amount], ); }); @@ -2902,7 +3042,7 @@ describe('Interchain Token Service', () => { (gasOptions) => service.contractCallWithTokenValue(sourceChain, trustedAddress, payload, symbol, amount + 1, gasOptions), service, 'InvalidGatewayTokenTransfer', - [payload, symbol, amount + 1], + [tokenId, payload, symbol, amount + 1], ); }); diff --git a/test/UtilsTest.js b/test/UtilsTest.js index eee3c583..67369281 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -347,6 +347,19 @@ describe('Create3Deployer', () => { await expect(deployer.deploy(bytecode, salt)).to.emit(deployer, 'Deployed').withArgs(address); }); + + if (isHardhat) { + it('should deploy to the predicted address with a know salt', async () => { + const salt = '0x4943fe1231449cc1baa660716a0cb38ff09af0b2c9acb63d40d9a7ba06d33d21'; + + const address = '0xa0523e6a9989706465F17E668Ff5A6766b58B855'; + + const factory = new ContractFactory(BurnableMintableCappedERC20.abi, BurnableMintableCappedERC20.bytecode); + const bytecode = factory.getDeployTransaction(name, symbol, decimals).data; + + await expect(deployer.deploy(bytecode, salt)).to.emit(deployer, 'Deployed').withArgs(address); + }); + } it('should not forward native value', async () => { const salt = getRandomBytes32(); From b57cefcc2587459cf74d64514a697d0ce359a475 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 20 Feb 2024 14:33:54 +0200 Subject: [PATCH 22/38] prettier --- contracts/InterchainTokenService.sol | 13 +++++++++---- contracts/utils/Create3AddressFixed.sol | 4 ++-- contracts/utils/Create3Fixed.sol | 2 +- test/InterchainTokenService.js | 9 ++++++--- test/UtilsTest.js | 4 ++-- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 742d7d17..ec283c53 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -686,7 +686,7 @@ contract InterchainTokenService is } /** - * @notice Check that the tokenId is has a token that is registered in the gateway with the proper tokenSymbol. + * @notice Check that the tokenId is has a token that is registered in the gateway with the proper tokenSymbol. * Also check that the amount in the payload matches the one for the call. * @param payload The payload for the call contract with token. * @param tokenSymbol The tokenSymbol for the call contract with token. @@ -910,7 +910,7 @@ contract InterchainTokenService is address expressExecutor = _getExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); - if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) + if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) revert NotApprovedByGateway(); uint256 messageType = abi.decode(payload, (uint256)); @@ -919,7 +919,7 @@ contract InterchainTokenService is } _checkPayloadAgainstGatewayData(payload, tokenSymbol, amount); - + _processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload); } @@ -1176,7 +1176,12 @@ contract InterchainTokenService is return (validTokenAddress(tokenId), amount); } - function _getExpressExecutor(bytes32 commandId, string calldata sourceChain, string calldata sourceAddress, bytes32 payloadHash) internal returns (address expressExecutor) { + function _getExpressExecutor( + bytes32 commandId, + string calldata sourceChain, + string calldata sourceAddress, + bytes32 payloadHash + ) internal returns (address expressExecutor) { expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); if (expressExecutor != address(0)) { diff --git a/contracts/utils/Create3AddressFixed.sol b/contracts/utils/Create3AddressFixed.sol index faaf8bf9..a0151101 100644 --- a/contracts/utils/Create3AddressFixed.sol +++ b/contracts/utils/Create3AddressFixed.sol @@ -5,8 +5,8 @@ pragma solidity ^0.8.0; /** * @title Create3AddressFixed contract * @notice This contract can be used to predict the deterministic deployment address of a contract deployed with the `CREATE3` technique. - * It is equivalent to the Create3Address found in axelar-sdk-solidity but uses a fixed bytecode for CreateDeploy, - * which allows changing compilation options (like number of runs) without affecting the future deployment addresses. + * It is equivalent to the Create3Address found in axelar-sdk-solidity but uses a fixed bytecode for CreateDeploy, + * which allows changing compilation options (like number of runs) without affecting the future deployment addresses. */ contract Create3AddressFixed { // slither-disable-next-line too-many-digits diff --git a/contracts/utils/Create3Fixed.sol b/contracts/utils/Create3Fixed.sol index f2bf5907..3d13d946 100644 --- a/contracts/utils/Create3Fixed.sol +++ b/contracts/utils/Create3Fixed.sol @@ -10,7 +10,7 @@ import { Create3AddressFixed } from './Create3AddressFixed.sol'; /** * @title Create3Fixed contract * @notice This contract can be used to deploy a contract with a deterministic address that depends only on - * the deployer address and deployment salt, not the contract bytecode and constructor parameters. + * the deployer address and deployment salt, not the contract bytecode and constructor parameters. * It uses a fixed bytecode to allow changing the compilation settings without affecting the deployment address in the future. */ contract Create3Fixed is Create3AddressFixed, IDeploy { diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index 3c83519c..bef1d418 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -2420,7 +2420,8 @@ describe('Interchain Token Service', () => { await service.setPauseStatus(true).then((tx) => tx.wait); await expectRevert( - (gasOptions) => service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), + (gasOptions) => + service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), service, 'Pause', ); @@ -2450,7 +2451,8 @@ describe('Interchain Token Service', () => { const errorData = errorSignatureHash.substring(0, 10); await expectRevert( - (gasOptions) => service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), + (gasOptions) => + service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), service, 'TokenHandlerFailed', [errorData], @@ -2464,7 +2466,8 @@ describe('Interchain Token Service', () => { ); await expectRevert( - (gasOptions) => service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), + (gasOptions) => + service.expressExecuteWithToken(commandId, sourceChain, sourceAddress, payload, tokenSymbol, amount, gasOptions), service, 'ExpressExecuteWithInterchainTokenFailed', [invalidExecutable.address], diff --git a/test/UtilsTest.js b/test/UtilsTest.js index 67369281..2aa008bd 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -347,7 +347,7 @@ describe('Create3Deployer', () => { await expect(deployer.deploy(bytecode, salt)).to.emit(deployer, 'Deployed').withArgs(address); }); - + if (isHardhat) { it('should deploy to the predicted address with a know salt', async () => { const salt = '0x4943fe1231449cc1baa660716a0cb38ff09af0b2c9acb63d40d9a7ba06d33d21'; @@ -356,7 +356,7 @@ describe('Create3Deployer', () => { const factory = new ContractFactory(BurnableMintableCappedERC20.abi, BurnableMintableCappedERC20.bytecode); const bytecode = factory.getDeployTransaction(name, symbol, decimals).data; - + await expect(deployer.deploy(bytecode, salt)).to.emit(deployer, 'Deployed').withArgs(address); }); } From 5e311f25ac078b8a81a4e9d5e0c64cb9bb915190 Mon Sep 17 00:00:00 2001 From: Foivos Date: Tue, 20 Feb 2024 14:44:08 +0200 Subject: [PATCH 23/38] fixed a test --- test/UtilsTest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/UtilsTest.js b/test/UtilsTest.js index 2aa008bd..ce02c88c 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -352,7 +352,7 @@ describe('Create3Deployer', () => { it('should deploy to the predicted address with a know salt', async () => { const salt = '0x4943fe1231449cc1baa660716a0cb38ff09af0b2c9acb63d40d9a7ba06d33d21'; - const address = '0xa0523e6a9989706465F17E668Ff5A6766b58B855'; + const address = '0x03C2D7E8Fbcc46C62B3DCBB72121818334af2565'; const factory = new ContractFactory(BurnableMintableCappedERC20.abi, BurnableMintableCappedERC20.bytecode); const bytecode = factory.getDeployTransaction(name, symbol, decimals).data; From 0fcba298091706322766044b97d32fd17a55d492 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 21 Feb 2024 18:03:11 +0200 Subject: [PATCH 24/38] remove modifier that should not exist --- contracts/InterchainTokenService.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index ec283c53..faefcd44 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -656,7 +656,7 @@ contract InterchainTokenService is bytes calldata payload, string calldata symbol, uint256 amount - ) public view virtual onlyRemoteService(sourceChain, sourceAddress) whenNotPaused returns (address, uint256) { + ) public view virtual returns (address, uint256) { _checkPayloadAgainstGatewayData(payload, symbol, amount); return _contractCallValue(payload); } From c5a5ba53b138f27752b6c9de3ec574f78f37f39e Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 21 Feb 2024 18:05:35 +0200 Subject: [PATCH 25/38] rename a function --- contracts/InterchainTokenService.sol | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index faefcd44..e552ddca 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -887,7 +887,7 @@ contract InterchainTokenService is ) internal { uint256 messageType = abi.decode(payload, (uint256)); if (messageType == MESSAGE_TYPE_INTERCHAIN_TRANSFER) { - address expressExecutor = _getExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); + address expressExecutor = _getExpressExecutorAndEmitEvent(commandId, sourceChain, sourceAddress, payloadHash); _processInterchainTransferPayload(commandId, expressExecutor, sourceChain, payload); } else if (messageType == MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER) { _processDeployTokenManagerPayload(payload); @@ -908,7 +908,7 @@ contract InterchainTokenService is ) internal { bytes32 payloadHash = keccak256(payload); - address expressExecutor = _getExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); + address expressExecutor = _getExpressExecutorAndEmitEvent(commandId, sourceChain, sourceAddress, payloadHash); if (!gateway.validateContractCallAndMint(commandId, sourceChain, sourceAddress, payloadHash, tokenSymbol, amount)) revert NotApprovedByGateway(); @@ -1176,7 +1176,7 @@ contract InterchainTokenService is return (validTokenAddress(tokenId), amount); } - function _getExpressExecutor( + function _getExpressExecutorAndEmitEvent( bytes32 commandId, string calldata sourceChain, string calldata sourceAddress, From dbc605d2ec996fc2748992a7ff5d9637e7f40d06 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 21 Feb 2024 18:06:48 +0200 Subject: [PATCH 26/38] Update contracts/InterchainTokenService.sol Co-authored-by: Milap Sheth --- contracts/InterchainTokenService.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index e552ddca..c0f48f4a 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -686,7 +686,7 @@ contract InterchainTokenService is } /** - * @notice Check that the tokenId is has a token that is registered in the gateway with the proper tokenSymbol. + * @notice Check that the tokenId from the payload is a token that is registered in the gateway with the proper tokenSymbol, with the right amount from the payload. * Also check that the amount in the payload matches the one for the call. * @param payload The payload for the call contract with token. * @param tokenSymbol The tokenSymbol for the call contract with token. From b1c926603fe0291ef4a4c83dae4e76e8ef0f2311 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 21 Feb 2024 18:10:22 +0200 Subject: [PATCH 27/38] reinteroduce the modifiers since they are needed after all --- contracts/InterchainTokenService.sol | 2 +- test/UtilsTest.js | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index c0f48f4a..03c0be21 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -656,7 +656,7 @@ contract InterchainTokenService is bytes calldata payload, string calldata symbol, uint256 amount - ) public view virtual returns (address, uint256) { + ) public view virtual onlyRemoteService(sourceChain, sourceAddress) whenNotPaused returns (address, uint256) { _checkPayloadAgainstGatewayData(payload, symbol, amount); return _contractCallValue(payload); } diff --git a/test/UtilsTest.js b/test/UtilsTest.js index ce02c88c..5b2eadf2 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -348,7 +348,8 @@ describe('Create3Deployer', () => { await expect(deployer.deploy(bytecode, salt)).to.emit(deployer, 'Deployed').withArgs(address); }); - if (isHardhat) { + // Reintroduce this test if we know the address of the deployer. + /* if (isHardhat) { it('should deploy to the predicted address with a know salt', async () => { const salt = '0x4943fe1231449cc1baa660716a0cb38ff09af0b2c9acb63d40d9a7ba06d33d21'; @@ -359,7 +360,7 @@ describe('Create3Deployer', () => { await expect(deployer.deploy(bytecode, salt)).to.emit(deployer, 'Deployed').withArgs(address); }); - } + } */ it('should not forward native value', async () => { const salt = getRandomBytes32(); From 515be9c9e493ee1963738a0e980ce71f4171d529 Mon Sep 17 00:00:00 2001 From: Milap Sheth Date: Fri, 23 Feb 2024 03:59:52 -0500 Subject: [PATCH 28/38] Update contracts/utils/Create3AddressFixed.sol --- contracts/utils/Create3AddressFixed.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/utils/Create3AddressFixed.sol b/contracts/utils/Create3AddressFixed.sol index a0151101..4dd4e81f 100644 --- a/contracts/utils/Create3AddressFixed.sol +++ b/contracts/utils/Create3AddressFixed.sol @@ -5,7 +5,7 @@ pragma solidity ^0.8.0; /** * @title Create3AddressFixed contract * @notice This contract can be used to predict the deterministic deployment address of a contract deployed with the `CREATE3` technique. - * It is equivalent to the Create3Address found in axelar-sdk-solidity but uses a fixed bytecode for CreateDeploy, + * It is equivalent to the Create3Address found in axelar-gmp-sdk-solidity repo but uses a fixed bytecode for CreateDeploy, * which allows changing compilation options (like number of runs) without affecting the future deployment addresses. */ contract Create3AddressFixed { From f787f13442237a27a748dc79c14a2f273098ecc7 Mon Sep 17 00:00:00 2001 From: Foivos Date: Fri, 23 Feb 2024 17:01:16 +0200 Subject: [PATCH 29/38] add a docstring --- contracts/InterchainTokenService.sol | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 03c0be21..8c5186eb 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -660,7 +660,16 @@ contract InterchainTokenService is _checkPayloadAgainstGatewayData(payload, symbol, amount); return _contractCallValue(payload); } - + + /** + * @notice Express executes with a gateway token operations based on the payload and selector. + * @param commandId The unique message id. + * @param sourceChain The chain 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. + * @param symbol The symbol symbol for the call. + * @param amount The amount for the call. + */ function expressExecuteWithToken( bytes32 commandId, string calldata sourceChain, From 10ed39408e53cab39cc9de10213757e384b1a96c Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 7 Mar 2024 15:27:46 +0200 Subject: [PATCH 30/38] prettier and fixed tests --- contracts/InterchainTokenService.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 8c5186eb..58c6ba3c 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -660,14 +660,14 @@ contract InterchainTokenService is _checkPayloadAgainstGatewayData(payload, symbol, amount); return _contractCallValue(payload); } - + /** * @notice Express executes with a gateway token operations based on the payload and selector. * @param commandId The unique message id. * @param sourceChain The chain 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. - * @param symbol The symbol symbol for the call. + * @param tokenSymbol The symbol symbol for the call. * @param amount The amount for the call. */ function expressExecuteWithToken( From ce3cf52235f2d50f5f545da9d1aa87693676c7a4 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 25 Mar 2024 16:52:34 +0200 Subject: [PATCH 31/38] fixed tests --- contracts/interfaces/IInterchainTokenFactory.sol | 6 ------ 1 file changed, 6 deletions(-) diff --git a/contracts/interfaces/IInterchainTokenFactory.sol b/contracts/interfaces/IInterchainTokenFactory.sol index e11f9d8d..7e55efbf 100644 --- a/contracts/interfaces/IInterchainTokenFactory.sol +++ b/contracts/interfaces/IInterchainTokenFactory.sol @@ -26,12 +26,6 @@ interface IInterchainTokenFactory is IUpgradable, IMulticall { */ function interchainTokenService() external view returns (IInterchainTokenService); - /** - * @notice Returns the address of the interchain token service. - * @return IInterchainTokenService The address of the interchain token service. - */ - function interchainTokenService() external view returns (IInterchainTokenService); - /** * @notice Returns the hash of the chain name. * @return bytes32 The hash of the chain name. From acfbecbb23b138faf552d3110a30f8d13fd19702 Mon Sep 17 00:00:00 2001 From: Foivos Date: Wed, 27 Mar 2024 16:09:23 +0200 Subject: [PATCH 32/38] added the changes --- contracts/InterchainTokenService.sol | 24 +++++++++++++++++------- contracts/TokenHandler.sol | 8 +++++++- 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index e018b8f8..eb61cf5a 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -53,6 +53,8 @@ contract InterchainTokenService is address public immutable interchainTokenDeployer; address public immutable tokenManagerDeployer; + string internal constant AXELAR = 'Axelar'; + /** * @dev Token manager implementation addresses */ @@ -73,6 +75,7 @@ contract InterchainTokenService is uint256 private constant MESSAGE_TYPE_INTERCHAIN_TRANSFER = 0; uint256 private constant MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN = 1; uint256 private constant MESSAGE_TYPE_DEPLOY_TOKEN_MANAGER = 2; + uint256 private constant MESSAGE_TYPE_ROUTE_PAYLOAD = 10; /** * @dev Tokens and token managers deployed via the Token Factory contract use a special deployer address. @@ -772,7 +775,7 @@ contract InterchainTokenService is * @param gasValue The amount of gas to be paid for the transaction. */ function _callContract( - string calldata destinationChain, + string memory destinationChain, bytes memory payload, MetadataVersion metadataVersion, uint256 gasValue @@ -780,6 +783,12 @@ contract InterchainTokenService is string memory destinationAddress = trustedAddress(destinationChain); if (bytes(destinationAddress).length == 0) revert UntrustedChain(); + if (bytes(destinationAddress).length == 1) { + payload = abi.encode(MESSAGE_TYPE_ROUTE_PAYLOAD, destinationChain, payload); + destinationChain = AXELAR; + destinationAddress = trustedAddress(destinationChain); + } + if (gasValue > 0) { if (metadataVersion == MetadataVersion.CONTRACT_CALL) { gasService.payNativeGasForContractCall{ value: gasValue }( @@ -812,7 +821,7 @@ contract InterchainTokenService is * @param gasValue The amount of gas to be paid for the transaction. */ function _callContractWithToken( - string calldata destinationChain, + string memory destinationChain, bytes memory payload, string memory symbol, uint256 amount, @@ -821,6 +830,12 @@ contract InterchainTokenService is ) internal { string memory destinationAddress = trustedAddress(destinationChain); if (bytes(destinationAddress).length == 0) revert UntrustedChain(); + + if (bytes(destinationAddress).length == 1) { + payload = abi.encode(MESSAGE_TYPE_ROUTE_PAYLOAD, destinationChain, payload); + destinationChain = AXELAR; + destinationAddress = trustedAddress(destinationChain); + } if (gasValue > 0) { if (metadataVersion == MetadataVersion.CONTRACT_CALL) { @@ -1085,8 +1100,6 @@ contract InterchainTokenService is if (!success) revert TakeTokenFailed(data); amount = abi.decode(data, (uint256)); - /// @dev Track the flow amount being sent out as a message - ITokenManager(tokenManager_).addFlowOut(amount); if (tokenManagerType == uint256(TokenManagerType.GATEWAY)) { symbol = IERC20Named(tokenAddress).symbol(); } @@ -1101,9 +1114,6 @@ contract InterchainTokenService is (uint256 tokenManagerType, address tokenAddress) = ITokenManagerProxy(tokenManager_).getImplementationTypeAndTokenAddress(); - /// @dev Track the flow amount being received via the message - ITokenManager(tokenManager_).addFlowIn(amount); - (bool success, bytes memory data) = tokenHandler.delegatecall( abi.encodeWithSelector(ITokenHandler.giveToken.selector, tokenManagerType, tokenAddress, tokenManager_, to, amount) ); diff --git a/contracts/TokenHandler.sol b/contracts/TokenHandler.sol index 0a9c432b..0bcc3139 100644 --- a/contracts/TokenHandler.sol +++ b/contracts/TokenHandler.sol @@ -47,6 +47,9 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard { address to, uint256 amount ) external payable returns (uint256) { + /// @dev Track the flow amount being received via the message + ITokenManager(tokenManager).addFlowIn(amount); + if (tokenManagerType == uint256(TokenManagerType.MINT_BURN) || tokenManagerType == uint256(TokenManagerType.MINT_BURN_FROM)) { _giveTokenMintBurn(tokenAddress, to, amount); return amount; @@ -86,7 +89,10 @@ contract TokenHandler is ITokenHandler, ITokenManagerType, ReentrancyGuard { address tokenManager, address from, uint256 amount - ) external payable returns (uint256) { + ) external payable returns (uint256) { + /// @dev Track the flow amount being sent out as a message + ITokenManager(tokenManager).addFlowOut(amount); + if (tokenManagerType == uint256(TokenManagerType.MINT_BURN)) { _takeTokenMintBurn(tokenAddress, from, amount); return amount; From a4a9a4979bdb2439ad7468539aa731c7d9bc57f0 Mon Sep 17 00:00:00 2001 From: Foivos Date: Thu, 11 Apr 2024 13:46:38 +0300 Subject: [PATCH 33/38] Added wrapping and unwrapping of payloads --- contracts/InterchainTokenService.sol | 24 +++++++++++++----------- hardhat.config.js | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index a3b015d8..7c44ff9b 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -7,7 +7,6 @@ import { IAxelarGasService } from '@axelar-network/axelar-gmp-sdk-solidity/contr 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 { AddressBytes } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/libs/AddressBytes.sol'; import { Multicall } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Multicall.sol'; import { Pausable } from '@axelar-network/axelar-gmp-sdk-solidity/contracts/utils/Pausable.sol'; @@ -716,8 +715,8 @@ contract InterchainTokenService is function _processInterchainTransferPayload( bytes32 commandId, address expressExecutor, - string calldata sourceChain, - bytes calldata payload + string memory sourceChain, + bytes memory payload ) internal { bytes32 tokenId; bytes memory sourceAddress; @@ -774,7 +773,7 @@ contract InterchainTokenService is * @notice Processes a deploy token manager payload. * @param payload The encoded data payload to be processed */ - function _processDeployTokenManagerPayload(bytes calldata payload) internal { + function _processDeployTokenManagerPayload(bytes memory payload) internal { (, bytes32 tokenId, TokenManagerType tokenManagerType, bytes memory params) = abi.decode( payload, (uint256, bytes32, TokenManagerType, bytes) @@ -787,7 +786,7 @@ contract InterchainTokenService is * @notice Processes a deploy interchain token manager payload. * @param payload The encoded data payload to be processed. */ - function _processDeployInterchainTokenPayload(bytes calldata payload) internal { + function _processDeployInterchainTokenPayload(bytes memory payload) internal { (, bytes32 tokenId, string memory name, string memory symbol, uint8 decimals, bytes memory minterBytes) = abi.decode( payload, (uint256, bytes32, string, string, uint8, bytes) @@ -861,7 +860,7 @@ contract InterchainTokenService is ) internal { string memory destinationAddress = trustedAddress(destinationChain); if (bytes(destinationAddress).length == 0) revert UntrustedChain(); - + if (bytes(destinationAddress).length == 1) { payload = abi.encode(MESSAGE_TYPE_ROUTE_PAYLOAD, destinationChain, payload); destinationChain = AXELAR; @@ -899,9 +898,9 @@ contract InterchainTokenService is function _execute( bytes32 commandId, - string calldata sourceChain, - string calldata sourceAddress, - bytes calldata payload, + string memory sourceChain, + string memory sourceAddress, + bytes memory payload, bytes32 payloadHash ) internal { uint256 messageType = abi.decode(payload, (uint256)); @@ -912,6 +911,9 @@ contract InterchainTokenService is _processDeployTokenManagerPayload(payload); } else if (messageType == MESSAGE_TYPE_DEPLOY_INTERCHAIN_TOKEN) { _processDeployInterchainTokenPayload(payload); + } else if (messageType == MESSAGE_TYPE_ROUTE_PAYLOAD) { + (, sourceChain, sourceAddress, payload) = abi.decode(payload, (uint256, string, string, bytes)); + _execute(commandId, sourceChain, sourceAddress, payload, keccak256(payload)); } else { revert InvalidMessageType(messageType); } @@ -1192,8 +1194,8 @@ contract InterchainTokenService is function _getExpressExecutorAndEmitEvent( bytes32 commandId, - string calldata sourceChain, - string calldata sourceAddress, + string memory sourceChain, + string memory sourceAddress, bytes32 payloadHash ) internal returns (address expressExecutor) { expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); diff --git a/hardhat.config.js b/hardhat.config.js index c55dd8d0..fd0c8ac0 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -40,7 +40,7 @@ const itsCompilerSettings = { evmVersion: process.env.EVM_VERSION || 'london', optimizer: { ...optimizerSettings, - runs: 600, // Reduce runs to keep bytecode size under limit + runs: 1, // Reduce runs to keep bytecode size under limit }, }, }; From c394e130d5e041c8b8997eefa11c523bb56bea43 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 15 Apr 2024 13:58:20 +0300 Subject: [PATCH 34/38] fix merge --- contracts/InterchainTokenService.sol | 4 ++-- contracts/interfaces/IInterchainTokenService.sol | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index cb5119d1..6a1b510b 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -1171,8 +1171,8 @@ contract InterchainTokenService is function _getExpressExecutorAndEmitEvent( bytes32 commandId, - string calldata sourceChain, - string calldata sourceAddress, + string memory sourceChain, + string memory sourceAddress, bytes32 payloadHash ) internal returns (address expressExecutor) { expressExecutor = _popExpressExecutor(commandId, sourceChain, sourceAddress, payloadHash); diff --git a/contracts/interfaces/IInterchainTokenService.sol b/contracts/interfaces/IInterchainTokenService.sol index 213ef440..f9fc592d 100644 --- a/contracts/interfaces/IInterchainTokenService.sol +++ b/contracts/interfaces/IInterchainTokenService.sol @@ -46,7 +46,6 @@ interface IInterchainTokenService is error TokenHandlerFailed(bytes data); error EmptyData(); error PostDeployFailed(bytes data); - error InvalidGatewayTokenTransfer(bytes32 tokenId, bytes payload, string tokenSymbol, uint256 amount); error ZeroAmount(); error InvalidGatewayTokenTransfer(bytes32 tokenId, bytes payload, string tokenSymbol, uint256 amount); From 5b0dd0fa9d6163571ce9ac5b61c5233e7aa6e5e1 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 15 Apr 2024 14:03:27 +0300 Subject: [PATCH 35/38] fixed compilation --- hardhat.config.js | 2 +- test/InterchainTokenService.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hardhat.config.js b/hardhat.config.js index c02b040f..88ea8008 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -40,7 +40,7 @@ const itsCompilerSettings = { evmVersion: process.env.EVM_VERSION || 'london', optimizer: { ...optimizerSettings, - runs: 600, // Reduce runs to keep bytecode size under limit + runs: 1, // Reduce runs to keep bytecode size under limit }, }, }; diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index cdefab02..79da4587 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -222,7 +222,7 @@ describe('Interchain Token Service', () => { ]); interchainTokenFactoryAddress = await getCreate3Address(create3Deployer.address, wallet, factoryDeploymentKey); - serviceTest = await deployContract(wallet, 'TestInterchainTokenService', [ + /*serviceTest = await deployContract(wallet, 'TestInterchainTokenService', [ tokenManagerDeployer.address, interchainTokenDeployer.address, gateway.address, @@ -231,7 +231,7 @@ describe('Interchain Token Service', () => { chainName, tokenManager.address, tokenHandler.address, - ]); + ]);*/ }); describe('Interchain Token Service Deployment', () => { @@ -243,7 +243,7 @@ describe('Interchain Token Service', () => { ); }); - it('Should test setup revert cases', async () => { + it.skip('Should test setup revert cases', async () => { const operator = wallet.address; const trustedChainNames = ['ChainA', 'ChainB']; const trustedAddresses = [wallet.address, wallet.address]; From 8abb2b271c6dade0f66703b6f41f16f30cc517a0 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 15 Apr 2024 16:15:25 +0300 Subject: [PATCH 36/38] fixed tests --- hardhat.config.js | 2 +- test/InterchainTokenService.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hardhat.config.js b/hardhat.config.js index aa8ac303..97e15209 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -40,7 +40,7 @@ const itsCompilerSettings = { evmVersion: process.env.EVM_VERSION || 'london', optimizer: { ...optimizerSettings, - runs: 1000, // Reduce runs to keep bytecode size under limit + runs: 100, // Reduce runs to keep bytecode size under limit }, }, }; diff --git a/test/InterchainTokenService.js b/test/InterchainTokenService.js index d8bbc8bc..88438162 100644 --- a/test/InterchainTokenService.js +++ b/test/InterchainTokenService.js @@ -222,7 +222,7 @@ describe('Interchain Token Service', () => { ]); interchainTokenFactoryAddress = await getCreate3Address(create3Deployer.address, wallet, factoryDeploymentKey); - /*serviceTest = await deployContract(wallet, 'TestInterchainTokenService', [ + serviceTest = await deployContract(wallet, 'TestInterchainTokenService', [ tokenManagerDeployer.address, interchainTokenDeployer.address, gateway.address, @@ -231,7 +231,7 @@ describe('Interchain Token Service', () => { chainName, tokenManager.address, tokenHandler.address, - ]);*/ + ]); }); describe('Interchain Token Service Deployment', () => { @@ -249,7 +249,7 @@ describe('Interchain Token Service', () => { ); }); - it.skip('Should test setup revert cases', async () => { + it('Should test setup revert cases', async () => { const operator = wallet.address; const trustedChainNames = ['ChainA', 'ChainB']; const trustedAddresses = [wallet.address, wallet.address]; From 37ee0c43677c940597a6ef7043cf0ecb6e8d4105 Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 15 Apr 2024 16:23:15 +0300 Subject: [PATCH 37/38] optimize compilation runs --- hardhat.config.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/hardhat.config.js b/hardhat.config.js index 97e15209..bdd2cd53 100644 --- a/hardhat.config.js +++ b/hardhat.config.js @@ -40,7 +40,17 @@ const itsCompilerSettings = { evmVersion: process.env.EVM_VERSION || 'london', optimizer: { ...optimizerSettings, - runs: 100, // Reduce runs to keep bytecode size under limit + runs: 800, // Reduce runs to keep bytecode size under limit + }, + }, +}; +const itsTestCompilerSettings = { + version: '0.8.21', + settings: { + evmVersion: process.env.EVM_VERSION || 'london', + optimizer: { + ...optimizerSettings, + runs: 200, // Reduce runs to keep bytecode size under limit }, }, }; @@ -58,7 +68,7 @@ module.exports = { 'contracts/proxies/Proxy.sol': compilerSettings, 'contracts/proxies/TokenManagerProxy.sol': compilerSettings, 'contracts/InterchainTokenService.sol': itsCompilerSettings, - 'contracts/test/TestInterchainTokenService.sol': itsCompilerSettings, + 'contracts/test/TestInterchainTokenService.sol': itsTestCompilerSettings, }, }, defaultNetwork: 'hardhat', From f18b7223d4a40e4eba8baa75d371a59f1c3a1efc Mon Sep 17 00:00:00 2001 From: Foivos Date: Mon, 15 Apr 2024 16:24:30 +0300 Subject: [PATCH 38/38] make lint happy --- contracts/InterchainTokenService.sol | 2 -- test/UtilsTest.js | 2 -- 2 files changed, 4 deletions(-) diff --git a/contracts/InterchainTokenService.sol b/contracts/InterchainTokenService.sol index 96d9b68c..6f784fad 100644 --- a/contracts/InterchainTokenService.sol +++ b/contracts/InterchainTokenService.sol @@ -3,7 +3,6 @@ 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'; @@ -19,7 +18,6 @@ import { IInterchainTokenDeployer } from './interfaces/IInterchainTokenDeployer. import { IInterchainTokenExecutable } from './interfaces/IInterchainTokenExecutable.sol'; import { IInterchainTokenExpressExecutable } from './interfaces/IInterchainTokenExpressExecutable.sol'; import { ITokenManager } from './interfaces/ITokenManager.sol'; -import { IERC20Named } from './interfaces/IERC20Named.sol'; import { Create3AddressFixed } from './utils/Create3AddressFixed.sol'; import { Operator } from './utils/Operator.sol'; diff --git a/test/UtilsTest.js b/test/UtilsTest.js index 490fdee6..0b7c64c1 100644 --- a/test/UtilsTest.js +++ b/test/UtilsTest.js @@ -6,13 +6,11 @@ const { Wallet, getContractAt, constants: { AddressZero }, - ContractFactory, } = ethers; const { time } = require('@nomicfoundation/hardhat-network-helpers'); const { expect } = chai; const { getRandomBytes32, expectRevert, isHardhat, waitFor } = require('./utils'); const { deployContract } = require('../scripts/deploy'); -const BurnableMintableCappedERC20 = require('../artifacts/contracts/test/TestMintableBurnableERC20.sol/TestMintableBurnableERC20.json'); let ownerWallet, otherWallet;