GovernorBase.spec 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325
  1. //////////////////////////////////////////////////////////////////////////////
  2. ///////////////////// Governor.sol base definitions //////////////////////////
  3. //////////////////////////////////////////////////////////////////////////////
  4. methods {
  5. proposalSnapshot(uint256) returns uint256 envfree // matches proposalVoteStart
  6. proposalDeadline(uint256) returns uint256 envfree // matches proposalVoteEnd
  7. hashProposal(address[],uint256[],bytes[],bytes32) returns uint256 envfree
  8. isExecuted(uint256) returns bool envfree
  9. isCanceled(uint256) returns bool envfree
  10. execute(address[], uint256[], bytes[], bytes32) returns uint256
  11. hasVoted(uint256, address) returns bool
  12. castVote(uint256, uint8) returns uint256
  13. updateQuorumNumerator(uint256)
  14. // internal functions made public in harness:
  15. _quorumReached(uint256) returns bool
  16. _voteSucceeded(uint256) returns bool envfree
  17. _pId_Harness() returns uint256 envfree;
  18. // function summarization
  19. hashProposal(address[], uint256[], bytes[], bytes32) => CONSTANT
  20. proposalThreshold() returns uint256 envfree
  21. getVotes(address, uint256) returns uint256 => DISPATCHER(true)
  22. //getVotes(address, uint256) => DISPATCHER(true)
  23. getPastTotalSupply(uint256) returns uint256 envfree => DISPATCHER(true)
  24. //getPastTotalSupply(uint256) => DISPATCHER(true)
  25. getPastVotes(address, uint256) returns uint256 envfree => DISPATCHER(true)
  26. }
  27. //////////////////////////////////////////////////////////////////////////////
  28. ////////////////////////////////// Ghosts ////////////////////////////////////
  29. //////////////////////////////////////////////////////////////////////////////
  30. // ghost uniqueHashGhost(bytes32) returns uint256;
  31. //////////////////////////////////////////////////////////////////////////////
  32. ///////////////////////////// Helper Functions ///////////////////////////////
  33. //////////////////////////////////////////////////////////////////////////////
  34. function callFunctionWithProposal(uint256 proposalId, method f) {
  35. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descriptionHash;
  36. uint8 support; uint8 v; bytes32 r; bytes32 s;
  37. env e;
  38. if (f.selector == callPropose(address[], uint256[], bytes[]).selector) {
  39. uint256 result = callPropose@withrevert(e, targets, values, calldatas);
  40. require(proposalId == result);
  41. } else if (f.selector == execute(address[], uint256[], bytes[], bytes32).selector) {
  42. uint256 result = execute@withrevert(e, targets, values, calldatas, descriptionHash);
  43. require(result == proposalId);
  44. } else if (f.selector == castVote(uint256, uint8).selector) {
  45. castVote@withrevert(e, proposalId, support);
  46. } else if (f.selector == 0x7b3c71d3) {
  47. calldataarg args;
  48. require(_pId_Harness() == proposalId);
  49. f@withrevert(e, args);
  50. } else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector) {
  51. castVoteBySig@withrevert(e, proposalId, support, v, r, s);
  52. } else {
  53. calldataarg args;
  54. f@withrevert(e, args);
  55. }
  56. }
  57. /*
  58. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  59. ///////////////////////////////////////////////////// State Diagram //////////////////////////////////////////////////////////
  60. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  61. // //
  62. // castVote(s)() //
  63. // ------------- propose() ---------------------- time pass --------------- time passes ----------- //
  64. // | No Proposal | --------> | Before Start (Delay) | --------> | Voting Period | ----------------------> | execute() | //
  65. // ------------- ---------------------- --------------- -> Executed/Canceled ----------- //
  66. // ------------------------------------------------------------|---------------|-------------------------|--------------> //
  67. // t start end timelock //
  68. // //
  69. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  70. */
  71. ///////////////////////////////////////////////////////////////////////////////////////
  72. ///////////////////////////////// Global Valid States /////////////////////////////////
  73. ///////////////////////////////////////////////////////////////////////////////////////
  74. /*
  75. * If any of the properties are non zero, the rest has to be non zero
  76. */
  77. invariant proposalInitiated(uint256 pId)
  78. (proposalSnapshot(pId) != 0 <=> proposalDeadline(pId) != 0) &&
  79. (isCanceled(pId) => proposalSnapshot(pId) != 0) &&
  80. (isExecuted(pId) => proposalSnapshot(pId) != 0)
  81. /*{ preserved with (env e){
  82. require e.block.number > 0;
  83. }}*/
  84. /*
  85. * A proposal cannot end unless it started.
  86. */
  87. invariant voteStartBeforeVoteEnd(uint256 pId)
  88. (proposalSnapshot(pId) > 0 => proposalSnapshot(pId) < proposalDeadline(pId))
  89. && (proposalSnapshot(pId) == 0 => proposalDeadline(pId) == 0)
  90. /*
  91. * A proposal cannot be both executed and canceled.
  92. */
  93. invariant noBothExecutedAndCanceled(uint256 pId)
  94. !isExecuted(pId) || !isCanceled(pId)
  95. /**
  96. * A proposal could be executed only if quorum was reached and vote succeeded
  97. */
  98. invariant executionOnlyIfQuoromReachedAndVoteSucceeded(uint256 pId, env e)
  99. isExecuted(pId) => _quorumReached(e, pId) && _voteSucceeded(pId)
  100. ///////////////////////////////////////////////////////////////////////////////////////
  101. ////////////////////////////////// In-State Rules /////////////////////////////////////
  102. ///////////////////////////////////////////////////////////////////////////////////////
  103. //==========================================
  104. //------------- Voting Period --------------
  105. //==========================================
  106. /*
  107. * A user cannot vote twice
  108. */
  109. rule doubleVoting(uint256 pId, uint8 sup) {
  110. env e;
  111. address user = e.msg.sender;
  112. bool votedCheck = hasVoted(e, pId, user);
  113. castVote@withrevert(e, pId, sup);
  114. bool reverted = lastReverted;
  115. assert votedCheck => reverted, "double voting accured";
  116. }
  117. ///////////////////////////////////////////////////////////////////////////////////////
  118. //////////////////////////// State Transitions Rules //////////////////////////////////
  119. ///////////////////////////////////////////////////////////////////////////////////////
  120. //===========================================
  121. //-------- Propose() --> End of Time --------
  122. //===========================================
  123. /*
  124. * The voting must start not before the proposal’s creation time
  125. */
  126. rule noStartBeforeCreation(uint256 pId) {
  127. uint256 previousStart = proposalSnapshot(pId);
  128. require previousStart == 0;
  129. env e;
  130. calldataarg arg;
  131. propose(e, arg);
  132. uint newStart = proposalSnapshot(pId);
  133. // if created, start is after current block number (creation block)
  134. assert(newStart != previousStart => newStart >= e.block.number);
  135. }
  136. /*
  137. * Once a proposal is created, voteStart and voteEnd are immutable
  138. */
  139. rule immutableFieldsAfterProposalCreation(uint256 pId, method f) {
  140. uint _voteStart = proposalSnapshot(pId);
  141. uint _voteEnd = proposalDeadline(pId);
  142. require _voteStart > 0; // proposal was created - relation proved in noStartBeforeCreation
  143. env e;
  144. calldataarg arg;
  145. f(e, arg);
  146. uint voteStart_ = proposalSnapshot(pId);
  147. uint voteEnd_ = proposalDeadline(pId);
  148. assert _voteStart == voteStart_;
  149. assert _voteEnd == voteEnd_;
  150. }
  151. /*
  152. * A proposal cannot be neither executed nor canceled before it starts
  153. */
  154. rule noExecuteOrCancelBeforeStarting(uint256 pId, method f){
  155. env e;
  156. require !isExecuted(pId) && !isCanceled(pId);
  157. calldataarg arg;
  158. f(e, arg);
  159. assert e.block.number < proposalSnapshot(pId) => (!isExecuted(pId) && !isCanceled(pId)), "executed/cancelled before start";
  160. }
  161. //============================================
  162. //--- End of Voting Period --> End of Time ---
  163. //============================================
  164. /*
  165. * A proposal cannot be neither executed nor canceled before proposal's deadline
  166. */
  167. rule noExecuteOrCancelBeforeDeadline(uint256 pId, method f){
  168. env e;
  169. requireInvariant voteStartBeforeVoteEnd(pId);
  170. require !isExecuted(pId) && !isCanceled(pId);
  171. calldataarg arg;
  172. f(e, arg);
  173. assert e.block.number < proposalDeadline(pId) => (!isExecuted(pId) && !isCanceled(pId)), "executed/cancelled before deadline";
  174. }
  175. ////////////////////////////////////////////////////////////////////////////////
  176. ////////////////////// Integrity Of Functions (Unit Tests) /////////////////////
  177. ////////////////////////////////////////////////////////////////////////////////
  178. /**
  179. * Check hashProposal hashing is reliable (different inputs lead to different buffers hashed)
  180. */
  181. /*
  182. rule checkHashProposal {
  183. address[] t1;
  184. address[] t2;
  185. uint256[] v1;
  186. uint256[] v2;
  187. bytes[] c1;
  188. bytes[] c2;
  189. bytes32 d1;
  190. bytes32 d2;
  191. uint256 h1 = hashProposal(t1,v1,c1,d1);
  192. uint256 h2 = hashProposal(t2,v2,c2,d2);
  193. bool equalHashes = h1 == h2;
  194. assert equalHashes => t1.length == t2.length;
  195. assert equalHashes => v1.length == v2.length;
  196. assert equalHashes => c1.length == c2.length;
  197. assert equalHashes => d1 == d2;
  198. }
  199. */
  200. ////////////////////////////////////////////////////////////////////////////////
  201. ////////////////////////////// High Level Rules ////////////////////////////////
  202. ////////////////////////////////////////////////////////////////////////////////
  203. ////////////////////////////////////////////////////////////////////////////////
  204. ///////////////////////////// Not Categorized Yet //////////////////////////////
  205. ////////////////////////////////////////////////////////////////////////////////
  206. /*
  207. * all non-view functions should revert if proposal is executed
  208. */
  209. // summarization - hashProposal => Const - for any set of arguments passed to the function the same value will be returned.
  210. // that means that for different arguments passed, the same value will be returned, for example: func(a,b,c,d) == func(o,p,g,r)
  211. // the summarization is not an under estimation in this case, because we want to check that for a specific proposal ID (pId), any
  212. // (non view) function call is reverting. We dont care what happen with other pIds, and dont care how the hash function generates the ID.
  213. rule allFunctionsRevertIfExecuted(method f) filtered { f -> !f.isView && f.selector != 0x7d5e81e2 && !f.isFallback && f.selector != updateQuorumNumerator(uint256).selector && f.selector != 0xa890c910} {
  214. env e; calldataarg args; // ^ ^
  215. uint256 pId; // propose updateTimelock
  216. require(isExecuted(pId));
  217. requireInvariant proposalInitiated(pId);
  218. requireInvariant noBothExecutedAndCanceled(pId);
  219. callFunctionWithProposal(pId, f);
  220. assert(lastReverted, "Function was not reverted");
  221. }
  222. /*
  223. * all non-view functions should revert if proposal is canceled
  224. */
  225. rule allFunctionsRevertIfCanceled(method f) filtered { f -> !f.isView && f.selector != 0x7d5e81e2 && !f.isFallback && f.selector != updateQuorumNumerator(uint256).selector && f.selector != 0xa890c910} {
  226. env e; calldataarg args; // ^ ^
  227. uint256 pId; // propose updateTimelock
  228. require(isCanceled(pId));
  229. requireInvariant noBothExecutedAndCanceled(pId);
  230. requireInvariant proposalInitiated(pId);
  231. callFunctionWithProposal(pId, f);
  232. assert(lastReverted, "Function was not reverted");
  233. }
  234. /*
  235. * Shows that executed can only change due to execute()
  236. */
  237. rule executedOnlyAfterExecuteFunc(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash, method f) {
  238. env e; calldataarg args;
  239. uint256 pId;
  240. bool executedBefore = isExecuted(pId);
  241. require(!executedBefore);
  242. callFunctionWithProposal(pId, f);
  243. require(!lastReverted);
  244. // execute(e, targets, values, calldatas, descriptionHash);
  245. bool executedAfter = isExecuted(pId);
  246. assert(executedAfter != executedBefore, "executed property did not change");
  247. }
  248. /*
  249. * User should not be able to affect proposal threshold
  250. */
  251. rule unaffectedThreshhold(method f){
  252. uint256 thresholdBefore = proposalThreshold();
  253. env e;
  254. calldataarg args;
  255. f(e, args);
  256. uint256 thresholdAfter = proposalThreshold();
  257. assert thresholdBefore == thresholdAfter, "threshold was changed";
  258. }