123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308 |
- import "GovernorBase.spec"
- import "GovernorCountingSimple.spec"
- using ERC20VotesHarness as token
- methods {
- // envfree
- quorumNumerator(uint256) returns uint256
- quorumDenominator() returns uint256 envfree
- // harness
- getDeprecatedQuorumNumerator() returns uint256 envfree
- getQuorumNumeratorLength() returns uint256 envfree
- getQuorumNumeratorLatest() returns uint256 envfree
- getExtendedDeadline(uint256) returns uint64 envfree
- getPastTotalSupply(uint256) returns (uint256) envfree
- }
- ////////////////////////////////////////////////////////////////////////////////
- // Helper Functions //
- ////////////////////////////////////////////////////////////////////////////////
- function sanity() {
- require getQuorumNumeratorLength() + 1 < max_uint;
- }
- definition deadlineExtendable(env e, uint256 pId) returns bool = getExtendedDeadline(pId) == 0;
- definition deadlineExtended(env e, uint256 pId) returns bool = getExtendedDeadline(pId) > 0;
- invariant deadlineConsistency(env e, uint256 pId)
- (!quorumReached(e, pId) => deadlineExtendable(pId))
- &&
- (deadlineExtended(pId) => quorumReached(e, pId))
- invariant proposalStateConsistencyExtended(uint256 pId)
- !proposalCreated(pId) => (getAgainstVotes(pId) == 0 && getAbstainVotes(pId) == 0 && getForVotes(pId) == 0)
- && (proposalProposer(pId) == 0 <=> proposalSnapshot(pId) == 0)
- && (proposalProposer(pId) == 0 <=> proposalDeadline(pId) == 0)
- {
- preserved with (env e) {
- require e.block.number > 0;
- }
- }
- /**
- * If a proposal has reached quorum then the proposal snapshot (start `block.number`) must be non-zero
- */
- invariant quorumReachedEffect(env e, uint256 pId)
- (quorumReached(e, pId) && getPastTotalSupply(proposalSnapshot(pId)) > 0) => proposalCreated(pId)
- /**
- * The quorum numerator is always less than or equal to the quorum denominator.
- */
- invariant quorumRatioLessThanOne(env e, uint256 blockNumber)
- quorumNumerator(e, blockNumber) <= quorumDenominator()
- /**
- * If a proposal's deadline has been extended, then the proposal must have been created and reached quorum.
- */
- invariant cantExtendWhenQuorumUnreached(env e, uint256 pId)
- getExtendedDeadline(pId) > 0 => (quorumReached(e, pId) && proposalCreated(pId))
- //{
- // preserved with (env e1) {
- // require e1.block.number > proposalSnapshot(pId);
- // setup(e1, e);
- // }
- //}
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: `updateQuorumNumerator` can only change quorum requirements for future proposals. โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule quorumReachedCantChange(uint256 pId, env e, method f) filtered { f ->
- !f.isFallback
- && !f.isView
- && f.selector != relay(address,uint256,bytes).selector
- && !castVoteSubset(f)
- } {
- sanity(); // not overflowing checkpoint struct
- require proposalSnapshot(pId) < e.block.number; // vote has started
- bool quorumReachedBefore = quorumReached(e, pId);
- uint256 newQuorumNumerator;
- updateQuorumNumerator(e, newQuorumNumerator);
- assert quorumReachedBefore == quorumReached(e, pId);
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: Casting a vote must not decrease any category's total number of votes and increase at least one category's โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule hasVotedCorrelationNonzero(uint256 pId, env e, method f, calldataarg args) filtered { f ->
- !f.isFallback
- && !f.isView
- && f.selector != relay(address,uint256,bytes).selector
- && !castVoteSubset(f)
- } {
- require getVotes(e, e.msg.sender, proposalSnapshot(pId)) > 0; // assuming voter has non-zero voting power
- uint256 againstBefore = votesAgainst();
- uint256 forBefore = votesFor();
- uint256 abstainBefore = votesAbstain();
- bool hasVotedBefore = hasVoted(pId, e.msg.sender);
- f(e, args);
- uint256 againstAfter = votesAgainst();
- uint256 forAfter = votesFor();
- uint256 abstainAfter = votesAbstain();
- bool hasVotedAfter = hasVoted(pId, e.msg.sender);
- // want all vote categories to not decrease and at least one category to increase
- assert
- (!hasVotedBefore && hasVotedAfter) =>
- (againstBefore <= againstAfter && forBefore <= forAfter && abstainBefore <= abstainAfter),
- "after a vote is cast, the number of votes for each category must not decrease";
- assert
- (!hasVotedBefore && hasVotedAfter) =>
- (againstBefore < againstAfter || forBefore < forAfter || abstainBefore < abstainAfter),
- "after a vote is cast, the number of votes of at least one category must increase";
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: Voting against a proposal does not count towards quorum. โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule againstVotesDontCount(uint256 pId, env e, method f, calldataarg args) filtered { f ->
- !f.isFallback
- && !f.isView
- && f.selector != relay(address,uint256,bytes).selector
- && !castVoteSubset(f)
- } {
- bool quorumBefore = quorumReached(e, pId);
- uint256 againstBefore = votesAgainst();
- f(e, args);
- bool quorumAfter = quorumReached(e, pId);
- uint256 againstAfter = votesAgainst();
- assert againstBefore < againstAfter => quorumBefore == quorumAfter, "quorum must not be reached with an against vote";
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: โ
- โ * Deadline can never be reduced โ
- โ * If deadline increases then we are in `deadlineExtended` state and `castVote` was called. โ
- โ * A proposal's deadline can't change in `deadlineExtended` state. โ
- โ * A proposal's deadline can't be unextended. โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule deadlineChangeEffects(uint256 pId, env e, method f, calldataarg args) filtered { f ->
- !f.isFallback
- && !f.isView
- && f.selector != relay(address,uint256,bytes).selector
- && !castVoteSubset(f)
- } {
- requireInvariant proposalStateConsistencyExtended(pId);
- uint256 deadlineBefore = proposalDeadline(pId);
- bool deadlineExtendedBefore = deadlineExtended(e, pId);
- f(e, args);
- uint256 deadlineAfter = proposalDeadline(pId);
- bool deadlineExtendedAfter = deadlineExtended(e, pId);
- // deadline can never be reduced
- assert deadlineBefore <= proposalDeadline(pId);
- // deadline can only be extended in proposal or on cast vote
- assert (
- deadlineAfter > deadlineBefore
- ) => (
- (!deadlineExtendedBefore && !deadlineExtendedAfter && f.selector == propose(address[], uint256[], bytes[], string).selector)
- ||
- (!deadlineExtendedBefore && deadlineExtendedAfter && f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector)
- );
- // a deadline can only be extended once
- assert deadlineExtendedBefore => deadlineBefore == deadlineAfter;
- // a deadline cannot be un-extended
- assert deadlineExtendedBefore => deadlineExtendedAfter;
- }
- function setup(env e1, env e2) {
- require getQuorumNumeratorLength() + 1 < max_uint;
- require e2.block.number >= e1.block.number;
- }
- /// The proposal with proposal id `pId` has not been created.
- definition proposalNotCreated(env e, uint256 pId) returns bool =
- proposalSnapshot(pId) == 0
- && proposalDeadline(pId) == 0
- && getAgainstVotes(pId) == 0
- && getAbstainVotes(pId) == 0
- && getForVotes(pId) == 0;
- /// Method f is a version of `castVote` whose state changing effects are covered by `castVoteBySig`.
- /// @dev castVoteBySig allows anyone to cast a vote for anyone else if they can supply the signature. Specifically,
- /// it covers the case where the msg.sender supplies a signature for themselves which is normally done using the normal
- /// `castVote`.
- definition castVoteSubset(method f) returns bool =
- f.selector == castVote(uint256, uint8).selector ||
- f.selector == castVoteWithReason(uint256, uint8, string).selector ||
- f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector ||
- f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector;
- /**
- * A created proposal must be in state `deadlineExtendable` or `deadlineExtended`.
- * @dev We assume the total supply of the voting token is non-zero
- */
- invariant proposalInOneState(env e1, uint256 pId)
- getPastTotalSupply(0) > 0 => (proposalNotCreated(e1, pId) || deadlineExtendable(e1, pId) || deadlineExtended(e1, pId))
- filtered { f -> !f.isFallback && !f.isView && !castVoteSubset(f) && f.selector != relay(address,uint256,bytes).selector }
- {
- preserved with (env e2) {
- setup(e1, e2);
- }
- }
|