Skip to content

Commit

Permalink
test: test that APIs can change on upgrade
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris-Hibbert committed Jan 8, 2025
1 parent bb578a2 commit cc5512a
Show file tree
Hide file tree
Showing 5 changed files with 360 additions and 1 deletion.
1 change: 1 addition & 0 deletions packages/boot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
"@agoric/cosmic-swingset": "^0.41.3",
"@agoric/ertp": "^0.16.2",
"@agoric/fast-usdc": "0.1.0",
"@agoric/governance": "^0.10.3",
"@agoric/inter-protocol": "^0.16.1",
"@agoric/internal": "^0.3.2",
"@agoric/kmarshal": "^0.1.0",
Expand Down
85 changes: 85 additions & 0 deletions packages/boot/test/bootstrapTests/governedContract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import {
CONTRACT_ELECTORATE,
ParamTypes,
handleParamGovernance,
} from '@agoric/governance';
import { prepareExoClassKit, provide } from '@agoric/vat-data';

/**
* @import {GovernanceTerms} from '@agoric/governance/src/types.js';
* @import {Baggage} from '@agoric/vat-data';
*/

const MALLEABLE_NUMBER = 'MalleableNumber';

const makeTerms = (number, invitationAmount) => {
return harden({
governedParams: {
[MALLEABLE_NUMBER]: { type: ParamTypes.NAT, value: number },
[CONTRACT_ELECTORATE]: {
type: ParamTypes.INVITATION,
value: invitationAmount,
},
},
});
};

/**
*
* @param {ZCF<
* GovernanceTerms<{
* MalleableNumber: 'nat',
* }>>} zcf
* @param {{initialPoserInvitation: Invitation}} privateArgs
* @param {Baggage} baggage
*/
const start = async (zcf, privateArgs, baggage) => {
const { makeDurableGovernorFacet, params } = await handleParamGovernance(
zcf,
privateArgs.initialPoserInvitation,
{
[MALLEABLE_NUMBER]: ParamTypes.NAT,
},
);

const makeGoverned = prepareExoClassKit(
baggage,
'governed Public',
undefined,
() =>
harden({
governanceAPICalled: 0,
}),
{
public: {
getNum() {
return params.getMalleableNumber();
},
getApiCalled() {
const { governanceAPICalled } = this.state;
return governanceAPICalled;
},
},
creator: {},
governed: {
add1() {
const { state } = this;
state.governanceAPICalled += 1;
},
},
},
);
const facets = provide(baggage, 'theContract', () => makeGoverned());

const { governorFacet } = makeDurableGovernorFacet(baggage, facets.creator, {
add1: () => facets.governed.add1(),
});

return { publicFacet: facets.public, creatorFacet: governorFacet };
};

harden(start);
harden(MALLEABLE_NUMBER);
harden(makeTerms);

export { start, MALLEABLE_NUMBER, makeTerms };
90 changes: 90 additions & 0 deletions packages/boot/test/bootstrapTests/governedContract2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import {
CONTRACT_ELECTORATE,
ParamTypes,
handleParamGovernance,
} from '@agoric/governance';
import { prepareExoClassKit, provide } from '@agoric/vat-data';

/**
* @import {GovernanceTerms} from '@agoric/governance/src/types.js';
* @import {Baggage} from '@agoric/vat-data';
*/

const MALLEABLE_NUMBER = 'MalleableNumber';

const makeTerms = (number, invitationAmount) => {
return harden({
governedParams: {
[MALLEABLE_NUMBER]: { type: ParamTypes.NAT, value: number },
[CONTRACT_ELECTORATE]: {
type: ParamTypes.INVITATION,
value: invitationAmount,
},
},
});
};

/**
*
* @param {ZCF<
* GovernanceTerms<{
* MalleableNumber: 'nat',
* }>>} zcf
* @param {{initialPoserInvitation: Invitation}} privateArgs
* @param {Baggage} baggage
*/
const start = async (zcf, privateArgs, baggage) => {
const { makeDurableGovernorFacet, params } = await handleParamGovernance(
zcf,
privateArgs.initialPoserInvitation,
{
[MALLEABLE_NUMBER]: ParamTypes.NAT,
},
);

const makeGoverned = prepareExoClassKit(
baggage,
'governed Public',
undefined,
() =>
harden({
governanceAPICalled: 0,
}),
{
public: {
getNum() {
return params.getMalleableNumber();
},
getApiCalled() {
const { governanceAPICalled } = this.state;
return governanceAPICalled;
},
},
creator: {},
governed: {
add1() {
const { state } = this;
state.governanceAPICalled += 1;
},
add2() {
const { state } = this;
state.governanceAPICalled += 2;
},
},
},
);
const facets = provide(baggage, 'theContract', () => makeGoverned());

const { governorFacet } = makeDurableGovernorFacet(baggage, facets.creator, {
add1: () => facets.governed.add1(),
add2: () => facets.governed.add2(),
});

return { publicFacet: facets.public, creatorFacet: governorFacet };
};

harden(start);
harden(MALLEABLE_NUMBER);
harden(makeTerms);

export { start, MALLEABLE_NUMBER, makeTerms };
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* We change a parameter so that provideParamGovernance() is called once, and
* paramGoverance has been set. Then upgrade vaultFactory, so any ephemeral
* objects from the contract held by the governor are gone, then try to change
* param again, to show that the bug is fixedd.
* param again, to show that the bug is fixed.
*/
import { test as anyTest } from '@agoric/zoe/tools/prepare-test-env-ava.js';

