Skip to content

Commit

Permalink
refactor: turn committee into a heapFarClass
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris-Hibbert committed Sep 3, 2022
1 parent 9c60883 commit 0e12651
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 67 deletions.
136 changes: 85 additions & 51 deletions packages/governance/src/committee.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// @ts-check

import { makeStoredPublishKit } from '@agoric/notifier';
import { makeStore } from '@agoric/store';
import { defineHeapFarClassKit, initEmpty, makeStore } from '@agoric/store';
import { natSafeMath } from '@agoric/zoe/src/contractSupport/index.js';
import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';
Expand All @@ -14,9 +14,7 @@ import {
startCounter,
} from './electorateTools.js';
import { QuorumRule } from './question.js';
import { defineHeapFarClass } from '../../store/src/patterns/interface-tools';
import { CommitteeAdminI, CommitteeIKit } from './typeGuards';
import { initEmpty } from '../../store/src';
import { CommitteeIKit } from './typeGuards.js';

const { ceilDivide } = natSafeMath;

Expand Down Expand Up @@ -93,58 +91,94 @@ const start = (zcf, privateArgs) => {
[...Array(committeeSize).keys()].map(makeCommitteeVoterInvitation),
);

/** @type {AddQuestion} */
const addQuestion = async (voteCounter, questionSpec) => {
const quorumThreshold = quorumRule => {
switch (quorumRule) {
case QuorumRule.MAJORITY:
return ceilDivide(committeeSize, 2);
case QuorumRule.ALL:
return committeeSize;
case QuorumRule.NO_QUORUM:
return 0;
default:
throw Error(`${quorumRule} is not a recognized quorum rule`);
}
};

return startCounter(
zcf,
questionSpec,
quorumThreshold(questionSpec.quorumRule),
voteCounter,
allQuestions,
questionsPublisher,
);
};

/** @type {CommitteeElectoratePublic} */
const publicFacet = {
getQuestionSubscriber: () => questionsSubscriber,
getOpenQuestions: () => getOpenQuestions(allQuestions),
getName: () => committeeName,
getInstance: zcf.getInstance,
getQuestion: handleP => getQuestion(handleP, allQuestions),
};

// /** @ type {CommitteeElectorateCreatorFacet} */
const adminFacet = {
getPoserInvitation: () => getPoserInvitation(zcf, addQuestion),
addQuestion,
getVoterInvitations: () => invitations,
getQuestionSubscriber: () => questionsSubscriber,
getPublicFacet: () => publicFacet,
};

const makeCommitteeFacets = defineHeapFarClass(
// TODO(CTH) a Kit!
// const makeCommitteeFacets = defineHeapFarClassKit(
const makeCommitteeFacets = defineHeapFarClassKit(
'Committee Facets',
CommitteeIKit,
initEmpty,
{ adminFacet, publicFacet },
{
creatorFacet: {
getPoserInvitation() {
return getPoserInvitation(zcf, async (voteCounter, questionSpec) => {
const quorumThreshold = quorumRule => {
switch (quorumRule) {
case QuorumRule.MAJORITY:
return ceilDivide(committeeSize, 2);
case QuorumRule.ALL:
return committeeSize;
case QuorumRule.NO_QUORUM:
return 0;
default:
throw Error(`${quorumRule} is not a recognized quorum rule`);
}
};

return startCounter(
zcf,
questionSpec,
quorumThreshold(questionSpec.quorumRule),
voteCounter,
allQuestions,
questionsPublisher,
);
});
},
/** @type {AddQuestion} */
async addQuestion(voteCounter, questionSpec) {
const quorumThreshold = quorumRule => {
switch (quorumRule) {
case QuorumRule.MAJORITY:
return ceilDivide(committeeSize, 2);
case QuorumRule.ALL:
return committeeSize;
case QuorumRule.NO_QUORUM:
return 0;
default:
throw Error(`${quorumRule} is not a recognized quorum rule`);
}
};

return startCounter(
zcf,
questionSpec,
quorumThreshold(questionSpec.quorumRule),
voteCounter,
allQuestions,
questionsPublisher,
);
},
getVoterInvitations() {
return invitations;
},
getQuestionSubscriber() {
return questionsSubscriber;
},

getPublicFacet() {
return this.facets.publicFacet;
},
},

publicFacet: {
getQuestionSubscriber() {
return questionsSubscriber;
},
getOpenQuestions() {
return getOpenQuestions(allQuestions);
},
getName() {
return committeeName;
},
getInstance() {
return zcf.getInstance();
},
getQuestion(handleP) {
return getQuestion(handleP, allQuestions);
},
},
},
);

// @ts-expect-error How to type farClasses?
return makeCommitteeFacets();
};

