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

6701 EC addOracles to a governed fluxAggregator #6975

Merged
merged 11 commits into from
Feb 13, 2023
6 changes: 3 additions & 3 deletions packages/governance/src/contractGovernance/governApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const { Fail, quote: q } = assert;
const makeApiInvocationPositions = (apiMethodName, methodArgs) => {
const positive = harden({ apiMethodName, methodArgs });
const negative = harden({ dontInvoke: apiMethodName });
return { positive, negative };
return harden({ positive, negative });
};

/**
Expand Down Expand Up @@ -113,11 +113,11 @@ const setupApiGovernance = async (
},
);

return {
return harden({
outcomeOfUpdate,
instance: voteCounter,
details: E(counterPublicFacet).getDetails(),
};
});
};

return Far('paramGovernor', {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ const isAsync = {
* @typedef {[type: T, value: ParamValueForType<T>]} ST param spec tuple
*/

/**
* @typedef {{ type: 'invitation', value: Amount<'set'> }} InvitationParam
*/

// XXX better to use the manifest constant ParamTypes
// but importing that here turns this file into a module,
// breaking the ambient typing
Expand Down
18 changes: 17 additions & 1 deletion packages/governance/src/contractGovernor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';
import { mustMatch } from '@agoric/store';

import { makeTracer } from '@agoric/internal';
import {
CONTRACT_ELECTORATE,
setupParamGovernance,
Expand All @@ -12,6 +13,8 @@ import { ParamChangesQuestionDetailsShape } from './typeGuards.js';

const { Fail } = assert;

const trace = makeTracer('CGov', false);

/**
* Validate that the question details correspond to a parameter change question
* that the electorate hosts, and that the voteCounter and other details are
Expand Down Expand Up @@ -121,7 +124,13 @@ const validateQuestionFromCounter = async (zoe, electorate, voteCounter) => {
*/

/**
* @template {() => {creatorFacet: GovernorFacet<any>, publicFacet: GovernedPublicFacetMethods} } SF Start function of governed contract
* @typedef {() => {creatorFacet: GovernorFacet<any>, publicFacet: GovernedPublicFacetMethods}} GovernableStartFn
*/

/**
* Start an instance of a governor, governing a "governed" contract specified in terms.
*
* @template {GovernableStartFn} SF Start function of governed contract
* @param {ZCF<{
* timer: import('@agoric/time/src/types').TimerService,
* governedContractInstallation: Installation<SF>,
Expand All @@ -135,7 +144,9 @@ const validateQuestionFromCounter = async (zoe, electorate, voteCounter) => {
* }} privateArgs
*/
const start = async (zcf, privateArgs) => {
trace('start');
const zoe = zcf.getZoeService();
trace('getTerms', zcf.getTerms());
const {
timer,
governedContractInstallation,
Expand All @@ -144,6 +155,7 @@ const start = async (zcf, privateArgs) => {
terms: contractTerms,
},
} = zcf.getTerms();
trace('contractTerms', contractTerms);
contractTerms.governedParams[CONTRACT_ELECTORATE] ||
Fail`Contract must declare ${CONTRACT_ELECTORATE} as a governed parameter`;

Expand All @@ -152,6 +164,7 @@ const start = async (zcf, privateArgs) => {
electionManager: zcf.getInstance(),
});

trace('starting governedContractInstallation');
const {
creatorFacet: governedCF,
instance: governedInstance,
Expand Down Expand Up @@ -191,9 +204,11 @@ const start = async (zcf, privateArgs) => {
}
return poserFacet;
};
trace('awaiting getUpdatedPoserFacet');
await getUpdatedPoserFacet();
assert(poserFacet, 'question poser facet must be initialized');

trace('awaiting setupParamGovernance');
// All governed contracts have at least a governed electorate
const { voteOnParamChanges, createdQuestion: createdParamQuestion } =
await setupParamGovernance(
Expand All @@ -204,6 +219,7 @@ const start = async (zcf, privateArgs) => {
getUpdatedPoserFacet,
);

trace('awaiting setupFilterGovernance');
const { voteOnFilter, createdFilterQuestion } = await setupFilterGovernance(
zoe,
governedInstance,
Expand Down
3 changes: 1 addition & 2 deletions packages/governance/src/contractHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ const facetHelpers = (zcf, paramManager) => {
/**
* @template {{}} CF
* @param {CF} limitedCreatorFacet
* @param {{}} [governedApis]
* @param {Record<string, (...any) => unknown>} [governedApis]
* @returns {GovernorFacet<CF>}
*/
const makeFarGovernorFacet = (limitedCreatorFacet, governedApis = {}) => {
Expand All @@ -127,7 +127,6 @@ const facetHelpers = (zcf, paramManager) => {
// The contract provides a facet with the APIs that can be invoked by
// governance
/** @type {() => GovernedApis} */
// @ts-expect-error TS think this is a RemotableBrand??
getGovernedApis: () => Far('governedAPIs', governedApis),
// The facet returned by getGovernedApis is Far, so we can't see what
// methods it has. There's no clean way to have contracts specify the APIs
Expand Down
120 changes: 31 additions & 89 deletions packages/governance/test/unitTests/test-puppetContractGovernor.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,27 @@
import { test } from '@agoric/zoe/tools/prepare-test-env-ava.js';
import '@agoric/zoe/exported.js';

import { makeNotifierFromAsyncIterable } from '@agoric/notifier';
import { makeZoeKit } from '@agoric/zoe';
import bundleSource from '@endo/bundle-source';
import buildManualTimer from '@agoric/zoe/tools/manualTimer.js';
import { makeFakeVatAdmin } from '@agoric/zoe/tools/fakeVatAdmin.js';
import buildManualTimer from '@agoric/zoe/tools/manualTimer.js';
import bundleSource from '@endo/bundle-source';
import { E } from '@endo/eventual-send';

import { resolve as importMetaResolve } from 'import-meta-resolve';
import { MALLEABLE_NUMBER } from '../swingsetTests/contractGovernor/governedContract.js';
import { CONTRACT_ELECTORATE, ParamTypes } from '../../src/index.js';

const governedRoot = '../swingsetTests/contractGovernor/governedContract.js';
const contractGovernorRoot = '../../tools/puppetContractGovernor.js';
const autoRefundRoot = '@agoric/zoe/src/contracts/automaticRefund.js';
import { CONTRACT_ELECTORATE, ParamTypes } from '../../src/index.js';
import { setUpGovernedContract } from '../../tools/puppetGovernance.js';
import { MALLEABLE_NUMBER } from '../swingsetTests/contractGovernor/governedContract.js';

const makeBundle = async sourceRoot => {
const url = await importMetaResolve(sourceRoot, import.meta.url);
const path = new URL(url).pathname;
const contractBundle = await bundleSource(path);
return contractBundle;
};

// makeBundle is a slow step, so we do it once for all the tests.
const contractGovernorBundleP = makeBundle(contractGovernorRoot);
const governedBundleP = makeBundle(governedRoot);
// could be called fakeCommittee. It's used as a source of invitations only
const autoRefundBundleP = makeBundle(autoRefundRoot);
const governedBundleP = await makeBundle(
'../swingsetTests/contractGovernor/governedContract.js',
);

const setUpZoeForTest = async setJig => {
const makeFar = o => o;
Expand All @@ -50,81 +44,23 @@ const setUpZoeForTest = async setJig => {
};
};

const installBundle = (zoe, contractBundle) => E(zoe).install(contractBundle);

// contract governor wants a committee invitation. give it a random invitation
async function getInvitation(zoe, autoRefundInstance) {
const autoRefundFacets = await E(zoe).startInstance(autoRefundInstance);
const invitationP = E(autoRefundFacets.publicFacet).makeInvitation();
const [fakeInvitationPayment, fakeInvitationAmount] = await Promise.all([
invitationP,
E(E(zoe).getInvitationIssuer()).getAmountOf(invitationP),
]);
return { fakeInvitationPayment, fakeInvitationAmount };
}

const setUpGovernedContract = async (zoe, electorateTerms, timer) => {
const [contractGovernorBundle, autoRefundBundle, governedBundle] =
await Promise.all([
contractGovernorBundleP,
autoRefundBundleP,
governedBundleP,
]);

const [governor, autoRefund, governed] = await Promise.all([
installBundle(zoe, contractGovernorBundle),
installBundle(zoe, autoRefundBundle),
installBundle(zoe, governedBundle),
]);
const installs = { governor, autoRefund, governed };
const { fakeInvitationPayment, fakeInvitationAmount } = await getInvitation(
zoe,
autoRefund,
);

const governedTerms = {
governedParams: {
[MALLEABLE_NUMBER]: {
type: ParamTypes.NAT,
value: 602214090000000000000000n,
},
[CONTRACT_ELECTORATE]: {
type: ParamTypes.INVITATION,
value: fakeInvitationAmount,
},
},
governedApis: ['governanceApi'],
};
const governorTerms = {
timer,
governedContractInstallation: governed,
governed: {
terms: governedTerms,
issuerKeywordRecord: {},
},
};

const governorFacets = await E(zoe).startInstance(
governor,
{},
governorTerms,
{
governed: {
initialPoserInvitation: fakeInvitationPayment,
},
const governedTerms = {
governedParams: {
[MALLEABLE_NUMBER]: {
type: ParamTypes.NAT,
value: 602214090000000000000000n,
},
);

return { governorFacets, installs };
},
};

test('multiple params bad change', async t => {
const { zoe } = await setUpZoeForTest(() => {});
const timer = buildManualTimer(t.log);
const { governorFacets } = await setUpGovernedContract(
zoe,
{ committeeName: 'Demos', committeeSize: 1 },
E(zoe).install(governedBundleP),
timer,
governedTerms,
);

const paramChangesSpec = harden({
Expand All @@ -147,10 +83,11 @@ test('multiple params bad change', async t => {
test('change a param', async t => {
const { zoe } = await setUpZoeForTest(() => {});
const timer = buildManualTimer(t.log);
const { governorFacets, installs } = await setUpGovernedContract(
const { governorFacets, getFakeInvitation } = await setUpGovernedContract(
zoe,
{ committeeName: 'Demos', committeeSize: 1 },
E(zoe).install(governedBundleP),
timer,
governedTerms,
);

/** @type {GovernedPublicFacet<unknown>} */
Expand All @@ -173,10 +110,8 @@ test('change a param', async t => {
});

// This is the wrong kind of invitation, but governance can't tell
const { fakeInvitationPayment, fakeInvitationAmount } = await getInvitation(
zoe,
installs.autoRefund,
);
const { fakeInvitationPayment, fakeInvitationAmount } =
await getFakeInvitation();

const paramChangesSpec = harden({
paramPath: { key: 'governedParams' },
Expand Down Expand Up @@ -207,8 +142,9 @@ test('set offer Filter directly', async t => {
const timer = buildManualTimer(t.log);
const { governorFacets } = await setUpGovernedContract(
zoe,
{ committeeName: 'Demos', committeeSize: 1 },
E(zoe).install(governedBundleP),
timer,
governedTerms,
);

await E(governorFacets.creatorFacet).setFilters(['whatever']);
Expand All @@ -223,12 +159,18 @@ test('call API directly', async t => {
const timer = buildManualTimer(t.log);
const { governorFacets } = await setUpGovernedContract(
zoe,
{ committeeName: 'Demos', committeeSize: 1 },
E(zoe).install(governedBundleP),
timer,
governedTerms,
);

await E(governorFacets.creatorFacet).invokeAPI('governanceApi', []);
const result = await E(governorFacets.creatorFacet).invokeAPI(
'governanceApi',
[],
);
t.deepEqual(result, { apiMethodName: 'governanceApi', methodArgs: [] });
t.deepEqual(
// @ts-expect-error FIXME type the puppet extensions
await E(E(governorFacets.creatorFacet).getPublicFacet()).getApiCalled(),
1,
);
Expand Down
22 changes: 17 additions & 5 deletions packages/governance/tools/puppetContractGovernor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,24 @@ import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';

// eslint-disable-next-line no-unused-vars
import { Fail } from '@agoric/assert';
// eslint-disable-next-line no-unused-vars -- used by typedef
import { CONTRACT_ELECTORATE } from '../src/contractGovernance/governParam.js';
import { makeApiInvocationPositions } from '../src/contractGovernance/governApi.js';

// @file a version of the contractGovernor.js contract simplified for testing.
// It removes the electorate and doesn't try to support legibility.
// It maintains the API for the governed contract (parameters, apis, and filters)
// It adds the ability for tests to update parameters directly.

/**
* @template {() => {creatorFacet: GovernorFacet<any>, publicFacet: unknown} } SF Start function of governed contract
* @template {import('../src/contractGovernor.js').GovernableStartFn} SF Start function of governed contract
* @param {ZCF<{
* timer: import('@agoric/time/src/types').TimerService,
* governedContractInstallation: Installation<SF>,
* governed: {
* issuerKeywordRecord: IssuerKeywordRecord,
* terms: {governedParams: {[CONTRACT_ELECTORATE]: Amount<'set'>}},
* terms: {governedParams: {[CONTRACT_ELECTORATE]: import('../src/contractGovernance/typedParamManager.js').InvitationParam }},
* }
* }>} zcf
* @param {{
Expand Down Expand Up @@ -71,8 +74,17 @@ export const start = async (zcf, privateArgs) => {
* @param {string} apiMethodName
* @param {unknown[]} methodArgs
*/
const invokeAPI = (apiMethodName, methodArgs) =>
E(E(governedCF).getGovernedApis())[apiMethodName](...methodArgs);
const invokeAPI = async (apiMethodName, methodArgs) => {
const governedNames = await E(governedCF).getGovernedApiNames();
governedNames.includes(apiMethodName) ||
Fail`${apiMethodName} is not a governed API.`;

const { positive } = makeApiInvocationPositions(apiMethodName, methodArgs);

return E(E(governedCF).getGovernedApis())
[apiMethodName](...methodArgs)
.then(() => positive);
};

const creatorFacet = Far('governor creatorFacet', {
changeParams,
Expand All @@ -94,6 +106,6 @@ export const start = async (zcf, privateArgs) => {
};
harden(start);
/**
* @template {() => {creatorFacet: GovernorFacet<any>, publicFacet: unknown} } SF Start function of governed contract
* @template {import('../src/contractGovernor.js').GovernableStartFn} SF Start function of governed contract
* @typedef {Awaited<ReturnType<typeof start<SF>>>} PuppetContractGovernorKit<SF>
*/
Loading