From 1ec46ecfdd0d9e50e9a06b620eedd35cb5304d73 Mon Sep 17 00:00:00 2001 From: Dean Date: Mon, 20 Nov 2023 16:24:36 -0500 Subject: [PATCH 1/2] feat: dedicated ITS script (#106) * feat: dedicated ITS script * feat: integrated script optimizations * feat: address comments * feat: update validateParameters function * fix: hardcoded event names * feat: add interchain token factory script * feat: some refactoring * feat: add to gh action * fix: gh action salt * feat: added rawSalt option * fix: gh action --------- Co-authored-by: Dean Amiel --- .github/workflows/test.yaml | 6 + evm/cli-utils.js | 2 +- evm/deploy-contract.js | 10 +- evm/execute-contract.js | 6 +- evm/interchainTokenFactory.js | 323 ++++++++++++++++++++++ evm/its.js | 500 ++++++++++++++++++++++++++++++++++ evm/multisig.js | 4 +- evm/utils.js | 175 ++++++++---- 8 files changed, 962 insertions(+), 64 deletions(-) create mode 100644 evm/interchainTokenFactory.js create mode 100644 evm/its.js diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 059073fa8..651d4eeb0 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -113,3 +113,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 --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 --salt "salt" -y diff --git a/evm/cli-utils.js b/evm/cli-utils.js index 6ce17f321..36d7f2434 100644 --- a/evm/cli-utils.js +++ b/evm/cli-utils.js @@ -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/deploy-contract.js b/evm/deploy-contract.js index 27d44849a..884330d48 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 e8189096d..5b477b8e7 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 new file mode 100644 index 000000000..ffdc1e337 --- /dev/null +++ b/evm/interchainTokenFactory.js @@ -0,0 +1,323 @@ +'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 { getDeploymentSalt, handleTx } = require('./its'); +const IInterchainTokenFactory = getContractJSON('IInterchainTokenFactory'); +const IInterchainTokenService = getContractJSON('IInterchainTokenService'); +const IERC20 = getContractJSON('IERC20'); + +async function processCommand(config, chain, options) { + const { privateKey, address, action, yes } = options; + + const contracts = chain.contracts; + const contractName = 'InterchainTokenFactory'; + const contractConfig = contracts.InterchainTokenService; + + const interchainTokenFactoryAddress = address || contracts.InterchainTokenService?.interchainTokenFactory; + const interchainTokenServiceAddress = contracts.InterchainTokenService?.address; + + validateParameters({ isValidAddress: { interchainTokenFactoryAddress, interchainTokenServiceAddress } }); + + 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 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 interchainTokenFactory.contractId(); + printInfo('InterchainTokenFactory contract ID', contractId); + + break; + } + + case 'interchainTokenSalt': { + const { chainNameHash, deployer } = options; + + const deploymentSalt = getDeploymentSalt(options); + + validateParameters({ isValidAddress: { deployer }, isKeccak256Hash: { chainNameHash } }); + + const interchainTokenSalt = await interchainTokenFactory.interchainTokenSalt(chainNameHash, deployer, deploymentSalt); + printInfo(`interchainTokenSalt for deployer ${deployer} and deployment salt: ${deploymentSalt}`, 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 } = options; + + const deploymentSalt = getDeploymentSalt(options); + + validateParameters({ isValidAddress: { deployer } }); + + const interchainTokenId = await interchainTokenFactory.interchainTokenId(deployer, deploymentSalt); + printInfo(`InterchainTokenId for deployer ${deployer} and deployment salt: ${deploymentSalt}`, 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 } = options; + + const deploymentSalt = getDeploymentSalt(options); + + validateParameters({ isValidAddress: { deployer } }); + + const interchainTokenAddress = await interchainTokenFactory.interchainTokenAddress(deployer, deploymentSalt); + printInfo(`interchainTokenAddress for deployer ${deployer} and deployment salt: ${deploymentSalt}`, interchainTokenAddress); + + break; + } + + case 'deployInterchainToken': { + const { name, symbol, decimals, mintAmount, distributor } = options; + + const deploymentSalt = getDeploymentSalt(options); + + validateParameters({ + isNonEmptyString: { name, symbol }, + isValidAddress: { distributor }, + isValidNumber: { decimals, mintAmount }, + }); + + const tx = await interchainTokenFactory.deployInterchainToken(deploymentSalt, name, symbol, decimals, mintAmount, distributor); + + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); + + break; + } + + case 'deployRemoteInterchainToken': { + const { originalChain, distributor, destinationChain, gasValue } = options; + + const deploymentSalt = getDeploymentSalt(options); + + validateParameters({ + isNonEmptyString: { originalChain, destinationChain }, + isValidBytesAddress: { distributor }, + isValidNumber: { gasValue }, + }); + + const tx = await interchainTokenFactory.deployRemoteInterchainToken( + originalChain, + deploymentSalt, + distributor, + destinationChain, + gasValue, + ); + + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); + + break; + } + + case 'registerCanonicalInterchainToken': { + const { tokenAddress } = options; + + validateParameters({ isValidAddress: { tokenAddress } }); + + const tx = await interchainTokenFactory.registerCanonicalInterchainToken(tokenAddress); + + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'TokenManagerDeploymentStarted'); + + break; + } + + case 'deployRemoteCanonicalInterchainToken': { + const { originalChain, tokenAddress, destinationChain, gasValue } = options; + + validateParameters({ + isValidAddress: { tokenAddress }, + isNonEmptyString: { originalChain, destinationChain }, + isValidNumber: { gasValue }, + }); + + const tx = await interchainTokenFactory.deployRemoteCanonicalInterchainToken( + originalChain, + tokenAddress, + destinationChain, + gasValue, + ); + + await handleTx(tx, chain, interchainTokenService, 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({ + isValidTokenId: { tokenId }, + isString: { destinationChain }, + isValidCalldata: { destinationAddress }, + isValidNumber: { amount, gasValue }, + }); + + const tx = await interchainTokenFactory.interchainTransfer( + tokenIdBytes32, + destinationChain, + destinationAddress, + amount, + gasValue, + ); + + 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; + } + + case 'tokenTransferFrom': { + const { tokenId, amount } = options; + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + 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, token, options.action, 'Transfer'); + + break; + } + + case 'tokenApprove': { + const { tokenId, amount } = options; + + const tokenIdBytes32 = hexZeroPad(tokenId.startsWith('0x') ? tokenId : '0x' + tokenId, 32); + + 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, token, 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').default(0)); + 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.addOption(new Option('--rawSalt ', 'raw deployment salt').env('RAW_SALT')); + + program.action((options) => { + main(options); + }); + + program.parse(); +} diff --git a/evm/its.js b/evm/its.js new file mode 100644 index 000000000..a2b703af6 --- /dev/null +++ b/evm/its.js @@ -0,0 +1,500 @@ +'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, + wasEventEmitted, + mainProcessor, + validateParameters, + getContractJSON, + isValidTokenId, +} = require('./utils'); +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, + LOCK_UNLOCK: 2, + 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); + + const receipt = await tx.wait(chain.confirmations); + + const eventEmitted = + (firstEvent ? wasEventEmitted(receipt, contract, firstEvent) : true) || + (secondEvent ? wasEventEmitted(receipt, contract, secondEvent) : false); + + if (!eventEmitted) { + printWarn('Event not emitted in receipt.'); + } +} + +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; + + validateParameters({ isValidAddress: { interchainTokenServiceAddress } }); + + 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; + } + + const tokenId = options.tokenId; + + switch (action) { + case 'contractId': { + const contractId = await interchainTokenService.contractId(); + printInfo('InterchainTokenService contract ID', contractId); + + break; + } + + 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); + + try { + await interchainTokenService.validTokenManagerAddress(tokenIdBytes32); + printInfo(`TokenManager for tokenId: ${tokenId} exists at address:`, tokenManagerAddress); + } catch (error) { + printInfo(`TokenManager for tokenId: ${tokenId} does not yet exist.`); + } + + break; + } + + 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); + + break; + } + + case 'interchainTokenId': { + const { sender } = options; + + const deploymentSalt = getDeploymentSalt(options); + + validateParameters({ isValidAddress: { sender } }); + + const interchainTokenId = await interchainTokenService.interchainTokenId(sender, deploymentSalt); + printInfo(`InterchainTokenId for sender ${sender} and deployment salt: ${deploymentSalt}`, interchainTokenId); + + break; + } + + case 'tokenManagerImplementation': { + const type = options.type; + + const tokenManagerImplementation = await interchainTokenService.tokenManagerImplementation(tokenManagerImplementations[type]); + 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); + + 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); + + 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); + + break; + } + + case 'deployTokenManager': { + const { destinationChain, type, params, gasValue } = options; + + const deploymentSalt = getDeploymentSalt(options); + + validateParameters({ + isString: { destinationChain }, + isValidCalldata: { params }, + isValidNumber: { gasValue }, + }); + + const tx = await interchainTokenService.deployTokenManager( + deploymentSalt, + destinationChain, + tokenManagerImplementations[type], + params, + gasValue, + ); + + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'TokenManagerDeploymentStarted'); + + break; + } + + case 'deployInterchainToken': { + const { destinationChain, name, symbol, decimals, distributor, gasValue } = options; + + const deploymentSalt = getDeploymentSalt(options); + + validateParameters({ + isNonEmptyString: { name, symbol }, + isString: { destinationChain }, + isValidBytesAddress: { distributor }, + isValidNumber: { decimals, gasValue }, + }); + + const tx = await interchainTokenService.deployInterchainToken( + deploymentSalt, + destinationChain, + name, + symbol, + decimals, + distributor, + gasValue, + ); + + await handleTx(tx, chain, interchainTokenService, options.action, 'TokenManagerDeployed', 'InterchainTokenDeploymentStarted'); + + break; + } + + case 'contractCallValue': { + const { sourceChain, sourceAddress, payload } = options; + + validateParameters({ isNonEmptyString: { sourceChain, sourceAddress } }); + + const isTrustedAddress = await interchainTokenService.isTrustedAddress(sourceChain, sourceAddress); + + if (!isTrustedAddress) { + throw new Error('Invalid remote service.'); + } + + 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); + + break; + } + + case 'expressExecute': { + const { commandID, sourceChain, sourceAddress, payload } = options; + + validateParameters({ + isKeccak256Hash: { commandID }, + isNonEmptyString: { sourceChain, sourceAddress }, + isValidCalldata: { payload }, + }); + + const tx = await interchainTokenService.expressExecute(commandID, sourceChain, sourceAddress, payload); + + await handleTx(tx, chain, interchainTokenService, options.action, 'ExpressExecuted'); + + break; + } + + case 'interchainTransfer': { + const { destinationChain, destinationAddress, amount, metadata } = options; + + validateParameters({ + 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, + destinationAddress, + amount, + metadata, + ); + + await handleTx(tx, chain, interchainTokenService, options.action, 'InterchainTransfer', 'InterchainTransferWithData'); + + break; + } + + case 'callContractWithInterchainToken': { + const { destinationChain, destinationAddress, amount, data } = options; + + validateParameters({ + 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, + destinationAddress, + amount, + data, + ); + + await handleTx(tx, chain, interchainTokenService, options.action, 'InterchainTransfer', 'InterchainTransferWithData'); + + 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); + } + + validateParameters({ isNumberArray: { flowLimits } }); + + const tx = await interchainTokenService.setFlowLimits(tokenIdsBytes32, flowLimits); + + await handleTx(tx, chain, interchainTokenService, options.action, 'FlowLimitSet'); + + 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; + + validateParameters({ isNonEmptyString: { trustedChain, trustedAddress } }); + + const tx = await interchainTokenService.setTrustedAddress(trustedChain, trustedAddress); + + await handleTx(tx, chain, interchainTokenService, options.action, 'TrustedAddressSet'); + + 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; + + validateParameters({ isNonEmptyString: { trustedChain } }); + + const tx = await interchainTokenService.removeTrustedAddress(trustedChain); + + await handleTx(tx, chain, interchainTokenService, options.action, 'TrustedAddressRemoved'); + + 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); + + await handleTx(tx, chain, interchainTokenService, options.action, 'Paused', 'Unpaused'); + + break; + } + + case 'execute': { + const { commandID, sourceChain, sourceAddress, payload } = options; + + validateParameters({ isKeccak256Hash: { commandID }, isNonEmptyString: { sourceChain, sourceAddress } }); + + const isTrustedAddress = await interchainTokenService.isTrustedAddress(sourceChain, sourceAddress); + + if (!isTrustedAddress) { + throw new Error('Invalid remote service.'); + } + + validateParameters({ isValidCalldata: { payload } }); + + const tx = await interchainTokenService.execute(commandID, sourceChain, sourceAddress, payload); + + await handleTx(tx, chain, interchainTokenService, options.action); + + 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'); + + addExtendedOptions(program, { address: true, salt: true }); + + program.addOption( + new Option('--action ', 'ITS action') + .choices([ + 'contractId', + 'tokenManagerAddress', + 'tokenAddress', + 'interchainTokenAddress', + 'interchainTokenId', + 'tokenManagerImplementation', + 'flowLimit', + 'flowOutAmount', + 'flowInAmount', + 'deployTokenManager', + 'deployInterchainToken', + 'contractCallValue', + 'expressExecute', + 'interchainTransfer', + 'callContractWithInterchainToken', + 'setFlowLimits', + 'setTrustedAddress', + 'removeTrustedAddress', + 'setPauseStatus', + 'execute', + ]) + .makeOptionMandatory(true), + ); + + 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('--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.addOption(new Option('--rawSalt ', 'raw deployment salt').env('RAW_SALT')); + + program.action((options) => { + main(options); + }); + + program.parse(); +} + +module.exports = { getDeploymentSalt, handleTx }; diff --git a/evm/multisig.js b/evm/multisig.js index d6118800e..dbdf5a705 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 fd9d75bfd..9bc48067a 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; } @@ -293,6 +297,110 @@ function isValidCalldata(input) { return hexPattern.test(input.slice(2)); } +function isValidBytesAddress(input) { + const addressRegex = /^0x[a-fA-F0-9]{40}$/; + 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 [validatorFunctionString, paramsObj] of Object.entries(parameters)) { + const validatorFunction = validationFunctions[validatorFunctionString]; + + if (typeof validatorFunction !== 'function') { + throw new Error(`Validator function ${validatorFunction} is not defined`); + } + + for (const paramKey of Object.keys(paramsObj)) { + const paramValue = paramsObj[paramKey]; + const isValid = validatorFunction(paramValue); + + if (!isValid) { + throw new Error(`Input validation failed for ${validatorFunctionString} with parameter ${paramKey}: ${paramValue}`); + } + } + } +} + /** * Parses the input string into an array of arguments, recognizing and converting * to the following types: boolean, number, array, and string. @@ -334,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'); } @@ -408,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'); } @@ -431,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'); } @@ -568,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'); } @@ -591,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'); } @@ -619,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; @@ -687,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)); } @@ -872,14 +937,17 @@ module.exports = { predictAddressCreate, getDeployedAddress, isString, + isNonEmptyString, isNumber, isValidNumber, isValidDecimal, isNumberArray, - isStringArray, + isNonEmptyStringArray, isAddressArray, isKeccak256Hash, isValidCalldata, + isValidBytesAddress, + validateParameters, parseArgs, getProxy, getEVMBatch, @@ -897,6 +965,7 @@ module.exports = { isContract, isValidAddress, isValidPrivateKey, + isValidTokenId, verifyContract, prompt, mainProcessor, From fafbfb67d7e6796427ad66bb2f6e8cd37209fef5 Mon Sep 17 00:00:00 2001 From: jcs47 <11947034+jcs47@users.noreply.github.com> Date: Mon, 20 Nov 2023 21:47:27 +0000 Subject: [PATCH 2/2] feat: update testnet configuration (#116) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * update testnet configuration * add contracts * addendum * deployed gateways * deployed gas service * explorer field * add governance contracts --------- Co-authored-by: João Sousa Co-authored-by: Canh Trinh Co-authored-by: Milap Sheth --- axelar-chains-config/info/testnet.json | 281 +++++++++++++++++++++++++ evm/upgradable.js | 4 +- 2 files changed, 283 insertions(+), 2 deletions(-) diff --git a/axelar-chains-config/info/testnet.json b/axelar-chains-config/info/testnet.json index 61e95e9b9..2c8a358d3 100644 --- a/axelar-chains-config/info/testnet.json +++ b/axelar-chains-config/info/testnet.json @@ -1714,6 +1714,287 @@ "gasOptions": { "gasLimit": 8000000 } + }, + "immutable": { + "name": "Immutable", + "id": "immutable", + "chainId": 13473, + "rpc": "https://rpc.testnet.immutable.com", + "tokenSymbol": "IMX", + "contracts": { + "ConstAddressDeployer": { + "address": "0x98B2920D53612483F91F12Ed7754E51b4A77919e", + "deployer": "0xE86375704CDb8491a5Ed82D90DceCE02Ee0ac25F", + "deploymentMethod": "create", + "codehash": "0x8fda47a596dfba923270da84e0c32a2d0312f1c03389f83e16f2b5a35ed37fbe", + "predeployCodehash": "0x8fda47a596dfba923270da84e0c32a2d0312f1c03389f83e16f2b5a35ed37fbe" + }, + "Create3Deployer": { + "address": "0x6513Aedb4D1593BA12e50644401D976aebDc90d8", + "deployer": "0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05", + "deploymentMethod": "create2", + "codehash": "0xf0ad66defbe082df243d4d274e626f557f97579c5c9e19f33d8093d6160808b7", + "predeployCodehash": "0x73fc31262c4bad113c79439fd231281201c7c7d45b50328bd86bccf37684bf92", + "salt": "Create3Deployer" + }, + "AxelarGateway": { + "deployer": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", + "startingKeyIDs": [ + "evm-immutable-genesis" + ], + "address": "0xe432150cce91c13a887f7D836923d5597adD8E31", + "implementation": "0xc1712652326E87D193Ac11910934085FF45C2F48", + "implementationCodehash": "0xd0e057031b5acbd22b8e98686f6cda19dcbcac6495bd7297cff31dcb22ddcbae", + "authModule": "0x1a920B29eBD437074225cAeE44f78FC700B27a5d", + "tokenDeployer": "0xD2aDceFd0496449E3FDE873A2332B18A0F0FCADf", + "deploymentMethod": "create3", + "salt": "AxelarGateway v6.2" + }, + "InterchainGovernance": { + "address": "0xfDF36A30070ea0241d69052ea85ff44Ad0476a66", + "governanceChain": "Axelarnet", + "governanceAddress": "axelar10d07y265gmmuvt4z0w9aw880jnsr700j7v9daj", + "minimumTimeDelay": 300, + "deployer": "0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05", + "deploymentMethod": "create3", + "codehash": "0x4bc0efa16652748f5c3fbb77aedff01e0c1df4156a4f4c82d6d8748ee28cb9af", + "predeployCodehash": "0xe2de43b29f2387b6f3575a1b50d566908fc00e03a8d88ad6be74b674a70874d2", + "salt": "InterchainGovernance v5.5" + }, + "Multisig": { + "threshold": 2, + "signers": [ + "0x15837c1318AB83d99b19392Fd4811813f520d843", + "0x64247a441CeF0b7A46614AC34d046c0fdfe35954", + "0xEE64c8eb48437DbD2D5B8598dc4A3E8a6c8CEaD9" + ], + "address": "0xCC940AE49C78F20E3F13F3cF37e996b98Ac3EC68", + "deployer": "0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05", + "deploymentMethod": "create3", + "codehash": "0x912095d5076ee40a9dd49c0f9d61d61334c47a78c7512852791652baef26c296", + "predeployCodehash": "0x912095d5076ee40a9dd49c0f9d61d61334c47a78c7512852791652baef26c296", + "salt": "Multisig v5.5" + }, + "Operators": { + "owner": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", + "address": "0x7F83F5cA2AE4206AbFf8a3C3668e88ce5F11C0B5", + "deployer": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", + "deploymentMethod": "create2", + "codehash": "0xc561dc32ef670c929db9d7fbf6b5f6c074a62a30602481ba3b88912ca6d79feb", + "predeployCodehash": "0xc561dc32ef670c929db9d7fbf6b5f6c074a62a30602481ba3b88912ca6d79feb", + "salt": "Operators" + }, + "AxelarGasService": { + "collector": "0x7F83F5cA2AE4206AbFf8a3C3668e88ce5F11C0B5", + "address": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "implementation": "0xCD6b34FaF1FD1056C728A27426AB6807f84BAa1b", + "deployer": "0x5b593E7b1725dc6FcbbFe80b2415B19153F94A85" + } + }, + "gasOptions": { + "gasLimit": 8000000, + "maxPriorityFeePerGas": 150000000000, + "maxFeePerGas": 160000000000 + }, + "explorer": { + "url": "" + } + }, + "ethereum-sepolia": { + "name": "Ethereum-Sepolia", + "id": "ethereum-sepolia", + "chainId": 11155111, + "rpc": "https://1rpc.io/sepolia", + "tokenSymbol": "ETH", + "confirmations": 2, + "contracts": { + "ConstAddressDeployer": { + "address": "0x98B2920D53612483F91F12Ed7754E51b4A77919e", + "deployer": "0xE86375704CDb8491a5Ed82D90DceCE02Ee0ac25F", + "deploymentMethod": "create", + "codehash": "0x8fda47a596dfba923270da84e0c32a2d0312f1c03389f83e16f2b5a35ed37fbe", + "predeployCodehash": "0x8fda47a596dfba923270da84e0c32a2d0312f1c03389f83e16f2b5a35ed37fbe" + }, + "Create3Deployer": { + "address": "0x6513Aedb4D1593BA12e50644401D976aebDc90d8", + "deployer": "0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05", + "deploymentMethod": "create2", + "codehash": "0xf0ad66defbe082df243d4d274e626f557f97579c5c9e19f33d8093d6160808b7", + "predeployCodehash": "0x73fc31262c4bad113c79439fd231281201c7c7d45b50328bd86bccf37684bf92", + "salt": "Create3Deployer" + }, + "AxelarGateway": { + "deployer": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", + "startingKeyIDs": [ + "evm-ethereum-sepolia-genesis" + ], + "address": "0xe432150cce91c13a887f7D836923d5597adD8E31", + "implementation": "0xc1712652326E87D193Ac11910934085FF45C2F48", + "implementationCodehash": "0xd0e057031b5acbd22b8e98686f6cda19dcbcac6495bd7297cff31dcb22ddcbae", + "authModule": "0x1a920B29eBD437074225cAeE44f78FC700B27a5d", + "tokenDeployer": "0xD2aDceFd0496449E3FDE873A2332B18A0F0FCADf", + "deploymentMethod": "create3", + "salt": "AxelarGateway v6.2" + }, + "InterchainGovernance": { + "address": "0xfDF36A30070ea0241d69052ea85ff44Ad0476a66", + "governanceChain": "Axelarnet", + "governanceAddress": "axelar10d07y265gmmuvt4z0w9aw880jnsr700j7v9daj", + "minimumTimeDelay": 300, + "deployer": "0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05", + "deploymentMethod": "create3", + "codehash": "0x4bc0efa16652748f5c3fbb77aedff01e0c1df4156a4f4c82d6d8748ee28cb9af", + "predeployCodehash": "0xe2de43b29f2387b6f3575a1b50d566908fc00e03a8d88ad6be74b674a70874d2", + "salt": "InterchainGovernance v5.5" + }, + "Multisig": { + "threshold": 2, + "signers": [ + "0x15837c1318AB83d99b19392Fd4811813f520d843", + "0x64247a441CeF0b7A46614AC34d046c0fdfe35954", + "0xEE64c8eb48437DbD2D5B8598dc4A3E8a6c8CEaD9" + ], + "address": "0xCC940AE49C78F20E3F13F3cF37e996b98Ac3EC68", + "deployer": "0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05", + "deploymentMethod": "create3", + "codehash": "0x912095d5076ee40a9dd49c0f9d61d61334c47a78c7512852791652baef26c296", + "predeployCodehash": "0x912095d5076ee40a9dd49c0f9d61d61334c47a78c7512852791652baef26c296", + "salt": "Multisig v5.5" + }, + "Operators": { + "owner": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", + "address": "0x7F83F5cA2AE4206AbFf8a3C3668e88ce5F11C0B5", + "deployer": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", + "deploymentMethod": "create2", + "codehash": "0xc561dc32ef670c929db9d7fbf6b5f6c074a62a30602481ba3b88912ca6d79feb", + "predeployCodehash": "0xc561dc32ef670c929db9d7fbf6b5f6c074a62a30602481ba3b88912ca6d79feb", + "salt": "Operators" + }, + "AxelarGasService": { + "collector": "0x7F83F5cA2AE4206AbFf8a3C3668e88ce5F11C0B5", + "address": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "implementation": "0xCD6b34FaF1FD1056C728A27426AB6807f84BAa1b", + "deployer": "0x5b593E7b1725dc6FcbbFe80b2415B19153F94A85" + } + }, + "explorer": { + "url": "https://sepolia.etherscan.io", + "api": "https://api-sepolia.etherscan.io/api" + }, + "gasOptions": { + "maxFeePerGas": 2000000000, + "maxPriorityFeePerGas": 100000000 + } + }, + "arbitrum-sepolia": { + "name": "Arbitrum-Sepolia", + "id": "arbitrum-sepolia", + "chainId": 421614, + "rpc": "https://sepolia-rollup.arbitrum.io/rpc", + "tokenSymbol": "ETH", + "confirmations": 2, + "contracts": { + "ConstAddressDeployer": { + "address": "0x98B2920D53612483F91F12Ed7754E51b4A77919e", + "deployer": "0xE86375704CDb8491a5Ed82D90DceCE02Ee0ac25F", + "deploymentMethod": "create", + "codehash": "0x8fda47a596dfba923270da84e0c32a2d0312f1c03389f83e16f2b5a35ed37fbe", + "predeployCodehash": "0x8fda47a596dfba923270da84e0c32a2d0312f1c03389f83e16f2b5a35ed37fbe" + }, + "Create3Deployer": { + "address": "0x6513Aedb4D1593BA12e50644401D976aebDc90d8", + "deployer": "0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05", + "deploymentMethod": "create2", + "codehash": "0xf0ad66defbe082df243d4d274e626f557f97579c5c9e19f33d8093d6160808b7", + "predeployCodehash": "0x73fc31262c4bad113c79439fd231281201c7c7d45b50328bd86bccf37684bf92", + "salt": "Create3Deployer" + } + }, + "explorer": { + "url": "https://sepolia.arbiscan.io", + "api": "https://api-sepolia.arbiscan.io/api" + } + }, + "centrifuge": { + "name": "Centrifuge", + "id": "centrifuge-2", + "chainId": 2090, + "rpc": "https://node-7118620155331796992.gx.onfinality.io/rpc?apikey=00538f2d-6297-44e3-8812-4b9d579524b2", + "tokenSymbol": "CFG", + "confirmations": 1, + "contracts": { + "ConstAddressDeployer": { + "address": "0x98B2920D53612483F91F12Ed7754E51b4A77919e", + "deployer": "0xE86375704CDb8491a5Ed82D90DceCE02Ee0ac25F", + "deploymentMethod": "create", + "codehash": "0x8fda47a596dfba923270da84e0c32a2d0312f1c03389f83e16f2b5a35ed37fbe", + "predeployCodehash": "0x8fda47a596dfba923270da84e0c32a2d0312f1c03389f83e16f2b5a35ed37fbe" + }, + "Create3Deployer": { + "address": "0x6513Aedb4D1593BA12e50644401D976aebDc90d8", + "deployer": "0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05", + "deploymentMethod": "create2", + "codehash": "0xf0ad66defbe082df243d4d274e626f557f97579c5c9e19f33d8093d6160808b7", + "predeployCodehash": "0x73fc31262c4bad113c79439fd231281201c7c7d45b50328bd86bccf37684bf92", + "salt": "Create3Deployer" + }, + "AxelarGateway": { + "deployer": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", + "startingKeyIDs": [ + "evm-centrifuge-2-genesis" + ], + "address": "0xe432150cce91c13a887f7D836923d5597adD8E31", + "implementation": "0xc1712652326E87D193Ac11910934085FF45C2F48", + "implementationCodehash": "0xd0e057031b5acbd22b8e98686f6cda19dcbcac6495bd7297cff31dcb22ddcbae", + "authModule": "0x1a920B29eBD437074225cAeE44f78FC700B27a5d", + "tokenDeployer": "0xD2aDceFd0496449E3FDE873A2332B18A0F0FCADf", + "deploymentMethod": "create3", + "salt": "AxelarGateway v6.2" + }, + "InterchainGovernance": { + "address": "0xfDF36A30070ea0241d69052ea85ff44Ad0476a66", + "governanceChain": "Axelarnet", + "governanceAddress": "axelar10d07y265gmmuvt4z0w9aw880jnsr700j7v9daj", + "minimumTimeDelay": 300, + "deployer": "0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05", + "deploymentMethod": "create3", + "codehash": "0x4bc0efa16652748f5c3fbb77aedff01e0c1df4156a4f4c82d6d8748ee28cb9af", + "predeployCodehash": "0xe2de43b29f2387b6f3575a1b50d566908fc00e03a8d88ad6be74b674a70874d2", + "salt": "InterchainGovernance v5.5" + }, + "Multisig": { + "threshold": 2, + "signers": [ + "0x15837c1318AB83d99b19392Fd4811813f520d843", + "0x64247a441CeF0b7A46614AC34d046c0fdfe35954", + "0xEE64c8eb48437DbD2D5B8598dc4A3E8a6c8CEaD9" + ], + "address": "0xCC940AE49C78F20E3F13F3cF37e996b98Ac3EC68", + "deployer": "0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05", + "deploymentMethod": "create3", + "codehash": "0x912095d5076ee40a9dd49c0f9d61d61334c47a78c7512852791652baef26c296", + "predeployCodehash": "0x912095d5076ee40a9dd49c0f9d61d61334c47a78c7512852791652baef26c296", + "salt": "Multisig v5.5" + }, + "Operators": { + "owner": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", + "address": "0x7F83F5cA2AE4206AbFf8a3C3668e88ce5F11C0B5", + "deployer": "0xB8Cd93C83A974649D76B1c19f311f639e62272BC", + "deploymentMethod": "create2", + "codehash": "0xc561dc32ef670c929db9d7fbf6b5f6c074a62a30602481ba3b88912ca6d79feb", + "predeployCodehash": "0xc561dc32ef670c929db9d7fbf6b5f6c074a62a30602481ba3b88912ca6d79feb", + "salt": "Operators" + }, + "AxelarGasService": { + "collector": "0x7F83F5cA2AE4206AbFf8a3C3668e88ce5F11C0B5", + "address": "0xbE406F0189A0B4cf3A05C286473D23791Dd44Cc6", + "implementation": "0xCD6b34FaF1FD1056C728A27426AB6807f84BAa1b", + "deployer": "0x5b593E7b1725dc6FcbbFe80b2415B19153F94A85" + } + }, + "explorer": { + "url": "" + } } }, "axelar": { diff --git a/evm/upgradable.js b/evm/upgradable.js index 6c673245f..4786d9c66 100644 --- a/evm/upgradable.js +++ b/evm/upgradable.js @@ -21,13 +21,13 @@ async function deployUpgradable( const proxyFactory = new ContractFactory(proxyJson.abi, proxyJson.bytecode, wallet); - const implementation = await implementationFactory.deploy(...implementationConstructorArgs); + const implementation = await implementationFactory.deploy(...implementationConstructorArgs, txOptions); await implementation.deployed(); const proxy = await proxyFactory.deploy(...proxyConstructorArgs, txOptions); await proxy.deployed(); - await proxy.init(implementation.address, wallet.address, setupParams).then((tx) => tx.wait()); + await proxy.init(implementation.address, wallet.address, setupParams, txOptions).then((tx) => tx.wait()); if (verifyOptions) { await verifyContract(verifyOptions.env, verifyOptions.chain, proxy.address, proxyConstructorArgs);