diff --git a/common/utils.js b/common/utils.js index a63f1eb6..8fa77a69 100644 --- a/common/utils.js +++ b/common/utils.js @@ -440,6 +440,16 @@ const getMultisigProof = async (config, chain, multisigSessionId) => { const calculateDomainSeparator = (chain, router, network) => keccak256(Buffer.from(`${chain}${router}${network}`)); +const getItsEdgeContract = (chainConfig) => { + const itsEdgeContract = chainConfig.contracts.InterchainTokenService?.address || chainConfig.contracts.ITS?.objects?.ChannelId; + + if (!itsEdgeContract) { + throw new Error(`Missing ITS edge contract for chain ${chainConfig.name}`); + } + + return itsEdgeContract; +}; + module.exports = { loadConfig, saveConfig, @@ -478,4 +488,5 @@ module.exports = { getAmplifierContractOnchainConfig, getSaltFromKey, calculateDomainSeparator, + getItsEdgeContract, }; diff --git a/cosmwasm/README.md b/cosmwasm/README.md index f666c045..b32e9488 100644 --- a/cosmwasm/README.md +++ b/cosmwasm/README.md @@ -216,6 +216,43 @@ Example usage: node cosmwasm/submit-proposal.js execute -c Router -t "Proposal title" -d "Proposal description" --deposit 100000000 --msg '{"register_chain":{"chain":"avalanche","gateway_address":"axelar17cnq5hujmkf2lr2c5hatqmhzlvwm365rqc5ugryphxeftavjef9q89zxvp","msg_id_format":"hex_tx_hash_and_event_index"}}' ``` +### Register chain on ITS Hub through governance proposal + +To submit a governance proposal to register an ITS chain, use the `submit-proposal` script with the `its-hub-register-chains ` command. The `chains` argument is used to pass a list of chains to register on ITS hub. + +**Prerequisites**: ITS hub contract configuration in json file must include the following attributes per chain: + +| Attribute | Description | EVM | Sui | +| --------------------------- | ------------------------------------------------------------------------------------------ | --- | --- | +| `maxUintBits` | Number of bits for the chain's maximum uint representation | 256 | 64 | +| `maxDecimalsWhenTruncating` | Maximum decimal places allowed when truncating ITS token amounts transferred to this chain | 255 | 6 | + +For EVM chains, the values above are used by default if not specified explicitly. + +Example configuration: + +``` +"axelar": { + "contracts": { + ... + "InterchainTokenService": { + ... + "some-sui-chain": { + "maxUintBits": 64, + "maxDecimalsWhenTruncating": 6, + } + } + ... + } +} +``` + +Example usage: + +``` +node cosmwasm/submit-proposal.js its-hub-register-chains avalanche-fuji sui-test2 -t "Proposal title" -d "Proposal description" --deposit 100000000 -r $RUN_AS_ACCOUNT +``` + ### Submit a proposal to change a parameter To submit a governance proposal to change a parameter, use the `submit-proposal` script with the `paramChange` command. The `--changes` option should be used to pass a JSON string representing an array of parameter changes. diff --git a/cosmwasm/submit-proposal.js b/cosmwasm/submit-proposal.js index 90f3c73a..92c29a15 100644 --- a/cosmwasm/submit-proposal.js +++ b/cosmwasm/submit-proposal.js @@ -16,6 +16,7 @@ const { getAmplifierBaseContractConfig, getAmplifierContractConfig, updateCodeId, + getChainTruncationParams, decodeProposalAttributes, encodeStoreCodeProposal, encodeStoreInstantiateProposal, @@ -27,7 +28,7 @@ const { submitProposal, makeInstantiateMsg, } = require('./utils'); -const { saveConfig, loadConfig, printInfo, prompt } = require('../common'); +const { saveConfig, loadConfig, printInfo, prompt, getChainConfig, getItsEdgeContract } = require('../common'); const { StoreCodeProposal, StoreAndInstantiateContractProposal, @@ -167,6 +168,30 @@ const execute = async (client, wallet, config, options) => { await callSubmitProposal(client, wallet, config, options, proposal); }; +const registerItsChain = async (client, wallet, config, options) => { + const chains = options.chains.map((chain) => { + const chainConfig = getChainConfig(config, chain); + const { maxUintBits, maxDecimalsWhenTruncating } = getChainTruncationParams(config, chainConfig); + + const itsEdgeContract = getItsEdgeContract(chainConfig); + + return { + chain: chainConfig.axelarId, + its_edge_contract: itsEdgeContract, + truncation: { + max_uint: (2n ** BigInt(maxUintBits) - 1n).toString(), + max_decimals_when_truncating: maxDecimalsWhenTruncating, + }, + }; + }); + + await execute(client, wallet, config, { + ...options, + contractName: 'InterchainTokenService', + msg: `{ "register_chains": { "chains": ${JSON.stringify(chains)} } }`, + }); +}; + const paramChange = async (client, wallet, config, options) => { const proposal = encodeParameterChangeProposal(options); @@ -256,12 +281,22 @@ const programHandler = () => { const executeCmd = program .command('execute') - .description('Submit a execute wasm contract proposal') + .description('Submit an execute wasm contract proposal') .action((options) => { mainProcessor(execute, options); }); addAmplifierOptions(executeCmd, { contractOptions: true, executeProposalOptions: true, proposalOptions: true, runAs: true }); + const registerItsChainCmd = program + .command('its-hub-register-chains') + .description('Submit an execute wasm contract proposal to register an ITS chain') + .argument('', 'list of chains to register on ITS hub') + .action((chains, options) => { + options.chains = chains; + mainProcessor(registerItsChain, options); + }); + addAmplifierOptions(registerItsChainCmd, { registerItsChainOptions: true, proposalOptions: true, runAs: true }); + const paramChangeCmd = program .command('paramChange') .description('Submit a parameter change proposal') diff --git a/cosmwasm/utils.js b/cosmwasm/utils.js index ab902c55..cbaf8400 100644 --- a/cosmwasm/utils.js +++ b/cosmwasm/utils.js @@ -29,9 +29,13 @@ const { getChainConfig, getSaltFromKey, calculateDomainSeparator, + validateParameters, } = require('../common'); const { normalizeBech32 } = require('@cosmjs/encoding'); +const DEFAULT_MAX_UINT_BITS_EVM = 256; +const DEFAULT_MAX_DECIMALS_WHEN_TRUNCATING_EVM = 255; + const governanceAddress = 'axelar10d07y265gmmuvt4z0w9aw880jnsr700j7v9daj'; const prepareWallet = async ({ mnemonic }) => await DirectSecp256k1HdWallet.fromMnemonic(mnemonic, { prefix: 'axelar' }); @@ -606,6 +610,24 @@ const fetchCodeIdFromCodeHash = async (client, contractBaseConfig) => { return codeId; }; +const getChainTruncationParams = (config, chainConfig) => { + const key = chainConfig.axelarId.toLowerCase(); + const chainTruncationParams = config.axelar.contracts.InterchainTokenService[key]; + + let maxUintBits = chainTruncationParams?.maxUintBits; + let maxDecimalsWhenTruncating = chainTruncationParams?.maxDecimalsWhenTruncating; + + // set EVM default values + if (chainConfig.chainType === 'evm') { + maxUintBits = maxUintBits || DEFAULT_MAX_UINT_BITS_EVM; + maxDecimalsWhenTruncating = maxDecimalsWhenTruncating || DEFAULT_MAX_DECIMALS_WHEN_TRUNCATING_EVM; + } + + validateParameters({ isValidNumber: { maxUintBits, maxDecimalsWhenTruncating } }); + + return { maxUintBits, maxDecimalsWhenTruncating }; +}; + const getInstantiatePermission = (accessType, addresses) => { return { permission: accessType, @@ -840,6 +862,7 @@ module.exports = { instantiateContract, makeInstantiateMsg, fetchCodeIdFromCodeHash, + getChainTruncationParams, decodeProposalAttributes, encodeStoreCodeProposal, encodeStoreInstantiateProposal,