diff --git a/packages/agoric-cli/src/bin-agops.js b/packages/agoric-cli/src/bin-agops.js index 58b0cad121e..1b21a7d2451 100755 --- a/packages/agoric-cli/src/bin-agops.js +++ b/packages/agoric-cli/src/bin-agops.js @@ -19,6 +19,7 @@ import { makeReserveCommand } from './commands/reserve.js'; import { makeVaultsCommand } from './commands/vaults.js'; import { makePerfCommand } from './commands/perf.js'; import { makeInterCommand } from './commands/inter.js'; +import { makeAuctionCommand } from './commands/auction.js'; const logger = anylogger('agops'); const progname = path.basename(process.argv[1]); @@ -30,23 +31,22 @@ program.addCommand(await makeOracleCommand(logger)); program.addCommand(await makeEconomicCommiteeCommand(logger)); program.addCommand(await makePerfCommand(logger)); program.addCommand(await makePsmCommand(logger)); -program.addCommand(await makeReserveCommand(logger)); program.addCommand(await makeVaultsCommand(logger)); -program.addCommand( - await makeInterCommand( - { - env: { ...process.env }, - stdout: process.stdout, - stderr: process.stderr, - createCommand, - execFileSync, - now: () => Date.now(), - setTimeout, - }, - { fetch }, - ), -); +const procIO = { + env: { ...process.env }, + stdout: process.stdout, + stderr: process.stderr, + createCommand, + execFileSync, + now: () => Date.now(), + clock: () => Promise.resolve(Date.now()), + setTimeout, +}; + +program.addCommand(await makeReserveCommand(logger, procIO)); +program.addCommand(makeAuctionCommand(logger, { ...procIO, fetch })); +program.addCommand(await makeInterCommand(procIO, { fetch })); try { await program.parseAsync(process.argv); diff --git a/packages/agoric-cli/src/commands/auction.js b/packages/agoric-cli/src/commands/auction.js new file mode 100644 index 00000000000..b336ee5b6df --- /dev/null +++ b/packages/agoric-cli/src/commands/auction.js @@ -0,0 +1,167 @@ +/* eslint-disable @jessie.js/no-nested-await */ +// @ts-check +/* eslint-disable func-names */ +import { InvalidArgumentError } from 'commander'; +import { makeRpcUtils } from '../lib/rpc.js'; +import { outputActionAndHint } from '../lib/wallet.js'; + +const { Fail } = assert; + +/** @typedef {import('@agoric/governance/src/contractGovernance/typedParamManager.js').ParamTypesMap} ParamTypesMap */ +/** @template M @typedef {import('@agoric/governance/src/contractGovernance/typedParamManager.js').ParamTypesMapFromRecord} ParamTypesMapFromRecord */ + +/** + * @template {ParamTypesMap} M + * @typedef {{ + * [K in keyof M]: ParamValueForType + * }} ParamValues + */ + +/** @typedef {ReturnType} AuctionParamRecord */ +/** @typedef {ParamValues>} AuctionParams */ + +/** + * @param {import('anylogger').Logger} _logger + * @param {{ + * createCommand: typeof import('commander').createCommand, + * fetch: typeof window.fetch, + * stdout: Pick, + * stderr: Pick, + * clock: () => Promise, + * }} io + */ +export const makeAuctionCommand = ( + _logger, + { createCommand, stdout, stderr, fetch, clock }, +) => { + const reserve = createCommand('auctioneer').description( + 'Auctioneer commands', + ); + + reserve + .command('proposeParamChange') + .description('propose a change to start frequency') + .option( + '--start-frequency ', + 'how often to start auctions', + BigInt, + ) + .option('--clock-step ', 'descending clock frequency', BigInt) + .option( + '--starting-rate ', + 'relative to oracle: 999 = 1bp discount', + BigInt, + ) + .option('--lowest-rate ', 'lower limit for discount', BigInt) + .option( + '--discount-step ', + 'descending clock step size', + BigInt, + ) + .option( + '--discount-step ', + 'proposed value (basis points)', + BigInt, + ) + .requiredOption( + '--charterAcceptOfferId ', + 'offer that had continuing invitation result', + ) + .option('--offer-id ', 'Offer id', String, `propose-${Date.now()}`) + .option( + '--deadline [minutes]', + 'minutes from now to close the vote', + Number, + 1, + ) + .action( + /** + * + * @param {{ + * charterAcceptOfferId: string, + * startFrequency?: bigint, + * clockStep?: bigint, + * startingRate?: bigint, + * lowestRate?: bigint, + * discountStep?: bigint, + * offerId: string, + * deadline: number, + * }} opts + */ + async opts => { + const { agoricNames, readLatestHead } = await makeRpcUtils({ fetch }); + const { brand } = agoricNames; + + /** @type {{ current: AuctionParamRecord }} */ + // @ts-expect-error XXX should runtime check? + const { current } = await readLatestHead( + `published.auction.governance`, + ); + + const { + AuctionStartDelay: { + // @ts-expect-error XXX RelativeTime includes raw bigint + value: { timerBrand }, + }, + } = current; + timerBrand || Fail`no timer brand?`; + + /** + * typed param manager requires RelativeTimeRecord + * but TimeMath.toRel prodocues a RelativeTime (which may be a bare bigint). + * + * @param {bigint} relValue + * @returns {import('@agoric/time/src/types').RelativeTimeRecord} + */ + const toRel = relValue => relValue; // ({ timerBrand, relValue }); + + /** @type {Partial} */ + const params = { + ...(opts.startFrequency && { + StartFrequency: toRel(opts.startFrequency), + }), + ...(opts.clockStep && { ClockStep: toRel(opts.clockStep) }), + ...(opts.startingRate && { StartingRate: opts.startingRate }), + ...(opts.lowestRate && { LowestRate: opts.lowestRate }), + ...(opts.discountStep && { DiscountStep: opts.discountStep }), + }; + + if (Object.keys(params).length === 0) { + throw new InvalidArgumentError(`no parameters given`); + } + + const instance = agoricNames.instance.auctioneer; + instance || Fail`missing auctioneer in names`; + + const t0 = await clock(); + const deadline = BigInt(Math.round(t0 / 1000) + 60 * opts.deadline); + + /** @type {import('@agoric/inter-protocol/src/econCommitteeCharter.js').ParamChangesOfferArgs} */ + const offerArgs = { + deadline, + params, + instance, + path: { paramPath: { key: 'governedParams' } }, + }; + + /** @type {import('@agoric/smart-wallet/src/offers.js').OfferSpec} */ + const offer = { + id: opts.offerId, + invitationSpec: { + source: 'continuing', + previousOffer: opts.charterAcceptOfferId, + invitationMakerName: 'VoteOnParamChange', + }, + offerArgs, + proposal: {}, + }; + + outputActionAndHint( + { method: 'executeOffer', offer }, + { stdout, stderr }, + ); + }, + ); + + return reserve; +};