Skip to content

Commit

Permalink
Registrar 3188 (#3299)
Browse files Browse the repository at this point in the history
* feat: add a committeeRegistrar

update testing imports for issue 768
add minimal types
better fix for test dependencies
change jsconfig.json includes, update packages, change swingset test (#3300)
update committeeRegistrar for relocation of voterFacet
integrate with changes to ballot PR (3233)

* refactor: more legibility

make ballotCounter instance accessible from ballot
add more to terms of ballotCounter (closureRule)
add more to public facet of Registrar
one registrar per contract/vat: details in terms
use zcf.getInstance() to supply instances

In tests
  voters look up their own positions
  voters cast ballots on notification

* refactor: closureRule renamed to closingRule

* refactor: test and repair getOpenQuestions

* chore: more cleanup

* chore: review suggestions

move some dependencies to devDependencies
expect promises for ballots
allow voters to cast directly with registrar

* refactor: await all the promises, not just the last in a chain

add a test for multiple votes by a single electorate
  • Loading branch information
Chris-Hibbert authored Jun 23, 2021
1 parent 7a0bb8a commit 7547357
Show file tree
Hide file tree
Showing 10 changed files with 717 additions and 20 deletions.
8 changes: 7 additions & 1 deletion packages/governance/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,20 @@
"@agoric/eventual-send": "^0.13.17",
"@agoric/marshal": "^0.4.14",
"@agoric/nat": "^4.1.0",
"@agoric/notifier": "^0.3.16",
"@agoric/promise-kit": "^0.2.16",
"@agoric/same-structure": "^0.1.15",
"@agoric/store": "^0.4.17",
"@agoric/zoe": "^0.17.0"
},
"devDependencies": {
"@agoric/install-ses": "^0.5.16",
"@agoric/babel-standalone": "^7.14.3",
"@agoric/bundle-source": "^1.3.9",
"@agoric/swingset-vat": "^0.18.0",
"ava": "^3.12.1",
"esm": "agoric-labs/esm#Agoric-built"
"esm": "agoric-labs/esm#Agoric-built",
"ses": "^0.13.4"
},
"files": [
"src/",
Expand Down
13 changes: 11 additions & 2 deletions packages/governance/src/ballotBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ const buildEqualWeightBallot = (
question,
positions,
maxChoices = 0,
instance,
) => {
const choose = chosenPositions => {
assert(
Expand All @@ -46,21 +47,29 @@ const buildEqualWeightBallot = (
question,
positions,
maxChoices,
instance,
});

return {
getBallotCounter: () => instance,
getDetails,
choose,
};
};

/** @type {BuildBallot} */
const buildBallot = (method, question, positions, maxChoices = 0) => {
const buildBallot = (method, question, positions, maxChoices = 0, instance) => {
assert.typeof(question, 'string');

switch (method) {
case ChoiceMethod.CHOOSE_N:
return buildEqualWeightBallot(method, question, positions, maxChoices);
return buildEqualWeightBallot(
method,
question,
positions,
maxChoices,
instance,
);
case ChoiceMethod.ORDER:
case ChoiceMethod.WEIGHT:
throw Error(`choice method ${ChoiceMethod.WEIGHT} is unimplemented`);
Expand Down
47 changes: 31 additions & 16 deletions packages/governance/src/binaryBallotCounter.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { makeStore } from '@agoric/store';
import { makePromiseKit } from '@agoric/promise-kit';
import { Far } from '@agoric/marshal';

import { E } from '@agoric/eventual-send';
import { ChoiceMethod, buildBallot } from './ballotBuilder';
import { scheduleClose } from './closingRule';

Expand All @@ -29,6 +30,7 @@ const makeBinaryBallotCounter = (
threshold,
tieOutcome = undefined,
closingRule,
instance,
) => {
assert(
positions.length === 2,
Expand All @@ -44,7 +46,13 @@ const makeBinaryBallotCounter = (
);
}

const template = buildBallot(ChoiceMethod.CHOOSE_N, question, positions, 1);
const template = buildBallot(
ChoiceMethod.CHOOSE_N,
question,
positions,
1,
instance,
);
const ballotDetails = template.getDetails();

assert(
Expand All @@ -56,18 +64,20 @@ const makeBinaryBallotCounter = (
const tallyPromise = makePromiseKit();
const allBallots = makeStore('seat');

const recordBallot = (seat, filledBallot, shares = 1n) => {
assert(
filledBallot.question === question,
X`Ballot not for this question ${filledBallot.question} should have been ${question}`,
);
assert(
positions.includes(filledBallot.chosen[0]),
X`The ballot's choice is not a legal position: ${filledBallot.chosen[0]}.`,
);
allBallots.has(seat)
? allBallots.set(seat, makeWeightedBallot(filledBallot, shares))
: allBallots.init(seat, makeWeightedBallot(filledBallot, shares));
const recordBallot = (seat, filledBallotP, shares = 1n) => {
return E.when(filledBallotP, filledBallot => {
assert(
filledBallot.question === question,
X`Ballot not for this question ${filledBallot.question} should have been ${question}`,
);
assert(
positions.includes(filledBallot.chosen[0]),
X`The ballot's choice is not a legal position: ${filledBallot.chosen[0]}.`,
);
allBallots.has(seat)
? allBallots.set(seat, makeWeightedBallot(filledBallot, shares))
: allBallots.init(seat, makeWeightedBallot(filledBallot, shares));
});
};

const countVotes = () => {
Expand Down Expand Up @@ -144,8 +154,7 @@ const makeBinaryBallotCounter = (
getVoterFacet: () => voterFacet,
});

/** @type {BallotCounterPublicFacet} */
const publicFacet = Far('publicFacet', {
const publicFacet = Far('preliminaryPublicFacet', {
...sharedFacet,
getOutcome: () => outcomePromise.promise,
getStats: () => tallyPromise.promise,
Expand Down Expand Up @@ -174,11 +183,17 @@ const start = zcf => {
quorumThreshold,
tieOutcome,
closingRule,
zcf.getInstance(),
);

scheduleClose(closingRule, closeFacet.closeVoting);

return { publicFacet, creatorFacet };
/** @type {BallotCounterPublicFacet} */
const publicFacetWithGetInstance = Far('publicFacet', {
...publicFacet,
getInstance: zcf.getInstance,
});
return { publicFacet: publicFacetWithGetInstance, creatorFacet };
};

harden(start);
Expand Down
92 changes: 92 additions & 0 deletions packages/governance/src/committeeRegistrar.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// @ts-check

import { Far } from '@agoric/marshal';
import { makeNotifierKit } from '@agoric/notifier';
import { E } from '@agoric/eventual-send';
import { makeStore } from '@agoric/store';
import { allComparable } from '@agoric/same-structure';

// Each CommitteeRegistrar represents a particular set of voters. The number of
// voters is visible in the terms.
const start = zcf => {
// Question => { voter, publicFacet }
const allQuestions = makeStore('Question');
const { notifier, updater } = makeNotifierKit();
const invitations = [];

const getOpenQuestions = async () => {
const isOpenPQuestions = allQuestions.keys().map(key => {
const { publicFacet } = allQuestions.get(key);
return [E(publicFacet).isOpen(), key];
});
const isOpenQuestions = await allComparable(harden(isOpenPQuestions));
return isOpenQuestions
.filter(([open, _key]) => open)
.map(([_open, key]) => key);
};

const makeCommitteeVoterInvitation = index => {
const handler = voterSeat => {
return Far(`voter${index}`, {
castBallot: ballotp => {
E.when(ballotp, ballot => {
const { voter } = allQuestions.get(ballot.question);
return E(voter).submitVote(voterSeat, ballot);
});
},
castBallotFor: (question, positions) => {
const { publicFacet: counter, voter } = allQuestions.get(question);
const ballotTemplate = E(counter).getBallotTemplate();
const ballot = E(ballotTemplate).choose(positions);
return E(voter).submitVote(voterSeat, ballot);
},
});
};

return zcf.makeInvitation(handler, `Voter${index}`);
};

const { committeeName, committeeSize } = zcf.getTerms();
for (let i = 0; i < committeeSize; i += 1) {
invitations[i] = makeCommitteeVoterInvitation(i);
}

/** @type {AddQuestion} */
const addQuestion = async (voteCounter, questionDetailsShort) => {
const questionDetails = {
...questionDetailsShort,
registrar: zcf.getInstance(),
};
// facets of the ballot counter. Suppress creatorInvitation and adminFacet.
const { creatorFacet, publicFacet, instance } = await E(
zcf.getZoeService(),
).startInstance(voteCounter, {}, questionDetails);
const facets = { voter: E(creatorFacet).getVoterFacet(), publicFacet };

updater.updateState(questionDetails.question);
allQuestions.init(questionDetails.question, facets);
return { creatorFacet, publicFacet, instance };
};

const creatorFacet = Far('adminFacet', {
addQuestion,
getVoterInvitations: () => invitations,
getQuestionNotifier: () => notifier,
});

const publicFacet = Far('publicFacet', {
getQuestionNotifier: () => notifier,
getOpenQuestions,
getName: () => committeeName,
getInstance: zcf.getInstance,
getDetails: name =>
E(E(allQuestions.get(name).publicFacet).getBallotTemplate()).getDetails(),
getBallot: name =>
E(allQuestions.get(name).publicFacet).getBallotTemplate(),
});

return { publicFacet, creatorFacet };
};

harden(start);
export { start };
38 changes: 37 additions & 1 deletion packages/governance/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,29 @@
* @returns {ParamManagerFull}
*/

/**
* @typedef {Object} QuestionTermsShort
* BallotDetails as provided to the Registrar
* @property {ChoiceMethod} method
* @property {string} question
* @property {string[]} positions
* @property {number} maxChoices
* @property {ClosingRule} closingRule
*/

/**
* @typedef {Object} QuestionTerms
* BallotDetails after the Registrar adds its Instance
* @property {ChoiceMethod} method
* @property {string} question
* @property {string[]} positions
* @property {number} maxChoices
* @property {ClosingRule} closingRule
* @property {Instance} registrar
*/
/**
* @typedef {Object} BallotDetails
* BallotDetails after the Registrar adds its Instance
* @property {ChoiceMethod} method
* @property {string} question
* @property {string[]} positions
Expand Down Expand Up @@ -75,6 +96,7 @@
* @param {string} question
* @param {string[]} positions
* @param {number} maxChoices
* @param {Instance} instance - ballotCounter instance
* @returns {Ballot}
*/

Expand Down Expand Up @@ -126,7 +148,7 @@
/**
* @callback SubmitVote
* @param {Handle<'Voter'>} seat
* @param {CompletedBallot} filledBallot
* @param {ERef<CompletedBallot>} filledBallot
* @param {bigint=} weight
*/

Expand All @@ -146,3 +168,17 @@
* @param {ClosingRule} closingRule
* @param {() => void} closeVoting
*/

/**
* @typedef {Object} AddQuestionReturn
* @property {BallotCounterPublicFacet} publicFacet
* @property {BallotCounterCreatorFacet} creatorFacet
* @property {Instance} instance
*/

/**
* @callback AddQuestion
* @param {Installation} voteCounter
* @param {QuestionTermsShort} questionDetailsShort
* @returns {Promise<AddQuestionReturn>}
*/
Loading

0 comments on commit 7547357

Please sign in to comment.