From a7fb54952f149a9388572c938e0de2fabf757c6c Mon Sep 17 00:00:00 2001 From: "Mark S. Miller" Date: Sun, 1 Oct 2023 20:31:57 -0700 Subject: [PATCH] feat(ertp): Upgrade quotes to drop newly optional recovery sets --- packages/ERTP/src/issuerKit.js | 181 ++++++++++++++++-- packages/ERTP/src/paymentLedger.js | 34 +++- packages/ERTP/src/purse.js | 62 +++++- packages/ERTP/src/types-ambient.js | 56 +++++- .../ertpService/vat-ertp-service.js | 4 +- .../src/price/fluxAggregatorContract.js | 17 +- packages/vats/src/mintHolder.js | 14 +- .../priceAuthorityQuoteMint.js | 43 ++--- packages/zoe/src/zoeService/feeMint.js | 40 ++-- packages/zoe/src/zoeService/makeInvitation.js | 41 ++-- .../zoe/src/zoeService/zoeStorageManager.js | 4 +- 11 files changed, 376 insertions(+), 120 deletions(-) diff --git a/packages/ERTP/src/issuerKit.js b/packages/ERTP/src/issuerKit.js index 7b13135a14e3..11fe3d18fe2f 100644 --- a/packages/ERTP/src/issuerKit.js +++ b/packages/ERTP/src/issuerKit.js @@ -1,6 +1,6 @@ // @jessie-check -import { assert } from '@agoric/assert'; +import { assert, Fail } from '@agoric/assert'; import { assertPattern } from '@agoric/store'; import { makeScalarBigMapStore } from '@agoric/vat-data'; @@ -10,6 +10,8 @@ import { preparePaymentLedger } from './paymentLedger.js'; import './types-ambient.js'; +// TODO Why does TypeScript lose the `MapStore` typing of `Baggage` here, even +// though it knows the correct type at the exporting `@agoric/vat-data` /** @typedef {import('@agoric/vat-data').Baggage} Baggage */ /** @@ -22,8 +24,12 @@ import './types-ambient.js'; */ /** + * Used _only_ internally, to make a new issuerKit or to revive an old one. + * * @template {AssetKind} K * @param {IssuerRecord} issuerRecord + * @param {RecoverySetsOption} recoverySetsState Omitted from issuerRecord + * because it was added in an upgrade. * @param {Baggage} issuerBaggage * @param {ShutdownWithFailure} [optShutdownWithFailure] If this issuer fails in * the middle of an atomic action (which btw should never happen), it @@ -36,6 +42,7 @@ import './types-ambient.js'; */ const setupIssuerKit = ( { name, assetKind, displayInfo, elementShape }, + recoverySetsState, issuerBaggage, optShutdownWithFailure = undefined, ) => { @@ -61,6 +68,7 @@ const setupIssuerKit = ( assetKind, cleanDisplayInfo, elementShape, + recoverySetsState, optShutdownWithFailure, ); @@ -76,8 +84,17 @@ harden(setupIssuerKit); /** The key at which the issuer record is stored. */ const INSTANCE_KEY = 'issuer'; +/** + * The key at which the issuerKit's `RecoverySetsOption` state is stored. + * Introduced by an upgrade, so may be absent on an ancestor. See + * `RecoverySetsOption` for defaulting behavior. + */ +const RECOVERY_SETS_STATE = 'recoverySetsState'; /** + * Used _only_ to upgrade an ancestor issuerKit. Use `makeDurableIssuerKit` to + * make a new one. + * * @template {AssetKind} K * @param {Baggage} issuerBaggage * @param {ShutdownWithFailure} [optShutdownWithFailure] If this issuer fails in @@ -87,28 +104,77 @@ const INSTANCE_KEY = 'issuer'; * unit of computation, like the enclosing vat, can be shutdown before * anything else is corrupted by that corrupted state. See * https://github.com/Agoric/agoric-sdk/issues/3434 + * @param {RecoverySetsOption} [recoverySetsOption] Added in upgrade, so last + * and optional. See `RecoverySetsOption` for defaulting behavior. * @returns {IssuerKit} */ -export const prepareIssuerKit = ( +export const upgradeIssuerKit = ( issuerBaggage, optShutdownWithFailure = undefined, + recoverySetsOption = undefined, ) => { const issuerRecord = issuerBaggage.get(INSTANCE_KEY); - return setupIssuerKit(issuerRecord, issuerBaggage, optShutdownWithFailure); + const oldRecoverySetsState = issuerBaggage.has(RECOVERY_SETS_STATE) + ? issuerBaggage.get(RECOVERY_SETS_STATE) + : 'hasRecoverySets'; + if ( + oldRecoverySetsState === 'noRecoverySets' && + recoverySetsOption === 'hasRecoverySets' + ) { + Fail`Cannot (yet?) upgrade from 'noRecoverySets' to 'hasRecoverySets'`; + } + const recoverySetsState = recoverySetsOption || oldRecoverySetsState; + return setupIssuerKit( + issuerRecord, + issuerBaggage, + recoverySetsState, + optShutdownWithFailure, + ); }; -harden(prepareIssuerKit); +harden(upgradeIssuerKit); /** - * Does baggage already have an issuer from prepareIssuerKit()? That is: does it - * have the relevant keys defined? + * Confusingly, `prepareIssuerKit` was the original name for `upgradeIssuerKit`, + * even though it is used only to upgrade an ancestor issuerKit. Use + * `makeDurableIssuerKit` to make a new one. + * + * @deprecated Use `upgradeIssuerKit` instead if that's what you want. Or + * `reallyPrepareIssuerKit` if you want the behavior that should have been + * bound to this name. + */ +export const prepareIssuerKit = upgradeIssuerKit; + +/** + * Does baggage already have an issuerKit? * * @param {Baggage} baggage */ export const hasIssuer = baggage => baggage.has(INSTANCE_KEY); -/** @typedef {Partial<{ elementShape: Pattern }>} IssuerOptionsRecord */ +/** + * `elementShape`, may only be present for collection-style amounts. If present, + * it is a `Pattern` that every element of this issuerKits's amounts must + * satisfy. For example, the Zoe Invitation issuerKit uses an elementShape + * describing the invitation details for an individual invitation. An invitation + * purse or payment has an amount that can only be a set of these. (Though + * typically, the amount of an invitation payment is a singleton set. Such a + * payment is often referred to in the singular as "an invitation".) + * + * `recoverySetsOption` added in upgrade. Note that `IssuerOptionsRecord` is + * never stored, so we never need to worry about inheriting one from an ancestor + * predating the introduction of recovery sets. See `RecoverySetsOption` for + * defaulting behavior. + * + * @typedef {Partial<{ + * elementShape: Pattern; + * recoverySetsOption: RecoverySetsOption; + * }>} IssuerOptionsRecord + */ /** + * Used _only_ to make a _new_ durable issuer, i.e., the initial incarnation of + * that issuer. + * * @template {AssetKind} K The name becomes part of the brand in asset * descriptions. The name is useful for debugging and double-checking * assumptions, but should not be trusted wrt any external namespace. For @@ -142,15 +208,106 @@ export const makeDurableIssuerKit = ( assetKind = AssetKind.NAT, displayInfo = harden({}), optShutdownWithFailure = undefined, - { elementShape = undefined } = {}, + { elementShape = undefined, recoverySetsOption = undefined } = {}, ) => { - const issuerData = harden({ name, assetKind, displayInfo, elementShape }); + const issuerData = harden({ + name, + assetKind, + displayInfo, + elementShape, + }); issuerBaggage.init(INSTANCE_KEY, issuerData); - return setupIssuerKit(issuerData, issuerBaggage, optShutdownWithFailure); + const recoverySetsState = recoverySetsOption || 'hasRecoverySets'; + issuerBaggage.init(RECOVERY_SETS_STATE, recoverySetsState); + return setupIssuerKit( + issuerData, + recoverySetsState, + issuerBaggage, + optShutdownWithFailure, + ); }; harden(makeDurableIssuerKit); /** + * What _should_ have been named `prepareIssuerKit`. Used to either revive an + * ancestor issuer kit, or to make a new durable if it absent, and to place it + * in baggage for the next successor. + * + * @template {AssetKind} K The name becomes part of the brand in asset + * descriptions. The name is useful for debugging and double-checking + * assumptions, but should not be trusted wrt any external namespace. For + * example, anyone could create a new issuer kit with name 'BTC', but it is + * not bitcoin or even related. It is only the name according to that issuer + * and brand. + * + * The assetKind will be used to import a specific mathHelpers from the + * mathHelpers library. For example, natMathHelpers, the default, is used for + * basic fungible tokens. + * + * `displayInfo` gives information to the UI on how to display the amount. + * @param {Baggage} issuerBaggage + * @param {string} name + * @param {K} [assetKind] + * @param {AdditionalDisplayInfo} [displayInfo] + * @param {ShutdownWithFailure} [optShutdownWithFailure] If this issuer fails in + * the middle of an atomic action (which btw should never happen), it + * potentially leaves its ledger in a corrupted state. If this function was + * provided, then the failed atomic action will call it, so that some larger + * unit of computation, like the enclosing vat, can be shutdown before + * anything else is corrupted by that corrupted state. See + * https://github.com/Agoric/agoric-sdk/issues/3434 + * @param {IssuerOptionsRecord} [options] + * @returns {IssuerKit} + */ +export const reallyPrepareIssuerKit = ( + issuerBaggage, + name, + // @ts-expect-error K could be instantiated with a different subtype of AssetKind + assetKind = AssetKind.NAT, + displayInfo = harden({}), + optShutdownWithFailure = undefined, + options = {}, +) => { + if (hasIssuer(issuerBaggage)) { + const { elementShape: _ = undefined, recoverySetsOption = undefined } = + options; + const issuerKit = upgradeIssuerKit( + issuerBaggage, + optShutdownWithFailure, + recoverySetsOption, + ); + + // TODO check consistency with name, assetKind, displayInfo, elementShape. + // Consistency either means that these are the same, or that they differ + // in a direction we are prepared to upgrade. Note that it is the + // responsibility of `upgradeIssuerKit` to check consistency of + // `recoverySetsOption`, so continue to not do that here. + + // @ts-expect-error Type parameter confusion. + return issuerKit; + } else { + const issuerKit = makeDurableIssuerKit( + issuerBaggage, + name, + assetKind, + displayInfo, + optShutdownWithFailure, + options, + ); + return issuerKit; + } +}; +harden(reallyPrepareIssuerKit); + +/** + * Used _only_ to make a new issuerKit that is effectively non-durable. This is + * currently done by making a durable one in a baggage not reachable from + * anywhere. TODO Once rebuilt on zones, this should instead just build on the + * virtual zone. See https://github.com/Agoric/agoric-sdk/pull/7116 + * + * Currently used for testing only. Should probably continue to be used for + * testing only. + * * @template {AssetKind} [K='nat'] The name becomes part of the brand in asset * descriptions. The name is useful for debugging and double-checking * assumptions, but should not be trusted wrt any external namespace. For @@ -182,7 +339,7 @@ export const makeIssuerKit = ( assetKind = AssetKind.NAT, displayInfo = harden({}), optShutdownWithFailure = undefined, - { elementShape = undefined } = {}, + { elementShape = undefined, recoverySetsOption = undefined } = {}, ) => makeDurableIssuerKit( makeScalarBigMapStore('dropped issuer kit', { durable: true }), @@ -190,6 +347,6 @@ export const makeIssuerKit = ( assetKind, displayInfo, optShutdownWithFailure, - { elementShape }, + { elementShape, recoverySetsOption }, ); harden(makeIssuerKit); diff --git a/packages/ERTP/src/paymentLedger.js b/packages/ERTP/src/paymentLedger.js index b4575854d14a..16beffd5c3bb 100644 --- a/packages/ERTP/src/paymentLedger.js +++ b/packages/ERTP/src/paymentLedger.js @@ -79,6 +79,7 @@ const amountShapeFromElementShape = (brand, assetKind, elementShape) => { * @param {K} assetKind * @param {DisplayInfo} displayInfo * @param {Pattern} elementShape + * @param {RecoverySetsOption} recoverySetsState * @param {ShutdownWithFailure} [optShutdownWithFailure] * @returns {PaymentLedger} */ @@ -88,6 +89,7 @@ export const preparePaymentLedger = ( assetKind, displayInfo, elementShape, + recoverySetsState, optShutdownWithFailure = undefined, ) => { /** @type {Brand} */ @@ -145,6 +147,18 @@ export const preparePaymentLedger = ( { valueShape: amountShape }, ); + if ( + recoverySetsState === 'noRecoverySets' && + issuerBaggage.has('paymentRecoverySets') + ) { + // Upgrade to `'noRecoverySets'` by dropping this all at once. + // Depending on conditions elsewhere, this may result in a ton of + // payments becoming unreachable all at once. Depend on (TODO upcoming) + // SwingSet support for incrementalizing the resulting deallocation + // work. + issuerBaggage.delete('paymentRecoverySets'); + } + /** * A withdrawn live payment is associated with the recovery set of the purse * it was withdrawn from. Let's call these "recoverable" payments. All @@ -162,12 +176,12 @@ export const preparePaymentLedger = ( * - A purse's recovery set only contains payments withdrawn from that purse and * not yet consumed. * - * @type {WeakMapStore>} + * @type {WeakMapStore> | undefined} */ - const paymentRecoverySets = provideDurableWeakMapStore( - issuerBaggage, - 'paymentRecoverySets', - ); + const paymentRecoverySets = + recoverySetsState === 'noRecoverySets' + ? undefined + : provideDurableWeakMapStore(issuerBaggage, 'paymentRecoverySets'); /** * To maintain the invariants listed in the `paymentRecoverySets` comment, @@ -179,6 +193,7 @@ export const preparePaymentLedger = ( */ const initPayment = (payment, amount, optRecoverySet = undefined) => { if (optRecoverySet !== undefined) { + assert(paymentRecoverySets !== undefined); optRecoverySet.add(payment); paymentRecoverySets.init(payment, optRecoverySet); } @@ -193,7 +208,7 @@ export const preparePaymentLedger = ( */ const deletePayment = payment => { paymentLedger.delete(payment); - if (paymentRecoverySets.has(payment)) { + if (paymentRecoverySets !== undefined && paymentRecoverySets.has(payment)) { const recoverySet = paymentRecoverySets.get(payment); paymentRecoverySets.delete(payment); recoverySet.delete(payment); @@ -283,14 +298,14 @@ export const preparePaymentLedger = ( * @param {(newPurseBalance: Amount) => void} updatePurseBalance - commit the * purse balance * @param {Amount} amount - the amount to be withdrawn - * @param {SetStore} recoverySet + * @param {SetStore} [recoverySet] * @returns {Payment} */ const withdrawInternal = ( currentBalance, updatePurseBalance, amount, - recoverySet, + recoverySet = undefined, ) => { amount = coerce(amount); AmountMath.isGTE(currentBalance, amount) || @@ -310,6 +325,8 @@ export const preparePaymentLedger = ( return payment; }; + /** @type {() => Purse} */ + // @ts-expect-error type parameter confusion const makeEmptyPurse = preparePurseKind( issuerBaggage, name, @@ -320,6 +337,7 @@ export const preparePaymentLedger = ( depositInternal, withdrawInternal, }), + recoverySetsState, ); /** @type {Issuer} */ diff --git a/packages/ERTP/src/purse.js b/packages/ERTP/src/purse.js index 35d766eda79c..c8f06bc27b2c 100644 --- a/packages/ERTP/src/purse.js +++ b/packages/ERTP/src/purse.js @@ -3,8 +3,27 @@ import { prepareExoClassKit, makeScalarBigSetStore } from '@agoric/vat-data'; import { AmountMath } from './amountMath.js'; import { makeTransientNotifierKit } from './transientNotifier.js'; +// TODO `InterfaceGuard` type parameter +/** @typedef {import('@endo/patterns').InterfaceGuard} InterfaceGuard */ +/** @typedef {import('@agoric/vat-data').Baggage} Baggage */ + const { Fail } = assert; +/** + * @param {Baggage} issuerBaggage + * @param {string} name + * @param {AssetKind} assetKind + * @param {Brand} brand + * @param {{ + * purse: InterfaceGuard; + * depositFacet: InterfaceGuard; + * }} PurseIKit + * @param {{ + * depositInternal: any; + * withdrawInternal: any; + * }} purseMethods + * @param {RecoverySetsOption} recoverySetsState + */ export const preparePurseKind = ( issuerBaggage, name, @@ -12,6 +31,7 @@ export const preparePurseKind = ( brand, PurseIKit, purseMethods, + recoverySetsState, ) => { const amountShape = brand.getAmountShape(); @@ -24,6 +44,34 @@ export const preparePurseKind = ( updateBalance(purse, purse.getCurrentAmount()); }; + /** + * @param {any} state + * @returns {SetStore | undefined} + */ + const getRecoverySet = state => { + const { recoverySet } = state; + if (recoverySetsState === 'hasRecoverySets') { + return recoverySet; + } else { + assert(recoverySetsState === 'noRecoverySets'); + if (recoverySet.getSize() >= 1) { + // The stateShape constraint and the current lack of support for schema + // upgrade means that we cannot upgrade `state.recoverySet` to + // `undefined` or any non-remotable. + // + // Upgrade by dropping the old recoverySet and replacing it with a + // new empty one, which we hopefully never add to. + // Depending on conditions elsewhere, this may cause lots of payments + // to become unreachable simultaneously. This code depends on SwingSet to + // incrementalize the resulting gc work. (TODO this SwingSet feature is + // not yet implemented.) + + state.recoverySet = makeScalarBigSetStore('recovery set'); + } + return undefined; + } + }; + // - This kind is a pair of purse and depositFacet that have a 1:1 // correspondence. // - They are virtualized together to share a single state record. @@ -71,7 +119,7 @@ export const preparePurseKind = ( newPurseBalance => updatePurseBalance(state, newPurseBalance, this.facets.purse), amount, - state.recoverySet, + getRecoverySet(state), ); }, getCurrentAmount() { @@ -89,18 +137,24 @@ export const preparePurseKind = ( }, getRecoverySet() { - return this.state.recoverySet.snapshot(); + const { state } = this; + void getRecoverySet(state); // just for the possible side effect + return state.recoverySet.snapshot(); }, recoverAll() { const { state, facets } = this; let amount = AmountMath.makeEmpty(brand, assetKind); - for (const payment of state.recoverySet.keys()) { + const recoverySet = getRecoverySet(state); + if (recoverySet === undefined) { + return amount; // empty at this time + } + for (const payment of recoverySet.keys()) { // This does cause deletions from the set while iterating, // but this special case is allowed. const delta = facets.purse.deposit(payment); amount = AmountMath.add(amount, delta, brand); } - state.recoverySet.getSize() === 0 || + recoverySet.getSize() === 0 || Fail`internal: Remaining unrecovered payments: ${facets.purse.getRecoverySet()}`; return amount; }, diff --git a/packages/ERTP/src/types-ambient.js b/packages/ERTP/src/types-ambient.js index 5fbd0e805b4a..fefe515d5fe1 100644 --- a/packages/ERTP/src/types-ambient.js +++ b/packages/ERTP/src/types-ambient.js @@ -115,7 +115,7 @@ /** * @callback IssuerIsLive Return true if the payment continues to exist. * - * If the payment is a promise, the operation will proceed upon resolution. + * If the payment is a promise, the operation will proceed upon fulfillment. * @param {ERef} payment * @returns {Promise} */ @@ -125,18 +125,25 @@ * Because the payment is not trusted, we cannot call a method on it directly, * and must use the issuer instead. * - * If the payment is a promise, the operation will proceed upon resolution. + * If the payment is a promise, the operation will proceed upon fulfillment. * @param {ERef} payment * @returns {Promise>} */ /** * @callback IssuerBurn Burn all of the digital assets in the payment. - * `optAmount` is optional. If `optAmount` is present, the code will insist - * that the amount of the digital assets in the payment is equal to - * `optAmount`, to prevent sending the wrong payment and other confusion. + * `optAmountShape` is optional. If the `optAmountShape` pattern is present, + * the amount of the digital assets in the payment must match + * `optAmountShape`, to prevent sending the wrong payment and other + * confusion. * - * If the payment is a promise, the operation will proceed upon resolution. + * If the payment is a promise, the operation will proceed upon fulfillment. + * + * As always with optional `Pattern` arguments, keep in mind that technically + * the value `undefined` itself is a valid `Key` and therefore a valid + * `Pattern`. But in optional pattern position, a top level `undefined` will + * be interpreted as absence. If you want to express a `Pattern` that will + * match only `undefined`, use `M.undefined()` instead. * @param {ERef} payment * @param {Pattern} [optAmountShape] * @returns {Promise} @@ -171,7 +178,8 @@ * @template {AssetKind} [K=AssetKind] * @typedef {object} PaymentLedger * @property {Mint} mint - * @property {Purse} mintRecoveryPurse + * @property {Purse} mintRecoveryPurse Externally useful only if this issuer + * uses recovery sets. * @property {Issuer} issuer * @property {Brand} brand */ @@ -180,7 +188,8 @@ * @template {AssetKind} [K=AssetKind] * @typedef {object} IssuerKit * @property {Mint} mint - * @property {Purse} mintRecoveryPurse + * @property {Purse} mintRecoveryPurse Externally useful only if this issuer + * uses recovery sets. * @property {Issuer} issuer * @property {Brand} brand * @property {DisplayInfo} displayInfo @@ -211,6 +220,31 @@ * Payment containing newly minted amount. */ +/** + * Issuers first became durable with recovery sets and no option to suppress + * them. Thus, absence of a `RecoverySetsOption` state is equivalent to + * `'hasRecoverySets'`. By contrast, the absence of `RecoverySetsOption` provide + * parameter defaults to the ancestor's `RecoverySetsOption` state, or + * `'hasRecoverySets'` if none. + * + * The `'noRecoverySets'` state, if used for the first incarnation, makes an + * issuer without recovery sets. If used for a successor incarnation, no matter + * whether the ancestor was `'hasRecoverySets'` or `'noRecoverySets'`, + * + * - will start emptying recovery sets, + * - will prevent any new payments from being added to recovery sets, + * - and (controversially) will not provide access via recovery sets of any + * payments that have not yet been emptied out. + * + * At this time, a `'noRecoverySets'` ancestor cannot be upgraded to a + * `'hasRecoverySets'` successor. If it turns out this transition is needed, it + * can likely be supported in a future upgrade. + * + * @typedef {'hasRecoverySets' | 'noRecoverySets'} RecoverySetsOption + */ + +// /////////////////////////// Purse / Payment ///////////////////////////////// + /** * @callback DepositFacetReceive * @param {Payment} payment @@ -270,10 +304,14 @@ * can spend the assets at stake on other things. Afterwards, if the recipient * of the original check finally gets around to depositing it, their deposit * fails. + * + * Returns an empty set if this issuer does not support recovery sets. * @property {() => Amount} recoverAll For use in emergencies, such as coming * back from a traumatic crash and upgrade. This deposits all the payments in * this purse's recovery set into the purse itself, returning the total amount * of assets recovered. + * + * Returns an empty amount if this issuer does not support recovery sets. */ /** @@ -298,6 +336,8 @@ * be treated with suspicion and verified elsewhere. */ +// /////////////////////////// MathHelpers ///////////////////////////////////// + /** * @template {AmountValue} V * @typedef {object} MathHelpers All of the difference in how digital asset diff --git a/packages/ERTP/test/swingsetTests/ertpService/vat-ertp-service.js b/packages/ERTP/test/swingsetTests/ertpService/vat-ertp-service.js index f2ee6f0463be..f675ea609d34 100644 --- a/packages/ERTP/test/swingsetTests/ertpService/vat-ertp-service.js +++ b/packages/ERTP/test/swingsetTests/ertpService/vat-ertp-service.js @@ -8,7 +8,7 @@ import { import { AssetKind, makeDurableIssuerKit, - prepareIssuerKit, + upgradeIssuerKit, } from '../../../src/index.js'; export const prepareErtpService = (baggage, exitVatWithFailure) => { @@ -36,7 +36,7 @@ export const prepareErtpService = (baggage, exitVatWithFailure) => { }); for (const issuerBaggage of issuerBaggageSet.values()) { - prepareIssuerKit(issuerBaggage, exitVatWithFailure); + upgradeIssuerKit(issuerBaggage, exitVatWithFailure); } return ertpService; diff --git a/packages/inter-protocol/src/price/fluxAggregatorContract.js b/packages/inter-protocol/src/price/fluxAggregatorContract.js index 2ac33c6482df..7f9fee9cabc0 100644 --- a/packages/inter-protocol/src/price/fluxAggregatorContract.js +++ b/packages/inter-protocol/src/price/fluxAggregatorContract.js @@ -1,10 +1,6 @@ // @jessie-check -import { - hasIssuer, - makeDurableIssuerKit, - prepareIssuerKit, -} from '@agoric/ertp'; +import { reallyPrepareIssuerKit } from '@agoric/ertp'; import { handleParamGovernance } from '@agoric/governance'; import { makeTracer, StorageNodeShape } from '@agoric/internal'; import { prepareDurablePublishKit } from '@agoric/notifier'; @@ -72,9 +68,14 @@ export const start = async (zcf, privateArgs, baggage) => { // xxx uses contract baggage as issuerBagage, assumes one issuer in this contract /** @type {import('./roundsManager.js').QuoteKit} */ - const quoteIssuerKit = hasIssuer(baggage) - ? prepareIssuerKit(baggage) - : makeDurableIssuerKit(baggage, 'quote', 'set'); + const quoteIssuerKit = reallyPrepareIssuerKit( + baggage, + 'quote', + 'set', + undefined, + undefined, + { recoverySetsOption: 'noRecoverySets' }, + ); const { highPrioritySendersManager, diff --git a/packages/vats/src/mintHolder.js b/packages/vats/src/mintHolder.js index 0529d3f16e0e..e89add895a63 100644 --- a/packages/vats/src/mintHolder.js +++ b/packages/vats/src/mintHolder.js @@ -1,11 +1,7 @@ // @ts-check // @jessie-check -import { - hasIssuer, - makeDurableIssuerKit, - prepareIssuerKit, -} from '@agoric/ertp'; +import { reallyPrepareIssuerKit } from '@agoric/ertp'; /** @typedef {import('@agoric/vat-data').Baggage} Baggage */ @@ -24,12 +20,8 @@ import { * @param {Baggage} baggage */ function provideIssuerKit(zcf, baggage) { - if (!hasIssuer(baggage)) { - const { keyword, assetKind, displayInfo } = zcf.getTerms(); - return makeDurableIssuerKit(baggage, keyword, assetKind, displayInfo); - } else { - return prepareIssuerKit(baggage); - } + const { keyword, assetKind, displayInfo } = zcf.getTerms(); + return reallyPrepareIssuerKit(baggage, keyword, assetKind, displayInfo); } /** @type {ContractMeta} */ diff --git a/packages/zoe/src/contractSupport/priceAuthorityQuoteMint.js b/packages/zoe/src/contractSupport/priceAuthorityQuoteMint.js index 813d4ffbbe92..da11b43f7476 100644 --- a/packages/zoe/src/contractSupport/priceAuthorityQuoteMint.js +++ b/packages/zoe/src/contractSupport/priceAuthorityQuoteMint.js @@ -1,34 +1,23 @@ -import { - AssetKind, - makeDurableIssuerKit, - prepareIssuerKit, -} from '@agoric/ertp'; -import { makeScalarBigMapStore } from '@agoric/vat-data'; +import { AssetKind, reallyPrepareIssuerKit } from '@agoric/ertp'; +import { provideDurableMapStore } from '@agoric/vat-data'; /** * * @param {import('@agoric/vat-data').Baggage} baggage + * @returns {ERef>} */ export const provideQuoteMint = baggage => { - /** @type {ERef>} */ - let baggageQuoteMint; - if (baggage.has(`quoteMintIssuerBaggage`)) { - const issuerBaggage = baggage.get(`quoteMintIssuerBaggage`); - baggageQuoteMint = /** @type {Mint<'set'>} */ ( - prepareIssuerKit(issuerBaggage).mint - ); - } else { - const issuerBaggage = makeScalarBigMapStore( - `scaledPriceAuthority quoteMintIssuerBaggage`, - { durable: true }, - ); - baggage.init(`quoteMintIssuerBaggage`, issuerBaggage); - baggageQuoteMint = makeDurableIssuerKit( - issuerBaggage, - 'quote', - AssetKind.SET, - ).mint; - } - - return baggageQuoteMint; + const issuerBaggage = provideDurableMapStore( + baggage, + 'quoteMintIssuerBaggage', + ); + const issuerKit = reallyPrepareIssuerKit( + issuerBaggage, + 'quote', + AssetKind.SET, + undefined, + undefined, + { recoverySetsOption: 'noRecoverySets' }, + ); + return issuerKit.mint; }; diff --git a/packages/zoe/src/zoeService/feeMint.js b/packages/zoe/src/zoeService/feeMint.js index 86b4a54c7e34..ad0e4d951cb0 100644 --- a/packages/zoe/src/zoeService/feeMint.js +++ b/packages/zoe/src/zoeService/feeMint.js @@ -1,9 +1,9 @@ import { - makeDurableIssuerKit, AssetKind, - prepareIssuerKit, IssuerShape, BrandShape, + reallyPrepareIssuerKit, + hasIssuer, } from '@agoric/ertp'; import { initEmpty, M } from '@agoric/store'; import { @@ -13,8 +13,9 @@ import { } from '@agoric/vat-data'; import { FeeMintAccessShape } from '../typeGuards.js'; -const { Fail } = assert; +const { Fail, quote: q } = assert; +/** @deprecated Redundant. Just omit it. */ const FEE_MINT_KIT = 'FeeMintKit'; export const defaultFeeIssuerConfig = harden( @@ -32,19 +33,22 @@ export const defaultFeeIssuerConfig = harden( */ const prepareFeeMint = (zoeBaggage, feeIssuerConfig, shutdownZoeVat) => { const mintBaggage = provideDurableMapStore(zoeBaggage, 'mintBaggage'); - if (!mintBaggage.has(FEE_MINT_KIT)) { - /** @type {IssuerKit} */ - const feeIssuerKit = makeDurableIssuerKit( - mintBaggage, - feeIssuerConfig.name, - feeIssuerConfig.assetKind, - feeIssuerConfig.displayInfo, - shutdownZoeVat, - ); - mintBaggage.init(FEE_MINT_KIT, feeIssuerKit); - } else { - prepareIssuerKit(mintBaggage, shutdownZoeVat); + if (mintBaggage.has(FEE_MINT_KIT)) { + hasIssuer(mintBaggage) || + Fail`Legacy ${q( + FEE_MINT_KIT, + )} must be redundant with normal storing of issuerKit in issuerBaggage`; + // Upgrade this legacy state by simply deleting it. + mintBaggage.delete(FEE_MINT_KIT); } + /** @type {IssuerKit} */ + const feeIssuerKit = reallyPrepareIssuerKit( + mintBaggage, + feeIssuerConfig.name, + feeIssuerConfig.assetKind, + feeIssuerConfig.displayInfo, + shutdownZoeVat, + ); const FeeMintIKit = harden({ feeMint: M.interface('FeeMint', { @@ -66,13 +70,13 @@ const prepareFeeMint = (zoeBaggage, feeIssuerConfig, shutdownZoeVat) => { const { facets } = this; facets.feeMintAccess === allegedFeeMintAccess || Fail`The object representing access to the fee brand mint was not provided`; - return mintBaggage.get(FEE_MINT_KIT); + return feeIssuerKit; }, getFeeIssuer() { - return mintBaggage.get(FEE_MINT_KIT).issuer; + return feeIssuerKit.issuer; }, getFeeBrand() { - return mintBaggage.get(FEE_MINT_KIT).brand; + return feeIssuerKit.brand; }, }, // feeMintAccess is an opaque durable object representing the right to get diff --git a/packages/zoe/src/zoeService/makeInvitation.js b/packages/zoe/src/zoeService/makeInvitation.js index cb59e7c549f6..7f51cc73e5c3 100644 --- a/packages/zoe/src/zoeService/makeInvitation.js +++ b/packages/zoe/src/zoeService/makeInvitation.js @@ -1,13 +1,13 @@ // @jessie-check +import { Fail } from '@agoric/assert'; import { provideDurableMapStore } from '@agoric/vat-data'; -import { - AssetKind, - makeDurableIssuerKit, - prepareIssuerKit, -} from '@agoric/ertp'; +import { AssetKind, hasIssuer, reallyPrepareIssuerKit } from '@agoric/ertp'; import { InvitationElementShape } from '../typeGuards.js'; +/** + * Not deprecated because the first use below is still correct. + */ const ZOE_INVITATION_KIT = 'ZoeInvitationKit'; /** @@ -15,26 +15,27 @@ const ZOE_INVITATION_KIT = 'ZoeInvitationKit'; * @param {ShutdownWithFailure | undefined} shutdownZoeVat */ export const prepareInvitationKit = (baggage, shutdownZoeVat = undefined) => { - /** @type {IssuerKit<'set'> | undefined} */ - let invitationKit; - const invitationKitBaggage = provideDurableMapStore( baggage, ZOE_INVITATION_KIT, ); - if (!invitationKitBaggage.has(ZOE_INVITATION_KIT)) { - invitationKit = makeDurableIssuerKit( - invitationKitBaggage, - 'Zoe Invitation', - AssetKind.SET, - undefined, - shutdownZoeVat, - { elementShape: InvitationElementShape }, - ); - invitationKitBaggage.init(ZOE_INVITATION_KIT, invitationKit); - } else { - invitationKit = prepareIssuerKit(invitationKitBaggage); + if (invitationKitBaggage.has(ZOE_INVITATION_KIT)) { + // This legacy second use of ZOE_INVITATION_KIT is unneeded. + hasIssuer(invitationKitBaggage) || + Fail`Legacy use of ${q( + ZOE_INVITATION_KIT, + )} must be redundant with normal storing of issuerKit in issuerBaggage`; + // Upgrade this legacy state by simply deleting it. + invitationKitBaggage.delete(ZOE_INVITATION_KIT); } + const invitationKit = reallyPrepareIssuerKit( + invitationKitBaggage, + 'Zoe Invitation', + AssetKind.SET, + undefined, + shutdownZoeVat, + { elementShape: InvitationElementShape }, + ); return harden({ invitationIssuer: invitationKit.issuer, diff --git a/packages/zoe/src/zoeService/zoeStorageManager.js b/packages/zoe/src/zoeService/zoeStorageManager.js index 9f2d8d36d11f..675b097daed6 100644 --- a/packages/zoe/src/zoeService/zoeStorageManager.js +++ b/packages/zoe/src/zoeService/zoeStorageManager.js @@ -3,7 +3,7 @@ import { AssetKind, makeDurableIssuerKit, AmountMath, - prepareIssuerKit, + upgradeIssuerKit, } from '@agoric/ertp'; import { makeScalarBigMapStore, @@ -122,7 +122,7 @@ export const makeZoeStorageManager = ( 'zoeMintBaggageSet', ); for (const issuerBaggage of zoeMintBaggageSet.values()) { - prepareIssuerKit(issuerBaggage); + upgradeIssuerKit(issuerBaggage); } const makeZoeMint = prepareExoClass(