From fd13b2339deb9b236e706b59818544e0aa748477 Mon Sep 17 00:00:00 2001 From: npty Date: Fri, 26 Jul 2024 18:41:02 +0700 Subject: [PATCH 01/30] chore: add test contract deployment into deploy-contract script --- sui/deploy-contract.js | 69 ++++++++++++++++++++++++++++++++---------- 1 file changed, 53 insertions(+), 16 deletions(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index e97113cd..7a7405d1 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -1,16 +1,64 @@ const { saveConfig, printInfo } = require('../evm/utils'); const { Command, Argument, Option } = require('commander'); const { addBaseOptions } = require('./cli-utils'); -const { getWallet, printWalletInfo } = require('./sign-utils'); -const { loadSuiConfig, findPublishedObject, deployPackage } = require('./utils'); +const { Transaction } = require('@mysten/sui/transactions'); +const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); +const { singletonStruct } = require('./types-utils'); +const { loadSuiConfig, findPublishedObject, deployPackage, getBcsBytesByObjectId } = require('./utils'); // Add more contracts here to support more modules deployment const contractMap = { GasService: { packageName: 'gas_service', }, + Test: { + packageName: 'test', + }, +}; + +const postDeploy = { + GasService: postDeployGasService, + Test: postDeployTest, }; +// Parse bcs bytes from singleton object to get channel id +async function getChannelId(client, singletonObjectId) { + const bcsBytes = await getBcsBytesByObjectId(client, singletonObjectId); + const data = singletonStruct.parse(bcsBytes); + return '0x' + data.channel.id; +} + +async function postDeployGasService(published, config, chain, options) { + chain.contracts.GasService.objects.GasCollectorCap = findPublishedObject( + published, + contractMap.GasService.packageName, + 'GasCollectorCap', + ).objectId; + + chain.contracts.GasService.objects.GasService = findPublishedObject( + published, + contractMap.GasService.packageName, + 'GasService', + ).objectId; +} + +async function postDeployTest(published, config, chain, options) { + const [keypair, client] = getWallet(chain, options); + + const singleton = published.publishTxn.objectChanges.find((change) => change.objectType === `${published.packageId}::test::Singleton`); + const relayerDiscovery = config.sui.contracts.axelar_gateway?.objects?.relayerDiscovery; + + const tx = new Transaction(); + tx.moveCall({ + target: `${published.packageId}::test::register_transaction`, + arguments: [tx.object(relayerDiscovery), tx.object(singleton.objectId)], + }); + + const registerTx = await broadcast(client, keypair, tx); + + printInfo('Register transaction', registerTx.digest); +} + async function processCommand(contractName, config, chain, options) { const contract = contractMap[contractName]; const packageName = options.packageName || contract.packageName; @@ -26,24 +74,13 @@ async function processCommand(contractName, config, chain, options) { const published = await deployPackage(packageName, client, keypair); const packageId = published.packageId; - const contractObject = findPublishedObject(published, packageName, contractName); - const gasCollectorCapObject = findPublishedObject(published, packageName, 'GasCollectorCap'); - const contractConfig = chain.contracts[contractName]; contractConfig.address = packageId; - contractConfig.objects = { - [contractName]: contractObject.objectId, - }; - - switch (contractName) { - case 'GasService': - contractConfig.objects.GasCollectorCap = gasCollectorCapObject.objectId; - break; - default: - throw new Error(`${contractName} is not supported.`); - } printInfo(`${contractName} deployed`, JSON.stringify(contractConfig, null, 2)); + + // Submitting additional setup transaction or saving additional objects to the chain config here + await postDeploy[contractName]?.(published, config, chain, options); } async function mainProcessor(contractName, options, processor) { From 49d90575893d495f81854e3c9de3006d0ea73f44 Mon Sep 17 00:00:00 2001 From: npty Date: Fri, 26 Jul 2024 18:42:47 +0700 Subject: [PATCH 02/30] chore: update post deployment for test contract --- sui/deploy-contract.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index 7a7405d1..a29a9562 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -57,6 +57,11 @@ async function postDeployTest(published, config, chain, options) { const registerTx = await broadcast(client, keypair, tx); printInfo('Register transaction', registerTx.digest); + + const channelId = await getChannelId(client, singleton.objectId); + + chain.contracts.Test.address = published.packageId; + chain.contracts.Test.objects = { singleton: singleton.objectId, channelId }; } async function processCommand(contractName, config, chain, options) { @@ -77,10 +82,10 @@ async function processCommand(contractName, config, chain, options) { const contractConfig = chain.contracts[contractName]; contractConfig.address = packageId; - printInfo(`${contractName} deployed`, JSON.stringify(contractConfig, null, 2)); - // Submitting additional setup transaction or saving additional objects to the chain config here await postDeploy[contractName]?.(published, config, chain, options); + + printInfo(`${contractName} deployed`, JSON.stringify(contractConfig, null, 2)); } async function mainProcessor(contractName, options, processor) { From f1874dc7ae495b52c2b9621c2220af551af1eb06 Mon Sep 17 00:00:00 2001 From: npty Date: Fri, 26 Jul 2024 18:43:39 +0700 Subject: [PATCH 03/30] chore: remove unnecessary address record --- sui/deploy-contract.js | 1 - 1 file changed, 1 deletion(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index a29a9562..209ee5b8 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -60,7 +60,6 @@ async function postDeployTest(published, config, chain, options) { const channelId = await getChannelId(client, singleton.objectId); - chain.contracts.Test.address = published.packageId; chain.contracts.Test.objects = { singleton: singleton.objectId, channelId }; } From 473521db11c1438a158dac5aa9e4e9f80761e9e0 Mon Sep 17 00:00:00 2001 From: npty Date: Mon, 29 Jul 2024 16:06:32 +0700 Subject: [PATCH 04/30] chore: refactor deploy-contract --- sui/deploy-contract.js | 106 ++++++++++++++++++++--------------------- sui/utils.js | 25 ++++++++-- 2 files changed, 75 insertions(+), 56 deletions(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index 209ee5b8..8b3e43d0 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -1,25 +1,19 @@ const { saveConfig, printInfo } = require('../evm/utils'); -const { Command, Argument, Option } = require('commander'); +const { Command, Argument } = require('commander'); const { addBaseOptions } = require('./cli-utils'); const { Transaction } = require('@mysten/sui/transactions'); const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); const { singletonStruct } = require('./types-utils'); -const { loadSuiConfig, findPublishedObject, deployPackage, getBcsBytesByObjectId } = require('./utils'); - -// Add more contracts here to support more modules deployment -const contractMap = { - GasService: { - packageName: 'gas_service', - }, - Test: { - packageName: 'test', - }, -}; - -const postDeploy = { - GasService: postDeployGasService, - Test: postDeployTest, -}; +const { loadSuiConfig, findPublishedObject, deployPackage, getBcsBytesByObjectId, readMovePackageName } = require('./utils'); + +// A list of currently supported packages which are the folder names in `node_modules/@axelar-network/axelar-cgp-sui/move` +const supportedPackageDirs = ['gas_service', 'test']; + +// Map supported packages to their package names and directories +const supportedPackages = supportedPackageDirs.map((dir) => ({ + packageName: readMovePackageName(dir), + packageDir: dir, +})); // Parse bcs bytes from singleton object to get channel id async function getChannelId(client, singletonObjectId) { @@ -28,26 +22,27 @@ async function getChannelId(client, singletonObjectId) { return '0x' + data.channel.id; } -async function postDeployGasService(published, config, chain, options) { - chain.contracts.GasService.objects.GasCollectorCap = findPublishedObject( - published, - contractMap.GasService.packageName, - 'GasCollectorCap', - ).objectId; - - chain.contracts.GasService.objects.GasService = findPublishedObject( - published, - contractMap.GasService.packageName, - 'GasService', - ).objectId; +/** ######## Post Deployment Functions ######## **/ +// Define the post deployment functions for each supported package here. These functions should be called after the package is deployed. +// Use cases include: +// 1. Update the chain config with deployed object ids +// 2. Submit additional transactions to setup the contracts. + +async function postDeployGasService(published, chain) { + chain.contracts.GasService.objects = { + GasCollectorCap: findPublishedObject(published, 'gas_service', 'GasCollectorCap').objectId, + GasService: findPublishedObject(published, 'gas_service', 'GasService').objectId, + }; } async function postDeployTest(published, config, chain, options) { const [keypair, client] = getWallet(chain, options); - - const singleton = published.publishTxn.objectChanges.find((change) => change.objectType === `${published.packageId}::test::Singleton`); const relayerDiscovery = config.sui.contracts.axelar_gateway?.objects?.relayerDiscovery; + const singleton = findPublishedObject(published, 'test', 'Singleton'); + const channelId = await getChannelId(client, singleton.objectId); + chain.contracts.Test.objects = { singleton: singleton.objectId, channelId }; + const tx = new Transaction(); tx.moveCall({ target: `${published.packageId}::test::register_transaction`, @@ -57,39 +52,41 @@ async function postDeployTest(published, config, chain, options) { const registerTx = await broadcast(client, keypair, tx); printInfo('Register transaction', registerTx.digest); - - const channelId = await getChannelId(client, singleton.objectId); - - chain.contracts.Test.objects = { singleton: singleton.objectId, channelId }; } -async function processCommand(contractName, config, chain, options) { - const contract = contractMap[contractName]; - const packageName = options.packageName || contract.packageName; +/** ######## Main Processor ######## **/ + +async function processCommand(supportedContract, config, chain, options) { + const { packageDir, packageName } = supportedContract; const [keypair, client] = getWallet(chain, options); await printWalletInfo(keypair, client, chain, options); - if (!chain.contracts[contractName]) { - chain.contracts[contractName] = {}; + if (!chain.contracts[packageName]) { + chain.contracts[packageName] = {}; } - const published = await deployPackage(packageName, client, keypair); - const packageId = published.packageId; - - const contractConfig = chain.contracts[contractName]; - contractConfig.address = packageId; + const published = await deployPackage(packageDir, client, keypair); // Submitting additional setup transaction or saving additional objects to the chain config here - await postDeploy[contractName]?.(published, config, chain, options); + switch (packageName) { + case 'GasService': + await postDeployGasService(published, chain); + break; + case 'Test': + await postDeployTest(published, config, chain, options); + break; + default: + throw new Error(`Unsupported package: ${packageName}`); + } - printInfo(`${contractName} deployed`, JSON.stringify(contractConfig, null, 2)); + printInfo(`${packageName} deployed`, JSON.stringify(chain.contracts[packageName], null, 2)); } -async function mainProcessor(contractName, options, processor) { +async function mainProcessor(supportedContract, options, processor) { const config = loadSuiConfig(options.env); - await processor(contractName, config, config.sui, options); + await processor(supportedContract, config, config.sui, options); saveConfig(config, options.env); } @@ -98,14 +95,17 @@ if (require.main === module) { program .name('deploy-contract') - .addOption(new Option('--packageName ', 'Package name to deploy')) - .addArgument(new Argument('', 'Contract name to deploy').choices(Object.keys(contractMap))) + .addArgument( + new Argument('', 'Contract name to deploy') + .choices(supportedPackages.map((p) => p.packageName)) + .argParser((packageName) => supportedPackages.find((p) => p.packageName === packageName)), + ) .description('Deploy SUI modules'); addBaseOptions(program); - program.action((contractName, options) => { - mainProcessor(contractName, options, processCommand); + program.action((supportedContract, options) => { + mainProcessor(supportedContract, options, processCommand); }); program.parse(); diff --git a/sui/utils.js b/sui/utils.js index e46aedcb..4e2d2545 100644 --- a/sui/utils.js +++ b/sui/utils.js @@ -1,11 +1,12 @@ 'use strict'; const { ethers } = require('hardhat'); -const { loadConfig } = require('../evm/utils'); +const { loadConfig, printError } = require('../evm/utils'); const { BigNumber, utils: { arrayify, hexlify }, } = ethers; +const fs = require('fs'); const { fromB64 } = require('@mysten/bcs'); const { CosmWasmClient } = require('@cosmjs/cosmwasm-stargate'); const { updateMoveToml, copyMovePackage, TxBuilder } = require('@axelar-network/axelar-cgp-sui'); @@ -77,9 +78,26 @@ const deployPackage = async (packageName, client, keypair, options = {}) => { return { packageId, publishTxn }; }; -const findPublishedObject = (published, packageName, contractName) => { +const findPublishedObject = (published, packageDir, contractName) => { const packageId = published.packageId; - return published.publishTxn.objectChanges.find((change) => change.objectType === `${packageId}::${packageName}::${contractName}`); + return published.publishTxn.objectChanges.find((change) => change.objectType === `${packageId}::${packageDir}::${contractName}`); +}; + +const readMovePackageName = (moveDir) => { + try { + const moveToml = fs.readFileSync(`${__dirname}/../node_modules/@axelar-network/axelar-cgp-sui/move/${moveDir}/Move.toml`, 'utf8'); + + const nameMatch = moveToml.match(/^\s*name\s*=\s*"([^"]+)"/m); + + if (nameMatch && nameMatch[1]) { + return nameMatch[1]; + } + + throw new Error('Package name not found in TOML file'); + } catch (err) { + printError('Error reading TOML file'); + throw err; + } }; module.exports = { @@ -88,4 +106,5 @@ module.exports = { loadSuiConfig, deployPackage, findPublishedObject, + readMovePackageName, }; From 8aaa5cd0e087212e241768ffe1713cb76554fb18 Mon Sep 17 00:00:00 2001 From: npty Date: Mon, 29 Jul 2024 16:16:33 +0700 Subject: [PATCH 05/30] fix: choices is overrided by the handler --- sui/deploy-contract.js | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index 8b3e43d0..fd283f6a 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -96,9 +96,19 @@ if (require.main === module) { program .name('deploy-contract') .addArgument( - new Argument('', 'Contract name to deploy') - .choices(supportedPackages.map((p) => p.packageName)) - .argParser((packageName) => supportedPackages.find((p) => p.packageName === packageName)), + new Argument('', 'Contract name to deploy').argParser((packageName) => { + const supportedPackage = supportedPackages.find((p) => p.packageName === packageName); + + if (!supportedPackage) { + throw new Error( + `Unsupported package: ${packageName}. Supported packages: ${supportedPackages + .map((p) => p.packageName) + .join(', ')}`, + ); + } + + return supportedPackage; + }), ) .description('Deploy SUI modules'); From ab5d5c3a816709685a4df08e6bf3e1a123514086 Mon Sep 17 00:00:00 2001 From: npty Date: Mon, 29 Jul 2024 16:21:51 +0700 Subject: [PATCH 06/30] chore: update test contract deployment doc --- sui/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sui/README.md b/sui/README.md index 980e6521..d5d7a2c7 100644 --- a/sui/README.md +++ b/sui/README.md @@ -80,7 +80,7 @@ node sui/deploy-contract.js GasService Deploy the test GMP package: ```bash -node sui/deploy-test.js +node sui/deploy-contract.js Test ``` Call Contract: From 74fe6205a10bf2f1b7b11570caaf2624bad6f64b Mon Sep 17 00:00:00 2001 From: npty Date: Tue, 30 Jul 2024 16:19:40 +0700 Subject: [PATCH 07/30] chore: refactor commands in deploy script --- sui/cli-utils.js | 32 +++++++ sui/deploy.js | 240 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 272 insertions(+) create mode 100644 sui/deploy.js diff --git a/sui/cli-utils.js b/sui/cli-utils.js index d36d9712..d0aa40ab 100644 --- a/sui/cli-utils.js +++ b/sui/cli-utils.js @@ -52,6 +52,37 @@ const addExtendedOptions = (program, options = {}) => { return program; }; +const getDeployGatewayOptions = () => { + return [ + new Option('--signers ', 'JSON with the initial signer set').env('SIGNERS'), + new Option('--operator ', 'operator for the gateway (defaults to the deployer address)').env('OPERATOR'), + new Option('--minimumRotationDelay ', 'minium delay for signer rotations (in second)') + .argParser((val) => parseInt(val) * 1000) + .default(24 * 60 * 60), + new Option('--domainSeparator ', 'domain separator'), + new Option('--nonce ', 'nonce for the signer (defaults to HashZero)'), + new Option('--previousSigners ', 'number of previous signers to retain').default('15'), + new Option('--policy ', 'upgrade policy for upgrade cap: For example, use "any_upgrade" to allow all types of upgrades') + .choices(['any_upgrade', 'code_upgrade', 'dep_upgrade']) + .default('any_upgrade'), + ]; +}; + +const addDeployOptions = (program) => { + switch (program.name()) { + case 'AxelarGateway': + getDeployGatewayOptions().forEach((option) => program.addOption(option)); + break; + case 'GasService': + case 'Test': + break; + default: + throw new Error(`Unsupported package: ${program.name()}. `); + } + + return program; +}; + // `optionMethod` is a method such as `addBaseOptions` // `options` is an option object for optionMethod const addOptionsToCommands = (program, optionMethod, options) => { @@ -80,4 +111,5 @@ module.exports = { addExtendedOptions, addOptionsToCommands, parseSuiUnitAmount, + addDeployOptions, }; diff --git a/sui/deploy.js b/sui/deploy.js new file mode 100644 index 00000000..3a501b74 --- /dev/null +++ b/sui/deploy.js @@ -0,0 +1,240 @@ +const { Command, Option } = require('commander'); +const { updateMoveToml, TxBuilder } = require('@axelar-network/axelar-cgp-sui'); +const { ethers } = require('hardhat'); +const { toB64 } = require('@mysten/sui/utils'); +const { bcs } = require('@mysten/sui/bcs'); +const { Transaction } = require('@mysten/sui/transactions'); +const { + utils: { arrayify, hexlify, toUtf8Bytes, keccak256 }, + constants: { HashZero }, +} = ethers; +const { saveConfig, printInfo, validateParameters, writeJSON } = require('../evm/utils'); +const { addBaseOptions, addDeployOptions, addOptionsToCommands } = require('./cli-utils'); +const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); +const { loadSuiConfig, getAmplifierSigners, deployPackage, getObjectIdsByObjectTypes } = require('./utils'); +const { bytes32Struct, signersStruct } = require('./types-utils'); +const { upgradePackage } = require('./deploy-utils'); +const { suiPackageAddress, suiClockAddress, readMovePackageName } = require('./utils'); + +// A list of currently supported packages which are the folder names in `node_modules/@axelar-network/axelar-cgp-sui/move` +const supportedPackageDirs = ['gas_service', 'test', 'axelar_gateway']; + +// Map supported packages to their package names and directories +const supportedPackages = supportedPackageDirs.map((dir) => ({ + packageName: readMovePackageName(dir), + packageDir: dir, +})); + +async function getSigners(keypair, config, chain, options) { + if (options.signers === 'wallet') { + const pubKey = keypair.getPublicKey().toRawBytes(); + printInfo('Using wallet pubkey as the signer for the gateway', hexlify(pubKey)); + + if (keypair.getKeyScheme() !== 'Secp256k1') { + throw new Error('Only Secp256k1 pubkeys are supported by the gateway'); + } + + return { + signers: [{ pub_key: pubKey, weight: 1 }], + threshold: 1, + nonce: options.nonce ? keccak256(toUtf8Bytes(options.nonce)) : HashZero, + }; + } else if (options.signers) { + printInfo('Using provided signers', options.signers); + + const signers = JSON.parse(options.signers); + return { + signers: signers.signers.map(({ pub_key: pubKey, weight }) => { + return { pub_key: arrayify(pubKey), weight }; + }), + threshold: signers.threshold, + nonce: arrayify(signers.nonce) || HashZero, + }; + } + + return getAmplifierSigners(config, chain); +} + +async function deploy(keypair, client, contractName, config, chain, options) { + if (!chain.contracts[contractName]) { + chain.contracts[contractName] = {}; + } + + const { packageId, publishTxn } = await deployPackage(contractName, client, keypair, options); + + printInfo('Publish transaction digest: ', publishTxn.digest); + + const contractConfig = chain.contracts[contractName]; + contractConfig.address = packageId; + contractConfig.objects = {}; + + switch (contractName) { + case 'gas_service': { + const [GasService, GasCollectorCap] = getObjectIdsByObjectTypes(publishTxn, [ + `${packageId}::gas_service::GasService`, + `${packageId}::gas_service::GasCollectorCap`, + ]); + contractConfig.objects = { GasService, GasCollectorCap }; + break; + } + + case 'axelar_gateway': { + const { minimumRotationDelay, domainSeparator, policy, previousSigners } = options; + const operator = options.operator || keypair.toSuiAddress(); + const signers = await getSigners(keypair, config, chain, options); + + validateParameters({ isNonEmptyString: { previousSigners, minimumRotationDelay }, isKeccak256Hash: { domainSeparator } }); + + const [creatorCap, relayerDiscovery, upgradeCap] = getObjectIdsByObjectTypes(publishTxn, [ + `${packageId}::gateway::CreatorCap`, + `${packageId}::discovery::RelayerDiscovery`, + `${suiPackageAddress}::package::UpgradeCap`, + ]); + + const encodedSigners = signersStruct + .serialize({ + ...signers, + nonce: bytes32Struct.serialize(signers.nonce).toBytes(), + }) + .toBytes(); + + const tx = new Transaction(); + + const separator = tx.moveCall({ + target: `${packageId}::bytes32::new`, + arguments: [tx.pure(arrayify(domainSeparator))], + }); + + tx.moveCall({ + target: `${packageId}::gateway::setup`, + arguments: [ + tx.object(creatorCap), + tx.pure.address(operator), + separator, + tx.pure.u64(minimumRotationDelay), + tx.pure.u64(options.previousSigners), + tx.pure(bcs.vector(bcs.u8()).serialize(encodedSigners).toBytes()), + tx.object(suiClockAddress), + ], + }); + + if (policy !== 'any_upgrade') { + const upgradeType = policy === 'code_upgrade' ? 'only_additive_upgrades' : 'only_dep_upgrades'; + + tx.moveCall({ + target: `${suiPackageAddress}::package::${upgradeType}`, + arguments: [tx.object(upgradeCap)], + }); + } + + const result = await broadcast(client, keypair, tx); + + printInfo('Setup transaction digest', result.digest); + + const [gateway] = getObjectIdsByObjectTypes(result, [`${packageId}::gateway::Gateway`]); + + contractConfig.objects = { + gateway, + relayerDiscovery, + upgradeCap, + }; + contractConfig.domainSeparator = domainSeparator; + contractConfig.operator = operator; + contractConfig.minimumRotationDelay = minimumRotationDelay; + break; + } + + default: { + throw new Error(`${contractName} is not supported.`); + } + } + + printInfo(`${contractName} deployed`, JSON.stringify(chain.contracts[contractName], null, 2)); +} + +async function upgrade(keypair, client, contractName, policy, config, chain, options) { + const { packageDependencies } = options; + options.policy = policy; + + if (!chain.contracts[contractName]) { + throw new Error(`Cannot find specified contract: ${contractName}`); + } + + const contractsConfig = chain.contracts; + const packageConfig = contractsConfig?.[contractName]; + + validateParameters({ isNonEmptyString: { contractName } }); + + if (packageDependencies) { + for (const dependencies of packageDependencies) { + const packageId = contractsConfig[dependencies]?.address; + updateMoveToml(dependencies, packageId); + } + } + + const builder = new TxBuilder(client); + await upgradePackage(client, keypair, contractName, packageConfig, builder, options); +} + +async function mainProcessor(args, options, processor) { + const config = loadSuiConfig(options.env); + const [keypair, client] = getWallet(config.sui, options); + await printWalletInfo(keypair, client, config.sui, options); + await processor(keypair, client, ...args, config, config.sui, options); + saveConfig(config, options.env); + + if (options.offline) { + const { txFilePath } = options; + validateParameters({ isNonEmptyString: { txFilePath } }); + + const txB64Bytes = toB64(options.txBytes); + + writeJSON({ message: options.offlineMessage, status: 'PENDING', unsignedTx: txB64Bytes }, txFilePath); + printInfo(`Unsigned transaction`, txFilePath); + } +} + +if (require.main === module) { + // 1st level command + const program = new Command("deploy-contract").description('Deploy/Upgrade packages'); + + // 2nd level commands + const deployCmd = new Command('deploy') + const upgradeCmd = new Command('upgrade') + + // 3rd level commands + const deployContractCmds = supportedPackages.map(({ packageName }) => { + const command = new Command(packageName) + .description(`Deploy ${packageName} contract`) + + return addDeployOptions(command) + .action((options) => { + mainProcessor([packageName], options, deploy); + }) + }); + + // Add 3rd level commands to 2nd level command `deploy` + deployContractCmds.forEach((cmd) => deployCmd.addCommand(cmd)); + + // Add base options to all 2nd and 3rd level commands + addOptionsToCommands(deployCmd, addBaseOptions) + addBaseOptions(upgradeCmd) + + // Define options for 2nd level command `upgrade` + upgradeCmd + .description('Upgrade a Sui package') + .command('upgrade ') + .addOption(new Option('--sender ', 'transaction sender')) + .addOption(new Option('--digest ', 'digest hash for upgrade')) + .addOption(new Option('--offline', 'store tx block for sign')) + .addOption(new Option('--txFilePath ', 'unsigned transaction will be stored')) + .action((contractName, policy, options) => { + mainProcessor([contractName, policy], options, upgrade); + }); + + // Add 2nd level commands to 1st level command + program.addCommand(deployCmd); + program.addCommand(upgradeCmd); + + program.parse(); +} From c29a4fdf76df884917d18c5b3ee075a8b7a87448 Mon Sep 17 00:00:00 2001 From: npty Date: Tue, 30 Jul 2024 19:34:23 +0700 Subject: [PATCH 08/30] chore: use common utils --- common/utils.js | 51 +++++++++++++++++++++++++++++++++++++++++ sui/deploy-test.js | 2 +- sui/deploy-utils.js | 2 +- sui/faucet.js | 2 +- sui/gas-service.js | 2 +- sui/gateway.js | 2 +- sui/generate-keypair.js | 2 +- sui/gmp.js | 2 +- sui/multisig.js | 2 +- sui/sign-utils.js | 2 +- sui/transfer-object.js | 2 +- sui/utils.js | 4 +++- 12 files changed, 64 insertions(+), 11 deletions(-) diff --git a/common/utils.js b/common/utils.js index 2c2a85c2..db00f532 100644 --- a/common/utils.js +++ b/common/utils.js @@ -198,6 +198,24 @@ function timeout(prom, time, exception) { ); } +/** + * Determines if a given input is a valid keccak256 hash. + * + * @param {string} input - The string to validate. + * @returns {boolean} - Returns true if the input is a valid keccak256 hash, false otherwise. + */ +function isKeccak256Hash(input) { + // Ensure it's a string of 66 characters length and starts with '0x' + if (typeof input !== 'string' || input.length !== 66 || input.slice(0, 2) !== '0x') { + return false; + } + + // Ensure all characters after the '0x' prefix are hexadecimal (0-9, a-f, A-F) + const hexPattern = /^[a-fA-F0-9]{64}$/; + + return hexPattern.test(input.slice(2)); +} + /** * Validate if the input string matches the time format YYYY-MM-DDTHH:mm:ss * @@ -214,6 +232,37 @@ function isValidTimeFormat(timeString) { return regex.test(timeString); } +const validationFunctions = { + isNonEmptyString, + isNumber, + isValidNumber, + isValidDecimal, + isNumberArray, + isKeccak256Hash, + isString, + isNonEmptyStringArray, + isValidTimeFormat, +}; + +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}`); + } + } + } +} + const dateToEta = (utcTimeString) => { if (utcTimeString === '0') { return 0; @@ -290,6 +339,7 @@ module.exports = { printWarn, printError, printLog, + isKeccak256Hash, isNonEmptyString, isString, isStringArray, @@ -311,4 +361,5 @@ module.exports = { findProjectRoot, toBigNumberString, timeout, + validateParameters, }; diff --git a/sui/deploy-test.js b/sui/deploy-test.js index 3f8feb51..8b0db9e9 100644 --- a/sui/deploy-test.js +++ b/sui/deploy-test.js @@ -1,4 +1,4 @@ -const { saveConfig, prompt, printInfo } = require('../evm/utils'); +const { saveConfig, prompt, printInfo } = require('../common/utils'); const { Command } = require('commander'); const { loadSuiConfig, deployPackage, getBcsBytesByObjectId } = require('./utils'); const { singletonStruct } = require('./types-utils'); diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index fc46e5ff..3c594116 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -2,7 +2,7 @@ const { Command, Option } = require('commander'); const { TxBuilder, updateMoveToml } = require('@axelar-network/axelar-cgp-sui'); const { bcs } = require('@mysten/bcs'); const { fromB64, toB64 } = require('@mysten/bcs'); -const { saveConfig, printInfo, validateParameters, prompt, writeJSON } = require('../evm/utils'); +const { saveConfig, printInfo, validateParameters, prompt, writeJSON } = require('../common/utils'); const { addBaseOptions } = require('./cli-utils'); const { getWallet } = require('./sign-utils'); const { loadSuiConfig, getObjectIdsByObjectTypes, suiPackageAddress } = require('./utils'); diff --git a/sui/faucet.js b/sui/faucet.js index 47fbc171..0bdb52b5 100644 --- a/sui/faucet.js +++ b/sui/faucet.js @@ -4,7 +4,7 @@ const { addBaseOptions } = require('./cli-utils'); const { requestSuiFromFaucetV0, getFaucetHost } = require('@mysten/sui/faucet'); const { getWallet, printWalletInfo } = require('./sign-utils'); const { Command } = require('commander'); -const { saveConfig, loadConfig, printInfo } = require('../evm/utils'); +const { saveConfig, loadConfig, printInfo } = require('../common/utils'); async function processCommand(config, chain, options) { const [keypair, client] = getWallet(chain, options); diff --git a/sui/gas-service.js b/sui/gas-service.js index a3dc5a13..1a6ba290 100644 --- a/sui/gas-service.js +++ b/sui/gas-service.js @@ -1,4 +1,4 @@ -const { saveConfig, printInfo, printError } = require('../evm/utils'); +const { saveConfig, printInfo, printError } = require('../common/utils'); const { Command } = require('commander'); const { Transaction } = require('@mysten/sui/transactions'); const { bcs } = require('@mysten/sui/bcs'); diff --git a/sui/gateway.js b/sui/gateway.js index d49fa04d..a26ef9ec 100644 --- a/sui/gateway.js +++ b/sui/gateway.js @@ -1,4 +1,4 @@ -const { saveConfig, printInfo } = require('../evm/utils'); +const { saveConfig, printInfo } = require('../common/utils'); const { Command, Option } = require('commander'); const { Transaction } = require('@mysten/sui/transactions'); const { bcs } = require('@mysten/sui/bcs'); diff --git a/sui/generate-keypair.js b/sui/generate-keypair.js index 8d091570..e9a76322 100644 --- a/sui/generate-keypair.js +++ b/sui/generate-keypair.js @@ -3,7 +3,7 @@ const { addBaseOptions } = require('./cli-utils'); const { generateKeypair, getRawPrivateKey } = require('./sign-utils'); const { Command, Option } = require('commander'); -const { saveConfig, loadConfig, printInfo } = require('../evm/utils'); +const { saveConfig, loadConfig, printInfo } = require('../common/utils'); const { ethers } = require('hardhat'); const { hexlify } = ethers.utils; diff --git a/sui/gmp.js b/sui/gmp.js index 87730567..a1bf4a96 100644 --- a/sui/gmp.js +++ b/sui/gmp.js @@ -1,4 +1,4 @@ -const { saveConfig, printInfo } = require('../evm/utils'); +const { saveConfig, printInfo } = require('../common/utils'); const { Command } = require('commander'); const { Transaction } = require('@mysten/sui/transactions'); const { bcs } = require('@mysten/sui/bcs'); diff --git a/sui/multisig.js b/sui/multisig.js index 403bae2b..9d69414d 100644 --- a/sui/multisig.js +++ b/sui/multisig.js @@ -4,7 +4,7 @@ const { addBaseOptions } = require('./cli-utils'); const { getWallet, getMultisig, signTransactionBlockBytes, broadcastSignature } = require('./sign-utils'); const { getSignedTx, storeSignedTx } = require('../evm/sign-utils'); const { loadSuiConfig } = require('./utils'); -const { printInfo, validateParameters } = require('../evm/utils'); +const { printInfo, validateParameters } = require('../common/utils'); async function signTx(keypair, client, options) { const txFileData = getSignedTx(options.txBlockPath); diff --git a/sui/sign-utils.js b/sui/sign-utils.js index 1dfeb2b5..c8aeee4c 100644 --- a/sui/sign-utils.js +++ b/sui/sign-utils.js @@ -8,7 +8,7 @@ const { Secp256k1Keypair, Secp256k1PublicKey } = require('@mysten/sui/keypairs/s const { Secp256r1Keypair, Secp256r1PublicKey } = require('@mysten/sui/keypairs/secp256r1'); const { SuiClient, getFullnodeUrl } = require('@mysten/sui/client'); const { fromB64, fromHEX } = require('@mysten/bcs'); -const { printInfo } = require('../evm/utils'); +const { printInfo } = require('../common/utils'); const { ethers } = require('hardhat'); const { utils: { hexlify }, diff --git a/sui/transfer-object.js b/sui/transfer-object.js index abb27c42..1d58ee6f 100644 --- a/sui/transfer-object.js +++ b/sui/transfer-object.js @@ -1,6 +1,6 @@ const { Transaction } = require('@mysten/sui/transactions'); const { Command, Option } = require('commander'); -const { printInfo, validateParameters } = require('../evm/utils'); +const { printInfo, validateParameters } = require('../common/utils'); const { addExtendedOptions } = require('./cli-utils'); const { getWallet, printWalletInfo } = require('./sign-utils'); const { loadSuiConfig } = require('./utils'); diff --git a/sui/utils.js b/sui/utils.js index 2d9ee6e5..10db89f5 100644 --- a/sui/utils.js +++ b/sui/utils.js @@ -1,7 +1,7 @@ 'use strict'; const { ethers } = require('hardhat'); -const { loadConfig, printError } = require('../evm/utils'); +const { loadConfig, printError } = require('../common/utils'); const { BigNumber, utils: { arrayify, hexlify }, @@ -110,6 +110,8 @@ const getObjectIdsByObjectTypes = (txn, objectTypes) => if (!objectId) { throw new Error(`No object found for type: ${objectType}`); } + + return objectId }); module.exports = { From 5feec849810c89ce50f8cef93d6ce603faddb3e0 Mon Sep 17 00:00:00 2001 From: npty Date: Tue, 30 Jul 2024 20:34:40 +0700 Subject: [PATCH 09/30] chore: move getDomainSeparator to common --- common/utils.js | 131 ++++++++++++++++++++++++-------- evm/deploy-amplifier-gateway.js | 44 +---------- evm/utils.js | 11 ++- 3 files changed, 111 insertions(+), 75 deletions(-) diff --git a/common/utils.js b/common/utils.js index db00f532..71ce7284 100644 --- a/common/utils.js +++ b/common/utils.js @@ -7,6 +7,12 @@ const chalk = require('chalk'); const https = require('https'); const http = require('http'); const readlineSync = require('readline-sync'); +const { CosmWasmClient } = require('@cosmjs/cosmwasm-stargate'); +const { ethers } = require('hardhat'); +const { + utils: { keccak256, hexlify }, +} = ethers; +const { normalizeBech32 } = require('@cosmjs/encoding'); function loadConfig(env) { return require(`${__dirname}/../axelar-chains-config/info/${env}.json`); @@ -205,15 +211,15 @@ function timeout(prom, time, exception) { * @returns {boolean} - Returns true if the input is a valid keccak256 hash, false otherwise. */ function isKeccak256Hash(input) { - // Ensure it's a string of 66 characters length and starts with '0x' - if (typeof input !== 'string' || input.length !== 66 || input.slice(0, 2) !== '0x') { - return false; - } + // Ensure it's a string of 66 characters length and starts with '0x' + if (typeof input !== 'string' || input.length !== 66 || input.slice(0, 2) !== '0x') { + return false; + } - // Ensure all characters after the '0x' prefix are hexadecimal (0-9, a-f, A-F) - const hexPattern = /^[a-fA-F0-9]{64}$/; + // Ensure all characters after the '0x' prefix are hexadecimal (0-9, a-f, A-F) + const hexPattern = /^[a-fA-F0-9]{64}$/; - return hexPattern.test(input.slice(2)); + return hexPattern.test(input.slice(2)); } /** @@ -233,34 +239,34 @@ function isValidTimeFormat(timeString) { } const validationFunctions = { - isNonEmptyString, - isNumber, - isValidNumber, - isValidDecimal, - isNumberArray, - isKeccak256Hash, - isString, - isNonEmptyStringArray, - isValidTimeFormat, + isNonEmptyString, + isNumber, + isValidNumber, + isValidDecimal, + isNumberArray, + isKeccak256Hash, + isString, + isNonEmptyStringArray, + isValidTimeFormat, }; 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}`); - } - } - } + 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}`); + } + } + } } const dateToEta = (utcTimeString) => { @@ -331,6 +337,66 @@ function toBigNumberString(number) { return Math.ceil(number).toLocaleString('en', { useGrouping: false }); } +const isValidCosmosAddress = (str) => { + try { + normalizeBech32(str); + + return true; + } catch (error) { + return false; + } +}; + +async function getDomainSeparator(config, chain, options) { + // Allow any domain separator for local deployments or `0x` if not provided + if (options.env === 'local') { + return options.domainSeparator || ethers.constants.HashZero; + } + + if (isKeccak256Hash(options.domainSeparator)) { + // return the domainSeparator for debug deployments + return options.domainSeparator; + } + + const { + axelar: { contracts, chainId }, + } = config; + const { + Router: { address: routerAddress }, + } = contracts; + + if (!isString(chain.axelarId)) { + throw new Error(`missing or invalid axelar ID for chain ${chain.name}`); + } + + if (!isString(routerAddress) || !isValidCosmosAddress(routerAddress)) { + throw new Error(`missing or invalid router address`); + } + + if (!isString(chainId)) { + throw new Error(`missing or invalid chain ID`); + } + + printInfo(`Retrieving domain separator for ${chain.name} from Axelar network`); + const domainSeparator = hexlify((await getContractConfig(config, chain.axelarId)).domain_separator); + const expectedDomainSeparator = calculateDomainSeparator(chain.axelarId, routerAddress, chainId); + + if (domainSeparator !== expectedDomainSeparator) { + throw new Error(`unexpected domain separator (want ${expectedDomainSeparator}, got ${domainSeparator})`); + } + + return domainSeparator; +} + +const getContractConfig = async (config, chain) => { + const key = Buffer.from('config'); + const client = await CosmWasmClient.connect(config.axelar.rpc); + const value = await client.queryContractRaw(config.axelar.contracts.MultisigProver[chain].address, key); + return JSON.parse(Buffer.from(value).toString('ascii')); +}; + +const calculateDomainSeparator = (chain, router, network) => keccak256(Buffer.from(`${chain}${router}${network}`)); + module.exports = { loadConfig, saveConfig, @@ -362,4 +428,5 @@ module.exports = { toBigNumberString, timeout, validateParameters, + getDomainSeparator, }; diff --git a/evm/deploy-amplifier-gateway.js b/evm/deploy-amplifier-gateway.js index a0b0bb10..419dbfff 100644 --- a/evm/deploy-amplifier-gateway.js +++ b/evm/deploy-amplifier-gateway.js @@ -7,7 +7,7 @@ const { ContractFactory, Contract, Wallet, - utils: { defaultAbiCoder, keccak256, hexlify }, + utils: { defaultAbiCoder, keccak256 }, getDefaultProvider, } = ethers; @@ -22,15 +22,12 @@ const { mainProcessor, deployContract, getGasOptions, - isKeccak256Hash, - getContractConfig, - isString, getWeightedSigners, getContractJSON, getDeployedAddress, getDeployOptions, + getDomainSeparator, } = require('./utils'); -const { calculateDomainSeparator, isValidCosmosAddress } = require('../cosmwasm/utils'); const { addExtendedOptions } = require('./cli-utils'); const { storeSignedTx, signTransaction, getWallet } = require('./sign-utils.js'); @@ -38,43 +35,6 @@ const { WEIGHTED_SIGNERS_TYPE, encodeWeightedSigners } = require('@axelar-networ const AxelarAmplifierGatewayProxy = require('@axelar-network/axelar-gmp-sdk-solidity/artifacts/contracts/gateway/AxelarAmplifierGatewayProxy.sol/AxelarAmplifierGatewayProxy.json'); const AxelarAmplifierGateway = require('@axelar-network/axelar-gmp-sdk-solidity/artifacts/contracts/gateway/AxelarAmplifierGateway.sol/AxelarAmplifierGateway.json'); -async function getDomainSeparator(config, chain, options) { - printInfo(`Retrieving domain separator for ${chain.name} from Axelar network`); - - if (isKeccak256Hash(options.domainSeparator)) { - // return the domainSeparator for debug deployments - return options.domainSeparator; - } - - const { - axelar: { contracts, chainId }, - } = config; - const { - Router: { address: routerAddress }, - } = contracts; - - if (!isString(chain.axelarId)) { - throw new Error(`missing or invalid axelar ID for chain ${chain.name}`); - } - - if (!isString(routerAddress) || !isValidCosmosAddress(routerAddress)) { - throw new Error(`missing or invalid router address`); - } - - if (!isString(chainId)) { - throw new Error(`missing or invalid chain ID`); - } - - const domainSeparator = hexlify((await getContractConfig(config, chain.axelarId)).domain_separator); - const expectedDomainSeparator = calculateDomainSeparator(chain.axelarId, routerAddress, chainId); - - if (domainSeparator !== expectedDomainSeparator) { - throw new Error(`unexpected domain separator (want ${expectedDomainSeparator}, got ${domainSeparator})`); - } - - return domainSeparator; -} - async function getSetupParams(config, chain, operator, options) { const { signers: signerSets, verifierSetId } = await getWeightedSigners(config, chain, options); printInfo('Setup params', JSON.stringify([operator, signerSets], null, 2)); diff --git a/evm/utils.js b/evm/utils.js index d9e8c058..34d2b992 100644 --- a/evm/utils.js +++ b/evm/utils.js @@ -4,7 +4,16 @@ const { ethers } = require('hardhat'); const { ContractFactory, Contract, - utils: { computeAddress, getContractAddress, keccak256, isAddress, getCreate2Address, defaultAbiCoder, isHexString, hexZeroPad }, + utils: { + computeAddress, + getContractAddress, + keccak256, + isAddress, + getCreate2Address, + defaultAbiCoder, + isHexString, + hexZeroPad, + }, constants: { AddressZero, HashZero }, getDefaultProvider, BigNumber, From 248195897827beab6a9595c06304e354dd30e4e6 Mon Sep 17 00:00:00 2001 From: npty Date: Tue, 30 Jul 2024 20:35:16 +0700 Subject: [PATCH 10/30] fix: all contracts deployment --- sui/deploy-contract-bak.js | 122 --------------- sui/deploy-contract.js | 311 ++++++++++++++++++++++--------------- sui/deploy-test.js | 75 --------- sui/deploy.js | 240 ---------------------------- sui/utils.js | 2 +- 5 files changed, 189 insertions(+), 561 deletions(-) delete mode 100644 sui/deploy-contract-bak.js delete mode 100644 sui/deploy-test.js delete mode 100644 sui/deploy.js diff --git a/sui/deploy-contract-bak.js b/sui/deploy-contract-bak.js deleted file mode 100644 index fd283f6a..00000000 --- a/sui/deploy-contract-bak.js +++ /dev/null @@ -1,122 +0,0 @@ -const { saveConfig, printInfo } = require('../evm/utils'); -const { Command, Argument } = require('commander'); -const { addBaseOptions } = require('./cli-utils'); -const { Transaction } = require('@mysten/sui/transactions'); -const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); -const { singletonStruct } = require('./types-utils'); -const { loadSuiConfig, findPublishedObject, deployPackage, getBcsBytesByObjectId, readMovePackageName } = require('./utils'); - -// A list of currently supported packages which are the folder names in `node_modules/@axelar-network/axelar-cgp-sui/move` -const supportedPackageDirs = ['gas_service', 'test']; - -// Map supported packages to their package names and directories -const supportedPackages = supportedPackageDirs.map((dir) => ({ - packageName: readMovePackageName(dir), - packageDir: dir, -})); - -// Parse bcs bytes from singleton object to get channel id -async function getChannelId(client, singletonObjectId) { - const bcsBytes = await getBcsBytesByObjectId(client, singletonObjectId); - const data = singletonStruct.parse(bcsBytes); - return '0x' + data.channel.id; -} - -/** ######## Post Deployment Functions ######## **/ -// Define the post deployment functions for each supported package here. These functions should be called after the package is deployed. -// Use cases include: -// 1. Update the chain config with deployed object ids -// 2. Submit additional transactions to setup the contracts. - -async function postDeployGasService(published, chain) { - chain.contracts.GasService.objects = { - GasCollectorCap: findPublishedObject(published, 'gas_service', 'GasCollectorCap').objectId, - GasService: findPublishedObject(published, 'gas_service', 'GasService').objectId, - }; -} - -async function postDeployTest(published, config, chain, options) { - const [keypair, client] = getWallet(chain, options); - const relayerDiscovery = config.sui.contracts.axelar_gateway?.objects?.relayerDiscovery; - - const singleton = findPublishedObject(published, 'test', 'Singleton'); - const channelId = await getChannelId(client, singleton.objectId); - chain.contracts.Test.objects = { singleton: singleton.objectId, channelId }; - - const tx = new Transaction(); - tx.moveCall({ - target: `${published.packageId}::test::register_transaction`, - arguments: [tx.object(relayerDiscovery), tx.object(singleton.objectId)], - }); - - const registerTx = await broadcast(client, keypair, tx); - - printInfo('Register transaction', registerTx.digest); -} - -/** ######## Main Processor ######## **/ - -async function processCommand(supportedContract, config, chain, options) { - const { packageDir, packageName } = supportedContract; - - const [keypair, client] = getWallet(chain, options); - - await printWalletInfo(keypair, client, chain, options); - - if (!chain.contracts[packageName]) { - chain.contracts[packageName] = {}; - } - - const published = await deployPackage(packageDir, client, keypair); - - // Submitting additional setup transaction or saving additional objects to the chain config here - switch (packageName) { - case 'GasService': - await postDeployGasService(published, chain); - break; - case 'Test': - await postDeployTest(published, config, chain, options); - break; - default: - throw new Error(`Unsupported package: ${packageName}`); - } - - printInfo(`${packageName} deployed`, JSON.stringify(chain.contracts[packageName], null, 2)); -} - -async function mainProcessor(supportedContract, options, processor) { - const config = loadSuiConfig(options.env); - await processor(supportedContract, config, config.sui, options); - saveConfig(config, options.env); -} - -if (require.main === module) { - const program = new Command(); - - program - .name('deploy-contract') - .addArgument( - new Argument('', 'Contract name to deploy').argParser((packageName) => { - const supportedPackage = supportedPackages.find((p) => p.packageName === packageName); - - if (!supportedPackage) { - throw new Error( - `Unsupported package: ${packageName}. Supported packages: ${supportedPackages - .map((p) => p.packageName) - .join(', ')}`, - ); - } - - return supportedPackage; - }), - ) - .description('Deploy SUI modules'); - - addBaseOptions(program); - - program.action((supportedContract, options) => { - mainProcessor(supportedContract, options, processCommand); - }); - - program.parse(); -} diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index a853959f..ceb69075 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -8,13 +8,145 @@ const { utils: { arrayify, hexlify, toUtf8Bytes, keccak256 }, constants: { HashZero }, } = ethers; -const { saveConfig, printInfo, validateParameters, writeJSON } = require('../evm/utils'); -const { addBaseOptions } = require('./cli-utils'); +const { saveConfig, printInfo, validateParameters, writeJSON, getDomainSeparator } = require('../common'); +const { addBaseOptions, addDeployOptions, addOptionsToCommands } = require('./cli-utils'); const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); -const { loadSuiConfig, getAmplifierSigners, deployPackage, getObjectIdsByObjectTypes } = require('./utils'); -const { bytes32Struct, signersStruct } = require('./types-utils'); +const { bytes32Struct, signersStruct, singletonStruct } = require('./types-utils'); const { upgradePackage } = require('./deploy-utils'); -const { suiPackageAddress, suiClockAddress } = require('./utils'); +const { + loadSuiConfig, + getAmplifierSigners, + deployPackage, + getObjectIdsByObjectTypes, + suiPackageAddress, + suiClockAddress, + readMovePackageName, + getBcsBytesByObjectId, +} = require('./utils'); + +// A list of currently supported packages which are the folder names in `node_modules/@axelar-network/axelar-cgp-sui/move` +const supportedPackageDirs = ['gas_service', 'test', 'axelar_gateway']; + +// Map supported packages to their package names and directories +const supportedPackages = supportedPackageDirs.map((dir) => ({ + packageName: readMovePackageName(dir), + packageDir: dir, +})); + +// Parse bcs bytes from singleton object to get channel id +async function getChannelId(client, singletonObjectId) { + const bcsBytes = await getBcsBytesByObjectId(client, singletonObjectId); + const data = singletonStruct.parse(bcsBytes); + return '0x' + data.channel.id; +} + +/** ######## Post Deployment Functions ######## **/ +// Define the post deployment functions for each supported package here. These functions should be called after the package is deployed. +// Use cases include: +// 1. Update the chain config with deployed object ids +// 2. Submit additional transactions to setup the contracts. + +async function postDeployGasService(published, chain) { + const [gasCollectorCapObjectId, gasServiceObjectId] = getObjectIdsByObjectTypes(published.publishTxn, [ + `${published.packageId}::gas_service::GasCollectorCap`, + `${published.packageId}::gas_service::GasService`, + ]); + chain.contracts.GasService.objects = { + GasCollectorCap: gasCollectorCapObjectId, + GasService: gasServiceObjectId, + }; +} + +async function postDeployTest(published, config, chain, options) { + const [keypair, client] = getWallet(chain, options); + const relayerDiscovery = config.sui.contracts.AxelarGateway?.objects?.RelayerDiscovery; + + const [singletonObjectId] = getObjectIdsByObjectTypes(published.publishTxn, [`${published.packageId}::test::Singleton`]); + const channelId = await getChannelId(client, singletonObjectId); + chain.contracts.Test.objects = { Singleton: singletonObjectId, ChannelId: channelId }; + + const tx = new Transaction(); + tx.moveCall({ + target: `${published.packageId}::test::register_transaction`, + arguments: [tx.object(relayerDiscovery), tx.object(singletonObjectId)], + }); + + const registerTx = await broadcast(client, keypair, tx); + + printInfo('Register transaction', registerTx.digest); +} + +async function postDeployAxelarGateway(published, keypair, client, config, chain, options) { + const { packageId, publishTxn } = published; + const { minimumRotationDelay, policy, previousSigners } = options; + const operator = options.operator || keypair.toSuiAddress(); + const signers = await getSigners(keypair, config, chain, options); + const domainSeparator = await getDomainSeparator(config, chain, options); + + validateParameters({ + isNonEmptyString: { previousSigners }, + isValidNumber: { minimumRotationDelay }, + }); + + const [creatorCap, relayerDiscovery, upgradeCap] = getObjectIdsByObjectTypes(publishTxn, [ + `${packageId}::gateway::CreatorCap`, + `${packageId}::discovery::RelayerDiscovery`, + `${suiPackageAddress}::package::UpgradeCap`, + ]); + + const encodedSigners = signersStruct + .serialize({ + ...signers, + nonce: bytes32Struct.serialize(signers.nonce).toBytes(), + }) + .toBytes(); + + const tx = new Transaction(); + + const separator = tx.moveCall({ + target: `${packageId}::bytes32::new`, + arguments: [tx.pure(arrayify(domainSeparator))], + }); + + tx.moveCall({ + target: `${packageId}::gateway::setup`, + arguments: [ + tx.object(creatorCap), + tx.pure.address(operator), + separator, + tx.pure.u64(minimumRotationDelay), + tx.pure.u64(options.previousSigners), + tx.pure(bcs.vector(bcs.u8()).serialize(encodedSigners).toBytes()), + tx.object(suiClockAddress), + ], + }); + + if (policy !== 'any_upgrade') { + const upgradeType = policy === 'code_upgrade' ? 'only_additive_upgrades' : 'only_dep_upgrades'; + + tx.moveCall({ + target: `${suiPackageAddress}::package::${upgradeType}`, + arguments: [tx.object(upgradeCap)], + }); + } + + const result = await broadcast(client, keypair, tx); + + printInfo('Setup transaction digest', result.digest); + + const [gateway] = getObjectIdsByObjectTypes(result, [`${packageId}::gateway::Gateway`]); + + const contractConfig = chain.contracts.AxelarGateway; + + contractConfig.objects = { + Gateway: gateway, + RelayerDiscovery: relayerDiscovery, + UpgradeCap: upgradeCap, + }; + contractConfig.domainSeparator = domainSeparator; + contractConfig.operator = operator; + contractConfig.minimumRotationDelay = minimumRotationDelay; +} async function getSigners(keypair, config, chain, options) { if (options.signers === 'wallet') { @@ -46,101 +178,36 @@ async function getSigners(keypair, config, chain, options) { return getAmplifierSigners(config, chain); } -async function deploy(keypair, client, contractName, config, chain, options) { - if (!chain.contracts[contractName]) { - chain.contracts[contractName] = {}; +async function deploy(keypair, client, supportedContract, config, chain, options) { + const { packageDir, packageName } = supportedContract; + + if (!chain.contracts[packageName]) { + chain.contracts[packageName] = {}; } - const { packageId, publishTxn } = await deployPackage(contractName, client, keypair, options); + const published = await deployPackage(packageDir, client, keypair, options); - printInfo('Publish transaction digest: ', publishTxn.digest); + printInfo(`Deployed ${packageName}`, published.publishTxn.digest); - const contractConfig = chain.contracts[contractName]; - contractConfig.address = packageId; - contractConfig.objects = {}; + if (!chain.contracts[packageName]) { + chain.contracts[packageName] = {}; + } - switch (contractName) { - case 'gas_service': { - const [GasService, GasCollectorCap] = getObjectIdsByObjectTypes(publishTxn, [ - `${packageId}::gas_service::GasService`, - `${packageId}::gas_service::GasCollectorCap`, - ]); - contractConfig.objects = { GasService, GasCollectorCap }; + switch (packageName) { + case 'GasService': + await postDeployGasService(published, chain); break; - } - - case 'axelar_gateway': { - const { minimumRotationDelay, domainSeparator, policy, previousSigners } = options; - const operator = options.operator || keypair.toSuiAddress(); - const signers = await getSigners(keypair, config, chain, options); - - validateParameters({ isNonEmptyString: { previousSigners, minimumRotationDelay }, isKeccak256Hash: { domainSeparator } }); - - const [creatorCap, relayerDiscovery, upgradeCap] = getObjectIdsByObjectTypes(publishTxn, [ - `${packageId}::gateway::CreatorCap`, - `${packageId}::discovery::RelayerDiscovery`, - `${suiPackageAddress}::package::UpgradeCap`, - ]); - - const encodedSigners = signersStruct - .serialize({ - ...signers, - nonce: bytes32Struct.serialize(signers.nonce).toBytes(), - }) - .toBytes(); - - const tx = new Transaction(); - - const separator = tx.moveCall({ - target: `${packageId}::bytes32::new`, - arguments: [tx.pure(arrayify(domainSeparator))], - }); - - tx.moveCall({ - target: `${packageId}::gateway::setup`, - arguments: [ - tx.object(creatorCap), - tx.pure.address(operator), - separator, - tx.pure.u64(minimumRotationDelay), - tx.pure.u64(options.previousSigners), - tx.pure(bcs.vector(bcs.u8()).serialize(encodedSigners).toBytes()), - tx.object(suiClockAddress), - ], - }); - - if (policy !== 'any_upgrade') { - const upgradeType = policy === 'code_upgrade' ? 'only_additive_upgrades' : 'only_dep_upgrades'; - - tx.moveCall({ - target: `${suiPackageAddress}::package::${upgradeType}`, - arguments: [tx.object(upgradeCap)], - }); - } - - const result = await broadcast(client, keypair, tx); - - printInfo('Setup transaction digest', result.digest); - - const [gateway] = getObjectIdsByObjectTypes(result, [`${packageId}::gateway::Gateway`]); - - contractConfig.objects = { - gateway, - relayerDiscovery, - upgradeCap, - }; - contractConfig.domainSeparator = domainSeparator; - contractConfig.operator = operator; - contractConfig.minimumRotationDelay = minimumRotationDelay; + case 'AxelarGateway': + await postDeployAxelarGateway(published, keypair, client, config, chain, options); break; - } - - default: { - throw new Error(`${contractName} is not supported.`); - } + case 'Test': + await postDeployTest(published, config, chain, options); + break; + default: + throw new Error(`${packageName} is not supported.`); } - printInfo(`${contractName} deployed`, JSON.stringify(chain.contracts[contractName], null, 2)); + printInfo(`${packageName} deployed`, JSON.stringify(chain.contracts[packageName], null, 2)); } async function upgrade(keypair, client, contractName, policy, config, chain, options) { @@ -186,47 +253,45 @@ async function mainProcessor(args, options, processor) { } if (require.main === module) { - const program = new Command(); - - program.name('deploy-contract').description('Deploy/Upgrade packages'); - - const deployCmd = program - .name('deploy') - .description('Deploy a Sui package') - .command('deploy ') - .addOption(new Option('--signers ', 'JSON with the initial signer set').env('SIGNERS')) - .addOption(new Option('--operator ', 'operator for the gateway (defaults to the deployer address)').env('OPERATOR')) - .addOption( - new Option('--minimumRotationDelay ', 'minium delay for signer rotations (in second)') - .default(24 * 60 * 60) - .parseArg((val) => parseInt(val) * 1000), - ) - .addOption(new Option('--domainSeparator ', 'domain separator')) - .addOption(new Option('--nonce ', 'nonce for the signer (defaults to HashZero)')) - .addOption(new Option('--previousSigners ', 'number of previous signers to retain').default('15')) - .addOption( - new Option('--policy ', 'upgrade policy for upgrade cap: For example, use "any_upgrade" to allow all types of upgrades') - .choices(['any_upgrade', 'code_upgrade', 'dep_upgrade']) - .default('any_upgrade'), - ) - .action((contractName, options) => { - mainProcessor([contractName], options, deploy); + // 1st level command + const program = new Command('deploy-contract').description('Deploy/Upgrade packages'); + + // 2nd level commands + const deployCmd = new Command('deploy'); + const upgradeCmd = new Command('upgrade'); + + // 3rd level commands + const deployContractCmds = supportedPackages.map((supportedPackage) => { + const { packageName } = supportedPackage; + const command = new Command(packageName).description(`Deploy ${packageName} contract`); + + return addDeployOptions(command).action((options) => { + mainProcessor([supportedPackage], options, deploy); }); + }); - const upgradeCmd = program - .name('upgrade') + // Add 3rd level commands to 2nd level command `deploy` + deployContractCmds.forEach((cmd) => deployCmd.addCommand(cmd)); + + // Add base options to all 2nd and 3rd level commands + addOptionsToCommands(deployCmd, addBaseOptions); + addBaseOptions(upgradeCmd); + + // Define options for 2nd level command `upgrade` + upgradeCmd .description('Upgrade a Sui package') - .command('upgrade ') + .command('upgrade ') .addOption(new Option('--sender ', 'transaction sender')) .addOption(new Option('--digest ', 'digest hash for upgrade')) .addOption(new Option('--offline', 'store tx block for sign')) .addOption(new Option('--txFilePath ', 'unsigned transaction will be stored')) - .action((contractName, policy, options) => { - mainProcessor([contractName, policy], options, upgrade); + .action((packageName, policy, options) => { + mainProcessor([packageName, policy], options, upgrade); }); - addBaseOptions(deployCmd); - addBaseOptions(upgradeCmd); + // Add 2nd level commands to 1st level command + program.addCommand(deployCmd); + program.addCommand(upgradeCmd); program.parse(); } diff --git a/sui/deploy-test.js b/sui/deploy-test.js deleted file mode 100644 index 8b0db9e9..00000000 --- a/sui/deploy-test.js +++ /dev/null @@ -1,75 +0,0 @@ -const { saveConfig, prompt, printInfo } = require('../common/utils'); -const { Command } = require('commander'); -const { loadSuiConfig, deployPackage, getBcsBytesByObjectId } = require('./utils'); -const { singletonStruct } = require('./types-utils'); -const { Transaction } = require('@mysten/sui/transactions'); -const { addBaseOptions } = require('./cli-utils'); -const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); - -// Parse bcs bytes from singleton object to get channel id -async function getChannelId(client, singletonObjectId) { - const bcsBytes = await getBcsBytesByObjectId(client, singletonObjectId); - const data = singletonStruct.parse(bcsBytes); - return '0x' + data.channel.id; -} - -async function processCommand(config, chain, options) { - const [keypair, client] = getWallet(chain, options); - - await printWalletInfo(keypair, client, chain, options); - - if (!chain.contracts.test) { - chain.contracts.test = {}; - } - - const relayerDiscovery = config.sui.contracts.axelar_gateway?.objects?.relayerDiscovery; - - if (!relayerDiscovery) { - throw new Error('Relayer discovery object not found'); - } - - if (prompt(`Proceed with deployment on ${chain.name}?`, options.yes)) { - return; - } - - const published = await deployPackage('test', client, keypair); - - const singleton = published.publishTxn.objectChanges.find((change) => change.objectType === `${published.packageId}::test::Singleton`); - - const tx = new Transaction(); - - tx.moveCall({ - target: `${published.packageId}::test::register_transaction`, - arguments: [tx.object(relayerDiscovery), tx.object(singleton.objectId)], - }); - - await broadcast(client, keypair, tx); - - const channelId = await getChannelId(client, singleton.objectId); - - chain.contracts.test.address = published.packageId; - chain.contracts.test.objects = { singleton: singleton.objectId, channelId }; - - printInfo('Test package deployed', JSON.stringify(chain.contracts.test, null, 2)); -} - -async function mainProcessor(options, processor) { - const config = loadSuiConfig(options.env); - - await processor(config, config.sui, options); - saveConfig(config, options.env); -} - -if (require.main === module) { - const program = new Command(); - - program.name('deploy-test').description('Deploys/publishes the test module'); - - addBaseOptions(program); - - program.action((options) => { - mainProcessor(options, processCommand); - }); - - program.parse(); -} diff --git a/sui/deploy.js b/sui/deploy.js deleted file mode 100644 index 3a501b74..00000000 --- a/sui/deploy.js +++ /dev/null @@ -1,240 +0,0 @@ -const { Command, Option } = require('commander'); -const { updateMoveToml, TxBuilder } = require('@axelar-network/axelar-cgp-sui'); -const { ethers } = require('hardhat'); -const { toB64 } = require('@mysten/sui/utils'); -const { bcs } = require('@mysten/sui/bcs'); -const { Transaction } = require('@mysten/sui/transactions'); -const { - utils: { arrayify, hexlify, toUtf8Bytes, keccak256 }, - constants: { HashZero }, -} = ethers; -const { saveConfig, printInfo, validateParameters, writeJSON } = require('../evm/utils'); -const { addBaseOptions, addDeployOptions, addOptionsToCommands } = require('./cli-utils'); -const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); -const { loadSuiConfig, getAmplifierSigners, deployPackage, getObjectIdsByObjectTypes } = require('./utils'); -const { bytes32Struct, signersStruct } = require('./types-utils'); -const { upgradePackage } = require('./deploy-utils'); -const { suiPackageAddress, suiClockAddress, readMovePackageName } = require('./utils'); - -// A list of currently supported packages which are the folder names in `node_modules/@axelar-network/axelar-cgp-sui/move` -const supportedPackageDirs = ['gas_service', 'test', 'axelar_gateway']; - -// Map supported packages to their package names and directories -const supportedPackages = supportedPackageDirs.map((dir) => ({ - packageName: readMovePackageName(dir), - packageDir: dir, -})); - -async function getSigners(keypair, config, chain, options) { - if (options.signers === 'wallet') { - const pubKey = keypair.getPublicKey().toRawBytes(); - printInfo('Using wallet pubkey as the signer for the gateway', hexlify(pubKey)); - - if (keypair.getKeyScheme() !== 'Secp256k1') { - throw new Error('Only Secp256k1 pubkeys are supported by the gateway'); - } - - return { - signers: [{ pub_key: pubKey, weight: 1 }], - threshold: 1, - nonce: options.nonce ? keccak256(toUtf8Bytes(options.nonce)) : HashZero, - }; - } else if (options.signers) { - printInfo('Using provided signers', options.signers); - - const signers = JSON.parse(options.signers); - return { - signers: signers.signers.map(({ pub_key: pubKey, weight }) => { - return { pub_key: arrayify(pubKey), weight }; - }), - threshold: signers.threshold, - nonce: arrayify(signers.nonce) || HashZero, - }; - } - - return getAmplifierSigners(config, chain); -} - -async function deploy(keypair, client, contractName, config, chain, options) { - if (!chain.contracts[contractName]) { - chain.contracts[contractName] = {}; - } - - const { packageId, publishTxn } = await deployPackage(contractName, client, keypair, options); - - printInfo('Publish transaction digest: ', publishTxn.digest); - - const contractConfig = chain.contracts[contractName]; - contractConfig.address = packageId; - contractConfig.objects = {}; - - switch (contractName) { - case 'gas_service': { - const [GasService, GasCollectorCap] = getObjectIdsByObjectTypes(publishTxn, [ - `${packageId}::gas_service::GasService`, - `${packageId}::gas_service::GasCollectorCap`, - ]); - contractConfig.objects = { GasService, GasCollectorCap }; - break; - } - - case 'axelar_gateway': { - const { minimumRotationDelay, domainSeparator, policy, previousSigners } = options; - const operator = options.operator || keypair.toSuiAddress(); - const signers = await getSigners(keypair, config, chain, options); - - validateParameters({ isNonEmptyString: { previousSigners, minimumRotationDelay }, isKeccak256Hash: { domainSeparator } }); - - const [creatorCap, relayerDiscovery, upgradeCap] = getObjectIdsByObjectTypes(publishTxn, [ - `${packageId}::gateway::CreatorCap`, - `${packageId}::discovery::RelayerDiscovery`, - `${suiPackageAddress}::package::UpgradeCap`, - ]); - - const encodedSigners = signersStruct - .serialize({ - ...signers, - nonce: bytes32Struct.serialize(signers.nonce).toBytes(), - }) - .toBytes(); - - const tx = new Transaction(); - - const separator = tx.moveCall({ - target: `${packageId}::bytes32::new`, - arguments: [tx.pure(arrayify(domainSeparator))], - }); - - tx.moveCall({ - target: `${packageId}::gateway::setup`, - arguments: [ - tx.object(creatorCap), - tx.pure.address(operator), - separator, - tx.pure.u64(minimumRotationDelay), - tx.pure.u64(options.previousSigners), - tx.pure(bcs.vector(bcs.u8()).serialize(encodedSigners).toBytes()), - tx.object(suiClockAddress), - ], - }); - - if (policy !== 'any_upgrade') { - const upgradeType = policy === 'code_upgrade' ? 'only_additive_upgrades' : 'only_dep_upgrades'; - - tx.moveCall({ - target: `${suiPackageAddress}::package::${upgradeType}`, - arguments: [tx.object(upgradeCap)], - }); - } - - const result = await broadcast(client, keypair, tx); - - printInfo('Setup transaction digest', result.digest); - - const [gateway] = getObjectIdsByObjectTypes(result, [`${packageId}::gateway::Gateway`]); - - contractConfig.objects = { - gateway, - relayerDiscovery, - upgradeCap, - }; - contractConfig.domainSeparator = domainSeparator; - contractConfig.operator = operator; - contractConfig.minimumRotationDelay = minimumRotationDelay; - break; - } - - default: { - throw new Error(`${contractName} is not supported.`); - } - } - - printInfo(`${contractName} deployed`, JSON.stringify(chain.contracts[contractName], null, 2)); -} - -async function upgrade(keypair, client, contractName, policy, config, chain, options) { - const { packageDependencies } = options; - options.policy = policy; - - if (!chain.contracts[contractName]) { - throw new Error(`Cannot find specified contract: ${contractName}`); - } - - const contractsConfig = chain.contracts; - const packageConfig = contractsConfig?.[contractName]; - - validateParameters({ isNonEmptyString: { contractName } }); - - if (packageDependencies) { - for (const dependencies of packageDependencies) { - const packageId = contractsConfig[dependencies]?.address; - updateMoveToml(dependencies, packageId); - } - } - - const builder = new TxBuilder(client); - await upgradePackage(client, keypair, contractName, packageConfig, builder, options); -} - -async function mainProcessor(args, options, processor) { - const config = loadSuiConfig(options.env); - const [keypair, client] = getWallet(config.sui, options); - await printWalletInfo(keypair, client, config.sui, options); - await processor(keypair, client, ...args, config, config.sui, options); - saveConfig(config, options.env); - - if (options.offline) { - const { txFilePath } = options; - validateParameters({ isNonEmptyString: { txFilePath } }); - - const txB64Bytes = toB64(options.txBytes); - - writeJSON({ message: options.offlineMessage, status: 'PENDING', unsignedTx: txB64Bytes }, txFilePath); - printInfo(`Unsigned transaction`, txFilePath); - } -} - -if (require.main === module) { - // 1st level command - const program = new Command("deploy-contract").description('Deploy/Upgrade packages'); - - // 2nd level commands - const deployCmd = new Command('deploy') - const upgradeCmd = new Command('upgrade') - - // 3rd level commands - const deployContractCmds = supportedPackages.map(({ packageName }) => { - const command = new Command(packageName) - .description(`Deploy ${packageName} contract`) - - return addDeployOptions(command) - .action((options) => { - mainProcessor([packageName], options, deploy); - }) - }); - - // Add 3rd level commands to 2nd level command `deploy` - deployContractCmds.forEach((cmd) => deployCmd.addCommand(cmd)); - - // Add base options to all 2nd and 3rd level commands - addOptionsToCommands(deployCmd, addBaseOptions) - addBaseOptions(upgradeCmd) - - // Define options for 2nd level command `upgrade` - upgradeCmd - .description('Upgrade a Sui package') - .command('upgrade ') - .addOption(new Option('--sender ', 'transaction sender')) - .addOption(new Option('--digest ', 'digest hash for upgrade')) - .addOption(new Option('--offline', 'store tx block for sign')) - .addOption(new Option('--txFilePath ', 'unsigned transaction will be stored')) - .action((contractName, policy, options) => { - mainProcessor([contractName, policy], options, upgrade); - }); - - // Add 2nd level commands to 1st level command - program.addCommand(deployCmd); - program.addCommand(upgradeCmd); - - program.parse(); -} diff --git a/sui/utils.js b/sui/utils.js index 10db89f5..8fe58de1 100644 --- a/sui/utils.js +++ b/sui/utils.js @@ -111,7 +111,7 @@ const getObjectIdsByObjectTypes = (txn, objectTypes) => throw new Error(`No object found for type: ${objectType}`); } - return objectId + return objectId; }); module.exports = { From 8f0eac1caeb766ce2e0ba8eac097aabcdf0f9944 Mon Sep 17 00:00:00 2001 From: npty Date: Tue, 30 Jul 2024 20:35:40 +0700 Subject: [PATCH 11/30] chore: lint --- evm/utils.js | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/evm/utils.js b/evm/utils.js index 34d2b992..d9e8c058 100644 --- a/evm/utils.js +++ b/evm/utils.js @@ -4,16 +4,7 @@ const { ethers } = require('hardhat'); const { ContractFactory, Contract, - utils: { - computeAddress, - getContractAddress, - keccak256, - isAddress, - getCreate2Address, - defaultAbiCoder, - isHexString, - hexZeroPad, - }, + utils: { computeAddress, getContractAddress, keccak256, isAddress, getCreate2Address, defaultAbiCoder, isHexString, hexZeroPad }, constants: { AddressZero, HashZero }, getDefaultProvider, BigNumber, From 4dc660f8587ac901d54568120724db6805eba5fe Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 11:10:05 +0700 Subject: [PATCH 12/30] chore: move getSigners and getChannelId to utils --- sui/deploy-contract.js | 46 ++++-------------------------------------- sui/utils.js | 45 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 47 insertions(+), 44 deletions(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index ceb69075..cc0a11e3 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -5,23 +5,22 @@ const { toB64 } = require('@mysten/sui/utils'); const { bcs } = require('@mysten/sui/bcs'); const { Transaction } = require('@mysten/sui/transactions'); const { - utils: { arrayify, hexlify, toUtf8Bytes, keccak256 }, - constants: { HashZero }, + utils: { arrayify }, } = ethers; const { saveConfig, printInfo, validateParameters, writeJSON, getDomainSeparator } = require('../common'); const { addBaseOptions, addDeployOptions, addOptionsToCommands } = require('./cli-utils'); const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); -const { bytes32Struct, signersStruct, singletonStruct } = require('./types-utils'); +const { bytes32Struct, signersStruct } = require('./types-utils'); const { upgradePackage } = require('./deploy-utils'); const { loadSuiConfig, - getAmplifierSigners, + getSigners, deployPackage, getObjectIdsByObjectTypes, suiPackageAddress, suiClockAddress, readMovePackageName, - getBcsBytesByObjectId, + getChannelId, } = require('./utils'); // A list of currently supported packages which are the folder names in `node_modules/@axelar-network/axelar-cgp-sui/move` @@ -33,13 +32,6 @@ const supportedPackages = supportedPackageDirs.map((dir) => ({ packageDir: dir, })); -// Parse bcs bytes from singleton object to get channel id -async function getChannelId(client, singletonObjectId) { - const bcsBytes = await getBcsBytesByObjectId(client, singletonObjectId); - const data = singletonStruct.parse(bcsBytes); - return '0x' + data.channel.id; -} - /** ######## Post Deployment Functions ######## **/ // Define the post deployment functions for each supported package here. These functions should be called after the package is deployed. // Use cases include: @@ -148,36 +140,6 @@ async function postDeployAxelarGateway(published, keypair, client, config, chain contractConfig.minimumRotationDelay = minimumRotationDelay; } -async function getSigners(keypair, config, chain, options) { - if (options.signers === 'wallet') { - const pubKey = keypair.getPublicKey().toRawBytes(); - printInfo('Using wallet pubkey as the signer for the gateway', hexlify(pubKey)); - - if (keypair.getKeyScheme() !== 'Secp256k1') { - throw new Error('Only Secp256k1 pubkeys are supported by the gateway'); - } - - return { - signers: [{ pub_key: pubKey, weight: 1 }], - threshold: 1, - nonce: options.nonce ? keccak256(toUtf8Bytes(options.nonce)) : HashZero, - }; - } else if (options.signers) { - printInfo('Using provided signers', options.signers); - - const signers = JSON.parse(options.signers); - return { - signers: signers.signers.map(({ pub_key: pubKey, weight }) => { - return { pub_key: arrayify(pubKey), weight }; - }), - threshold: signers.threshold, - nonce: arrayify(signers.nonce) || HashZero, - }; - } - - return getAmplifierSigners(config, chain); -} - async function deploy(keypair, client, supportedContract, config, chain, options) { const { packageDir, packageName } = supportedContract; diff --git a/sui/utils.js b/sui/utils.js index 8fe58de1..0f84efae 100644 --- a/sui/utils.js +++ b/sui/utils.js @@ -1,15 +1,17 @@ 'use strict'; const { ethers } = require('hardhat'); -const { loadConfig, printError } = require('../common/utils'); +const { printInfo, loadConfig, printError } = require('../common/utils'); const { BigNumber, - utils: { arrayify, hexlify }, + utils: { arrayify, hexlify, toUtf8Bytes, keccak256 }, + constants: { HashZero }, } = ethers; const fs = require('fs'); const { fromB64 } = require('@mysten/bcs'); const { CosmWasmClient } = require('@cosmjs/cosmwasm-stargate'); const { updateMoveToml, copyMovePackage, TxBuilder } = require('@axelar-network/axelar-cgp-sui'); +const { singletonStruct } = require('./types-utils'); const suiPackageAddress = '0x2'; const suiClockAddress = '0x6'; @@ -114,6 +116,43 @@ const getObjectIdsByObjectTypes = (txn, objectTypes) => return objectId; }); +// Parse bcs bytes from singleton object which is created when the Test contract is deployed +const getChannelId = async (client, singletonObjectId) => { + const bcsBytes = await getBcsBytesByObjectId(client, singletonObjectId); + const data = singletonStruct.parse(bcsBytes); + return '0x' + data.channel.id; +}; + +const getSigners = async (keypair, config, chain, options) => { + if (options.signers === 'wallet') { + const pubKey = keypair.getPublicKey().toRawBytes(); + printInfo('Using wallet pubkey as the signer for the gateway', hexlify(pubKey)); + + if (keypair.getKeyScheme() !== 'Secp256k1') { + throw new Error('Only Secp256k1 pubkeys are supported by the gateway'); + } + + return { + signers: [{ pub_key: pubKey, weight: 1 }], + threshold: 1, + nonce: options.nonce ? keccak256(toUtf8Bytes(options.nonce)) : HashZero, + }; + } else if (options.signers) { + printInfo('Using provided signers', options.signers); + + const signers = JSON.parse(options.signers); + return { + signers: signers.signers.map(({ pub_key: pubKey, weight }) => { + return { pub_key: arrayify(pubKey), weight }; + }), + threshold: signers.threshold, + nonce: arrayify(signers.nonce) || HashZero, + }; + } + + return getAmplifierSigners(config, chain); +}; + module.exports = { suiPackageAddress, suiClockAddress, @@ -124,4 +163,6 @@ module.exports = { findPublishedObject, readMovePackageName, getObjectIdsByObjectTypes, + getChannelId, + getSigners, }; From 9d5efa6136dc98660b81c2cd930b773a997fb1cd Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 13:07:56 +0700 Subject: [PATCH 13/30] chore: refactor upgrade --- sui/deploy-contract.js | 75 +++++++++++++++++++++++++----------------- sui/deploy-utils.js | 33 ++++++++++++------- sui/gmp.js | 4 +-- 3 files changed, 67 insertions(+), 45 deletions(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index cc0a11e3..341a5a69 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -23,20 +23,26 @@ const { getChannelId, } = require('./utils'); -// A list of currently supported packages which are the folder names in `node_modules/@axelar-network/axelar-cgp-sui/move` -const supportedPackageDirs = ['gas_service', 'test', 'axelar_gateway']; +// A list of move package directories which are the folder names in `node_modules/@axelar-network/axelar-cgp-sui/move` +// To support a new package deployment, add the folder name to this list. +const PACKAGE_DIRS = ['gas_service', 'test', 'axelar_gateway']; -// Map supported packages to their package names and directories -const supportedPackages = supportedPackageDirs.map((dir) => ({ +// Read the move package name from the `Move.toml` file from each directory in `PACKAGE_DIRS`. +const supportedPackages = PACKAGE_DIRS.map((dir) => ({ packageName: readMovePackageName(dir), packageDir: dir, })); -/** ######## Post Deployment Functions ######## **/ -// Define the post deployment functions for each supported package here. These functions should be called after the package is deployed. -// Use cases include: -// 1. Update the chain config with deployed object ids -// 2. Submit additional transactions to setup the contracts. +/** + * Post-Deployment Functions + * + * This section defines functions to be executed after package deployment. + * These functions serve purposes such as: + * 1. Updating chain configuration with newly deployed object IDs + * 2. Submitting additional transactions for contract setup + * + * Define post-deployment functions for each supported package below. + */ async function postDeployGasService(published, chain) { const [gasCollectorCapObjectId, gasServiceObjectId] = getObjectIdsByObjectTypes(published.publishTxn, [ @@ -172,18 +178,19 @@ async function deploy(keypair, client, supportedContract, config, chain, options printInfo(`${packageName} deployed`, JSON.stringify(chain.contracts[packageName], null, 2)); } -async function upgrade(keypair, client, contractName, policy, config, chain, options) { +async function upgrade(keypair, client, supportedPackage, policy, config, chain, options) { const { packageDependencies } = options; + const { packageDir, packageName } = supportedPackage; options.policy = policy; - if (!chain.contracts[contractName]) { - throw new Error(`Cannot find specified contract: ${contractName}`); + if (!chain.contracts[packageName]) { + throw new Error(`Cannot find specified contract: ${packageName}`); } const contractsConfig = chain.contracts; - const packageConfig = contractsConfig?.[contractName]; + const packageConfig = contractsConfig?.[packageName]; - validateParameters({ isNonEmptyString: { contractName } }); + validateParameters({ isNonEmptyString: { packageName } }); if (packageDependencies) { for (const dependencies of packageDependencies) { @@ -193,7 +200,7 @@ async function upgrade(keypair, client, contractName, policy, config, chain, opt } const builder = new TxBuilder(client); - await upgradePackage(client, keypair, contractName, packageConfig, builder, options); + await upgradePackage(client, keypair, packageDir, packageConfig, builder, options); } async function mainProcessor(args, options, processor) { @@ -219,10 +226,10 @@ if (require.main === module) { const program = new Command('deploy-contract').description('Deploy/Upgrade packages'); // 2nd level commands - const deployCmd = new Command('deploy'); - const upgradeCmd = new Command('upgrade'); + const deployCmd = new Command('deploy').description('Deploy a Sui package'); + const upgradeCmd = new Command('upgrade').description('Upgrade a Sui package'); - // 3rd level commands + // 3rd level commands for `deploy` const deployContractCmds = supportedPackages.map((supportedPackage) => { const { packageName } = supportedPackage; const command = new Command(packageName).description(`Deploy ${packageName} contract`); @@ -235,21 +242,27 @@ if (require.main === module) { // Add 3rd level commands to 2nd level command `deploy` deployContractCmds.forEach((cmd) => deployCmd.addCommand(cmd)); + // 3rd level commands for `upgrade` + const upgradeContractCmds = supportedPackages.map((supportedPackage) => { + const { packageName } = supportedPackage; + return new Command(packageName) + .description(`Deploy ${packageName} contract`) + .command(`${packageName} `) + .addOption(new Option('--sender ', 'transaction sender')) + .addOption(new Option('--digest ', 'digest hash for upgrade')) + .addOption(new Option('--offline', 'store tx block for sign')) + .addOption(new Option('--txFilePath ', 'unsigned transaction will be stored')) + .action((policy, options) => { + mainProcessor([supportedPackage, policy], options, upgrade); + }); + }); + + // Add 3rd level commands to 2nd level command `upgrade` + upgradeContractCmds.forEach((cmd) => upgradeCmd.addCommand(cmd)); + // Add base options to all 2nd and 3rd level commands addOptionsToCommands(deployCmd, addBaseOptions); - addBaseOptions(upgradeCmd); - - // Define options for 2nd level command `upgrade` - upgradeCmd - .description('Upgrade a Sui package') - .command('upgrade ') - .addOption(new Option('--sender ', 'transaction sender')) - .addOption(new Option('--digest ', 'digest hash for upgrade')) - .addOption(new Option('--offline', 'store tx block for sign')) - .addOption(new Option('--txFilePath ', 'unsigned transaction will be stored')) - .action((packageName, policy, options) => { - mainProcessor([packageName, policy], options, upgrade); - }); + addOptionsToCommands(upgradeCmd, addBaseOptions); // Add 2nd level commands to 1st level command program.addCommand(deployCmd); diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index 3c594116..bdc06ecf 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -7,19 +7,28 @@ const { addBaseOptions } = require('./cli-utils'); const { getWallet } = require('./sign-utils'); const { loadSuiConfig, getObjectIdsByObjectTypes, suiPackageAddress } = require('./utils'); -async function upgradePackage(client, keypair, packageName, packageConfig, builder, options) { - const { modules, dependencies, digest } = await builder.getContractBuild(packageName); - const { policy, offline } = options; - const sender = options.sender || keypair.toSuiAddress(); - - if (!['any_upgrade', 'code_upgrade', 'dep_upgrade'].includes(policy)) { - throw new Error(`Unknown upgrade policy: ${policy}. Supported policies: any_upgrade, code_upgrade, dep_upgrade`); +function getUpgradePolicyId(policy) { + switch (policy) { + case 'any_upgrade': + return 0; + case 'code_upgrade': + return 128; + case 'dep_upgrade': + return 192; + default: + throw new Error(`Unknown upgrade policy: ${policy}. Supported policies: any_upgrade, code_upgrade, dep_upgrade`); } +} - const upgradeCap = packageConfig.objects?.upgradeCap; +async function upgradePackage(client, keypair, packageDir, packageConfig, builder, options) { + const { modules, dependencies, digest } = await builder.getContractBuild(packageDir); + const { offline } = options; + const sender = options.sender || keypair.toSuiAddress(); + const policy = getUpgradePolicyId(options.policy); + const upgradeCap = packageConfig.objects?.UpgradeCap; const digestHash = options.digest ? fromB64(options.digest) : digest; - validateParameters({ isNonEmptyString: { upgradeCap, policy }, isNonEmptyStringArray: { modules, dependencies } }); + validateParameters({ isNonEmptyString: { upgradeCap }, isNonEmptyStringArray: { modules, dependencies } }); const tx = builder.tx; const cap = tx.object(upgradeCap); @@ -45,7 +54,7 @@ async function upgradePackage(client, keypair, packageName, packageConfig, build if (offline) { options.txBytes = txBytes; - options.offlineMessage = `Transaction to upgrade ${packageName}`; + options.offlineMessage = `Transaction to upgrade ${packageDir}`; } else { const signature = (await keypair.signTransaction(txBytes)).signature; const result = await client.executeTransactionBlock({ @@ -61,10 +70,10 @@ async function upgradePackage(client, keypair, packageName, packageConfig, build const packageId = (result.objectChanges?.filter((a) => a.type === 'published') ?? [])[0].packageId; packageConfig.address = packageId; const [upgradeCap] = getObjectIdsByObjectTypes(result, ['0x2::package::UpgradeCap']); - packageConfig.objects.upgradeCap = upgradeCap; + packageConfig.objects.UpgradeCap = upgradeCap; printInfo('Transaction digest', JSON.stringify(result.digest, null, 2)); - printInfo(`${packageName} upgraded`, packageId); + printInfo(`${packageDir} upgraded`, packageId); } } diff --git a/sui/gmp.js b/sui/gmp.js index a1bf4a96..786c5c55 100644 --- a/sui/gmp.js +++ b/sui/gmp.js @@ -65,8 +65,8 @@ async function execute(keypair, client, contracts, args, options) { const [sourceChain, messageId, sourceAddress, payload] = args; - const gatewayObjectId = axelarGatewayConfig.objects.gateway; - const discoveryObjectId = axelarGatewayConfig.objects.relayerDiscovery; + const gatewayObjectId = axelarGatewayConfig.objects.Gateway; + const discoveryObjectId = axelarGatewayConfig.objects.RelayerDiscovery; // Get the channel id from the options or use the channel id from the deployed test contract object. const channelId = options.channelId || testConfig.objects.channelId; From 292945c2206fc9f46f4d4756c1aca0b1d0074da3 Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 13:22:28 +0700 Subject: [PATCH 14/30] chore: fix missing package address --- sui/deploy-contract.js | 12 ++++++------ sui/deploy-utils.js | 17 +++++++++-------- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index 341a5a69..68430851 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -157,9 +157,9 @@ async function deploy(keypair, client, supportedContract, config, chain, options printInfo(`Deployed ${packageName}`, published.publishTxn.digest); - if (!chain.contracts[packageName]) { - chain.contracts[packageName] = {}; - } + chain.contracts[packageName] = { + address: published.packageId, + }; switch (packageName) { case 'GasService': @@ -180,7 +180,7 @@ async function deploy(keypair, client, supportedContract, config, chain, options async function upgrade(keypair, client, supportedPackage, policy, config, chain, options) { const { packageDependencies } = options; - const { packageDir, packageName } = supportedPackage; + const { packageName } = supportedPackage; options.policy = policy; if (!chain.contracts[packageName]) { @@ -188,7 +188,7 @@ async function upgrade(keypair, client, supportedPackage, policy, config, chain, } const contractsConfig = chain.contracts; - const packageConfig = contractsConfig?.[packageName]; + const contractConfig = contractsConfig?.[packageName]; validateParameters({ isNonEmptyString: { packageName } }); @@ -200,7 +200,7 @@ async function upgrade(keypair, client, supportedPackage, policy, config, chain, } const builder = new TxBuilder(client); - await upgradePackage(client, keypair, packageDir, packageConfig, builder, options); + await upgradePackage(client, keypair, supportedPackage, contractConfig, builder, options); } async function mainProcessor(args, options, processor) { diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index bdc06ecf..409ba128 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -20,13 +20,14 @@ function getUpgradePolicyId(policy) { } } -async function upgradePackage(client, keypair, packageDir, packageConfig, builder, options) { +async function upgradePackage(client, keypair, packageToUpgrade, contractConfig, builder, options) { + const { packageDir, packageName } = packageToUpgrade; const { modules, dependencies, digest } = await builder.getContractBuild(packageDir); const { offline } = options; const sender = options.sender || keypair.toSuiAddress(); - const policy = getUpgradePolicyId(options.policy); - const upgradeCap = packageConfig.objects?.UpgradeCap; + const upgradeCap = contractConfig.objects?.UpgradeCap; const digestHash = options.digest ? fromB64(options.digest) : digest; + const policy = getUpgradePolicyId(options.policy); validateParameters({ isNonEmptyString: { upgradeCap }, isNonEmptyStringArray: { modules, dependencies } }); @@ -40,7 +41,7 @@ async function upgradePackage(client, keypair, packageDir, packageConfig, builde const receipt = tx.upgrade({ modules, dependencies, - package: packageConfig.address, + package: contractConfig.address, ticket, }); @@ -68,12 +69,12 @@ async function upgradePackage(client, keypair, packageDir, packageConfig, builde }); const packageId = (result.objectChanges?.filter((a) => a.type === 'published') ?? [])[0].packageId; - packageConfig.address = packageId; + contractConfig.address = packageId; const [upgradeCap] = getObjectIdsByObjectTypes(result, ['0x2::package::UpgradeCap']); - packageConfig.objects.UpgradeCap = upgradeCap; + contractConfig.objects.UpgradeCap = upgradeCap; - printInfo('Transaction digest', JSON.stringify(result.digest, null, 2)); - printInfo(`${packageDir} upgraded`, packageId); + printInfo('Transaction Digest', JSON.stringify(result.digest, null, 2)); + printInfo(`${packageName} Upgraded Address`, packageId); } } From 845c015db71d5522e5812b6d3195281199b43a76 Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 13:31:26 +0700 Subject: [PATCH 15/30] chore: refactor deploy script --- sui/deploy-contract.js | 56 ++++++++++++++++++++++++++---------------- sui/deploy-utils.js | 6 +++++ 2 files changed, 41 insertions(+), 21 deletions(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index 68430851..3ae46721 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -11,7 +11,7 @@ const { saveConfig, printInfo, validateParameters, writeJSON, getDomainSeparator const { addBaseOptions, addDeployOptions, addOptionsToCommands } = require('./cli-utils'); const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); const { bytes32Struct, signersStruct } = require('./types-utils'); -const { upgradePackage } = require('./deploy-utils'); +const { upgradePackage, UPGRADE_POLICIES } = require('./deploy-utils'); const { loadSuiConfig, getSigners, @@ -23,11 +23,29 @@ const { getChannelId, } = require('./utils'); -// A list of move package directories which are the folder names in `node_modules/@axelar-network/axelar-cgp-sui/move` -// To support a new package deployment, add the folder name to this list. +/** + * Move Package Directories + * + * This array contains the names of Move package directories located in: + * `node_modules/@axelar-network/axelar-cgp-sui/move` + * + * Each string in this array corresponds to a folder name within that path. + * + * To deploy a new package: + * 1. Add the new package's folder name to this array + * 2. Ensure the corresponding folder exists in the specified path + * + */ const PACKAGE_DIRS = ['gas_service', 'test', 'axelar_gateway']; -// Read the move package name from the `Move.toml` file from each directory in `PACKAGE_DIRS`. +/** + * Supported Move Packages + * + * Maps each directory in PACKAGE_DIRS to an object containing: + * - packageName: Read from 'Move.toml' in the directory + * - packageDir: The directory name + * + */ const supportedPackages = PACKAGE_DIRS.map((dir) => ({ packageName: readMovePackageName(dir), packageDir: dir, @@ -120,8 +138,7 @@ async function postDeployAxelarGateway(published, keypair, client, config, chain }); if (policy !== 'any_upgrade') { - const upgradeType = policy === 'code_upgrade' ? 'only_additive_upgrades' : 'only_dep_upgrades'; - + const upgradeType = UPGRADE_POLICIES[policy]; tx.moveCall({ target: `${suiPackageAddress}::package::${upgradeType}`, arguments: [tx.object(upgradeCap)], @@ -130,29 +147,26 @@ async function postDeployAxelarGateway(published, keypair, client, config, chain const result = await broadcast(client, keypair, tx); - printInfo('Setup transaction digest', result.digest); + printInfo('Setup Gateway', result.digest); const [gateway] = getObjectIdsByObjectTypes(result, [`${packageId}::gateway::Gateway`]); - const contractConfig = chain.contracts.AxelarGateway; - - contractConfig.objects = { - Gateway: gateway, - RelayerDiscovery: relayerDiscovery, - UpgradeCap: upgradeCap, + // Update chain configuration + chain.contracts.AxelarGateway = { + objects: { + Gateway: gateway, + RelayerDiscovery: relayerDiscovery, + UpgradeCap: upgradeCap, + }, + domainSeparator, + operator, + minimumRotationDelay, }; - contractConfig.domainSeparator = domainSeparator; - contractConfig.operator = operator; - contractConfig.minimumRotationDelay = minimumRotationDelay; } async function deploy(keypair, client, supportedContract, config, chain, options) { const { packageDir, packageName } = supportedContract; - if (!chain.contracts[packageName]) { - chain.contracts[packageName] = {}; - } - const published = await deployPackage(packageDir, client, keypair, options); printInfo(`Deployed ${packageName}`, published.publishTxn.digest); @@ -175,7 +189,7 @@ async function deploy(keypair, client, supportedContract, config, chain, options throw new Error(`${packageName} is not supported.`); } - printInfo(`${packageName} deployed`, JSON.stringify(chain.contracts[packageName], null, 2)); + printInfo(`${packageName} Configuration Updated`, JSON.stringify(chain.contracts[packageName], null, 2)); } async function upgrade(keypair, client, supportedPackage, policy, config, chain, options) { diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index 409ba128..844edf47 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -7,6 +7,11 @@ const { addBaseOptions } = require('./cli-utils'); const { getWallet } = require('./sign-utils'); const { loadSuiConfig, getObjectIdsByObjectTypes, suiPackageAddress } = require('./utils'); +const UPGRADE_POLICIES = { + 'code_upgrade': 'only_additive_upgrades', + 'dependency_upgrade': 'only_dep_upgrades', +}; + function getUpgradePolicyId(policy) { switch (policy) { case 'any_upgrade': @@ -193,6 +198,7 @@ if (require.main === module) { } module.exports = { + UPGRADE_POLICIES, upgradePackage, deployPackage, }; From 98e6f642ed99335e22bcb09f442d1b1ca9c47142 Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 14:32:06 +0700 Subject: [PATCH 16/30] chore: update README.md --- sui/README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/sui/README.md b/sui/README.md index 443cfcf0..bb6e5a78 100644 --- a/sui/README.md +++ b/sui/README.md @@ -54,7 +54,7 @@ Deploy the gateway package: - By querying the signer set from the Amplifier contract (this only works if Amplifier contracts have been setup): ```bash -node sui/deploy-contract.js deploy axelar_gateway +node sui/deploy-contract.js deploy AxelarGateway ``` Note: the `minimumRotationDelay` is in `seconds` unit. The default value is `24 * 60 * 60` (1 day). @@ -64,13 +64,13 @@ Use `--help` flag to see other setup params that can be overridden. - For testing convenience, you can use the secp256k1 wallet as the signer set for the gateway. ```bash -node sui/deploy-contract.js deploy axelar_gateway --signers wallet --nonce test +node sui/deploy-contract.js deploy AxelarGateway --signers wallet --nonce test ``` - You can also provide a JSON object with a full signer set: ```bash -node sui/deploy-contract.js deploy axelar_gateway -e testnet --signers '{"signers": [{"pub_key": "0x020194ead85b350d90472117e6122cf1764d93bf17d6de4b51b03d19afc4d6302b", "weight": 1}], "threshold": 1, "nonce": "0x0000000000000000000000000000000000000000000000000000000000000000"}' +node sui/deploy-contract.js deploy AxelarGateway -e testnet --signers '{"signers": [{"pub_key": "0x020194ead85b350d90472117e6122cf1764d93bf17d6de4b51b03d19afc4d6302b", "weight": 1}], "threshold": 1, "nonce": "0x0000000000000000000000000000000000000000000000000000000000000000"}' ``` Upgrading Gateway: @@ -78,7 +78,7 @@ Upgrading Gateway: To update the gateway run the following command: ```bash -node sui/deploy-contract.js upgrade axelar_gateway +node sui/deploy-contract.js upgrade AxelarGateway ``` policy should be one of the following: @@ -92,13 +92,13 @@ Provide `--txFilePath` with `--offline` to generate tx data file for offline sig Deploy the Gas Service package: ```bash -node sui/deploy-contract.js deploy gas_service +node sui/deploy-contract.js deploy GasService ``` Deploy the test GMP package: ```bash -node sui/deploy-contract.js Test +node sui/deploy-contract.js deploy Test ``` Call Contract: From 07f46c2b852c415738ab1c67337deee5c194ae7d Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 14:32:22 +0700 Subject: [PATCH 17/30] chore: prettier --- sui/deploy-utils.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index 844edf47..142e33b3 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -8,8 +8,8 @@ const { getWallet } = require('./sign-utils'); const { loadSuiConfig, getObjectIdsByObjectTypes, suiPackageAddress } = require('./utils'); const UPGRADE_POLICIES = { - 'code_upgrade': 'only_additive_upgrades', - 'dependency_upgrade': 'only_dep_upgrades', + code_upgrade: 'only_additive_upgrades', + dependency_upgrade: 'only_dep_upgrades', }; function getUpgradePolicyId(policy) { From d44cef77f7caeee228fd5131776b91b2d11d6db5 Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 15:00:34 +0700 Subject: [PATCH 18/30] chore: remove program and deploy function in deploy-utils.js --- sui/deploy-utils.js | 125 ++------------------------------------------ 1 file changed, 3 insertions(+), 122 deletions(-) diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index 142e33b3..56b144b5 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -1,11 +1,7 @@ -const { Command, Option } = require('commander'); -const { TxBuilder, updateMoveToml } = require('@axelar-network/axelar-cgp-sui'); const { bcs } = require('@mysten/bcs'); -const { fromB64, toB64 } = require('@mysten/bcs'); -const { saveConfig, printInfo, validateParameters, prompt, writeJSON } = require('../common/utils'); -const { addBaseOptions } = require('./cli-utils'); -const { getWallet } = require('./sign-utils'); -const { loadSuiConfig, getObjectIdsByObjectTypes, suiPackageAddress } = require('./utils'); +const { fromB64 } = require('@mysten/bcs'); +const { printInfo, validateParameters } = require('../common/utils'); +const { getObjectIdsByObjectTypes, suiPackageAddress } = require('./utils'); const UPGRADE_POLICIES = { code_upgrade: 'only_additive_upgrades', @@ -83,122 +79,7 @@ async function upgradePackage(client, keypair, packageToUpgrade, contractConfig, } } -async function deployPackage(chain, client, keypair, packageName, packageConfig, builder, options) { - const { offline, sender } = options; - - const address = sender || keypair.toSuiAddress(); - await builder.publishPackageAndTransferCap(packageName, address); - const tx = builder.tx; - tx.setSender(address); - const txBytes = await tx.build({ client }); - - if (offline) { - options.txBytes = txBytes; - } else { - if (prompt(`Proceed with deployment on ${chain.name}?`, options.yes)) { - return; - } - - const signature = (await keypair.signTransaction(txBytes)).signature; - const publishTxn = await client.executeTransactionBlock({ - transactionBlock: txBytes, - signature, - options: { - showEffects: true, - showObjectChanges: true, - showEvents: true, - }, - }); - - packageConfig.address = (publishTxn.objectChanges?.find((a) => a.type === 'published') ?? []).packageId; - const objectChanges = publishTxn.objectChanges.filter((object) => object.type === 'created'); - packageConfig.objects = {}; - - for (const object of objectChanges) { - const array = object.objectType.split('::'); - const objectName = array[array.length - 1]; - - if (objectName) { - packageConfig.objects[objectName] = object.objectId; - } - } - - printInfo(`${packageName} deployed`, JSON.stringify(packageConfig, null, 2)); - } -} - -async function processCommand(chain, options) { - const [keypair, client] = getWallet(chain, options); - const { upgrade, packageName, packageDependencies, offline, txFilePath } = options; - - printInfo('Wallet address', keypair.toSuiAddress()); - - if (!chain.contracts[packageName]) { - chain.contracts[packageName] = {}; - } - - const contractsConfig = chain.contracts; - const packageConfig = contractsConfig?.[packageName]; - - validateParameters({ isNonEmptyString: { packageName } }); - - if (packageDependencies) { - for (const dependencies of packageDependencies) { - const packageId = contractsConfig[dependencies]?.address; - updateMoveToml(dependencies, packageId); - } - } - - const builder = new TxBuilder(client); - - if (upgrade) { - await upgradePackage(client, keypair, packageName, packageConfig, builder, options); - } else { - await deployPackage(chain, client, keypair, packageName, packageConfig, builder, options); - } - - if (offline) { - validateParameters({ isNonEmptyString: { txFilePath } }); - - const txB64Bytes = toB64(options.txBytes); - - writeJSON({ status: 'PENDING', bytes: txB64Bytes }, txFilePath); - printInfo(`The unsigned transaction is`, txB64Bytes); - } -} - -async function mainProcessor(options, processor) { - const config = loadSuiConfig(options.env); - - await processor(config.sui, options); - saveConfig(config, options.env); -} - -if (require.main === module) { - const program = new Command(); - - program.name('deploy-upgrade').description('Deploy/Upgrade the Sui package'); - - addBaseOptions(program); - - program.addOption(new Option('--packageName ', 'package name to deploy/upgrade')); - program.addOption(new Option('--packageDependencies [packageDependencies...]', 'array of package dependencies')); - program.addOption(new Option('--upgrade', 'deploy or upgrade')); - program.addOption(new Option('--policy ', 'new policy to upgrade')); - program.addOption(new Option('--sender ', 'transaction sender')); - program.addOption(new Option('--digest ', 'digest hash for upgrade')); - program.addOption(new Option('--offline', 'store tx block for sign')); - program.addOption(new Option('--txFilePath ', 'unsigned transaction will be stored')); - - program.action((options) => { - mainProcessor(options, processCommand); - }); - - program.parse(); -} - module.exports = { UPGRADE_POLICIES, upgradePackage, - deployPackage, }; From 4f41195af2fe05447353680c299ff07a4931979a Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 17:58:41 +0700 Subject: [PATCH 19/30] chore: move getDeplooyGatewayOptions to deploy-utils --- sui/cli-utils.js | 32 -------------------------------- sui/deploy-contract.js | 4 ++-- sui/deploy-utils.js | 34 +++++++++++++++++++++++++++++++++- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/sui/cli-utils.js b/sui/cli-utils.js index d0aa40ab..d36d9712 100644 --- a/sui/cli-utils.js +++ b/sui/cli-utils.js @@ -52,37 +52,6 @@ const addExtendedOptions = (program, options = {}) => { return program; }; -const getDeployGatewayOptions = () => { - return [ - new Option('--signers ', 'JSON with the initial signer set').env('SIGNERS'), - new Option('--operator ', 'operator for the gateway (defaults to the deployer address)').env('OPERATOR'), - new Option('--minimumRotationDelay ', 'minium delay for signer rotations (in second)') - .argParser((val) => parseInt(val) * 1000) - .default(24 * 60 * 60), - new Option('--domainSeparator ', 'domain separator'), - new Option('--nonce ', 'nonce for the signer (defaults to HashZero)'), - new Option('--previousSigners ', 'number of previous signers to retain').default('15'), - new Option('--policy ', 'upgrade policy for upgrade cap: For example, use "any_upgrade" to allow all types of upgrades') - .choices(['any_upgrade', 'code_upgrade', 'dep_upgrade']) - .default('any_upgrade'), - ]; -}; - -const addDeployOptions = (program) => { - switch (program.name()) { - case 'AxelarGateway': - getDeployGatewayOptions().forEach((option) => program.addOption(option)); - break; - case 'GasService': - case 'Test': - break; - default: - throw new Error(`Unsupported package: ${program.name()}. `); - } - - return program; -}; - // `optionMethod` is a method such as `addBaseOptions` // `options` is an option object for optionMethod const addOptionsToCommands = (program, optionMethod, options) => { @@ -111,5 +80,4 @@ module.exports = { addExtendedOptions, addOptionsToCommands, parseSuiUnitAmount, - addDeployOptions, }; diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index 3ae46721..21dcc937 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -8,10 +8,10 @@ const { utils: { arrayify }, } = ethers; const { saveConfig, printInfo, validateParameters, writeJSON, getDomainSeparator } = require('../common'); -const { addBaseOptions, addDeployOptions, addOptionsToCommands } = require('./cli-utils'); +const { addBaseOptions, addOptionsToCommands } = require('./cli-utils'); const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); const { bytes32Struct, signersStruct } = require('./types-utils'); -const { upgradePackage, UPGRADE_POLICIES } = require('./deploy-utils'); +const { upgradePackage, addDeployOptions, UPGRADE_POLICIES } = require('./deploy-utils'); const { loadSuiConfig, getSigners, diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index 56b144b5..510e9d08 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -2,7 +2,7 @@ const { bcs } = require('@mysten/bcs'); const { fromB64 } = require('@mysten/bcs'); const { printInfo, validateParameters } = require('../common/utils'); const { getObjectIdsByObjectTypes, suiPackageAddress } = require('./utils'); - +const { Option } = require('commander'); const UPGRADE_POLICIES = { code_upgrade: 'only_additive_upgrades', dependency_upgrade: 'only_dep_upgrades', @@ -79,7 +79,39 @@ async function upgradePackage(client, keypair, packageToUpgrade, contractConfig, } } +const getDeployGatewayOptions = () => { + return [ + new Option('--signers ', 'JSON with the initial signer set').env('SIGNERS'), + new Option('--operator ', 'operator for the gateway (defaults to the deployer address)').env('OPERATOR'), + new Option('--minimumRotationDelay ', 'minium delay for signer rotations (in second)') + .argParser((val) => parseInt(val) * 1000) + .default(24 * 60 * 60), + new Option('--domainSeparator ', 'domain separator'), + new Option('--nonce ', 'nonce for the signer (defaults to HashZero)'), + new Option('--previousSigners ', 'number of previous signers to retain').default('15'), + new Option('--policy ', 'upgrade policy for upgrade cap: For example, use "any_upgrade" to allow all types of upgrades') + .choices(['any_upgrade', 'code_upgrade', 'dep_upgrade']) + .default('any_upgrade'), + ]; +}; + +const addDeployOptions = (program) => { + switch (program.name()) { + case 'AxelarGateway': + getDeployGatewayOptions().forEach((option) => program.addOption(option)); + break; + case 'GasService': + case 'Test': + break; + default: + throw new Error(`Unsupported package: ${program.name()}. `); + } + + return program; +}; + module.exports = { UPGRADE_POLICIES, upgradePackage, + addDeployOptions, }; From fe2be1af5db5cd67e9cea8de75dce3677c4e4324 Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 18:00:13 +0700 Subject: [PATCH 20/30] chore: use constant for 0x2 --- sui/deploy-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index 510e9d08..63a30fa7 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -71,7 +71,7 @@ async function upgradePackage(client, keypair, packageToUpgrade, contractConfig, const packageId = (result.objectChanges?.filter((a) => a.type === 'published') ?? [])[0].packageId; contractConfig.address = packageId; - const [upgradeCap] = getObjectIdsByObjectTypes(result, ['0x2::package::UpgradeCap']); + const [upgradeCap] = getObjectIdsByObjectTypes(result, [`${suiPackageAddress}::package::UpgradeCap`]); contractConfig.objects.UpgradeCap = upgradeCap; printInfo('Transaction Digest', JSON.stringify(result.digest, null, 2)); From bdbbce918594268e5b5ff1842895718299482f1c Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 18:00:41 +0700 Subject: [PATCH 21/30] chore: prettier --- sui/deploy-utils.js | 48 ++++++++++++++++++++++----------------------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index 63a30fa7..bb06309e 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -80,34 +80,34 @@ async function upgradePackage(client, keypair, packageToUpgrade, contractConfig, } const getDeployGatewayOptions = () => { - return [ - new Option('--signers ', 'JSON with the initial signer set').env('SIGNERS'), - new Option('--operator ', 'operator for the gateway (defaults to the deployer address)').env('OPERATOR'), - new Option('--minimumRotationDelay ', 'minium delay for signer rotations (in second)') - .argParser((val) => parseInt(val) * 1000) - .default(24 * 60 * 60), - new Option('--domainSeparator ', 'domain separator'), - new Option('--nonce ', 'nonce for the signer (defaults to HashZero)'), - new Option('--previousSigners ', 'number of previous signers to retain').default('15'), - new Option('--policy ', 'upgrade policy for upgrade cap: For example, use "any_upgrade" to allow all types of upgrades') - .choices(['any_upgrade', 'code_upgrade', 'dep_upgrade']) - .default('any_upgrade'), - ]; + return [ + new Option('--signers ', 'JSON with the initial signer set').env('SIGNERS'), + new Option('--operator ', 'operator for the gateway (defaults to the deployer address)').env('OPERATOR'), + new Option('--minimumRotationDelay ', 'minium delay for signer rotations (in second)') + .argParser((val) => parseInt(val) * 1000) + .default(24 * 60 * 60), + new Option('--domainSeparator ', 'domain separator'), + new Option('--nonce ', 'nonce for the signer (defaults to HashZero)'), + new Option('--previousSigners ', 'number of previous signers to retain').default('15'), + new Option('--policy ', 'upgrade policy for upgrade cap: For example, use "any_upgrade" to allow all types of upgrades') + .choices(['any_upgrade', 'code_upgrade', 'dep_upgrade']) + .default('any_upgrade'), + ]; }; const addDeployOptions = (program) => { - switch (program.name()) { - case 'AxelarGateway': - getDeployGatewayOptions().forEach((option) => program.addOption(option)); - break; - case 'GasService': - case 'Test': - break; - default: - throw new Error(`Unsupported package: ${program.name()}. `); - } + switch (program.name()) { + case 'AxelarGateway': + getDeployGatewayOptions().forEach((option) => program.addOption(option)); + break; + case 'GasService': + case 'Test': + break; + default: + throw new Error(`Unsupported package: ${program.name()}. `); + } - return program; + return program; }; module.exports = { From 29b8bd19d5eac0268514ba1c84d4e580bbc5a44d Mon Sep 17 00:00:00 2001 From: npty Date: Wed, 31 Jul 2024 18:09:10 +0700 Subject: [PATCH 22/30] feat: deploy Operators contract --- sui/README.md | 6 ++++++ sui/deploy-contract.js | 16 +++++++++++++++- sui/deploy-utils.js | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/sui/README.md b/sui/README.md index bb6e5a78..2cc6c789 100644 --- a/sui/README.md +++ b/sui/README.md @@ -101,6 +101,12 @@ Deploy the test GMP package: node sui/deploy-contract.js deploy Test ``` +Deploy the Operators package: + +```bash +node sui/deploy-contract.js deploy Operators +``` + Call Contract: ```bash diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index 21dcc937..f48eb2d8 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -36,7 +36,7 @@ const { * 2. Ensure the corresponding folder exists in the specified path * */ -const PACKAGE_DIRS = ['gas_service', 'test', 'axelar_gateway']; +const PACKAGE_DIRS = ['gas_service', 'test', 'axelar_gateway', 'operators']; /** * Supported Move Packages @@ -92,6 +92,17 @@ async function postDeployTest(published, config, chain, options) { printInfo('Register transaction', registerTx.digest); } +async function postDeployOperators(published, chain) { + const [operatorsObjectId, ownerCapObjectId] = getObjectIdsByObjectTypes(published.publishTxn, [ + `${published.packageId}::operators::Operators`, + `${published.packageId}::operators::OwnerCap`, + ]); + chain.contracts.Operators.objects = { + Operators: operatorsObjectId, + OwnerCap: ownerCapObjectId, + }; +} + async function postDeployAxelarGateway(published, keypair, client, config, chain, options) { const { packageId, publishTxn } = published; const { minimumRotationDelay, policy, previousSigners } = options; @@ -185,6 +196,9 @@ async function deploy(keypair, client, supportedContract, config, chain, options case 'Test': await postDeployTest(published, config, chain, options); break; + case 'Operators': + await postDeployOperators(published, chain); + break; default: throw new Error(`${packageName} is not supported.`); } diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index bb06309e..e2f0f204 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -101,6 +101,7 @@ const addDeployOptions = (program) => { getDeployGatewayOptions().forEach((option) => program.addOption(option)); break; case 'GasService': + case 'Operators': case 'Test': break; default: From 6f2010e88ce7c6540b5fa3c6ee730525720a991e Mon Sep 17 00:00:00 2001 From: npty Date: Thu, 1 Aug 2024 09:37:17 +0700 Subject: [PATCH 23/30] chore: move the command options into deploy-contract file --- sui/deploy-contract.js | 41 ++++++++++++++++++++++++++++++++++++++--- sui/deploy-utils.js | 34 ---------------------------------- 2 files changed, 38 insertions(+), 37 deletions(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index f48eb2d8..2f470180 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -11,7 +11,7 @@ const { saveConfig, printInfo, validateParameters, writeJSON, getDomainSeparator const { addBaseOptions, addOptionsToCommands } = require('./cli-utils'); const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); const { bytes32Struct, signersStruct } = require('./types-utils'); -const { upgradePackage, addDeployOptions, UPGRADE_POLICIES } = require('./deploy-utils'); +const { upgradePackage, UPGRADE_POLICIES } = require('./deploy-utils'); const { loadSuiConfig, getSigners, @@ -98,8 +98,8 @@ async function postDeployOperators(published, chain) { `${published.packageId}::operators::OwnerCap`, ]); chain.contracts.Operators.objects = { - Operators: operatorsObjectId, - OwnerCap: ownerCapObjectId, + Operators: operatorsObjectId, + OwnerCap: ownerCapObjectId, }; } @@ -249,6 +249,41 @@ async function mainProcessor(args, options, processor) { } } +/** + * Command Options + * + * This section defines options for the command that are specific to each package. + */ +const GATEWAY_CMD_OPTIONS = [ + new Option('--signers ', 'JSON with the initial signer set').env('SIGNERS'), + new Option('--operator ', 'operator for the gateway (defaults to the deployer address)').env('OPERATOR'), + new Option('--minimumRotationDelay ', 'minium delay for signer rotations (in second)') + .argParser((val) => parseInt(val) * 1000) + .default(24 * 60 * 60), + new Option('--domainSeparator ', 'domain separator'), + new Option('--nonce ', 'nonce for the signer (defaults to HashZero)'), + new Option('--previousSigners ', 'number of previous signers to retain').default('15'), + new Option('--policy ', 'upgrade policy for upgrade cap: For example, use "any_upgrade" to allow all types of upgrades') + .choices(['any_upgrade', 'code_upgrade', 'dep_upgrade']) + .default('any_upgrade'), +]; + +const addDeployOptions = (program) => { + switch (program.name()) { + case 'AxelarGateway': + GATEWAY_CMD_OPTIONS.forEach((option) => program.addOption(option)); + break; + case 'GasService': + case 'Operators': + case 'Test': + break; + default: + throw new Error(`Unsupported package: ${program.name()}. `); + } + + return program; +}; + if (require.main === module) { // 1st level command const program = new Command('deploy-contract').description('Deploy/Upgrade packages'); diff --git a/sui/deploy-utils.js b/sui/deploy-utils.js index e2f0f204..0dd21568 100644 --- a/sui/deploy-utils.js +++ b/sui/deploy-utils.js @@ -2,7 +2,6 @@ const { bcs } = require('@mysten/bcs'); const { fromB64 } = require('@mysten/bcs'); const { printInfo, validateParameters } = require('../common/utils'); const { getObjectIdsByObjectTypes, suiPackageAddress } = require('./utils'); -const { Option } = require('commander'); const UPGRADE_POLICIES = { code_upgrade: 'only_additive_upgrades', dependency_upgrade: 'only_dep_upgrades', @@ -79,40 +78,7 @@ async function upgradePackage(client, keypair, packageToUpgrade, contractConfig, } } -const getDeployGatewayOptions = () => { - return [ - new Option('--signers ', 'JSON with the initial signer set').env('SIGNERS'), - new Option('--operator ', 'operator for the gateway (defaults to the deployer address)').env('OPERATOR'), - new Option('--minimumRotationDelay ', 'minium delay for signer rotations (in second)') - .argParser((val) => parseInt(val) * 1000) - .default(24 * 60 * 60), - new Option('--domainSeparator ', 'domain separator'), - new Option('--nonce ', 'nonce for the signer (defaults to HashZero)'), - new Option('--previousSigners ', 'number of previous signers to retain').default('15'), - new Option('--policy ', 'upgrade policy for upgrade cap: For example, use "any_upgrade" to allow all types of upgrades') - .choices(['any_upgrade', 'code_upgrade', 'dep_upgrade']) - .default('any_upgrade'), - ]; -}; - -const addDeployOptions = (program) => { - switch (program.name()) { - case 'AxelarGateway': - getDeployGatewayOptions().forEach((option) => program.addOption(option)); - break; - case 'GasService': - case 'Operators': - case 'Test': - break; - default: - throw new Error(`Unsupported package: ${program.name()}. `); - } - - return program; -}; - module.exports = { UPGRADE_POLICIES, upgradePackage, - addDeployOptions, }; From d0002f124eb6e945192db46c2fd9a62e5663d982 Mon Sep 17 00:00:00 2001 From: npty Date: Thu, 1 Aug 2024 10:50:19 +0700 Subject: [PATCH 24/30] chore: create a mapping for command options and post deployment scripts --- sui/deploy-contract.js | 88 ++++++++++++++++++++++++++---------------- 1 file changed, 54 insertions(+), 34 deletions(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index 2f470180..f484c082 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -38,6 +38,30 @@ const { */ const PACKAGE_DIRS = ['gas_service', 'test', 'axelar_gateway', 'operators']; +/** + * Post-Deployment Functions Mapping + * + * This object maps each package name to a post-deployment function. + */ +const POST_DEPLOY_FUNCTIONS = { + GasService: postDeployGasService, + Test: postDeployTest, + Operators: postDeployOperators, + AxelarGateway: postDeployAxelarGateway, +}; + +/** + * Command Options Mapping + * + * This object maps each package name to a function that returns an array of command options. + */ +const CMD_OPTIONS = { + AxelarGateway: () => [...DEPLOY_CMD_OPTIONS, ...GATEWAY_CMD_OPTIONS], + GasService: () => DEPLOY_CMD_OPTIONS, + Test: () => DEPLOY_CMD_OPTIONS, + Operators: () => DEPLOY_CMD_OPTIONS, +}; + /** * Supported Move Packages * @@ -62,7 +86,8 @@ const supportedPackages = PACKAGE_DIRS.map((dir) => ({ * Define post-deployment functions for each supported package below. */ -async function postDeployGasService(published, chain) { +async function postDeployGasService(published, args) { + const { chain } = args; const [gasCollectorCapObjectId, gasServiceObjectId] = getObjectIdsByObjectTypes(published.publishTxn, [ `${published.packageId}::gas_service::GasCollectorCap`, `${published.packageId}::gas_service::GasService`, @@ -73,7 +98,8 @@ async function postDeployGasService(published, chain) { }; } -async function postDeployTest(published, config, chain, options) { +async function postDeployTest(published, args) { + const { chain, config, options } = args; const [keypair, client] = getWallet(chain, options); const relayerDiscovery = config.sui.contracts.AxelarGateway?.objects?.RelayerDiscovery; @@ -92,7 +118,8 @@ async function postDeployTest(published, config, chain, options) { printInfo('Register transaction', registerTx.digest); } -async function postDeployOperators(published, chain) { +async function postDeployOperators(published, args) { + const { chain } = args; const [operatorsObjectId, ownerCapObjectId] = getObjectIdsByObjectTypes(published.publishTxn, [ `${published.packageId}::operators::Operators`, `${published.packageId}::operators::OwnerCap`, @@ -103,7 +130,8 @@ async function postDeployOperators(published, chain) { }; } -async function postDeployAxelarGateway(published, keypair, client, config, chain, options) { +async function postDeployAxelarGateway(published, args) { + const { keypair, client, config, chain, options } = args; const { packageId, publishTxn } = published; const { minimumRotationDelay, policy, previousSigners } = options; const operator = options.operator || keypair.toSuiAddress(); @@ -178,30 +206,19 @@ async function postDeployAxelarGateway(published, keypair, client, config, chain async function deploy(keypair, client, supportedContract, config, chain, options) { const { packageDir, packageName } = supportedContract; + // Deploy package const published = await deployPackage(packageDir, client, keypair, options); printInfo(`Deployed ${packageName}`, published.publishTxn.digest); + // Update chain configuration with deployed contract address chain.contracts[packageName] = { address: published.packageId, }; - switch (packageName) { - case 'GasService': - await postDeployGasService(published, chain); - break; - case 'AxelarGateway': - await postDeployAxelarGateway(published, keypair, client, config, chain, options); - break; - case 'Test': - await postDeployTest(published, config, chain, options); - break; - case 'Operators': - await postDeployOperators(published, chain); - break; - default: - throw new Error(`${packageName} is not supported.`); - } + // Execute post-deployment function + const executePostDeploymentFn = POST_DEPLOY_FUNCTIONS[packageName]; + executePostDeploymentFn(published, { keypair, client, config, chain, options }); printInfo(`${packageName} Configuration Updated`, JSON.stringify(chain.contracts[packageName], null, 2)); } @@ -254,6 +271,15 @@ async function mainProcessor(args, options, processor) { * * This section defines options for the command that are specific to each package. */ + +// Common deploy command options for all packages +const DEPLOY_CMD_OPTIONS = [ + new Option('--policy ', 'upgrade policy for upgrade cap: For example, use "any_upgrade" to allow all types of upgrades') + .choices(['any_upgrade', 'code_upgrade', 'dep_upgrade']) + .default('any_upgrade'), +]; + +// Gateway deploy command options const GATEWAY_CMD_OPTIONS = [ new Option('--signers ', 'JSON with the initial signer set').env('SIGNERS'), new Option('--operator ', 'operator for the gateway (defaults to the deployer address)').env('OPERATOR'), @@ -263,23 +289,17 @@ const GATEWAY_CMD_OPTIONS = [ new Option('--domainSeparator ', 'domain separator'), new Option('--nonce ', 'nonce for the signer (defaults to HashZero)'), new Option('--previousSigners ', 'number of previous signers to retain').default('15'), - new Option('--policy ', 'upgrade policy for upgrade cap: For example, use "any_upgrade" to allow all types of upgrades') - .choices(['any_upgrade', 'code_upgrade', 'dep_upgrade']) - .default('any_upgrade'), ]; const addDeployOptions = (program) => { - switch (program.name()) { - case 'AxelarGateway': - GATEWAY_CMD_OPTIONS.forEach((option) => program.addOption(option)); - break; - case 'GasService': - case 'Operators': - case 'Test': - break; - default: - throw new Error(`Unsupported package: ${program.name()}. `); - } + // Get the package name from the program name + const packageName = program.name(); + + // Find the corresponding options for the package + const options = CMD_OPTIONS[packageName](); + + // Add the options to the program + options.forEach((option) => program.addOption(option)); return program; }; From 5eed90252e58c3e83c80cecd083852a6d981fc34 Mon Sep 17 00:00:00 2001 From: npty Date: Thu, 1 Aug 2024 10:56:03 +0700 Subject: [PATCH 25/30] chore: add toml parser --- package-lock.json | 3 ++- package.json | 3 ++- sui/utils.js | 9 +++------ 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/package-lock.json b/package-lock.json index 55b67d22..296d788b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,8 @@ "@mysten/sui": "^1.3.0", "@stellar/stellar-sdk": "^12.0.0-rc3", "axios": "^1.6.2", - "path": "^0.12.7" + "path": "^0.12.7", + "toml": "^3.0.0" }, "devDependencies": { "@ledgerhq/hw-transport-node-hid": "^6.27.21", diff --git a/package.json b/package.json index 01379e8c..bcdb007e 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,8 @@ "@mysten/sui": "^1.3.0", "@stellar/stellar-sdk": "^12.0.0-rc3", "axios": "^1.6.2", - "path": "^0.12.7" + "path": "^0.12.7", + "toml": "^3.0.0" }, "devDependencies": { "@ledgerhq/hw-transport-node-hid": "^6.27.21", diff --git a/sui/utils.js b/sui/utils.js index 0f84efae..0626ae9b 100644 --- a/sui/utils.js +++ b/sui/utils.js @@ -1,6 +1,7 @@ 'use strict'; const { ethers } = require('hardhat'); +const toml = require('toml'); const { printInfo, loadConfig, printError } = require('../common/utils'); const { BigNumber, @@ -92,13 +93,9 @@ const readMovePackageName = (moveDir) => { try { const moveToml = fs.readFileSync(`${__dirname}/../node_modules/@axelar-network/axelar-cgp-sui/move/${moveDir}/Move.toml`, 'utf8'); - const nameMatch = moveToml.match(/^\s*name\s*=\s*"([^"]+)"/m); + const { package: movePackage } = toml.parse(moveToml); - if (nameMatch && nameMatch[1]) { - return nameMatch[1]; - } - - throw new Error('Package name not found in TOML file'); + return movePackage.name; } catch (err) { printError('Error reading TOML file'); throw err; From 6b3d1c4db13dae053b1e4aaa4ad19a3eb2d8eee8 Mon Sep 17 00:00:00 2001 From: npty Date: Thu, 1 Aug 2024 10:57:21 +0700 Subject: [PATCH 26/30] chore: fix default value for config.sui --- sui/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sui/utils.js b/sui/utils.js index 0626ae9b..0d6574cc 100644 --- a/sui/utils.js +++ b/sui/utils.js @@ -61,7 +61,7 @@ const loadSuiConfig = (env) => { networkType: suiEnv, name: 'Sui', contracts: { - axelar_gateway: {}, + AxelarGateway: {}, }, }; } From 4964688372671d6a5c06eb8027c1b1161957ee37 Mon Sep 17 00:00:00 2001 From: npty Date: Thu, 1 Aug 2024 11:06:09 +0700 Subject: [PATCH 27/30] chore: use loadConfig instead of loadSuiConfig --- common/utils.js | 14 +++++++++++++- sui/deploy-contract.js | 5 ++--- sui/deploy-test.js | 4 ++-- sui/gas-service.js | 5 +++-- sui/gateway.js | 4 ++-- sui/gmp.js | 5 +++-- sui/multisig.js | 4 ++-- sui/transfer-object.js | 4 ++-- sui/utils.js | 20 +------------------- 9 files changed, 30 insertions(+), 35 deletions(-) diff --git a/common/utils.js b/common/utils.js index 71ce7284..e6819154 100644 --- a/common/utils.js +++ b/common/utils.js @@ -15,7 +15,19 @@ const { const { normalizeBech32 } = require('@cosmjs/encoding'); function loadConfig(env) { - return require(`${__dirname}/../axelar-chains-config/info/${env}.json`); + const config = require(`${__dirname}/../axelar-chains-config/info/${env}.json`); + + if (!config.sui) { + config.sui = { + networkType: env === 'local' ? 'localnet' : env, + name: 'Sui', + contracts: { + AxelarGateway: {}, + }, + }; + } + + return config; } function saveConfig(config, env) { diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index f484c082..2b9ba94f 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -7,13 +7,12 @@ const { Transaction } = require('@mysten/sui/transactions'); const { utils: { arrayify }, } = ethers; -const { saveConfig, printInfo, validateParameters, writeJSON, getDomainSeparator } = require('../common'); +const { saveConfig, printInfo, validateParameters, writeJSON, getDomainSeparator, loadConfig } = require('../common'); const { addBaseOptions, addOptionsToCommands } = require('./cli-utils'); const { getWallet, printWalletInfo, broadcast } = require('./sign-utils'); const { bytes32Struct, signersStruct } = require('./types-utils'); const { upgradePackage, UPGRADE_POLICIES } = require('./deploy-utils'); const { - loadSuiConfig, getSigners, deployPackage, getObjectIdsByObjectTypes, @@ -249,7 +248,7 @@ async function upgrade(keypair, client, supportedPackage, policy, config, chain, } async function mainProcessor(args, options, processor) { - const config = loadSuiConfig(options.env); + const config = loadConfig(options.env); const [keypair, client] = getWallet(config.sui, options); await printWalletInfo(keypair, client, config.sui, options); await processor(keypair, client, ...args, config, config.sui, options); diff --git a/sui/deploy-test.js b/sui/deploy-test.js index 8b0db9e9..88f8d463 100644 --- a/sui/deploy-test.js +++ b/sui/deploy-test.js @@ -1,6 +1,6 @@ const { saveConfig, prompt, printInfo } = require('../common/utils'); const { Command } = require('commander'); -const { loadSuiConfig, deployPackage, getBcsBytesByObjectId } = require('./utils'); +const { deployPackage, getBcsBytesByObjectId } = require('./utils'); const { singletonStruct } = require('./types-utils'); const { Transaction } = require('@mysten/sui/transactions'); const { addBaseOptions } = require('./cli-utils'); @@ -54,7 +54,7 @@ async function processCommand(config, chain, options) { } async function mainProcessor(options, processor) { - const config = loadSuiConfig(options.env); + const config = loadConfig(options.env); await processor(config, config.sui, options); saveConfig(config, options.env); diff --git a/sui/gas-service.js b/sui/gas-service.js index 1a6ba290..4b81be71 100644 --- a/sui/gas-service.js +++ b/sui/gas-service.js @@ -2,8 +2,9 @@ const { saveConfig, printInfo, printError } = require('../common/utils'); const { Command } = require('commander'); const { Transaction } = require('@mysten/sui/transactions'); const { bcs } = require('@mysten/sui/bcs'); +const { loadConfig } = require('../common/utils'); const { gasServiceStruct } = require('./types-utils'); -const { loadSuiConfig, getBcsBytesByObjectId } = require('./utils'); +const { getBcsBytesByObjectId } = require('./utils'); const { ethers } = require('hardhat'); const { getFormattedAmount } = require('./amount-utils'); const { @@ -160,7 +161,7 @@ async function processCommand(command, chain, args, options) { } async function mainProcessor(options, args, processor, command) { - const config = loadSuiConfig(options.env); + const config = loadConfig(options.env); await processor(command, config.sui, args, options); saveConfig(config, options.env); } diff --git a/sui/gateway.js b/sui/gateway.js index a26ef9ec..d46721de 100644 --- a/sui/gateway.js +++ b/sui/gateway.js @@ -8,10 +8,10 @@ const { constants: { HashZero }, } = ethers; +const { loadConfig } = require('../common/utils'); const { addBaseOptions } = require('./cli-utils'); const { getWallet, printWalletInfo, getRawPrivateKey, broadcast } = require('./sign-utils'); const { bytes32Struct, signersStruct, messageToSignStruct, messageStruct, proofStruct } = require('./types-utils'); -const { loadSuiConfig } = require('./utils'); const { getSigners } = require('./deploy-gateway'); const secp256k1 = require('secp256k1'); @@ -218,7 +218,7 @@ async function rotateSigners(keypair, client, config, chain, args, options) { } async function mainProcessor(processor, args, options) { - const config = loadSuiConfig(options.env); + const config = loadConfig(options.env); const [keypair, client] = getWallet(config.sui, options); await printWalletInfo(keypair, client, config.sui, options); diff --git a/sui/gmp.js b/sui/gmp.js index 786c5c55..5c3ab2a5 100644 --- a/sui/gmp.js +++ b/sui/gmp.js @@ -2,7 +2,8 @@ const { saveConfig, printInfo } = require('../common/utils'); const { Command } = require('commander'); const { Transaction } = require('@mysten/sui/transactions'); const { bcs } = require('@mysten/sui/bcs'); -const { loadSuiConfig, getBcsBytesByObjectId } = require('./utils'); +const { getBcsBytesByObjectId } = require('./utils'); +const { loadConfig } = require('../common/utils'); const { ethers } = require('hardhat'); const { utils: { arrayify }, @@ -165,7 +166,7 @@ async function processCommand(command, chain, args, options) { } async function mainProcessor(command, options, args, processor) { - const config = loadSuiConfig(options.env); + const config = loadConfig(options.env); await processor(command, config.sui, args, options); saveConfig(config, options.env); } diff --git a/sui/multisig.js b/sui/multisig.js index 9d69414d..2571ac8d 100644 --- a/sui/multisig.js +++ b/sui/multisig.js @@ -3,7 +3,7 @@ const { fromB64 } = require('@mysten/bcs'); const { addBaseOptions } = require('./cli-utils'); const { getWallet, getMultisig, signTransactionBlockBytes, broadcastSignature } = require('./sign-utils'); const { getSignedTx, storeSignedTx } = require('../evm/sign-utils'); -const { loadSuiConfig } = require('./utils'); +const { loadConfig } = require('../common/utils'); const { printInfo, validateParameters } = require('../common/utils'); async function signTx(keypair, client, options) { @@ -143,7 +143,7 @@ async function processCommand(chain, options) { } async function mainProcessor(options, processor) { - const config = loadSuiConfig(options.env); + const config = loadConfig(options.env); await processor(config.sui, options); } diff --git a/sui/transfer-object.js b/sui/transfer-object.js index 1d58ee6f..10919e00 100644 --- a/sui/transfer-object.js +++ b/sui/transfer-object.js @@ -3,7 +3,7 @@ const { Command, Option } = require('commander'); const { printInfo, validateParameters } = require('../common/utils'); const { addExtendedOptions } = require('./cli-utils'); const { getWallet, printWalletInfo } = require('./sign-utils'); -const { loadSuiConfig } = require('./utils'); +const { loadConfig } = require('../common/utils'); async function processCommand(chain, options) { const [keypair, client] = getWallet(chain, options); @@ -54,7 +54,7 @@ async function processCommand(chain, options) { } async function mainProcessor(options, processor) { - const config = loadSuiConfig(options.env); + const config = loadConfig(options.env); await processor(config.sui, options); } diff --git a/sui/utils.js b/sui/utils.js index 0d6574cc..eac07a8a 100644 --- a/sui/utils.js +++ b/sui/utils.js @@ -2,7 +2,7 @@ const { ethers } = require('hardhat'); const toml = require('toml'); -const { printInfo, loadConfig, printError } = require('../common/utils'); +const { printInfo, printError } = require('../common/utils'); const { BigNumber, utils: { arrayify, hexlify, toUtf8Bytes, keccak256 }, @@ -52,23 +52,6 @@ const getBcsBytesByObjectId = async (client, objectId) => { return fromB64(response.data.bcs.bcsBytes); }; -const loadSuiConfig = (env) => { - const config = loadConfig(env); - const suiEnv = env === 'local' ? 'localnet' : env; - - if (!config.sui) { - config.sui = { - networkType: suiEnv, - name: 'Sui', - contracts: { - AxelarGateway: {}, - }, - }; - } - - return config; -}; - const deployPackage = async (packageName, client, keypair, options = {}) => { const compileDir = `${__dirname}/move`; @@ -155,7 +138,6 @@ module.exports = { suiClockAddress, getAmplifierSigners, getBcsBytesByObjectId, - loadSuiConfig, deployPackage, findPublishedObject, readMovePackageName, From 1a78c793eb2cb7725edf05aae82de5b7e8ba5d9f Mon Sep 17 00:00:00 2001 From: npty Date: Thu, 1 Aug 2024 11:08:44 +0700 Subject: [PATCH 28/30] chore: reword --- sui/deploy-contract.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index 2b9ba94f..af002165 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -328,7 +328,7 @@ if (require.main === module) { const upgradeContractCmds = supportedPackages.map((supportedPackage) => { const { packageName } = supportedPackage; return new Command(packageName) - .description(`Deploy ${packageName} contract`) + .description(`Upgrade ${packageName} contract`) .command(`${packageName} `) .addOption(new Option('--sender ', 'transaction sender')) .addOption(new Option('--digest ', 'digest hash for upgrade')) From e56c0546dc363c568afa7cf9b8da73f72dc3e0c7 Mon Sep 17 00:00:00 2001 From: npty Date: Thu, 1 Aug 2024 11:09:24 +0700 Subject: [PATCH 29/30] chore: prettier --- sui/deploy-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sui/deploy-test.js b/sui/deploy-test.js index 88f8d463..6a9694f5 100644 --- a/sui/deploy-test.js +++ b/sui/deploy-test.js @@ -1,6 +1,6 @@ const { saveConfig, prompt, printInfo } = require('../common/utils'); const { Command } = require('commander'); -const { deployPackage, getBcsBytesByObjectId } = require('./utils'); +const { deployPackage, getBcsBytesByObjectId } = require('./utils'); const { singletonStruct } = require('./types-utils'); const { Transaction } = require('@mysten/sui/transactions'); const { addBaseOptions } = require('./cli-utils'); From fd2421e7f576479d8895fb19c14a69a14bd31d81 Mon Sep 17 00:00:00 2001 From: npty Date: Thu, 1 Aug 2024 11:11:44 +0700 Subject: [PATCH 30/30] chore: fix prettier --- sui/deploy-test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sui/deploy-test.js b/sui/deploy-test.js index 6a9694f5..9ab7bc77 100644 --- a/sui/deploy-test.js +++ b/sui/deploy-test.js @@ -1,4 +1,4 @@ -const { saveConfig, prompt, printInfo } = require('../common/utils'); +const { loadConfig, saveConfig, prompt, printInfo } = require('../common/utils'); const { Command } = require('commander'); const { deployPackage, getBcsBytesByObjectId } = require('./utils'); const { singletonStruct } = require('./types-utils');