Skip to content

Commit

Permalink
Merge pull request #7256 from Agoric/dc-bid-cli
Browse files Browse the repository at this point in the history
feat: more usable bidding CLI: integrated sign/broadcast
  • Loading branch information
mergify[bot] authored Apr 5, 2023
2 parents 4347a81 + 1200ad5 commit 013fff8
Show file tree
Hide file tree
Showing 20 changed files with 1,544 additions and 456 deletions.
65 changes: 44 additions & 21 deletions packages/agoric-cli/src/bin-agops.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/usr/bin/env node
// @ts-check
/* eslint-disable @jessie.js/no-nested-await */
/* global fetch */
/* global fetch, setTimeout */

import '@agoric/casting/node-fetch-shim.js';
import '@endo/init';
Expand All @@ -19,33 +19,56 @@ 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]);

const program = new Command();
program.name(progname).version('unversioned');

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(),
},
{ fetch },
),
);
program.addCommand(makeOracleCommand(logger));
program.addCommand(makeEconomicCommiteeCommand(logger));
program.addCommand(makePerfCommand(logger));
program.addCommand(makePsmCommand(logger));
program.addCommand(makeVaultsCommand(logger));

/**
* XXX Threading I/O powers has gotten a bit jumbled.
*
* Perhaps a more straightforward approach would be:
*
* - makeTUI({ stdout, stderr, logger })
* where tui.show(data) prints data as JSON to stdout
* and tui.warn() and tui.error() log ad-hoc to stderr
* - makeQueryClient({ fetch })
* with q.withConfig(networkConfig)
* and q.vstorage.get('published...') (no un-marshaling)
* and q.pollBlocks(), q.pollTx()
* also, printing the progress message should be done
* in the lookup callback
* - makeBoardClient(queryClient)
* with b.readLatestHead('published...')
* - makeKeyringNames({ execFileSync })
* with names.lookup('gov1') -> 'agoric1...'
* and names.withBackend('test')
* and names.withHome('~/.agoric')
* - makeSigner({ execFileSync })
* signer.sendSwingsetTx()
*/
const procIO = {
env: { ...process.env },
stdout: process.stdout,
stderr: process.stderr,
createCommand,
execFileSync,
now: () => Date.now(),
setTimeout,
};

program.addCommand(makeReserveCommand(logger, procIO));
program.addCommand(makeAuctionCommand(logger, { ...procIO, fetch }));
program.addCommand(makeInterCommand(procIO, { fetch }));

try {
await program.parseAsync(process.argv);
Expand Down
166 changes: 166 additions & 0 deletions packages/agoric-cli/src/commands/auction.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/* 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<M>} ParamTypesMapFromRecord */

/**
* @template {ParamTypesMap} M
* @typedef {{
* [K in keyof M]: ParamValueForType<M[K]>
* }} ParamValues
*/

/** @typedef {ReturnType<import('@agoric/inter-protocol/src/auction/params.js').makeAuctioneerParams>} AuctionParamRecord */
/** @typedef {ParamValues<ParamTypesMapFromRecord<AuctionParamRecord>>} AuctionParams */

/**
* @param {import('anylogger').Logger} _logger
* @param {{
* createCommand: typeof import('commander').createCommand,
* fetch: typeof window.fetch,
* stdout: Pick<import('stream').Writable, 'write'>,
* stderr: Pick<import('stream').Writable, 'write'>,
* now: () => number,
* }} io
*/
export const makeAuctionCommand = (
_logger,
{ createCommand, stdout, stderr, fetch, now },
) => {
const auctioneer = createCommand('auctioneer').description(
'Auctioneer commands',
);

auctioneer
.command('proposeParamChange')
.description('propose a change to start frequency')
.option(
'--start-frequency <seconds>',
'how often to start auctions',
BigInt,
)
.option('--clock-step <seconds>', 'descending clock frequency', BigInt)
.option(
'--starting-rate <basis-points>',
'relative to oracle: 999 = 1bp discount',
BigInt,
)
.option('--lowest-rate <basis-points>', 'lower limit for discount', BigInt)
.option(
'--discount-step <basis-points>',
'descending clock step size',
BigInt,
)
.option(
'--discount-step <integer>',
'proposed value (basis points)',
BigInt,
)
.requiredOption(
'--charterAcceptOfferId <string>',
'offer that had continuing invitation result',
)
.option('--offer-id <string>', '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 });

/** @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 => ({ timerBrand, relValue });

/** @type {Partial<AuctionParams>} */
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 = now();
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 auctioneer;
};
Loading

0 comments on commit 013fff8

Please sign in to comment.