UNPKG

@agoric/zoe

Version:

Zoe: the Smart Contract Framework for Offer Enforcement

308 lines (289 loc) • 10.1 kB
import { canBeDurable, makeScalarBigSetStore, provideDurableWeakMapStore, prepareKindMulti, prepareExoClassKit, M, provide, } from '@agoric/vat-data'; import { E } from '@endo/eventual-send'; import { q, Fail } from '@endo/errors'; import { defineDurableHandle } from '../makeHandle.js'; import { BrandKeywordRecordShape, InstanceAdminShape, InstanceHandleShape, IssuerKeywordRecordShape, } from '../typeGuards.js'; import { makeZoeSeatAdminFactory } from './zoeSeat.js'; /** * @import {WeakMapStore} from '@agoric/store'; */ /** * @file Two objects are defined here, both called InstanceAdminSomething. * InstanceAdminStorage is a container for individual InstanceAdmins. Each * InstanceAdmin is associated with a particular contract instance, and is used * by both Zoe and ZCF to record and access the instance state. startInstance * also defines zoeInstanceAdmin, which is a facet that is passed to startZcf() * that wraps access to a zoeInstanceAdmin and other stores. */ const InstanceAdminStorageIKit = harden({ accessor: M.interface('InstanceAdminStorage', { getPublicFacet: M.callWhen(M.await(InstanceHandleShape)).returns( M.remotable(), ), getBrands: M.call(InstanceHandleShape).returns(BrandKeywordRecordShape), getIssuers: M.call(InstanceHandleShape).returns(IssuerKeywordRecordShape), getTerms: M.call(InstanceHandleShape).returns(M.record()), getOfferFilter: M.call(InstanceHandleShape).returns(M.arrayOf(M.string())), getInstallation: M.call(InstanceHandleShape).returns(M.remotable()), getInstanceAdmin: M.call(InstanceHandleShape).returns(InstanceAdminShape), }), updater: M.interface('InstanceAdmin updater', { initInstanceAdmin: M.call(InstanceHandleShape, InstanceAdminShape).returns( M.promise(), ), deleteInstanceAdmin: M.call(InstanceHandleShape).returns(), }), }); /** @param {import('@agoric/vat-data').Baggage} baggage */ export const makeInstanceAdminStorage = baggage => { const makeIAS = prepareExoClassKit( baggage, 'InstanceAdmin', InstanceAdminStorageIKit, () => ({ instanceToInstanceAdmin: provideDurableWeakMapStore( baggage, 'instanceToInstanceAdmin', ), }), { // ZoeStorageManager uses the accessor facet to get info about instances accessor: { getPublicFacet(instance) { const { state } = this; const ia = state.instanceToInstanceAdmin.get(instance); return ia.getPublicFacet(); }, getBrands(instance) { const { state } = this; return state.instanceToInstanceAdmin.get(instance).getBrands(); }, getIssuers(instance) { const { state } = this; return state.instanceToInstanceAdmin.get(instance).getIssuers(); }, getTerms(instance) { const { state } = this; return state.instanceToInstanceAdmin.get(instance).getTerms(); }, getOfferFilter(instance) { const { state } = this; return state.instanceToInstanceAdmin.get(instance).getOfferFilter(); }, getInstallation(instance) { const { state } = this; return state.instanceToInstanceAdmin.get(instance).getInstallation(); }, getInstanceAdmin(instance) { const { state } = this; return state.instanceToInstanceAdmin.get(instance); }, }, // The updater facet is only used inside the instanceStorageManager updater: { async initInstanceAdmin(instance, instanceAdmin) { const { state } = this; return state.instanceToInstanceAdmin.init(instance, instanceAdmin); }, deleteInstanceAdmin(instance) { const { state } = this; return state.instanceToInstanceAdmin.delete(instance); }, }, }, ); return provide(baggage, 'theInstanceAdminStorage', () => makeIAS()); }; harden(makeInstanceAdminStorage); /** * @param {import('@agoric/vat-data').Baggage} zoeBaggage * @param {ReturnType<makeZoeSeatAdminFactory>} makeZoeSeatAdminKit */ const makeInstanceAdminBehavior = (zoeBaggage, makeZoeSeatAdminKit) => { const makeSeatHandle = defineDurableHandle(zoeBaggage, 'Seat'); return harden({ getPublicFacet: ({ state }) => state.publicFacet, getTerms: ({ state }) => state.zoeInstanceStorageManager.getTerms(), getIssuers: ({ state }) => state.zoeInstanceStorageManager.getIssuers(), getBrands: ({ state }) => state.zoeInstanceStorageManager.getBrands(), // instanceAdmin is created early in startInstance. initDelayedState is // called after startZcf, but before the contract facets are made available. initDelayedState: ({ state }, handleOfferObj, publicFacet) => { state.handleOfferObj = handleOfferObj; canBeDurable(publicFacet) || Fail`publicFacet must be durable`; state.publicFacet = publicFacet; }, getInstallation: ({ state }) => state.zoeInstanceStorageManager.getInstallation(), getInstance: ({ state }) => state.instanceHandle, assertAcceptingOffers: ({ state }) => { state.acceptingOffers || Fail`No further offers are accepted`; }, exitAllSeats: ({ state }, completion) => { state.acceptingOffers = false; for (const zoeSeatAdmin of state.zoeSeatAdmins.keys()) { zoeSeatAdmin.exit(completion); } }, failAllSeats: ({ state }, reason) => { state.acceptingOffers = false; for (const zoeSeatAdmin of state.zoeSeatAdmins.keys()) { zoeSeatAdmin.fail(reason); } }, stopAcceptingOffers: ({ state }) => { state.acceptingOffers = false; }, makeUserSeat: ( { state, facets: { helper } }, invitationHandle, initialAllocation, proposal, offerArgs = undefined, ) => { const { userSeat, zoeSeatAdmin } = makeZoeSeatAdminKit( initialAllocation, proposal, helper, state.zoeInstanceStorageManager.getWithdrawFacet(), ); const seatHandle = makeSeatHandle(); state.seatHandleToZoeSeatAdmin.init(seatHandle, zoeSeatAdmin); const seatData = harden({ proposal, initialAllocation, seatHandle, offerArgs, }); state.zoeSeatAdmins.add(zoeSeatAdmin); state.handleOfferObj || Fail`incomplete setup of zoe seat`; void E.when( E(state.handleOfferObj).handleOffer(invitationHandle, seatData), /** @param {HandleOfferResult} result */ result => zoeSeatAdmin.resolveExitAndResult(result), err => { // nothing for Zoe to do if the termination fails void E(state.adminNode).terminateWithFailure(err); throw err; }, ); // return the userSeat before the offerHandler is called return userSeat; }, makeNoEscrowSeat: ( { state, facets: { helper } }, initialAllocation, proposal, exitObj, seatHandle, ) => { const { userSeat, zoeSeatAdmin } = makeZoeSeatAdminKit( initialAllocation, proposal, helper, state.zoeInstanceStorageManager.getWithdrawFacet(), exitObj, true, ); state.zoeSeatAdmins.add(zoeSeatAdmin); state.seatHandleToZoeSeatAdmin.init(seatHandle, zoeSeatAdmin); return userSeat; }, getOfferFilter: ({ state }) => state.offerFilterStrings, setOfferFilter: ({ state }, strings) => { Array.isArray(strings) || Fail`${q(strings)} must be an Array`; const proposedStrings = harden([...strings]); proposedStrings.every(s => typeof s === 'string') || Fail`Blocked strings (${q( proposedStrings, )}) must be an Array of strings.`; state.offerFilterStrings = proposedStrings; }, // If any offer filter string matches the input string, don't process the // invitation. Offer filter strings that end in ':' match if they are a // prefix of the input string; others must match it exactly. isBlocked: ({ state }, string) => { return state.offerFilterStrings.some(filterString => { return ( filterString === string || (filterString.endsWith(':') && string.startsWith(filterString)) ); }); }, }); }; const helperBehavior = { exitZoeSeatAdmin: ({ state }, zoeSeatAdmin) => state.zoeSeatAdmins.delete(zoeSeatAdmin), hasExited: ({ state }, zoeSeatAdmin) => !state.zoeSeatAdmins.has(zoeSeatAdmin), }; /** * @typedef {Readonly<{ * publicFacet: unknown, * handlerOfferObj: unknown, * }>} ImmutableState * @typedef {{ * offerFilterStrings: string[], * }} MutableState * @typedef {MutableState & ImmutableState} State * @typedef {{ * state: State, * }} MethodContext */ /** * @param {import('@agoric/vat-data').Baggage} zoeBaggage * @param {WeakMapStore<SeatHandle, ZoeSeatAdmin>} seatHandleToZoeSeatAdmin */ export const makeInstanceAdminMaker = ( zoeBaggage, seatHandleToZoeSeatAdmin, ) => { const makeZoeSeatAdminKit = makeZoeSeatAdminFactory(zoeBaggage); const makeInstanceAdminMulti = prepareKindMulti( zoeBaggage, 'instanceAdmin', (instanceHandle, zoeInstanceStorageManager, adminNode) => { const retVal = { offerFilterStrings: harden([]), publicFacet: undefined, handleOfferObj: undefined, zoeInstanceStorageManager, seatHandleToZoeSeatAdmin, instanceHandle, acceptingOffers: true, zoeSeatAdmins: makeScalarBigSetStore('zoeSeatAdmins', { durable: true, }), adminNode, }; return retVal; }, { instanceAdmin: makeInstanceAdminBehavior(zoeBaggage, makeZoeSeatAdminKit), helper: helperBehavior, }, ); return (instanceHandle, zoeInstanceStorageManager, adminNode) => { const instanceAdmin = makeInstanceAdminMulti( instanceHandle, zoeInstanceStorageManager, adminNode, ).instanceAdmin; return instanceAdmin; }; }; harden(makeInstanceAdminMaker);