Skip to content

Commit

Permalink
feat(stellar): update stellar deployment and gateway scripts (#405)
Browse files Browse the repository at this point in the history
  • Loading branch information
ahramy authored Oct 16, 2024
1 parent e5ccf0e commit 167e672
Show file tree
Hide file tree
Showing 6 changed files with 64 additions and 84 deletions.
61 changes: 32 additions & 29 deletions stellar/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,31 @@ brew install libsodium
npm ci
```

Install Soroban CLI
Install Stellar CLI

```bash
cargo install --locked soroban-cli --features opt
cargo install --locked stellar-cli --features opt
```

Add Soroban network in the config

```bash
soroban network add testnet --rpc-url https://soroban-testnet.stellar.org:443 "Test SDF Network ; September 2015" --global
stellar network add \
--global testnet \
--rpc-url https://soroban-testnet.stellar.org:443 \
--network-passphrase "Test SDF Network ; September 2015"
```

Create a new Stellar keypair

```bash
soroban keys generate wallet --network testnet
stellar keys generate wallet --network testnet

# Address
soroban keys address wallet
stellar keys address wallet

# Get private key
soroban keys show wallet
stellar keys show wallet
```

Set `PRIVATE_KEY` in `.env` to the above value.
Expand All @@ -44,64 +47,65 @@ Setup

1. Checkout the axelar-cgp-soroban repo.
2. Compile the Soroban wasm contracts

```bash
cargo wasm --release
cargo build
stellar contract build
```

3. Optimize the contracts

```bash
./optimize.sh
```

### Gateway

Deploy the auth contract

```bash
node stellar/deploy-contract.js --contractName axelar_auth_verifier --wasmPath ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/axelar_auth_verifier.optimized.wasm --initialize
```

Deploy the gateway contract

```bash
node stellar/deploy-contract.js --contractName axelar_gateway --wasmPath ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/axelar_gateway.optimized.wasm --initialize
node stellar/deploy-contract.js --contract-name axelar_gateway --wasm-path ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/axelar_gateway.optimized.wasm --initialize
```

Provide `--estimateCost` to show the gas costs for the initialize transaction instead of executing it.
Provide `--estimate-cost` to show the gas costs for the initialize transaction instead of executing it.

### Operators

```bash
node stellar/deploy-contract.js --contractName axelar_operators --wasmPath ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/axelar_operators.optimized.wasm --initialize
node stellar/deploy-contract.js --contract-name axelar_operators --wasm-path ../axelar-cgp-soroban/target/wasm32-unknown-unknown/release/axelar_operators.optimized.wasm --initialize
```

## Generate bindings

Generate TypeScript bindings for the contract

```bash
node stellar/generate-bindings.js --wasmPath /path/to/optimized.wasm --contractId [contract address] --outputDir ./stellar/bindings/[contract name]
node stellar/generate-bindings.js --wasm-path /path/to/optimized.wasm --contract-id [contract-address] --output-dir ./stellar/bindings/[contract-name]
```

## Contract Interaction

Soroban contracts can be interacted directly via the CLI as well. See the help text for individual contract cmds as follows.

```bash
soroban contract invoke --network testnet --id [contract-address] --source-account wallet -- --help
stellar contract invoke --network testnet --id [contract-address] --source-account wallet -- --help
```

### Gateway

To get help on the gateway commands, run:
`node stellar/gateway.js --help`

```bash
node stellar/gateway.js --help
```

#### Call contract

```bash
node stellar/gateway.js call-contract [destination_chain] [dstination_address] [payload]
node stellar/gateway.js call-contract [destination-chain] [dstination-address] [payload]

# Example
node stellar/gateway.js call-contract ethereum 0x4F4495243837681061C4743b74B3eEdf548D56A5 0x1234
node stellar/gateway.js call-contract avalanche 0x4F4495243837681061C4743b74B3eEdf548D56A5 0x1234
```

### Submit multisig prover proof
Expand All @@ -112,28 +116,27 @@ Submit a proof constructed on Amplifier to the Stellar gateway contract.
node stellar/gateway.js submit-proof [multisig-session-id]
```

#### Approve messages
### Approve messages

A message approval can be submitted to the gateway contract for a test deployment where the wallet is the signer on the gateway. Setting `[destination address]` to `wallet` will use the wallet address as the destination.
A message approval can be submitted to the gateway contract for a test deployment where the wallet is the signer on the gateway. Setting `[destination-address]` to `wallet` will use the wallet address as the destination.

```bash
node stellar/gateway.js approve [source-chain] [message-id] [source-address] [destination-address] [payload]
```

#### Validate messages
### Validate messages

An approved message can be validated by the gateway contract for a test deployment as follows:

```bash
node stellar/gateway.js validate-message [source-chain] [message-id] [source-address] [payload]
```

#### Rotate signers
### Rotate signers

A signer rotation can be submitted to the gateway contract. Use `--currentNonce` to override the default current nonce set for subsequent rotations. Skip `--signers` to rotate to the Amplifier verifier set registered in the prover contract.
A signer rotation can be submitted to the gateway contract. Use `--current-nonce` to override the default current nonce set for subsequent rotations. Skip `--signers` to rotate to the Amplifier verifier set registered in the prover contract.

```bash
node node stellar/gateway.js rotate --newNonce test --signers wallet

node node stellar/gateway.js rotate --newNonce test2 --currentNonce test --signers wallet
node stellar/gateway.js rotate --new-nonce test --signers wallet
node stellar/gateway.js rotate --new-nonce test2 --current-nonce test --signers wallet
```
57 changes: 15 additions & 42 deletions stellar/deploy-contract.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { Contract, Address, nativeToScVal, scValToNative } = require('@stellar/st
const { Command, Option } = require('commander');
const { execSync } = require('child_process');
const { loadConfig, printInfo, saveConfig } = require('../evm/utils');
const { getNetworkPassphrase, getWallet, broadcast, serializeValue, addBaseOptions } = require('./utils');
const { stellarCmd, getNetworkPassphrase, getWallet, broadcast, serializeValue, addBaseOptions } = require('./utils');
const { getDomainSeparator, getChainConfig } = require('../common');
const { weightedSignersToScVal } = require('./type-utils');
const { ethers } = require('hardhat');
Expand All @@ -14,26 +14,13 @@ const {
require('./cli-utils');

async function getInitializeArgs(config, chain, contractName, wallet, options) {
const owner = nativeToScVal(Address.fromString(wallet.publicKey()), { type: 'address' });
const operator = nativeToScVal(Address.fromString(wallet.publicKey()), { type: 'address' });

switch (contractName) {
case 'axelar_gateway': {
const authAddress = chain.contracts?.axelar_auth_verifier?.address;

if (!authAddress) {
throw new Error('Missing axelar_auth_verifier contract address');
}

return {
authAddress: nativeToScVal(authAddress, { type: 'address' }),
owner,
};
}

case 'axelar_auth_verifier': {
const previousSignersRetention = nativeToScVal(15);
const domainSeparator = nativeToScVal(Buffer.from(arrayify(await getDomainSeparator(config, chain, options))));
const minimumRotationDelay = nativeToScVal(0);
const previousSignersRetention = nativeToScVal(options.previousSignersRetention);
const nonce = options.nonce ? arrayify(id(options.nonce)) : Array(32).fill(0);
const initialSigners = nativeToScVal([
weightedSignersToScVal({
Expand All @@ -49,35 +36,23 @@ async function getInitializeArgs(config, chain, contractName, wallet, options) {
]);

return {
owner,
previousSignersRetention,
operator,
domainSeparator,
minimumRotationDelay,
previousSignersRetention,
initialSigners,
};
}

case 'axelar_operators':
return { owner };
return { operator };
default:
throw new Error(`Unknown contract: ${contractName}`);
}
}

async function postDeployGateway(chain, wallet, options) {
printInfo('Transferring ownership of auth contract to the gateway');
const auth = new Contract(chain.contracts.axelar_auth_verifier.address);
const operation = auth.call('transfer_ownership', nativeToScVal(chain.contracts.axelar_gateway.address, { type: 'address' }));
await broadcast(operation, wallet, chain, 'Transferred ownership', options);
}

const postDeployFunctions = {
axelar_gateway: postDeployGateway,
};

async function processCommand(options, config, chain) {
const { wasmPath, contractName } = options;

const { wasmPath, contractName, privateKey } = options;
const { rpc, networkType } = chain;
const networkPassphrase = getNetworkPassphrase(networkType);
const wallet = await getWallet(chain, options);
Expand All @@ -86,7 +61,7 @@ async function processCommand(options, config, chain) {
chain.contracts = {};
}

const cmd = `soroban contract deploy --wasm ${wasmPath} --source ${options.privateKey} --rpc-url ${rpc} --network-passphrase "${networkPassphrase}"`;
const cmd = `${stellarCmd} contract deploy --wasm ${wasmPath} --source ${privateKey} --rpc-url ${rpc} --network-passphrase "${networkPassphrase}"`;
printInfo('Deploying contract', contractName);

let contractAddress = options.address;
Expand Down Expand Up @@ -119,11 +94,6 @@ async function processCommand(options, config, chain) {
printInfo('Initializing contract with args', JSON.stringify(serializedArgs, null, 2));

await broadcast(operation, wallet, chain, 'Initialized contract', options);

if (postDeployFunctions[contractName]) {
await postDeployFunctions[contractName](chain, wallet, options);
printInfo('Post deployment setup executed');
}
}

async function mainProcessor(options, processor) {
Expand All @@ -139,13 +109,16 @@ function main() {
addBaseOptions(program, { address: true });

program.addOption(new Option('--initialize', 'initialize the contract'));
program.addOption(new Option('--contractName <contractName>', 'contract name to deploy').makeOptionMandatory(true));
program.addOption(new Option('--wasmPath <wasmPath>', 'path to the WASM file').makeOptionMandatory(true));
program.addOption(new Option('--estimateCost', 'estimate on-chain resources').default(false));
program.addOption(new Option('--contract-name <contractName>', 'contract name to deploy').makeOptionMandatory(true));
program.addOption(new Option('--wasm-path <wasmPath>', 'path to the WASM file').makeOptionMandatory(true));
program.addOption(new Option('--nonce <nonce>', 'optional nonce for the signer set'));
program.addOption(
new Option('--previous-signers-retention <previousSignersRetention>', 'previous signer retention').default(15).argParser(Number),
);

program.addOption(
new Option(
'--domainSeparator <domainSeparator>',
'--domain-separator <domainSeparator>',
'domain separator (pass in the keccak256 hash value OR "offline" meaning that its computed locally)',
).default('offline'),
);
Expand Down
11 changes: 6 additions & 5 deletions stellar/gateway.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ function getProof(dataHash, wallet, chain, options) {
});
const signerHash = keccak256(signers.toXDR());

const domainSeparator = chain.contracts.axelar_auth_verifier?.initializeArgs?.domainSeparator;
const domainSeparator = chain.contracts.axelar_gateway?.initializeArgs?.domainSeparator;

if (!domainSeparator) {
throw new Error('Domain separator not found');
Expand Down Expand Up @@ -127,8 +127,9 @@ async function rotate(wallet, config, chain, contractConfig, args, options) {

const dataHash = encodeDataHash('RotateSigners', newSigners);
const proof = getProof(dataHash, wallet, chain, options);
const bypassRotationDelay = nativeToScVal(false); // only operator can bypass rotation delay.

const operation = contract.call('rotate_signers', newSigners, proof);
const operation = contract.call('rotate_signers', newSigners, proof, bypassRotationDelay);

await broadcast(operation, wallet, chain, 'Signers Rotated', options);
}
Expand Down Expand Up @@ -186,16 +187,16 @@ if (require.main === module) {
.command('rotate')
.description('Rotate signers of the gateway contract')
.addOption(new Option('--signers <signers>', 'Either use `wallet` or provide a JSON with the new signer set'))
.addOption(new Option('--currentNonce <currentNonce>', 'nonce of the existing signers'))
.addOption(new Option('--newNonce <newNonce>', 'nonce of the new signers (useful for test rotations)'))
.addOption(new Option('--current-nonce <currentNonce>', 'nonce of the existing signers'))
.addOption(new Option('--new-nonce <newNonce>', 'nonce of the new signers (useful for test rotations)'))
.action((options) => {
mainProcessor(rotate, [], options);
});

program
.command('approve <sourceChain> <messageId> <sourceAddress> <destinationAddress> <payload>')
.description('Approve messages at the gateway contract')
.addOption(new Option('--currentNonce <currentNonce>', 'nonce of the existing signers'))
.addOption(new Option('--current-nonce <currentNonce>', 'nonce of the existing signers'))
.action((sourceChain, messageId, sourceAddress, destinationAddress, payload, options) => {
mainProcessor(approve, [sourceChain, messageId, sourceAddress, destinationAddress, payload], options);
});
Expand Down
10 changes: 5 additions & 5 deletions stellar/generate-bindings.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ const { Command, Option } = require('commander');
const { execSync } = require('child_process');
const { loadConfig } = require('../evm/utils');
const path = require('path');
const { getNetworkPassphrase } = require('./utils');
const { stellarCmd, getNetworkPassphrase } = require('./utils');
const { addEnvOption } = require('../common');
require('./cli-utils');

Expand All @@ -15,7 +15,7 @@ function processCommand(options, _, chain) {
const { rpc, networkType } = chain;
const networkPassphrase = getNetworkPassphrase(networkType);

const cmd = `soroban contract bindings typescript --wasm ${wasmPath} --rpc-url ${rpc} --network-passphrase "${networkPassphrase}" --contract-id ${contractId} --output-dir ${outputDir} ${
const cmd = `${stellarCmd} contract bindings typescript --wasm ${wasmPath} --rpc-url ${rpc} --network-passphrase "${networkPassphrase}" --contract-id ${contractId} --output-dir ${outputDir} ${
overwrite ? '--overwrite' : ''
}`;
console.log(`Executing command: ${cmd}`);
Expand All @@ -29,10 +29,10 @@ function main() {
program.name('Generate TypeScript Bindings for Soroban contract').description('Generates TypeScript bindings for a Soroban contract.');

addEnvOption(program);
program.addOption(new Option('--wasmPath <wasmPath>', 'path to the WASM file').makeOptionMandatory(true));
program.addOption(new Option('--contractId <contractId>', 'contract ID').makeOptionMandatory(true));
program.addOption(new Option('--wasm-path <wasmPath>', 'path to the WASM file').makeOptionMandatory(true));
program.addOption(new Option('--contract-id <contractId>', 'contract ID').makeOptionMandatory(true));
program.addOption(
new Option('--outputDir <outputDir>', 'output directory for the generated bindings').default(path.join(__dirname, 'bindings')),
new Option('--output-dir <outputDir>', 'output directory for the generated bindings').default(path.join(__dirname, 'bindings')),
);

program.action((options) => {
Expand Down
1 change: 0 additions & 1 deletion stellar/operators.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,6 @@ if (require.main === module) {
.choices(['is_operator', 'add_operator', 'remove_operator', 'refund', 'execute'])
.makeOptionMandatory(true),
);
program.addOption(new Option('--estimateCost', 'estimate on-chain resources').default(false));
program.addOption(new Option('--args <args>', 'arguments for the contract call'));
program.addOption(new Option('--target <target>', 'target contract for the execute call'));
program.addOption(new Option('--method <method>', 'target method for the execute call'));
Expand Down
8 changes: 6 additions & 2 deletions stellar/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ const {
BigNumber,
} = ethers;

const stellarCmd = 'stellar';

function getNetworkPassphrase(networkType) {
switch (networkType) {
case 'local':
Expand All @@ -37,11 +39,12 @@ function getNetworkPassphrase(networkType) {
const addBaseOptions = (program, options = {}) => {
addEnvOption(program);
program.addOption(new Option('-y, --yes', 'skip deployment prompt confirmation').env('YES'));
program.addOption(new Option('--chainName <chainName>', 'chain name for stellar in amplifier').default('stellar').env('CHAIN'));
program.addOption(new Option('--chain-name <chainName>', 'chain name for stellar in amplifier').default('stellar').env('CHAIN'));
program.addOption(new Option('-v, --verbose', 'verbose output').default(false));
program.addOption(new Option('--estimate-cost', 'estimate on-chain resources').default(false));

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

if (options.address) {
Expand Down Expand Up @@ -263,6 +266,7 @@ function serializeValue(value) {
}

module.exports = {
stellarCmd,
buildTransaction,
prepareTransaction,
sendTransaction,
Expand Down

0 comments on commit 167e672

Please sign in to comment.