Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: a test framework for verifying upgrade of Zoe and ZCF #7969

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions packages/vats/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"build:boot-viz-sim-gov": "node src/authorityViz.js --sim-chain --gov >docs/boot-sim-gov.dot && dot -Tsvg docs/boot-sim-gov.dot >docs/boot-sim-gov.dot.svg",
"build:restart-vats-proposal": "agoric run scripts/restart-vats.js",
"build:add-STARS-proposal": "agoric run scripts/add-STARS.js",
"build:zcf-proposal": "agoric run scripts/replace-zoe.js",
"prepack": "tsc --build jsconfig.build.json",
"postpack": "git clean -f '*.d.ts*'",
"test": "ava",
Expand Down
25 changes: 25 additions & 0 deletions packages/vats/scripts/replace-zoe.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { makeHelpers } from '@agoric/deploy-script-support';

/** @type {import('@agoric/deploy-script-support/src/externalTypes.js').ProposalBuilder} */
export const defaultProposalBuilder = async () =>
harden({
sourceSpec: '../src/proposals/zcf-proposal.js',
getManifestCall: [
'getManifestForZoe',
{
zcfRef: {
bundleID:
'b1-8674abc9a8de561c4a33fb475b87be75708cd901c37931fd5ac1f40d3ee99937a459a6ca7b4a8b7907512626caf98c125f22c15384826e37dfc899dc0bf2a63a',
},
zoeRef: {
bundleID:
'b1-68963663488ee6d178293b559b9d902cea1857dddac257f08540cb9748647d0218a991ce02cf9f61e1e49cca3979b20473103a7fee509cf808de43e323afab54',
},
},
],
});

export default async (homeP, endowments) => {
const { writeCoreProposal } = await makeHelpers(homeP, endowments);
await writeCoreProposal('replace-zcf', defaultProposalBuilder);
};
44 changes: 44 additions & 0 deletions packages/vats/src/proposals/zcf-proposal.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { E } from '@endo/far';

/**
* @param { BootstrapPowers & {
* consume: {
* vatAdminSvc: VatAdminSve,
* vatStore: MapStore<string, CreateVatResults>,
* }
* }} powers
*
* @param {object} options
* @param {{zoeRef: VatSourceRef, zcfRef: VatSourceRef}} options.options
*/
export const upgradeZcf = async (
{ consume: { vatAdminSvc, vatStore } },
options,
) => {
const { zoeRef, zcfRef } = options.options;

const zoeBundleCap = await E(vatAdminSvc).getBundleCap(zoeRef.bundleID);

const { adminNode, root: zoeRoot } = await E(vatStore).get('zoe');

await E(adminNode).upgrade(zoeBundleCap, {});

const zoeConfigFacet = await E(zoeRoot).getZoeConfigFacet();
await E(zoeConfigFacet).updateZcfBundleId(zcfRef.bundleID);
};

export const getManifestForZoe = (_powers, { zoeRef, zcfRef }) => ({
manifest: {
[upgradeZcf.name]: {
consume: {
vatAdminSvc: 'vatAdminSvc',
vatStore: 'vatStore',
},
produce: {},
},
},
options: {
zoeRef,
zcfRef,
},
});
81 changes: 81 additions & 0 deletions packages/vats/test/bootstrapTests/drivers.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { SECONDS_PER_MINUTE } from '@agoric/inter-protocol/src/proposals/econ-be
import { unmarshalFromVstorage } from '@agoric/internal/src/marshal.js';
import { slotToRemotable } from '@agoric/internal/src/storage-test-utils.js';
import { instanceNameFor } from '@agoric/inter-protocol/src/proposals/price-feed-proposal.js';

import { boardSlottingMarshaller } from '../../tools/board-utils.js';

