GovernorPreventLateQuorum.spec 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295
  1. import "GovernorCountingSimple.spec"
  2. methods {
  3. quorumDenominator() returns uint256 envfree
  4. votingPeriod() returns uint256 envfree
  5. lateQuorumVoteExtension() returns uint64 envfree
  6. propose(address[], uint256[], bytes[], string)
  7. // harness
  8. getExtendedDeadlineIsUnset(uint256) returns bool envfree
  9. getExtendedDeadlineIsStarted(uint256) returns bool envfree
  10. getExtendedDeadline(uint256) returns uint64 envfree
  11. getAgainstVotes(uint256) returns uint256 envfree
  12. getAbstainVotes(uint256) returns uint256 envfree
  13. getForVotes(uint256) returns uint256 envfree
  14. // more robust check than f.selector == _castVote(...).selector
  15. latestCastVoteCall() returns uint256 envfree
  16. // timelock dispatch
  17. getMinDelay() returns uint256 => DISPATCHER(true)
  18. hashOperationBatch(address[], uint256[], bytes[], bytes32, bytes32) => DISPATCHER(true)
  19. executeBatch(address[], uint256[], bytes[], bytes32, bytes32) => CONSTANT
  20. scheduleBatch(address[], uint256[], bytes[], bytes32, bytes32, uint256) => CONSTANT
  21. }
  22. //////////////////////////////////////////////////////////////////////////////
  23. ///////////////////////////// Helper Functions ///////////////////////////////
  24. //////////////////////////////////////////////////////////////////////////////
  25. function helperFunctionsWithRevertOnlyCastVote(uint256 proposalId, method f, env e) {
  26. string reason; uint8 support; uint8 v; bytes32 r; bytes32 s; bytes params;
  27. if (f.selector == castVote(uint256, uint8).selector) {
  28. castVote@withrevert(e, proposalId, support);
  29. } else if (f.selector == castVoteWithReason(uint256, uint8, string).selector) {
  30. castVoteWithReason@withrevert(e, proposalId, support, reason);
  31. } else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector) {
  32. castVoteBySig@withrevert(e, proposalId, support, v, r, s);
  33. } else if (f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector) {
  34. castVoteWithReasonAndParamsBySig@withrevert(e, proposalId, support, reason, params, v, r, s);
  35. } else if (f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector) {
  36. castVoteWithReasonAndParams@withrevert(e, proposalId, support, reason, params);
  37. } else {
  38. calldataarg args;
  39. f@withrevert(e, args);
  40. }
  41. }
  42. //////////////////////////////////////////////////////////////////////////////
  43. //////////////////////////////// Definitions /////////////////////////////////
  44. //////////////////////////////////////////////////////////////////////////////
  45. // proposal deadline can be extended (but isn't)
  46. definition deadlineExtendable(env e, uint256 pId) returns bool =
  47. getExtendedDeadlineIsUnset(pId) // deadline == 0
  48. && !quorumReached(e, pId);
  49. // proposal deadline has been extended
  50. definition deadlineExtended(env e, uint256 pId) returns bool =
  51. getExtendedDeadlineIsStarted(pId) // deadline > 0
  52. && quorumReached(e, pId);
  53. definition proposalNotCreated(env e, uint256 pId) returns bool =
  54. proposalSnapshot(pId) == 0
  55. && proposalDeadline(pId) == 0
  56. && getExtendedDeadlineIsUnset(pId)
  57. && getAgainstVotes(pId) == 0
  58. && getAbstainVotes(pId) == 0
  59. && getForVotes(pId) == 0
  60. && !quorumReached(e, pId);
  61. //////////////////////////////////////////////////////////////////////////////
  62. ///////////////////////////////// Invariants /////////////////////////////////
  63. //////////////////////////////////////////////////////////////////////////////
  64. /*
  65. * I1: If a proposal has reached quorum then the proposal snapshot (start block.number) must be non-zero
  66. * INVARIANT NOT PASSING // fails for updateQuorumNumerator and in the initial state when voting token total supply is 0 (causes quoromReached to return true)
  67. * ADVANCED SANITY NOT RAN
  68. */
  69. invariant quorumReachedEffect(env e, uint256 pId)
  70. quorumReached(e, pId) => proposalCreated(pId) // bug: 0 supply 0 votes => quorumReached
  71. // filtered { f -> f.selector != updateQuorumNumerator(uint256).selector } // * fails for this function
  72. /*
  73. * I2: A non-existant proposal must meet the definition of one.
  74. * INVARIANT NOT PASSING // fails for updateQuorumNumerator and in the initial state when voting token total supply is 0 (causes quoromReached to return true)
  75. * ADVANCED SANITY NOT RAN
  76. */
  77. invariant proposalNotCreatedEffects(env e, uint256 pId)
  78. !proposalCreated(pId) => proposalNotCreated(e, pId)
  79. // filtered { f -> f.selector != updateQuorumNumerator(uint256).selector } // * fails for this function
  80. /*
  81. * I3: A created propsal must be in state deadlineExtendable or deadlineExtended.
  82. * INVARIANT NOT PASSING // fails for updateQuorumNumerator and in the initial state when voting token total supply is 0 (causes quoromReached to return true)
  83. * ADVANCED SANITY NOT RAN
  84. */
  85. invariant proposalInOneState(env e, uint256 pId)
  86. proposalNotCreated(e, pId) || deadlineExtendable(e, pId) || deadlineExtended(e, pId)
  87. // filtered { f -> f.selector != updateQuorumNumerator(uint256).selector } // * fails for this function
  88. { preserved { requireInvariant proposalNotCreatedEffects(e, pId); }}
  89. //////////////////////////////////////////////////////////////////////////////
  90. ////////////////////////////////// Rules /////////////////////////////////////
  91. //////////////////////////////////////////////////////////////////////////////
  92. ///////////////////////////// first set of rules /////////////////////////////
  93. // R1 and R2 are assumed in R3, so we prove them first
  94. /*
  95. * R1: If deadline increases then we are in deadlineExtended state and castVote was called.
  96. * RULE PASSING
  97. * ADVANCED SANITY PASSING
  98. */
  99. rule deadlineChangeEffects(method f) filtered {f -> !f.isView} {
  100. env e; calldataarg args; uint256 pId;
  101. requireInvariant quorumReachedEffect(e, pId);
  102. uint256 deadlineBefore = proposalDeadline(pId);
  103. f(e, args);
  104. uint256 deadlineAfter = proposalDeadline(pId);
  105. assert(deadlineAfter > deadlineBefore => latestCastVoteCall() == e.block.number && deadlineExtended(e, pId));
  106. }
  107. /*
  108. * R2: A proposal can't leave deadlineExtended state.
  109. * RULE PASSING*
  110. * ADVANCED SANITY PASSING
  111. */
  112. rule deadlineCantBeUnextended(method f)
  113. filtered {
  114. f -> !f.isView
  115. // && f.selector != updateQuorumNumerator(uint256).selector // * fails for this function
  116. } {
  117. env e; calldataarg args; uint256 pId;
  118. require(deadlineExtended(e, pId));
  119. requireInvariant quorumReachedEffect(e, pId);
  120. f(e, args);
  121. assert(deadlineExtended(e, pId));
  122. }
  123. /*
  124. * R3: A proposal's deadline can't change in deadlineExtended state.
  125. * RULE PASSING
  126. * ADVANCED SANITY PASSING
  127. */
  128. rule canExtendDeadlineOnce(method f) filtered {f -> !f.isView} {
  129. env e; calldataarg args; uint256 pId;
  130. require(deadlineExtended(e, pId));
  131. requireInvariant quorumReachedEffect(e, pId);
  132. uint256 deadlineBefore = proposalDeadline(pId);
  133. f(e, args);
  134. uint256 deadlineAfter = proposalDeadline(pId);
  135. assert(deadlineBefore == deadlineAfter, "deadline can not be extended twice");
  136. }
  137. //////////////////////////// second set of rules ////////////////////////////
  138. // HIGH LEVEL RULE R6: deadline can only extended if quorum reached w/ <= timeOfExtension left to vote
  139. // I3, R4 and R5 are assumed in R6 so we prove them first
  140. /*
  141. * R4: A change in hasVoted must be correlated with an increasing of the vote supports, i.e. casting a vote increases the total number of votes.
  142. * RULE PASSING
  143. * ADVANCED SANITY PASSING
  144. */
  145. rule hasVotedCorrelationNonzero(uint256 pId, method f, env e) filtered {f -> !f.isView} {
  146. address acc = e.msg.sender;
  147. require(getVotes(e, acc, proposalSnapshot(pId)) > 0); // assuming voter has non-zero voting power
  148. uint256 againstBefore = votesAgainst();
  149. uint256 forBefore = votesFor();
  150. uint256 abstainBefore = votesAbstain();
  151. bool hasVotedBefore = hasVoted(e, pId, acc);
  152. helperFunctionsWithRevertOnlyCastVote(pId, f, e); // should be f(e, args)
  153. uint256 againstAfter = votesAgainst();
  154. uint256 forAfter = votesFor();
  155. uint256 abstainAfter = votesAbstain();
  156. bool hasVotedAfter = hasVoted(e, pId, acc);
  157. // want all vote categories to not decrease and at least one category to increase
  158. assert
  159. (!hasVotedBefore && hasVotedAfter) =>
  160. (againstBefore <= againstAfter && forBefore <= forAfter && abstainBefore <= abstainAfter),
  161. "after a vote is cast, the number of votes for each category must not decrease"; // currently vacous but keeping for CI tests
  162. assert
  163. (!hasVotedBefore && hasVotedAfter) =>
  164. (againstBefore < againstAfter || forBefore < forAfter || abstainBefore < abstainAfter),
  165. "after a vote is cast, the number of votes of at least one category must increase";
  166. }
  167. /*
  168. * R5: An against vote does not make a proposal reach quorum.
  169. * RULE PASSING
  170. * --ADVANCED SANITY PASSING vacuous but keeping
  171. */
  172. rule againstVotesDontCount(method f) filtered {f -> !f.isView} {
  173. env e; calldataarg args; uint256 pId;
  174. address acc = e.msg.sender;
  175. bool quorumBefore = quorumReached(e, pId);
  176. uint256 againstBefore = votesAgainst();
  177. f(e, args);
  178. bool quorumAfter = quorumReached(e, pId);
  179. uint256 againstAfter = votesAgainst();
  180. assert (againstBefore < againstAfter) => quorumBefore == quorumAfter, "quorum must not be reached with an against vote";
  181. }
  182. /*
  183. * R6: Deadline can only be extended from a `deadlineExtendible` state with quorum being reached with <= `lateQuorumVoteExtension` time left to vote
  184. * RULE PASSING
  185. * ADVANCED SANITY PASSING
  186. */
  187. rule deadlineExtenededIfQuorumReached(method f) filtered {f -> !f.isView} {
  188. env e; calldataarg args; uint256 pId;
  189. requireInvariant proposalInOneState(e, pId);
  190. requireInvariant quorumReachedEffect(e, pId);
  191. requireInvariant proposalNotCreatedEffects(e, pId);
  192. bool wasDeadlineExtendable = deadlineExtendable(e, pId);
  193. uint64 extension = lateQuorumVoteExtension();
  194. uint256 deadlineBefore = proposalDeadline(pId);
  195. f(e, args);
  196. uint256 deadlineAfter = proposalDeadline(pId);
  197. assert deadlineAfter > deadlineBefore => wasDeadlineExtendable, "deadline must have been extendable for the deadline to be extended";
  198. assert deadlineAfter > deadlineBefore => deadlineBefore - e.block.number <= extension, "deadline extension should not be used";
  199. }
  200. /*
  201. * R7: `extendedDeadlineField` is set iff `_castVote` is called and quroum is reached.
  202. * RULE PASSING
  203. * ADVANCED SANITY PASSING
  204. */
  205. rule extendedDeadlineValueSetIfQuorumReached(method f) filtered {f -> !f.isView} {
  206. env e; calldataarg args; uint256 pId;
  207. requireInvariant proposalInOneState(e, pId);
  208. bool extendedBefore = deadlineExtended(e, pId);
  209. f(e, args);
  210. bool extendedAfter = deadlineExtended(e, pId);
  211. uint256 extDeadline = getExtendedDeadline(pId);
  212. assert(
  213. !extendedBefore && extendedAfter
  214. => extDeadline == e.block.number + lateQuorumVoteExtension(),
  215. "extended deadline was not set"
  216. );
  217. }
  218. /*
  219. * R8: Deadline can never be reduced.
  220. * RULE PASSING
  221. * ADVANCED SANITY PASSING
  222. */
  223. rule deadlineNeverReduced(method f) filtered {f -> !f.isView} {
  224. env e; calldataarg args; uint256 pId;
  225. requireInvariant quorumReachedEffect(e, pId);
  226. requireInvariant proposalNotCreatedEffects(e, pId);
  227. uint256 deadlineBefore = proposalDeadline(pId);
  228. f(e, args);
  229. uint256 deadlineAfter = proposalDeadline(pId);
  230. assert(deadlineAfter >= deadlineBefore);
  231. }