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 9 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
54 changes: 51 additions & 3 deletions .github/workflows/test-sui.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ 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
- name: Sleep for 40 seconds
npty marked this conversation as resolved.
Show resolved Hide resolved
run: sleep 40s
shell: bash

- name: Setup Sui Local Network
Expand Down Expand Up @@ -139,7 +139,55 @@ 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)
ADDRESS_1=$(sui keytool export --key-identity multisig1 --json | jq -r .key.suiAddress)
ADDRESS_2=$(sui keytool export --key-identity multisig2 --json | jq -r .key.suiAddress)

# 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 $ADDRESS_1
node sui/faucet.js --recipient $ADDRESS_2
npty marked this conversation as resolved.
Show resolved Hide resolved
node sui/faucet.js --recipient $MULTISIG_ADDRESS

# Set environment variables
echo "KEY_1=$KEY_1" >> $GITHUB_ENV
echo "KEY_2=$KEY_2" >> $GITHUB_ENV
echo "ADDRESS_1=$ADDRESS_1" >> $GITHUB_ENV
echo "ADDRESS_2=$ADDRESS_2" >> $GITHUB_ENV
npty marked this conversation as resolved.
Show resolved Hide resolved
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: |
node sui/multisig.js --txBlockPath ./tx-upgrade.json --signatureFilePath ./combined.json --action combine --signatures signature-1.json signature-2.json
Copy link
Member

Choose a reason for hiding this comment

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

Test if gateway approve still works post upgrade

Copy link
Member

Choose a reason for hiding this comment

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

Is the contract address being updated to the new package id after upgrade?

Copy link
Member Author

Choose a reason for hiding this comment

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

@milapsheth Yes, it'll be upgraded to the new package id. Approval works with either the upgraded contract or the original contract. In this case, should we implement some mechanism to guard access to all prior versions of the package, because they're still exist on-chain?

Copy link
Member

Choose a reason for hiding this comment

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

Foivos is looking into the upgrade mechanism to create a design for our contracts. We want some methods to be accessible on the old contract potentially, and some to not be depending on upgrade versioning


###### 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();
}
74 changes: 71 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,7 @@ async function combineSignature(client, chain, options) {

if (!options.offline) {
const txResult = await broadcastSignature(client, txBlockBytes, combinedSignature);
printInfo('Transaction result', JSON.stringify(txResult));
printInfo('Executed', txResult.digest);
} else {
const data = {
message: firstSignData.message,
Expand All @@ -104,6 +158,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 +202,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,13 +213,21 @@ 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'));

// 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...]', 'weight for multisig'));
npty marked this conversation as resolved.
Show resolved Hide resolved
program.addOption(new Option('--schemeTypes [schemeTypes...]', 'scheme types for each base64 public keys'));
npty marked this conversation as resolved.
Show resolved Hide resolved
program.addOption(new Option('--threshold <threshold>', 'threshold for multisig'));

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