/**
Expand Down Expand Up @@ -340,3 +341,83 @@ export const makeGovernanceDriver = async (
},
};
};

/**
* @param {import('./supports.js').SwingsetTestKit} testKit
*/
export const makeZoeDriver = async testKit => {
const { EV } = testKit.runUtils;
const zoe = await EV.vat('bootstrap').consumeItem('zoe');
let creatorFacet;
let adminFacet;
let brand;
const sub = (a, v) => {
return { brand: a.brand, value: a.value - v };
};

return {
async instantiateProbeContract(probeContractBundle) {
const installation = await EV(zoe).install(probeContractBundle);
const startResults = await EV(zoe).startInstance(installation);
({ creatorFacet, adminFacet } = startResults);

const issuers = await EV(zoe).getIssuers(startResults.instance);
const brands = await EV(zoe).getBrands(startResults.instance);
brand = brands.Ducats;
return { creatorFacet, issuer: issuers.Ducats, brand };
},
async upgradeProbe(probeContractBundle) {
const fabricateBundleId = bundle => {
return `b1-${bundle.endoZipBase64Sha512}`;
};

await EV(adminFacet).upgradeContract(
fabricateBundleId(probeContractBundle),
);
},

verifyRealloc() {
const alloc = EV(creatorFacet).getAllocation();
return alloc;
},
async probeReallocation(value, payment) {
const stagingInv = await EV(creatorFacet).makeProbeStagingInvitation();

const stagingSeat = await EV(zoe).offer(
stagingInv,
{ give: { Ducats: value } },
{ Ducats: payment },
);
const helperPayments = await EV(stagingSeat).getPayouts();

const helperInv = await EV(creatorFacet).makeProbeHelperInvitation();
const helperSeat = await EV(zoe).offer(
helperInv,
{ give: { Ducats: sub(value, 1n) } },
{ Ducats: helperPayments.Ducats },
);
const internalPayments = await EV(helperSeat).getPayouts();

const internalInv = await EV(creatorFacet).makeProbeInternalInvitation();
const internalSeat = await EV(zoe).offer(
internalInv,
{ give: { Ducats: sub(value, 2n) } },
{ Ducats: internalPayments.Ducats },
);
const leftoverPayments = await EV(internalSeat).getPayouts();

return {
stagingResult: await EV(stagingSeat).getOfferResult(),
helperResult: await EV(helperSeat).getOfferResult(),
internalResult: await EV(internalSeat).getOfferResult(),
leftoverPayments,
};
},
async faucet() {
const faucetInv = await EV(creatorFacet).makeFaucetInvitation();
const seat = await EV(zoe).offer(faucetInv);

return EV(seat).getPayout('Ducats');
},
};
};
102 changes: 102 additions & 0 deletions packages/vats/test/bootstrapTests/supports.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// @ts-check

/* global process */
import * as fsAmbient from 'fs';

import { resolve as importMetaResolve } from 'import-meta-resolve';
import { basename } from 'path';
import { inspect } from 'util';
Expand All @@ -17,14 +19,22 @@ import { loadSwingsetConfigFile } from '@agoric/swingset-vat';
import { E } from '@endo/eventual-send';
import { makeQueue } from '@endo/stream';
import { TimeMath } from '@agoric/time';
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';

import * as processAmbient from 'child_process';
import {
boardSlottingMarshaller,
makeAgoricNamesRemotesFromFakeStorage,
slotToBoardRemote,
} from '../../tools/board-utils.js';
import { makeWalletFactoryDriver, makeZoeDriver } from './drivers.js';

// to retain for ESlint, used by typedef
E;

// main/production config doesn't have initialPrice, upon which 'open vaults' depends
const PLATFORM_CONFIG = '@agoric/vats/decentral-itest-vaults-config.json';

const sink = () => {};

const trace = makeTracer('BSTSupport', false);
Expand Down Expand Up @@ -488,4 +498,96 @@ export const makeSwingsetTestKit = async (
timer,
};
};

/** @typedef {Awaited<ReturnType<typeof makeSwingsetTestKit>>} SwingsetTestKit */

export const makeTestContext = async t => {
console.time('DefaultTestContext');
/** @type {SwingsetTestKit} */
const swingsetTestKit = await makeSwingsetTestKit(t, 'bundles/vaults', {
configSpecifier: PLATFORM_CONFIG,
});

const { runUtils, storage } = swingsetTestKit;
console.timeLog('DefaultTestContext', 'swingsetTestKit');
const { EV } = runUtils;

// Wait for ATOM to make it into agoricNames
await EV.vat('bootstrap').consumeItem('vaultFactoryKit');
console.timeLog('DefaultTestContext', 'vaultFactoryKit');

await eventLoopIteration();

// has to be late enough for agoricNames data to have been published
const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(
swingsetTestKit.storage,
);
agoricNamesRemotes.brand.ATOM || Fail`ATOM brand not yet defined`;
console.timeLog('DefaultTestContext', 'agoricNamesRemotes');

const walletFactoryDriver = await makeWalletFactoryDriver(
runUtils,
storage,
agoricNamesRemotes,
);
console.timeLog('DefaultTestContext', 'walletFactoryDriver');

console.timeEnd('DefaultTestContext');

const buildProposal = makeProposalExtractor({
childProcess: processAmbient,
fs: fsAmbient.promises,
Comment on lines +538 to +539
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

modules should not export powerful objects (for example, objects that close over fs or process.env)
-- OCap discipline

Please let's pass powers such as fs and childProcess into makeTestContext explicitly, if we're going to export it.

(yes, it's "just" test code, but I don't think this package as an exports thing that prevents unwanted deep imports, so this is actually part of the public API).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I converted the PR to DRAFT to make it clear that it's not intended to be merged. The point of the PR is to show that an upgrade from current state to post-ZCF would work. It's a rough copy of #7966, which is intended to merge, but has to start from the post-ZCF world.

It doesn't seem worth spending effort to clean this up, as long as it's convincing. Let me know if you'd like me to do it anyway.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah. thanks for clarifying. sorry to bother you.

});

return {
...swingsetTestKit,
agoricNamesRemotes,
walletFactoryDriver,
buildProposal,
};
};

