Skip to content

Commit

Permalink
refactor: make binaryVoteCounter and QuestionDetails heapFarClasses
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris-Hibbert committed Sep 4, 2022
1 parent 0e12651 commit 60a1188
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 69 deletions.
112 changes: 63 additions & 49 deletions packages/governance/src/binaryVoteCounter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@

import { Far } from '@endo/marshal';
import { makePromiseKit } from '@endo/promise-kit';
import { keyEQ, makeStore } from '@agoric/store';
import {
defineHeapFarClassKit,
initEmpty,
keyEQ,
makeStore,
} from '@agoric/store';

import {
ChoiceMethod,
buildUnrankedQuestion,
positionIncluded,
ChoiceMethod,
coerceQuestionSpec,
positionIncluded,
} from './question.js';
import { scheduleClose } from './closingRule.js';
import { BinaryVoteCounterIKit } from './typeGuards.js';

const { details: X } = assert;

Expand Down Expand Up @@ -77,23 +83,6 @@ const makeBinaryVoteCounter = (questionSpec, threshold, instance) => {
/** @type {Store<Handle<'Voter'>,RecordedBallot> } */
const allBallots = makeStore('voterHandle');

/** @type {SubmitVote} */
const submitVote = (voterHandle, chosenPositions, shares = 1n) => {
assert(chosenPositions.length === 1, 'only 1 position allowed');
const [position] = chosenPositions;
assert(
positionIncluded(positions, position),
X`The specified choice is not a legal position: ${position}.`,
);

// CRUCIAL: If the voter cast a valid ballot, we'll record it, but we need
// to make sure that each voter's vote is recorded only once.
const completedBallot = harden({ chosen: position, shares });
allBallots.has(voterHandle)
? allBallots.set(voterHandle, completedBallot)
: allBallots.init(voterHandle, completedBallot);
};

const countVotes = () => {
assert(!isOpen, X`can't count votes while the election is open`);

Expand Down Expand Up @@ -142,29 +131,58 @@ const makeBinaryVoteCounter = (questionSpec, threshold, instance) => {
}
};

const closeVoting = () => {
isOpen = false;
countVotes();
};

// exposed for testing. In contracts, shouldn't be released.
/** @type {VoteCounterCloseFacet} */
const closeFacet = Far('closeFacet', { closeVoting });

/** @type {VoteCounterCreatorFacet} */
const creatorFacet = Far('VoteCounter vote Cap', {
submitVote,
});

/** @type {VoteCounterPublicFacet} */
const publicFacet = Far('preliminaryPublicFacet', {
getQuestion: () => question,
isOpen: () => isOpen,
getOutcome: () => outcomePromise.promise,
getStats: () => tallyPromise.promise,
getDetails: () => details,
});
return { publicFacet, creatorFacet, closeFacet };
const makeBinaryVoteCounterKit = defineHeapFarClassKit(
'Binary VoteCounter',
BinaryVoteCounterIKit,
initEmpty,
{
closeFacet: {
closeVoting() {
isOpen = false;
countVotes();
},
},
creatorFacet: {
submitVote(voterHandle, chosenPositions, shares = 1n) {
assert(chosenPositions.length === 1, 'only 1 position allowed');
const [position] = chosenPositions;
assert(
positionIncluded(positions, position),
X`The specified choice is not a legal position: ${position}.`,
);

// CRUCIAL: If the voter cast a valid ballot, we'll record it, but we need
// to make sure that each voter's vote is recorded only once.
const completedBallot = harden({ chosen: position, shares });
allBallots.has(voterHandle)
? allBallots.set(voterHandle, completedBallot)
: allBallots.init(voterHandle, completedBallot);
},
},
publicFacet: {
getQuestion() {
return question;
},
isOpen() {
return isOpen;
},
getOutcome() {
return outcomePromise.promise;
},
getStats() {
return tallyPromise.promise;
},
getDetails() {
return details;
},
getInstance() {
return instance;
},
},
},
);
// @ts-expect-error teach typescript about far classes?
return makeBinaryVoteCounterKit();
};

// The contract wrapper extracts the terms and runs makeBinaryVoteCounter().
Expand All @@ -188,13 +206,9 @@ const start = zcf => {
zcf.getInstance(),
);

