parent
0deaee1217
commit
6add1e7718
@ -0,0 +1,159 @@ |
|||||||
|
// SPDX-License-Identifier: MIT |
||||||
|
pragma solidity ^0.8.2; |
||||||
|
|
||||||
|
import "../munged/governance/extensions/GovernorPreventLateQuorum.sol"; |
||||||
|
import "../munged/governance/Governor.sol"; |
||||||
|
import "../munged/governance/extensions/GovernorCountingSimple.sol"; |
||||||
|
import "../munged/governance/extensions/GovernorVotes.sol"; |
||||||
|
import "../munged/governance/extensions/GovernorVotesQuorumFraction.sol"; |
||||||
|
import "../munged/governance/extensions/GovernorTimelockControl.sol"; |
||||||
|
import "../munged/token/ERC20/extensions/ERC20Votes.sol"; |
||||||
|
|
||||||
|
contract GovernorPreventLateQuorumHarness is Governor, GovernorCountingSimple, GovernorVotes, GovernorVotesQuorumFraction, GovernorTimelockControl, GovernorPreventLateQuorum { |
||||||
|
using SafeCast for uint256; |
||||||
|
using Timers for Timers.BlockNumber; |
||||||
|
constructor(IVotes _token, TimelockController _timelock, uint64 initialVoteExtension, uint256 quorumNumeratorValue) |
||||||
|
Governor("Harness") |
||||||
|
GovernorVotes(_token) |
||||||
|
GovernorVotesQuorumFraction(quorumNumeratorValue) |
||||||
|
GovernorTimelockControl(_timelock) |
||||||
|
GovernorPreventLateQuorum(initialVoteExtension) |
||||||
|
{} |
||||||
|
|
||||||
|
// variable added to check when _castVote is called |
||||||
|
uint256 public latestCastVoteCall; |
||||||
|
|
||||||
|
// Harness from GovernorPreventLateQuorum // |
||||||
|
|
||||||
|
function getVoteExtension() public view returns(uint64) { |
||||||
|
return _voteExtension; |
||||||
|
} |
||||||
|
|
||||||
|
function getExtendedDeadlineIsUnset(uint256 id) public view returns(bool) { |
||||||
|
return _extendedDeadlines[id].isUnset(); |
||||||
|
} |
||||||
|
|
||||||
|
function getExtendedDeadline(uint256 id) public view returns(uint64) { |
||||||
|
return _extendedDeadlines[id].getDeadline(); |
||||||
|
} |
||||||
|
|
||||||
|
// Harness from GovernorCountingSimple // |
||||||
|
|
||||||
|
function quorumReached(uint256 proposalId) public view returns(bool) { |
||||||
|
return _quorumReached(proposalId); |
||||||
|
} |
||||||
|
|
||||||
|
// Harness from Governor // |
||||||
|
|
||||||
|
function isExecuted(uint256 proposalId) public view returns (bool) { |
||||||
|
return _proposals[proposalId].executed; |
||||||
|
} |
||||||
|
|
||||||
|
function isCanceled(uint256 proposalId) public view returns (bool) { |
||||||
|
return _proposals[proposalId].canceled; |
||||||
|
} |
||||||
|
|
||||||
|
// The following functions are overrides required by Solidity added by Certora. // |
||||||
|
|
||||||
|
function proposalDeadline(uint256 proposalId) public view virtual override(Governor, GovernorPreventLateQuorum, IGovernor) returns (uint256) { |
||||||
|
return super.proposalDeadline(proposalId); |
||||||
|
} |
||||||
|
|
||||||
|
function _castVote( |
||||||
|
uint256 proposalId, |
||||||
|
address account, |
||||||
|
uint8 support, |
||||||
|
string memory reason, |
||||||
|
bytes memory params |
||||||
|
) internal virtual override(Governor, GovernorPreventLateQuorum) returns (uint256) { |
||||||
|
latestCastVoteCall = block.number; |
||||||
|
return super._castVote(proposalId, account, support, reason, params); |
||||||
|
} |
||||||
|
|
||||||
|
function castVote( |
||||||
|
uint256 proposalId, |
||||||
|
address account, |
||||||
|
uint8 support, |
||||||
|
string memory reason, |
||||||
|
bytes memory params |
||||||
|
) public returns(uint256) { |
||||||
|
return _castVote(proposalId, account, support, reason, params); |
||||||
|
} |
||||||
|
|
||||||
|
function lateQuorumVoteExtension() public view virtual override returns (uint64) { |
||||||
|
return super.lateQuorumVoteExtension(); |
||||||
|
} |
||||||
|
|
||||||
|
function setLateQuorumVoteExtension(uint64 newVoteExtension) public virtual override onlyGovernance { |
||||||
|
super.setLateQuorumVoteExtension(newVoteExtension); |
||||||
|
} |
||||||
|
|
||||||
|
// The following functions are overrides required by Solidity added by OZ Wizard. // |
||||||
|
|
||||||
|
function votingDelay() public pure override returns (uint256) { |
||||||
|
return 1; // 1 block |
||||||
|
} |
||||||
|
|
||||||
|
function votingPeriod() public pure override returns (uint256) { |
||||||
|
return 45818; // 1 week |
||||||
|
} |
||||||
|
|
||||||
|
function quorum(uint256 blockNumber) |
||||||
|
public |
||||||
|
view |
||||||
|
override(IGovernor, GovernorVotesQuorumFraction) |
||||||
|
returns (uint256) |
||||||
|
{ |
||||||
|
return super.quorum(blockNumber); |
||||||
|
} |
||||||
|
|
||||||
|
function state(uint256 proposalId) |
||||||
|
public |
||||||
|
view |
||||||
|
override(Governor, GovernorTimelockControl) |
||||||
|
returns (ProposalState) |
||||||
|
{ |
||||||
|
return super.state(proposalId); |
||||||
|
} |
||||||
|
|
||||||
|
function propose(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, string memory description) |
||||||
|
public |
||||||
|
override(Governor, IGovernor) |
||||||
|
returns (uint256) |
||||||
|
{ |
||||||
|
return super.propose(targets, values, calldatas, description); |
||||||
|
} |
||||||
|
|
||||||
|
function _execute(uint256 proposalId, address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) |
||||||
|
internal |
||||||
|
override(Governor, GovernorTimelockControl) |
||||||
|
{ |
||||||
|
super._execute(proposalId, targets, values, calldatas, descriptionHash); |
||||||
|
} |
||||||
|
|
||||||
|
function _cancel(address[] memory targets, uint256[] memory values, bytes[] memory calldatas, bytes32 descriptionHash) |
||||||
|
internal |
||||||
|
override(Governor, GovernorTimelockControl) |
||||||
|
returns (uint256) |
||||||
|
{ |
||||||
|
return super._cancel(targets, values, calldatas, descriptionHash); |
||||||
|
} |
||||||
|
|
||||||
|
function _executor() |
||||||
|
internal |
||||||
|
view |
||||||
|
override(Governor, GovernorTimelockControl) |
||||||
|
returns (address) |
||||||
|
{ |
||||||
|
return super._executor(); |
||||||
|
} |
||||||
|
|
||||||
|
function supportsInterface(bytes4 interfaceId) |
||||||
|
public |
||||||
|
view |
||||||
|
override(Governor, GovernorTimelockControl) |
||||||
|
returns (bool) |
||||||
|
{ |
||||||
|
return super.supportsInterface(interfaceId); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,9 @@ |
|||||||
|
certoraRun \ |
||||||
|
certora/harnesses/ERC721VotesHarness.sol certora/harnesses/GovernorPreventLateQuorumHarness.sol \ |
||||||
|
--verify GovernorPreventLateQuorumHarness:certora/specs/GovernorPreventLateQuorum.spec \ |
||||||
|
--solc solc \ |
||||||
|
--optimistic_loop \ |
||||||
|
--loop_iter 3 \ |
||||||
|
--cloud \ |
||||||
|
--rule $1 \ |
||||||
|
--msg "GovernorPreventLateQuorum $1" |
@ -0,0 +1,115 @@ |
|||||||
|
////////////////////////////////////////////////////////////////////////////// |
||||||
|
///////////////////// Governor.sol base definitions ////////////////////////// |
||||||
|
////////////////////////////////////////////////////////////////////////////// |
||||||
|
|
||||||
|
using ERC721VotesHarness as erc20votes |
||||||
|
|
||||||
|
methods { |
||||||
|
proposalSnapshot(uint256) returns uint256 envfree // matches proposalVoteStart |
||||||
|
proposalDeadline(uint256) returns uint256 envfree // matches proposalVoteEnd |
||||||
|
hashProposal(address[],uint256[],bytes[],bytes32) returns uint256 envfree |
||||||
|
isExecuted(uint256) returns bool envfree |
||||||
|
isCanceled(uint256) returns bool envfree |
||||||
|
execute(address[], uint256[], bytes[], bytes32) returns uint256 |
||||||
|
hasVoted(uint256, address) returns bool |
||||||
|
castVote(uint256, uint8) returns uint256 |
||||||
|
updateQuorumNumerator(uint256) |
||||||
|
queue(address[], uint256[], bytes[], bytes32) returns uint256 |
||||||
|
quorumNumerator() returns uint256 envfree |
||||||
|
quorumDenominator() returns uint256 envfree |
||||||
|
votingPeriod() returns uint256 envfree |
||||||
|
lateQuorumVoteExtension() returns uint64 envfree |
||||||
|
|
||||||
|
// harness |
||||||
|
getExtendedDeadlineIsUnset(uint256) returns bool envfree |
||||||
|
getExtendedDeadline(uint256) returns uint64 envfree |
||||||
|
quorumReached(uint256) returns bool envfree |
||||||
|
latestCastVoteCall() returns uint256 envfree // more robust check than f.selector == _castVote(...).selector |
||||||
|
|
||||||
|
// function summarization |
||||||
|
proposalThreshold() returns uint256 envfree |
||||||
|
|
||||||
|
getVotes(address, uint256) returns uint256 => DISPATCHER(true) |
||||||
|
|
||||||
|
getPastTotalSupply(uint256 t) returns uint256 => PER_CALLEE_CONSTANT |
||||||
|
getPastVotes(address a, uint256 t) returns uint256 => PER_CALLEE_CONSTANT |
||||||
|
} |
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////// |
||||||
|
//////////////////////////////// Definitions ///////////////////////////////// |
||||||
|
////////////////////////////////////////////////////////////////////////////// |
||||||
|
|
||||||
|
// create definition for extended |
||||||
|
definition deadlineCanBeExtended(uint256 id) returns bool = |
||||||
|
getExtendedDeadlineIsUnset(id) && |
||||||
|
getExtendedDeadline(id) == 0 && |
||||||
|
!quorumReached(id); |
||||||
|
|
||||||
|
definition deadlineHasBeenExtended(uint256 id) returns bool = |
||||||
|
!getExtendedDeadlineIsUnset(id) && |
||||||
|
getExtendedDeadline(id) > 0 && |
||||||
|
quorumReached(id); |
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// RULE deadline can only be extended once |
||||||
|
// 1. if deadline changes then we have state transition from deadlineCanBeExtended to deadlineHasBeenExtended |
||||||
|
rule deadlineChangeEffects(method f) filtered {f -> !f.isView /* bottleneck, restrict for faster testing && f.selector != propose(address[], uint256[], bytes[], string).selector*/ } { |
||||||
|
env e; calldataarg args; uint256 id; |
||||||
|
|
||||||
|
require (latestCastVoteCall() < e.block.number); |
||||||
|
require (quorumNumerator() <= quorumDenominator()); |
||||||
|
require deadlineCanBeExtended(id); |
||||||
|
require (proposalDeadline(id) < e.block.number |
||||||
|
&& proposalDeadline(id) >= proposalSnapshot(id) + votingPeriod() |
||||||
|
&& proposalSnapshot(id) > e.block.number); |
||||||
|
|
||||||
|
uint256 deadlineBefore = proposalDeadline(id); |
||||||
|
f(e, args); |
||||||
|
uint256 deadlineAfter = proposalDeadline(id); |
||||||
|
|
||||||
|
assert(deadlineAfter > deadlineBefore => latestCastVoteCall() == e.block.number && deadlineHasBeenExtended(id)); |
||||||
|
} |
||||||
|
|
||||||
|
// 2. cant unextend |
||||||
|
rule deadlineCantBeUnextended(method f) filtered {f -> !f.isView /* && f.selector != propose(address[], uint256[], bytes[], string).selector*/ } { |
||||||
|
env e; calldataarg args; uint256 id; |
||||||
|
require(deadlineHasBeenExtended(id)); |
||||||
|
f(e, args); |
||||||
|
assert(deadlineHasBeenExtended(id)); |
||||||
|
} |
||||||
|
|
||||||
|
// 3. extended => can't change deadline |
||||||
|
//@note if deadline changed, then it wasnt extended and castvote was called |
||||||
|
rule canExtendDeadlineOnce(method f) filtered {f -> !f.isView /* && f.selector != propose(address[], uint256[], bytes[], string).selector*/ } { |
||||||
|
env e; calldataarg args; |
||||||
|
uint256 id; |
||||||
|
require(deadlineHasBeenExtended(id)); // stays true |
||||||
|
require (proposalDeadline(id) < e.block.number |
||||||
|
&& proposalDeadline(id) >= proposalSnapshot(id) + votingPeriod() |
||||||
|
&& proposalSnapshot(id) > e.block.number); |
||||||
|
uint256 deadlineBefore = proposalDeadline(id); |
||||||
|
f(e, args); |
||||||
|
uint256 deadlineAfter = proposalDeadline(id); |
||||||
|
assert(deadlineBefore == deadlineAfter, "deadline can not be extended twice"); |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
// RULE deadline can only extended if quorum reached w/ <= timeOfExtension left to vote |
||||||
|
// 3 rules |
||||||
|
// 1. voting increases total votes |
||||||
|
// 2. number of votes > quorum => quorum reached |
||||||
|
// 3. deadline can only extended if quorum reached w/ <= timeOfExtension left to vote |
||||||
|
rule deadlineCanOnlyBeExtenededIfQuorumReached() { |
||||||
|
env e; method f; calldataarg args; |
||||||
|
uint256 id; |
||||||
|
require(getExtendedDeadlineIsUnset(id)); |
||||||
|
f(e, args); |
||||||
|
assert(false); |
||||||
|
} |
||||||
|
|
||||||
|
// RULE extendedDeadline is used iff quorum is reached w/ <= extensionTime left to vote |
||||||
|
|
||||||
|
// RULE extendedDeadlineField is set iff quroum is reached |
||||||
|
|
||||||
|
// RULE if the deadline/extendedDeadline has not been reached, you can still vote (base) |
Loading…
Reference in new issue