GovernorFunctions.spec 13 KB

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