diff --git a/packages/zoe/src/contractFacet/internal-types.js b/packages/zoe/src/contractFacet/internal-types.js index 7991eb864300..a4973a904961 100644 --- a/packages/zoe/src/contractFacet/internal-types.js +++ b/packages/zoe/src/contractFacet/internal-types.js @@ -16,7 +16,7 @@ /** * @typedef ZCFZygote - * @property {(bundle: SourceBundle) => void} evaluateContract + * @property {(bundleOrBundleCap: SourceBundle | BundleCap) => void} evaluateContract * @property {(instanceAdminFromZoe: ERef, * instanceRecordFromZoe: InstanceRecord, * issuerStorageFromZoe: IssuerRecords, diff --git a/packages/zoe/src/contractFacet/vatRoot.js b/packages/zoe/src/contractFacet/vatRoot.js index e4b3a8b336cd..3331e4d7029f 100644 --- a/packages/zoe/src/contractFacet/vatRoot.js +++ b/packages/zoe/src/contractFacet/vatRoot.js @@ -29,7 +29,7 @@ export function buildRootObject(powers, _params, testJigSetter = undefined) { /** @type {ExecuteContract} */ const executeContract = ( - bundle, + bundleOrBundleCap, zoeService, invitationIssuer, zoeInstanceAdmin, @@ -44,7 +44,7 @@ export function buildRootObject(powers, _params, testJigSetter = undefined) { invitationIssuer, testJigSetter, ); - zcfZygote.evaluateContract(bundle); + zcfZygote.evaluateContract(bundleOrBundleCap); return zcfZygote.startContract( zoeInstanceAdmin, instanceRecordFromZoe, diff --git a/packages/zoe/src/contractFacet/zcfZygote.js b/packages/zoe/src/contractFacet/zcfZygote.js index c7f657facf72..32540606142c 100644 --- a/packages/zoe/src/contractFacet/zcfZygote.js +++ b/packages/zoe/src/contractFacet/zcfZygote.js @@ -2,7 +2,7 @@ import { assert, details as X, makeAssert } from '@agoric/assert'; import { E } from '@agoric/eventual-send'; -import { Far, Remotable } from '@endo/marshal'; +import { Far, Remotable, passStyleOf } from '@endo/marshal'; import { AssetKind, AmountMath } from '@agoric/ertp'; import { makeNotifierKit, observeNotifier } from '@agoric/notifier'; import { makePromiseKit } from '@endo/promise-kit'; @@ -275,7 +275,6 @@ export const makeZCFZygote = ( shutdown: completion => { E(zoeInstanceAdmin).exitAllSeats(completion); dropAllReferences(); - // @ts-ignore powers is not typed correctly: https://github.com/Agoric/agoric-sdk/issues/3239s powers.exitVat(completion); }, shutdownWithFailure, @@ -340,7 +339,15 @@ export const makeZCFZygote = ( * @type {ZCFZygote} * */ const zcfZygote = { - evaluateContract: bundle => { + evaluateContract: bundleOrBundleCap => { + let bundle; + if (passStyleOf(bundleOrBundleCap) === 'remotable') { + const bundleCap = bundleOrBundleCap; + // @ts-expect-error vatPowers is not typed correctly: https://github.com/Agoric/agoric-sdk/issues/3239 + bundle = powers.D(bundleCap).getBundle(); + } else { + bundle = bundleOrBundleCap; + } contractCode = evalContractBundle(bundle); handlePWarning(contractCode); }, diff --git a/packages/zoe/src/zoeService/installationStorage.js b/packages/zoe/src/zoeService/installationStorage.js index 2846c5541f2f..f188ba41e590 100644 --- a/packages/zoe/src/zoeService/installationStorage.js +++ b/packages/zoe/src/zoeService/installationStorage.js @@ -3,49 +3,85 @@ import { assert, details as X } from '@agoric/assert'; import { Far } from '@endo/marshal'; import { E } from '@agoric/eventual-send'; +import { makeWeakStore } from '@agoric/store'; + +/** @typedef { import('@agoric/swingset-vat').BundleID} BundleID */ /** - * + * @param {GetBundleCapForID} getBundleCapForID */ -export const makeInstallationStorage = () => { - /** @type {WeakSet} */ - const installations = new WeakSet(); +export const makeInstallationStorage = getBundleCapForID => { + /** @type {WeakStore} */ + const installationsBundleCap = makeWeakStore('installationsBundleCap'); + /** @type {WeakStore} */ + const installationsBundle = makeWeakStore('installationsBundle'); /** - * Create an installation by permanently storing the bundle. The code is - * currently evaluated each time it is used to make a new instance of a - * contract. When SwingSet supports zygotes, the code will be evaluated once - * when creating a zcfZygote, then the start() function will be called each - * time an instance is started. + * Create an installation from a bundle ID or a full bundle. If we are + * given a bundle ID, wait for the corresponding code bundle to be received + * by the swingset kernel, then store its bundlecap. The code is currently + * evaluated each time it is used to make a new instance of a contract. + * When SwingSet supports zygotes, the code will be evaluated once when + * creating a zcfZygote, then the start() function will be called each time + * an instance is started. */ - /** @type {Install} */ - const install = async bundle => { - assert.typeof(bundle, 'object', X`a bundle must be provided`); + + /** @type {InstallBundle} */ + const installBundle = async bundle => { + assert.typeof(bundle, 'object', 'a bundle must be provided'); /** @type {Installation} */ const installation = Far('Installation', { getBundle: () => bundle, }); - installations.add(installation); + installationsBundle.init(installation, bundle); return installation; }; - const assertInstallation = installation => - assert( - installations.has(installation), - X`${installation} was not a valid installation`, - ); + /** @type {InstallBundleID} */ + const installBundleID = async bundleID => { + assert.typeof(bundleID, 'string', `a bundle ID must be provided`); + // this waits until someone tells the host application to store the + // bundle into the kernel, with controller.validateAndInstallBundle() + const bundleCap = await getBundleCapForID(bundleID); + // AWAIT + + /** @type {Installation} */ + const installation = Far('Installation', { + getBundle: () => { + throw Error('bundleID-based Installation'); + }, + }); + installationsBundleCap.init(installation, { bundleCap, bundleID }); + return installation; + }; /** @type {UnwrapInstallation} */ const unwrapInstallation = installationP => { return E.when(installationP, installation => { - assertInstallation(installation); - const bundle = installation.getBundle(); - return { bundle, installation }; + if (installationsBundleCap.has(installation)) { + const { bundleCap, bundleID } = + installationsBundleCap.get(installation); + return { bundleCap, bundleID, installation }; + } else if (installationsBundle.has(installation)) { + const bundle = installationsBundle.get(installation); + return { bundle, installation }; + } else { + assert.fail(X`${installation} was not a valid installation`); + } }); }; + const getBundleIDFromInstallation = async allegedInstallationP => { + const { bundleID } = await unwrapInstallation(allegedInstallationP); + // AWAIT + assert(bundleID, 'installation does not have a bundle ID'); + return bundleID; + }; + return harden({ - install, + installBundle, + installBundleID, unwrapInstallation, + getBundleIDFromInstallation, }); }; diff --git a/packages/zoe/src/zoeService/internal-types.js b/packages/zoe/src/zoeService/internal-types.js index 9ead46bc8c53..767ad6a6142f 100644 --- a/packages/zoe/src/zoeService/internal-types.js +++ b/packages/zoe/src/zoeService/internal-types.js @@ -56,12 +56,14 @@ /** * @callback UnwrapInstallation * - * Assert the installation is known, and return the bundle and + * Assert the installation is known, and return the bundle/bundlecap and * installation * * @param {ERef} installationP * @returns {Promise<{ - * bundle: SourceBundle, + * bundle?: SourceBundle, + * bundleCap?: BundleCap, + * bundleID?: BundleID, * installation:Installation * }>} */ @@ -105,13 +107,21 @@ * @returns {ZoeInstanceStorageManager} */ +/** + * @callback GetBundleCapForID + * @param {BundleID} id + * @returns {Promise} + */ + /** * @typedef ZoeStorageManager * @property {MakeZoeInstanceStorageManager} makeZoeInstanceStorageManager * @property {GetAssetKindByBrand} getAssetKindByBrand * @property {DepositPayments} depositPayments * @property {Issuer} invitationIssuer - * @property {Install} install + * @property {InstallBundle} installBundle + * @property {InstallBundleID} installBundleID + * @property {GetBundleIDFromInstallation} getBundleIDFromInstallation * @property {GetPublicFacet} getPublicFacet * @property {GetBrands} getBrands * @property {GetIssuers} getIssuers diff --git a/packages/zoe/src/zoeService/startInstance.js b/packages/zoe/src/zoeService/startInstance.js index a95726e569ce..6d4558f85d86 100644 --- a/packages/zoe/src/zoeService/startInstance.js +++ b/packages/zoe/src/zoeService/startInstance.js @@ -31,9 +31,14 @@ export const makeStartInstance = ( /** @type {WeakStore} */ const seatHandleToZoeSeatAdmin = makeWeakStore('seatHandle'); - const { installation, bundle } = await unwrapInstallation(installationP); + const { installation, bundle, bundleCap } = await unwrapInstallation( + installationP, + ); // AWAIT /// + const bundleOrBundleCap = bundle || bundleCap; + assert(bundleOrBundleCap); + if (privateArgs !== undefined) { const passStyle = passStyleOf(privateArgs); assert( @@ -204,7 +209,7 @@ export const makeStartInstance = ( creatorInvitation: creatorInvitationP, handleOfferObj, } = await E(zcfRoot).executeContract( - bundle, + bundleOrBundleCap, zoeServicePromise, zoeInstanceStorageManager.invitationIssuer, zoeInstanceAdminForZcf, diff --git a/packages/zoe/src/zoeService/types.js b/packages/zoe/src/zoeService/types.js index b9c7ce6051cb..ef719369dd56 100644 --- a/packages/zoe/src/zoeService/types.js +++ b/packages/zoe/src/zoeService/types.js @@ -25,6 +25,7 @@ * a smart contract in particular ways. * * @property {Install} install + * @property {InstallBundleID} installBundleID * @property {StartInstance} startInstance * @property {Offer} offer * @property {GetPublicFacet} getPublicFacet @@ -45,6 +46,7 @@ * Deprecated. Does nothing useful but provided during transition so less old * code breaks. * @property {GetConfiguration} getConfiguration + * @property {GetBundleIDFromInstallation} getBundleIDFromInstallation */ /** @@ -113,15 +115,34 @@ */ /** - * @callback Install + * @callback InstallBundle * * Create an installation by safely evaluating the code and * registering it with Zoe. Returns an installation. * - * @param {SourceBundle} bundle + * @param {Bundle} bundle * @returns {Promise} */ +/** + * @callback InstallBundleID + * + * Create an installation from a Bundle ID. Returns an installation. + * + * @param {BundleID} bundleID + * @returns {Promise} + */ + +/** + * @callback GetBundleIDFromInstallation + * + * Verify that an alleged Invitation is real, and return the Bundle ID it + * will use for contract code. + * + * @param {ERef} + * @returns {Promise} + */ + /** * @callback StartInstance * @@ -268,9 +289,9 @@ /** * @typedef {Object} VatAdminSvc - * @property {(BundleID: id) => BundleCap} getBundleCap - * @property {(name: string) => BundleCap} getNamedBundleCap - * @property {(bundleCap: BundleCap) => RootAndAdminNode} createVat + * @property {(BundleID: id) => Promise} getBundleCap + * @property {(name: string) => Promise} getNamedBundleCap + * @property {(bundleCap: BundleCap) => Promise} createVat */ /** diff --git a/packages/zoe/src/zoeService/zoe.js b/packages/zoe/src/zoeService/zoe.js index b150cbc4005a..9aed604ac265 100644 --- a/packages/zoe/src/zoeService/zoe.js +++ b/packages/zoe/src/zoeService/zoe.js @@ -16,6 +16,7 @@ import '../../exported.js'; import '../internal-types.js'; import { AssetKind } from '@agoric/ertp'; +import { E } from '@agoric/eventual-send'; import { Far } from '@endo/marshal'; import { makePromiseKit } from '@endo/promise-kit'; @@ -61,6 +62,8 @@ const makeZoeKit = ( shutdownZoeVat, ); + const getBundleCapFromID = bundleID => E(vatAdminSvc).getBundleCap(bundleID); + // This method contains the power to create a new ZCF Vat, and must // be closely held. vatAdminSvc is even more powerful - any vat can // be created. We severely restrict access to vatAdminSvc for this reason. @@ -72,8 +75,10 @@ const makeZoeKit = ( depositPayments, getAssetKindByBrand, makeZoeInstanceStorageManager, - install, + installBundle, + installBundleID, unwrapInstallation, + getBundleIDFromInstallation, getPublicFacet, getBrands, getIssuers, @@ -83,6 +88,7 @@ const makeZoeKit = ( invitationIssuer, } = makeZoeStorageManager( createZCFVat, + getBundleCapFromID, getFeeIssuerKit, shutdownZoeVat, feeIssuer, @@ -117,7 +123,8 @@ const makeZoeKit = ( /** @type {ZoeService} */ const zoeService = Far('zoeService', { - install, + install: installBundle, + installBundleID, startInstance, offer, /** @@ -145,6 +152,7 @@ const makeZoeKit = ( getInstallation, getInvitationDetails, getConfiguration, + getBundleIDFromInstallation, }); // startInstance must pass the ZoeService to the newly created ZCF diff --git a/packages/zoe/src/zoeService/zoeStorageManager.js b/packages/zoe/src/zoeService/zoeStorageManager.js index bd634eab054f..73090058bb75 100644 --- a/packages/zoe/src/zoeService/zoeStorageManager.js +++ b/packages/zoe/src/zoeService/zoeStorageManager.js @@ -26,6 +26,7 @@ import { makeInstallationStorage } from './installationStorage.js'; * * @param {CreateZCFVat} createZCFVat - the ability to create a new * ZCF Vat + * @param {GetBundleCapForID} getBundleCapForID * @param {GetFeeIssuerKit} getFeeIssuerKit * @param {ShutdownWithFailure} shutdownZoeVat * @param {Issuer} feeIssuer @@ -34,6 +35,7 @@ import { makeInstallationStorage } from './installationStorage.js'; */ export const makeZoeStorageManager = ( createZCFVat, + getBundleCapForID, getFeeIssuerKit, shutdownZoeVat, feeIssuer, @@ -82,7 +84,12 @@ export const makeZoeStorageManager = ( // Zoe stores "installations" - identifiable bundles of contract // code that can be reused again and again to create new contract // instances - const { install, unwrapInstallation } = makeInstallationStorage(); + const { + installBundle, + installBundleID, + unwrapInstallation, + getBundleIDFromInstallation, + } = makeInstallationStorage(getBundleCapForID); /** @type {MakeZoeInstanceStorageManager} */ const makeZoeInstanceStorageManager = async ( @@ -237,7 +244,9 @@ export const makeZoeStorageManager = ( getAssetKindByBrand: issuerStorage.getAssetKindByBrand, depositPayments: escrowStorage.depositPayments, invitationIssuer, - install, + installBundle, + installBundleID, + getBundleIDFromInstallation, getPublicFacet, getBrands, getIssuers, diff --git a/packages/zoe/test/unitTests/setupBasicMints.js b/packages/zoe/test/unitTests/setupBasicMints.js index f734122556f9..ad37c6f9f91e 100644 --- a/packages/zoe/test/unitTests/setupBasicMints.js +++ b/packages/zoe/test/unitTests/setupBasicMints.js @@ -3,7 +3,7 @@ import { makeIssuerKit, AmountMath } from '@agoric/ertp'; import { makeStore } from '@agoric/store'; import { makeZoeKit } from '../../src/zoeService/zoe.js'; -import fakeVatAdmin from '../../tools/fakeVatAdmin.js'; +import { makeFakeVatAdmin } from '../../tools/fakeVatAdmin.js'; const setup = () => { const moolaBundle = makeIssuerKit('moola'); @@ -21,6 +21,7 @@ const setup = () => { brands.init(k, allBundles[k].brand); } + const { admin: fakeVatAdmin, vatAdminState } = makeFakeVatAdmin(); const { zoeService: zoe } = makeZoeKit(fakeVatAdmin); /** @type {(brand: Brand) => (value: AmountValue) => Amount} */ @@ -45,6 +46,7 @@ const setup = () => { * @property {(value: AmountValue) => Amount} simoleans * @property {(value: AmountValue) => Amount} bucks * @property {ERef} zoe + * @property {*} vatAdminState */ /** @type {BasicMints} */ @@ -66,6 +68,7 @@ const setup = () => { simoleans: makeSimpleMake(simoleanBundle.brand), bucks: makeSimpleMake(bucksBundle.brand), zoe, + vatAdminState, }; harden(result); return result; diff --git a/packages/zoe/test/unitTests/test-zoe.js b/packages/zoe/test/unitTests/test-zoe.js index 8f2bc9cbf97d..5706b5fc7c74 100644 --- a/packages/zoe/test/unitTests/test-zoe.js +++ b/packages/zoe/test/unitTests/test-zoe.js @@ -41,16 +41,34 @@ test(`E(zoe).install bad bundle`, async t => { }); }); -test(`E(zoe).install`, async t => { +test(`E(zoe).install(bundle)`, async t => { const { zoe } = setup(); const contractPath = `${dirname}/../../src/contracts/atomicSwap`; const bundle = await bundleSource(contractPath); const installation = await E(zoe).install(bundle); + t.is(passStyleOf(installation), 'remotable'); +}); + +test(`E(zoe).installBundleID bad id`, async t => { + const { zoe } = setup(); + // @ts-ignore deliberate invalid arguments for testing + await t.throwsAsync(() => E(zoe).installBundleID(), { + message: 'a bundle ID must be provided', + }); +}); + +test(`E(zoe).installBundleID(bundleID)`, async t => { + const { zoe, vatAdminState } = setup(); + const contractPath = `${dirname}/../../src/contracts/atomicSwap`; + const bundle = await bundleSource(contractPath); + vatAdminState.installBundle('b1-atomic', bundle); + const installation = await E(zoe).installBundleID('b1-atomic'); // TODO Check the integrity of the installation by its hash. // https://github.com/Agoric/agoric-sdk/issues/3859 // const hash = await E(installation).getHash(); // assert.is(hash, 'XXX'); - t.is(await E(installation).getBundle(), bundle); + // NOTE: the bundle ID is now the hash + t.is(await E(zoe).getBundleIDFromInstallation(installation), 'b1-atomic'); }); test(`E(zoe).startInstance bad installation`, async t => { diff --git a/packages/zoe/test/unitTests/zoe/test-installationStorage.js b/packages/zoe/test/unitTests/zoe/test-installationStorage.js index e59f00c243cd..3c5abe334abe 100644 --- a/packages/zoe/test/unitTests/zoe/test-installationStorage.js +++ b/packages/zoe/test/unitTests/zoe/test-installationStorage.js @@ -3,49 +3,67 @@ // eslint-disable-next-line import/no-extraneous-dependencies import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js'; +import { makeHandle } from '../../../src/makeHandle.js'; import { makeInstallationStorage } from '../../../src/zoeService/installationStorage.js'; test('install, unwrap installations', async t => { - const { install, unwrapInstallation } = makeInstallationStorage(); + const { installBundle, unwrapInstallation } = makeInstallationStorage(); const fakeBundle = {}; - const installation = await install(fakeBundle); + const installation = await installBundle(fakeBundle); const unwrapped = await unwrapInstallation(installation); t.is(unwrapped.installation, installation); t.is(unwrapped.bundle, fakeBundle); }); +test('install, unwrap installation of bundlecap', async t => { + const bundleCaps = { id: makeHandle('BundleCap') }; + const getBundleCapFromID = id => bundleCaps[id]; + + const { installBundleID, unwrapInstallation } = + makeInstallationStorage(getBundleCapFromID); + + const installation = await installBundleID('id'); + const unwrapped = await unwrapInstallation(installation); + t.is(unwrapped.installation, installation); + t.is(unwrapped.bundleID, 'id'); + t.is(unwrapped.bundleCap, bundleCaps.id); +}); + test('unwrap promise for installation', async t => { - const { install, unwrapInstallation } = makeInstallationStorage(); + const { installBundle, unwrapInstallation } = makeInstallationStorage(); const fakeBundle = {}; - const installation = await install(fakeBundle); + const installation = await installBundle(fakeBundle); const unwrapped = await unwrapInstallation(Promise.resolve(installation)); t.is(unwrapped.installation, installation); t.is(unwrapped.bundle, fakeBundle); }); test('install several', async t => { - const { install, unwrapInstallation } = makeInstallationStorage(); + const { installBundle, unwrapInstallation } = makeInstallationStorage(); const fakeBundle1 = {}; const fakeBundle2 = {}; - const installation1 = await install(fakeBundle1); + const installation1 = await installBundle(fakeBundle1); const unwrapped1 = await unwrapInstallation(installation1); t.is(unwrapped1.installation, installation1); t.is(unwrapped1.bundle, fakeBundle1); - const installation2 = await install(fakeBundle2); + const installation2 = await installBundle(fakeBundle2); const unwrapped2 = await unwrapInstallation(installation2); t.is(unwrapped2.installation, installation2); t.is(unwrapped2.bundle, fakeBundle2); }); test('install same twice', async t => { - const { install, unwrapInstallation } = makeInstallationStorage(); + const bundleCaps = { id: makeHandle('BundleCap') }; + const getBundleCapFromID = id => bundleCaps[id]; + const { installBundle, installBundleID, unwrapInstallation } = + makeInstallationStorage(getBundleCapFromID); const fakeBundle1 = {}; - const installation1 = await install(fakeBundle1); + const installation1 = await installBundle(fakeBundle1); const unwrapped1 = await unwrapInstallation(installation1); t.is(unwrapped1.installation, installation1); t.is(unwrapped1.bundle, fakeBundle1); @@ -53,9 +71,22 @@ test('install same twice', async t => { // If the same bundle is installed twice, the bundle is the same, // but the installation is different. Zoe does not currently care about // duplicate bundles. - const installation2 = await install(fakeBundle1); + const installation2 = await installBundle(fakeBundle1); const unwrapped2 = await unwrapInstallation(installation2); t.is(unwrapped2.installation, installation2); t.not(installation2, installation1); t.is(unwrapped2.bundle, fakeBundle1); + + // same for bundleIDs + const installation3 = await installBundleID('id'); + const installation4 = await installBundleID('id'); + t.not(installation3, installation4); + const unwrapped3 = await unwrapInstallation(installation3); + t.is(unwrapped3.installation, installation3); + t.is(unwrapped3.bundleID, 'id'); + t.is(unwrapped3.bundleCap, bundleCaps.id); + const unwrapped4 = await unwrapInstallation(installation4); + t.is(unwrapped4.installation, installation4); + t.is(unwrapped4.bundleID, 'id'); + t.is(unwrapped4.bundleCap, bundleCaps.id); }); diff --git a/packages/zoe/tools/fakeVatAdmin.js b/packages/zoe/tools/fakeVatAdmin.js index 2088c3988390..eec1f6fb3508 100644 --- a/packages/zoe/tools/fakeVatAdmin.js +++ b/packages/zoe/tools/fakeVatAdmin.js @@ -3,6 +3,7 @@ import { E } from '@agoric/eventual-send'; import { makePromiseKit } from '@endo/promise-kit'; import { Far } from '@endo/marshal'; +import { makeStore } from '@agoric/store'; import { assert } from '@agoric/assert'; import { evalContractBundle } from '../src/contractFacet/evalContractCode.js'; @@ -10,10 +11,14 @@ import { handlePKitWarning } from '../src/handleWarning.js'; import { makeHandle } from '../src/makeHandle.js'; import zcfContractBundle from '../bundles/bundle-contractFacet.js'; +/** @typedef { import('@agoric/swingset-vat').BundleID} BundleID */ +/** @typedef { import('@agoric/swingset-vat').EndoZipBase64Bundle} EndoZipBase64Bundle */ + // this simulates a bundlecap, which is normally a swingset "device node" /** @typedef { import('@agoric/swingset-vat').BundleCap } BundleCap */ -/** @type {BundleCap} */ -export const zcfBundleCap = makeHandle('BundleCap'); +/** @type {() => BundleCap} */ +const fakeBundleCap = () => makeHandle('BundleCap'); +export const zcfBundleCap = fakeBundleCap(); /** * @param { (...args) => unknown } [testContextSetter] @@ -25,6 +30,10 @@ function makeFakeVatAdmin(testContextSetter = undefined, makeRemote = x => x) { let exitMessage; let hasExited = false; let exitWithFailure; + /** @type {Store} */ + const idToBundleCap = makeStore('idToBundleCap'); + /** @type {Store} */ + const bundleCapToBundle = makeStore('bundleCapToBundle'); const fakeVatPowers = { exitVat: completion => { exitMessage = completion; @@ -36,49 +45,74 @@ function makeFakeVatAdmin(testContextSetter = undefined, makeRemote = x => x) { hasExited = true; exitWithFailure = true; }, + D: bundleCap => ({ + getBundle: () => bundleCapToBundle.get(bundleCap), + }), }; // This is explicitly intended to be mutable so that // test-only state can be provided from contracts // to their tests. const admin = Far('vatAdmin', { - getBundleCap: _bundleID => { - assert.fail(`fakeVatAdmin.getBundleCap() not yet implemented`); + getBundleCap: bundleID => { + if (!idToBundleCap.has(bundleID)) { + idToBundleCap.init(bundleID, fakeBundleCap()); + } + return Promise.resolve(idToBundleCap.get(bundleID)); }, getNamedBundleCap: name => { assert.equal(name, 'zcf', 'fakeVatAdmin only knows ZCF'); - return zcfBundleCap; + return Promise.resolve(zcfBundleCap); }, createVat: bundleCap => { assert.equal(bundleCap, zcfBundleCap, 'fakeVatAdmin only knows ZCF'); const bundle = zcfContractBundle; - return harden({ - root: makeRemote( - E(evalContractBundle(bundle)).buildRootObject( - fakeVatPowers, - undefined, - testContextSetter, + return Promise.resolve( + harden({ + root: makeRemote( + E(evalContractBundle(bundle)).buildRootObject( + fakeVatPowers, + undefined, + testContextSetter, + ), ), - ), - adminNode: Far('adminNode', { - done: () => { - const kit = makePromiseKit(); - handlePKitWarning(kit); - return kit.promise; - }, - terminateWithFailure: () => {}, + adminNode: Far('adminNode', { + done: () => { + const kit = makePromiseKit(); + handlePKitWarning(kit); + return kit.promise; + }, + terminateWithFailure: () => {}, + }), }), - }); + ); }, }); const vatAdminState = { getExitMessage: () => exitMessage, getHasExited: () => hasExited, getExitWithFailure: () => exitWithFailure, + installBundle: (id, bundle) => { + if (idToBundleCap.has(id)) { + assert.equal( + bundle.endoZipBase64, + bundleCapToBundle.get(idToBundleCap.get(id)).endoZipBase64, + ); + return; + } + const bundleCap = fakeBundleCap(); + idToBundleCap.init(id, bundleCap); + bundleCapToBundle.init(bundleCap, bundle); + }, }; return { admin, vatAdminState }; } +// Tests which use this global/shared fakeVatAdmin should really import +// makeFakeVatAdmin() instead, and build their own private instance. This +// will be forced when #4565 requires them to use +// vatAdminState.installBundle(). + const fakeVatAdmin = makeFakeVatAdmin().admin; export default fakeVatAdmin;