Expand Down
183 changes: 183 additions & 0 deletions packages/boot/test/bootstrapTests/upgradeAPI.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
import { test as anyTest } from '@agoric/swingset-vat/tools/prepare-test-env-ava.js';
import type { TestFn } from 'ava';
import path from 'path';
import bundleSource from '@endo/bundle-source';
import { CONTRACT_ELECTORATE, ParamTypes } from '@agoric/governance';
import { MALLEABLE_NUMBER } from '@agoric/governance/test/swingsetTests/contractGovernor/governedContract.js';
import { makeSwingsetTestKit } from '../../tools/supports.js';

const dirname = path.dirname(new URL(import.meta.url).pathname);

const GOVERNED_CONTRACT_SRC = './governedContract.js';
const GOVERNED_CONTRACT2_SRC = './governedContract2.js';

const setUpGovernedContract = async (zoe, timer, EV, controller) => {
const installBundle = contractBundle => EV(zoe).install(contractBundle);
const installBundleToVatAdmin = contractBundle =>
controller.validateAndInstallBundle(contractBundle);
const source = `${dirname}/${GOVERNED_CONTRACT_SRC}`;
const source2 = `${dirname}/${GOVERNED_CONTRACT2_SRC}`;
const governedContractBundle = await bundleSource(source);
const governedContract2Bundle = await bundleSource(source2);

const agoricNames = await EV.vat('bootstrap').consumeItem('agoricNames');
const governorInstallation = await EV(agoricNames).lookup(
'installation',
'contractGovernor',
);
const voteCounterInstallation = await EV(agoricNames).lookup(
'installation',
'binaryVoteCounter',
);

const electorateCreatorFacet = await EV.vat('bootstrap').consumeItem(
'economicCommitteeCreatorFacet',
);
const poserInvitation = await EV(electorateCreatorFacet).getPoserInvitation();
const poserInvitation2 = await EV(
electorateCreatorFacet,
).getPoserInvitation();

const invitationIssuer = await EV(zoe).getInvitationIssuer();
const invitationAmount =
await EV(invitationIssuer).getAmountOf(poserInvitation);

const governedTerms = {
governedParams: {
[MALLEABLE_NUMBER]: {
type: ParamTypes.NAT,
value: 602214090000000000000000n,
},
[CONTRACT_ELECTORATE]: {
type: ParamTypes.INVITATION,
value: invitationAmount,
},
},
governedApis: ['governanceApi'],
};

const governedInstallation = await installBundle(governedContractBundle);
await installBundleToVatAdmin(governedContract2Bundle);
const governorTerms = {
timer,
governedContractInstallation: governedInstallation,
governed: {
terms: governedTerms,
issuerKeywordRecord: {},
},
};

const governorFacets = await EV(zoe).startInstance(
governorInstallation,
{},
governorTerms,
{
governed: {
initialPoserInvitation: poserInvitation,
},
},
);

return {
governorFacets,
invitationAmount,
voteCounterInstallation,
contract2SHA: governedContract2Bundle.endoZipBase64Sha512,
poserInvitation2,
};
};

// A more minimal set would be better. We need governance, but not econ vats.
const PLATFORM_CONFIG = '@agoric/vm-config/decentral-test-vaults-config.json';

const makeDefaultTestContext = async t => {
console.time('DefaultTestContext');
const swingsetTestKit = await makeSwingsetTestKit(t.log, undefined, {
configSpecifier: PLATFORM_CONFIG,
});

const { runUtils, storage, controller } = swingsetTestKit;
console.timeLog('DefaultTestContext', 'swingsetTestKit');
const { EV } = runUtils;
const zoe: ZoeService = await EV.vat('bootstrap').consumeItem('zoe');
const timer = await EV.vat('bootstrap').consumeItem('chainTimerService');

const facets = await setUpGovernedContract(zoe, timer, EV, controller);

return { ...swingsetTestKit, facets };
};

const test = anyTest as TestFn<
Awaited<ReturnType<typeof makeDefaultTestContext>>
>;

test.before(async t => {
t.context = await makeDefaultTestContext(t);
});

test.after.always(t => {
return t.context.shutdown && t.context.shutdown();
});

test(`start contract; verify`, async t => {
const { runUtils, facets } = t.context;
const {
governorFacets: { creatorFacet },
} = facets;
const { EV } = runUtils;
const contractPublicFacet = await EV(creatorFacet).getPublicFacet();

const avogadro = await EV(contractPublicFacet).getNum();
t.is(await EV(contractPublicFacet).getApiCalled(), 0);
t.is(avogadro, 602214090000000000000000n);
});

test(`verify API governance`, async t => {
const { runUtils, facets } = t.context;
const {
governorFacets: { creatorFacet },
voteCounterInstallation: vci,
} = facets;

const { EV } = runUtils;

const question = await EV(creatorFacet).voteOnApiInvocation(
'add1',
[],
vci,
37n,
);
t.truthy(question.instance);

await t.throwsAsync(
() => EV(creatorFacet).voteOnApiInvocation('add2', [], vci, 37n),
{
message: /"add2" is not a governed API./,
},
);
});

test(`upgrade; verify enhanced API governance`, async t => {
const { runUtils, facets } = t.context;
const {
governorFacets: { creatorFacet },
voteCounterInstallation: vci,
contract2SHA,
poserInvitation2,
} = facets;

const { EV } = runUtils;
const af = await EV(creatorFacet).getAdminFacet();

await EV(af).upgradeContract(`b1-${contract2SHA}`, {
initialPoserInvitation: poserInvitation2,
});

const question2 = await EV(creatorFacet).voteOnApiInvocation(
'add2',
[],
vci,
37n,
);
t.truthy(question2.instance);
});

0 comments on commit cc5512a

Please sign in to comment.