GovernorPreventLateQuorum.spec 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. import "GovernorBase.spec"
  2. import "GovernorCountingSimple.spec"
  3. using ERC20VotesHarness as token
  4. methods {
  5. // envfree
  6. quorumNumerator(uint256) returns uint256
  7. quorumDenominator() returns uint256 envfree
  8. // harness
  9. getDeprecatedQuorumNumerator() returns uint256 envfree
  10. getQuorumNumeratorLength() returns uint256 envfree
  11. getQuorumNumeratorLatest() returns uint256 envfree
  12. getExtendedDeadline(uint256) returns uint64 envfree
  13. getPastTotalSupply(uint256) returns (uint256) envfree
  14. }
  15. ////////////////////////////////////////////////////////////////////////////////
  16. // Helper Functions //
  17. ////////////////////////////////////////////////////////////////////////////////
  18. function sanity() {
  19. require getQuorumNumeratorLength() + 1 < max_uint;
  20. }
  21. definition deadlineExtendable(env e, uint256 pId) returns bool = getExtendedDeadline(pId) == 0;
  22. definition deadlineExtended(env e, uint256 pId) returns bool = getExtendedDeadline(pId) > 0;
  23. invariant deadlineConsistency(env e, uint256 pId)
  24. (!quorumReached(e, pId) => deadlineExtendable(pId))
  25. &&
  26. (deadlineExtended(pId) => quorumReached(e, pId))
  27. invariant proposalStateConsistencyExtended(uint256 pId)
  28. !proposalCreated(pId) => (getAgainstVotes(pId) == 0 && getAbstainVotes(pId) == 0 && getForVotes(pId) == 0)
  29. && (proposalProposer(pId) == 0 <=> proposalSnapshot(pId) == 0)
  30. && (proposalProposer(pId) == 0 <=> proposalDeadline(pId) == 0)
  31. {
  32. preserved with (env e) {
  33. require e.block.number > 0;
  34. }
  35. }
  36. /**
  37. * If a proposal has reached quorum then the proposal snapshot (start `block.number`) must be non-zero
  38. */
  39. invariant quorumReachedEffect(env e, uint256 pId)
  40. (quorumReached(e, pId) && getPastTotalSupply(proposalSnapshot(pId)) > 0) => proposalCreated(pId)
  41. /**
  42. * The quorum numerator is always less than or equal to the quorum denominator.
  43. */
  44. invariant quorumRatioLessThanOne(env e, uint256 blockNumber)
  45. quorumNumerator(e, blockNumber) <= quorumDenominator()
  46. /**
  47. * If a proposal's deadline has been extended, then the proposal must have been created and reached quorum.
  48. */
  49. invariant cantExtendWhenQuorumUnreached(env e, uint256 pId)
  50. getExtendedDeadline(pId) > 0 => (quorumReached(e, pId) && proposalCreated(pId))
  51. //{
  52. // preserved with (env e1) {
  53. // require e1.block.number > proposalSnapshot(pId);
  54. // setup(e1, e);
  55. // }
  56. //}
  57. /*
  58. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  59. โ”‚ Rule: `updateQuorumNumerator` can only change quorum requirements for future proposals. โ”‚
  60. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  61. */
  62. rule quorumReachedCantChange(uint256 pId, env e, method f) filtered { f ->
  63. !f.isFallback
  64. && !f.isView
  65. && f.selector != relay(address,uint256,bytes).selector
  66. && !castVoteSubset(f)
  67. } {
  68. sanity(); // not overflowing checkpoint struct
  69. require proposalSnapshot(pId) < e.block.number; // vote has started
  70. bool quorumReachedBefore = quorumReached(e, pId);
  71. uint256 newQuorumNumerator;
  72. updateQuorumNumerator(e, newQuorumNumerator);
  73. assert quorumReachedBefore == quorumReached(e, pId);
  74. }
  75. /*
  76. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  77. โ”‚ Rule: Casting a vote must not decrease any category's total number of votes and increase at least one category's โ”‚
  78. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  79. */
  80. rule hasVotedCorrelationNonzero(uint256 pId, env e, method f, calldataarg args) filtered { f ->
  81. !f.isFallback
  82. && !f.isView
  83. && f.selector != relay(address,uint256,bytes).selector
  84. && !castVoteSubset(f)
  85. } {
  86. require getVotes(e, e.msg.sender, proposalSnapshot(pId)) > 0; // assuming voter has non-zero voting power
  87. uint256 againstBefore = votesAgainst();
  88. uint256 forBefore = votesFor();
  89. uint256 abstainBefore = votesAbstain();
  90. bool hasVotedBefore = hasVoted(pId, e.msg.sender);
  91. f(e, args);
  92. uint256 againstAfter = votesAgainst();
  93. uint256 forAfter = votesFor();
  94. uint256 abstainAfter = votesAbstain();
  95. bool hasVotedAfter = hasVoted(pId, e.msg.sender);
  96. // want all vote categories to not decrease and at least one category to increase
  97. assert
  98. (!hasVotedBefore && hasVotedAfter) =>
  99. (againstBefore <= againstAfter && forBefore <= forAfter && abstainBefore <= abstainAfter),
  100. "after a vote is cast, the number of votes for each category must not decrease";
  101. assert
  102. (!hasVotedBefore && hasVotedAfter) =>
  103. (againstBefore < againstAfter || forBefore < forAfter || abstainBefore < abstainAfter),
  104. "after a vote is cast, the number of votes of at least one category must increase";
  105. }
  106. /*
  107. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  108. โ”‚ Rule: Voting against a proposal does not count towards quorum. โ”‚
  109. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  110. */
  111. rule againstVotesDontCount(uint256 pId, env e, method f, calldataarg args) filtered { f ->
  112. !f.isFallback
  113. && !f.isView
  114. && f.selector != relay(address,uint256,bytes).selector
  115. && !castVoteSubset(f)
  116. } {
  117. bool quorumBefore = quorumReached(e, pId);
  118. uint256 againstBefore = votesAgainst();
  119. f(e, args);
  120. bool quorumAfter = quorumReached(e, pId);
  121. uint256 againstAfter = votesAgainst();
  122. assert againstBefore < againstAfter => quorumBefore == quorumAfter, "quorum must not be reached with an against vote";
  123. }
  124. /*
  125. โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
  126. โ”‚ Rule: โ”‚
  127. โ”‚ * Deadline can never be reduced โ”‚
  128. โ”‚ * If deadline increases then we are in `deadlineExtended` state and `castVote` was called. โ”‚
  129. โ”‚ * A proposal's deadline can't change in `deadlineExtended` state. โ”‚
  130. โ”‚ * A proposal's deadline can't be unextended. โ”‚
  131. โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
  132. */
  133. rule deadlineChangeEffects(uint256 pId, env e, method f, calldataarg args) filtered { f ->
  134. !f.isFallback
  135. && !f.isView
  136. && f.selector != relay(address,uint256,bytes).selector
  137. && !castVoteSubset(f)
  138. } {
  139. requireInvariant proposalStateConsistencyExtended(pId);
  140. uint256 deadlineBefore = proposalDeadline(pId);
  141. bool deadlineExtendedBefore = deadlineExtended(e, pId);
  142. f(e, args);
  143. uint256 deadlineAfter = proposalDeadline(pId);
  144. bool deadlineExtendedAfter = deadlineExtended(e, pId);
  145. // deadline can never be reduced
  146. assert deadlineBefore <= proposalDeadline(pId);
  147. // deadline can only be extended in proposal or on cast vote
  148. assert (
  149. deadlineAfter > deadlineBefore
  150. ) => (
  151. (!deadlineExtendedBefore && !deadlineExtendedAfter && f.selector == propose(address[], uint256[], bytes[], string).selector)
  152. ||
  153. (!deadlineExtendedBefore && deadlineExtendedAfter && f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector)
  154. );
  155. // a deadline can only be extended once
  156. assert deadlineExtendedBefore => deadlineBefore == deadlineAfter;
  157. // a deadline cannot be un-extended
  158. assert deadlineExtendedBefore => deadlineExtendedAfter;
  159. }
  160. function setup(env e1, env e2) {
  161. require getQuorumNumeratorLength() + 1 < max_uint;
  162. require e2.block.number >= e1.block.number;
  163. }
  164. /// The proposal with proposal id `pId` has not been created.
  165. definition proposalNotCreated(env e, uint256 pId) returns bool =
  166. proposalSnapshot(pId) == 0
  167. && proposalDeadline(pId) == 0
  168. && getAgainstVotes(pId) == 0
  169. && getAbstainVotes(pId) == 0
  170. && getForVotes(pId) == 0;
  171. /// Method f is a version of `castVote` whose state changing effects are covered by `castVoteBySig`.
  172. /// @dev castVoteBySig allows anyone to cast a vote for anyone else if they can supply the signature. Specifically,
  173. /// it covers the case where the msg.sender supplies a signature for themselves which is normally done using the normal
  174. /// `castVote`.
  175. definition castVoteSubset(method f) returns bool =
  176. f.selector == castVote(uint256, uint8).selector ||
  177. f.selector == castVoteWithReason(uint256, uint8, string).selector ||
  178. f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector ||
  179. f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector;
  180. /**
  181. * A created proposal must be in state `deadlineExtendable` or `deadlineExtended`.
  182. * @dev We assume the total supply of the voting token is non-zero
  183. */
  184. invariant proposalInOneState(env e1, uint256 pId)
  185. getPastTotalSupply(0) > 0 => (proposalNotCreated(e1, pId) || deadlineExtendable(e1, pId) || deadlineExtended(e1, pId))
  186. filtered { f -> !f.isFallback && !f.isView && !castVoteSubset(f) && f.selector != relay(address,uint256,bytes).selector }
  187. {
  188. preserved with (env e2) {
  189. setup(e1, e2);
  190. }
  191. }