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