From 21a03f816e392a25c197b692905b2d0388632ee9 Mon Sep 17 00:00:00 2001 From: Richard Gibson Date: Mon, 11 Nov 2024 18:51:46 -0500 Subject: [PATCH] feat(agoric-cli): Add `agoric wallet send` gas limit options ...defaulting to `--gas=auto --gas-adjustment=1.2`. --- packages/agoric-cli/src/commands/wallet.js | 26 ++++++++++++++++++++-- packages/agoric-cli/src/lib/chain.js | 24 ++++++++++++++++++++ packages/agoric-cli/src/lib/format.js | 12 ++++++++++ packages/agoric-cli/test/inter-cli.test.js | 2 +- 4 files changed, 61 insertions(+), 3 deletions(-) diff --git a/packages/agoric-cli/src/commands/wallet.js b/packages/agoric-cli/src/commands/wallet.js index ef137119b15..440fe2452d5 100644 --- a/packages/agoric-cli/src/commands/wallet.js +++ b/packages/agoric-cli/src/commands/wallet.js @@ -21,7 +21,11 @@ import { } from '../lib/chain.js'; import { getNetworkConfig } from '../lib/network-config.js'; import { coalesceWalletState, getCurrent } from '../lib/wallet.js'; -import { summarize, fmtRecordOfLines } from '../lib/format.js'; +import { + summarize, + fmtRecordOfLines, + parseFiniteNumber, +} from '../lib/format.js'; const networkConfig = await getNetworkConfig({ env: process.env, fetch }); @@ -135,12 +139,28 @@ export const makeWalletCommand = async command => { ) .requiredOption('--offer [filename]', 'path to file with prepared offer') .option('--dry-run', 'spit out the command instead of running it') + .option('--gas', 'gas limit; "auto" [default] to calculate automatically') + .option( + '--gas-adjustment', + 'factor by which to multiply the --gas=auto calculation result [default 1.2]', + ) .option('--verbose', 'print command output') .action(function (opts) { - /** @typedef {{ from: string, offer: string, dryRun: boolean, verbose: boolean }} Opts */ + /** + * @typedef {{ + * from: string, + * offer: string, + * dryRun: boolean, + * gas: string, + * gasAdjustment: string, + * verbose: boolean, + * }} Opts + */ const { dryRun, from, + gas = 'auto', + gasAdjustment = '1.2', offer, home, verbose, @@ -154,6 +174,8 @@ export const makeWalletCommand = async command => { ...networkConfig, keyring: { home, backend }, from, + gas: + gas === 'auto' ? ['auto', parseFiniteNumber(gasAdjustment)] : gas, dryRun, verbose, }, diff --git a/packages/agoric-cli/src/lib/chain.js b/packages/agoric-cli/src/lib/chain.js index d8f76a142e1..d56eb403688 100644 --- a/packages/agoric-cli/src/lib/chain.js +++ b/packages/agoric-cli/src/lib/chain.js @@ -43,6 +43,7 @@ harden(normalizeAddressWithOptions); * @param {MinimalNetworkConfig & { * from: string, * fees?: string, + * gas?: number | 'auto' | ['auto', adjustment?: number | undefined], * dryRun?: boolean, * verbose?: boolean, * keyring?: {home?: string, backend: string} @@ -54,6 +55,7 @@ export const execSwingsetTransaction = (swingsetArgs, opts) => { const { from, fees, + gas = ['auto', 1.2], dryRun = false, verbose = true, keyring = undefined, @@ -67,10 +69,32 @@ export const execSwingsetTransaction = (swingsetArgs, opts) => { ? [`--keyring-backend=${keyring.backend}`] : []; const feeOpt = fees ? ['--fees', fees] : []; + /** @type {string[]} */ + const gasOpt = []; + if (Number.isFinite(gas) || gas === 'auto') { + gasOpt.push(`--gas=${gas}`); + } else if (Array.isArray(gas) && gas.length >= 1 && gas[0] === 'auto') { + gasOpt.push('--gas=auto'); + if (gas.length > 1) { + const [adjustment, ...rest] = gas.slice(1); + const adjustmentIsValid = + adjustment === undefined || + (Number.isFinite(adjustment) && Number(adjustment) > 0); + if (rest.length !== 0 || !adjustmentIsValid) { + throw Error('invalid gas input'); + } + if (adjustment !== undefined) { + gasOpt.push(`--gas-adjustment=${adjustment}`); + } + } + } else { + throw Error('invalid gas input'); + } const cmd = [`--node=${rpcAddrs[0]}`, `--chain-id=${chainName}`].concat( homeOpt, backendOpt, feeOpt, + gasOpt, [`--from=${from}`, 'tx', 'swingset'], swingsetArgs, ); diff --git a/packages/agoric-cli/src/lib/format.js b/packages/agoric-cli/src/lib/format.js index 16ebe45308b..e5e1a277d3c 100644 --- a/packages/agoric-cli/src/lib/format.js +++ b/packages/agoric-cli/src/lib/format.js @@ -6,6 +6,18 @@ import { makeBoardRemote } from '@agoric/vats/tools/board-utils.js'; * @import {AgoricNamesRemotes, BoardRemote, VBankAssetDetail} from '@agoric/vats/tools/board-utils.js'; */ +// TODO Move to packages/internal. +/** + * Parses the input and returns either a finite number or NaN. + * + * @param {string} input + * @returns {number} + */ +export const parseFiniteNumber = input => { + const result = /[0-9]/.test(input || '') ? Number(input) : NaN; + return Number.isFinite(result) ? result : NaN; +}; + /** * JSON.stringify replacer to handle bigint * diff --git a/packages/agoric-cli/test/inter-cli.test.js b/packages/agoric-cli/test/inter-cli.test.js index 38e3b970feb..386f9b6e825 100644 --- a/packages/agoric-cli/test/inter-cli.test.js +++ b/packages/agoric-cli/test/inter-cli.test.js @@ -599,7 +599,7 @@ test('README ex1: inter bid place by-price: printed offer is correct', async t = const expected = [ 'Run this interactive command in shell:\n\n', 'agd ', - '--node=http://0.0.0.0:26657 --chain-id=agoriclocal --from=agoric18jr9nlvp300feu726y3v4n07ykfjwup3twnlyn tx swingset wallet-action --allow-spend {"body":"#{\\"method\\":\\"executeOffer\\",\\"offer\\":{\\"id\\":\\"bid-1680241587424\\",\\"invitationSpec\\":{\\"callPipe\\":[[\\"makeBidInvitation\\",[\\"$0.Alleged: BoardRemoteBrand\\"]]],\\"instancePath\\":[\\"auctioneer\\"],\\"source\\":\\"agoricContract\\"},\\"offerArgs\\":{\\"maxBuy\\":{\\"brand\\":\\"$0\\",\\"value\\":\\"+1000000000000\\"},\\"offerPrice\\":{\\"denominator\\":{\\"brand\\":\\"$0\\",\\"value\\":\\"+100\\"},\\"numerator\\":{\\"brand\\":\\"$1.Alleged: BoardRemoteBrand\\",\\"value\\":\\"+855\\"}}},\\"proposal\\":{\\"give\\":{\\"Bid\\":{\\"brand\\":\\"$1\\",\\"value\\":\\"+85000000\\"}}}}}","slots":["board03446","board0566"]} --output json', + '--node=http://0.0.0.0:26657 --chain-id=agoriclocal --gas=auto --gas-adjustment=1.2 --from=agoric18jr9nlvp300feu726y3v4n07ykfjwup3twnlyn tx swingset wallet-action --allow-spend {"body":"#{\\"method\\":\\"executeOffer\\",\\"offer\\":{\\"id\\":\\"bid-1680241587424\\",\\"invitationSpec\\":{\\"callPipe\\":[[\\"makeBidInvitation\\",[\\"$0.Alleged: BoardRemoteBrand\\"]]],\\"instancePath\\":[\\"auctioneer\\"],\\"source\\":\\"agoricContract\\"},\\"offerArgs\\":{\\"maxBuy\\":{\\"brand\\":\\"$0\\",\\"value\\":\\"+1000000000000\\"},\\"offerPrice\\":{\\"denominator\\":{\\"brand\\":\\"$0\\",\\"value\\":\\"+100\\"},\\"numerator\\":{\\"brand\\":\\"$1.Alleged: BoardRemoteBrand\\",\\"value\\":\\"+855\\"}}},\\"proposal\\":{\\"give\\":{\\"Bid\\":{\\"brand\\":\\"$1\\",\\"value\\":\\"+85000000\\"}}}}}","slots":["board03446","board0566"]} --output json', ].join(''); t.deepEqual(txt, expected); });