From bf46bdba7eb0167f0678b591a5be357c89c72d6b Mon Sep 17 00:00:00 2001 From: Chris Hibbert Date: Mon, 20 Jun 2022 17:51:54 -0700 Subject: [PATCH] refactor: make zcf durable unnest defineDurableKind relax for promises too use seatHandle rather than zoeSeatAdmin notifiers not stored make the zcfSeatMint durable pass bundleCap to createZcfVat and rename to zcfBundleCapP --- packages/ERTP/src/purse.js | 24 +- packages/ERTP/src/transientNotifier.js | 30 +++ .../src/liveslots/collectionManager.js | 8 +- .../src/liveslots/virtualObjectManager.js | 6 +- .../src/liveslots/virtualReferences.js | 8 +- packages/store/src/stores/store-utils.js | 2 +- .../src/contractFacet/offerHandlerStorage.js | 11 +- packages/zoe/src/contractFacet/types.js | 2 +- packages/zoe/src/contractFacet/vatRoot.js | 18 +- packages/zoe/src/contractFacet/zcfMint.js | 176 ++++++++++++++ packages/zoe/src/contractFacet/zcfSeat.js | 126 +++++----- packages/zoe/src/contractFacet/zcfZygote.js | 220 ++++++++---------- packages/zoe/src/internal-types.js | 10 +- packages/zoe/src/issuerStorage.js | 23 +- packages/zoe/src/zoeService/createZCFVat.js | 17 +- packages/zoe/src/zoeService/startInstance.js | 14 +- packages/zoe/src/zoeService/zoeSeat.js | 1 + .../unitTests/contractSupport/test-offerTo.js | 2 +- .../unitTests/contracts/test-sellTickets.js | 2 +- .../contracts/test-throwInOfferHandler.js | 2 +- .../zoe/test/unitTests/test-issuerStorage.js | 2 +- packages/zoe/test/unitTests/zcf/test-zcf.js | 12 +- .../zoe/test/unitTests/zcf/test-zcfSeat.js | 7 +- .../test/unitTests/zcf/test-zoeHelpersWZcf.js | 39 ++-- 24 files changed, 496 insertions(+), 266 deletions(-) create mode 100644 packages/ERTP/src/transientNotifier.js create mode 100644 packages/zoe/src/contractFacet/zcfMint.js diff --git a/packages/ERTP/src/purse.js b/packages/ERTP/src/purse.js index d0dc86d9d581..0db0d0a372ea 100644 --- a/packages/ERTP/src/purse.js +++ b/packages/ERTP/src/purse.js @@ -2,11 +2,9 @@ import { defineDurableKindMulti, makeScalarBigSetStore, provideKindHandle, - makeScalarBigWeakMapStore, } from '@agoric/vat-data'; -import { provide } from '@agoric/store'; -import { makeNotifierKit } from '@agoric/notifier'; import { AmountMath } from './amountMath.js'; +import { makeTransientNotifierKit } from './transientNotifier.js'; const { details: X } = assert; @@ -20,27 +18,11 @@ export const defineDurablePurse = ( ) => { // Note: Virtual for high cardinality, but *not* durable, and so // broken across an upgrade. - /** @type {WeakMapStore>} */ - const transientNotiferKits = makeScalarBigWeakMapStore( - 'transientNotiferKits', - ); - - const provideNotifierKit = purse => - provide(transientNotiferKits, purse, () => - makeNotifierKit(purse.getCurrentAmount()), - ); - - const provideNotifier = purse => provideNotifierKit(purse).notifier; - const notifyBalance = purse => { - if (transientNotiferKits.has(purse)) { - const { updater } = transientNotiferKits.get(purse); - updater.updateState(purse.getCurrentAmount()); - } - }; + const { provideNotifier, update: updateBalance } = makeTransientNotifierKit(); const updatePurseBalance = (state, newPurseBalance, purse) => { state.currentBalance = newPurseBalance; - notifyBalance(purse); + updateBalance(purse, purse.getCurrentAmount()); }; // - This kind is a pair of purse and depositFacet that have a 1:1 diff --git a/packages/ERTP/src/transientNotifier.js b/packages/ERTP/src/transientNotifier.js new file mode 100644 index 000000000000..ab9803a7efe2 --- /dev/null +++ b/packages/ERTP/src/transientNotifier.js @@ -0,0 +1,30 @@ +// @ts-check + +import { makeScalarBigWeakMapStore } from '@agoric/vat-data'; +import { provide } from '@agoric/store'; +import { makeNotifierKit } from '@agoric/notifier'; + +// Note: Virtual for high cardinality, but *not* durable, and so +// broken across an upgrade. +export const makeTransientNotifierKit = () => { + /** @type {WeakMapStore>} */ + const transientNotiferKits = makeScalarBigWeakMapStore( + 'transientNotiferKits', + ); + + const provideNotifierKit = key => + provide(transientNotiferKits, key, () => + makeNotifierKit(key.getCurrentAmount()), + ); + + const provideNotifier = key => provideNotifierKit(key).notifier; + const update = (key, newValue) => { + if (transientNotiferKits.has(key)) { + const { updater } = transientNotiferKits.get(key); + updater.updateState(newValue); + } + }; + + return { provideNotifier, update }; +}; +harden(makeTransientNotifierKit); diff --git a/packages/SwingSet/src/liveslots/collectionManager.js b/packages/SwingSet/src/liveslots/collectionManager.js index 845e438b6499..009e3723da5f 100644 --- a/packages/SwingSet/src/liveslots/collectionManager.js +++ b/packages/SwingSet/src/liveslots/collectionManager.js @@ -79,7 +79,9 @@ function throwNotDurable(value, slotIndex, serializedValue) { ); } catch (justinError) { const err = assert.error( - X`value is not durable: ${value} at slot ${slotIndex} of ${serializedValue}`, + X`value is not durable: ${value} at slot ${q( + slotIndex, + )} of ${serializedValue}`, ); assert.note( err, @@ -88,7 +90,9 @@ function throwNotDurable(value, slotIndex, serializedValue) { throw err; } assert.fail( - X`value is not durable: ${value} at slot ${slotIndex} of ${encodedValue}`, + X`value is not durable: ${value} at slot ${q( + slotIndex, + )} of ${encodedValue}`, ); } diff --git a/packages/SwingSet/src/liveslots/virtualObjectManager.js b/packages/SwingSet/src/liveslots/virtualObjectManager.js index 226bd575e24a..f98381490ccc 100644 --- a/packages/SwingSet/src/liveslots/virtualObjectManager.js +++ b/packages/SwingSet/src/liveslots/virtualObjectManager.js @@ -651,7 +651,9 @@ export function makeVirtualObjectManager( after.slots.forEach((vref, index) => { assert( vrm.isDurable(vref), - X`value for ${prop} is not durable at slot ${index} of ${after}`, + X`value for ${q(prop)} is not durable at slot ${q( + index, + )} of ${after}`, ); }); } @@ -736,7 +738,7 @@ export function makeVirtualObjectManager( const data = serialize(initialData[prop]); if (durable) { data.slots.forEach(vref => { - assert(vrm.isDurable(vref), X`value for ${prop} is not durable`); + assert(vrm.isDurable(vref), X`value for ${q(prop)} is not durable`); }); } data.slots.forEach(vrm.addReachableVref); diff --git a/packages/SwingSet/src/liveslots/virtualReferences.js b/packages/SwingSet/src/liveslots/virtualReferences.js index f99784a2f44b..6cbcdc11dd0c 100644 --- a/packages/SwingSet/src/liveslots/virtualReferences.js +++ b/packages/SwingSet/src/liveslots/virtualReferences.js @@ -256,15 +256,15 @@ export function makeVirtualReferenceManager( */ function isDurable(vref) { const { type, id, virtual, allocatedByVat } = parseVatSlot(vref); - if (type !== 'object') { + if (relaxDurabilityRules) { + // we'll pretend an object is durable if running with relaxed rules + return true; + } else if (type !== 'object') { // promises and devices are not durable return false; } else if (!allocatedByVat) { // imports are durable return true; - } else if (relaxDurabilityRules) { - // we'll pretend an object is durable if running with relaxed rules - return true; } else if (virtual) { // stores and virtual objects are durable if their kinds are so configured return isDurableKind(id); diff --git a/packages/store/src/stores/store-utils.js b/packages/store/src/stores/store-utils.js index b26ca1fd2a7b..541b938a6904 100644 --- a/packages/store/src/stores/store-utils.js +++ b/packages/store/src/stores/store-utils.js @@ -94,7 +94,7 @@ harden(makeCurrentKeysKit); * that key, and return it. * * @template K,V - * @param {MapStore} mapStore + * @param {WeakMapStore} mapStore * @param {K} key * @param {(key: K) => V} makeValue * @returns {V} diff --git a/packages/zoe/src/contractFacet/offerHandlerStorage.js b/packages/zoe/src/contractFacet/offerHandlerStorage.js index 025d2fe9afca..571b3b5d2528 100644 --- a/packages/zoe/src/contractFacet/offerHandlerStorage.js +++ b/packages/zoe/src/contractFacet/offerHandlerStorage.js @@ -2,17 +2,22 @@ import { makeWeakStore } from '@agoric/store'; import { ToFarFunction } from '@endo/marshal'; +import { makeScalarBigMapStore } from '@agoric/vat-data'; -import { makeHandle } from '../makeHandle.js'; +import { defineDurableHandle } from '../makeHandle.js'; -export const makeOfferHandlerStorage = () => { +export const makeOfferHandlerStorage = ( + zcfBaggage = makeScalarBigMapStore('zcfBaggage', { durable: true }), +) => { + const makeInvitationHandle = defineDurableHandle(zcfBaggage, 'Invitation'); /** @type {WeakStore} */ + // TODO(MSM): we need to manage durable offerHandlers const invitationHandleToHandler = makeWeakStore('invitationHandle'); /** @type {(offerHandler: OfferHandler) => InvitationHandle} */ const storeOfferHandler = offerHandler => { const farOfferHandler = ToFarFunction('offerHandler', offerHandler); - const invitationHandle = makeHandle('Invitation'); + const invitationHandle = makeInvitationHandle(); invitationHandleToHandler.init(invitationHandle, farOfferHandler); return invitationHandle; }; diff --git a/packages/zoe/src/contractFacet/types.js b/packages/zoe/src/contractFacet/types.js index 1c36d836bcdf..57a6fe4e5bff 100644 --- a/packages/zoe/src/contractFacet/types.js +++ b/packages/zoe/src/contractFacet/types.js @@ -188,7 +188,7 @@ * @typedef {object} ZCFSeat * @property {() => void} exit * @property {ZCFSeatFail} fail - * @property {() => Notifier} getNotifier + * @property {() => Promise>} getNotifier * @property {() => boolean} hasExited * @property {() => ProposalRecord} getProposal * @property {ZCFGetAmountAllocated} getAmountAllocated diff --git a/packages/zoe/src/contractFacet/vatRoot.js b/packages/zoe/src/contractFacet/vatRoot.js index a2a1a684a38a..f0cfee232818 100644 --- a/packages/zoe/src/contractFacet/vatRoot.js +++ b/packages/zoe/src/contractFacet/vatRoot.js @@ -28,13 +28,15 @@ export function buildRootObject(powers) { /** @type {ExecuteContract} */ const executeContract = ( - bundleOrBundleCap, - zoeService, - invitationIssuer, - zoeInstanceAdmin, - instanceRecordFromZoe, - issuerStorageFromZoe, - privateArgs = undefined, + contractBundleCap, // vatParams every time + zoeService, // vatParams first time and then baggage + invitationIssuer, // vatParams first time and then baggage + zoeInstanceAdmin, // in 1st message post-clone + instanceRecordFromZoe, // in 1st msg post-clone (could split out installation) + // terms might change on upgrade? + issuerStorageFromZoe, // instance specific; stored in baggage + privateArgs = undefined, // instance specific; stored in baggage; + // upgrade might supplement this ) => { /** @type {ZCFZygote} */ const zcfZygote = makeZCFZygote( @@ -43,7 +45,7 @@ export function buildRootObject(powers) { invitationIssuer, testJigSetter, ); - zcfZygote.evaluateContract(bundleOrBundleCap); + zcfZygote.evaluateContract(contractBundleCap); return zcfZygote.startContract( zoeInstanceAdmin, instanceRecordFromZoe, diff --git a/packages/zoe/src/contractFacet/zcfMint.js b/packages/zoe/src/contractFacet/zcfMint.js new file mode 100644 index 000000000000..581a496432cf --- /dev/null +++ b/packages/zoe/src/contractFacet/zcfMint.js @@ -0,0 +1,176 @@ +// @ts-check + +import { E } from '@endo/eventual-send'; +import { AmountMath } from '@agoric/ertp'; +import { + provideDurableSingleton, + provideDurableSetStore, + makeScalarBigMapStore, +} from '@agoric/vat-data'; + +import { coerceAmountKeywordRecord } from '../cleanProposal.js'; +import { makeIssuerRecord } from '../issuerRecord.js'; +import { addToAllocation, subtractFromAllocation } from './allocationMath.js'; + +import '../../exported.js'; +import '../internal-types.js'; +import './internal-types.js'; + +import '@agoric/swingset-vat/src/types-ambient.js'; + +// helpers for the code shared between MakeZCFMint and RegisterZCFMint + +export const makeZCFMintFactory = async ( + zcfBaggage, + recordIssuer, + getAssetKindByBrand, + makeEmptySeatKit, + reallocateForZCFMint, +) => { + // The set of baggages for zcfMints + const zcfMintBaggageSet = provideDurableSetStore(zcfBaggage, 'baggageSet'); + + /** + * retrieve the state of the zcfMint from the baggage, and create a durable + * singleton reflecting that state. + * + * @param {MapStore} zcfMintBaggage + */ + const provideDurableZcfMint = async zcfMintBaggage => { + const keyword = zcfMintBaggage.get('keyword'); + const zoeMintP = zcfMintBaggage.get('zoeMintP'); + const { + brand: mintyBrand, + issuer: mintyIssuer, + displayInfo: mintyDisplayInfo, + } = await E(zoeMintP).getIssuerRecord(); + // AWAIT + const mintyIssuerRecord = makeIssuerRecord( + mintyBrand, + mintyIssuer, + mintyDisplayInfo, + ); + recordIssuer(keyword, mintyIssuerRecord); + + const empty = AmountMath.makeEmpty(mintyBrand, mintyDisplayInfo.assetKind); + const add = (total, amountToAdd) => { + return AmountMath.add(total, amountToAdd, mintyBrand); + }; + + return provideDurableSingleton( + zcfMintBaggage, + 'zcfMint', + /** @type {ZCFMint} */ + { + getIssuerRecord: _context => { + return mintyIssuerRecord; + }, + mintGains: (_context, gains, zcfSeat = undefined) => { + gains = coerceAmountKeywordRecord(gains, getAssetKindByBrand); + if (zcfSeat === undefined) { + zcfSeat = makeEmptySeatKit().zcfSeat; + } + const totalToMint = Object.values(gains).reduce(add, empty); + assert( + // @ts-expect-error It's non-null + !zcfSeat.hasExited(), + `zcfSeat must be active to mint gains for the zcfSeat`, + ); + const allocationPlusGains = addToAllocation( + // @ts-expect-error It's non-null + zcfSeat.getCurrentAllocation(), + gains, + ); + + // Increment the stagedAllocation if it exists so that the + // stagedAllocation is kept up to the currentAllocation + // @ts-expect-error It's non-null + if (zcfSeat.hasStagedAllocation()) { + // @ts-expect-error It's non-null + zcfSeat.incrementBy(gains); + } + + // Offer safety should never be able to be violated here, as + // we are adding assets. However, we keep this check so that + // all reallocations are covered by offer safety checks, and + // that any bug within Zoe that may affect this is caught. + assert( + // @ts-expect-error It's non-null + zcfSeat.isOfferSafe(allocationPlusGains), + `The allocation after minting gains ${allocationPlusGains} for the zcfSeat was not offer safe`, + ); + // No effects above, apart from incrementBy. Note COMMIT POINT within + // reallocateForZCFMint. The following two steps *should* be + // committed atomically, but it is not a disaster if they are + // not. If we minted only, no one would ever get those + // invisibly-minted assets. + E(zoeMintP).mintAndEscrow(totalToMint); + reallocateForZCFMint(zcfSeat, allocationPlusGains); + return zcfSeat; + }, + burnLosses: (_context, losses, zcfSeat) => { + losses = coerceAmountKeywordRecord(losses, getAssetKindByBrand); + const totalToBurn = Object.values(losses).reduce(add, empty); + assert( + !zcfSeat.hasExited(), + `zcfSeat must be active to burn losses from the zcfSeat`, + ); + const allocationMinusLosses = subtractFromAllocation( + zcfSeat.getCurrentAllocation(), + losses, + ); + + // verifies offer safety + assert( + zcfSeat.isOfferSafe(allocationMinusLosses), + `The allocation after burning losses ${allocationMinusLosses}for the zcfSeat was not offer safe`, + ); + + // Decrement the stagedAllocation if it exists so that the + // stagedAllocation is kept up to the currentAllocation + if (zcfSeat.hasStagedAllocation()) { + zcfSeat.decrementBy(losses); + } + + // No effects above, apart from decrementBy. Note COMMIT POINT within + // reallocateForZCFMint. The following two steps *should* be + // committed atomically, but it is not a disaster if they are + // not. If we only commit the allocationMinusLosses no one would + // ever get the unburned assets. + reallocateForZCFMint(zcfSeat, allocationMinusLosses); + E(zoeMintP).withdrawAndBurn(totalToBurn); + }, + }, + ); + }; + + const makeDurableZcfMint = async (keyword, zoeMintP, zcfMintBaggage) => { + zcfMintBaggage.init('keyword', keyword); + zcfMintBaggage.init('zoeMintP', zoeMintP); + return provideDurableZcfMint(zcfMintBaggage); + }; + + /** + * zcfMintFactory has a method makeZcfMint() that takes a keyword and the + * promise returned by a makeZoeMint() call. makeZcfMint() creates a new + * baggage for the state of the zcfMint, makes a durableZcfMint from that + * baggage, and registers that baggage to be revived with the factory. + */ + const zcfMintFactory = provideDurableSingleton(zcfBaggage, 'zcfMintFactory', { + makeZcfMint: (keyword, zoeMintP) => { + const zcfMintBaggage = makeScalarBigMapStore('zcfMintBaggage', { + durable: true, + }); + const zcfMint = makeDurableZcfMint(keyword, zoeMintP, zcfMintBaggage); + zcfMintBaggageSet.add(zcfMint); + return zcfMint; + }, + }); + + for (const zcfMintBaggage of zcfMintBaggageSet.values()) { + provideDurableZcfMint(zcfMintBaggage); + } + + return zcfMintFactory; +}; +harden(makeZCFMintFactory); diff --git a/packages/zoe/src/contractFacet/zcfSeat.js b/packages/zoe/src/contractFacet/zcfSeat.js index cb3b3816e27d..f881095ea363 100644 --- a/packages/zoe/src/contractFacet/zcfSeat.js +++ b/packages/zoe/src/contractFacet/zcfSeat.js @@ -1,10 +1,16 @@ // @ts-check import { assert, details as X } from '@agoric/assert'; +import { + provideDurableWeakMapStore, + makeScalarBigMapStore, + provideKindHandle, + defineDurableKind, + dropContext, +} from '@agoric/vat-data'; import { makeWeakStore, makeStore } from '@agoric/store'; import { E } from '@endo/eventual-send'; import { AmountMath } from '@agoric/ertp'; -import { Far } from '@endo/marshal'; import { isOfferSafe } from './offerSafety.js'; import { assertRightsConserved } from './rightsConservation.js'; @@ -16,9 +22,10 @@ export const createSeatManager = ( zoeInstanceAdmin, getAssetKindByBrand, shutdownWithFailure, + zcfBaggage = makeScalarBigMapStore('zcfBaggage', { durable: true }), ) => { /** @type {WeakStore} */ - let activeZCFSeats = makeWeakStore('zcfSeat'); + let activeZCFSeats = provideDurableWeakMapStore(zcfBaggage, 'zcfSeat'); /** @type {Store} */ const zcfSeatToStagedAllocations = makeStore('zcfSeat'); @@ -220,35 +227,35 @@ export const createSeatManager = ( } }; - /** @type {MakeZCFSeat} */ - const makeZCFSeat = ( - zoeSeatAdmin, - { proposal, notifier, initialAllocation, seatHandle }, - ) => { - /** - * @param {ZCFSeat} zcfSeat - */ - const assertNoStagedAllocation = zcfSeat => { - if (hasStagedAllocation(zcfSeat)) { - assert.fail( - X`The seat could not be exited with a staged but uncommitted allocation: ${getStagedAllocation( - zcfSeat, - )}. Please reallocate over this seat or clear the staged allocation.`, - ); - } - }; - - /** @type {ZCFSeat} */ - const zcfSeat = Far('zcfSeat', { - getNotifier: () => notifier, - getProposal: () => proposal, - exit: completion => { - assertActive(zcfSeat); - assertNoStagedAllocation(zcfSeat); - doExitSeat(zcfSeat); - E(zoeSeatAdmin).exit(completion); + /** + * @param {ZCFSeat} zcfSeat + */ + const assertNoStagedAllocation = zcfSeat => { + if (hasStagedAllocation(zcfSeat)) { + assert.fail( + X`The seat could not be exited with a staged but uncommitted allocation: ${getStagedAllocation( + zcfSeat, + )}. Please reallocate over this seat or clear the staged allocation.`, + ); + } + }; + + const zcfSeatKindHandle = provideKindHandle(zcfBaggage, 'zcfSeat'); + const makeZCFSeatInternal = defineDurableKind( + zcfSeatKindHandle, + proposal => ({ proposal }), + { + getNotifier: ({ self }) => + E(zoeInstanceAdmin).getSeatNotifier(zcfSeatToSeatHandle.get(self)), + getProposal: ({ state }) => state.proposal, + exit: ({ self }, completion) => { + assertActive(self); + assertNoStagedAllocation(self); + doExitSeat(self); + E(zoeInstanceAdmin).exitSeat(zcfSeatToSeatHandle.get(self), completion); }, fail: ( + { self }, reason = new Error( 'Seat exited with failure. Please check the log for more information.', ), @@ -260,17 +267,20 @@ export const createSeatManager = ( X`ZCFSeat.fail was called with a string reason, but requires an Error argument.`, ); } - if (!hasExited(zcfSeat)) { - doExitSeat(zcfSeat); - E(zoeSeatAdmin).fail(harden(reason)); + if (!hasExited(self)) { + doExitSeat(self); + E(zoeInstanceAdmin).failSeat( + zcfSeatToSeatHandle.get(self), + harden(reason), + ); } return reason; }, - hasExited: () => hasExited(zcfSeat), + hasExited: ({ self }) => hasExited(self), - getAmountAllocated: (keyword, brand) => { - assertActive(zcfSeat); - const currentAllocation = getCurrentAllocation(zcfSeat); + getAmountAllocated: ({ self }, keyword, brand) => { + assertActive(self); + const currentAllocation = getCurrentAllocation(self); if (currentAllocation[keyword] !== undefined) { return currentAllocation[keyword]; } @@ -281,52 +291,58 @@ export const createSeatManager = ( const assetKind = getAssetKindByBrand(brand); return AmountMath.makeEmpty(brand, assetKind); }, - getCurrentAllocation: () => getCurrentAllocation(zcfSeat), - getStagedAllocation: () => getStagedAllocation(zcfSeat), - isOfferSafe: newAllocation => { - assertActive(zcfSeat); - const currentAllocation = getCurrentAllocation(zcfSeat); + getCurrentAllocation: ({ self }) => getCurrentAllocation(self), + getStagedAllocation: ({ self }) => getStagedAllocation(self), + isOfferSafe: ({ state, self }, newAllocation) => { + assertActive(self); + const currentAllocation = getCurrentAllocation(self); const reallocation = harden({ ...currentAllocation, ...newAllocation, }); - return isOfferSafe(proposal, reallocation); + return isOfferSafe(state.proposal, reallocation); }, - incrementBy: amountKeywordRecord => { - assertActive(zcfSeat); + incrementBy: ({ self }, amountKeywordRecord) => { + assertActive(self); amountKeywordRecord = coerceAmountKeywordRecord( amountKeywordRecord, getAssetKindByBrand, ); setStagedAllocation( - zcfSeat, - addToAllocation(getStagedAllocation(zcfSeat), amountKeywordRecord), + self, + addToAllocation(getStagedAllocation(self), amountKeywordRecord), ); return amountKeywordRecord; }, - decrementBy: amountKeywordRecord => { - assertActive(zcfSeat); + decrementBy: ({ self }, amountKeywordRecord) => { + assertActive(self); amountKeywordRecord = coerceAmountKeywordRecord( amountKeywordRecord, getAssetKindByBrand, ); setStagedAllocation( - zcfSeat, + self, subtractFromAllocation( - getStagedAllocation(zcfSeat), + getStagedAllocation(self), amountKeywordRecord, ), ); return amountKeywordRecord; }, - clear, - hasStagedAllocation: () => hasStagedAllocation(zcfSeat), - }); - + clear: dropContext(clear), + hasStagedAllocation: ({ self }) => hasStagedAllocation(self), + }, + ); + const makeZCFSeat = ({ + proposal, + notifier: _notifier, + initialAllocation, + seatHandle, + }) => { + const zcfSeat = makeZCFSeatInternal(proposal); activeZCFSeats.init(zcfSeat, initialAllocation); zcfSeatToSeatHandle.init(zcfSeat, seatHandle); - return zcfSeat; }; diff --git a/packages/zoe/src/contractFacet/zcfZygote.js b/packages/zoe/src/contractFacet/zcfZygote.js index 410713e55a62..aa9856978dc2 100644 --- a/packages/zoe/src/contractFacet/zcfZygote.js +++ b/packages/zoe/src/contractFacet/zcfZygote.js @@ -1,24 +1,26 @@ // @ts-check -import { assert, details as X, makeAssert } from '@agoric/assert'; import { E } from '@endo/eventual-send'; import { Far, Remotable, passStyleOf } from '@endo/marshal'; -import { AssetKind, AmountMath } from '@agoric/ertp'; +import { AssetKind } from '@agoric/ertp'; import { makeNotifierKit, observeNotifier } from '@agoric/notifier'; import { makePromiseKit } from '@endo/promise-kit'; import { assertPattern } from '@agoric/store'; +import { + makeScalarBigMapStore, + provideDurableMapStore, +} from '@agoric/vat-data'; -import { cleanProposal, coerceAmountKeywordRecord } from '../cleanProposal.js'; +import { cleanProposal } from '../cleanProposal.js'; import { evalContractBundle } from './evalContractCode.js'; import { makeExitObj } from './exit.js'; -import { makeHandle } from '../makeHandle.js'; +import { defineDurableHandle } from '../makeHandle.js'; import { makeIssuerStorage } from '../issuerStorage.js'; -import { makeIssuerRecord } from '../issuerRecord.js'; import { createSeatManager } from './zcfSeat.js'; import { makeInstanceRecordStorage } from '../instanceRecordStorage.js'; import { handlePWarning, handlePKitWarning } from '../handleWarning.js'; import { makeOfferHandlerStorage } from './offerHandlerStorage.js'; -import { addToAllocation, subtractFromAllocation } from './allocationMath.js'; +import { makeZCFMintFactory } from './zcfMint.js'; import '../../exported.js'; import '../internal-types.js'; @@ -26,6 +28,8 @@ import './internal-types.js'; import '@agoric/swingset-vat/src/types-ambient.js'; +const { details: X, makeAssert } = assert; + /** * Make the ZCF vat in zygote-usable form. First, a generic ZCF is * made, then the contract code is evaluated, then a particular @@ -35,6 +39,7 @@ import '@agoric/swingset-vat/src/types-ambient.js'; * @param {ERef} zoeService * @param {Issuer} invitationIssuer * @param {TestJigSetter} testJigSetter + * @param {MapStore} zcfBaggage * @returns {ZCFZygote} */ export const makeZCFZygote = ( @@ -42,7 +47,9 @@ export const makeZCFZygote = ( zoeService, invitationIssuer, testJigSetter, + zcfBaggage = makeScalarBigMapStore('zcfBaggage', { durable: true }), ) => { + const makeSeatHandle = defineDurableHandle(zcfBaggage, 'Seat'); /** @type {PromiseRecord} */ const zoeInstanceAdminPromiseKit = makePromiseKit(); const zoeInstanceAdmin = zoeInstanceAdminPromiseKit.promise; @@ -91,12 +98,9 @@ export const makeZCFZygote = ( const initialAllocation = harden({}); const proposal = cleanProposal(harden({ exit }), getAssetKindByBrand); const { notifier, updater } = makeNotifierKit(); - /** @type {PromiseRecord} */ - const zoeSeatAdminPromiseKit = makePromiseKit(); - handlePKitWarning(zoeSeatAdminPromiseKit); const userSeatPromiseKit = makePromiseKit(); handlePKitWarning(userSeatPromiseKit); - const seatHandle = makeHandle('SeatHandle'); + const seatHandle = makeSeatHandle(); const seatData = harden({ proposal, @@ -104,119 +108,27 @@ export const makeZCFZygote = ( notifier, seatHandle, }); - const zcfSeat = makeZCFSeat(zoeSeatAdminPromiseKit.promise, seatData); + const zcfSeat = makeZCFSeat(seatData); const exitObj = makeExitObj(seatData.proposal, zcfSeat); E(zoeInstanceAdmin) .makeNoEscrowSeat(initialAllocation, proposal, exitObj, seatHandle) - .then(({ zoeSeatAdmin, notifier: zoeNotifier, userSeat }) => { + .then(({ notifier: zoeNotifier, userSeat }) => { observeNotifier(zoeNotifier, updater); - zoeSeatAdminPromiseKit.resolve(zoeSeatAdmin); userSeatPromiseKit.resolve(userSeat); }); return { zcfSeat, userSeat: userSeatPromiseKit.promise }; }; - // A helper for the code shared between MakeZCFMint and RegisterZCFMint - const doMakeZCFMint = async (keyword, zoeMintP) => { - const { - brand: mintyBrand, - issuer: mintyIssuer, - displayInfo: mintyDisplayInfo, - } = await E(zoeMintP).getIssuerRecord(); - // AWAIT - const mintyIssuerRecord = makeIssuerRecord( - mintyBrand, - mintyIssuer, - mintyDisplayInfo, - ); - recordIssuer(keyword, mintyIssuerRecord); - - const empty = AmountMath.makeEmpty(mintyBrand, mintyDisplayInfo.assetKind); - const add = (total, amountToAdd) => { - return AmountMath.add(total, amountToAdd, mintyBrand); - }; - - /** @type {ZCFMint} */ - const zcfMint = Far('zcfMint', { - getIssuerRecord: () => { - return mintyIssuerRecord; - }, - mintGains: (gains, zcfSeat = undefined) => { - gains = coerceAmountKeywordRecord(gains, getAssetKindByBrand); - if (zcfSeat === undefined) { - zcfSeat = makeEmptySeatKit().zcfSeat; - } - const totalToMint = Object.values(gains).reduce(add, empty); - assert( - !zcfSeat.hasExited(), - `zcfSeat must be active to mint gains for the zcfSeat`, - ); - const allocationPlusGains = addToAllocation( - zcfSeat.getCurrentAllocation(), - gains, - ); - - // Increment the stagedAllocation if it exists so that the - // stagedAllocation is kept up to the currentAllocation - if (zcfSeat.hasStagedAllocation()) { - zcfSeat.incrementBy(gains); - } - - // Offer safety should never be able to be violated here, as - // we are adding assets. However, we keep this check so that - // all reallocations are covered by offer safety checks, and - // that any bug within Zoe that may affect this is caught. - assert( - zcfSeat.isOfferSafe(allocationPlusGains), - `The allocation after minting gains ${allocationPlusGains} for the zcfSeat was not offer safe`, - ); - // No effects above, apart from incrementBy. Note COMMIT POINT within - // reallocateForZCFMint. The following two steps *should* be - // committed atomically, but it is not a disaster if they are - // not. If we minted only, no one would ever get those - // invisibly-minted assets. - E(zoeMintP).mintAndEscrow(totalToMint); - reallocateForZCFMint(zcfSeat, allocationPlusGains); - return zcfSeat; - }, - burnLosses: (losses, zcfSeat) => { - losses = coerceAmountKeywordRecord(losses, getAssetKindByBrand); - const totalToBurn = Object.values(losses).reduce(add, empty); - assert( - !zcfSeat.hasExited(), - `zcfSeat must be active to burn losses from the zcfSeat`, - ); - const allocationMinusLosses = subtractFromAllocation( - zcfSeat.getCurrentAllocation(), - losses, - ); - - // verifies offer safety - assert( - zcfSeat.isOfferSafe(allocationMinusLosses), - `The allocation after burning losses ${allocationMinusLosses}for the zcfSeat was not offer safe`, - ); - - // Decrement the stagedAllocation if it exists so that the - // stagedAllocation is kept up to the currentAllocation - if (zcfSeat.hasStagedAllocation()) { - zcfSeat.decrementBy(losses); - } - - // No effects above, apart from decrementBy. Note COMMIT POINT within - // reallocateForZCFMint. The following two steps *should* be - // committed atomically, but it is not a disaster if they are - // not. If we only commit the allocationMinusLosses no one would - // ever get the unburned assets. - reallocateForZCFMint(zcfSeat, allocationMinusLosses); - E(zoeMintP).withdrawAndBurn(totalToBurn); - }, - }); - return zcfMint; - }; + const makeZcfMint = makeZCFMintFactory( + zcfBaggage, + recordIssuer, + getAssetKindByBrand, + makeEmptySeatKit, + reallocateForZCFMint, + ); /** * @template {AssetKind} [K='nat'] @@ -238,8 +150,7 @@ export const makeZCFZygote = ( assetKind, displayInfo, ); - - return doMakeZCFMint(keyword, zoeMintP); + return makeZcfMint(keyword, zoeMintP); }; /** @type {ZCFRegisterFeeMint} */ @@ -250,7 +161,7 @@ export const makeZCFZygote = ( keyword, feeMintAccess, ); - return doMakeZCFMint(keyword, zoeMintP); + return makeZcfMint(keyword, zoeMintP); }; /** @type {ZCF} */ @@ -329,8 +240,8 @@ export const makeZCFZygote = ( // added in offer(). ZCF responds with the exitObj and offerResult. /** @type {HandleOfferObj} */ const handleOfferObj = Far('handleOfferObj', { - handleOffer: (invitationHandle, zoeSeatAdmin, seatData) => { - const zcfSeat = makeZCFSeat(zoeSeatAdmin, seatData); + handleOffer: (invitationHandle, seatData) => { + const zcfSeat = makeZCFSeat(seatData); const offerHandler = takeOfferHandler(invitationHandle); const offerResultP = E(offerHandler)(zcfSeat, seatData.offerArgs).catch( reason => { @@ -375,21 +286,36 @@ export const makeZCFZygote = ( contractCode = evalContractBundle(bundle); handlePWarning(contractCode); }, - startContract: ( + startContract: async ( instanceAdminFromZoe, instanceRecordFromZoe, issuerStorageFromZoe, privateArgs = undefined, + baggage, // coming in #5658 ) => { zoeInstanceAdminPromiseKit.resolve(instanceAdminFromZoe); instantiateInstanceRecordStorage(instanceRecordFromZoe); instantiateIssuerStorage(issuerStorageFromZoe); - // Next, execute the contract code, passing in zcf - /** @type {Promise} */ - const result = E(contractCode) - .start(zcf, privateArgs) - .then( + const didStart = baggage.has('didStart'); // false for v1, true for v2 + + // const zcfHandle1 = provideHandle(baggage, 'zcfHandle1', 'zcfIface1'); + // const zcfHandle2 = provideHandle(baggage, 'zcfHandle2', 'zcfIface2'); + // const makeZcfThing1 = defineDurableKind(zcfHandle1, undefined, {}); // makeZcfSeat + // const makeZcfThing2 = defineDurableKind(zcfHandle2, undefined, {}); + + const installationBaggage = provideDurableMapStore( + baggage, + 'installation', + ); + // evaluate the contract (either the first version, or an upgrade) + const contractNS = await contractCode; + const { setupInstallation, start } = contractNS; + if (!setupInstallation) { + // fall back to old non-upgradable scheme + // Next, execute the contract code, passing in zcf + /** @type {Promise} */ + const result = start(zcf, privateArgs).then( ({ creatorFacet = Far('emptyCreatorFacet', {}), publicFacet = Far('emptyPublicFacet', {}), @@ -403,8 +329,54 @@ export const makeZCFZygote = ( }); }, ); - handlePWarning(result); - return result; + handlePWarning(result); + return result; + } + + // allow the contract (any version) to set up its installation Kinds + const setupInstance = await setupInstallation(installationBaggage); + + // For the version-1 installation, we stop doing work here: we + // define more function and objects, but none will be invoked in + // this heap. Instead, they will be invoked in a zygote/clone of + // this heap. + + let makeInstanceKit; + + const doSetupInstance = async _perInstanceZoeStuff => { + // maybe do some defineDurableKinds here + + // const { zcfThing1, zcfThing2 } = provideZcfThings(baggage, perInstanceZoeStuff); + + // const zcfThing1 = makeZcfThing1(perInstanceZoeStuff); + // const zcfThing2 = makeZcfThing2(perInstanceZoeStuff); + makeInstanceKit = await setupInstance(/* zcfThing1, zcfThing2 */); + }; + + const start2 = async perInstanceZoeStuff => { + // the version-1 *instance* will see this zcRoot.start() get + // invoked (in a clone of the original vat) exactly once per + // instance + baggage.init('didStart', true); + baggage.init('perInstanceZoeStuff', perInstanceZoeStuff); + // now that our clone is differentiated, we can do + // instance-specific setup + await doSetupInstance(perInstanceZoeStuff); + // and we can invoke makeInstanceKit() for the first and only time + const { publicFacet, privateFacet } = makeInstanceKit(); + return harden({ publicFacet, privateFacet }); + }; + + // For version-2 or later, we know we've already been started, so + // allow the contract to set up its instance Kinds + if (didStart) { + const perInstanceZoeStuff = baggage.get('perInstanceZoeStuff'); + doSetupInstance(perInstanceZoeStuff); + // however we do not call makeInstanceKit() again + } + + const zcfRoot = Far('zcfRoot', { start2 }); + return zcfRoot; }, }; return harden(zcfZygote); diff --git a/packages/zoe/src/internal-types.js b/packages/zoe/src/internal-types.js index 98a43fed92c8..11c87ae6156d 100644 --- a/packages/zoe/src/internal-types.js +++ b/packages/zoe/src/internal-types.js @@ -45,6 +45,7 @@ * @property {(allocation: Allocation) => void} replaceAllocation * @property {ZoeSeatAdminExit} exit * @property {ShutdownWithFailure} fail called with the reason + * @property {() => Promise> } getNotifier * for calling fail on this seat, where reason is normally an instanceof Error. */ @@ -89,7 +90,6 @@ * * @typedef {object} HandleOfferObj * @property {(invitationHandle: InvitationHandle, - * zoeSeatAdmin: ZoeSeatAdmin, * seatData: SeatData, * ) => HandleOfferResult} handleOffer */ @@ -115,6 +115,9 @@ * @property {ReplaceAllocations} replaceAllocations * @property {(completion: Completion) => void} exitAllSeats * @property {ShutdownWithFailure} failAllSeats + * @property {(seatHandle: SeatHandle, completion: Completion) => void} exitSeat + * @property {(seatHandle: SeatHandle, reason: Error) => void} failSeat + * @property {(seatHandle: SeatHandle) => Promise>} getSeatNotifier * @property {() => void} stopAcceptingOffers */ @@ -148,7 +151,7 @@ * @param {ProposalRecord} proposal * @param {ExitObj} exitObj * @param {SeatHandle} seatHandle - * @returns {ZoeSeatAdminKit} + * @returns {{userSeat: UserSeat, notifier: Notifier}} */ /** @@ -209,7 +212,7 @@ */ /** - * @typedef {Handle<'SeatHandle'>} SeatHandle + * @typedef {Handle<'Seat'>} SeatHandle */ /** @@ -248,7 +251,6 @@ /** * @callback MakeZCFSeat - * @param {ERef} zoeSeatAdmin * @param {SeatData} seatData * @returns {ZCFSeat} */ diff --git a/packages/zoe/src/issuerStorage.js b/packages/zoe/src/issuerStorage.js index cb52eabe8d3c..7223b7a7affe 100644 --- a/packages/zoe/src/issuerStorage.js +++ b/packages/zoe/src/issuerStorage.js @@ -1,6 +1,9 @@ // @ts-check -import { makeWeakStore } from '@agoric/store'; +import { + makeScalarBigMapStore, + provideDurableWeakMapStore, +} from '@agoric/vat-data'; import { assert, details as X } from '@agoric/assert'; import { E } from '@endo/eventual-send'; @@ -9,13 +12,23 @@ import { cleanKeywords } from './cleanProposal.js'; import { makeIssuerRecord } from './issuerRecord.js'; /** - * Make the Issuer Storage. + * Make the Issuer Storage. + * + * @param {MapStore} zcfBaggage */ -export const makeIssuerStorage = () => { +export const makeIssuerStorage = ( + zcfBaggage = makeScalarBigMapStore('zcfBaggage', { durable: true }), +) => { /** @type {WeakStore} */ - const brandToIssuerRecord = makeWeakStore('brand'); + const brandToIssuerRecord = provideDurableWeakMapStore( + zcfBaggage, + 'brandToIssuerRecord', + ); /** @type {WeakStore} */ - const issuerToIssuerRecord = makeWeakStore('issuer'); + const issuerToIssuerRecord = provideDurableWeakMapStore( + zcfBaggage, + 'issuerToIssuerRecord', + ); let instantiated = false; const assertInstantiated = () => diff --git a/packages/zoe/src/zoeService/createZCFVat.js b/packages/zoe/src/zoeService/createZCFVat.js index 8d9bf9dab5e1..55f3a938ba36 100644 --- a/packages/zoe/src/zoeService/createZCFVat.js +++ b/packages/zoe/src/zoeService/createZCFVat.js @@ -10,24 +10,25 @@ import { E } from '@endo/eventual-send'; */ export const setupCreateZCFVat = (vatAdminSvc, zcfSpec) => { /** @type {CreateZCFVat} */ - const createZCFVat = async () => { + const createZCFVat = async contractBundleCap => { /** @type {ERef} */ - let bundleCapP; + let zcfBundleCapP; if (zcfSpec.bundleCap) { - bundleCapP = zcfSpec.bundleCap; + zcfBundleCapP = zcfSpec.bundleCap; } else if (zcfSpec.name) { - bundleCapP = E(vatAdminSvc).getNamedBundleCap(zcfSpec.name); + zcfBundleCapP = E(vatAdminSvc).getNamedBundleCap(zcfSpec.name); } else if (zcfSpec.id) { - bundleCapP = E(vatAdminSvc).getBundleCap(zcfSpec.id); + zcfBundleCapP = E(vatAdminSvc).getBundleCap(zcfSpec.id); } else { const keys = Object.keys(zcfSpec).join(','); assert.fail(`setupCreateZCFVat: bad zcfSpec, has keys '${keys}'`); } /** @type {BundleCap} */ - const bundleCap = await bundleCapP; - assert(bundleCap, `setupCreateZCFVat did not get bundleCap`); - const rootAndAdminNodeP = E(vatAdminSvc).createVat(bundleCap, { + const zcfBundleCap = await zcfBundleCapP; + assert(zcfBundleCap, `setupCreateZCFVat did not get bundleCap`); + const rootAndAdminNodeP = E(vatAdminSvc).createVat(zcfBundleCap, { name: 'zcf', + vatParameters: { contractBundleCap }, }); const rootAndAdminNode = await rootAndAdminNodeP; return rootAndAdminNode; diff --git a/packages/zoe/src/zoeService/startInstance.js b/packages/zoe/src/zoeService/startInstance.js index 1aec7bc04e95..b9ea567eb2f6 100644 --- a/packages/zoe/src/zoeService/startInstance.js +++ b/packages/zoe/src/zoeService/startInstance.js @@ -26,7 +26,7 @@ export const makeStartInstance = ( ) => { const makeInstanceHandle = defineDurableHandle(zoeBaggage, 'Instance'); // TODO(MSM): Should be 'Seat' rather than 'SeatHandle' - const makeSeatHandle = defineDurableHandle(zoeBaggage, 'SeatHandle'); + const makeSeatHandle = defineDurableHandle(zoeBaggage, 'Seat'); const startInstance = async ( installationP, @@ -142,7 +142,7 @@ export const makeStartInstance = ( zoeSeatAdmins.add(zoeSeatAdmin); E(handleOfferObjPromiseKit.promise) - .handleOffer(invitationHandle, zoeSeatAdmin, seatData) + .handleOffer(invitationHandle, seatData) .then(({ offerResultP, exitObj }) => { offerResultPromiseKit.resolve(offerResultP); exitObjPromiseKit.resolve(exitObj); @@ -167,7 +167,7 @@ export const makeStartInstance = ( ); zoeSeatAdmins.add(zoeSeatAdmin); seatHandleToZoeSeatAdmin.init(seatHandle, zoeSeatAdmin); - return { userSeat, notifier, zoeSeatAdmin }; + return { userSeat, notifier }; }, }); return instanceAdmin; @@ -192,6 +192,14 @@ export const makeStartInstance = ( makeNoEscrowSeat: instanceAdmin.makeNoEscrowSeat, exitAllSeats: completion => instanceAdmin.exitAllSeats(completion), failAllSeats: reason => instanceAdmin.failAllSeats(reason), + exitSeat: (seatHandle, completion) => { + seatHandleToZoeSeatAdmin.get(seatHandle).exit(completion); + }, + failSeat: (seatHandle, reason) => { + seatHandleToZoeSeatAdmin.get(seatHandle).fail(reason); + }, + getSeatNotifier: seatHandle => + seatHandleToZoeSeatAdmin.get(seatHandle).getNotifier(), makeZoeMint: zoeInstanceStorageManager.makeZoeMint, registerFeeMint: zoeInstanceStorageManager.registerFeeMint, replaceAllocations: seatHandleAllocations => { diff --git a/packages/zoe/src/zoeService/zoeSeat.js b/packages/zoe/src/zoeService/zoeSeat.js index d8103009f381..cc9feb069841 100644 --- a/packages/zoe/src/zoeService/zoeSeat.js +++ b/packages/zoe/src/zoeService/zoeSeat.js @@ -79,6 +79,7 @@ export const makeZoeSeatAdminKit = ( updater.fail(reason); doExit(zoeSeatAdmin); }, + getNotifier: async () => notifier, }); /** @type {UserSeat} */ diff --git a/packages/zoe/test/unitTests/contractSupport/test-offerTo.js b/packages/zoe/test/unitTests/contractSupport/test-offerTo.js index f3614e5d110c..8a7a51b209e2 100644 --- a/packages/zoe/test/unitTests/contractSupport/test-offerTo.js +++ b/packages/zoe/test/unitTests/contractSupport/test-offerTo.js @@ -222,7 +222,7 @@ test(`offerTo - violates offer safety of fromSeat`, async t => { ), { message: - 'Offer safety was violated by the proposed allocation: {"TokenK":{"brand":"[Alleged: bucks brand]","value":"[0n]"},"TokenJ":{"brand":"[Alleged: moola brand]","value":"[0n]"}}. Proposal was {"want":{"TokenJ":{"brand":"[Alleged: moola brand]","value":"[3n]"}},"give":{"TokenK":{"brand":"[Alleged: bucks brand]","value":"[5n]"}},"exit":{"onDemand":null}}', + /Offer safety was violated by the proposed allocation: {"Token[JK]":{"brand":"\[Alleged: .* brand]","value":"\[0n]"},"Token[KJ]":{"brand":"\[Alleged: .* brand]","value":"\[0n]"}}. Proposal was/, }, ); diff --git a/packages/zoe/test/unitTests/contracts/test-sellTickets.js b/packages/zoe/test/unitTests/contracts/test-sellTickets.js index 2f9ec333307b..e5c245c9acfd 100644 --- a/packages/zoe/test/unitTests/contracts/test-sellTickets.js +++ b/packages/zoe/test/unitTests/contracts/test-sellTickets.js @@ -589,7 +589,7 @@ test('Testing publicFacet.getAvailableItemsNotifier()', async t => { E(birdSalesPublicFacet).getAvailableItemsNotifier(); const birdsForSalePresolved = await E(birdsForSaleNotifier).getUpdateSince(); - t.is(birdsForSale, birdsForSalePresolved.value); + t.deepEqual(birdsForSale, birdsForSalePresolved.value); t.is(birdsForSale.brand, birdBrand); t.is(birdsForSalePresolved.value.brand, birdBrand); }); diff --git a/packages/zoe/test/unitTests/contracts/test-throwInOfferHandler.js b/packages/zoe/test/unitTests/contracts/test-throwInOfferHandler.js index 744a6b04acfc..beeae9c1ee74 100644 --- a/packages/zoe/test/unitTests/contracts/test-throwInOfferHandler.js +++ b/packages/zoe/test/unitTests/contracts/test-throwInOfferHandler.js @@ -85,6 +85,6 @@ test('throw in offerHandler', async t => { // https://github.com/endojs/endo/pull/640 // // /"brand" not found: .*/, - /.* not found: .*/, + /no ordinal for .*/, }); }); diff --git a/packages/zoe/test/unitTests/test-issuerStorage.js b/packages/zoe/test/unitTests/test-issuerStorage.js index c76be651855e..1ad4aebb3367 100644 --- a/packages/zoe/test/unitTests/test-issuerStorage.js +++ b/packages/zoe/test/unitTests/test-issuerStorage.js @@ -79,7 +79,7 @@ test(`getAssetKindByBrand - brand isn't stored`, t => { instantiate(); const { currencyKit } = setupIssuersForTest(); t.throws(() => getAssetKindByBrand(currencyKit.brand), { - message: '"brand" not found: "[Alleged: currency brand]"', + message: 'no ordinal for "[Alleged: currency brand]"', }); }); diff --git a/packages/zoe/test/unitTests/zcf/test-zcf.js b/packages/zoe/test/unitTests/zcf/test-zcf.js index d1549a4b56cf..ab1960f1ef04 100644 --- a/packages/zoe/test/unitTests/zcf/test-zcf.js +++ b/packages/zoe/test/unitTests/zcf/test-zcf.js @@ -790,17 +790,17 @@ const allocateEasy = async ( test(`zcfSeat.getNotifier`, async t => { const { zcf } = await setupZCFTest(); - const { zcfSeat, userSeat } = zcf.makeEmptySeatKit(); + const { zcfSeat } = zcf.makeEmptySeatKit(); const notifier = zcfSeat.getNotifier(); - // These are different notifiers - t.not(notifier, await E(userSeat).getNotifier()); // Mint some gains to change the allocation. const { brand: brand1 } = await allocateEasy(zcf, 'Stuff', zcfSeat, 'A', 3n); let notifierResult; for (let remainingTries = 2; remainingTries >= 0; remainingTries -= 1) { // eslint-disable-next-line no-await-in-loop - notifierResult = await notifier.getUpdateSince(notifierResult?.updateCount); + notifierResult = await E(notifier).getUpdateSince( + notifierResult?.updateCount, + ); if (notifierResult.value?.A) { break; } @@ -813,7 +813,7 @@ test(`zcfSeat.getNotifier`, async t => { }); const { brand: brand2 } = await allocateEasy(zcf, 'Stuff2', zcfSeat, 'B', 6n); - notifierResult = await notifier.getUpdateSince(notifierResult.updateCount); + notifierResult = await E(notifier).getUpdateSince(notifierResult.updateCount); t.deepEqual(notifierResult.value, { A: { brand: brand1, @@ -827,7 +827,7 @@ test(`zcfSeat.getNotifier`, async t => { zcfSeat.exit(); - notifierResult = await notifier.getUpdateSince(notifierResult.updateCount); + notifierResult = await E(notifier).getUpdateSince(notifierResult.updateCount); t.deepEqual(notifierResult, { updateCount: undefined, value: undefined, diff --git a/packages/zoe/test/unitTests/zcf/test-zcfSeat.js b/packages/zoe/test/unitTests/zcf/test-zcfSeat.js index ebceda7b2c89..3da9cc0fdd03 100644 --- a/packages/zoe/test/unitTests/zcf/test-zcfSeat.js +++ b/packages/zoe/test/unitTests/zcf/test-zcfSeat.js @@ -69,9 +69,12 @@ test(`zoe - zcfSeat.fail() doesn't throw`, async t => { const userSeat1 = await E(zoe).offer(invitation1); const userSeat2 = await E(zoe).offer(invitation2); - t.is(await E(userSeat1).getOfferResult(), 'ok', `userSeat1 offer result`); + const result = await E(userSeat1).getOfferResult(); + t.is(result, 'ok', `userSeat1 offer result`); - t.deepEqual(await E(userSeat2).getPayouts(), {}); + const po = await userSeat2.getPayouts(); + const payouts = await E(userSeat2).getPayouts(); + t.deepEqual(payouts, {}); await t.throwsAsync(E(userSeat2).getOfferResult(), { message: 'second seat failed', diff --git a/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js b/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js index e2f48640bba8..b271776c1c5c 100644 --- a/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js +++ b/packages/zoe/test/unitTests/zcf/test-zoeHelpersWZcf.js @@ -151,7 +151,8 @@ test(`zcf assertNatAssetKind - brand not registered`, async t => { // https://github.com/endojs/endo/pull/640 // // /"brand" not found: .*/, - /.* not found: .*/, + // / not found: /, + /no ordinal /, }); }); @@ -217,8 +218,9 @@ test(`zcf saveAllIssuers - duplicate keyword`, async t => { // disclosure bug is fixed. See // https://github.com/endojs/endo/pull/640 // - // /"issuer" not found: .*/, - /.* not found: .*/, + // /"issuer" not found: /, + // /not found: /, + /no ordinal for /, }, 'issuer should not be found', ); @@ -230,8 +232,9 @@ test(`zcf saveAllIssuers - duplicate keyword`, async t => { // disclosure bug is fixed. See // https://github.com/endojs/endo/pull/640 // - // /"brand" not found: .*/, - /.* not found: .*/, + // /"brand" not found: /, + // /not found: /, + /no ordinal for /, }); }); @@ -368,6 +371,12 @@ test(`zoeHelper with zcf - assertProposalShape`, async t => { ); }); +const containsAll = (arr1, arr2) => + arr2.every(arr2Item => arr1.includes(arr2Item)); + +const sameMembers = (arr1, arr2) => + containsAll(arr1, arr2) && containsAll(arr2, arr1); + test(`zoeHelper w/zcf - swapExact`, async t => { const { moolaIssuer, @@ -409,10 +418,12 @@ test(`zoeHelper w/zcf - swapExact`, async t => { await userSeatA.getPayout('B'), simoleans(0n), ); - t.deepEqual(Object.getOwnPropertyNames(await userSeatA.getPayouts()), [ - 'B', - 'A', - ]); + t.truthy( + sameMembers(Object.getOwnPropertyNames(await userSeatA.getPayouts()), [ + 'B', + 'A', + ]), + ); t.truthy(zcfSeatB.hasExited(), 'exit right'); await assertPayoutAmount( t, @@ -426,10 +437,12 @@ test(`zoeHelper w/zcf - swapExact`, async t => { await userSeatB.getPayout('D'), moola(0n), ); - t.deepEqual(Object.getOwnPropertyNames(await userSeatB.getPayouts()), [ - 'D', - 'C', - ]); + t.truthy( + sameMembers(Object.getOwnPropertyNames(await userSeatB.getPayouts()), [ + 'D', + 'C', + ]), + ); }); test(`zoeHelper w/zcf - swapExact w/shortage`, async t => {