Expand Down
4 changes: 2 additions & 2 deletions packages/governance/src/contractGovernance/governParam.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
ElectionType,
QuorumRule,
} from '../question.js';
import { ParamChangesQuestionSpecShape } from '../typeGuards.js';
import { ParamChangesQuestionDetailsShape } from '../typeGuards.js';

const { details: X } = assert;

Expand Down Expand Up @@ -43,7 +43,7 @@ const makeParamChangePositions = changes => {
* @param {QuestionSpec<ParamChangeIssue<unknown>>} questionSpec
*/
const assertBallotConcernsParam = (paramSpec, questionSpec) => {
fit(questionSpec, ParamChangesQuestionSpecShape);
fit(questionSpec, ParamChangesQuestionDetailsShape);

const { parameterName, paramPath } = paramSpec;
const { issue } = questionSpec;
Expand Down
4 changes: 2 additions & 2 deletions packages/governance/src/contractGovernor.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
} from './contractGovernance/governParam.js';
import { setupApiGovernance } from './contractGovernance/governApi.js';
import { setupFilterGovernance } from './contractGovernance/governFilter.js';
import { ParamChangesQuestionSpecShape } from './typeGuards.js';
import { ParamChangesQuestionDetailsShape } from './typeGuards.js';

const { details: X } = assert;

Expand All @@ -28,7 +28,7 @@ const validateQuestionDetails = async (zoe, electorate, details) => {
counterInstance,
issue: { contract: governedInstance },
} = details;
fit(details, ParamChangesQuestionSpecShape);
fit(details, ParamChangesQuestionDetailsShape);

const governorInstance = await E.get(E(zoe).getTerms(governedInstance))
.electionManager;
Expand Down
82 changes: 70 additions & 12 deletions packages/governance/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ export const OfferFilterQuestionSpecShape = harden({
quorumRule: QuorumRuleShape,
tieOutcome: NoOfferFilterPositionShape,
});
export const OfferFilterQuestionDetailsShape = harden({
method: ChoiceMethodShape,
issue: OfferFilterIssueShape,
positions: OfferFilterPositionsShape,
electionType: 'offer_filter',
maxChoices: M.eq(1),
closingRule: ClosingRuleShape,
quorumRule: QuorumRuleShape,
tieOutcome: NoOfferFilterPositionShape,
questionHandle: makeHandleShape('Question'),
counterInstance: InstanceShape,
});

// keys are parameter names, values are proposed values
export const ParamChangesSpecShape = M.recordOf(M.string(), M.any());
Expand Down Expand Up @@ -79,6 +91,19 @@ export const ParamChangesQuestionSpecShape = harden({
tieOutcome: NoParamChangesPositionShape,
});

export const ParamChangesQuestionDetailsShape = harden({
method: 'unranked',
issue: ParamChangesIssueShape,
positions: ParamChangesPositionsShape,
electionType: 'param_change',
maxChoices: M.eq(1),
closingRule: ClosingRuleShape,
quorumRule: 'majority',
tieOutcome: NoParamChangesPositionShape,
questionHandle: makeHandleShape('Question'),
counterInstance: InstanceShape,
});

const ApiInvocationSpecShape = harden({
apiMethodName: M.string(),
methodArgs: M.arrayOf(M.string()),
Expand All @@ -101,6 +126,18 @@ export const ApiInvocationQuestionSpecShape = harden({
quorumRule: QuorumRuleShape,
tieOutcome: NoApiInvocationPositionShape,
});
export const ApiInvocationQuestionDetailsShape = harden({
method: 'unranked',
issue: ApiInvocationSpecShape,
positions: ApiInvocationPositionsShape,
electionType: 'api_invocation',
maxChoices: M.eq(1),
closingRule: ClosingRuleShape,
quorumRule: QuorumRuleShape,
tieOutcome: NoApiInvocationPositionShape,
questionHandle: makeHandleShape('Question'),
counterInstance: InstanceShape,
});

const SimpleSpecShape = harden({
text: M.string(),
Expand All @@ -122,6 +159,18 @@ export const SimpleQuestionSpecShape = harden({
quorumRule: QuorumRuleShape,
tieOutcome: NoSimplePositionShape,
});
export const SimpleQuestionDetailsShape = harden({
method: ChoiceMethodShape,
issue: SimpleIssueShape,
positions: SimplePositionsShape,
electionType: M.or('election', 'survey'),
maxChoices: M.gte(1),
closingRule: ClosingRuleShape,
quorumRule: QuorumRuleShape,
tieOutcome: NoSimplePositionShape,
questionHandle: makeHandleShape('Question'),
counterInstance: InstanceShape,
});

export const SimplePositionsShapeA = [
harden({ text: 'yes' }),
Expand Down Expand Up @@ -154,30 +203,38 @@ export const PositionShape = M.or(
export const QuestionHandleShape = makeHandleShape('question');

// TODO(hibbert): add details; move to a more appropriate location
export const SubscriberShape = M.remotable();
export const InvitationShape = M.remotable();
export const SubscriberShape = M.remotable('Subscriber');
export const InvitationShape = M.remotable('Invitation');

// XXX I want to add questionHandle and counterInstance to
// ParamChangesQuestionSpecShape. I don't see any alternative to adding the
// methods to each member separately
export const QuestionDetailsShape = M.or(
ParamChangesQuestionDetailsShape,
ApiInvocationQuestionDetailsShape,
OfferFilterQuestionDetailsShape,
SimpleQuestionDetailsShape,
);

export const CommitteePublicI = M.interface('Committee PublicFacet', {
getQuestionSubscriber: M.call().returns(SubscriberShape),
getOpenQuestions: M.call().returns(M.arrayOf(QuestionSpecShape)),
getOpenQuestions: M.call().returns(M.promise()),
getName: M.call().returns(M.string()),
getInstance: M.call().returns(InstanceShape),
getQuestion: M.call(QuestionHandleShape).returns(QuestionSpecShape),
getQuestion: M.call(QuestionHandleShape).returns(M.promise()),
});

export const CommitteeAdminI = M.interface('Committee AdminFacet', {
getPoserInvitation: M.call().returns(InvitationShape),
addQuestion: M.call(InstanceShape, QuestionSpecShape).return(InstanceShape),
getVoterInvitations: M.call().returns(InvitationShape),
getPoserInvitation: M.call().returns(M.promise()),
addQuestion: M.call(InstanceShape, QuestionSpecShape).returns(M.promise()),
getVoterInvitations: M.call().returns(M.arrayOf(M.promise())),
getQuestionSubscriber: M.call().returns(SubscriberShape),
getPublicFacet: M.call().returns(CommitteePublicI),
});

export const CommitteeIKit = harden({ CommitteePublicI, CommitteeAdminI });

export const QuestionDetailsShape = M.and(QuestionSpecShape, {
questionHandle: M.remotable(),
counterInstance: InstanceShape,
export const CommitteeIKit = harden({
publicFacet: CommitteePublicI,
creatorFacet: CommitteeAdminI,
});

export const QuestionStatsShape = harden({
Expand Down Expand Up @@ -217,4 +274,5 @@ export const BinaryVoteCounterCloseI = M.interface(
export const BinaryVoteCounterIKit = harden({
BinaryVoteCounterPublicI,
BinaryVoteCounterAdminI,
BinaryVoteCounterCloseI,
});

0 comments on commit 0e12651

Please sign in to comment.