export const makeZoeTestContext = async t => {
console.time('DefaultTestContext');
/** @type {SwingsetTestKit} */
const swingsetTestKit = await makeSwingsetTestKit(t, 'bundles/zoe', {
// configSpecifier: PLATFORM_CONFIG,
// configSpecifier: '@agoric/vats/decentral-core-config.json',
configSpecifier: '@agoric/vats/decentral-demo-config.json',
});

const { controller, runUtils } = swingsetTestKit;
console.timeLog('DefaultTestContext', 'swingsetTestKit');
const { EV } = runUtils;

await eventLoopIteration();

// We don't need vaults, but this gets the brand, which is checked somewhere
// Wait for ATOM to make it into agoricNames
await EV.vat('bootstrap').consumeItem('vaultFactoryKit');
console.timeLog('DefaultTestContext', 'vaultFactoryKit');

// has to be late enough for agoricNames data to have been published
const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(
swingsetTestKit.storage,
);
console.timeLog('DefaultTestContext', 'agoricNamesRemotes');

const zoeDriver = await makeZoeDriver(swingsetTestKit);
console.timeLog('DefaultTestContext', 'walletFactoryDriver');

console.timeEnd('DefaultTestContext');

const buildProposal = makeProposalExtractor({
childProcess: processAmbient,
fs: fsAmbient.promises,
});

return {
...swingsetTestKit,
controller,
agoricNamesRemotes,
zoeDriver,
buildProposal,
};
};
49 changes: 2 additions & 47 deletions packages/vats/test/bootstrapTests/test-vats-restart.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,21 @@
// @ts-check

/**
* @file Bootstrap test of restarting (almost) all vats
*/
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';

import { Fail } from '@agoric/assert';
import { Offers } from '@agoric/inter-protocol/src/clientSupport.js';
import { eventLoopIteration } from '@agoric/internal/src/testing-utils.js';
import { makeAgoricNamesRemotesFromFakeStorage } from '../../tools/board-utils.js';
import { makeWalletFactoryDriver } from './drivers.js';
import { makeSwingsetTestKit } from './supports.js';
import { makeTestContext } from './supports.js';

/**
* @type {import('ava').TestFn<Awaited<ReturnType<typeof makeTestContext>>>}
*/
const test = anyTest;

// main/production config doesn't have initialPrice, upon which 'open vaults' depends
const PLATFORM_CONFIG = '@agoric/vats/decentral-itest-vaults-config.json';

// presently all these tests use one collateral manager
const collateralBrandKey = 'ATOM';

const makeTestContext = async t => {
console.time('DefaultTestContext');
const swingsetTestKit = await makeSwingsetTestKit(t, 'bundles/vaults', {
configSpecifier: PLATFORM_CONFIG,
});

const { runUtils, storage } = swingsetTestKit;
console.timeLog('DefaultTestContext', 'swingsetTestKit');
const { EV } = runUtils;

// Wait for ATOM to make it into agoricNames
await EV.vat('bootstrap').consumeItem('vaultFactoryKit');
console.timeLog('DefaultTestContext', 'vaultFactoryKit');

await eventLoopIteration();

// has to be late enough for agoricNames data to have been published
const agoricNamesRemotes = makeAgoricNamesRemotesFromFakeStorage(
swingsetTestKit.storage,
);
agoricNamesRemotes.brand.ATOM || Fail`ATOM brand not yet defined`;
console.timeLog('DefaultTestContext', 'agoricNamesRemotes');

const walletFactoryDriver = await makeWalletFactoryDriver(
runUtils,
storage,
agoricNamesRemotes,
);
console.timeLog('DefaultTestContext', 'walletFactoryDriver');

console.timeEnd('DefaultTestContext');

return {
...swingsetTestKit,
agoricNamesRemotes,
walletFactoryDriver,
};
};

test.before(async t => {
t.context = await makeTestContext(t);
});
Expand Down
Loading