GovernorFunctions.spec 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. import "helpers.spec"
  2. import "Governor.helpers.spec"
  3. import "GovernorInvariants.spec"
  4. use invariant proposalStateConsistency
  5. use invariant queuedImplyDeadlineOver
  6. /*
  7. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  8. │ Rule: propose effect and liveness. Includes "no double proposition" │
  9. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  10. */
  11. rule propose_liveness(uint256 pId, env e) {
  12. require nonpayable(e);
  13. require clockSanity(e);
  14. uint8 stateBefore = state(e, pId);
  15. address[] targets; uint256[] values; bytes[] calldatas; string descr;
  16. require pId == hashProposal(targets, values, calldatas, descr);
  17. propose@withrevert(e, targets, values, calldatas, descr);
  18. // liveness & double proposal
  19. assert !lastReverted <=> (
  20. stateBefore == UNSET() &&
  21. validProposal(targets, values, calldatas)
  22. );
  23. }
  24. rule propose_effect(uint256 pId, env e) {
  25. address[] targets; uint256[] values; bytes[] calldatas; string descr;
  26. require pId == hashProposal(targets, values, calldatas, descr);
  27. propose(e, targets, values, calldatas, descr);
  28. // effect
  29. assert state(e, pId) == PENDING();
  30. assert proposalProposer(pId) == e.msg.sender;
  31. assert proposalSnapshot(pId) == clock(e) + votingDelay();
  32. assert proposalDeadline(pId) == clock(e) + votingDelay() + votingPeriod();
  33. }
  34. rule propose_sideeffect(uint256 pId, env e, uint256 otherId) {
  35. uint8 otherStateBefore = state(e, otherId);
  36. uint256 otherVoteStart = proposalSnapshot(otherId);
  37. uint256 otherVoteEnd = proposalDeadline(otherId);
  38. address otherProposer = proposalProposer(otherId);
  39. address[] targets; uint256[] values; bytes[] calldatas; string descr;
  40. require pId == hashProposal(targets, values, calldatas, descr);
  41. propose(e, targets, values, calldatas, descr);
  42. // no side-effect
  43. assert state(e, otherId) != otherStateBefore => otherId == pId;
  44. assert proposalSnapshot(otherId) == otherVoteStart;
  45. assert proposalDeadline(otherId) == otherVoteEnd;
  46. assert proposalProposer(otherId) == otherProposer;
  47. }
  48. /*
  49. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  50. │ Rule: votes effect and liveness. Includes "A user cannot vote twice" │
  51. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  52. */
  53. rule castVote_liveness(uint256 pId, env e, method f)
  54. filtered { f -> voting(f) }
  55. {
  56. require nonpayable(e);
  57. require clockSanity(e);
  58. uint8 support;
  59. address voter;
  60. uint8 stateBefore = state(e, pId);
  61. bool hasVotedBefore = hasVoted(pId, voter);
  62. uint256 voterWeight = token_getPastVotes(voter, proposalSnapshot(pId));
  63. // voting weight overflow check
  64. require getAgainstVotes(pId) + voterWeight <= max_uint256;
  65. require getForVotes(pId) + voterWeight <= max_uint256;
  66. require getAbstainVotes(pId) + voterWeight <= max_uint256;
  67. helperVoteWithRevert(e, f, pId, voter, support);
  68. assert !lastReverted <=> (
  69. stateBefore == ACTIVE() &&
  70. !hasVotedBefore &&
  71. (support == 0 || support == 1 || support == 2)
  72. );
  73. }
  74. rule castVote_effect(uint256 pId, env e, method f)
  75. filtered { f -> voting(f) }
  76. {
  77. uint8 support;
  78. address voter;
  79. uint256 againstVotesBefore = getAgainstVotes(pId);
  80. uint256 forVotesBefore = getForVotes(pId);
  81. uint256 abstainVotesBefore = getAbstainVotes(pId);
  82. uint256 voterWeight = token_getPastVotes(voter, proposalSnapshot(pId));
  83. uint256 weight = helperVoteWithRevert(e, f, pId, voter, support);
  84. require !lastReverted;
  85. assert state(e, pId) == ACTIVE();
  86. assert voterWeight == weight;
  87. assert getAgainstVotes(pId) == againstVotesBefore + (support == 0 ? weight : 0);
  88. assert getForVotes(pId) == forVotesBefore + (support == 1 ? weight : 0);
  89. assert getAbstainVotes(pId) == abstainVotesBefore + (support == 2 ? weight : 0);
  90. assert hasVoted(pId, voter);
  91. }
  92. rule castVote_sideeffect(uint256 pId, env e, method f)
  93. filtered { f -> voting(f) }
  94. {
  95. uint8 support;
  96. address voter;
  97. address otherVoter;
  98. uint256 otherId;
  99. bool otherVotedBefore = hasVoted(otherId, otherVoter);
  100. uint256 otherAgainstVotesBefore = getAgainstVotes(otherId);
  101. uint256 otherForVotesBefore = getForVotes(otherId);
  102. uint256 otherAbstainVotesBefore = getAbstainVotes(otherId);
  103. helperVoteWithRevert(e, f, pId, voter, support);
  104. require !lastReverted;
  105. // no side-effect
  106. assert hasVoted(otherId, otherVoter) != otherVotedBefore => (otherId == pId && otherVoter == voter);
  107. assert getAgainstVotes(otherId) != otherAgainstVotesBefore => (otherId == pId);
  108. assert getForVotes(otherId) != otherForVotesBefore => (otherId == pId);
  109. assert getAbstainVotes(otherId) != otherAbstainVotesBefore => (otherId == pId);
  110. }
  111. /*
  112. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  113. │ Rule: queue effect and liveness. │
  114. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  115. */
  116. rule queue_liveness(uint256 pId, env e) {
  117. require nonpayable(e);
  118. require clockSanity(e);
  119. uint8 stateBefore = state(e, pId);
  120. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
  121. require pId == hashProposal(targets, values, calldatas, descrHash);
  122. queue@withrevert(e, targets, values, calldatas, descrHash);
  123. // liveness
  124. assert !lastReverted <=> stateBefore == SUCCEEDED();
  125. }
  126. rule queue_effect(uint256 pId, env e) {
  127. uint8 stateBefore = state(e, pId);
  128. bool queuedBefore = isQueued(pId);
  129. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
  130. require pId == hashProposal(targets, values, calldatas, descrHash);
  131. queue(e, targets, values, calldatas, descrHash);
  132. assert state(e, pId) == QUEUED();
  133. assert isQueued(pId);
  134. assert !queuedBefore;
  135. }
  136. rule queue_sideeffect(uint256 pId, env e, uint256 otherId) {
  137. uint8 otherStateBefore = state(e, otherId);
  138. bool otherQueuedBefore = isQueued(otherId);
  139. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
  140. require pId == hashProposal(targets, values, calldatas, descrHash);
  141. queue(e, targets, values, calldatas, descrHash);
  142. // no side-effect
  143. assert state(e, otherId) != otherStateBefore => otherId == pId;
  144. assert isQueued(otherId) != otherQueuedBefore => otherId == pId;
  145. }
  146. /*
  147. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  148. │ Rule: execute effect and liveness. │
  149. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  150. */
  151. rule execute_liveness(uint256 pId, env e) {
  152. require nonpayable(e);
  153. require clockSanity(e);
  154. uint8 stateBefore = state(e, pId);
  155. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
  156. require pId == hashProposal(targets, values, calldatas, descrHash);
  157. execute@withrevert(e, targets, values, calldatas, descrHash);
  158. // liveness: can't check full equivalence because of execution call reverts
  159. assert !lastReverted => (stateBefore == SUCCEEDED() || stateBefore == QUEUED());
  160. }
  161. rule execute_effect(uint256 pId, env e) {
  162. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
  163. require pId == hashProposal(targets, values, calldatas, descrHash);
  164. execute(e, targets, values, calldatas, descrHash);
  165. // effect
  166. assert state(e, pId) == EXECUTED();
  167. }
  168. rule execute_sideeffect(uint256 pId, env e, uint256 otherId) {
  169. uint8 otherStateBefore = state(e, otherId);
  170. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
  171. require pId == hashProposal(targets, values, calldatas, descrHash);
  172. execute(e, targets, values, calldatas, descrHash);
  173. // no side-effect
  174. assert state(e, otherId) != otherStateBefore => otherId == pId;
  175. }
  176. /*
  177. ┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
  178. │ Rule: cancel (public) effect and liveness. │
  179. └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
  180. */
  181. rule cancel_liveness(uint256 pId, env e) {
  182. require nonpayable(e);
  183. require clockSanity(e);
  184. requireInvariant queuedImplyDeadlineOver(e, pId);
  185. uint8 stateBefore = state(e, pId);
  186. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
  187. require pId == hashProposal(targets, values, calldatas, descrHash);
  188. cancel@withrevert(e, targets, values, calldatas, descrHash);
  189. // liveness
  190. assert !lastReverted <=> (
  191. stateBefore == PENDING() &&
  192. e.msg.sender == proposalProposer(pId)
  193. );
  194. }
  195. rule cancel_effect(uint256 pId, env e) {
  196. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
  197. require pId == hashProposal(targets, values, calldatas, descrHash);
  198. cancel(e, targets, values, calldatas, descrHash);
  199. // effect
  200. assert state(e, pId) == CANCELED();
  201. assert !isQueued(pId); // cancel resets timelockId
  202. }
  203. rule cancel_sideeffect(uint256 pId, env e, uint256 otherId) {
  204. uint8 otherStateBefore = state(e, otherId);
  205. bool otherQueuedBefore = isQueued(otherId);
  206. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descrHash;
  207. require pId == hashProposal(targets, values, calldatas, descrHash);
  208. cancel(e, targets, values, calldatas, descrHash);
  209. // no side-effect
  210. assert state(e, otherId) != otherStateBefore => otherId == pId;
  211. assert isQueued(otherId) != otherQueuedBefore => otherId == pId;
  212. }