scheduleClose(questionSpec.closingRule, closeFacet.closeVoting);
scheduleClose(questionSpec.closingRule, () => closeFacet.closeVoting());

const publicFacetWithGetInstance = Far('publicFacet', {
...publicFacet,
getInstance: zcf.getInstance,
});
return { publicFacet: publicFacetWithGetInstance, creatorFacet };
return { publicFacet, creatorFacet };
};

harden(start);
Expand Down
4 changes: 2 additions & 2 deletions packages/governance/src/committee.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ const start = (zcf, privateArgs) => {
[...Array(committeeSize).keys()].map(makeCommitteeVoterInvitation),
);

const makeCommitteeFacets = defineHeapFarClassKit(
const makeCommitteeKit = defineHeapFarClassKit(
'Committee Facets',
CommitteeIKit,
initEmpty,
Expand Down Expand Up @@ -179,7 +179,7 @@ const start = (zcf, privateArgs) => {
);

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

harden(start);
Expand Down
27 changes: 14 additions & 13 deletions packages/governance/src/question.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// @ts-check

import { Far } from '@endo/marshal';
import { keyEQ, fit, M } from '@agoric/store';
import { defineHeapFarInstance, fit, keyEQ, M } from '@agoric/store';
import { makeHandle } from '@agoric/zoe/src/makeHandle.js';

import { QuestionSpecShape } from './typeGuards.js';
import { QuestionDetailI, QuestionSpecShape } from './typeGuards.js';

// Topics being voted on are 'Questions'. Before a Question is known to a
// electorate, the parameters can be described with a QuestionSpec. Once the
Expand Down Expand Up @@ -82,17 +81,19 @@ const coerceQuestionSpec = ({
const buildUnrankedQuestion = (questionSpec, counterInstance) => {
const questionHandle = makeHandle('Question');

const getDetails = () =>
harden({
...questionSpec,
questionHandle,
counterInstance,
});

/** @type {Question} */
return Far('question details', {
getVoteCounter: () => counterInstance,
getDetails,
// @ts-expect-error typescript doesn't understand farInstances.
return defineHeapFarInstance('question details', QuestionDetailI, {
getVoteCounter() {
return counterInstance;
},
getDetails() {
return harden({
...questionSpec,
questionHandle,
counterInstance,
});
},
});
};

Expand Down
17 changes: 12 additions & 5 deletions packages/governance/src/typeGuards.js
Original file line number Diff line number Diff line change
Expand Up @@ -243,14 +243,21 @@ export const QuestionStatsShape = harden({
results: M.arrayOf({ position: PositionShape, total: M.nat() }),
});

export const QuestionDetailI = M.interface('Question details', {
getVoteCounter: M.call().returns(InstanceShape),
getDetails: M.call().returns(QuestionDetailsShape),
});

export const BinaryVoteCounterPublicI = M.interface(
'BinaryVoteCounter PublicFacet',
{
getQuestion: M.call().returns(QuestionSpecShape),
// XXX I expected M.call().returns(QuestionDetailI)
getQuestion: M.call().returns(M.remotable()),
isOpen: M.call().returns(M.boolean()),
getOutcome: M.call().returns(M.eref(PositionShape)),
getStats: M.call().returns(QuestionStatsShape),
getDetails: M.call().returns(QuestionDetailsShape),
getInstance: M.call().returns(InstanceShape),
},
);

Expand All @@ -259,7 +266,7 @@ export const BinaryVoteCounterAdminI = M.interface(
'BinaryVoteCounter AdminFacet',
{
submitVote: M.call(VoterHandle, M.arrayOf(PositionShape))
.optional(M.nat)
.optional(M.nat())
.returns(),
},
);
Expand All @@ -272,7 +279,7 @@ export const BinaryVoteCounterCloseI = M.interface(
);

export const BinaryVoteCounterIKit = harden({
BinaryVoteCounterPublicI,
BinaryVoteCounterAdminI,
BinaryVoteCounterCloseI,
publicFacet: BinaryVoteCounterPublicI,
creatorFacet: BinaryVoteCounterAdminI,
closeFacet: BinaryVoteCounterCloseI,
});

0 comments on commit 60a1188

Please sign in to comment.