-
Notifications
You must be signed in to change notification settings - Fork 11.9k
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
Add a governor extension that implements a proposal guardian #5303
base: master
Are you sure you want to change the base?
Conversation
|
Looks good other than some comments. Would it make sense to call it something like |
Compound Governance already has a feature like this where the council is called the |
9bc440d
to
d613cc8
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the rationale to allow a proposer to cancel a proposal at any time?
I think the contract would be simpler if it just focus on allowing the guardian to cancel at any time and otherwise fallback to their original behavior with super
.
// if there is no proposal guardian | ||
// ... only the proposer can cancel | ||
// ... no restriction on when the proposer can cancel | ||
uint256 proposalId = getProposalId(targets, values, calldatas, descriptionHash); | ||
address proposer = proposalProposer(proposalId); | ||
if (caller != proposer) revert GovernorOnlyProposer(caller); | ||
return _cancel(targets, values, calldatas, descriptionHash); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The default behavior in Governor is to only allow the proposer to cancel when the proposal is still pending. I don't think we want to allow proposers to cancel after that.
Also, if we want to keep the default behavior, wouldn't it be better to just?:
// if there is no proposal guardian | |
// ... only the proposer can cancel | |
// ... no restriction on when the proposer can cancel | |
uint256 proposalId = getProposalId(targets, values, calldatas, descriptionHash); | |
address proposer = proposalProposer(proposalId); | |
if (caller != proposer) revert GovernorOnlyProposer(caller); | |
return _cancel(targets, values, calldatas, descriptionHash); | |
return super.cancel(targets, values, calldatas, descriptionHash); |
This would simplify this function imo
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
return super.cancel(targets, values, calldatas, descriptionHash);
is already what we do in the last case ... so what you are proposing would be simplified further by doing
if (guardian == caller) {
// if there is a proposal guardian, and the caller is the proposal guardian
// ... just cancel
return _cancel(targets, values, calldatas, descriptionHash);
} else {
// The caller is not the proposal guardian
// ... apply default behavior
return super.cancel(targets, values, calldatas, descriptionHash);
}
Note that it would change the behavior! What we have right now is:
- if there is no guardian, the proposer can cancel at any point
- if there is a guardian, the guardian can cancel at any point
- if there is a guardian, the proposer can only cancel during the propose phase (before vote starts)
The changes would make it that if there is no guardian, the proposer is still restricted to only canceling during the propose phase.
What behavior do we want ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I shared a proposal down below. My main concern is that we're missing supper in the branches where there's no guardian and when there's a guardian.
For example, in this code, users could override _validateCancel
and call super to 1) add the guardian behavior and 2) extend the canceler permissions:
function _validateCancel(...) internal virtual {
// Current logic
uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
_validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Pending));
if (_msgSender() != proposalProposer(proposalId)) {
revert GovernorOnlyProposer(_msgSender());
}
}
...
function cancel(...) public virtual returns (uint256) {
_validateCancel();
return _cancel(targets, values, calldatas, descriptionHash);
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In this solution, we will not call super for _validateCancel
, which may also skip side effects. I don't see how it's materially better and I'm weary of adding another internal function.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_validateCancel
could be view though
The inspiration for this PR is in #5260 which was then replaced by #5301. There is some context missing but will try to give a TLDR.
|
Co-authored-by: Ernesto García <[email protected]>
Co-authored-by: Ernesto García <[email protected]>
Right, this was my interpretation as well. I also don't have any concrete example of such an exploit. Getting back to the whole idea of enhancing cancellation capabilities, one concern I have with this design is the pattern: if (...) {
...
_cancel(...);
} else if (...) {
...
_cancel(...);
} else {
super.cancel();
} The reasoning is that we're allowing to bypass Consider a contract that inherits from contract GovernorCancellationCounter {
uint256 cancellations;
function cancel(...) public override virtual returns (uint256) {
cancellations++;
super.cancel(...);
}
}
contract MyGovernor is ..., GovernorCancellationCounter, GovernorProposalGuardian { ... } // Order is important In this case, the count would be missing the internal branches that don't call I'd feel more comfortable if function _validateCancel(...) internal virtual {
// Current logic
uint256 proposalId = hashProposal(targets, values, calldatas, descriptionHash);
_validateStateBitmap(proposalId, _encodeStateBitmap(ProposalState.Pending));
if (_msgSender() != proposalProposer(proposalId)) {
revert GovernorOnlyProposer(_msgSender());
}
}
...
function cancel(...) public virtual returns (uint256) {
_validateCancel();
return _cancel(targets, values, calldatas, descriptionHash);
} I think this way users can override |
Fixes #5301
PR Checklist
npx changeset add
)