@agoric/zoe
Version:
Zoe: the Smart Contract Framework for Offer Enforcement
161 lines (149 loc) • 5.84 kB
JavaScript
import { Fail, q } from '@endo/errors';
import {
M,
prepareExo,
prepareKind,
provideDurableWeakMapStore,
} from '@agoric/vat-data';
import {
InstallationShape,
UnwrappedInstallationShape,
} from '../typeGuards.js';
/**
* @import {Baggage} from '@agoric/swingset-liveslots';
* @import {WeakMapStore} from '@agoric/store';
* @import {BundleID, BundleCap} from '@agoric/swingset-vat';
* @import {SourceBundle} from '@agoric/zoe';
*/
/**
* @param {GetBundleCapForID} getBundleCapForID
* @param {Baggage} zoeBaggage
*/
export const makeInstallationStorage = (getBundleCapForID, zoeBaggage) => {
/** @type {WeakMapStore<Installation, { bundleCap: BundleCap, bundleID: BundleID }>} */
const installationsBundleCap = provideDurableWeakMapStore(
zoeBaggage,
'installationsBundleCap',
);
/** @type {WeakMapStore<Installation, SourceBundle>} */
const installationsBundle = provideDurableWeakMapStore(
zoeBaggage,
'installationsBundle',
);
/** @type {(bundleLabel: string) => Installation<unknown>} */
const makeBundleIDInstallation = prepareKind(
zoeBaggage,
'BundleIDInstallation',
bundleLabel => ({ bundleLabel }),
// @ts-expect-error cast without StartFunction property
{
getBundle: _context => Fail`bundleID-based Installation`,
getBundleLabel: ({ state: { bundleLabel } }) => bundleLabel,
},
);
/** @type {(bundle: SourceBundle, bundleLabel?: string) => Installation<unknown>} */
const makeBundleInstallation = prepareKind(
zoeBaggage,
'BundleInstallation',
(bundle, bundleLabel) => ({ bundle, bundleLabel }),
// @ts-expect-error cast without StartFunction property
{
getBundle: ({ state: { bundle } }) => bundle,
getBundleLabel: ({ state: { bundleLabel } }) => bundleLabel,
},
);
/**
* 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 {InstallBundle} */
const installSourceBundle = async (bundle, bundleLabel) => {
typeof bundle === 'object' || Fail`a bundle must be provided`;
/** @type {Installation} */
bundle || Fail`a bundle must be provided`;
const installation = makeBundleInstallation(bundle, bundleLabel);
installationsBundle.init(installation, bundle);
return installation;
};
const InstallationStorageI = M.interface('InstallationStorage', {
installBundle: M.call(
M.recordOf(M.string(), M.string({ stringLengthLimit: Infinity })),
)
.optional(M.string())
.returns(M.promise()),
installBundleID: M.call(M.string())
.optional(M.string())
.returns(M.promise()),
unwrapInstallation: M.callWhen(M.await(InstallationShape)).returns(
UnwrappedInstallationShape,
),
getBundleIDFromInstallation: M.callWhen(M.await(InstallationShape)).returns(
M.eref(M.string()),
),
});
const installationStorage = prepareExo(
zoeBaggage,
'InstallationStorage',
InstallationStorageI,
{
async installBundle(allegedBundle, bundleLabel) {
const { self } = this;
// Bundle is a very open-ended type and we must decide here whether to
// treat it as either a HashBundle or SourceBundle. So we have to
// inspect it.
typeof allegedBundle === 'object' || Fail`a bundle must be provided`;
allegedBundle !== null || Fail`a bundle must be provided`;
const { moduleFormat } = allegedBundle;
if (moduleFormat === 'endoZipBase64Sha512') {
const { endoZipBase64Sha512 } = allegedBundle;
typeof endoZipBase64Sha512 === 'string' ||
Fail`bundle endoZipBase64Sha512 must be a string, got ${q(
endoZipBase64Sha512,
)}`;
return self.installBundleID(`b1-${endoZipBase64Sha512}`, bundleLabel);
}
return installSourceBundle(allegedBundle, bundleLabel);
},
async installBundleID(bundleID, bundleLabel = bundleID.slice(0, 9)) {
typeof bundleID === 'string' || Fail`a bundle ID must be provided`;
typeof bundleLabel === 'string' ||
Fail`a bundle label must be a string`;
// this waits until someone tells the host application to store the
// bundle into the kernel, with controller.validateAndInstallBundle()
const bundleCap = await getBundleCapForID(bundleID);
// AWAIT
const installation = makeBundleIDInstallation(bundleLabel);
installationsBundleCap.init(
installation,
harden({ bundleCap, bundleID }),
);
return installation;
},
unwrapInstallation(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 {
throw Fail`${installation} was not a valid installation`;
}
},
async getBundleIDFromInstallation(allegedInstallation) {
const { self } = this;
const { bundleID } = await self.unwrapInstallation(allegedInstallation);
// AWAIT
bundleID || Fail`installation does not have a bundle ID`;
return bundleID;
},
},
);
return installationStorage;
};