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

refactor: method schemas in Governance contracts #6114

Merged
merged 7 commits into from
Sep 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/governance/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
"@agoric/nat": "^4.1.0",
"@agoric/notifier": "^0.4.0",
"@agoric/store": "^0.7.2",
"@agoric/swingset-vat": "^0.28.0",
"@agoric/vat-data": "^0.3.1",
"@agoric/vats": "^0.10.0",
"@agoric/zoe": "^0.24.0",
Expand Down
116 changes: 70 additions & 46 deletions packages/governance/src/binaryVoteCounter.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@

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

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

const { details: X } = assert;

Expand Down Expand Up @@ -77,24 +82,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);
return completedBallot;
};

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

Expand Down Expand Up @@ -143,29 +130,70 @@ const makeBinaryVoteCounter = (questionSpec, threshold, instance) => {
}
};

const closeVoting = () => {
isOpen = false;
countVotes();
};
const closeFacet = makeHeapFarInstance(
'BinaryVoteCounter close',
BinaryVoteCounterCloseI,
{
closeVoting() {
isOpen = false;
countVotes();
},
},
);

// exposed for testing. In contracts, shouldn't be released.
/** @type {VoteCounterCloseFacet} */
const closeFacet = Far('closeFacet', { closeVoting });
const creatorFacet = makeHeapFarInstance(
'BinaryVoteCounter creator',
BinaryVoteCounterAdminI,
{
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);
return completedBallot;
},
},
);

/** @type {VoteCounterCreatorFacet} */
const creatorFacet = Far('VoteCounter vote Cap', {
submitVote,
});
const publicFacet = makeHeapFarInstance(
'BinaryVoteCounter public',
BinaryVoteCounterPublicI,
{
getQuestion() {
return question;
},
isOpen() {
return isOpen;
},
getOutcome() {
return outcomePromise.promise;
},
getStats() {
return tallyPromise.promise;
},
getDetails() {
return details;
},
getInstance() {
return instance;
},
},
);

/** @type {VoteCounterPublicFacet} */
const publicFacet = Far('preliminaryPublicFacet', {
getQuestion: () => question,
isOpen: () => isOpen,
getOutcome: () => outcomePromise.promise,
getStats: () => tallyPromise.promise,
getDetails: () => details,
return harden({
creatorFacet,
publicFacet,
closeFacet,
});
return { publicFacet, creatorFacet, closeFacet };
};

// The contract wrapper extracts the terms and runs makeBinaryVoteCounter().
Expand All @@ -189,13 +217,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
156 changes: 99 additions & 57 deletions packages/governance/src/committee.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
// @ts-check

import { makeStoredPublishKit } from '@agoric/notifier';
import { makeStore } from '@agoric/store';
import { makeStore, makeHeapFarInstance, M } from '@agoric/store';
import { natSafeMath } from '@agoric/zoe/src/contractSupport/index.js';
import { E } from '@endo/eventual-send';
import { Far } from '@endo/marshal';

import { makeHandle } from '@agoric/zoe/src/makeHandle.js';
import {
Expand All @@ -14,6 +13,12 @@ import {
startCounter,
} from './electorateTools.js';
import { QuorumRule } from './question.js';
import {
QuestionHandleShape,
PositionShape,
ElectorateCreatorI,
ElectoratePublicI,
} from './typeGuards.js';

const { ceilDivide } = natSafeMath;

Expand Down Expand Up @@ -51,31 +56,43 @@ const start = (zcf, privateArgs) => {
const voterHandle = makeHandle('Voter');
seat.exit();

const VoterI = M.interface('voter', {
castBallotFor: M.call(
QuestionHandleShape,
M.arrayOf(PositionShape),
).returns(M.promise()),
});
const InvitationMakerI = M.interface('invitationMaker', {
makeVoteInvitation: M.call(QuestionHandleShape).returns(M.promise()),
});

// CRUCIAL: voteCap carries the ability to cast votes for any voter at
// any weight. It's wrapped here and given to the voter.
//
// Ensure that the voter can't get access to the unwrapped voteCap, and
// has no control over the voteHandle or weight
return harden({
voter: Far(`voter${index}`, {
castBallotFor: (questionHandle, positions) => {
voter: makeHeapFarInstance(`voter${index}`, VoterI, {
castBallotFor(questionHandle, positions) {
const { voteCap } = allQuestions.get(questionHandle);
return E(voteCap).submitVote(voterHandle, positions, 1n);
},
}),
invitationMakers: Far('invitation makers', {
makeVoteInvitation: questionHandle => {
const continuingVoteHandler = (cSeat, offerArgs) => {
assert(offerArgs, 'continuingVoteHandler missing offerArgs');
const { positions } = offerArgs;
cSeat.exit();
const { voteCap } = allQuestions.get(questionHandle);
return E(voteCap).submitVote(voterHandle, positions, 1n);
};

return zcf.makeInvitation(continuingVoteHandler, 'vote');
invitationMakers: makeHeapFarInstance(
'invitation makers',
InvitationMakerI,
{
makeVoteInvitation(questionHandle) {
const continuingVoteHandler = (cSeat, { positions }) => {
cSeat.exit();
const { voteCap } = allQuestions.get(questionHandle);
return E(voteCap).submitVote(voterHandle, positions, 1n);
};

return zcf.makeInvitation(continuingVoteHandler, 'vote');
},
},
}),
),
});
};

Expand All @@ -92,48 +109,73 @@ 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,
);
};
const publicFacet = makeHeapFarInstance(
'Committee publicFacet',
ElectoratePublicI,
{
getQuestionSubscriber() {
return questionsSubscriber;
},
getOpenQuestions() {
return getOpenQuestions(allQuestions);
},
getName() {
return committeeName;
},
getInstance() {
return zcf.getInstance();
},
getQuestion(handleP) {
return getQuestion(handleP, allQuestions);
},
},
);

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

/** @type {CommitteeElectorateCreatorFacet} */
const creatorFacet = Far('adminFacet', {
getPoserInvitation: () => getPoserInvitation(zcf, addQuestion),
addQuestion,
getVoterInvitations: () => invitations,
getQuestionSubscriber: () => questionsSubscriber,
getPublicFacet: () => publicFacet,
});
const creatorFacet = makeHeapFarInstance(
'Committee creatorFacet',
ElectorateCreatorI,
{
getPoserInvitation() {
return getPoserInvitation(zcf, async (voteCounter, questionSpec) =>
creatorFacet.addQuestion(voteCounter, questionSpec),
);
},
/** @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 publicFacet;
},
},
);

return { publicFacet, creatorFacet };
};
Expand Down
Loading