Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sui): add init multisig command #349

Merged
merged 17 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 62 additions & 4 deletions .github/workflows/test-sui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,13 @@ jobs:
- name: Spin up Sui Network
run: nohup sh -c "sui-test-validator" > nohup.out 2> nohup.err < /dev/null &

- name: Sleep for 30 seconds
run: sleep 30s
shell: bash
- 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: |
Expand Down Expand Up @@ -139,7 +143,61 @@ jobs:

###### Command: Multisig ######

# TODO: Add multisig tests
- 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
Expand Down
19 changes: 11 additions & 8 deletions sui/faucet.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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>', '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();
}
87 changes: 84 additions & 3 deletions sui/multisig.js
Original file line number Diff line number Diff line change
@@ -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');
npty marked this conversation as resolved.
Show resolved Hide resolved
}

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;
Expand Down Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -153,12 +218,28 @@ if (require.main === module) {
addBaseOptions(program);

program.addOption(new Option('--txBlockPath <file>', 'path to unsigned tx block'));
program.addOption(new Option('--action <action>', 'action').choices(['sign', 'combine', 'execute']).makeOptionMandatory(true));
program.addOption(new Option('--action <action>', 'action').choices(['sign', 'combine', 'execute', 'init']).makeOptionMandatory(true));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@blockchainguyy Refactor this to use Commands like we do for other Sui scripts

program.addOption(new Option('--multisigKey <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 <file>', 'combined signature file path'));
program.addOption(new Option('--signatureFilePath <file>', 'signed signature will be stored'));
program.addOption(new Option('--executeResultPath <file>', '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>', 'threshold for multisig'));

program.action((options) => {
mainProcessor(options, processCommand);
Expand Down
Loading