Skip to content

Commit

Permalink
feat: refactor parameter governance support to allow for Invitations (#…
Browse files Browse the repository at this point in the history
…4121)

Invitations didn't fit the old model, so turned it into more
polymorphic objects. I didn't get as much type-checking advantage as I
expected.

Moved parameter governance support into a subdirectory, which touched
a bunch of files lightly.

Added separate types for Amounts and Ratios whose Brands must match.

vaults weren't using the params passed to them, so they were dropped.

get parameters by type

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
Chris-Hibbert and mergify[bot] authored Dec 17, 2021
1 parent cdf58e6 commit 159596b
Show file tree
Hide file tree
Showing 39 changed files with 1,183 additions and 749 deletions.
74 changes: 54 additions & 20 deletions packages/governance/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,24 @@ connected to so that voters can verify that their votes mean what they say and
will be tabulated as expected.

Any occasion of governance starts with the creation of an Electorate. Two kinds
exist currently that represent committees and stakeholders (Stakeholder support
is in review). The electorate may deal with many questions governing many
things, so the electorate has to exist before any questions can be posed.
exist currently that represent committees (elected or appointed) and
stakeholders (Stakeholder support is in review). The electorate may deal with
many questions governing many things, so the electorate has to exist before any
questions can be posed.

The next piece to be created is an ElectionManager. (A Contract Governor, is a
particular example, discussed below). An ElectionManager is tied to a particular
The next piece to be created is an ElectionManager. (Contract Governor, one
implementation, is discussed below). An ElectionManager is tied to a particular
Electorate. It supports creation of questions, can manage what happens with the
results, and may limit the kinds of questions it can handle. The ElectionManager
is also responsible for specifying which VoteCounter will be used with any
particular question. Different VoteCounters will handle elections with two
positions or more, with plurality voting, single-transferable-vote, or
instant-runoff-voting.
positions or more. The architecture supports vote counters for
[majority decisions](https://en.wikipedia.org/wiki/Majority_rule),
[approval voting](https://en.wikipedia.org/wiki/Approval_voting), and
[proportional representation](https://en.wikipedia.org/wiki/Proportional_representation),
as well as [quadratic](https://en.wikipedia.org/wiki/Quadratic_voting),
[instant runoff](https://en.wikipedia.org/wiki/Category:Instant-runoff_voting),
and more esoteric approaches.

When a question is posed, it is only with respect to a particular Electorate,
(which identifies a collection of eligible voters) and a particular vote
Expand Down Expand Up @@ -66,7 +72,7 @@ voterHandle.

We want some contracts to be able to make it visible that their internal
parameters are controlled by a public process, and allow observers to see who
has control, and how those changes can happen. To do so, the contract would
has control, and how those changes can happen. To do so, the contract will
use a ParamManager to hold its mutable state. ParamManager has facets for
accessing the param values and for setting them. The governed contract would use
the access facet internally, and make that visible to anyone who should be able
Expand Down Expand Up @@ -115,21 +121,49 @@ the governed contract's publicFacet, creatorFacet, instance, and
### ParamManager

`ContractGovernor` expects to work with contracts that use `ParamManager` to
manage their parameters. `buildParamManager()` is designed to be called within
the managed contract so that internal access to the parameter values is
manage their parameters. `makeParamManagerBuilder()` is designed to be called
within the managed contract so that internal access to the parameter values is
synchronous. A separate facet allows visible management of changes to the
parameter values.

`buildParamManager()` takes a list of parameter descriptions as its argument.
Descriptions give `{ name, type, value }` for each parameter. The parameter
values are retrieved by name. A separate facet of the paramManager allows the
holder to call `updateFoo()` to change the value. ContractGovernor wraps that
facet up so that usage can be monitored.

The `type` part of the parameter description is a string. Current supported
values are
`{ AMOUNT, BRAND, INSTANCE, INSTALLATION, NAT, RATIO, STRING, UNKNOWN }`.
The list can be extended as we find more types that contracts want to manage.
`makeParamManagerBuilder(zoe)` makes a builder for the ParamManager. The
parameters that will be managed are specified by a sequence of calls to the
builder, each describing one parameter. For instance, such a squence might look
like this:

``` javascript
return makeParamManagerBuilder()
.addNat(CHARGING_PERIOD_KEY, loanParams.chargingPeriod)
.addNat(RECORDING_PERIOD_KEY, loanParams.recordingPeriod)
.addRatio(INITIAL_MARGIN_KEY, rates.initialMargin)
.addRatio(LIQUIDATION_MARGIN_KEY, rates.liquidationMargin)
.addRatio(INTEREST_RATE_KEY, rates.interestRate)
.addRatio(LOAN_FEE_KEY, rates.loanFee)
.build();
```

Each `addType()` call returns the builder, so the next call can continue the
call cascade. At the end, `.build()` is called. One of the calls
`addInvitation()`, is `async`, so it can't be cascaded.

``` javascript
const paramManagerBuilder = makeParamManagerBuilder(zoe)
.addBrand('Currency', drachmaBrand)
.addAmount('Amt', drachmaAmount);
// addInvitation is async, so it can't be part of the cascade.
await paramManagerBuilder.addInvitation('Invite', invitation);
const paramManager = paramManagerBuilder.build();
```

The parameter values are retrieved by name. A separate facet of the paramManager
allows the holder to call `updateFoo()` to change the value. ContractGovernor
wraps that facet up so that usage can be monitored.

Current supported methods for adding parameters include 'addAmount', 'addBrand',
'addInstance', 'addInstallation', 'addInvitation', 'addNat', 'addRatio',
'addString', and 'addUnknown'. The list can be extended as we find more types
that contracts want to manage. (If you find yourself using 'addUnknown', let us
know, as that's a sign that we should support a new type).

There's a contractHelper for the vast majority of expected clients that will
have a single set of parameters to manage. A contract only has to define the
Expand Down
5 changes: 4 additions & 1 deletion packages/governance/src/contractGovernor.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@
import { E } from '@agoric/eventual-send';
import { Far } from '@agoric/marshal';

import { setupGovernance, validateParamChangeQuestion } from './governParam.js';
import {
setupGovernance,
validateParamChangeQuestion,
} from './paramGovernance/governParam.js';

const { details: X } = assert;

Expand Down
33 changes: 22 additions & 11 deletions packages/governance/src/contractHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
import { Far } from '@agoric/marshal';
import { sameStructure } from '@agoric/same-structure';

import { buildParamManager } from './paramManager.js';

const { details: X, quote: q } = assert;

/**
Expand All @@ -21,29 +19,38 @@ const { details: X, quote: q } = assert;
*
* @type {HandleParamGovernance}
*/
const handleParamGovernance = (zcf, governedParamsTemplate) => {
const handleParamGovernance = (zcf, paramManager) => {
const terms = zcf.getTerms();
/** @type {ParamDescriptions} */
const governedParams = terms.main;
const { electionManager } = terms;

assert(
sameStructure(governedParams, governedParamsTemplate),
X`Terms must include ${q(governedParamsTemplate)}, but were ${q(
sameStructure(governedParams, paramManager.getParams()),
X`Terms must include ${q(paramManager.getParams())}, but were ${q(
governedParams,
)}`,
);
const paramManager = buildParamManager(governedParams);

const typedAccessors = {
getAmount: paramManager.getAmount,
getBrand: paramManager.getBrand,
getInstance: paramManager.getInstance,
getInstallation: paramManager.getInstallation,
getInvitationAmount: paramManager.getInvitationAmount,
getNat: paramManager.getNat,
getRatio: paramManager.getRatio,
getString: paramManager.getString,
getUnknown: paramManager.getUnknown,
};

const wrapPublicFacet = (originalPublicFacet = {}) => {
return Far('publicFacet', {
...originalPublicFacet,
getSubscription: () => paramManager.getSubscription(),
getContractGovernor: () => electionManager,
getGovernedParams: () => {
return paramManager.getParams();
},
getParamValue: name => paramManager.getParam(name).value,
getGovernedParams: () => paramManager.getParams(),
...typedAccessors,
});
};

Expand All @@ -56,19 +63,23 @@ const handleParamGovernance = (zcf, governedParamsTemplate) => {

const wrapCreatorFacet = (originalCreatorFacet = Far('creatorFacet', {})) => {
const limitedCreatorFacet = makeLimitedCreatorFacet(originalCreatorFacet);

// exclusively for contractGovernor, which only reveals limitedCreatorFacet
return Far('creatorFacet', {
getParamMgrRetriever: () => {
return Far('paramRetriever', { get: () => paramManager });
},
getInvitation: name => paramManager.getInternalParamValue(name),
getLimitedCreatorFacet: () => limitedCreatorFacet,
});
};

return harden({
wrapPublicFacet,
wrapCreatorFacet,
getParamValue: name => paramManager.getParam(name).value,
...typedAccessors,
});
};
harden(handleParamGovernance);

export { handleParamGovernance };
10 changes: 7 additions & 3 deletions packages/governance/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,16 @@ export {
export { handleParamGovernance } from './contractHelper.js';

export {
assertBallotConcernsQuestion,
makeParamChangePositions,
setupGovernance,
validateParamChangeQuestion,
assertBallotConcernsQuestion,
} from './governParam.js';
} from './paramGovernance/governParam.js';

export { ParamType, buildParamManager, assertType } from './paramManager.js';
export {
ParamType,
makeParamManagerBuilder,
} from './paramGovernance/paramManager.js';

export {
assertContractGovernance,
Expand Down
64 changes: 64 additions & 0 deletions packages/governance/src/paramGovernance/assertions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// @ts-check

import { isRemotable } from '@agoric/marshal';
import { assertIsRatio } from '@agoric/zoe/src/contractSupport/ratio.js';

const { details: X } = assert;

const makeLooksLikeBrand = name => {
return brand => {
assert(
// @ts-ignore value is undifferentiated to this point
isRemotable(brand),
X`value for ${name} must be a brand, was ${brand}`,
);
};
};
harden(makeLooksLikeBrand);

const makeAssertInstallation = name => {
return installation => {
// TODO(3344): add a better assertion once Zoe validates installations
assert(
typeof installation === 'object' &&
Object.getOwnPropertyNames(installation).length === 1,
X`value for ${name} must be an Installation, was ${installation}`,
);
};
};
harden(makeAssertInstallation);

const makeAssertInstance = name => {
return instance => {
// TODO(3344): add a better assertion once Zoe validates instances
assert(
typeof instance === 'object' &&
Object.getOwnPropertyNames(instance).length === 0,
X`value for ${name} must be an Instance, was ${instance}`,
);
};
};
harden(makeAssertInstance);

const makeAssertBrandedRatio = (name, modelRatio) => {
return ratio => {
assertIsRatio(ratio);
assert(
ratio.numerator.brand === modelRatio.numerator.brand,
X`Numerator brand for ${name} must be ${modelRatio.numerator.brand}`,
);
assert(
ratio.denominator.brand === modelRatio.denominator.brand,
X`Denominator brand for ${name} must be ${modelRatio.denominator.brand}`,
);
return true;
};
};
harden(makeAssertBrandedRatio);

export {
makeLooksLikeBrand,
makeAssertInstallation,
makeAssertInstance,
makeAssertBrandedRatio,
};
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,7 @@ import {
QuorumRule,
ElectionType,
looksLikeQuestionSpec,
} from './question.js';
import { assertType } from './paramManager.js';
} from '../question.js';

const { details: X } = assert;

Expand Down Expand Up @@ -76,8 +75,6 @@ const setupGovernance = async (
) => {
const paramMgr = E(paramManagerRetriever).get(paramSpec);
const paramName = paramSpec.parameterName;
const param = await E(paramMgr).getParam(paramName);
assertType(param.type, proposedValue, paramName);
const outcomeOfUpdateP = makePromiseKit();

const { positive, negative } = makeParamChangePositions(
Expand Down
62 changes: 62 additions & 0 deletions packages/governance/src/paramGovernance/paramMakers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
// ts-check

import { ParamType } from './paramManager.js';

const makeGovernedNat = value => {
return harden({ type: ParamType.NAT, value });
};

const makeGovernedAmount = value => {
return harden({ type: ParamType.AMOUNT, value });
};

const makeGovernedRatio = value => {
return harden({ type: ParamType.RATIO, value });
};

const makeGovernedBrand = value => {
return harden({ type: ParamType.BRAND, value });
};

const makeGovernedInstance = value => {
return harden({ type: ParamType.INSTANCE, value });
};

const makeGovernedInstallation = value => {
return harden({ type: ParamType.INSTALLATION, value });
};

// value is an invitation amount, not an invitation
const makeGovernedInvitation = value => {
return harden({ type: ParamType.INVITATION, value });
};

const makeGovernedString = value => {
return harden({ type: ParamType.STRING, value });
};

const makeGovernedUnknown = value => {
return harden({ type: ParamType.UNKNOWN, value });
};

harden(makeGovernedAmount);
harden(makeGovernedBrand);
harden(makeGovernedInstallation);
harden(makeGovernedInstance);
harden(makeGovernedInvitation);
harden(makeGovernedNat);
harden(makeGovernedRatio);
harden(makeGovernedString);
harden(makeGovernedUnknown);

export {
makeGovernedAmount,
makeGovernedBrand,
makeGovernedInstallation,
makeGovernedInstance,
makeGovernedInvitation,
makeGovernedNat,
makeGovernedRatio,
makeGovernedString,
makeGovernedUnknown,
};
Loading

0 comments on commit 159596b

Please sign in to comment.