From 2527e699e484cc3fb82579a5dfcc3d137e9f240b Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Wed, 19 Oct 2022 16:10:11 -0700 Subject: [PATCH] fix: tighten invitation proposal patterns --- packages/governance/src/committee.js | 15 +- packages/governance/src/electorateTools.js | 8 +- packages/governance/src/noActionElectorate.js | 9 +- packages/inter-protocol/src/psm/psm.js | 4 + packages/inter-protocol/src/psm/psmCharter.js | 152 ++++++++++++++++++ .../zoe/src/contractSupport/zoeHelpers.js | 11 +- packages/zoe/src/typeGuards.js | 6 + 7 files changed, 199 insertions(+), 6 deletions(-) create mode 100644 packages/inter-protocol/src/psm/psmCharter.js diff --git a/packages/governance/src/committee.js b/packages/governance/src/committee.js index ebcf720b3c22..c66dad51df95 100644 --- a/packages/governance/src/committee.js +++ b/packages/governance/src/committee.js @@ -3,6 +3,7 @@ import { makeStore, makeHeapFarInstance, M } from '@agoric/store'; import { natSafeMath } from '@agoric/zoe/src/contractSupport/index.js'; import { E } from '@endo/eventual-send'; +import { EmptyProposal } from '@agoric/zoe/src/typeGuards.js'; import { makeHandle } from '@agoric/zoe/src/makeHandle.js'; import { getOpenQuestions, @@ -96,7 +97,12 @@ const start = (zcf, privateArgs) => { return E(voteCap).submitVote(voterHandle, positions, 1n); }; - return zcf.makeInvitation(continuingVoteHandler, 'vote'); + return zcf.makeInvitation( + continuingVoteHandler, + 'vote', + undefined, + EmptyProposal, + ); }, }, ), @@ -107,7 +113,12 @@ const start = (zcf, privateArgs) => { // This will produce unique descriptions because // makeCommitteeVoterInvitation() is only called within the following loop, // which is only called once per Electorate. - return zcf.makeInvitation(offerHandler, `Voter${index}`); + return zcf.makeInvitation( + offerHandler, + `Voter${index}`, + undefined, + EmptyProposal, + ); }; const { committeeName, committeeSize } = zcf.getTerms(); diff --git a/packages/governance/src/electorateTools.js b/packages/governance/src/electorateTools.js index 881756a8c323..2f642a200a6b 100644 --- a/packages/governance/src/electorateTools.js +++ b/packages/governance/src/electorateTools.js @@ -1,3 +1,4 @@ +import { EmptyProposal } from '@agoric/zoe/src/typeGuards'; import { E } from '@endo/eventual-send'; import { deeplyFulfilled, Far } from '@endo/marshal'; @@ -86,7 +87,12 @@ const getQuestion = (questionHandleP, questionStore) => */ const getPoserInvitation = (zcf, addQuestion) => { const questionPoserHandler = () => Far(`questionPoser`, { addQuestion }); - return zcf.makeInvitation(questionPoserHandler, `questionPoser`); + return zcf.makeInvitation( + questionPoserHandler, + `questionPoser`, + undefined, + EmptyProposal, + ); }; harden(startCounter); diff --git a/packages/governance/src/noActionElectorate.js b/packages/governance/src/noActionElectorate.js index dcfca6540b82..1aace25cee2a 100644 --- a/packages/governance/src/noActionElectorate.js +++ b/packages/governance/src/noActionElectorate.js @@ -1,6 +1,7 @@ import { makePublishKit } from '@agoric/notifier'; import { makePromiseKit } from '@endo/promise-kit'; import { makeHeapFarInstance } from '@agoric/store'; +import { EmptyProposal } from '@agoric/zoe/src/typeGuards.js'; import { ElectoratePublicI, ElectorateCreatorI } from './typeGuards.js'; @@ -37,8 +38,12 @@ const start = zcf => { const creatorFacet = makeHeapFarInstance('creatorFacet', ElectorateCreatorI, { getPoserInvitation() { - return zcf.makeInvitation(() => {}, - `noActionElectorate doesn't allow posing questions`); + return zcf.makeInvitation( + () => {}, + `noActionElectorate doesn't allow posing questions`, + undefined, + EmptyProposal, + ); }, addQuestion(_instance, _question) { throw Error(`noActionElectorate doesn't add questions.`); diff --git a/packages/inter-protocol/src/psm/psm.js b/packages/inter-protocol/src/psm/psm.js index 8c20fd25ad53..ec589106cd28 100644 --- a/packages/inter-protocol/src/psm/psm.js +++ b/packages/inter-protocol/src/psm/psm.js @@ -279,6 +279,8 @@ export const start = async (zcf, privateArgs, baggage) => { M.splitRecord({ give: { In: anchorAmountShape }, want: M.or({ Out: stableAmountShape }, {}), + multiples: 1n, + exit: M.any(), }), ); }, @@ -290,6 +292,8 @@ export const start = async (zcf, privateArgs, baggage) => { M.splitRecord({ give: { In: stableAmountShape }, want: M.or({ Out: anchorAmountShape }, {}), + multiples: 1n, + exit: M.any(), }), ); }, diff --git a/packages/inter-protocol/src/psm/psmCharter.js b/packages/inter-protocol/src/psm/psmCharter.js new file mode 100644 index 000000000000..d9fc70bd171d --- /dev/null +++ b/packages/inter-protocol/src/psm/psmCharter.js @@ -0,0 +1,152 @@ +import '@agoric/governance/src/exported.js'; +import { makeScalarMapStore, M, makeHeapFarInstance, fit } from '@agoric/store'; +import '@agoric/zoe/exported.js'; +import '@agoric/zoe/src/contracts/exported.js'; +import { + EmptyProposal, + InstanceHandleShape, +} from '@agoric/zoe/src/typeGuards.js'; +import { BrandShape } from '@agoric/ertp'; +import { TimestampShape } from '@agoric/swingset-vat/src/vats/timer/typeGuards.js'; +import { E } from '@endo/far'; + +/** + * @file + * + * This contract makes it possible for those who govern the PSM to call for + * votes on changes. A more complete implementation would validate parameters, + * constrain deadlines and possibly split the ability to call for votes into + * separate capabilities for finer grain encapsulation. + */ + +/** + * @typedef {object} ParamChangesOfferArgs + * @property {bigint} deadline + * @property {Instance} instance + * @property {Record} params + * @property {{paramPath: { key: string }}} [path] + */ +const ParamChangesOfferArgsShape = harden( + M.split( + { + deadline: TimestampShape, + instance: InstanceHandleShape, + params: M.recordOf(M.string(), M.any()), + }, + M.partial({ + path: { paramPath: { key: M.string() } }, + }), + ), +); + +/** + * @param {ZCF<{binaryVoteCounterInstallation:Installation}>} zcf + */ +export const start = async zcf => { + const { binaryVoteCounterInstallation: counter } = zcf.getTerms(); + /** @type {MapStore>} */ + const instanceToGovernor = makeScalarMapStore(); + + const makeParamInvitation = () => { + /** + * @param {ZCFSeat} seat + * @param {ParamChangesOfferArgs} args + */ + const voteOnParamChanges = (seat, args) => { + fit(args, ParamChangesOfferArgsShape); + seat.exit(); + + const { + params, + instance, + deadline, + path = { paramPath: { key: 'governedApi' } }, + } = args; + const psmGovernor = instanceToGovernor.get(instance); + return E(psmGovernor).voteOnParamChanges(counter, deadline, { + ...path, + changes: params, + }); + }; + + return zcf.makeInvitation( + voteOnParamChanges, + 'vote on param changes', + undefined, + EmptyProposal, + ); + }; + + const makeOfferFilterInvitation = (instance, strings, deadline) => { + const voteOnOfferFilterHandler = seat => { + seat.exit(); + + const psmGovernor = instanceToGovernor.get(instance); + return E(psmGovernor).voteOnOfferFilter(counter, deadline, strings); + }; + + return zcf.makeInvitation( + voteOnOfferFilterHandler, + 'vote on offer filter', + undefined, + EmptyProposal, + ); + }; + + const MakerShape = M.interface('PSM Charter InvitationMakers', { + VoteOnParamChange: M.call().returns(M.promise()), + VoteOnPauseOffers: M.call( + InstanceHandleShape, + M.arrayOf(M.string()), + TimestampShape, + ).returns(M.promise()), + }); + const invitationMakers = makeHeapFarInstance( + 'PSM Invitation Makers', + MakerShape, + { + VoteOnParamChange: makeParamInvitation, + VoteOnPauseOffers: makeOfferFilterInvitation, + }, + ); + + const charterMemberHandler = seat => { + seat.exit(); + return harden({ invitationMakers }); + }; + + const psmCharterCreatorI = M.interface('PSM Charter creatorFacet', { + addInstance: M.call(InstanceHandleShape, M.any()) + .optional(BrandShape, BrandShape) + .returns(), + makeCharterMemberInvitation: M.call().returns(M.promise()), + }); + + const creatorFacet = makeHeapFarInstance( + 'PSM Charter creatorFacet', + psmCharterCreatorI, + { + /** + * @param {Instance} psmInstance + * @param {GovernedContractFacetAccess<{},{}>} psmGovernorFacet + * @param {Brand} [anchor] for diagnostic use only + * @param {Brand} [minted] for diagnostic use only + */ + addInstance: (psmInstance, psmGovernorFacet, anchor, minted) => { + console.log('psmCharter: adding instance', { minted, anchor }); + instanceToGovernor.init(psmInstance, psmGovernorFacet); + }, + makeCharterMemberInvitation: () => + zcf.makeInvitation( + charterMemberHandler, + 'PSM charter member invitation', + undefined, + EmptyProposal, + ), + }, + ); + + return harden({ creatorFacet }); +}; + +export const INVITATION_MAKERS_DESC = 'PSM charter member invitation'; diff --git a/packages/zoe/src/contractSupport/zoeHelpers.js b/packages/zoe/src/contractSupport/zoeHelpers.js index 1ed9a56c6025..801cc8e925d1 100644 --- a/packages/zoe/src/contractSupport/zoeHelpers.js +++ b/packages/zoe/src/contractSupport/zoeHelpers.js @@ -1,4 +1,6 @@ -import { fit, keyEQ } from '@agoric/store'; +import '../../exported.js'; + +import { fit, keyEQ, M } from '@agoric/store'; import { E } from '@endo/eventual-send'; import { makePromiseKit } from '@endo/promise-kit'; import { AssetKind } from '@agoric/ertp'; @@ -216,6 +218,13 @@ export const depositToSeat = async (zcf, recipientSeat, amounts, payments) => { const invitation = zcf.makeInvitation( reallocateAfterDeposit, 'temporary seat for deposit', + undefined, + harden({ + give: M.any(), + want: {}, + multiples: 1n, + exit: { onDemand: null }, + }), ); const proposal = harden({ give: amounts }); harden(payments); diff --git a/packages/zoe/src/typeGuards.js b/packages/zoe/src/typeGuards.js index 298025d7cb1f..b6dd36959a81 100644 --- a/packages/zoe/src/typeGuards.js +++ b/packages/zoe/src/typeGuards.js @@ -46,6 +46,12 @@ export const FullProposalShape = harden({ }); /** @see {Proposal} type */ export const ProposalShape = M.splitRecord({}, FullProposalShape, {}); +export const EmptyProposal = harden({ + give: {}, + want: {}, + multiples: 1n, + exit: { onDemand: null }, +}); export const isOnDemandExitRule = exit => { const [exitKey] = Object.getOwnPropertyNames(exit);