From 77cf5daa31426f7b51e9d8df283c10de621e9ea4 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 15 Sep 2022 12:01:43 -0700 Subject: [PATCH 1/2] refactor(cli): extract execSwingsetTransaction --- packages/agoric-cli/src/commands/perf.js | 10 ++----- packages/agoric-cli/src/commands/wallet.js | 21 +++++--------- packages/agoric-cli/src/lib/chain.js | 32 ++++++++++++++++++++++ packages/agoric-cli/src/lib/keys.js | 14 ---------- 4 files changed, 42 insertions(+), 35 deletions(-) create mode 100644 packages/agoric-cli/src/lib/chain.js delete mode 100644 packages/agoric-cli/src/lib/keys.js diff --git a/packages/agoric-cli/src/commands/perf.js b/packages/agoric-cli/src/commands/perf.js index 079ceaeae4d..c06aadbbe9b 100644 --- a/packages/agoric-cli/src/commands/perf.js +++ b/packages/agoric-cli/src/commands/perf.js @@ -9,12 +9,11 @@ import { makeFollower, makeLeaderFromRpcAddresses, } from '@agoric/casting'; -import { execSync } from 'child_process'; import { Command } from 'commander'; import fs from 'fs'; import { exit } from 'process'; import { makeLeaderOptions } from '../lib/casting.js'; -import { normalizeAddress } from '../lib/keys.js'; +import { execSwingsetTransaction, normalizeAddress } from '../lib/chain.js'; import { networkConfig } from '../lib/rpc.js'; // tight for perf testing but less than this tends to hang. @@ -52,8 +51,6 @@ export const makePerfCommand = async logger => { const { offer } = JSON.parse(JSON.parse(payloadStr).body); const { id: offerId } = offer; - const { chainName, rpcAddrs } = networkConfig; - const spec = `:published.wallet.${opts.from}`; const leaderOptions = makeLeaderOptions({ @@ -88,15 +85,14 @@ export const makePerfCommand = async logger => { void watchForSatisfied(); // now execute - let cmd = `agd --node=${rpcAddrs[0]} --chain-id=${chainName} --from=${opts.from} tx swingset wallet-action --allow-spend "$(cat ${opts.executeOffer})" --yes`; + let cmd = `wallet-action --allow-spend "$(cat ${opts.executeOffer})"`; if (opts.keyringBackend) { cmd = cmd.concat(' --keyring-backend ', opts.keyringBackend); } if (opts.home) { cmd = cmd.concat(' --home ', opts.home); } - console.warn('Executing in shell:', cmd); - execSync(cmd); + execSwingsetTransaction(cmd, networkConfig, opts.from); }); return perf; diff --git a/packages/agoric-cli/src/commands/wallet.js b/packages/agoric-cli/src/commands/wallet.js index e2494503992..f892972a8ae 100644 --- a/packages/agoric-cli/src/commands/wallet.js +++ b/packages/agoric-cli/src/commands/wallet.js @@ -1,7 +1,6 @@ // @ts-check /* eslint-disable func-names */ /* global fetch, process */ -import { execSync } from 'child_process'; import { iterateLatest, makeCastingSpec, @@ -15,7 +14,7 @@ import { makeRpcUtils, networkConfig } from '../lib/rpc.js'; import { getWalletState } from '../lib/wallet.js'; import { makeLeaderOptions } from '../lib/casting.js'; -import { normalizeAddress } from '../lib/keys.js'; +import { execSwingsetTransaction, normalizeAddress } from '../lib/chain.js'; const SLEEP_SECONDS = 3; @@ -34,19 +33,13 @@ export const makeWalletCommand = async () => { .option('--dry-run', 'spit out the command instead of running it') .action(function () { const { dryRun, from, offer } = this.opts(); - const { chainName, rpcAddrs } = networkConfig; - const cmd = `agd --node=${rpcAddrs[0]} --chain-id=${chainName} --from=${from} tx swingset wallet-action --allow-spend "$(cat ${offer})"`; - - if (dryRun) { - process.stdout.write('Run this interactive command in shell:\n\n'); - process.stdout.write(cmd); - process.stdout.write('\n'); - } else { - const yesCmd = `${cmd} --yes`; - console.log('Executing ', yesCmd); - execSync(yesCmd); - } + execSwingsetTransaction( + `wallet-action --allow-spend "$(cat ${offer})"`, + networkConfig, + from, + dryRun, + ); }); wallet diff --git a/packages/agoric-cli/src/lib/chain.js b/packages/agoric-cli/src/lib/chain.js new file mode 100644 index 00000000000..46a53a4dfc7 --- /dev/null +++ b/packages/agoric-cli/src/lib/chain.js @@ -0,0 +1,32 @@ +// @ts-check +/* global process */ +import { normalizeBech32 } from '@cosmjs/encoding'; +import { execSync } from 'child_process'; + +export const normalizeAddress = literalOrName => { + try { + return normalizeBech32(literalOrName); + } catch (_) { + // not an address so try as a key + const buff = execSync(`agd keys show --address ${literalOrName}`); + return normalizeBech32(buff.toString().trim()); + } +}; +harden(normalizeAddress); + +export const execSwingsetTransaction = (swingsetArgs, net, from, dryRun) => { + const { chainName, rpcAddrs } = net; + + const cmd = `agd --node=${rpcAddrs[0]} --chain-id=${chainName} --from=${from} tx swingset ${swingsetArgs}`; + + if (dryRun) { + process.stdout.write('Run this interactive command in shell:\n\n'); + process.stdout.write(cmd); + process.stdout.write('\n'); + } else { + const yesCmd = `${cmd} --yes`; + console.log('Executing ', yesCmd); + execSync(yesCmd); + } +}; +harden(execSwingsetTransaction); diff --git a/packages/agoric-cli/src/lib/keys.js b/packages/agoric-cli/src/lib/keys.js deleted file mode 100644 index bf5aa434010..00000000000 --- a/packages/agoric-cli/src/lib/keys.js +++ /dev/null @@ -1,14 +0,0 @@ -// @ts-check -import { normalizeBech32 } from '@cosmjs/encoding'; -import { execSync } from 'child_process'; - -export const normalizeAddress = literalOrName => { - try { - return normalizeBech32(literalOrName); - } catch (_) { - // not an address so try as a key - const buff = execSync(`agd keys show --address ${literalOrName}`); - return normalizeBech32(buff.toString().trim()); - } -}; -harden(normalizeAddress); From cda33775de869a72cb5c6ab5c97c326312171a3f Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Thu, 15 Sep 2022 12:02:15 -0700 Subject: [PATCH 2/2] feat(cli): wallet provision --- packages/agoric-cli/src/commands/wallet.js | 39 ++++++++++++++++++- packages/agoric-cli/src/lib/chain.js | 24 +++++++++++- .../test/agops-governance-smoketest.sh | 6 +-- .../agoric-cli/test/agops-perf-smoketest.sh | 12 +++--- 4 files changed, 68 insertions(+), 13 deletions(-) diff --git a/packages/agoric-cli/src/commands/wallet.js b/packages/agoric-cli/src/commands/wallet.js index f892972a8ae..d6bfdd1e1c5 100644 --- a/packages/agoric-cli/src/commands/wallet.js +++ b/packages/agoric-cli/src/commands/wallet.js @@ -14,13 +14,50 @@ import { makeRpcUtils, networkConfig } from '../lib/rpc.js'; import { getWalletState } from '../lib/wallet.js'; import { makeLeaderOptions } from '../lib/casting.js'; -import { execSwingsetTransaction, normalizeAddress } from '../lib/chain.js'; +import { + execSwingsetTransaction, + fetchSwingsetParams, + normalizeAddress, +} from '../lib/chain.js'; const SLEEP_SECONDS = 3; export const makeWalletCommand = async () => { const wallet = new Command('wallet').description('wallet commands'); + wallet + .command('provision') + .description('provision a Smart Wallet') + .requiredOption( + '--account [address]', + 'address literal or name', + normalizeAddress, + ) + .option('--spend', 'confirm you want to spend') + .option('--nickname [string]', 'nickname to use', 'my-wallet') + .action(function () { + const { account, nickname, spend } = this.opts(); + if (spend) { + const tx = `provision-one ${nickname} ${account} SMART_WALLET`; + execSwingsetTransaction(tx, networkConfig, account); + } else { + const params = fetchSwingsetParams(networkConfig); + assert( + params.power_flag_fees.length === 1, + 'multiple power_flag_fees not supported', + ); + const { fee: fees } = params.power_flag_fees[0]; + const nf = new Intl.NumberFormat('en-US'); + const costs = fees + .map(f => `${nf.format(Number(f.amount))} ${f.denom}`) + .join(' + '); + process.stdout.write(`Provisioning a wallet costs ${costs}\n`); + process.stdout.write( + `To really provision, repeat this command with --spend\n`, + ); + } + }); + wallet .command('send') .description('send a prepared offer') diff --git a/packages/agoric-cli/src/lib/chain.js b/packages/agoric-cli/src/lib/chain.js index 46a53a4dfc7..cfe658c70d5 100644 --- a/packages/agoric-cli/src/lib/chain.js +++ b/packages/agoric-cli/src/lib/chain.js @@ -14,7 +14,20 @@ export const normalizeAddress = literalOrName => { }; harden(normalizeAddress); -export const execSwingsetTransaction = (swingsetArgs, net, from, dryRun) => { +/** + * SECURITY: closes over process and child_process + * + * @param {string} swingsetArgs + * @param {import('./rpc').MinimalNetworkConfig} net + * @param {string} from + * @param {boolean} [dryRun] + */ +export const execSwingsetTransaction = ( + swingsetArgs, + net, + from, + dryRun = false, +) => { const { chainName, rpcAddrs } = net; const cmd = `agd --node=${rpcAddrs[0]} --chain-id=${chainName} --from=${from} tx swingset ${swingsetArgs}`; @@ -30,3 +43,12 @@ export const execSwingsetTransaction = (swingsetArgs, net, from, dryRun) => { } }; harden(execSwingsetTransaction); + +// xxx rpc should be able to query this by HTTP without shelling out +export const fetchSwingsetParams = net => { + const { chainName, rpcAddrs } = net; + const cmd = `agd --node=${rpcAddrs[0]} --chain-id=${chainName} query swingset params --output --json`; + const buffer = execSync(cmd); + return JSON.parse(buffer.toString()); +}; +harden(fetchSwingsetParams); diff --git a/packages/agoric-cli/test/agops-governance-smoketest.sh b/packages/agoric-cli/test/agops-governance-smoketest.sh index 9cb677bb708..deb300bb5e9 100644 --- a/packages/agoric-cli/test/agops-governance-smoketest.sh +++ b/packages/agoric-cli/test/agops-governance-smoketest.sh @@ -19,13 +19,11 @@ make scenario2-setup scenario2-run-chain-psm cd packages/cosmic-swingset # Fund the pool make fund-provision-pool -# Copy the agoric address from your keplr wallet or 'agd keys list', starts with 'agoric1' -KEY= # Provision your wallet -make ACCT_ADDR=$KEY AGORIC_POWERS=SMART_WALLET fund-acct provision-acct +agoric wallet provision --account # verify agoric wallet list -agoric wallet show --from \$KEY +agoric wallet show --from " exit 1 fi diff --git a/packages/agoric-cli/test/agops-perf-smoketest.sh b/packages/agoric-cli/test/agops-perf-smoketest.sh index 21bbd7f1883..1cbefa14d67 100755 --- a/packages/agoric-cli/test/agops-perf-smoketest.sh +++ b/packages/agoric-cli/test/agops-perf-smoketest.sh @@ -16,16 +16,14 @@ cd packages/cosmic-swingset make scenario2-setup scenario2-run-chain-psm # (new tab) -# Fund the pool (addr is a magic string) -make SOLO_COINS=1234000000ibc/usdc1234 ACCT_ADDR=agoric1megzytg65cyrgzs6fvzxgrcqvwwl7ugpt62346 fund-acct -# Provision your wallet cd packages/cosmic-swingset -# Copy the agoric address from your keplr wallet or 'agd keys list', starts with 'agoric1' -WALLET_ADDR= -make ACCT_ADDR=$WALLET_ADDR AGORIC_POWERS=SMART_WALLET fund-acct provision-acct +# Fund the pool +make fund-provision-pool +# Provision your wallet +agoric wallet provision --account # verify agoric wallet list -agoric wallet show --from $WALLET_ADDR +agoric wallet show --from " exit 1 fi