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

Conversation

Chris-Hibbert
Copy link
Contributor

@Chris-Hibbert Chris-Hibbert commented Sep 2, 2022

closes: #6060

Description

Convert Governance contracts to use method schemas

Can be reviewed as separate commits:

commit 0: basic governance types
commit 1: committee as heapFarClass
commit 2: binaryVoteCounter and questionDetails as heapFarClasses

Security Considerations

This uses method schemas to enforce types in the APIs.

Documentation Considerations

No changes in behavior.

Testing Considerations

Existing tests pass. I didn't add tests for out-of-range parameters.

@Chris-Hibbert Chris-Hibbert added the Governance Governance label Sep 2, 2022
@Chris-Hibbert Chris-Hibbert self-assigned this Sep 2, 2022
@Chris-Hibbert Chris-Hibbert marked this pull request as draft September 2, 2022 00:19
export const NoParamChangesPositionShape = harden({
noChange: M.arrayOf(M.string()),
});
export const ParamChangesPositionsShape = [
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
export const ParamChangesPositionsShape = [
export const ParamChangesPositionsShape = harden([

and several other places

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

'offer_filter',
);

export const TimerShape = M.handle('timer');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a remotable, the most that the pattern system can enforce is that it is a remotable. The string argument is only for diagnostic purposes. So the following does all the enforcement you have in mind, but by itself fails to provide the diagnostic info that you're expecting a handle.

Suggested change
export const TimerShape = M.handle('timer');
export const TimerShape = M.remotable('timer');

you could just indicate that in the name manually

Suggested change
export const TimerShape = M.handle('timer');
export const TimerShape = M.remotable('timerHandle');

or, if you think it's worth it, create an abstraction that just does the name mangling

Suggested change
export const TimerShape = M.handle('timer');
const makeHandleShape = name => M.remotable(`${name}Handle`);
//...
export const TimerShape = makeHandleShape('timer');

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cool! thanks.

@erights erights force-pushed the markm-heap-far-classes branch 2 times, most recently from 54815db to 3c0bcd7 Compare September 3, 2022 05:00
@Chris-Hibbert Chris-Hibbert force-pushed the 6060-governanceSchemas branch 2 times, most recently from ea40db9 to 0e12651 Compare September 3, 2022 21:39
@erights erights force-pushed the markm-heap-far-classes branch 2 times, most recently from f0d2aa5 to a2cff79 Compare September 4, 2022 01:02
Base automatically changed from markm-heap-far-classes to master September 4, 2022 06:10
@Chris-Hibbert Chris-Hibbert changed the title chore: basic types for governance refactor: method schemas in Governance contracts Sep 5, 2022
@Chris-Hibbert Chris-Hibbert marked this pull request as ready for review September 5, 2022 19:04
Copy link
Member

@erights erights left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review going well but not done yet.

packages/governance/src/binaryVoteCounter.js Outdated Show resolved Hide resolved
packages/governance/src/binaryVoteCounter.js Outdated Show resolved Hide resolved
packages/governance/src/typeGuards.js Outdated Show resolved Hide resolved
packages/governance/src/typeGuards.js Outdated Show resolved Hide resolved
@@ -27,7 +28,7 @@ const validateQuestionDetails = async (zoe, electorate, details) => {
counterInstance,
issue: { contract: governedInstance },
} = details;
validateParamChangeQuestion(details);
fit(details, ParamChangesQuestionDetailsShape);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

;)

packages/governance/src/typeGuards.js Outdated Show resolved Hide resolved
packages/governance/src/noActionElectorate.js Outdated Show resolved Hide resolved
packages/governance/src/typeGuards.js Outdated Show resolved Hide resolved
Copy link
Member

@erights erights left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

});

return { publicFacet, creatorFacet };
return makeCommitteeKit();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another one where the kit is instantiated only once, so the objects in the kit are singletons. You should just use makeHeapFarInstance on each.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Comment on lines 123 to 125
getPoserInvitation() {
return getPoserInvitation(zcf, async (voteCounter, questionSpec) => {
const quorumThreshold = quorumRule => {
Copy link
Member

@erights erights Sep 6, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT, the body of the argument function here is identical to the body of the addQuestion method below. Although the switch to inline concise methods admittedly make sharing this function harder, remember that you can send messages to self.

Suggested change
getPoserInvitation() {
return getPoserInvitation(zcf, async (voteCounter, questionSpec) => {
const quorumThreshold = quorumRule => {
getPoserInvitation() {
const { facet: { creatorFacet } } = this;
return getPoserInvitation(zcf, async (voteCounter, questionSpec) =>
creatorFacet.addQuestion(voteCounter, questionSpec),
);
},

The suggestion above is if you stick with the defineHeapFarClassKit. If you switch to multiple makeHeapFarInstances and call the lexical variable creatorFacet, then the thisful line disappears and the rest of the suggestion still applies.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. I should have noticed the duplication and looked for an appropriate way to get the sharing back.

Comment on lines +71 to +74
// XXX It would be nice to enforce this using parameterized types, but that
// seems to only enforce type constraints, (i.e. the tieOutcome will be the
// same type as any of the positions) unless you can provide the concrete
// value at pattern creation time.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. No change suggested.

@@ -43,124 +38,9 @@ const QuorumRule = /** @type {const} */ ({
ALL: 'all',
});

/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re the deletions from here (line 46) through line 196:

OMG that's a lot of manual input validation code deleted! Awesome!

It strikes me that this might serve as one of the extremes for our benchmarking, comparing manual input validation to pattern-based. Attn @gibson042 @warner

Comment on lines 162 to 145
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: InstanceHandleShape,
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAICT, the parts in common with SimpleQuestionSpecShape are identical. If this is an intentional part of the design, then

Suggested change
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: InstanceHandleShape,
});
export const SimpleQuestionDetailsShape = harden({
...SimpleQuestionSpecShape,
questionHandle: makeHandleShape('Question'),
counterInstance: InstanceHandleShape,
});

Wherever interface extension is an intentional design, this is a good way to express it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks. I was looking for that, and didn't find it.

Comment on lines +199 to +179
// 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,
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting the question inline. Would be good for anything in common to be pulled out of the expensive M.or. If you pull these two common fields out of the branches, then you should also rename the branches to not imply that they're meaningful by themselves, and stop exporting them. For three of these, they are only used here, so no problem. But ParamChangesQuestionDetailsShape is used elsewhere. Assuming that this is dealt with somehow, we could rewrite the factored pattern as

Suggested change
// 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 QuestionDetailsShape = M.split({
questionHandle: QuestionHandleShape, // making the handle shape should already be factored out.
counterInstance: InstanceHandleShape,
},
M.or(
ParamChangesQuestionPartialDetailsShape,
ApiInvocationQuestionPartialDetailsShape,
OfferFilterQuestionPartialDetailsShape,
SimpleQuestionPartialDetailsShape,
),
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the explanation. That's a helpful alternative.

Now that I see how ParamChangesQuestionDetailsShape is used, I wouldn't be surprised to find other uses of the other QuestionDetailsShapes. I think I'll leave it as is.

split up more makeHeapFarInstances
clean up some interface shapes.
reduce duplication between farInstance methods
@mergify mergify bot merged commit d6e382a into master Sep 7, 2022
@mergify mergify bot deleted the 6060-governanceSchemas branch September 7, 2022 18:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
automerge:squash Automatically squash merge Governance Governance
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Utilize method schemas in Governance contracts
2 participants