From 94e5beb4f7904efe3c684bba1db52605bf27d812 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Mon, 25 Sep 2023 07:35:33 -0700 Subject: [PATCH 1/3] feat: generalize ec cmd to gov any committee --- packages/agoric-cli/src/bin-agops.js | 4 +- .../agoric-cli/src/commands/{ec.js => gov.js} | 87 +++++++++++++------ 2 files changed, 64 insertions(+), 27 deletions(-) rename packages/agoric-cli/src/commands/{ec.js => gov.js} (81%) diff --git a/packages/agoric-cli/src/bin-agops.js b/packages/agoric-cli/src/bin-agops.js index daeaf9c0e6f..1f893353055 100755 --- a/packages/agoric-cli/src/bin-agops.js +++ b/packages/agoric-cli/src/bin-agops.js @@ -18,7 +18,7 @@ import process from 'process'; import anylogger from 'anylogger'; import { Command, CommanderError, createCommand } from 'commander'; import { makeOracleCommand } from './commands/oracle.js'; -import { makeEconomicCommiteeCommand } from './commands/ec.js'; +import { makeGovCommand } from './commands/gov.js'; import { makePsmCommand } from './commands/psm.js'; import { makeReserveCommand } from './commands/reserve.js'; import { makeVaultsCommand } from './commands/vaults.js'; @@ -34,7 +34,7 @@ const program = new Command(); program.name(progname).version('unversioned'); program.addCommand(makeOracleCommand(logger)); -program.addCommand(makeEconomicCommiteeCommand(logger)); +program.addCommand(makeGovCommand(logger)); program.addCommand(makePerfCommand(logger)); program.addCommand(makePsmCommand(logger)); program.addCommand(makeVaultsCommand(logger)); diff --git a/packages/agoric-cli/src/commands/ec.js b/packages/agoric-cli/src/commands/gov.js similarity index 81% rename from packages/agoric-cli/src/commands/ec.js rename to packages/agoric-cli/src/commands/gov.js index 0282ae39ccd..e16facfd453 100644 --- a/packages/agoric-cli/src/commands/ec.js +++ b/packages/agoric-cli/src/commands/gov.js @@ -15,6 +15,11 @@ import { /** @typedef {import('@agoric/smart-wallet/src/offers.js').OfferSpec} OfferSpec */ +const collectValues = (val, memo) => { + memo.push(val); + return memo; +}; + /** * @param {import('anylogger').Logger} _logger * @param {{ @@ -26,7 +31,7 @@ import { * delay?: (ms: number) => Promise, * }} [io] */ -export const makeEconomicCommiteeCommand = (_logger, io = {}) => { +export const makeGovCommand = (_logger, io = {}) => { const { // Allow caller to provide access explicitly, but // default to conventional ambient IO facilities. @@ -38,7 +43,10 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { delay = ms => new Promise(resolve => setTimeout(resolve, ms)), } = io; - const ec = new Command('ec').description('Economic Committee commands'); + const cmd = new Command('gov').description('Electoral governance commands'); + // backwards compatibility with less general "ec" command. To make this work + // the new CLI options default to the values used for Economic Committee + cmd.alias('ec'); /** @param {string} literalOrName */ const normalizeAddress = literalOrName => @@ -67,7 +75,7 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { * }} detail * @param {Awaited>} [optUtils] */ - const processOffer = async function ({ toOffer, sendFrom }, optUtils) { + const processOffer = async ({ toOffer, sendFrom }, optUtils) => { const networkConfig = await getNetworkConfig(env); const utils = await (optUtils || makeRpcUtils({ fetch })); const { agoricNames, readLatestHead } = utils; @@ -126,8 +134,14 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { show(blockInfo); }; - ec.command('committee') - .description('accept invitation to join the economic committee') + cmd + .command('committee') + .description('accept invitation to join a committee') + .requiredOption( + '--name ', + 'Committee instance name', + 'economicCommittee', + ) .option('--voter ', 'Voter number', Number, 0) .option( '--offerId ', @@ -141,14 +155,16 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { normalizeAddress, ) .action(async function (opts) { + const { name: instanceName } = opts; + /** @type {Parameters[0]['toOffer']} */ const toOffer = (agoricNames, current) => { - const instance = agoricNames.instance.economicCommittee; - assert(instance, `missing economicCommittee`); + const instance = agoricNames.instance[instanceName]; + assert(instance, `missing ${instanceName}`); if (current) { const found = findContinuingIds(current, agoricNames); - abortIfSeen('economicCommittee', found); + abortIfSeen(instanceName, found); } return { @@ -164,28 +180,36 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { await processOffer({ toOffer, - instanceName: 'economicCommittee', + instanceName, ...opts, }); }); - ec.command('charter') + cmd + .command('charter') .description('accept the charter invitation') - .option('--offerId ', 'Offer id', String, `ecCharter-${Date.now()}`) + .requiredOption( + '--name ', + 'Charter instance name', + 'economicCommitteeCharter', + ) + .option('--offerId ', 'Offer id', String, `charter-${Date.now()}`) .option( '--send-from ', 'Send from address', normalizeAddress, ) .action(async function (opts) { + const { name: instanceName } = opts; + /** @type {Parameters[0]['toOffer']} */ const toOffer = (agoricNames, current) => { - const instance = agoricNames.instance.econCommitteeCharter; - assert(instance, `missing econCommitteeCharter`); + const instance = agoricNames.instance[instanceName]; + assert(instance, `missing ${instanceName}`); if (current) { const found = findContinuingIds(current, agoricNames); - abortIfSeen('econCommitteeCharter', found); + abortIfSeen(instanceName, found); } return { @@ -201,12 +225,13 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { await processOffer({ toOffer, - instanceName: 'econCommitteeCharter', + instanceName, ...opts, }); }); - ec.command('find-continuing-id') + cmd + .command('find-continuing-id') .description('print id of specified voting continuing invitation') .requiredOption( '--from ', @@ -232,7 +257,8 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { console.log(match.offerId); }); - ec.command('find-continuing-ids') + cmd + .command('find-continuing-ids') .description('print records of voting continuing invitations') .requiredOption( '--from ', @@ -247,9 +273,22 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { found.forEach(it => show({ ...it, address: opts.from })); }); - ec.command('vote') - .description('vote on a question (hard-coded for now))') - .option('--offerId ', 'Offer id', String, `ecVote-${Date.now()}`) + cmd + .command('vote') + .description('vote on latest question') + .requiredOption( + '--instance ', + 'Committee name under agoricNames.instances', + String, + 'economicCommittee', + ) + .requiredOption( + '--pathname ', + 'Committee name under published.committees', + String, + 'Economic_Committee', + ) + .option('--offerId ', 'Offer id', String, `gov-vote-${Date.now()}`) .requiredOption( '--forPosition ', 'index of one position to vote for (within the question description.positions); ', @@ -265,7 +304,7 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { const { readLatestHead } = utils; const info = await readLatestHead( - 'published.committees.Economic_Committee.latestQuestion', + `published.committees.${opts.pathname}.latestQuestion`, ).catch(err => { throw new CommanderError(1, 'VSTORAGE_FAILURE', err.message); }); @@ -279,9 +318,7 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { /** @type {Parameters[0]['toOffer']} */ const toOffer = (agoricNames, current) => { const cont = current ? findContinuingIds(current, agoricNames) : []; - const votingRight = cont.find( - it => it.instance === agoricNames.instance.economicCommittee, - ); + const votingRight = cont.find(it => it.instanceName === opts.instance); if (!votingRight) { console.debug('continuing ids', cont, 'for', current); throw new CommanderError( @@ -309,5 +346,5 @@ export const makeEconomicCommiteeCommand = (_logger, io = {}) => { await processOffer({ toOffer, sendFrom: opts.sendFrom }, utils); }); - return ec; + return cmd; }; From 46a12a2b120700040c7f0d2282bc392b42596111 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 27 Sep 2023 06:40:41 -0700 Subject: [PATCH 2/3] feat(atops): add proposePauseOffers --- packages/agoric-cli/src/commands/gov.js | 73 +++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/packages/agoric-cli/src/commands/gov.js b/packages/agoric-cli/src/commands/gov.js index e16facfd453..9fcba9ddc3b 100644 --- a/packages/agoric-cli/src/commands/gov.js +++ b/packages/agoric-cli/src/commands/gov.js @@ -346,5 +346,78 @@ export const makeGovCommand = (_logger, io = {}) => { await processOffer({ toOffer, sendFrom: opts.sendFrom }, utils); }); + cmd + .command('proposePauseOffers') + .description('propose a vote to pause offers') + .option( + '--send-from ', + 'Send from address', + normalizeAddress, + ) + .option( + '--offerId ', + 'Offer id', + String, + `proposePauseOffers-${Date.now()}`, + ) + .requiredOption( + '--instance ', + 'name of governed instance in agoricNames', + ) + .requiredOption( + '--substring ', + 'an offer string to pause (can be repeated)', + collectValues, + [], + ) + .option( + '--deadline ', + 'minutes from now to close the vote', + Number, + 1, + ) + .action(async function (opts) { + const { instance: instanceName } = opts; + + /** @type {Parameters[0]['toOffer']} */ + const toOffer = (agoricNames, current) => { + const instance = agoricNames.instance[instanceName]; + assert(instance, `missing ${instanceName}`); + assert(current, 'missing current wallet'); + + const known = findContinuingIds(current, agoricNames); + + assert(known, 'could not find committee acceptance offer id'); + + // TODO magic string + const match = known.find( + r => r.description === 'charter member invitation', + ); + assert(match, 'no offer found for charter member invitation'); + + return { + id: opts.offerId, + invitationSpec: { + source: 'continuing', + previousOffer: match.offerId, + invitationMakerName: 'VoteOnPauseOffers', + // ( instance, strings list, timer deadline seconds ) + invitationArgs: harden([ + instance, + opts.substring, + BigInt(opts.deadline * 60 + Math.round(Date.now() / 1000)), + ]), + }, + proposal: {}, + }; + }; + + await processOffer({ + toOffer, + instanceName, + ...opts, + }); + }); + return cmd; }; From cc61e93a1249824c90f27961e076684f947a6a45 Mon Sep 17 00:00:00 2001 From: Turadg Aleahmad Date: Wed, 27 Sep 2023 07:16:27 -0700 Subject: [PATCH 3/3] feat(agops): gov keyring option --- packages/agoric-cli/package.json | 2 + packages/agoric-cli/src/commands/gov.js | 58 +++++++++++++++++++------ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/packages/agoric-cli/package.json b/packages/agoric-cli/package.json index 71a2e87f2d6..4ed1aa4a7ca 100644 --- a/packages/agoric-cli/package.json +++ b/packages/agoric-cli/package.json @@ -41,6 +41,7 @@ "@agoric/casting": "^0.4.2", "@agoric/cosmic-proto": "^0.3.0", "@agoric/ertp": "^0.16.2", + "@agoric/governance": "^0.10.3", "@agoric/inter-protocol": "^0.16.1", "@agoric/internal": "^0.3.2", "@agoric/network": "^0.1.0", @@ -63,6 +64,7 @@ "@endo/init": "^0.5.59", "@endo/marshal": "^0.8.8", "@endo/nat": "^4.1.30", + "@endo/patterns": "^0.2.6", "@endo/promise-kit": "^0.2.59", "@iarna/toml": "^2.2.3", "anylogger": "^0.21.0", diff --git a/packages/agoric-cli/src/commands/gov.js b/packages/agoric-cli/src/commands/gov.js index 9fcba9ddc3b..12ec24da538 100644 --- a/packages/agoric-cli/src/commands/gov.js +++ b/packages/agoric-cli/src/commands/gov.js @@ -20,6 +20,8 @@ const collectValues = (val, memo) => { return memo; }; +const defaultKeyring = process.env.AGORIC_KEYRING_BACKEND || 'test'; + /** * @param {import('anylogger').Logger} _logger * @param {{ @@ -47,10 +49,18 @@ export const makeGovCommand = (_logger, io = {}) => { // backwards compatibility with less general "ec" command. To make this work // the new CLI options default to the values used for Economic Committee cmd.alias('ec'); + cmd.option( + '--keyring-backend ', + `keyring's backend (os|file|test) (default "${defaultKeyring}")`, + defaultKeyring, + ); /** @param {string} literalOrName */ const normalizeAddress = literalOrName => - normalizeAddressWithOptions(literalOrName, { keyringBackend: 'test' }); + normalizeAddressWithOptions(literalOrName, { + // FIXME does not observe keyring-backend option, which isn't available during arg parsing + keyringBackend: defaultKeyring, + }); /** @type {(info: unknown, indent?: unknown) => boolean } */ const show = (info, indent) => @@ -71,15 +81,21 @@ export const makeGovCommand = (_logger, io = {}) => { * @param {{ * toOffer: (agoricNames: *, current: import('@agoric/smart-wallet/src/smartWallet').CurrentWalletRecord | undefined) => OfferSpec, * sendFrom?: string | undefined, + * keyringBackend: string, * instanceName?: string, * }} detail * @param {Awaited>} [optUtils] */ - const processOffer = async ({ toOffer, sendFrom }, optUtils) => { + const processOffer = async function ( + { toOffer, sendFrom, keyringBackend }, + optUtils, + ) { const networkConfig = await getNetworkConfig(env); const utils = await (optUtils || makeRpcUtils({ fetch })); const { agoricNames, readLatestHead } = utils; + assert(keyringBackend, 'missing keyring-backend option'); + let current; if (sendFrom) { current = await getCurrent(sendFrom, { readLatestHead }); @@ -97,7 +113,7 @@ export const makeGovCommand = (_logger, io = {}) => { const result = await sendAction( { method: 'executeOffer', offer }, { - keyring: { backend: 'test' }, // XXX + keyring: { backend: keyringBackend }, from: sendFrom, verbose: false, ...networkConfig, @@ -140,6 +156,7 @@ export const makeGovCommand = (_logger, io = {}) => { .requiredOption( '--name ', 'Committee instance name', + String, 'economicCommittee', ) .option('--voter ', 'Voter number', Number, 0) @@ -147,14 +164,14 @@ export const makeGovCommand = (_logger, io = {}) => { '--offerId ', 'Offer id', String, - `ecCommittee-${Date.now()}`, + `gov-committee-${Date.now()}`, ) .option( '--send-from ', 'Send from address', normalizeAddress, ) - .action(async function (opts) { + .action(async function (opts, options) { const { name: instanceName } = opts; /** @type {Parameters[0]['toOffer']} */ @@ -181,7 +198,8 @@ export const makeGovCommand = (_logger, io = {}) => { await processOffer({ toOffer, instanceName, - ...opts, + sendFrom: opts.sendFrom, + keyringBackend: options.optsWithGlobals().keyringBackend, }); }); @@ -199,7 +217,7 @@ export const makeGovCommand = (_logger, io = {}) => { 'Send from address', normalizeAddress, ) - .action(async function (opts) { + .action(async function (opts, options) { const { name: instanceName } = opts; /** @type {Parameters[0]['toOffer']} */ @@ -226,7 +244,8 @@ export const makeGovCommand = (_logger, io = {}) => { await processOffer({ toOffer, instanceName, - ...opts, + sendFrom: opts.sendFrom, + keyringBackend: options.optsWithGlobals().keyringBackend, }); }); @@ -270,7 +289,9 @@ export const makeGovCommand = (_logger, io = {}) => { const current = await getCurrent(opts.from, { readLatestHead }); const found = findContinuingIds(current, agoricNames); - found.forEach(it => show({ ...it, address: opts.from })); + for (const it of found) { + show({ ...it, address: opts.from }); + } }); cmd @@ -299,7 +320,7 @@ export const makeGovCommand = (_logger, io = {}) => { 'Send from address', normalizeAddress, ) - .action(async function (opts) { + .action(async function (opts, options) { const utils = await makeRpcUtils({ fetch }); const { readLatestHead } = utils; @@ -308,8 +329,9 @@ export const makeGovCommand = (_logger, io = {}) => { ).catch(err => { throw new CommanderError(1, 'VSTORAGE_FAILURE', err.message); }); + // XXX runtime shape-check - const questionDesc = /** @type {any} */ (info); + const questionDesc = /** @type {QuestionDetails} */ (info); // TODO support multiple position arguments const chosenPositions = [questionDesc.positions[opts.forPosition]]; @@ -343,7 +365,14 @@ export const makeGovCommand = (_logger, io = {}) => { }; }; - await processOffer({ toOffer, sendFrom: opts.sendFrom }, utils); + await processOffer( + { + toOffer, + sendFrom: opts.sendFrom, + keyringBackend: options.optsWithGlobals().keyringBackend, + }, + utils, + ); }); cmd @@ -376,7 +405,7 @@ export const makeGovCommand = (_logger, io = {}) => { Number, 1, ) - .action(async function (opts) { + .action(async function (opts, options) { const { instance: instanceName } = opts; /** @type {Parameters[0]['toOffer']} */ @@ -415,7 +444,8 @@ export const makeGovCommand = (_logger, io = {}) => { await processOffer({ toOffer, instanceName, - ...opts, + sendFrom: opts.sendFrom, + keyringBackend: options.optsWithGlobals().keyringBackend, }); });