GovernorStates.spec 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. import "helpers/helpers.spec"
  2. import "helpers/Governor.helpers.spec"
  3. import "GovernorInvariants.spec"
  4. use invariant proposalStateConsistency
  5. use invariant votesImplySnapshotPassed
  6. definition assumedSafeOrDuplicateOrDuplicate(method f) returns bool =
  7. assumedSafeOrDuplicate(f)
  8. || f.selector == castVoteWithReason(uint256,uint8,string).selector
  9. || f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector
  10. || f.selector == castVoteBySig(uint256,uint8,uint8,bytes32,bytes32).selector
  11. || f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector;
  12. /*
  13. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  14. │ Rule: state returns one of the value in the enumeration │
  15. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  16. */
  17. invariant stateConsistency(env e, uint256 pId)
  18. state(e, pId) == safeState(e, pId)
  19. filtered { f -> !assumedSafeOrDuplicateOrDuplicate(f); }
  20. rule stateDomain(env e, uint256 pId) {
  21. uint8 result = safeState(e, pId);
  22. assert (
  23. result == UNSET() ||
  24. result == PENDING() ||
  25. result == ACTIVE() ||
  26. result == CANCELED() ||
  27. result == DEFEATED() ||
  28. result == SUCCEEDED() ||
  29. result == QUEUED() ||
  30. result == EXECUTED()
  31. );
  32. }
  33. /*
  34. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  35. │ Rule: State transitions caused by function calls │
  36. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  37. */
  38. /// Previous version that results in the prover timing out.
  39. // rule stateTransitionFn(uint256 pId, env e, method f, calldataarg args)
  40. // filtered { f -> !assumedSafeOrDuplicate(f) }
  41. // {
  42. // require clockSanity(e);
  43. // require quorumNumeratorLength() < max_uint256; // sanity
  44. //
  45. // uint8 stateBefore = safeState(e, pId);
  46. // f(e, args);
  47. // uint8 stateAfter = safeState(e, pId);
  48. //
  49. // assert (stateBefore != stateAfter) => (
  50. // (stateBefore == UNSET() && stateAfter == PENDING() && f.selector == propose(address[],uint256[],bytes[],string).selector ) ||
  51. // (stateBefore == PENDING() && stateAfter == CANCELED() && f.selector == cancel(address[],uint256[],bytes[],bytes32).selector ) ||
  52. // (stateBefore == SUCCEEDED() && stateAfter == QUEUED() && f.selector == queue(address[],uint256[],bytes[],bytes32).selector ) ||
  53. // (stateBefore == SUCCEEDED() && stateAfter == EXECUTED() && f.selector == execute(address[],uint256[],bytes[],bytes32).selector) ||
  54. // (stateBefore == QUEUED() && stateAfter == EXECUTED() && f.selector == execute(address[],uint256[],bytes[],bytes32).selector)
  55. // );
  56. // }
  57. function stateTransitionFnHelper(method f, uint8 s) returns uint8 {
  58. uint256 pId; env e; calldataarg args;
  59. require clockSanity(e);
  60. require quorumNumeratorLength() < max_uint256; // sanity
  61. require safeState(e, pId) == s; // constrain state before
  62. f(e, args);
  63. require safeState(e, pId) != s; // constrain state after
  64. return safeState(e, pId);
  65. }
  66. rule stateTransitionFn_UNSET(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
  67. uint8 stateAfter = stateTransitionFnHelper(f, UNSET());
  68. assert stateAfter == PENDING() && f.selector == propose(address[],uint256[],bytes[],string).selector;
  69. }
  70. rule stateTransitionFn_PENDING(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
  71. uint8 stateAfter = stateTransitionFnHelper(f, PENDING());
  72. assert stateAfter == CANCELED() && f.selector == cancel(address[],uint256[],bytes[],bytes32).selector;
  73. }
  74. rule stateTransitionFn_ACTIVE(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
  75. uint8 stateAfter = stateTransitionFnHelper(f, ACTIVE());
  76. assert false;
  77. }
  78. rule stateTransitionFn_CANCELED(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
  79. uint8 stateAfter = stateTransitionFnHelper(f, CANCELED());
  80. assert false;
  81. }
  82. rule stateTransitionFn_DEFEATED(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
  83. uint8 stateAfter = stateTransitionFnHelper(f, DEFEATED());
  84. assert false;
  85. }
  86. rule stateTransitionFn_SUCCEEDED(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
  87. uint8 stateAfter = stateTransitionFnHelper(f, SUCCEEDED());
  88. assert (
  89. (stateAfter == QUEUED() && f.selector == queue(address[],uint256[],bytes[],bytes32).selector) ||
  90. (stateAfter == EXECUTED() && f.selector == execute(address[],uint256[],bytes[],bytes32).selector)
  91. );
  92. }
  93. rule stateTransitionFn_QUEUED(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
  94. uint8 stateAfter = stateTransitionFnHelper(f, QUEUED());
  95. assert stateAfter == EXECUTED() && f.selector == execute(address[],uint256[],bytes[],bytes32).selector;
  96. }
  97. rule stateTransitionFn_EXECUTED(method f) filtered { f -> !assumedSafeOrDuplicate(f) } {
  98. uint8 stateAfter = stateTransitionFnHelper(f, EXECUTED());
  99. assert false;
  100. }
  101. /*
  102. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  103. │ Rule: State transitions caused by time passing │
  104. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  105. */
  106. // The timelockId can be set in states QUEUED, EXECUTED and CANCELED. However, checking the full scope of this results
  107. // in a timeout. This is a weaker version that is still useful
  108. invariant noTimelockBeforeEndOfVote(env e, uint256 pId)
  109. safeState(e, pId) == ACTIVE() => timelockId(pId) == 0
  110. rule stateTransitionWait(uint256 pId, env e1, env e2) {
  111. require clockSanity(e1);
  112. require clockSanity(e2);
  113. require clock(e2) > clock(e1);
  114. // Force the state to be consistent with e1 (before). We want the storage related to `pId` to match what is
  115. // possible before the time passes. We don't want the state transition include elements that cannot have happened
  116. // before e1. This ensure that the e1 → e2 state transition is purelly a consequence of time passing.
  117. requireInvariant votesImplySnapshotPassed(e1, pId);
  118. requireInvariant noTimelockBeforeEndOfVote(e1, pId);
  119. uint8 stateBefore = safeState(e1, pId);
  120. uint8 stateAfter = safeState(e2, pId);
  121. assert (stateBefore != stateAfter) => (
  122. (stateBefore == PENDING() && stateAfter == ACTIVE() ) ||
  123. (stateBefore == PENDING() && stateAfter == DEFEATED() ) ||
  124. (stateBefore == ACTIVE() && stateAfter == SUCCEEDED()) ||
  125. (stateBefore == ACTIVE() && stateAfter == DEFEATED() )
  126. );
  127. }
  128. /*
  129. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  130. │ Rule: State corresponds to the vote timing and results │
  131. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  132. */
  133. rule stateIsConsistentWithVotes(uint256 pId, env e) {
  134. require clockSanity(e);
  135. requireInvariant proposalStateConsistency(pId);
  136. uint48 currentClock = clock(e);
  137. uint8 currentState = safeState(e, pId);
  138. uint256 snapshot = proposalSnapshot(pId);
  139. uint256 deadline = proposalDeadline(pId);
  140. bool quorumSuccess = quorumReached(pId);
  141. bool voteSuccess = voteSucceeded(pId);
  142. // Pending: before vote starts
  143. assert currentState == PENDING() => (
  144. snapshot >= currentClock
  145. );
  146. // Active: after vote starts & before vote ends
  147. assert currentState == ACTIVE() => (
  148. snapshot < currentClock &&
  149. deadline >= currentClock
  150. );
  151. // Succeeded: after vote end, with vote successful and quorum reached
  152. assert currentState == SUCCEEDED() => (
  153. deadline < currentClock &&
  154. (
  155. quorumSuccess &&
  156. voteSuccess
  157. )
  158. );
  159. // Defeated: after vote end, with vote not successful or quorum not reached
  160. assert currentState == DEFEATED() => (
  161. deadline < currentClock &&
  162. (
  163. !quorumSuccess ||
  164. !voteSuccess
  165. )
  166. );
  167. }
  168. /*
  169. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  170. │ [NEED WORK] Rule: `updateQuorumNumerator` cannot cause quorumReached to change. │
  171. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  172. */
  173. //// This would be nice, but its way to slow to run because "quorumReached" is a FV nightmare
  174. //// Also, for it to work we need to prove that the checkpoints have (strictly) increasing keys.
  175. // rule onlyVoteCanChangeQuorumReached(uint256 pId, env e, method f, calldataarg args)
  176. // filtered { f -> !assumedSafeOrDuplicate(f) }
  177. // {
  178. // require clockSanity(e);
  179. // require clock(e) > proposalSnapshot(pId); // vote has started
  180. // require quorumNumeratorLength() < max_uint256; // sanity
  181. //
  182. // bool quorumReachedBefore = quorumReached(pId);
  183. //
  184. // uint256 snapshot = proposalSnapshot(pId);
  185. // uint256 totalSupply = token_getPastTotalSupply(snapshot);
  186. //
  187. // f(e, args);
  188. //
  189. // // Needed because the prover doesn't understand the checkpoint properties of the voting token.
  190. // require clock(e) > snapshot => token_getPastTotalSupply(snapshot) == totalSupply;
  191. //
  192. // assert quorumReached(pId) != quorumReachedBefore => (
  193. // !quorumReachedBefore &&
  194. // votingAll(f)
  195. // );
  196. // }
  197. //// To prove that, we need to prove that the checkpoints have (strictly) increasing keys.
  198. //// otherwise it gives us counter example where the checkpoint history has keys:
  199. //// [ 12,12,13,13,12] and the lookup obviously fail to get the correct value
  200. // rule quorumUpdateDoesntAffectPastProposals(uint256 pId, env e) {
  201. // require clockSanity(e);
  202. // require clock(e) > proposalSnapshot(pId); // vote has started
  203. // require quorumNumeratorLength() < max_uint256; // sanity
  204. //
  205. // bool quorumReachedBefore = quorumReached(pId);
  206. //
  207. // uint256 newQuorumNumerator;
  208. // updateQuorumNumerator(e, newQuorumNumerator);
  209. //
  210. // assert quorumReached(pId) == quorumReachedBefore;
  211. // }