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: initial sui deployment scripts #240

Merged
merged 28 commits into from
May 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
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
12 changes: 12 additions & 0 deletions axelar-chains-config/info/testnet.json
Original file line number Diff line number Diff line change
Expand Up @@ -2027,6 +2027,18 @@
"tokenSymbol": "XLM",
"contracts": {}
},
"sui": {
"name": "Sui",
"axelarId": "sui",
"networkType": "testnet",
"rpc": "https://fullnode.testnet.sui.io:443",
"tokenSymbol": "SUI",
"explorer": {
"name": "Suiscan",
"url": "https://suiscan.xyz"
},
"contracts": {}
},
"axelar": {
"id": "Axelarnet",
"axelarId": "Axelarnet",
Expand Down
799 changes: 707 additions & 92 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"homepage": "https://github.com/axelarnetwork/axelar-contract-deployments#readme",
"dependencies": {
"@0xpolygonhermez/zkevm-commonjs": "github:0xpolygonhermez/zkevm-commonjs#v1.0.0",
"@axelar-network/axelar-cgp-sui": "https://github.com/axelarnetwork/axelar-cgp-sui.git#main",
"@axelar-network/axelar-cgp-solidity": "6.3.1",
"@axelar-network/axelar-gmp-sdk-solidity": "5.9.0",
"@axelar-network/interchain-token-service": "1.2.4",
Expand Down
61 changes: 61 additions & 0 deletions sui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# Sui deployment scripts

## Prerequisites

Install Sui CLI: `brew install sui`

A Sui keypair can be created as follows.

1. Using Sui CLI:

```bash
sui client new-address secp256k1 wallet

# Export private key in bech32 format
sui keytool export --key-identity wallet
```

2. Using the script

```bash
node sui/generate-keypair.js
```

Set `PRIVATE_KEY=[suiprivkey...]` in your `.env` file. Other private key types are supported via `--privateKeyType` and `--signatureScheme` flags.

If you want to run against a local Sui network, then create a `local.json` config containing:

```bash
{
"sui": {
"name": "Sui",
"axelarId": "sui",
"networkType": "localnet",
"tokenSymbol": "SUI",
"rpc": "[local rpc]",
"contracts": {}
}
}
```

Use the `-e local` (or `ENV=local` in the `.env` config) flag with scripts to run against the local network.

## Scripts

1. Faucet: To get test SUI coins to your address.

```bash
node sui/faucet.js
```

2. Deploy the gateway:

```bash
node sui/deploy-gateway.js -e testnet --signers '{"signers": [{"pubkey": "0x020194ead85b350d90472117e6122cf1764d93bf17d6de4b51b03d19afc4d6302b", "weight": 1}], "threshold": 1, "nonce": ""}'
```

3. Deploy the test GMP package:

```bash
node sui/deploy-test.js
```
46 changes: 46 additions & 0 deletions sui/cli-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
'use strict';

require('dotenv').config();

const { Option } = require('commander');

const addBaseOptions = (program, options = {}) => {
program.addOption(
new Option('-e, --env <env>', 'environment')
.choices(['local', 'devnet', 'devnet-amplifier', 'devnet-verifiers', 'stagenet', 'testnet', 'mainnet'])
.default('testnet')
.makeOptionMandatory(true)
.env('ENV'),
);
program.addOption(new Option('-y, --yes', 'skip deployment prompt confirmation').env('YES'));
program.addOption(new Option('--gasOptions <gasOptions>', 'gas options cli override'));

if (!options.ignorePrivateKey) {
program.addOption(new Option('-p, --privateKey <privateKey>', 'private key').makeOptionMandatory(true).env('PRIVATE_KEY'));

program.addOption(
new Option('--privateKeyType <privateKeyType>', 'private key type')
.makeOptionMandatory(true)
.choices(['bech32', 'mnemonic', 'hex'])
.default('bech32')
.env('PRIVATE_KEY_TYPE'),
);

program.addOption(
new Option('--signatureScheme <signatureScheme>', 'signature scheme to use')
.choices(['secp256k1', 'ed25519', 'secp256r1'])
.default('secp256k1')
.env('SIGNATURE_SCHEME'),
);
}

if (options.address) {
program.addOption(new Option('--address <address>', 'override contract address'));
}

return program;
};

module.exports = {
addBaseOptions,
};
153 changes: 153 additions & 0 deletions sui/deploy-gateway.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
const { saveConfig, prompt, printInfo } = require('../evm/utils');
const { Command, Option } = require('commander');
const { publishPackage, updateMoveToml } = require('@axelar-network/axelar-cgp-sui/scripts/publish-package');
const { TransactionBlock } = require('@mysten/sui.js/transactions');
const { bcs } = require('@mysten/sui.js/bcs');
const { ethers } = require('hardhat');
const {
utils: { arrayify, hexlify },
constants: { HashZero },
} = ethers;

const { addBaseOptions } = require('./cli-utils');
const { getWallet, printWalletInfo } = require('./sign-utils');
const { getAmplifierSigners, loadSuiConfig } = require('./utils');

async function getSigners(config, chain, options) {
if (options.signers) {
printInfo('Using provided signers', options.signers);

const signers = JSON.parse(options.signers);
return {
signers: signers.signers.map(({ pubkey, weight }) => {
return { pubkey: arrayify(pubkey), weight };
}),
threshold: signers.threshold,
nonce: signers.nonce || HashZero,
};
}

return getAmplifierSigners(config, chain);
}

async function processCommand(config, chain, options) {
const [keypair, client] = getWallet(chain, options);

await printWalletInfo(keypair, client, chain, options);

if (!chain.contracts.axelar_gateway) {
chain.contracts.axelar_gateway = {};
}

const contractConfig = chain.contracts.axelar_gateway;
const { minimumRotationDelay, domainSeparator } = options;
const signers = await getSigners(config, chain, options);
const operator = options.operator || keypair.toSuiAddress();

if (prompt(`Proceed with deployment on ${chain.name}?`, options.yes)) {
return;
}

const published = await publishPackage('axelar_gateway', client, keypair);
const packageId = published.packageId;

updateMoveToml('axelar_gateway', packageId);

const creatorCap = published.publishTxn.objectChanges.find((change) => change.objectType === `${packageId}::gateway::CreatorCap`);
const relayerDiscovery = published.publishTxn.objectChanges.find(
(change) => change.objectType === `${packageId}::discovery::RelayerDiscovery`,
);

const signerStruct = bcs.struct('WeightedSigner', {
pubkey: bcs.vector(bcs.u8()),
weight: bcs.u128(),
});
const bytes32Struct = bcs.fixedArray(32, bcs.u8()).transform({
input: (id) => arrayify(id),
output: (id) => hexlify(id),
});

const signersStruct = bcs.struct('WeightedSigners', {
signers: bcs.vector(signerStruct),
threshold: bcs.u128(),
nonce: bytes32Struct,
});

const encodedSigners = signersStruct
.serialize({
...signers,
nonce: bytes32Struct.serialize(signers.nonce).toBytes(),
})
.toBytes();

const tx = new TransactionBlock();

const separator = tx.moveCall({
target: `${packageId}::bytes32::new`,
arguments: [tx.pure(arrayify(domainSeparator))],
});

tx.moveCall({
target: `${packageId}::gateway::setup`,
arguments: [
tx.object(creatorCap.objectId),
tx.pure.address(operator),
separator,
tx.pure(minimumRotationDelay),
tx.pure(bcs.vector(bcs.u8()).serialize(encodedSigners).toBytes()),
tx.object('0x6'),
],
});
const result = await client.signAndExecuteTransactionBlock({
transactionBlock: tx,
signer: keypair,
options: {
showEffects: true,
showObjectChanges: true,
showContent: true,
},
});

const gateway = result.objectChanges.find((change) => change.objectType === `${packageId}::gateway::Gateway`);

contractConfig.address = packageId;
contractConfig.objects = {
gateway: gateway.objectId,
relayerDiscovery: relayerDiscovery.objectId,
};
contractConfig.domainSeparator = domainSeparator;
contractConfig.operator = operator;
contractConfig.minimumRotationDelay = minimumRotationDelay;

printInfo('Gateway deployed', JSON.stringify(contractConfig, 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-gateway').description('Deploys/publishes the Sui gateway');

addBaseOptions(program);

program.addOption(new Option('--signers <signers>', 'JSON with the initial signer set').env('SIGNERS'));
program.addOption(new Option('--operator <operator>', 'operator for the gateway (defaults to the deployer address)').env('OPERATOR'));
program.addOption(
new Option('--minimumRotationDelay <minimumRotationDelay>', 'minium delay for signer rotations (in ms)').default(
24 * 60 * 60 * 1000,
),
); // 1 day (in ms)
program.addOption(new Option('--domainSeparator <domainSeparator>', 'domain separator').default(HashZero));

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

program.parse();
}
89 changes: 89 additions & 0 deletions sui/deploy-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
const { saveConfig, prompt, printInfo } = require('../evm/utils');
const { Command, Option } = require('commander');
const { publishPackage, updateMoveToml } = require('@axelar-network/axelar-cgp-sui/scripts/publish-package');
const { TransactionBlock } = require('@mysten/sui.js/transactions');
const { ethers } = require('hardhat');
const {
constants: { HashZero },
} = ethers;
const { loadSuiConfig } = require('./utils');

const { addBaseOptions } = require('./cli-utils');
const { getWallet, printWalletInfo } = require('./sign-utils');

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 publishPackage('test', client, keypair);
updateMoveToml('test', published.packageId);

const singleton = published.publishTxn.objectChanges.find((change) => change.objectType === `${published.packageId}::test::Singleton`);

const tx = new TransactionBlock();

tx.moveCall({
target: `${published.packageId}::test::register_transaction`,
arguments: [tx.object(relayerDiscovery), tx.object(singleton.objectId)],
});

await client.signAndExecuteTransactionBlock({
transactionBlock: tx,
signer: keypair,
options: {
showEffects: true,
showObjectChanges: true,
showContent: true,
},
});

chain.contracts.test.address = published.packageId;
chain.contracts.test.objects = { singleton: singleton.objectId };

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-gateway').description('Deploys/publishes the Sui gateway');

addBaseOptions(program);

program.addOption(new Option('--signers <signers>', 'JSON with the initial signer set').env('SIGNERS'));
program.addOption(new Option('--operator <operator>', 'operator for the gateway (defaults to the deployer address)'));
program.addOption(
new Option('--minimumRotationDelay <minimumRotationDelay>', 'minium delay for signer rotations (in ms)').default(
24 * 60 * 60 * 1000,
),
); // 1 day (in ms)
program.addOption(new Option('--domainSeparator <domainSeparator>', 'domain separator').default(HashZero));

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

program.parse();
}
Loading
Loading