diff --git a/.github/actions/setup-sui/action.yaml b/.github/actions/setup-sui/action.yaml new file mode 100644 index 00000000..030604cf --- /dev/null +++ b/.github/actions/setup-sui/action.yaml @@ -0,0 +1,67 @@ +name: Setup Sui CLI +description: 'Setup Sui CLI and install dependencies' + +inputs: + SUI_VERSION: + description: 'The version of Sui CLI to install' + required: true + +runs: + using: 'composite' + + steps: + - name: Debug Action Input + shell: bash + run: echo "SUI_VERSION=${{ inputs.SUI_VERSION }}" + + - name: Install Dependencies + shell: bash + run: | + sudo apt-get update + sudo apt-get install -y libpq-dev + + - name: Cache Sui binaries + id: cache-sui + uses: actions/cache@v4 + with: + path: sui-cli/sui-binaries/ + key: sui-${{ inputs.SUI_VERSION }} + + - name: Download and Install Sui + shell: bash + if: steps.cache-sui.outputs.cache-hit != 'true' + run: | + mkdir -p sui-cli && cd sui-cli + curl -L -o sui-${{ inputs.SUI_VERSION }}-ubuntu-x86_64.tgz https://github.com/MystenLabs/sui/releases/download/${{ inputs.SUI_VERSION }}/sui-${{ inputs.SUI_VERSION }}-ubuntu-x86_64.tgz + tar -xvf sui-${{ inputs.SUI_VERSION }}-ubuntu-x86_64.tgz + mkdir -p sui-binaries + mv ./sui ./sui-binaries/ + mv ./sui-debug ./sui-binaries/ + mv ./sui-test-validator ./sui-binaries/ + rm -rf sui-${{ inputs.SUI_VERSION }}-ubuntu-x86_64.tgz + + - name: Save Sui binaries + if: steps.cache-sui.outputs.cache-hit != 'true' + id: cache-sui-save + uses: actions/cache@v4 + with: + path: sui-cli/sui-binaries/ + key: sui-${{ inputs.SUI_VERSION }} + + - name: Add Sui binaries to PATH + shell: bash + run: | + sudo cp ./sui-cli/sui-binaries/sui /usr/local/bin/sui + sudo cp ./sui-cli/sui-binaries/sui-debug /usr/local/bin/sui-debug + sudo cp ./sui-cli/sui-binaries/sui-test-validator /usr/local/bin/sui-test-validator + + - name: Setup Sui Wallet + shell: bash + run: | + echo -e "y\n\n1" | sui client envs + sui client new-address secp256k1 wallet + sui client switch --address wallet + SUI_PRIVATE_KEY=$(sui keytool export --key-identity wallet --json | jq .exportedPrivateKey | sed 's/"//g') + SUI_ADDRESS=$(sui keytool export --key-identity wallet --json | jq .key.suiAddress | sed 's/"//g') + echo "SUI_PRIVATE_KEY=${SUI_PRIVATE_KEY}" >> $GITHUB_ENV + echo "SUI_ADDRESS=${SUI_ADDRESS}" >> $GITHUB_ENV diff --git a/.github/workflows/test.yaml b/.github/workflows/test-evm.yaml similarity index 99% rename from .github/workflows/test.yaml rename to .github/workflows/test-evm.yaml index d773b858..4a3a2119 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test-evm.yaml @@ -1,9 +1,9 @@ -name: Test +name: Test Evm on: pull_request jobs: - test: + test-evm: runs-on: blacksmith-2vcpu-ubuntu-2204 steps: diff --git a/.github/workflows/test-sui.yaml b/.github/workflows/test-sui.yaml new file mode 100644 index 00000000..47d14a32 --- /dev/null +++ b/.github/workflows/test-sui.yaml @@ -0,0 +1,212 @@ +name: Test Sui + +on: + pull_request: + paths: + - sui/** + - common/** + - .github/actions/setup-sui/** + - .github/workflows/test-sui.yaml + +jobs: + test-sui: + name: Test Sui + runs-on: blacksmith-2vcpu-ubuntu-2204 + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Node.js + uses: useblacksmith/setup-node@v5 + with: + node-version: 18.x + cache: 'npm' + + - name: Install + run: npm ci + + - name: Read SUI_VERSION from JSON + id: read-json + run: | + SUI_VERSION=$(jq -r '.SUI_VERSION' sui/version.json) + echo "SUI_VERSION=${SUI_VERSION}" >> $GITHUB_ENV + + - name: Setup Sui CLI and install dependencies + uses: ./.github/actions/setup-sui + with: + SUI_VERSION: ${{ env.SUI_VERSION }} + + - name: Spin up Sui Network + run: nohup sh -c "sui-test-validator" > nohup.out 2> nohup.err < /dev/null & + + - name: Wait for Sui network + uses: nev7n/wait_for_response@v1 + with: + url: 'http://localhost:9123' + responseCode: 200 + timeout: 60000 + interval: 1000 + + - name: Setup Sui Local Network + run: | + sui client new-env --alias local --rpc http://127.0.0.1:9000 + sui client switch --env local + + - name: Prepare local.json + run: | + echo '{ + "sui": { + "name": "Sui", + "axelarId": "sui", + "networkType": "localnet", + "tokenSymbol": "SUI", + "rpc": "http://127.0.0.1:9000", + "faucetUrl": "http://127.0.0.1:9123", + "contracts": {} + } + }' > ./axelar-chains-config/info/local.json + + # Create .env file with default hardhat private key that's prefunded + - name: Prepare .env + run: | + echo "PRIVATE_KEY=$SUI_PRIVATE_KEY" >> .env + echo 'ENV=local' >> .env + echo 'SKIP_EXISTING = true' >> .env + + - name: Display local.json + run: cat ./axelar-chains-config/info/local.json + + - name: Request SUI from faucet + run: node sui/faucet.js + + ###### Command: Deploy Contract ###### + + - name: Deploy AxelarGateway + run: node sui/deploy-contract deploy AxelarGateway --signers wallet + + - name: Deploy GasService + run: node sui/deploy-contract deploy GasService + + - name: Deploy Operators + run: node sui/deploy-contract deploy Operators + + - name: Deploy Example + run: node sui/deploy-contract deploy Example + + ###### Command: Gas Service ###### + + - name: Pay Gas + run: node sui/gas-service.js payGas --amount 100 ethereum 0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05 0xba76c6980428A0b10CFC5d8ccb61949677A61233 0x1234 + + - name: Refund Gas + run: node sui/gas-service.js refund 0x32034b47cb29d162d9d803cc405356f4ac0ec07fe847ace431385fe8acf3e6e5-2 --amount 1 + + - name: Collect Gas + run: node sui/gas-service.js collectGas --amount 0.1 + + ###### Command: Gateway ###### + - name: Gateway Approve + run: node sui/gateway.js approve --proof wallet ethereum 0x32034b47cb29d162d9d803cc405356f4ac0ec07fe847ace431385fe8acf3e6e5-2 0x4F4495243837681061C4743b74B3eEdf548D56A5 0x6ce0d81b412abca2770eddb1549c9fcff721889c3aab1203dc93866db22ecc4b 0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432 + + - name: Gateway Call Contract + run: node sui/gateway.js call-contract ethereum 0x4F4495243837681061C4743b74B3eEdf548D56A5 0x1234 + + - name: Gateway Rotate Signers + run: node sui/gateway.js rotate --signers wallet --proof wallet --newNonce test2 + + ###### Command: GMP ###### + + - name: Execute Outgoing Call Contract + run: node sui/gmp.js sendCall ethereum 0x6f24A47Fc8AE5441Eb47EFfC3665e70e69Ac3F05 0.1 0x1234 + + - name: Execute Incoming Call Contract + run: | + channel_id=$(cat axelar-chains-config/info/local.json | jq '.sui.contracts.Example.objects.ChannelId' | sed 's/"//g') + echo "Channel ID: $channel_id" + node sui/gateway.js approve --proof wallet ethereum 0x32034b47cb29d162d9d803cc405356f4ac0ec07fe847ace431385fe8acf3e6e5-3 0x4F4495243837681061C4743b74B3eEdf548D56A5 $channel_id 0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432 + node sui/gmp.js execute ethereum 0x32034b47cb29d162d9d803cc405356f4ac0ec07fe847ace431385fe8acf3e6e5-3 0x4F4495243837681061C4743b74B3eEdf548D56A5 0x1234 + + ###### Command: Operators ###### + + - name: Store Capability Object in Operators + run: node sui/operators.js storeCap + + - name: Add Operator + run: node sui/operators.js add $SUI_ADDRESS + + - name: Collect Gas with Operator + run: node sui/operators.js collectGas --amount 1 + + - name: Refund Gas with Operator + run: node sui/operators.js refund 0x32034b47cb29d162d9d803cc405356f4ac0ec07fe847ace431385fe8acf3e6e5-2 --amount 1 + + - name: Remove Operator + run: node sui/operators.js remove $SUI_ADDRESS + + ###### Command: Generate Keypair ###### + - name: Generate Keypair + run: node sui/generate-keypair.js + + ###### Command: Multisig ###### + + - name: Init Multisig + run: | + # Create new addresses + sui client new-address secp256k1 multisig1 + sui client new-address secp256k1 multisig2 + + # Export keys and addresses + KEY_1=$(sui keytool export --key-identity multisig1 --json | jq -r .key.publicBase64Key) + KEY_2=$(sui keytool export --key-identity multisig2 --json | jq -r .key.publicBase64Key) + + # Get multisig address + MULTISIG_ADDRESS=$(sui keytool multi-sig-address --pks $KEY_1 $KEY_2 --weights 1 1 --threshold 1 --json | jq -r .multisigAddress) + + # Initialize multisig + node sui/multisig.js --action init --threshold 1 --base64PublicKeys $KEY_1 $KEY_2 --schemeTypes secp256k1 secp256k1 + + # Faucet operations + node sui/faucet.js --recipient $MULTISIG_ADDRESS + + # Set environment variables + echo "MULTISIG_ADDRESS=$MULTISIG_ADDRESS" >> $GITHUB_ENV + + - name: Transfer Upgrade Cap to Multisig Address + run: | + upgrade_cap=$(cat axelar-chains-config/info/local.json | jq -r '.sui.contracts.AxelarGateway.objects.UpgradeCap') + node sui/transfer-object.js --objectId $upgrade_cap --recipient $MULTISIG_ADDRESS + + - name: Generate Unsigned Tx File + run: | + node sui/deploy-contract.js upgrade AxelarGateway any_upgrade --offline --txFilePath ./tx-upgrade.json --sender $MULTISIG_ADDRESS + + - name: Sign Tx File with Multisig Signer + run: | + pk_1=$(sui keytool export --key-identity multisig1 --json | jq .exportedPrivateKey | sed 's/"//g') + pk_2=$(sui keytool export --key-identity multisig2 --json | jq .exportedPrivateKey | sed 's/"//g') + node sui/multisig.js --action sign --txBlockPath ./tx-upgrade.json --signatureFilePath signature-1.json --offline --privateKey $pk_1 + node sui/multisig.js --action sign --txBlockPath ./tx-upgrade.json --signatureFilePath signature-2.json --offline --privateKey $pk_2 + + - name: Submit Signed Tx File + run: | + # Define output file for the executed transaction + output_file="./output.json" + + # Execute the upgrade transaction + node sui/multisig.js --txBlockPath ./tx-upgrade.json --signatureFilePath ./combined.json --action combine --signatures signature-1.json signature-2.json --executeResultPath ${output_file} + + # Store the new package id in a variable + new_package_id=$(jq '.objectChanges[] | select(.type == "published") | .packageId' $output_file | sed 's/"//g') + + # Update the local.json file with the new package id + jq --arg pkg "$new_package_id" '.sui.contracts.AxelarGateway.address = $pkg' axelar-chains-config/info/local.json > temp.json \ + && mv temp.json axelar-chains-config/info/local.json + + - name: Post Upgrade Gateway Approval With New Package ID + run: node sui/gateway.js approve --proof wallet ethereum 0x32034b47cb29d162d9d803cc405356f4ac0ec07fe847ace431385fe8acf3e6e5-10 0x4F4495243837681061C4743b74B3eEdf548D56A5 0x6ce0d81b412abca2770eddb1549c9fcff721889c3aab1203dc93866db22ecc4b 0x56570de287d73cd1cb6092bb8fdee6173974955fdef345ae579ee9f475ea7432 + + ###### Command: Transfer Object ###### + - name: Transfer Object + run: | + object_id=$(sui client objects --json | jq -r '.[-1].data.objectId') + node sui/transfer-object.js --objectId $object_id --recipient 0xdd7c964ff032273889eb6029a29314413b461629c45c0442c6f9cf8342450c12 diff --git a/package-lock.json b/package-lock.json index 1c96f88d..4e5b0c8d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "MIT", "dependencies": { "@axelar-network/axelar-cgp-solidity": "6.3.1", - "@axelar-network/axelar-cgp-sui": "0.0.0-snapshot.218635e", + "@axelar-network/axelar-cgp-sui": "^0.4.1", "@axelar-network/axelar-gmp-sdk-solidity": "5.10.0", "@axelar-network/interchain-token-service": "1.2.4", "@cosmjs/cosmwasm-stargate": "^0.32.1", @@ -176,15 +176,14 @@ } }, "node_modules/@axelar-network/axelar-cgp-sui": { - "version": "0.0.0-snapshot.218635e", - "resolved": "https://registry.npmjs.org/@axelar-network/axelar-cgp-sui/-/axelar-cgp-sui-0.0.0-snapshot.218635e.tgz", - "integrity": "sha512-+k72piOoq6oz/NFMX57keooSP/OZ7FG8IMPbx5Y8BBIKzxusQzMovZ0inz+StJFfd4i+Ho+FlN98m8tPIqLypg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@axelar-network/axelar-cgp-sui/-/axelar-cgp-sui-0.4.1.tgz", + "integrity": "sha512-7ZPotOheP0ZxSm++SHsjpvTTsTuyJx747U3S0Ud+3RVXNBMnMCuC0jZsoyqD0sJMJdQLmT4pz/fFOIPCWAhjhw==", "dependencies": { "@cosmjs/cosmwasm-stargate": "^0.32.2", "@mysten/sui": "^1.3.0", "ethers": "^5.0.0", "secp256k1": "^5.0.0", - "tmp": "^0.2.1", "typescript": "^5.3.3" }, "engines": { @@ -9935,14 +9934,6 @@ "node": ">= 0.12" } }, - "node_modules/tmp": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", - "engines": { - "node": ">=14.14" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", diff --git a/package.json b/package.json index d136354b..12fc79e1 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "main": "index.js", "scripts": { "lint": "eslint --fix '**/*.js'", - "prettier": "prettier --write '**/*.js' 'axelar-chains-config/info/*.json' 'package.json' 'evm/**/*.json' '.github/**/*.yaml'" + "prettier": "prettier --write '**/*.js' 'axelar-chains-config/info/*.json' 'package.json' 'evm/**/*.json' '.github/**/*.yaml'", + "test:sui": "mocha sui" }, "repository": { "type": "git", @@ -23,7 +24,7 @@ "homepage": "https://github.com/axelarnetwork/axelar-contract-deployments#readme", "dependencies": { "@axelar-network/axelar-cgp-solidity": "6.3.1", - "@axelar-network/axelar-cgp-sui": "0.0.0-snapshot.218635e", + "@axelar-network/axelar-cgp-sui": "^0.4.1", "@axelar-network/axelar-gmp-sdk-solidity": "5.10.0", "@axelar-network/interchain-token-service": "1.2.4", "@cosmjs/cosmwasm-stargate": "^0.32.1", diff --git a/sui/README.md b/sui/README.md index 636858d4..ef5f4a74 100644 --- a/sui/README.md +++ b/sui/README.md @@ -100,7 +100,7 @@ node sui/deploy-contract.js deploy GasService Deploy the test GMP package: ```bash -node sui/deploy-contract.js deploy Test +node sui/deploy-contract.js deploy Example ``` Deploy the Operators package: diff --git a/sui/deploy-contract.js b/sui/deploy-contract.js index fd8b454e..9aa99b58 100644 --- a/sui/deploy-contract.js +++ b/sui/deploy-contract.js @@ -27,6 +27,7 @@ const { getSingletonChannelId, getItsChannelId, getSquidChannelId, + checkSuiVersionMatch, } = require('./utils'); /** @@ -42,7 +43,7 @@ const { * 2. Ensure the corresponding folder exists in the specified path * */ -const PACKAGE_DIRS = ['gas_service', 'test', 'axelar_gateway', 'operators', 'abi', 'governance', 'its', 'squid']; +const PACKAGE_DIRS = ['gas_service', 'example', 'axelar_gateway', 'operators', 'abi', 'governance', 'its', 'squid']; /** * Package Mapping Object for Command Options and Post-Deployment Functions @@ -51,7 +52,7 @@ const PACKAGE_CONFIGS = { cmdOptions: { AxelarGateway: () => GATEWAY_CMD_OPTIONS, GasService: () => [], - Test: () => [], + Example: () => [], Operators: () => [], Abi: () => [], Governance: () => [], @@ -61,7 +62,7 @@ const PACKAGE_CONFIGS = { postDeployFunctions: { AxelarGateway: postDeployAxelarGateway, GasService: postDeployGasService, - Test: postDeployTest, + Example: postDeployExample, Operators: postDeployOperators, Abi: {}, Governance: {}, @@ -105,16 +106,16 @@ async function postDeployGasService(published, keypair, client, config, chain, o }; } -async function postDeployTest(published, keypair, client, config, chain, options) { +async function postDeployExample(published, keypair, client, config, chain, options) { const relayerDiscovery = config.sui.contracts.AxelarGateway?.objects?.RelayerDiscovery; - const [singletonObjectId] = getObjectIdsByObjectTypes(published.publishTxn, [`${published.packageId}::test::Singleton`]); + const [singletonObjectId] = getObjectIdsByObjectTypes(published.publishTxn, [`${published.packageId}::gmp::Singleton`]); const channelId = await getSingletonChannelId(client, singletonObjectId); - chain.contracts.Test.objects = { Singleton: singletonObjectId, ChannelId: channelId }; + chain.contracts.Example.objects = { Singleton: singletonObjectId, ChannelId: channelId }; const tx = new Transaction(); tx.moveCall({ - target: `${published.packageId}::test::register_transaction`, + target: `${published.packageId}::gmp::register_transaction`, arguments: [tx.object(relayerDiscovery), tx.object(singletonObjectId)], }); @@ -195,7 +196,7 @@ async function postDeployAxelarGateway(published, keypair, client, config, chain // Update chain configuration chain.contracts.AxelarGateway = { - address: packageId, + ...chain.contracts.AxelarGateway, objects: { Gateway: gateway, RelayerDiscovery: relayerDiscovery, @@ -246,6 +247,9 @@ async function postDeploySquid(published, keypair, client, config, chain, option async function deploy(keypair, client, supportedContract, config, chain, options) { const { packageDir, packageName } = supportedContract; + // Print warning if version mismatch from defined version in version.json + checkSuiVersionMatch(); + // Deploy package const published = await deployPackage(packageDir, client, keypair, options); diff --git a/sui/docs/gmp.md b/sui/docs/gmp.md index 77529797..a12ea268 100644 --- a/sui/docs/gmp.md +++ b/sui/docs/gmp.md @@ -57,7 +57,6 @@ This command will execute the message to the contract that associated with the g node sui/gmp.js execute ethereum 0x32034b47cb29d162d9d803cc405356f4ac0ec07fe847ace431385fe8acf3e6e5-2 0x4F4495243837681061C4743b74B3eEdf548D56A5 0x1234 --channelId 0xcd5d203ea2cf1139af83939e3f74114a31fe682cc90f73a0d2647956bc3e5acf ``` - Note: - `source`, `sourceAddress` and `messageId` needed to be matched with the approve command. - `payload` must be associated with the `payloadHash` in the approve command. diff --git a/sui/faucet.js b/sui/faucet.js index a52acdb1..2ffb69b0 100644 --- a/sui/faucet.js +++ b/sui/faucet.js @@ -3,19 +3,20 @@ const { requestSuiFromFaucetV0, getFaucetHost } = require('@mysten/sui/faucet'); const { saveConfig, loadConfig, printInfo } = require('../common/utils'); const { getWallet, printWalletInfo, addBaseOptions } = require('./utils'); -const { Command } = require('commander'); +const { Command, Option } = require('commander'); async function processCommand(config, chain, options) { const [keypair, client] = getWallet(chain, options); + const recipient = options.recipient || keypair.toSuiAddress(); await printWalletInfo(keypair, client, chain, options); await requestSuiFromFaucetV0({ host: getFaucetHost(chain.networkType), - recipient: keypair.toSuiAddress(), + recipient, }); - printInfo('Funds requested'); + printInfo('Funds requested', recipient); } async function mainProcessor(options, processor) { @@ -27,13 +28,15 @@ async function mainProcessor(options, processor) { if (require.main === module) { const program = new Command(); - program.name('faucet').description('Query the faucet for funds.'); + program + .name('faucet') + .addOption(new Option('--recipient ', 'recipient to request funds for')) + .description('Query the faucet for funds.') + .action((options) => { + mainProcessor(options, processCommand); + }); addBaseOptions(program); - program.action((options) => { - mainProcessor(options, processCommand); - }); - program.parse(); } diff --git a/sui/gmp.js b/sui/gmp.js index 6aa5ccf2..9751b1f5 100644 --- a/sui/gmp.js +++ b/sui/gmp.js @@ -1,9 +1,8 @@ const { Command } = require('commander'); const { Transaction } = require('@mysten/sui/transactions'); const { bcs } = require('@mysten/sui/bcs'); -const { saveConfig, printInfo } = require('../common/utils'); +const { loadConfig, saveConfig, printInfo } = require('../common/utils'); const { - loadConfig, getBcsBytesByObjectId, addBaseOptions, addOptionsToCommands, @@ -22,9 +21,9 @@ async function sendCommand(keypair, client, contracts, args, options) { const [destinationChain, destinationAddress, feeAmount, payload] = args; const params = options.params; - const [testConfig, gasServiceConfig] = contracts; + const [exampleConfig, gasServiceConfig] = contracts; const gasServiceObjectId = gasServiceConfig.objects.GasService; - const singletonObjectId = testConfig.objects.Singleton; + const singletonObjectId = exampleConfig.objects.Singleton; const unitAmount = getUnitAmount(feeAmount); const walletAddress = keypair.toSuiAddress(); @@ -34,7 +33,7 @@ async function sendCommand(keypair, client, contracts, args, options) { const [coin] = tx.splitCoins(tx.gas, [unitAmount]); tx.moveCall({ - target: `${testConfig.address}::test::send_call`, + target: `${exampleConfig.address}::gmp::send_call`, arguments: [ tx.object(singletonObjectId), tx.object(gasServiceObjectId), @@ -53,18 +52,18 @@ async function sendCommand(keypair, client, contracts, args, options) { } async function execute(keypair, client, contracts, args, options) { - const [testConfig, , axelarGatewayConfig] = contracts; + const [exampleConfig, , axelarGatewayConfig] = contracts; const [sourceChain, messageId, sourceAddress, payload] = args; 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; + // Get the channel id from the options or use the channel id from the deployed Example contract object. + const channelId = options.channelId || exampleConfig.objects.ChannelId; if (!channelId) { - throw new Error('Please provide either a channel id (--channelId) or deploy the test contract'); + throw new Error('Please provide either a channel id (--channelId) or deploy the Example contract'); } // Get Discovery table id from discovery object @@ -151,7 +150,7 @@ async function processCommand(command, chain, args, options) { await printWalletInfo(keypair, client, chain, options); - const contracts = [chain.contracts.Test, chain.contracts.GasService, chain.contracts.AxelarGateway]; + const contracts = [chain.contracts.Example, chain.contracts.GasService, chain.contracts.AxelarGateway]; await command(keypair, client, contracts, args, options); } diff --git a/sui/multisig.js b/sui/multisig.js index c09fef3a..a1cfbd9e 100644 --- a/sui/multisig.js +++ b/sui/multisig.js @@ -1,9 +1,63 @@ const { Command, Option } = require('commander'); const { fromB64 } = require('@mysten/bcs'); -const { loadConfig, printInfo, validateParameters } = require('../common/utils'); +const { saveConfig } = require('../common/utils'); +const { loadConfig, printInfo, validateParameters, printWarn } = require('../common/utils'); const { getSignedTx, storeSignedTx } = require('../evm/sign-utils'); const { addBaseOptions, getWallet, getMultisig, signTransactionBlockBytes, broadcastSignature } = require('./utils'); +async function initMultisigConfig(chain, options) { + const { base64PublicKeys, threshold } = options; + + if (!base64PublicKeys) { + throw new Error('Please provide public keys with --base64PublicKeys option'); + } + + if (!threshold) { + throw new Error('Please provide threshold with --threshold option'); + } + + const uniqueKeys = new Set(base64PublicKeys); + + if (uniqueKeys.size !== base64PublicKeys.length) { + throw new Error('Duplicate public keys found'); + } + + const schemeTypes = options.schemeTypes || Array(base64PublicKeys.length).fill('secp256k1'); + const weights = options.weights || Array(base64PublicKeys.length).fill(1); + + if (!options.schemeTypes) { + printWarn('Scheme types not provided, defaulting to secp256k1'); + } + + if (!options.weights) { + printWarn('Weights not provided, defaulting to 1'); + } + + if (base64PublicKeys.length !== weights.length) { + throw new Error('Public keys and weights length mismatch'); + } + + if (base64PublicKeys.length !== schemeTypes.length) { + throw new Error('Public keys and scheme types length mismatch'); + } + + const signers = base64PublicKeys.map((key, i) => ({ + publicKey: key, + weight: parseInt(weights[i]), + schemeType: options.schemeTypes[i], + })); + + chain.multisig = { + signers, + threshold: parseInt(threshold), + }; + + printInfo('Saved multisig config'); + + // To print in the separate lines with proper indentation + printInfo(JSON.stringify(chain.multisig, null, 2)); +} + async function signTx(keypair, client, options) { const txFileData = getSignedTx(options.txBlockPath); const txData = txFileData?.unsignedTx; @@ -85,7 +139,12 @@ async function combineSignature(client, chain, options) { if (!options.offline) { const txResult = await broadcastSignature(client, txBlockBytes, combinedSignature); - printInfo('Transaction result', JSON.stringify(txResult)); + + if (options.executeResultPath) { + storeSignedTx(options.executeResultPath, txResult); + } + + printInfo('Executed', txResult.digest); } else { const data = { message: firstSignData.message, @@ -104,6 +163,11 @@ async function processCommand(chain, options) { let fileData; switch (options.action) { + case 'init': { + fileData = await initMultisigConfig(chain, options); + break; + } + case 'sign': { fileData = await signTx(keypair, client, options); break; @@ -143,6 +207,7 @@ async function processCommand(chain, options) { async function mainProcessor(options, processor) { const config = loadConfig(options.env); await processor(config.sui, options); + saveConfig(config, options.env); } if (require.main === module) { @@ -153,12 +218,28 @@ if (require.main === module) { addBaseOptions(program); program.addOption(new Option('--txBlockPath ', 'path to unsigned tx block')); - program.addOption(new Option('--action ', 'action').choices(['sign', 'combine', 'execute']).makeOptionMandatory(true)); + program.addOption(new Option('--action ', 'action').choices(['sign', 'combine', 'execute', 'init']).makeOptionMandatory(true)); program.addOption(new Option('--multisigKey ', 'multisig key').env('MULTISIG_KEY')); program.addOption(new Option('--signatures [files...]', 'array of signed transaction files')); program.addOption(new Option('--offline', 'run in offline mode')); program.addOption(new Option('--combinedSignPath ', 'combined signature file path')); program.addOption(new Option('--signatureFilePath ', 'signed signature will be stored')); + program.addOption(new Option('--executeResultPath ', 'execute result will be stored')); + + // The following options are only used with the init action + program.addOption( + new Option('--base64PublicKeys [base64PublicKeys...]', 'An array of public keys to use for init the multisig address'), + ); + program.addOption( + new Option('--weights [weights...]', 'An array of weight for each base64 public key. The default value is 1 for each'), + ); + program.addOption( + new Option( + '--schemeTypes [schemeTypes...]', + 'An array of scheme types for each base64 public key. The default value is secp256k1 for each', + ), + ); + program.addOption(new Option('--threshold ', 'threshold for multisig')); program.action((options) => { mainProcessor(options, processCommand); diff --git a/sui/transfer-object.js b/sui/transfer-object.js index 2d786127..47f5d41a 100644 --- a/sui/transfer-object.js +++ b/sui/transfer-object.js @@ -1,7 +1,7 @@ const { Transaction } = require('@mysten/sui/transactions'); const { Command, Option } = require('commander'); const { loadConfig, printInfo, validateParameters } = require('../common/utils'); -const { getWallet, printWalletInfo, addExtendedOptions } = require('./utils'); +const { getWallet, printWalletInfo, addExtendedOptions, broadcast } = require('./utils'); async function processCommand(chain, options) { const [keypair, client] = getWallet(chain, options); @@ -38,17 +38,9 @@ async function processCommand(chain, options) { const tx = new Transaction(); tx.transferObjects([`${objectId}`], tx.pure.address(recipient)); - const result = await client.signAndExecuteTransaction({ - transactionBlock: tx, - signer: keypair, - options: { - showObjectChanges: true, - showBalanceChanges: true, - showEvents: true, - }, - }); + const result = await broadcast(client, keypair, tx); - printInfo('Transaction result', JSON.stringify(result)); + printInfo('Object Transferred', result.digest); } async function mainProcessor(options, processor) { diff --git a/sui/utils/utils.js b/sui/utils/utils.js index 46753402..7d083767 100644 --- a/sui/utils/utils.js +++ b/sui/utils/utils.js @@ -2,7 +2,8 @@ const { ethers } = require('hardhat'); const toml = require('toml'); -const { printInfo, printError } = require('../../common/utils'); +const { execSync } = require('child_process'); +const { printInfo, printError, printWarn } = require('../../common/utils'); const { BigNumber, utils: { arrayify, hexlify, toUtf8Bytes, keccak256 }, @@ -73,6 +74,33 @@ const findPublishedObject = (published, packageDir, contractName) => { return published.publishTxn.objectChanges.find((change) => change.objectType === `${packageId}::${packageDir}::${contractName}`); }; +const getInstalledSuiVersion = () => { + const suiVersion = execSync('sui --version').toString().trim(); + return parseVersion(suiVersion); +}; + +const getDefinedSuiVersion = () => { + const version = fs.readFileSync(`${__dirname}/../version.json`, 'utf8'); + const suiVersion = JSON.parse(version).SUI_VERSION; + return parseVersion(suiVersion); +}; + +const parseVersion = (version) => { + const versionMatch = version.match(/\d+\.\d+\.\d+/); + return versionMatch[0]; +}; + +const checkSuiVersionMatch = () => { + const installedVersion = getInstalledSuiVersion(); + const definedVersion = getDefinedSuiVersion(); + + if (installedVersion !== definedVersion) { + printWarn('Version mismatch detected:'); + printWarn(`- Installed SUI version: ${installedVersion}`); + printWarn(`- Version used for tests: ${definedVersion}`); + } +}; + const readMovePackageName = (moveDir) => { try { const moveToml = fs.readFileSync( @@ -220,6 +248,7 @@ module.exports = { paginateAll, suiPackageAddress, suiClockAddress, + checkSuiVersionMatch, findOwnedObjectId, getBcsBytesByObjectId, deployPackage, diff --git a/sui/version.json b/sui/version.json new file mode 100644 index 00000000..32a8c3ef --- /dev/null +++ b/sui/version.json @@ -0,0 +1,3 @@ +{ + "SUI_VERSION": "mainnet-v1.25.3" +}