From 9b659ac190a513c54b07986dbc60956d6f022f8a Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Tue, 7 Nov 2023 00:46:31 -0500 Subject: [PATCH 01/11] feat: dedicated ITS script --- evm/its.js | 733 +++++++++++++++++++++++++++++++++++++++++++++++++++ evm/utils.js | 31 +++ 2 files changed, 764 insertions(+) create mode 100644 evm/its.js diff --git a/evm/its.js b/evm/its.js new file mode 100644 index 00000000..73045cfa --- /dev/null +++ b/evm/its.js @@ -0,0 +1,733 @@ +'use strict'; + +require('dotenv').config(); + +const { ethers } = require('hardhat'); +const { + getDefaultProvider, + utils: { hexZeroPad }, + Contract, +} = ethers; +const { Command, Option } = require('commander'); +const { + printInfo, + prompt, + printWarn, + printWalletInfo, + isValidAddress, + isKeccak256Hash, + wasEventEmitted, + mainProcessor, + isValidTokenId, + isValidNumber, + isString, + isValidCalldata, + isValidBytesAddress, + isNumberArray, +} = require('./utils'); +const { getWallet } = require('./sign-utils'); +const IInterchainTokenService = require('@axelar-network/interchain-token-service/dist/interchain-token-service/InterchainTokenService.sol'); +const tokenManagerImplementations = { + MINT_BURN: 0, + MINT_BURN_FROM: 1, + LOCK_UNLOCK: 2, + LOCK_UNLOCK_FEE: 3, +}; + +async function processCommand(chain, options) { + const { privateKey, address, action, yes } = options; + + const contracts = chain.contracts; + const contractName = 'InterchainTokenService'; + const contractConfig = contracts.InterchainTokenService; + + const interchainTokenServiceAddress = address || contracts.interchainTokenService?.address; + + if (!isValidAddress(interchainTokenServiceAddress)) { + throw new Error(`Contract ${contractName} is not deployed on ${chain.name}`); + } + + const rpc = chain.rpc; + const provider = getDefaultProvider(rpc); + + printInfo('Chain', chain.name); + + const wallet = await getWallet(privateKey, provider, options); + const { address: walletAddress } = await printWalletInfo(wallet, options); + + printInfo('Contract name', contractName); + printInfo('Contract address', interchainTokenServiceAddress); + + const interchainTokenService = new Contract(interchainTokenServiceAddress, IInterchainTokenService.abi, wallet); + + const gasOptions = contractConfig?.gasOptions || chain?.gasOptions || {}; + printInfo('Gas options', JSON.stringify(gasOptions, null, 2)); + + printInfo('Action', action); + + if (prompt(`Proceed with action ${action}`, yes)) { + return; + } + + switch (action) { + case 'contractId': { + const contractId = await interchainTokenService.contractId(); + printInfo('InterchainTokenService contract ID', contractId); + + break; + } + + case 'tokenManagerAddress': { + const tokenId = options.tokenId; + + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + const tokenManagerAddress = await interchainTokenService.tokenManagerAddress(tokenIdBytes32); + printInfo(`TokenManager address for tokenId: ${tokenId}:`, tokenManagerAddress); + + break; + } + + case 'validTokenManagerAddress': { + const tokenId = options.tokenId; + + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + try { + const tokenManagerAddress = await interchainTokenService.validTokenManagerAddress(tokenIdBytes32); + printInfo(`TokenManager for tokenId: ${tokenId} exists at address:`, tokenManagerAddress); + } catch (error) { + printInfo(`TokenManager for tokenId: ${tokenId} does not exist.`); + } + + break; + } + + case 'interchainTokenAddress': { + const tokenId = options.tokenId; + + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + const interchainTokenAddress = await interchainTokenService.interchainTokenAddress(tokenIdBytes32); + printInfo(`InterchainToken address for tokenId: ${tokenId}:`, interchainTokenAddress); + + break; + } + + case 'interchainTokenId': { + const sender = options.sender; + + if (!isValidAddress(sender)) { + throw new Error(`Invalid sender address: ${sender}`); + } + + const salt = options.salt; + + if (!isKeccak256Hash(salt)) { + throw new Error(`Invalid salt: ${salt}`); + } + + const interchainTokenId = await interchainTokenService.interchainTokenId(sender, salt); + printInfo(`InterchainTokenId for sender ${sender} and deployment salt: ${salt}`, interchainTokenId); + + break; + } + + case 'tokenManagerImplementation': { + const type = options.type; + + const tokenManagerImplementation = await interchainTokenService.tokenManagerImplementation(tokenManagerImplementations[type]); + printInfo(`${type} TokenManager implementation address:`, tokenManagerImplementation); + + break; + } + + case 'flowLimit': { + const tokenId = options.tokenId; + + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + const flowLimit = await interchainTokenService.flowLimit(tokenIdBytes32); + printInfo(`Flow limit for TokenManager with tokenId: ${tokenId}`, flowLimit); + + break; + } + + case 'flowOutAmount': { + const tokenId = options.tokenId; + + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + const flowOutAmount = await interchainTokenService.flowOutAmount(tokenIdBytes32); + printInfo(`Flow out amount for TokenManager with tokenId: ${tokenId}`, flowOutAmount); + + break; + } + + case 'flowInAmount': { + const tokenId = options.tokenId; + + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + const flowInAmount = await interchainTokenService.flowInAmount(tokenIdBytes32); + printInfo(`Flow out amount for TokenManager with tokenId: ${tokenId}`, flowInAmount); + + break; + } + + case 'deployTokenManager': { + const isPaused = await interchainTokenService.paused(); + + if (isPaused) { + throw new Error(`${action} invalid while service is paused.`); + } + + const { salt, destinationChain, type, params, gasValue } = options; + + if (!isKeccak256Hash(salt)) { + throw new Error(`Invalid salt: ${salt}`); + } + + if (!isString(destinationChain)) { + throw new Error(`Invalid destinationChain: ${destinationChain}`); + } + + if (!isValidCalldata(params)) { + throw new Error(`Invalid params: ${params}`); + } + + if (!isValidNumber(gasValue)) { + throw new Error(`Invalid gas value: ${gasValue}`); + } + + const tx = await interchainTokenService.deployTokenManager( + salt, + destinationChain, + tokenManagerImplementations[type], + params, + gasValue, + ); + printInfo('deploy TokenManager tx', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = + wasEventEmitted(receipt, interchainTokenService, 'TokenManagerDeployed') || + wasEventEmitted(receipt, interchainTokenService, 'TokenManagerDeploymentStarted'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'deployInterchainToken': { + const isPaused = await interchainTokenService.paused(); + + if (isPaused) { + throw new Error(`${action} invalid while service is paused.`); + } + + const { salt, destinationChain, name, symbol, decimals, distributor, gasValue } = options; + + if (!isKeccak256Hash(salt)) { + throw new Error(`Invalid salt: ${salt}`); + } + + if (!isString(destinationChain)) { + throw new Error(`Invalid destinationChain: ${destinationChain}`); + } + + if (!isString(name)) { + throw new Error(`Invalid name: ${name}`); + } + + if (!isString(symbol)) { + throw new Error(`Invalid symbol: ${symbol}`); + } + + if (!isValidNumber(decimals)) { + throw new Error(`Invalid decimals value: ${decimals}`); + } + + if (!isValidBytesAddress(distributor)) { + throw new Error(`Invalid distributor address: ${distributor}`); + } + + if (!isValidNumber(gasValue)) { + throw new Error(`Invalid gas value: ${gasValue}`); + } + + const tx = await interchainTokenService.deployInterchainToken( + salt, + destinationChain, + name, + symbol, + decimals, + distributor, + gasValue, + ); + printInfo('deploy InterchainToken tx', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = + wasEventEmitted(receipt, interchainTokenService, 'TokenManagerDeployed') || + wasEventEmitted(receipt, interchainTokenService, 'InterchainTokenDeploymentStarted'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'contractCallValue': { + const isPaused = await interchainTokenService.paused(); + + if (isPaused) { + throw new Error(`${action} invalid while service is paused.`); + } + + const { sourceChain, sourceAddress, payload } = options; + + if (!isString(sourceChain)) { + throw new Error(`Invalid sourceChain: ${sourceChain}`); + } + + if (!isString(sourceAddress)) { + throw new Error(`Invalid sourceAddress: ${sourceAddress}`); + } + + const isTrustedAddress = await interchainTokenService.isTrustedAddress(sourceChain, sourceAddress); + + if (!isTrustedAddress) { + throw new Error('Invalid remote service.'); + } + + if (!isValidCalldata(payload)) { + throw new Error(`Invalid payload: ${payload}`); + } + + const [tokenAddress, tokenAmount] = await interchainTokenService.contractCallValue(sourceChain, sourceAddress, payload); + printInfo(`Amount of tokens with address ${tokenAddress} that the call is worth:`, tokenAmount); + + break; + } + + case 'expressExecute': { + const isPaused = await interchainTokenService.paused(); + + if (isPaused) { + throw new Error(`${action} invalid while service is paused.`); + } + + const { commandID, sourceChain, sourceAddress, payload } = options; + + if (!isKeccak256Hash(commandID)) { + throw new Error(`Invalid commandID: ${commandID}`); + } + + if (!isString(sourceChain)) { + throw new Error(`Invalid sourceChain: ${sourceChain}`); + } + + if (!isString(sourceAddress)) { + throw new Error(`Invalid sourceAddress: ${sourceAddress}`); + } + + if (!isValidCalldata(payload)) { + throw new Error(`Invalid payload: ${payload}`); + } + + const tx = await interchainTokenService.expressExecute(commandID, sourceChain, sourceAddress, payload); + printInfo('expressExecute tx', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = wasEventEmitted(receipt, interchainTokenService, 'ExpressExecuted'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'interchainTransfer': { + const isPaused = await interchainTokenService.paused(); + + if (isPaused) { + throw new Error(`${action} invalid while service is paused.`); + } + + const { tokenId, destinationChain, destinationAddress, amount, metadata } = options; + + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + if (!isString(destinationChain)) { + throw new Error(`Invalid destinationChain: ${destinationChain}`); + } + + if (!isString(destinationAddress)) { + throw new Error(`Invalid destinationAddress: ${destinationAddress}`); + } + + if (!isValidNumber(amount)) { + throw new Error(`Invalid token amount: ${amount}`); + } + + if (!isValidCalldata(metadata)) { + throw new Error(`Invalid metadata: ${metadata}`); + } + + const tx = await interchainTokenService.interchainTransfer( + tokenIdBytes32, + destinationChain, + destinationAddress, + amount, + metadata, + ); + printInfo('interchainTransfer tx', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = + wasEventEmitted(receipt, interchainTokenService, 'InterchainTransfer') || + wasEventEmitted(receipt, interchainTokenService, 'InterchainTransferWithData'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'callContractWithInterchainToken': { + const isPaused = await interchainTokenService.paused(); + + if (isPaused) { + throw new Error(`${action} invalid while service is paused.`); + } + + const { tokenId, destinationChain, destinationAddress, amount, data } = options; + + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + if (!isString(destinationChain)) { + throw new Error(`Invalid destinationChain: ${destinationChain}`); + } + + if (!isString(destinationAddress)) { + throw new Error(`Invalid destinationAddress: ${destinationAddress}`); + } + + if (!isValidNumber(amount)) { + throw new Error(`Invalid token amount: ${amount}`); + } + + if (!isValidCalldata(data)) { + throw new Error(`Invalid data: ${data}`); + } + + const tx = await interchainTokenService.callContractWithInterchainToken( + tokenIdBytes32, + destinationChain, + destinationAddress, + amount, + data, + ); + printInfo('callContractWithInterchainToken tx', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = + wasEventEmitted(receipt, interchainTokenService, 'InterchainTransfer') || + wasEventEmitted(receipt, interchainTokenService, 'InterchainTransferWithData'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'setFlowLimits': { + const { tokenIds, flowLimits } = options; + const tokenIdsBytes32 = []; + + for (const tokenId of tokenIds) { + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + tokenIdsBytes32.push(tokenIdBytes32); + } + + if (!isNumberArray(flowLimits)) { + throw new Error(`Invalid flowLimits array: ${flowLimits}`); + } + + const tx = await interchainTokenService.setFlowLimits(tokenIdsBytes32, flowLimits); + printInfo('setFlowLimits tx', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = wasEventEmitted(receipt, interchainTokenService, 'FlowLimitSet'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'setTrustedAddress': { + const owner = await interchainTokenService.owner(); + + if (owner.toLowerCase() !== walletAddress.toLowerCase()) { + throw new Error(`${action} can only be performed by contract owner: ${owner}`); + } + + const { trustedChain, trustedAddress } = options; + + if (!isString(trustedChain)) { + throw new Error(`Invalid chain name: ${trustedChain}`); + } + + if (!isString(trustedAddress)) { + throw new Error(`Invalid trusted address: ${trustedAddress}`); + } + + const tx = await interchainTokenService.setTrustedAddress(trustedChain, trustedAddress); + printInfo('setTrustedAddress tx', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = wasEventEmitted(receipt, interchainTokenService, 'TrustedAddressSet'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'removeTrustedAddress': { + const owner = await interchainTokenService.owner(); + + if (owner.toLowerCase() !== walletAddress.toLowerCase()) { + throw new Error(`${action} can only be performed by contract owner: ${owner}`); + } + + const trustedChain = options.trustedChain; + + if (!isString(trustedChain)) { + throw new Error(`Invalid chain name: ${trustedChain}`); + } + + const tx = await interchainTokenService.removeTrustedAddress(trustedChain); + printInfo('removeTrustedAddress tx', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = wasEventEmitted(receipt, interchainTokenService, 'TrustedAddressRemoved'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'setPauseStatus': { + const owner = await interchainTokenService.owner(); + + if (owner.toLowerCase() !== walletAddress.toLowerCase()) { + throw new Error(`${action} can only be performed by contract owner: ${owner}`); + } + + const pauseStatus = options.pauseStatus; + + const tx = await interchainTokenService.setPauseStatus(pauseStatus); + printInfo('setPauseStatus tx', tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = pauseStatus + ? wasEventEmitted(receipt, interchainTokenService, 'Paused') + : wasEventEmitted(receipt, interchainTokenService, 'Unpaused'); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } + + break; + } + + case 'execute': { + const isPaused = await interchainTokenService.paused(); + + if (isPaused) { + throw new Error(`${action} invalid while service is paused.`); + } + + const { commandID, sourceChain, sourceAddress, payload } = options; + + if (!isKeccak256Hash(commandID)) { + throw new Error(`Invalid commandID: ${commandID}`); + } + + if (!isString(sourceChain)) { + throw new Error(`Invalid sourceChain: ${sourceChain}`); + } + + if (!isString(sourceAddress)) { + throw new Error(`Invalid sourceAddress: ${sourceAddress}`); + } + + const isTrustedAddress = await interchainTokenService.isTrustedAddress(sourceChain, sourceAddress); + + if (!isTrustedAddress) { + throw new Error('Invalid remote service.'); + } + + if (!isValidCalldata(payload)) { + throw new Error(`Invalid payload: ${payload}`); + } + + const tx = await interchainTokenService.execute(commandID, sourceChain, sourceAddress, payload); + printInfo('execute tx', tx.hash); + + await tx.wait(chain.confirmations); + + break; + } + + default: { + throw new Error(`Unknown action ${action}`); + } + } +} + +async function main(options) { + await mainProcessor(options, processCommand); +} + +if (require.main === module) { + const program = new Command(); + + program.name('ITS').description('Script to perform ITS commands'); + + program.addOption( + new Option('-e, --env ', 'environment') + .choices(['local', 'devnet', 'stagenet', 'testnet', 'mainnet']) + .default('testnet') + .makeOptionMandatory(true) + .env('ENV'), + ); + program.addOption(new Option('-a, --address
', 'override address')); + program.addOption(new Option('-n, --chainNames ', 'chain names').makeOptionMandatory(true).env('CHAINS')); + program.addOption(new Option('--skipChains ', 'chains to skip over')); + program.addOption( + new Option('--action ', 'ITS action') + .choices([ + 'contractId', + 'tokenManagerAddress', + 'validTokenManagerAddress', + 'tokenAddress', + 'interchainTokenAddress', + 'interchainTokenId', + 'tokenManagerImplementation', + 'flowLimit', + 'flowOutAmount', + 'flowInAmount', + 'deployTokenManager', + 'deployInterchainToken', + 'contractCallValue', + 'expressExecute', + 'interchainTransfer', + 'callContractWithInterchainToken', + 'setFlowLimits', + 'setTrustedAddress', + 'removeTrustedAddress', + 'setPauseStatus', + 'execute', + ]) + .makeOptionMandatory(true), + ); + program.addOption(new Option('-p, --privateKey ', 'private key').makeOptionMandatory(true).env('PRIVATE_KEY')); + program.addOption(new Option('-y, --yes', 'skip deployment prompt confirmation').env('YES')); + + program.addOption(new Option('--commandID ', 'execute command ID')); + program.addOption(new Option('--tokenId ', 'ID of the token')); + program.addOption(new Option('--sender ', 'TokenManager deployer address')); + program.addOption(new Option('--salt ', 'deployment salt')); + program.addOption( + new Option('--type ', 'TokenManager implementation type').choices([ + 'MINT_BURN', + 'MINT_BURN_FROM', + 'LOCK_UNLOCK', + 'LOCK_UNLOCK_FEE', + ]), + ); + program.addOption(new Option('--destinationChain ', 'destination chain')); + program.addOption(new Option('--destinationAddress ', 'destination address')); + program.addOption(new Option('--params ', 'params for TokenManager deployment')); + program.addOption(new Option('--gasValue ', 'gas value')); + program.addOption(new Option('--name ', 'token name')); + program.addOption(new Option('--symbol ', 'token symbol')); + program.addOption(new Option('--decimals ', 'token decimals')); + program.addOption(new Option('--distributor ', 'token distributor')); + program.addOption(new Option('--sourceChain ', 'source chain')); + program.addOption(new Option('--sourceAddress ', 'source address')); + program.addOption(new Option('--payload ', 'payload')); + program.addOption(new Option('--amount ', 'token amount')); + program.addOption(new Option('--metadata ', 'token transfer metadata')); + program.addOption(new Option('--data ', 'token transfer data')); + program.addOption(new Option('--tokenIds ', 'tokenId array')); + program.addOption(new Option('--flowLimits ', 'flow limit array')); + program.addOption(new Option('--trustedChain ', 'chain name for trusted addresses')); + program.addOption(new Option('--trustedAddress ', 'trusted address')); + program.addOption(new Option('--pauseStatus ', 'pause status').choices(['true', 'false'])); + + program.action((options) => { + main(options); + }); + + program.parse(); +} diff --git a/evm/utils.js b/evm/utils.js index 97b0ad17..857f7b5b 100644 --- a/evm/utils.js +++ b/evm/utils.js @@ -291,6 +291,35 @@ function isValidCalldata(input) { return hexPattern.test(input.slice(2)); } +/** + * Checks if a given string is a valid tokenId. + * + * @param {string} input - The input string to check. + * @returns {boolean} - True if the input is a valid tokenId, false otherwise. + */ +function isValidTokenId(input) { + if (!input.startsWith('0x')) { + return false; + } + + const hexPattern = /^[0-9a-fA-F]+$/; + + if (!hexPattern.test(input.slice(2))) { + return false; + } + + const minValue = BigInt('0x00'); + const maxValue = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); + const numericValue = BigInt(input); + + return numericValue >= minValue && numericValue <= maxValue; +} + +function isValidBytesAddress(input) { + const addressRegex = /^0x[a-fA-F0-9]{40}$/; + return addressRegex.test(input); +} + /** * Parses the input string into an array of arguments, recognizing and converting * to the following types: boolean, number, array, and string. @@ -802,6 +831,8 @@ module.exports = { isAddressArray, isKeccak256Hash, isValidCalldata, + isValidTokenId, + isValidBytesAddress, parseArgs, getProxy, getEVMBatch, From b6a57684edaf6928c7dbfe343097ec0e5ded7360 Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Tue, 7 Nov 2023 11:40:30 -0500 Subject: [PATCH 02/11] feat: integrated script optimizations --- evm/cli-utils.js | 2 +- evm/its.js | 16 +++------------- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/evm/cli-utils.js b/evm/cli-utils.js index 6ce17f32..680d8d85 100644 --- a/evm/cli-utils.js +++ b/evm/cli-utils.js @@ -24,7 +24,7 @@ const addBaseOptions = (program, options = {}) => { } if (options.address) { - program.addOption(new Option('-a, --address
', 'override address')); + program.addOption(new Option('--address
', 'override address')); } return program; diff --git a/evm/its.js b/evm/its.js index 73045cfa..0c4b7036 100644 --- a/evm/its.js +++ b/evm/its.js @@ -27,6 +27,7 @@ const { } = require('./utils'); const { getWallet } = require('./sign-utils'); const IInterchainTokenService = require('@axelar-network/interchain-token-service/dist/interchain-token-service/InterchainTokenService.sol'); +const { addExtendedOptions } = require('./cli-utils'); const tokenManagerImplementations = { MINT_BURN: 0, MINT_BURN_FROM: 1, @@ -653,16 +654,8 @@ if (require.main === module) { program.name('ITS').description('Script to perform ITS commands'); - program.addOption( - new Option('-e, --env ', 'environment') - .choices(['local', 'devnet', 'stagenet', 'testnet', 'mainnet']) - .default('testnet') - .makeOptionMandatory(true) - .env('ENV'), - ); - program.addOption(new Option('-a, --address
', 'override address')); - program.addOption(new Option('-n, --chainNames ', 'chain names').makeOptionMandatory(true).env('CHAINS')); - program.addOption(new Option('--skipChains ', 'chains to skip over')); + addExtendedOptions(program, { address: true, salt: true }); + program.addOption( new Option('--action ', 'ITS action') .choices([ @@ -690,13 +683,10 @@ if (require.main === module) { ]) .makeOptionMandatory(true), ); - program.addOption(new Option('-p, --privateKey ', 'private key').makeOptionMandatory(true).env('PRIVATE_KEY')); - program.addOption(new Option('-y, --yes', 'skip deployment prompt confirmation').env('YES')); program.addOption(new Option('--commandID ', 'execute command ID')); program.addOption(new Option('--tokenId ', 'ID of the token')); program.addOption(new Option('--sender ', 'TokenManager deployer address')); - program.addOption(new Option('--salt ', 'deployment salt')); program.addOption( new Option('--type ', 'TokenManager implementation type').choices([ 'MINT_BURN', From 9626030514754492334110432644da6a20f9269d Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Mon, 13 Nov 2023 03:10:13 -0500 Subject: [PATCH 03/11] feat: address comments --- evm/cli-utils.js | 4 +- evm/its.js | 411 ++++++++++------------------------------------- evm/utils.js | 42 ++--- 3 files changed, 108 insertions(+), 349 deletions(-) diff --git a/evm/cli-utils.js b/evm/cli-utils.js index 680d8d85..36d7f243 100644 --- a/evm/cli-utils.js +++ b/evm/cli-utils.js @@ -24,7 +24,7 @@ const addBaseOptions = (program, options = {}) => { } if (options.address) { - program.addOption(new Option('--address
', 'override address')); + program.addOption(new Option('-a, --address
', 'override address')); } return program; @@ -36,7 +36,7 @@ const addExtendedOptions = (program, options = {}) => { program.addOption(new Option('-v, --verify', 'verify the deployed contract on the explorer').env('VERIFY')); if (options.artifactPath) { - program.addOption(new Option('-a, --artifactPath ', 'artifact path')); + program.addOption(new Option('--artifactPath ', 'artifact path')); } if (options.contractName) { diff --git a/evm/its.js b/evm/its.js index 0c4b7036..fa57a3c0 100644 --- a/evm/its.js +++ b/evm/its.js @@ -15,15 +15,9 @@ const { printWarn, printWalletInfo, isValidAddress, - isKeccak256Hash, wasEventEmitted, mainProcessor, - isValidTokenId, - isValidNumber, - isString, - isValidCalldata, - isValidBytesAddress, - isNumberArray, + validateParameters, } = require('./utils'); const { getWallet } = require('./sign-utils'); const IInterchainTokenService = require('@axelar-network/interchain-token-service/dist/interchain-token-service/InterchainTokenService.sol'); @@ -35,6 +29,38 @@ const tokenManagerImplementations = { LOCK_UNLOCK_FEE: 3, }; +function isValidTokenId(input) { + if (!input.startsWith('0x')) { + return false; + } + + const hexPattern = /^[0-9a-fA-F]+$/; + + if (!hexPattern.test(input.slice(2))) { + return false; + } + + const minValue = BigInt('0x00'); + const maxValue = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); + const numericValue = BigInt(input); + + return numericValue >= minValue && numericValue <= maxValue; +} + +async function handleTx(tx, chain, contract, action, firstEvent, secondEvent) { + printInfo(`${action} tx`, tx.hash); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = + (firstEvent ? wasEventEmitted(receipt, contract, 'TokenManagerDeployed') : true) || + (secondEvent ? wasEventEmitted(receipt, contract, 'TokenManagerDeploymentStarted') : false); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } +} + async function processCommand(chain, options) { const { privateKey, address, action, yes } = options; @@ -70,6 +96,12 @@ async function processCommand(chain, options) { return; } + const tokenId = options.tokenId; + + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + switch (action) { case 'contractId': { const contractId = await interchainTokenService.contractId(); @@ -79,46 +111,22 @@ async function processCommand(chain, options) { } case 'tokenManagerAddress': { - const tokenId = options.tokenId; - - if (!isValidTokenId(tokenId)) { - throw new Error(`Invalid tokenId value: ${tokenId}`); - } - const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); const tokenManagerAddress = await interchainTokenService.tokenManagerAddress(tokenIdBytes32); printInfo(`TokenManager address for tokenId: ${tokenId}:`, tokenManagerAddress); - break; - } - - case 'validTokenManagerAddress': { - const tokenId = options.tokenId; - - if (!isValidTokenId(tokenId)) { - throw new Error(`Invalid tokenId value: ${tokenId}`); - } - - const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); - try { - const tokenManagerAddress = await interchainTokenService.validTokenManagerAddress(tokenIdBytes32); + await interchainTokenService.validTokenManagerAddress(tokenIdBytes32); printInfo(`TokenManager for tokenId: ${tokenId} exists at address:`, tokenManagerAddress); } catch (error) { - printInfo(`TokenManager for tokenId: ${tokenId} does not exist.`); + printInfo(`TokenManager for tokenId: ${tokenId} does not yet exist.`); } break; } case 'interchainTokenAddress': { - const tokenId = options.tokenId; - - if (!isValidTokenId(tokenId)) { - throw new Error(`Invalid tokenId value: ${tokenId}`); - } - const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); const interchainTokenAddress = await interchainTokenService.interchainTokenAddress(tokenIdBytes32); @@ -128,17 +136,9 @@ async function processCommand(chain, options) { } case 'interchainTokenId': { - const sender = options.sender; + const { sender, salt } = options; - if (!isValidAddress(sender)) { - throw new Error(`Invalid sender address: ${sender}`); - } - - const salt = options.salt; - - if (!isKeccak256Hash(salt)) { - throw new Error(`Invalid salt: ${salt}`); - } + validateParameters({ isValidAddress: [sender], isKeccak256Hash: [salt] }); const interchainTokenId = await interchainTokenService.interchainTokenId(sender, salt); printInfo(`InterchainTokenId for sender ${sender} and deployment salt: ${salt}`, interchainTokenId); @@ -156,12 +156,6 @@ async function processCommand(chain, options) { } case 'flowLimit': { - const tokenId = options.tokenId; - - if (!isValidTokenId(tokenId)) { - throw new Error(`Invalid tokenId value: ${tokenId}`); - } - const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); const flowLimit = await interchainTokenService.flowLimit(tokenIdBytes32); @@ -171,12 +165,6 @@ async function processCommand(chain, options) { } case 'flowOutAmount': { - const tokenId = options.tokenId; - - if (!isValidTokenId(tokenId)) { - throw new Error(`Invalid tokenId value: ${tokenId}`); - } - const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); const flowOutAmount = await interchainTokenService.flowOutAmount(tokenIdBytes32); @@ -186,12 +174,6 @@ async function processCommand(chain, options) { } case 'flowInAmount': { - const tokenId = options.tokenId; - - if (!isValidTokenId(tokenId)) { - throw new Error(`Invalid tokenId value: ${tokenId}`); - } - const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); const flowInAmount = await interchainTokenService.flowInAmount(tokenIdBytes32); @@ -201,29 +183,14 @@ async function processCommand(chain, options) { } case 'deployTokenManager': { - const isPaused = await interchainTokenService.paused(); - - if (isPaused) { - throw new Error(`${action} invalid while service is paused.`); - } - const { salt, destinationChain, type, params, gasValue } = options; - if (!isKeccak256Hash(salt)) { - throw new Error(`Invalid salt: ${salt}`); - } - - if (!isString(destinationChain)) { - throw new Error(`Invalid destinationChain: ${destinationChain}`); - } - - if (!isValidCalldata(params)) { - throw new Error(`Invalid params: ${params}`); - } - - if (!isValidNumber(gasValue)) { - throw new Error(`Invalid gas value: ${gasValue}`); - } + validateParameters({ + isKeccak256Hash: [salt], + isString: [destinationChain], + isValidCalldata: [params], + isValidNumber: [gasValue], + }); const tx = await interchainTokenService.deployTokenManager( salt, @@ -232,57 +199,21 @@ async function processCommand(chain, options) { params, gasValue, ); - printInfo('deploy TokenManager tx', tx.hash); - - const receipt = await tx.wait(chain.confirmations); - const eventEmitted = - wasEventEmitted(receipt, interchainTokenService, 'TokenManagerDeployed') || - wasEventEmitted(receipt, interchainTokenService, 'TokenManagerDeploymentStarted'); - - if (!eventEmitted) { - printWarn('Event not emitted in receipt.'); - } + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'TokenManagerDeploymentStarted'); break; } case 'deployInterchainToken': { - const isPaused = await interchainTokenService.paused(); - - if (isPaused) { - throw new Error(`${action} invalid while service is paused.`); - } - const { salt, destinationChain, name, symbol, decimals, distributor, gasValue } = options; - if (!isKeccak256Hash(salt)) { - throw new Error(`Invalid salt: ${salt}`); - } - - if (!isString(destinationChain)) { - throw new Error(`Invalid destinationChain: ${destinationChain}`); - } - - if (!isString(name)) { - throw new Error(`Invalid name: ${name}`); - } - - if (!isString(symbol)) { - throw new Error(`Invalid symbol: ${symbol}`); - } - - if (!isValidNumber(decimals)) { - throw new Error(`Invalid decimals value: ${decimals}`); - } - - if (!isValidBytesAddress(distributor)) { - throw new Error(`Invalid distributor address: ${distributor}`); - } - - if (!isValidNumber(gasValue)) { - throw new Error(`Invalid gas value: ${gasValue}`); - } + validateParameters({ + isKeccak256Hash: [salt], + isString: [destinationChain, name, symbol], + isValidBytesAddress: [distributor], + isValidNumber: [decimals, gasValue], + }); const tx = await interchainTokenService.deployInterchainToken( salt, @@ -293,37 +224,16 @@ async function processCommand(chain, options) { distributor, gasValue, ); - printInfo('deploy InterchainToken tx', tx.hash); - - const receipt = await tx.wait(chain.confirmations); - const eventEmitted = - wasEventEmitted(receipt, interchainTokenService, 'TokenManagerDeployed') || - wasEventEmitted(receipt, interchainTokenService, 'InterchainTokenDeploymentStarted'); - - if (!eventEmitted) { - printWarn('Event not emitted in receipt.'); - } + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); break; } case 'contractCallValue': { - const isPaused = await interchainTokenService.paused(); - - if (isPaused) { - throw new Error(`${action} invalid while service is paused.`); - } - const { sourceChain, sourceAddress, payload } = options; - if (!isString(sourceChain)) { - throw new Error(`Invalid sourceChain: ${sourceChain}`); - } - - if (!isString(sourceAddress)) { - throw new Error(`Invalid sourceAddress: ${sourceAddress}`); - } + validateParameters({ isString: [sourceChain, sourceAddress] }); const isTrustedAddress = await interchainTokenService.isTrustedAddress(sourceChain, sourceAddress); @@ -331,9 +241,7 @@ async function processCommand(chain, options) { throw new Error('Invalid remote service.'); } - if (!isValidCalldata(payload)) { - throw new Error(`Invalid payload: ${payload}`); - } + validateParameters({ isValidCalldata: [payload] }); const [tokenAddress, tokenAmount] = await interchainTokenService.contractCallValue(sourceChain, sourceAddress, payload); printInfo(`Amount of tokens with address ${tokenAddress} that the call is worth:`, tokenAmount); @@ -342,74 +250,31 @@ async function processCommand(chain, options) { } case 'expressExecute': { - const isPaused = await interchainTokenService.paused(); - - if (isPaused) { - throw new Error(`${action} invalid while service is paused.`); - } - const { commandID, sourceChain, sourceAddress, payload } = options; - if (!isKeccak256Hash(commandID)) { - throw new Error(`Invalid commandID: ${commandID}`); - } - - if (!isString(sourceChain)) { - throw new Error(`Invalid sourceChain: ${sourceChain}`); - } - - if (!isString(sourceAddress)) { - throw new Error(`Invalid sourceAddress: ${sourceAddress}`); - } - - if (!isValidCalldata(payload)) { - throw new Error(`Invalid payload: ${payload}`); - } + validateParameters({ + isKeccak256Hash: [commandID], + isString: [sourceChain, sourceAddress], + isValidCalldata: [payload], + }); const tx = await interchainTokenService.expressExecute(commandID, sourceChain, sourceAddress, payload); - printInfo('expressExecute tx', tx.hash); - - const receipt = await tx.wait(chain.confirmations); - - const eventEmitted = wasEventEmitted(receipt, interchainTokenService, 'ExpressExecuted'); - if (!eventEmitted) { - printWarn('Event not emitted in receipt.'); - } + await handleTx(tx, chain, interchainTokenService, options.action, 'ExpressExecuted'); break; } case 'interchainTransfer': { - const isPaused = await interchainTokenService.paused(); - - if (isPaused) { - throw new Error(`${action} invalid while service is paused.`); - } - - const { tokenId, destinationChain, destinationAddress, amount, metadata } = options; - - if (!isValidTokenId(tokenId)) { - throw new Error(`Invalid tokenId value: ${tokenId}`); - } + const { destinationChain, destinationAddress, amount, metadata } = options; const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); - if (!isString(destinationChain)) { - throw new Error(`Invalid destinationChain: ${destinationChain}`); - } - - if (!isString(destinationAddress)) { - throw new Error(`Invalid destinationAddress: ${destinationAddress}`); - } - - if (!isValidNumber(amount)) { - throw new Error(`Invalid token amount: ${amount}`); - } - - if (!isValidCalldata(metadata)) { - throw new Error(`Invalid metadata: ${metadata}`); - } + validateParameters({ + isString: [destinationChain, destinationAddress], + isValidNumber: [amount], + isValidCalldata: [metadata], + }); const tx = await interchainTokenService.interchainTransfer( tokenIdBytes32, @@ -418,51 +283,22 @@ async function processCommand(chain, options) { amount, metadata, ); - printInfo('interchainTransfer tx', tx.hash); - const receipt = await tx.wait(chain.confirmations); - - const eventEmitted = - wasEventEmitted(receipt, interchainTokenService, 'InterchainTransfer') || - wasEventEmitted(receipt, interchainTokenService, 'InterchainTransferWithData'); - - if (!eventEmitted) { - printWarn('Event not emitted in receipt.'); - } + await handleTx(tx, chain, interchainTokenService, options.action, 'InterchainTransfer', 'InterchainTransferWithData'); break; } case 'callContractWithInterchainToken': { - const isPaused = await interchainTokenService.paused(); - - if (isPaused) { - throw new Error(`${action} invalid while service is paused.`); - } - - const { tokenId, destinationChain, destinationAddress, amount, data } = options; - - if (!isValidTokenId(tokenId)) { - throw new Error(`Invalid tokenId value: ${tokenId}`); - } + const { destinationChain, destinationAddress, amount, data } = options; const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); - if (!isString(destinationChain)) { - throw new Error(`Invalid destinationChain: ${destinationChain}`); - } - - if (!isString(destinationAddress)) { - throw new Error(`Invalid destinationAddress: ${destinationAddress}`); - } - - if (!isValidNumber(amount)) { - throw new Error(`Invalid token amount: ${amount}`); - } - - if (!isValidCalldata(data)) { - throw new Error(`Invalid data: ${data}`); - } + validateParameters({ + isString: [destinationChain, destinationAddress], + isValidNumber: [amount], + isValidCalldata: [data], + }); const tx = await interchainTokenService.callContractWithInterchainToken( tokenIdBytes32, @@ -471,17 +307,8 @@ async function processCommand(chain, options) { amount, data, ); - printInfo('callContractWithInterchainToken tx', tx.hash); - - const receipt = await tx.wait(chain.confirmations); - const eventEmitted = - wasEventEmitted(receipt, interchainTokenService, 'InterchainTransfer') || - wasEventEmitted(receipt, interchainTokenService, 'InterchainTransferWithData'); - - if (!eventEmitted) { - printWarn('Event not emitted in receipt.'); - } + await handleTx(tx, chain, interchainTokenService, options.action, 'InterchainTransfer', 'InterchainTransferWithData'); break; } @@ -499,20 +326,11 @@ async function processCommand(chain, options) { tokenIdsBytes32.push(tokenIdBytes32); } - if (!isNumberArray(flowLimits)) { - throw new Error(`Invalid flowLimits array: ${flowLimits}`); - } + validateParameters({ isNumberArray: [flowLimits] }); const tx = await interchainTokenService.setFlowLimits(tokenIdsBytes32, flowLimits); - printInfo('setFlowLimits tx', tx.hash); - const receipt = await tx.wait(chain.confirmations); - - const eventEmitted = wasEventEmitted(receipt, interchainTokenService, 'FlowLimitSet'); - - if (!eventEmitted) { - printWarn('Event not emitted in receipt.'); - } + await handleTx(tx, chain, interchainTokenService, options.action, 'FlowLimitSet'); break; } @@ -526,24 +344,11 @@ async function processCommand(chain, options) { const { trustedChain, trustedAddress } = options; - if (!isString(trustedChain)) { - throw new Error(`Invalid chain name: ${trustedChain}`); - } - - if (!isString(trustedAddress)) { - throw new Error(`Invalid trusted address: ${trustedAddress}`); - } + validateParameters({ isString: [trustedChain, trustedAddress] }); const tx = await interchainTokenService.setTrustedAddress(trustedChain, trustedAddress); - printInfo('setTrustedAddress tx', tx.hash); - const receipt = await tx.wait(chain.confirmations); - - const eventEmitted = wasEventEmitted(receipt, interchainTokenService, 'TrustedAddressSet'); - - if (!eventEmitted) { - printWarn('Event not emitted in receipt.'); - } + await handleTx(tx, chain, interchainTokenService, options.action, 'TrustedAddressSet'); break; } @@ -557,20 +362,11 @@ async function processCommand(chain, options) { const trustedChain = options.trustedChain; - if (!isString(trustedChain)) { - throw new Error(`Invalid chain name: ${trustedChain}`); - } + validateParameters({ isString: [trustedChain] }); const tx = await interchainTokenService.removeTrustedAddress(trustedChain); - printInfo('removeTrustedAddress tx', tx.hash); - - const receipt = await tx.wait(chain.confirmations); - const eventEmitted = wasEventEmitted(receipt, interchainTokenService, 'TrustedAddressRemoved'); - - if (!eventEmitted) { - printWarn('Event not emitted in receipt.'); - } + await handleTx(tx, chain, interchainTokenService, options.action, 'TrustedAddressRemoved'); break; } @@ -585,41 +381,16 @@ async function processCommand(chain, options) { const pauseStatus = options.pauseStatus; const tx = await interchainTokenService.setPauseStatus(pauseStatus); - printInfo('setPauseStatus tx', tx.hash); - - const receipt = await tx.wait(chain.confirmations); - const eventEmitted = pauseStatus - ? wasEventEmitted(receipt, interchainTokenService, 'Paused') - : wasEventEmitted(receipt, interchainTokenService, 'Unpaused'); - - if (!eventEmitted) { - printWarn('Event not emitted in receipt.'); - } + await handleTx(tx, chain, interchainTokenService, options.action, 'Paused', 'Unpaused'); break; } case 'execute': { - const isPaused = await interchainTokenService.paused(); - - if (isPaused) { - throw new Error(`${action} invalid while service is paused.`); - } - const { commandID, sourceChain, sourceAddress, payload } = options; - if (!isKeccak256Hash(commandID)) { - throw new Error(`Invalid commandID: ${commandID}`); - } - - if (!isString(sourceChain)) { - throw new Error(`Invalid sourceChain: ${sourceChain}`); - } - - if (!isString(sourceAddress)) { - throw new Error(`Invalid sourceAddress: ${sourceAddress}`); - } + validateParameters({ isKeccak256Hash: [commandID], isString: [sourceChain, sourceAddress] }); const isTrustedAddress = await interchainTokenService.isTrustedAddress(sourceChain, sourceAddress); @@ -627,14 +398,11 @@ async function processCommand(chain, options) { throw new Error('Invalid remote service.'); } - if (!isValidCalldata(payload)) { - throw new Error(`Invalid payload: ${payload}`); - } + validateParameters({ isValidCalldata: [payload] }); const tx = await interchainTokenService.execute(commandID, sourceChain, sourceAddress, payload); - printInfo('execute tx', tx.hash); - await tx.wait(chain.confirmations); + await handleTx(tx, chain, interchainTokenService, options.action); break; } @@ -661,7 +429,6 @@ if (require.main === module) { .choices([ 'contractId', 'tokenManagerAddress', - 'validTokenManagerAddress', 'tokenAddress', 'interchainTokenAddress', 'interchainTokenId', diff --git a/evm/utils.js b/evm/utils.js index f0c4bf52..d9539cf1 100644 --- a/evm/utils.js +++ b/evm/utils.js @@ -293,35 +293,27 @@ function isValidCalldata(input) { return hexPattern.test(input.slice(2)); } -/** - * Checks if a given string is a valid tokenId. - * - * @param {string} input - The input string to check. - * @returns {boolean} - True if the input is a valid tokenId, false otherwise. - */ -function isValidTokenId(input) { - if (!input.startsWith('0x')) { - return false; - } - - const hexPattern = /^[0-9a-fA-F]+$/; - - if (!hexPattern.test(input.slice(2))) { - return false; - } - - const minValue = BigInt('0x00'); - const maxValue = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); - const numericValue = BigInt(input); - - return numericValue >= minValue && numericValue <= maxValue; -} - function isValidBytesAddress(input) { const addressRegex = /^0x[a-fA-F0-9]{40}$/; return addressRegex.test(input); } +function validateParameters(parameters) { + for (const [validatorFunction, params] of Object.entries(parameters)) { + if (typeof validatorFunction !== 'function') { + throw new Error(`Validator function ${validatorFunction} is not defined`); + } + + params.forEach((param) => { + const isValid = validatorFunction(param); + + if (!isValid) { + throw new Error(`Input validation failed for ${validatorFunction} with parameter ${param}`); + } + }); + } +} + /** * Parses the input string into an array of arguments, recognizing and converting * to the following types: boolean, number, array, and string. @@ -910,8 +902,8 @@ module.exports = { isAddressArray, isKeccak256Hash, isValidCalldata, - isValidTokenId, isValidBytesAddress, + validateParameters, parseArgs, getProxy, getEVMBatch, From a771d7847e02b9f933b671e79969c6aba2c3e35c Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Thu, 16 Nov 2023 02:02:30 -0500 Subject: [PATCH 04/11] feat: update validateParameters function --- evm/its.js | 65 ++++++++++++++++++++++------------------------------ evm/utils.js | 11 +++++---- 2 files changed, 33 insertions(+), 43 deletions(-) diff --git a/evm/its.js b/evm/its.js index fa57a3c0..9b525110 100644 --- a/evm/its.js +++ b/evm/its.js @@ -9,16 +9,7 @@ const { Contract, } = ethers; const { Command, Option } = require('commander'); -const { - printInfo, - prompt, - printWarn, - printWalletInfo, - isValidAddress, - wasEventEmitted, - mainProcessor, - validateParameters, -} = require('./utils'); +const { printInfo, prompt, printWarn, printWalletInfo, wasEventEmitted, mainProcessor, validateParameters } = require('./utils'); const { getWallet } = require('./sign-utils'); const IInterchainTokenService = require('@axelar-network/interchain-token-service/dist/interchain-token-service/InterchainTokenService.sol'); const { addExtendedOptions } = require('./cli-utils'); @@ -70,9 +61,7 @@ async function processCommand(chain, options) { const interchainTokenServiceAddress = address || contracts.interchainTokenService?.address; - if (!isValidAddress(interchainTokenServiceAddress)) { - throw new Error(`Contract ${contractName} is not deployed on ${chain.name}`); - } + validateParameters({ isValidAddress: { interchainTokenServiceAddress } }); const rpc = chain.rpc; const provider = getDefaultProvider(rpc); @@ -138,7 +127,7 @@ async function processCommand(chain, options) { case 'interchainTokenId': { const { sender, salt } = options; - validateParameters({ isValidAddress: [sender], isKeccak256Hash: [salt] }); + validateParameters({ isValidAddress: { sender }, isKeccak256Hash: { salt } }); const interchainTokenId = await interchainTokenService.interchainTokenId(sender, salt); printInfo(`InterchainTokenId for sender ${sender} and deployment salt: ${salt}`, interchainTokenId); @@ -186,10 +175,10 @@ async function processCommand(chain, options) { const { salt, destinationChain, type, params, gasValue } = options; validateParameters({ - isKeccak256Hash: [salt], - isString: [destinationChain], - isValidCalldata: [params], - isValidNumber: [gasValue], + isKeccak256Hash: { salt }, + isString: { destinationChain }, + isValidCalldata: { params }, + isValidNumber: { gasValue }, }); const tx = await interchainTokenService.deployTokenManager( @@ -209,10 +198,10 @@ async function processCommand(chain, options) { const { salt, destinationChain, name, symbol, decimals, distributor, gasValue } = options; validateParameters({ - isKeccak256Hash: [salt], - isString: [destinationChain, name, symbol], - isValidBytesAddress: [distributor], - isValidNumber: [decimals, gasValue], + isKeccak256Hash: { salt }, + isString: { destinationChain, name, symbol }, + isValidBytesAddress: { distributor }, + isValidNumber: { decimals, gasValue }, }); const tx = await interchainTokenService.deployInterchainToken( @@ -233,7 +222,7 @@ async function processCommand(chain, options) { case 'contractCallValue': { const { sourceChain, sourceAddress, payload } = options; - validateParameters({ isString: [sourceChain, sourceAddress] }); + validateParameters({ isString: { sourceChain, sourceAddress } }); const isTrustedAddress = await interchainTokenService.isTrustedAddress(sourceChain, sourceAddress); @@ -241,7 +230,7 @@ async function processCommand(chain, options) { throw new Error('Invalid remote service.'); } - validateParameters({ isValidCalldata: [payload] }); + validateParameters({ isValidCalldata: { payload } }); const [tokenAddress, tokenAmount] = await interchainTokenService.contractCallValue(sourceChain, sourceAddress, payload); printInfo(`Amount of tokens with address ${tokenAddress} that the call is worth:`, tokenAmount); @@ -253,9 +242,9 @@ async function processCommand(chain, options) { const { commandID, sourceChain, sourceAddress, payload } = options; validateParameters({ - isKeccak256Hash: [commandID], - isString: [sourceChain, sourceAddress], - isValidCalldata: [payload], + isKeccak256Hash: { commandID }, + isString: { sourceChain, sourceAddress }, + isValidCalldata: { payload }, }); const tx = await interchainTokenService.expressExecute(commandID, sourceChain, sourceAddress, payload); @@ -271,9 +260,9 @@ async function processCommand(chain, options) { const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); validateParameters({ - isString: [destinationChain, destinationAddress], - isValidNumber: [amount], - isValidCalldata: [metadata], + isString: { destinationChain, destinationAddress }, + isValidNumber: { amount }, + isValidCalldata: { metadata }, }); const tx = await interchainTokenService.interchainTransfer( @@ -295,9 +284,9 @@ async function processCommand(chain, options) { const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); validateParameters({ - isString: [destinationChain, destinationAddress], - isValidNumber: [amount], - isValidCalldata: [data], + isString: { destinationChain, destinationAddress }, + isValidNumber: { amount }, + isValidCalldata: { data }, }); const tx = await interchainTokenService.callContractWithInterchainToken( @@ -326,7 +315,7 @@ async function processCommand(chain, options) { tokenIdsBytes32.push(tokenIdBytes32); } - validateParameters({ isNumberArray: [flowLimits] }); + validateParameters({ isNumberArray: { flowLimits } }); const tx = await interchainTokenService.setFlowLimits(tokenIdsBytes32, flowLimits); @@ -344,7 +333,7 @@ async function processCommand(chain, options) { const { trustedChain, trustedAddress } = options; - validateParameters({ isString: [trustedChain, trustedAddress] }); + validateParameters({ isString: { trustedChain, trustedAddress } }); const tx = await interchainTokenService.setTrustedAddress(trustedChain, trustedAddress); @@ -362,7 +351,7 @@ async function processCommand(chain, options) { const trustedChain = options.trustedChain; - validateParameters({ isString: [trustedChain] }); + validateParameters({ isString: { trustedChain } }); const tx = await interchainTokenService.removeTrustedAddress(trustedChain); @@ -390,7 +379,7 @@ async function processCommand(chain, options) { case 'execute': { const { commandID, sourceChain, sourceAddress, payload } = options; - validateParameters({ isKeccak256Hash: [commandID], isString: [sourceChain, sourceAddress] }); + validateParameters({ isKeccak256Hash: { commandID }, isString: { sourceChain, sourceAddress } }); const isTrustedAddress = await interchainTokenService.isTrustedAddress(sourceChain, sourceAddress); @@ -398,7 +387,7 @@ async function processCommand(chain, options) { throw new Error('Invalid remote service.'); } - validateParameters({ isValidCalldata: [payload] }); + validateParameters({ isValidCalldata: { payload } }); const tx = await interchainTokenService.execute(commandID, sourceChain, sourceAddress, payload); diff --git a/evm/utils.js b/evm/utils.js index bdc19add..12b6e7f5 100644 --- a/evm/utils.js +++ b/evm/utils.js @@ -299,18 +299,19 @@ function isValidBytesAddress(input) { } function validateParameters(parameters) { - for (const [validatorFunction, params] of Object.entries(parameters)) { + for (const [validatorFunction, paramsObj] of Object.entries(parameters)) { if (typeof validatorFunction !== 'function') { throw new Error(`Validator function ${validatorFunction} is not defined`); } - params.forEach((param) => { - const isValid = validatorFunction(param); + for (const paramKey of Object.keys(paramsObj)) { + const paramValue = paramsObj[paramKey]; + const isValid = validatorFunction(paramValue); if (!isValid) { - throw new Error(`Input validation failed for ${validatorFunction} with parameter ${param}`); + throw new Error(`Input validation failed for ${validatorFunction} with parameter ${paramKey}: ${paramValue}`); } - }); + } } } From fc1c16c437bd2ef1871f583fbdcc8a7e09367802 Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Thu, 16 Nov 2023 02:44:05 -0500 Subject: [PATCH 05/11] fix: hardcoded event names --- evm/its.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/evm/its.js b/evm/its.js index 9b525110..a385f7cd 100644 --- a/evm/its.js +++ b/evm/its.js @@ -44,8 +44,8 @@ async function handleTx(tx, chain, contract, action, firstEvent, secondEvent) { const receipt = await tx.wait(chain.confirmations); const eventEmitted = - (firstEvent ? wasEventEmitted(receipt, contract, 'TokenManagerDeployed') : true) || - (secondEvent ? wasEventEmitted(receipt, contract, 'TokenManagerDeploymentStarted') : false); + (firstEvent ? wasEventEmitted(receipt, contract, firstEvent) : true) || + (secondEvent ? wasEventEmitted(receipt, contract, secondEvent) : false); if (!eventEmitted) { printWarn('Event not emitted in receipt.'); From dc8a667ea05f996c89f87be5ab6c6921c4ba919b Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Fri, 17 Nov 2023 05:24:41 -0500 Subject: [PATCH 06/11] feat: add interchain token factory script --- evm/interchainTokenFactory.js | 302 ++++++++++++++++++++++++++++++++++ evm/its.js | 15 +- 2 files changed, 315 insertions(+), 2 deletions(-) create mode 100644 evm/interchainTokenFactory.js diff --git a/evm/interchainTokenFactory.js b/evm/interchainTokenFactory.js new file mode 100644 index 00000000..ffebd29b --- /dev/null +++ b/evm/interchainTokenFactory.js @@ -0,0 +1,302 @@ +'use strict'; + +require('dotenv').config(); + +const { ethers } = require('hardhat'); +const { + getDefaultProvider, + utils: { hexZeroPad }, + Contract, +} = ethers; +const { Command, Option } = require('commander'); +const { printInfo, prompt, mainProcessor, validateParameters, getContractJSON } = require('./utils'); +const { getWallet } = require('./sign-utils'); +const { addExtendedOptions } = require('./cli-utils'); +const { isValidTokenId, handleTx } = require('./its'); +const IInterchainTokenFactory = getContractJSON('IInterchainTokenFactory'); + +async function processCommand(chain, options) { + const { privateKey, address, action, yes } = options; + + const contracts = chain.contracts; + const contractName = 'InterchainTokenFactory'; + const contractConfig = contracts.InterchainTokenFactory; + + const interchainTokenFactoryAddress = address || contracts.interchainTokenFactory?.interchainTokenFactory; + + validateParameters({ isValidAddress: { interchainTokenFactoryAddress } }); + + const rpc = chain.rpc; + const provider = getDefaultProvider(rpc); + + printInfo('Chain', chain.name); + + const wallet = await getWallet(privateKey, provider, options); + + printInfo('Contract name', contractName); + printInfo('Contract address', interchainTokenFactoryAddress); + + const interchainTokenFactory = new Contract(interchainTokenFactoryAddress, IInterchainTokenFactory.abi, wallet); + + const gasOptions = contractConfig?.gasOptions || chain?.gasOptions || {}; + printInfo('Gas options', JSON.stringify(gasOptions, null, 2)); + + printInfo('Action', action); + + if (prompt(`Proceed with action ${action}`, yes)) { + return; + } + + const tokenId = options.tokenId; + + if (!isValidTokenId(tokenId)) { + throw new Error(`Invalid tokenId value: ${tokenId}`); + } + + switch (action) { + case 'contractId': { + const contractId = await interchainTokenFactory.contractId(); + printInfo('InterchainTokenFactory contract ID', contractId); + + break; + } + + case 'interchainTokenSalt': { + const { chainNameHash, deployer, salt } = options; + + validateParameters({ isValidAddress: { deployer }, isKeccak256Hash: { chainNameHash, salt } }); + + const interchainTokenSalt = await interchainTokenFactory.interchainTokenSalt(chainNameHash, deployer, salt); + printInfo(`interchainTokenSalt for deployer ${deployer} and deployment salt: ${salt}`, interchainTokenSalt); + + break; + } + + case 'canonicalInterchainTokenSalt': { + const { chainNameHash, tokenAddress } = options; + + validateParameters({ isValidAddress: { tokenAddress }, isKeccak256Hash: { chainNameHash } }); + + const canonicalInterchainTokenSalt = await interchainTokenFactory.canonicalInterchainTokenSalt(chainNameHash, tokenAddress); + printInfo(`canonicalInterchainTokenSalt for token address: ${tokenAddress}`, canonicalInterchainTokenSalt); + + break; + } + + case 'interchainTokenId': { + const { deployer, salt } = options; + + validateParameters({ isValidAddress: { deployer }, isKeccak256Hash: { salt } }); + + const interchainTokenId = await interchainTokenFactory.interchainTokenId(deployer, salt); + printInfo(`InterchainTokenId for deployer ${deployer} and deployment salt: ${salt}`, interchainTokenId); + + break; + } + + case 'canonicalInterchainTokenId': { + const { tokenAddress } = options; + + validateParameters({ isValidAddress: { tokenAddress } }); + + const canonicalInterchainTokenId = await interchainTokenFactory.canonicalInterchainTokenId(tokenAddress); + printInfo(`canonicalInterchainTokenId for token address: ${tokenAddress}`, canonicalInterchainTokenId); + + break; + } + + case 'interchainTokenAddress': { + const { deployer, salt } = options; + + validateParameters({ isValidAddress: { deployer }, isKeccak256Hash: { salt } }); + + const interchainTokenAddress = await interchainTokenFactory.interchainTokenAddress(deployer, salt); + printInfo(`interchainTokenAddress for deployer ${deployer} and deployment salt: ${salt}`, interchainTokenAddress); + + break; + } + + case 'deployInterchainToken': { + const { salt, name, symbol, decimals, mintAmount, distributor } = options; + + validateParameters({ + isKeccak256Hash: { salt }, + isString: { name, symbol }, + isValidAddress: { distributor }, + isValidNumber: { decimals, mintAmount }, + }); + + const tx = await interchainTokenFactory.deployInterchainToken(salt, name, symbol, decimals, mintAmount, distributor); + + await handleTx(tx, chain, interchainTokenFactory, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); + + break; + } + + case 'deployRemoteInterchainToken': { + const { originalChain, salt, distributor, destinationChain, gasValue } = options; + + validateParameters({ + isKeccak256Hash: { salt }, + isString: { originalChain, destinationChain }, + isValidBytesAddress: { distributor }, + isValidNumber: { gasValue }, + }); + + const tx = await interchainTokenFactory.deployRemoteInterchainToken( + originalChain, + salt, + distributor, + destinationChain, + gasValue, + ); + + await handleTx(tx, chain, interchainTokenFactory, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); + + break; + } + + case 'registerCanonicalInterchainToken': { + const { tokenAddress } = options; + + validateParameters({ isValidAddress: { tokenAddress } }); + + const tx = await interchainTokenFactory.registerCanonicalInterchainToken(tokenAddress); + + await handleTx(tx, chain, interchainTokenFactory, options.action, 'TokenManagerDeployed', 'TokenManagerDeploymentStarted'); + + break; + } + + case 'deployRemoteCanonicalInterchainToken': { + const { originalChain, tokenAddress, destinationChain, gasValue } = options; + + validateParameters({ + isValidAddress: { tokenAddress }, + isString: { originalChain, destinationChain }, + isValidNumber: { gasValue }, + }); + + const tx = await interchainTokenFactory.deployRemoteCanonicalInterchainToken( + originalChain, + tokenAddress, + destinationChain, + gasValue, + ); + + await handleTx(tx, chain, interchainTokenFactory, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); + + break; + } + + case 'interchainTransfer': { + const { tokenId, destinationChain, destinationAddress, amount, gasValue } = options; + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + validateParameters({ + isString: { destinationChain }, + isValidCalldata: { destinationAddress }, + isValidNumber: { amount, gasValue }, + }); + + const tx = await interchainTokenFactory.interchainTransfer( + tokenIdBytes32, + destinationChain, + destinationAddress, + amount, + gasValue, + ); + + await handleTx(tx, chain, interchainTokenFactory, options.action, 'Transfer', 'InterchainTransferWithData'); + + break; + } + + case 'tokenTransferFrom': { + const { tokenId, amount } = options; + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + validateParameters({ isValidNumber: { amount } }); + + const tx = await interchainTokenFactory.tokenTransferFrom(tokenIdBytes32, amount); + + await handleTx(tx, chain, interchainTokenFactory, options.action, 'Transfer'); + + break; + } + + case 'tokenApprove': { + const { tokenId, amount } = options; + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + validateParameters({ isValidNumber: { amount } }); + + const tx = await interchainTokenFactory.tokenApprove(tokenIdBytes32, amount); + + await handleTx(tx, chain, interchainTokenFactory, options.action, 'Approval'); + + break; + } + + default: { + throw new Error(`Unknown action ${action}`); + } + } +} + +async function main(options) { + await mainProcessor(options, processCommand); +} + +if (require.main === module) { + const program = new Command(); + + program.name('InterchainTokenFactory').description('Script to perform interchain token factory commands'); + + addExtendedOptions(program, { address: true, salt: true }); + + program.addOption( + new Option('--action ', 'interchain token factory action') + .choices([ + 'contractId', + 'interchainTokenSalt', + 'canonicalInterchainTokenSalt', + 'interchainTokenId', + 'canonicalInterchainTokenId', + 'interchainTokenAddress', + 'deployInterchainToken', + 'deployRemoteInterchainToken', + 'registerCanonicalInterchainToken', + 'deployRemoteCanonicalInterchainToken', + 'interchainTransfer', + 'tokenTransferFrom', + 'tokenApprove', + ]) + .makeOptionMandatory(true), + ); + + program.addOption(new Option('--tokenId ', 'ID of the token')); + program.addOption(new Option('--sender ', 'TokenManager deployer address')); + program.addOption(new Option('--chainNameHash ', 'chain name hash')); + program.addOption(new Option('--deployer ', 'deployer address')); + program.addOption(new Option('--tokenAddress ', 'token address')); + program.addOption(new Option('--name ', 'token name')); + program.addOption(new Option('--symbol ', 'token symbol')); + program.addOption(new Option('--decimals ', 'token decimals')); + program.addOption(new Option('--distributor ', 'token distributor')); + program.addOption(new Option('--mintAmount ', 'mint amount')); + program.addOption(new Option('--originalChain ', 'original chain')); + program.addOption(new Option('--destinationChain ', 'destination chain')); + program.addOption(new Option('--destinationAddress ', 'destination address')); + program.addOption(new Option('--gasValue ', 'gas value')); + program.addOption(new Option('--amount ', 'token amount')); + + program.action((options) => { + main(options); + }); + + program.parse(); +} diff --git a/evm/its.js b/evm/its.js index a385f7cd..e1f50470 100644 --- a/evm/its.js +++ b/evm/its.js @@ -9,9 +9,18 @@ const { Contract, } = ethers; const { Command, Option } = require('commander'); -const { printInfo, prompt, printWarn, printWalletInfo, wasEventEmitted, mainProcessor, validateParameters } = require('./utils'); +const { + printInfo, + prompt, + printWarn, + printWalletInfo, + wasEventEmitted, + mainProcessor, + validateParameters, + getContractJSON, +} = require('./utils'); const { getWallet } = require('./sign-utils'); -const IInterchainTokenService = require('@axelar-network/interchain-token-service/dist/interchain-token-service/InterchainTokenService.sol'); +const IInterchainTokenService = getContractJSON('IInterchainTokenService'); const { addExtendedOptions } = require('./cli-utils'); const tokenManagerImplementations = { MINT_BURN: 0, @@ -477,3 +486,5 @@ if (require.main === module) { program.parse(); } + +module.exports = { isValidTokenId, handleTx }; From 8f9ee170b069cddbeb4cf0884b9e0f970e56804c Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Mon, 20 Nov 2023 05:18:50 -0500 Subject: [PATCH 07/11] feat: some refactoring --- evm/deploy-contract.js | 10 +-- evm/execute-contract.js | 6 +- evm/interchainTokenFactory.js | 58 ++++++++----- evm/its.js | 78 ++++++++--------- evm/multisig.js | 4 +- evm/utils.js | 155 ++++++++++++++++++++++------------ 6 files changed, 180 insertions(+), 131 deletions(-) diff --git a/evm/deploy-contract.js b/evm/deploy-contract.js index 27d44849..884330d4 100644 --- a/evm/deploy-contract.js +++ b/evm/deploy-contract.js @@ -15,7 +15,7 @@ const { printWarn, printError, copyObject, - isString, + isNonEmptyString, isNumber, isAddressArray, getBytecodeHash, @@ -45,14 +45,14 @@ async function getConstructorArgs(contractName, chain, wallet) { const governanceChain = contractConfig.governanceChain || 'Axelarnet'; contractConfig.governanceChain = governanceChain; - if (!isString(governanceChain)) { + if (!isNonEmptyString(governanceChain)) { throw new Error(`Missing AxelarServiceGovernance.governanceChain in the chain info.`); } const governanceAddress = contractConfig.governanceAddress || 'axelar10d07y265gmmuvt4z0w9aw880jnsr700j7v9daj'; contractConfig.governanceAddress = governanceAddress; - if (!isString(governanceAddress)) { + if (!isNonEmptyString(governanceAddress)) { throw new Error(`Missing AxelarServiceGovernance.governanceAddress in the chain info.`); } @@ -102,14 +102,14 @@ async function getConstructorArgs(contractName, chain, wallet) { const governanceChain = contractConfig.governanceChain || 'Axelarnet'; contractConfig.governanceChain = governanceChain; - if (!isString(governanceChain)) { + if (!isNonEmptyString(governanceChain)) { throw new Error(`Missing InterchainGovernance.governanceChain in the chain info.`); } const governanceAddress = contractConfig.governanceAddress || 'axelar10d07y265gmmuvt4z0w9aw880jnsr700j7v9daj'; contractConfig.governanceAddress = governanceAddress; - if (!isString(governanceAddress)) { + if (!isNonEmptyString(governanceAddress)) { throw new Error(`Missing InterchainGovernance.governanceAddress in the chain info.`); } diff --git a/evm/execute-contract.js b/evm/execute-contract.js index e8189096..5b477b8e 100644 --- a/evm/execute-contract.js +++ b/evm/execute-contract.js @@ -12,7 +12,7 @@ const { const readlineSync = require('readline-sync'); const { Command, Option } = require('commander'); -const { isNumber, isString, loadConfig, saveConfig, printObj, printLog, printError, getContractJSON } = require('./utils'); +const { isNumber, isNonEmptyString, loadConfig, saveConfig, printObj, printLog, printError, getContractJSON } = require('./utils'); const { addBaseOptions } = require('./cli-utils'); async function getCallData(action, targetContract, inputRecipient, inputAmount) { @@ -129,7 +129,7 @@ async function executeContract(options, chain, wallet) { throw new Error('Missing target address in the address info.'); } - if (!isString(action)) { + if (!isNonEmptyString(action)) { throw new Error('Missing method name from the user info.'); } @@ -181,7 +181,7 @@ async function main(options) { const provider = getDefaultProvider(rpc); const privateKey = options.privateKey; - if (!isString(privateKey)) { + if (!isNonEmptyString(privateKey)) { throw new Error('Private Key value is not provided in the info file'); } diff --git a/evm/interchainTokenFactory.js b/evm/interchainTokenFactory.js index ffebd29b..86de5add 100644 --- a/evm/interchainTokenFactory.js +++ b/evm/interchainTokenFactory.js @@ -12,19 +12,22 @@ const { Command, Option } = require('commander'); const { printInfo, prompt, mainProcessor, validateParameters, getContractJSON } = require('./utils'); const { getWallet } = require('./sign-utils'); const { addExtendedOptions } = require('./cli-utils'); -const { isValidTokenId, handleTx } = require('./its'); +const { handleTx } = require('./its'); const IInterchainTokenFactory = getContractJSON('IInterchainTokenFactory'); +const IInterchainTokenService = getContractJSON('IInterchainTokenService'); +const IERC20 = getContractJSON('IERC20'); -async function processCommand(chain, options) { +async function processCommand(config, chain, options) { const { privateKey, address, action, yes } = options; const contracts = chain.contracts; const contractName = 'InterchainTokenFactory'; - const contractConfig = contracts.InterchainTokenFactory; + const contractConfig = contracts.InterchainTokenService; - const interchainTokenFactoryAddress = address || contracts.interchainTokenFactory?.interchainTokenFactory; + const interchainTokenFactoryAddress = address || contracts.InterchainTokenService?.interchainTokenFactory; + const interchainTokenServiceAddress = contracts.InterchainTokenService?.address; - validateParameters({ isValidAddress: { interchainTokenFactoryAddress } }); + validateParameters({ isValidAddress: { interchainTokenFactoryAddress, interchainTokenServiceAddress } }); const rpc = chain.rpc; const provider = getDefaultProvider(rpc); @@ -37,6 +40,7 @@ async function processCommand(chain, options) { printInfo('Contract address', interchainTokenFactoryAddress); const interchainTokenFactory = new Contract(interchainTokenFactoryAddress, IInterchainTokenFactory.abi, wallet); + const interchainTokenService = new Contract(interchainTokenServiceAddress, IInterchainTokenService.abi, wallet); const gasOptions = contractConfig?.gasOptions || chain?.gasOptions || {}; printInfo('Gas options', JSON.stringify(gasOptions, null, 2)); @@ -47,12 +51,6 @@ async function processCommand(chain, options) { return; } - const tokenId = options.tokenId; - - if (!isValidTokenId(tokenId)) { - throw new Error(`Invalid tokenId value: ${tokenId}`); - } - switch (action) { case 'contractId': { const contractId = await interchainTokenFactory.contractId(); @@ -121,14 +119,14 @@ async function processCommand(chain, options) { validateParameters({ isKeccak256Hash: { salt }, - isString: { name, symbol }, + isNonEmptyString: { name, symbol }, isValidAddress: { distributor }, isValidNumber: { decimals, mintAmount }, }); const tx = await interchainTokenFactory.deployInterchainToken(salt, name, symbol, decimals, mintAmount, distributor); - await handleTx(tx, chain, interchainTokenFactory, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); break; } @@ -138,7 +136,7 @@ async function processCommand(chain, options) { validateParameters({ isKeccak256Hash: { salt }, - isString: { originalChain, destinationChain }, + isNonEmptyString: { originalChain, destinationChain }, isValidBytesAddress: { distributor }, isValidNumber: { gasValue }, }); @@ -151,7 +149,7 @@ async function processCommand(chain, options) { gasValue, ); - await handleTx(tx, chain, interchainTokenFactory, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); break; } @@ -163,7 +161,7 @@ async function processCommand(chain, options) { const tx = await interchainTokenFactory.registerCanonicalInterchainToken(tokenAddress); - await handleTx(tx, chain, interchainTokenFactory, options.action, 'TokenManagerDeployed', 'TokenManagerDeploymentStarted'); + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'TokenManagerDeploymentStarted'); break; } @@ -173,7 +171,7 @@ async function processCommand(chain, options) { validateParameters({ isValidAddress: { tokenAddress }, - isString: { originalChain, destinationChain }, + isNonEmptyString: { originalChain, destinationChain }, isValidNumber: { gasValue }, }); @@ -184,7 +182,7 @@ async function processCommand(chain, options) { gasValue, ); - await handleTx(tx, chain, interchainTokenFactory, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); break; } @@ -195,6 +193,7 @@ async function processCommand(chain, options) { const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); validateParameters({ + isValidTokenId: { tokenId }, isString: { destinationChain }, isValidCalldata: { destinationAddress }, isValidNumber: { amount, gasValue }, @@ -208,7 +207,14 @@ async function processCommand(chain, options) { gasValue, ); - await handleTx(tx, chain, interchainTokenFactory, options.action, 'Transfer', 'InterchainTransferWithData'); + if (destinationChain === '') { + const tokenAddress = await interchainTokenService.interchainTokenAddress(tokenIdBytes32); + const token = new Contract(tokenAddress, IERC20.abi, wallet); + + await handleTx(tx, chain, token, options.action, 'Transfer'); + } else { + await handleTx(tx, chain, interchainTokenFactory, options.action, 'InterchainTransferWithData'); + } break; } @@ -218,11 +224,14 @@ async function processCommand(chain, options) { const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); - validateParameters({ isValidNumber: { amount } }); + validateParameters({ isValidTokenId: { tokenId }, isValidNumber: { amount } }); + + const tokenAddress = await interchainTokenService.interchainTokenAddress(tokenIdBytes32); + const token = new Contract(tokenAddress, IERC20.abi, wallet); const tx = await interchainTokenFactory.tokenTransferFrom(tokenIdBytes32, amount); - await handleTx(tx, chain, interchainTokenFactory, options.action, 'Transfer'); + await handleTx(tx, chain, token, options.action, 'Transfer'); break; } @@ -232,11 +241,14 @@ async function processCommand(chain, options) { const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); - validateParameters({ isValidNumber: { amount } }); + validateParameters({ isValidTokenId: { tokenId }, isValidNumber: { amount } }); + + const tokenAddress = await interchainTokenService.interchainTokenAddress(tokenIdBytes32); + const token = new Contract(tokenAddress, IERC20.abi, wallet); const tx = await interchainTokenFactory.tokenApprove(tokenIdBytes32, amount); - await handleTx(tx, chain, interchainTokenFactory, options.action, 'Approval'); + await handleTx(tx, chain, token, options.action, 'Approval'); break; } diff --git a/evm/its.js b/evm/its.js index e1f50470..a59bbd3a 100644 --- a/evm/its.js +++ b/evm/its.js @@ -18,6 +18,7 @@ const { mainProcessor, validateParameters, getContractJSON, + isValidTokenId, } = require('./utils'); const { getWallet } = require('./sign-utils'); const IInterchainTokenService = getContractJSON('IInterchainTokenService'); @@ -29,24 +30,6 @@ const tokenManagerImplementations = { LOCK_UNLOCK_FEE: 3, }; -function isValidTokenId(input) { - if (!input.startsWith('0x')) { - return false; - } - - const hexPattern = /^[0-9a-fA-F]+$/; - - if (!hexPattern.test(input.slice(2))) { - return false; - } - - const minValue = BigInt('0x00'); - const maxValue = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); - const numericValue = BigInt(input); - - return numericValue >= minValue && numericValue <= maxValue; -} - async function handleTx(tx, chain, contract, action, firstEvent, secondEvent) { printInfo(`${action} tx`, tx.hash); @@ -61,14 +44,14 @@ async function handleTx(tx, chain, contract, action, firstEvent, secondEvent) { } } -async function processCommand(chain, options) { +async function processCommand(config, chain, options) { const { privateKey, address, action, yes } = options; const contracts = chain.contracts; const contractName = 'InterchainTokenService'; const contractConfig = contracts.InterchainTokenService; - const interchainTokenServiceAddress = address || contracts.interchainTokenService?.address; + const interchainTokenServiceAddress = address || contracts.InterchainTokenService?.address; validateParameters({ isValidAddress: { interchainTokenServiceAddress } }); @@ -96,10 +79,6 @@ async function processCommand(chain, options) { const tokenId = options.tokenId; - if (!isValidTokenId(tokenId)) { - throw new Error(`Invalid tokenId value: ${tokenId}`); - } - switch (action) { case 'contractId': { const contractId = await interchainTokenService.contractId(); @@ -109,10 +88,12 @@ async function processCommand(chain, options) { } case 'tokenManagerAddress': { + validateParameters({ isValidTokenId: { tokenId } }); + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); const tokenManagerAddress = await interchainTokenService.tokenManagerAddress(tokenIdBytes32); - printInfo(`TokenManager address for tokenId: ${tokenId}:`, tokenManagerAddress); + printInfo(`TokenManager address for tokenId: ${tokenId}`, tokenManagerAddress); try { await interchainTokenService.validTokenManagerAddress(tokenIdBytes32); @@ -125,10 +106,12 @@ async function processCommand(chain, options) { } case 'interchainTokenAddress': { + validateParameters({ isValidTokenId: { tokenId } }); + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); const interchainTokenAddress = await interchainTokenService.interchainTokenAddress(tokenIdBytes32); - printInfo(`InterchainToken address for tokenId: ${tokenId}:`, interchainTokenAddress); + printInfo(`InterchainToken address for tokenId: ${tokenId}`, interchainTokenAddress); break; } @@ -148,34 +131,40 @@ async function processCommand(chain, options) { const type = options.type; const tokenManagerImplementation = await interchainTokenService.tokenManagerImplementation(tokenManagerImplementations[type]); - printInfo(`${type} TokenManager implementation address:`, tokenManagerImplementation); + printInfo(`${type} TokenManager implementation address`, tokenManagerImplementation); break; } case 'flowLimit': { + validateParameters({ isValidTokenId: { tokenId } }); + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); const flowLimit = await interchainTokenService.flowLimit(tokenIdBytes32); - printInfo(`Flow limit for TokenManager with tokenId: ${tokenId}`, flowLimit); + printInfo(`Flow limit for TokenManager with tokenId ${tokenId}`, flowLimit); break; } case 'flowOutAmount': { + validateParameters({ isValidTokenId: { tokenId } }); + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); const flowOutAmount = await interchainTokenService.flowOutAmount(tokenIdBytes32); - printInfo(`Flow out amount for TokenManager with tokenId: ${tokenId}`, flowOutAmount); + printInfo(`Flow out amount for TokenManager with tokenId ${tokenId}`, flowOutAmount); break; } case 'flowInAmount': { + validateParameters({ isValidTokenId: { tokenId } }); + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); const flowInAmount = await interchainTokenService.flowInAmount(tokenIdBytes32); - printInfo(`Flow out amount for TokenManager with tokenId: ${tokenId}`, flowInAmount); + printInfo(`Flow out amount for TokenManager with tokenId ${tokenId}`, flowInAmount); break; } @@ -208,7 +197,8 @@ async function processCommand(chain, options) { validateParameters({ isKeccak256Hash: { salt }, - isString: { destinationChain, name, symbol }, + isNonEmptyString: { name, symbol }, + isString: { destinationChain }, isValidBytesAddress: { distributor }, isValidNumber: { decimals, gasValue }, }); @@ -231,7 +221,7 @@ async function processCommand(chain, options) { case 'contractCallValue': { const { sourceChain, sourceAddress, payload } = options; - validateParameters({ isString: { sourceChain, sourceAddress } }); + validateParameters({ isNonEmptyString: { sourceChain, sourceAddress } }); const isTrustedAddress = await interchainTokenService.isTrustedAddress(sourceChain, sourceAddress); @@ -252,7 +242,7 @@ async function processCommand(chain, options) { validateParameters({ isKeccak256Hash: { commandID }, - isString: { sourceChain, sourceAddress }, + isNonEmptyString: { sourceChain, sourceAddress }, isValidCalldata: { payload }, }); @@ -266,14 +256,15 @@ async function processCommand(chain, options) { case 'interchainTransfer': { const { destinationChain, destinationAddress, amount, metadata } = options; - const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); - validateParameters({ - isString: { destinationChain, destinationAddress }, + isValidTokenId: { tokenId }, + isNonEmptyString: { destinationChain, destinationAddress }, isValidNumber: { amount }, isValidCalldata: { metadata }, }); + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + const tx = await interchainTokenService.interchainTransfer( tokenIdBytes32, destinationChain, @@ -290,14 +281,15 @@ async function processCommand(chain, options) { case 'callContractWithInterchainToken': { const { destinationChain, destinationAddress, amount, data } = options; - const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); - validateParameters({ - isString: { destinationChain, destinationAddress }, + isValidTokenId: { tokenId }, + isNonEmptyString: { destinationChain, destinationAddress }, isValidNumber: { amount }, isValidCalldata: { data }, }); + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + const tx = await interchainTokenService.callContractWithInterchainToken( tokenIdBytes32, destinationChain, @@ -342,7 +334,7 @@ async function processCommand(chain, options) { const { trustedChain, trustedAddress } = options; - validateParameters({ isString: { trustedChain, trustedAddress } }); + validateParameters({ isNonEmptyString: { trustedChain, trustedAddress } }); const tx = await interchainTokenService.setTrustedAddress(trustedChain, trustedAddress); @@ -360,7 +352,7 @@ async function processCommand(chain, options) { const trustedChain = options.trustedChain; - validateParameters({ isString: { trustedChain } }); + validateParameters({ isNonEmptyString: { trustedChain } }); const tx = await interchainTokenService.removeTrustedAddress(trustedChain); @@ -388,7 +380,7 @@ async function processCommand(chain, options) { case 'execute': { const { commandID, sourceChain, sourceAddress, payload } = options; - validateParameters({ isKeccak256Hash: { commandID }, isString: { sourceChain, sourceAddress } }); + validateParameters({ isKeccak256Hash: { commandID }, isNonEmptyString: { sourceChain, sourceAddress } }); const isTrustedAddress = await interchainTokenService.isTrustedAddress(sourceChain, sourceAddress); @@ -487,4 +479,4 @@ if (require.main === module) { program.parse(); } -module.exports = { isValidTokenId, handleTx }; +module.exports = { handleTx }; diff --git a/evm/multisig.js b/evm/multisig.js index d6118800..dbdf5a70 100644 --- a/evm/multisig.js +++ b/evm/multisig.js @@ -16,7 +16,7 @@ const { isNumber, isValidCalldata, printWarn, - isStringArray, + isNonEmptyStringArray, isNumberArray, isValidAddress, mainProcessor, @@ -151,7 +151,7 @@ async function processCommand(_, chain, options) { const symbolsArray = JSON.parse(symbols); const limitsArray = JSON.parse(limits); - if (!isStringArray(symbolsArray)) { + if (!isNonEmptyStringArray(symbolsArray)) { throw new Error(`Invalid token symbols: ${symbols})}`); } diff --git a/evm/utils.js b/evm/utils.js index 12b6e7f5..9bc48067 100644 --- a/evm/utils.js +++ b/evm/utils.js @@ -191,10 +191,14 @@ const httpGet = (url) => { }); }; -const isString = (arg) => { +const isNonEmptyString = (arg) => { return typeof arg === 'string' && arg !== ''; }; +const isString = (arg) => { + return typeof arg === 'string'; +}; + const isNumber = (arg) => { return Number.isInteger(arg); }; @@ -221,7 +225,7 @@ const isNumberArray = (arr) => { return true; }; -const isStringArray = (arr) => { +const isNonEmptyStringArray = (arr) => { if (!Array.isArray(arr)) { return false; } @@ -298,8 +302,90 @@ function isValidBytesAddress(input) { return addressRegex.test(input); } +const isContract = async (address, provider) => { + const code = await provider.getCode(address); + return code && code !== '0x'; +}; + +function isValidAddress(address, allowZeroAddress) { + if (!allowZeroAddress && address === AddressZero) { + return false; + } + + return isAddress(address); +} + +/** + * Validate if the input string matches the time format YYYY-MM-DDTHH:mm:ss + * + * @param {string} timeString - The input time string. + * @return {boolean} - Returns true if the format matches, false otherwise. + */ +function isValidTimeFormat(timeString) { + const regex = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2\d|3[01])T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/; + + if (timeString === '0') { + return true; + } + + return regex.test(timeString); +} + +// Validate if the input privateKey is correct +function isValidPrivateKey(privateKey) { + // Check if it's a valid hexadecimal string + if (!privateKey?.startsWith('0x')) { + privateKey = '0x' + privateKey; + } + + if (!isHexString(privateKey) || privateKey.length !== 66) { + return false; + } + + return true; +} + +function isValidTokenId(input) { + if (!input?.startsWith('0x')) { + return false; + } + + const hexPattern = /^[0-9a-fA-F]+$/; + + if (!hexPattern.test(input.slice(2))) { + return false; + } + + const minValue = BigInt('0x00'); + const maxValue = BigInt('0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF'); + const numericValue = BigInt(input); + + return numericValue >= minValue && numericValue <= maxValue; +} + +const validationFunctions = { + isNonEmptyString, + isNumber, + isValidNumber, + isValidDecimal, + isNumberArray, + isString, + isNonEmptyStringArray, + isAddressArray, + isKeccak256Hash, + isValidCalldata, + isValidBytesAddress, + isValidTimeFormat, + isContract, + isValidAddress, + isValidPrivateKey, + isValidTokenId, +}; + function validateParameters(parameters) { - for (const [validatorFunction, paramsObj] of Object.entries(parameters)) { + for (const [validatorFunctionString, paramsObj] of Object.entries(parameters)) { + const validatorFunction = validationFunctions[validatorFunctionString]; + if (typeof validatorFunction !== 'function') { throw new Error(`Validator function ${validatorFunction} is not defined`); } @@ -309,7 +395,7 @@ function validateParameters(parameters) { const isValid = validatorFunction(paramValue); if (!isValid) { - throw new Error(`Input validation failed for ${validatorFunction} with parameter ${paramKey}: ${paramValue}`); + throw new Error(`Input validation failed for ${validatorFunctionString} with parameter ${paramKey}: ${paramValue}`); } } } @@ -356,7 +442,7 @@ const parseArgs = (args) => { async function getBytecodeHash(contractObject, chain = '', provider = null) { let bytecode; - if (isString(contractObject)) { + if (isNonEmptyString(contractObject)) { if (provider === null) { throw new Error('Provider must be provided for chain'); } @@ -430,7 +516,7 @@ const getDeployedAddress = async (deployer, deployMethod, options = {}) => { const deployerContract = options.deployerContract; - if (!isString(deployerContract)) { + if (!isNonEmptyString(deployerContract)) { throw new Error('Deployer contract address was not provided'); } @@ -453,7 +539,7 @@ const getDeployedAddress = async (deployer, deployMethod, options = {}) => { case 'create3': { const deployerContract = options.deployerContract; - if (!isString(deployerContract)) { + if (!isNonEmptyString(deployerContract)) { throw new Error('Deployer contract address was not provided'); } @@ -590,11 +676,11 @@ const deployContract = async ( } case 'create2': { - if (!isString(deployOptions.deployerContract)) { + if (!isNonEmptyString(deployOptions.deployerContract)) { throw new Error('Deployer contract address was not provided'); } - if (!isString(deployOptions.salt)) { + if (!isNonEmptyString(deployOptions.salt)) { throw new Error('Salt was not provided'); } @@ -613,11 +699,11 @@ const deployContract = async ( } case 'create3': { - if (!isString(deployOptions.deployerContract)) { + if (!isNonEmptyString(deployOptions.deployerContract)) { throw new Error('Deployer contract address was not provided'); } - if (!isString(deployOptions.salt)) { + if (!isNonEmptyString(deployOptions.salt)) { throw new Error('Salt was not provided'); } @@ -641,36 +727,6 @@ const deployContract = async ( } }; -/** - * Validate if the input string matches the time format YYYY-MM-DDTHH:mm:ss - * - * @param {string} timeString - The input time string. - * @return {boolean} - Returns true if the format matches, false otherwise. - */ -function isValidTimeFormat(timeString) { - const regex = /^\d{4}-(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2\d|3[01])T(?:[01]\d|2[0-3]):[0-5]\d:[0-5]\d$/; - - if (timeString === '0') { - return true; - } - - return regex.test(timeString); -} - -// Validate if the input privateKey is correct -function isValidPrivateKey(privateKey) { - // Check if it's a valid hexadecimal string - if (!privateKey.startsWith('0x')) { - privateKey = '0x' + privateKey; - } - - if (!isHexString(privateKey) || privateKey.length !== 66) { - return false; - } - - return true; -} - const dateToEta = (utcTimeString) => { if (utcTimeString === '0') { return 0; @@ -709,19 +765,6 @@ function wasEventEmitted(receipt, contract, eventName) { return receipt.logs.some((log) => log.topics[0] === event.topics[0]); } -const isContract = async (address, provider) => { - const code = await provider.getCode(address); - return code && code !== '0x'; -}; - -function isValidAddress(address, allowZeroAddress) { - if (!allowZeroAddress && address === AddressZero) { - return false; - } - - return isAddress(address); -} - function copyObject(obj) { return JSON.parse(JSON.stringify(obj)); } @@ -894,11 +937,12 @@ module.exports = { predictAddressCreate, getDeployedAddress, isString, + isNonEmptyString, isNumber, isValidNumber, isValidDecimal, isNumberArray, - isStringArray, + isNonEmptyStringArray, isAddressArray, isKeccak256Hash, isValidCalldata, @@ -921,6 +965,7 @@ module.exports = { isContract, isValidAddress, isValidPrivateKey, + isValidTokenId, verifyContract, prompt, mainProcessor, From b253c57f01603194d8bce7c19c1909359078f76a Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Mon, 20 Nov 2023 05:30:41 -0500 Subject: [PATCH 08/11] feat: add to gh action --- .github/workflows/test.yaml | 9 ++++++++- evm/interchainTokenFactory.js | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 059073fa..f8242c54 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -65,12 +65,13 @@ jobs: } }' > ./axelar-chains-config/info/local.json - # Create .env file with default hardhat private key that's prefunded + # Create .env file with default hardhat private key that's prefunded, default salt is keccak256('salt') - name: Prepare .env run: | echo 'PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' >> .env echo 'ENV=local' >> .env echo 'CHAINS=test' >> .env + echo 'SALT=a05e334153147e75f3f416139b5109d1179cb56fef6a4ecb4c4cbc92a7c37b70' >> .env - name: Display local.json run: cat ./axelar-chains-config/info/local.json @@ -113,3 +114,9 @@ jobs: - name: Upgrade ITS using create2 run: node evm/deploy-its.js -s "ITS v1.0.0" -f "ITS v1.0.0 Factory" -m create2 -u -y + + - name: InterchainTokenFactory deploy interchain token on current chain + run: node evm/interchainTokenFactory.js --action deployInterchainToken --name "test" --symbol "TST" --decimals 18 --distributor 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -y + + - name: InterchainTokenService deploy interchain token on current chain + run: node evm/its.js --action deployInterchainToken --name "test" --symbol "TST" --decimals 18 --distributor 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 --destinationChain '' --gasValue 0 -y diff --git a/evm/interchainTokenFactory.js b/evm/interchainTokenFactory.js index 86de5add..aca41fee 100644 --- a/evm/interchainTokenFactory.js +++ b/evm/interchainTokenFactory.js @@ -299,7 +299,7 @@ if (require.main === module) { program.addOption(new Option('--symbol ', 'token symbol')); program.addOption(new Option('--decimals ', 'token decimals')); program.addOption(new Option('--distributor ', 'token distributor')); - program.addOption(new Option('--mintAmount ', 'mint amount')); + program.addOption(new Option('--mintAmount ', 'mint amount').default(0)); program.addOption(new Option('--originalChain ', 'original chain')); program.addOption(new Option('--destinationChain ', 'destination chain')); program.addOption(new Option('--destinationAddress ', 'destination address')); From cb986d5024c524012fb22e53fd1e481e092ee874 Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Mon, 20 Nov 2023 05:33:17 -0500 Subject: [PATCH 09/11] fix: gh action salt --- .github/workflows/test.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index f8242c54..318f6eda 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -71,7 +71,7 @@ jobs: echo 'PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' >> .env echo 'ENV=local' >> .env echo 'CHAINS=test' >> .env - echo 'SALT=a05e334153147e75f3f416139b5109d1179cb56fef6a4ecb4c4cbc92a7c37b70' >> .env + echo 'SALT=0xa05e334153147e75f3f416139b5109d1179cb56fef6a4ecb4c4cbc92a7c37b70' >> .env - name: Display local.json run: cat ./axelar-chains-config/info/local.json From 34911c4117b281642903c42df0205f829d3177a4 Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Mon, 20 Nov 2023 13:51:58 -0500 Subject: [PATCH 10/11] feat: added rawSalt option --- evm/interchainTokenFactory.js | 47 +++++++++++++++++++++-------------- evm/its.js | 41 ++++++++++++++++++++++-------- 2 files changed, 58 insertions(+), 30 deletions(-) diff --git a/evm/interchainTokenFactory.js b/evm/interchainTokenFactory.js index aca41fee..ffdc1e33 100644 --- a/evm/interchainTokenFactory.js +++ b/evm/interchainTokenFactory.js @@ -12,7 +12,7 @@ const { Command, Option } = require('commander'); const { printInfo, prompt, mainProcessor, validateParameters, getContractJSON } = require('./utils'); const { getWallet } = require('./sign-utils'); const { addExtendedOptions } = require('./cli-utils'); -const { handleTx } = require('./its'); +const { getDeploymentSalt, handleTx } = require('./its'); const IInterchainTokenFactory = getContractJSON('IInterchainTokenFactory'); const IInterchainTokenService = getContractJSON('IInterchainTokenService'); const IERC20 = getContractJSON('IERC20'); @@ -60,12 +60,14 @@ async function processCommand(config, chain, options) { } case 'interchainTokenSalt': { - const { chainNameHash, deployer, salt } = options; + const { chainNameHash, deployer } = options; - validateParameters({ isValidAddress: { deployer }, isKeccak256Hash: { chainNameHash, salt } }); + const deploymentSalt = getDeploymentSalt(options); - const interchainTokenSalt = await interchainTokenFactory.interchainTokenSalt(chainNameHash, deployer, salt); - printInfo(`interchainTokenSalt for deployer ${deployer} and deployment salt: ${salt}`, interchainTokenSalt); + validateParameters({ isValidAddress: { deployer }, isKeccak256Hash: { chainNameHash } }); + + const interchainTokenSalt = await interchainTokenFactory.interchainTokenSalt(chainNameHash, deployer, deploymentSalt); + printInfo(`interchainTokenSalt for deployer ${deployer} and deployment salt: ${deploymentSalt}`, interchainTokenSalt); break; } @@ -82,12 +84,14 @@ async function processCommand(config, chain, options) { } case 'interchainTokenId': { - const { deployer, salt } = options; + const { deployer } = options; + + const deploymentSalt = getDeploymentSalt(options); - validateParameters({ isValidAddress: { deployer }, isKeccak256Hash: { salt } }); + validateParameters({ isValidAddress: { deployer } }); - const interchainTokenId = await interchainTokenFactory.interchainTokenId(deployer, salt); - printInfo(`InterchainTokenId for deployer ${deployer} and deployment salt: ${salt}`, interchainTokenId); + const interchainTokenId = await interchainTokenFactory.interchainTokenId(deployer, deploymentSalt); + printInfo(`InterchainTokenId for deployer ${deployer} and deployment salt: ${deploymentSalt}`, interchainTokenId); break; } @@ -104,27 +108,30 @@ async function processCommand(config, chain, options) { } case 'interchainTokenAddress': { - const { deployer, salt } = options; + const { deployer } = options; + + const deploymentSalt = getDeploymentSalt(options); - validateParameters({ isValidAddress: { deployer }, isKeccak256Hash: { salt } }); + validateParameters({ isValidAddress: { deployer } }); - const interchainTokenAddress = await interchainTokenFactory.interchainTokenAddress(deployer, salt); - printInfo(`interchainTokenAddress for deployer ${deployer} and deployment salt: ${salt}`, interchainTokenAddress); + const interchainTokenAddress = await interchainTokenFactory.interchainTokenAddress(deployer, deploymentSalt); + printInfo(`interchainTokenAddress for deployer ${deployer} and deployment salt: ${deploymentSalt}`, interchainTokenAddress); break; } case 'deployInterchainToken': { - const { salt, name, symbol, decimals, mintAmount, distributor } = options; + const { name, symbol, decimals, mintAmount, distributor } = options; + + const deploymentSalt = getDeploymentSalt(options); validateParameters({ - isKeccak256Hash: { salt }, isNonEmptyString: { name, symbol }, isValidAddress: { distributor }, isValidNumber: { decimals, mintAmount }, }); - const tx = await interchainTokenFactory.deployInterchainToken(salt, name, symbol, decimals, mintAmount, distributor); + const tx = await interchainTokenFactory.deployInterchainToken(deploymentSalt, name, symbol, decimals, mintAmount, distributor); await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); @@ -132,10 +139,11 @@ async function processCommand(config, chain, options) { } case 'deployRemoteInterchainToken': { - const { originalChain, salt, distributor, destinationChain, gasValue } = options; + const { originalChain, distributor, destinationChain, gasValue } = options; + + const deploymentSalt = getDeploymentSalt(options); validateParameters({ - isKeccak256Hash: { salt }, isNonEmptyString: { originalChain, destinationChain }, isValidBytesAddress: { distributor }, isValidNumber: { gasValue }, @@ -143,7 +151,7 @@ async function processCommand(config, chain, options) { const tx = await interchainTokenFactory.deployRemoteInterchainToken( originalChain, - salt, + deploymentSalt, distributor, destinationChain, gasValue, @@ -305,6 +313,7 @@ if (require.main === module) { program.addOption(new Option('--destinationAddress ', 'destination address')); program.addOption(new Option('--gasValue ', 'gas value')); program.addOption(new Option('--amount ', 'token amount')); + program.addOption(new Option('--rawSalt ', 'raw deployment salt').env('RAW_SALT')); program.action((options) => { main(options); diff --git a/evm/its.js b/evm/its.js index a59bbd3a..4b747cae 100644 --- a/evm/its.js +++ b/evm/its.js @@ -23,6 +23,7 @@ const { const { getWallet } = require('./sign-utils'); const IInterchainTokenService = getContractJSON('IInterchainTokenService'); const { addExtendedOptions } = require('./cli-utils'); +const { getSaltFromKey } = require('@axelar-network/axelar-gmp-sdk-solidity/scripts/utils'); const tokenManagerImplementations = { MINT_BURN: 0, MINT_BURN_FROM: 1, @@ -30,6 +31,19 @@ const tokenManagerImplementations = { LOCK_UNLOCK_FEE: 3, }; +function getDeploymentSalt(options) { + const { rawSalt, salt } = options; + + if (rawSalt) { + validateParameters({ isKeccak256Hash: { rawSalt } }); + return rawSalt; + } + + validateParameters({ isString: { salt } }); + return getSaltFromKey(salt); + +} + async function handleTx(tx, chain, contract, action, firstEvent, secondEvent) { printInfo(`${action} tx`, tx.hash); @@ -117,12 +131,14 @@ async function processCommand(config, chain, options) { } case 'interchainTokenId': { - const { sender, salt } = options; + const { sender } = options; - validateParameters({ isValidAddress: { sender }, isKeccak256Hash: { salt } }); + const deploymentSalt = getDeploymentSalt(options); - const interchainTokenId = await interchainTokenService.interchainTokenId(sender, salt); - printInfo(`InterchainTokenId for sender ${sender} and deployment salt: ${salt}`, interchainTokenId); + validateParameters({ isValidAddress: { sender } }); + + const interchainTokenId = await interchainTokenService.interchainTokenId(sender, deploymentSalt); + printInfo(`InterchainTokenId for sender ${sender} and deployment salt: ${deploymentSalt}`, interchainTokenId); break; } @@ -170,17 +186,18 @@ async function processCommand(config, chain, options) { } case 'deployTokenManager': { - const { salt, destinationChain, type, params, gasValue } = options; + const { destinationChain, type, params, gasValue } = options; + + const deploymentSalt = getDeploymentSalt(options); validateParameters({ - isKeccak256Hash: { salt }, isString: { destinationChain }, isValidCalldata: { params }, isValidNumber: { gasValue }, }); const tx = await interchainTokenService.deployTokenManager( - salt, + deploymentSalt, destinationChain, tokenManagerImplementations[type], params, @@ -193,10 +210,11 @@ async function processCommand(config, chain, options) { } case 'deployInterchainToken': { - const { salt, destinationChain, name, symbol, decimals, distributor, gasValue } = options; + const { destinationChain, name, symbol, decimals, distributor, gasValue } = options; + + const deploymentSalt = getDeploymentSalt(options); validateParameters({ - isKeccak256Hash: { salt }, isNonEmptyString: { name, symbol }, isString: { destinationChain }, isValidBytesAddress: { distributor }, @@ -204,7 +222,7 @@ async function processCommand(config, chain, options) { }); const tx = await interchainTokenService.deployInterchainToken( - salt, + deploymentSalt, destinationChain, name, symbol, @@ -471,6 +489,7 @@ if (require.main === module) { program.addOption(new Option('--trustedChain ', 'chain name for trusted addresses')); program.addOption(new Option('--trustedAddress ', 'trusted address')); program.addOption(new Option('--pauseStatus ', 'pause status').choices(['true', 'false'])); + program.addOption(new Option('--rawSalt ', 'raw deployment salt').env('RAW_SALT')); program.action((options) => { main(options); @@ -479,4 +498,4 @@ if (require.main === module) { program.parse(); } -module.exports = { handleTx }; +module.exports = { getDeploymentSalt, handleTx }; From 500fbdf882e4dd7f1c8f0adc4546247cce630313 Mon Sep 17 00:00:00 2001 From: Dean Amiel Date: Mon, 20 Nov 2023 13:53:44 -0500 Subject: [PATCH 11/11] fix: gh action --- .github/workflows/test.yaml | 7 +++---- evm/its.js | 7 +++---- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 318f6eda..651d4eeb 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -65,13 +65,12 @@ jobs: } }' > ./axelar-chains-config/info/local.json - # Create .env file with default hardhat private key that's prefunded, default salt is keccak256('salt') + # Create .env file with default hardhat private key that's prefunded - name: Prepare .env run: | echo 'PRIVATE_KEY=0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80' >> .env echo 'ENV=local' >> .env echo 'CHAINS=test' >> .env - echo 'SALT=0xa05e334153147e75f3f416139b5109d1179cb56fef6a4ecb4c4cbc92a7c37b70' >> .env - name: Display local.json run: cat ./axelar-chains-config/info/local.json @@ -116,7 +115,7 @@ jobs: run: node evm/deploy-its.js -s "ITS v1.0.0" -f "ITS v1.0.0 Factory" -m create2 -u -y - name: InterchainTokenFactory deploy interchain token on current chain - run: node evm/interchainTokenFactory.js --action deployInterchainToken --name "test" --symbol "TST" --decimals 18 --distributor 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 -y + run: node evm/interchainTokenFactory.js --action deployInterchainToken --name "test" --symbol "TST" --decimals 18 --distributor 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 --salt "salt" -y - name: InterchainTokenService deploy interchain token on current chain - run: node evm/its.js --action deployInterchainToken --name "test" --symbol "TST" --decimals 18 --distributor 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 --destinationChain '' --gasValue 0 -y + run: node evm/its.js --action deployInterchainToken --name "test" --symbol "TST" --decimals 18 --distributor 0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266 --destinationChain '' --gasValue 0 --salt "salt" -y diff --git a/evm/its.js b/evm/its.js index 4b747cae..a2b703af 100644 --- a/evm/its.js +++ b/evm/its.js @@ -38,10 +38,9 @@ function getDeploymentSalt(options) { validateParameters({ isKeccak256Hash: { rawSalt } }); return rawSalt; } - - validateParameters({ isString: { salt } }); - return getSaltFromKey(salt); - + + validateParameters({ isString: { salt } }); + return getSaltFromKey(salt); } async function handleTx(tx, chain, contract, action, firstEvent, secondEvent) {