GovernorBase.spec 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375
  1. //////////////////////////////////////////////////////////////////////////////
  2. ///////////////////// Governor.sol base definitions //////////////////////////
  3. //////////////////////////////////////////////////////////////////////////////
  4. using ERC20VotesHarness as erc20votes
  5. methods {
  6. proposalSnapshot(uint256) returns uint256 envfree // matches proposalVoteStart
  7. proposalDeadline(uint256) returns uint256 envfree // matches proposalVoteEnd
  8. hashProposal(address[],uint256[],bytes[],bytes32) returns uint256 envfree
  9. isExecuted(uint256) returns bool envfree
  10. isCanceled(uint256) returns bool envfree
  11. execute(address[], uint256[], bytes[], bytes32) returns uint256
  12. hasVoted(uint256, address) returns bool
  13. castVote(uint256, uint8) returns uint256
  14. updateQuorumNumerator(uint256)
  15. queue(address[], uint256[], bytes[], bytes32) returns uint256
  16. // internal functions made public in harness:
  17. _quorumReached(uint256) returns bool
  18. _voteSucceeded(uint256) returns bool envfree
  19. _pId_Harness() returns uint256 envfree;
  20. // function summarization
  21. proposalThreshold() returns uint256 envfree
  22. getVotes(address, uint256) returns uint256 => DISPATCHER(true)
  23. erc20votes.getPastTotalSupply(uint256) returns uint256
  24. erc20votes.getPastVotes(address, uint256) returns uint256
  25. scheduleBatch(address[],uint256[],bytes[],bytes32,bytes32,uint256) => NONDET
  26. executeBatch(address[], uint256[], bytes[], bytes32, bytes32) => NONDET
  27. }
  28. //////////////////////////////////////////////////////////////////////////////
  29. ///////////////////////////// Helper Functions ///////////////////////////////
  30. //////////////////////////////////////////////////////////////////////////////
  31. function callFunctionWithProposal(uint256 proposalId, method f) {
  32. address[] targets; uint256[] values; bytes[] calldatas; bytes32 descriptionHash;
  33. uint8 support; uint8 v; bytes32 r; bytes32 s;
  34. env e;
  35. if (f.selector == callPropose(address[], uint256[], bytes[]).selector) {
  36. uint256 result = callPropose@withrevert(e, targets, values, calldatas);
  37. require(proposalId == result);
  38. } else if (f.selector == execute(address[], uint256[], bytes[], bytes32).selector) {
  39. uint256 result = execute@withrevert(e, targets, values, calldatas, descriptionHash);
  40. require(result == proposalId);
  41. } else if (f.selector == castVote(uint256, uint8).selector) {
  42. castVote@withrevert(e, proposalId, support);
  43. } else if (f.selector == 0x7b3c71d3 /* castVoteWithReason */) {
  44. calldataarg args;
  45. require(_pId_Harness() == proposalId);
  46. f@withrevert(e, args);
  47. } else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector) {
  48. castVoteBySig@withrevert(e, proposalId, support, v, r, s);
  49. } else if (f.selector == queue(address[], uint256[], bytes[], bytes32).selector) {
  50. require targets.length <= 1 && values.length <= 1 && calldatas.length <= 1;
  51. queue@withrevert(e, targets, values, calldatas, descriptionHash);
  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. * Start and end date are either initialized (non zero) or uninitialized (zero) simultaneously
  76. */
  77. // To use env with general preserved block first disable type checking then
  78. // use Uri's branch - --staging uri/add_with_env_to_preserved_all
  79. invariant startAndEndDatesNonZero(uint256 pId)
  80. proposalSnapshot(pId) != 0 <=> proposalDeadline(pId) != 0
  81. /*{ preserved with (env e){
  82. require e.block.number > 0;
  83. }}*/
  84. /*
  85. * If a proposal is canceled it must have a start and an end date
  86. */
  87. // To use env with general preserved block first disable type checking then
  88. // use Uri's branch - --staging uri/add_with_env_to_preserved_all
  89. invariant canceledImplyStartAndEndDateNonZero(uint pId)
  90. isCanceled(pId) => proposalSnapshot(pId) != 0
  91. /*{ preserved with (env e){
  92. requireInvariant startAndEndDatesNonZero(pId);
  93. require e.block.number > 0;
  94. }}*/
  95. /*
  96. * If a proposal is executed it must have a start and an end date
  97. */
  98. // To use env with general preserved block first disable type checking then
  99. // use Uri's branch - --staging uri/add_with_env_to_preserved_all
  100. invariant executedImplyStartAndEndDateNonZero(uint pId)
  101. isExecuted(pId) => proposalSnapshot(pId) != 0
  102. /*{ preserved with (env e){
  103. requireInvariant startAndEndDatesNonZero(pId);
  104. require e.block.number > 0;
  105. }}*/
  106. /*
  107. * A proposal starting block number must be <= to the proposal end date
  108. */
  109. invariant voteStartBeforeVoteEnd(uint256 pId)
  110. // from < to <= because snapshot and deadline can be the same block number if delays are set to 0
  111. // This is possible before the integration of GovernorSettings.sol to the system.
  112. // After integration of GovernorSettings.sol the invariant expression should be changed from <= to <
  113. (proposalSnapshot(pId) > 0 => proposalSnapshot(pId) <= proposalDeadline(pId))
  114. { preserved {
  115. requireInvariant startAndEndDatesNonZero(pId);
  116. }}
  117. /*
  118. * A proposal cannot be both executed and canceled simultaneously.
  119. */
  120. invariant noBothExecutedAndCanceled(uint256 pId)
  121. !isExecuted(pId) || !isCanceled(pId)
  122. /*
  123. * A proposal could be executed only if quorum was reached and vote succeeded
  124. */
  125. // the executeBatch line in _execute was commented in GovernorTimelockContril.sol
  126. rule executionOnlyIfQuoromReachedAndVoteSucceeded(uint256 pId, env e, method f){
  127. bool isExecutedBefore = isExecuted(pId);
  128. calldataarg args;
  129. f(e, args);
  130. bool isExecutedAfter = isExecuted(pId);
  131. assert ((isExecutedBefore != isExecutedAfter) && !isExecutedBefore) => (_quorumReached(e, pId) && _voteSucceeded(pId)), "quorum was changed";
  132. }
  133. ///////////////////////////////////////////////////////////////////////////////////////
  134. ////////////////////////////////// In-State Rules /////////////////////////////////////
  135. ///////////////////////////////////////////////////////////////////////////////////////
  136. //==========================================
  137. //------------- Voting Period --------------
  138. //==========================================
  139. /*
  140. * A user cannot vote twice
  141. */
  142. rule doubleVoting(uint256 pId, uint8 sup, method f) filtered { f-> f.selector == castVote(uint256, uint8).selector ||
  143. f.selector == castVoteWithReason(uint256, uint8, string).selector ||
  144. f.selector == castVoteBySig(uint256, uint8, uint8, bytes32, bytes32).selector} {
  145. env e; calldataarg args;
  146. address user = e.msg.sender;
  147. bool votedCheck = hasVoted(e, pId, user);
  148. if (f.selector == castVote(uint256, uint8).selector)
  149. {
  150. castVote@withrevert(e, pId, sup);
  151. } else if (f.selector == castVoteWithReason(uint256, uint8, string).selector) {
  152. string reason;
  153. castVoteWithReason@withrevert(e, pId, sup, reason);
  154. } else if (f.selector == castVoteBySig(uint256, uint8, uint8, bytes32, bytes32).selector) {
  155. uint8 v; bytes32 r; bytes32 s;
  156. castVoteBySig@withrevert(e, pId, sup, v, r, s);
  157. } else{
  158. f@withrevert(e, args);
  159. }
  160. assert votedCheck => lastReverted, "double voting accured";
  161. }
  162. ///////////////////////////////////////////////////////////////////////////////////////
  163. //////////////////////////// State Transitions Rules //////////////////////////////////
  164. ///////////////////////////////////////////////////////////////////////////////////////
  165. //===========================================
  166. //-------- Propose() --> End of Time --------
  167. //===========================================
  168. /*
  169. * The voting must start not before the proposal’s creation time
  170. */
  171. rule noStartBeforeCreation(uint256 pId) {
  172. uint256 previousStart = proposalSnapshot(pId);
  173. require previousStart == 0;
  174. env e;
  175. calldataarg arg;
  176. propose(e, arg);
  177. uint newStart = proposalSnapshot(pId);
  178. // if created, start is after current block number (creation block)
  179. assert(newStart != previousStart => newStart >= e.block.number);
  180. }
  181. /*
  182. * Once a proposal is created, voteStart and voteEnd are immutable
  183. */
  184. rule immutableFieldsAfterProposalCreation(uint256 pId, method f) {
  185. uint _voteStart = proposalSnapshot(pId);
  186. uint _voteEnd = proposalDeadline(pId);
  187. require _voteStart > 0; // proposal was created - relation proved in noStartBeforeCreation
  188. env e;
  189. calldataarg arg;
  190. f(e, arg);
  191. uint voteStart_ = proposalSnapshot(pId);
  192. uint voteEnd_ = proposalDeadline(pId);
  193. assert _voteStart == voteStart_;
  194. assert _voteEnd == voteEnd_;
  195. }
  196. /*
  197. * A proposal cannot be neither executed nor canceled before it starts
  198. */
  199. rule noExecuteOrCancelBeforeStarting(uint256 pId, method f){
  200. env e;
  201. require !isExecuted(pId) && !isCanceled(pId);
  202. calldataarg arg;
  203. f(e, arg);
  204. assert e.block.number < proposalSnapshot(pId) => (!isExecuted(pId) && !isCanceled(pId)), "executed/cancelled before start";
  205. }
  206. //============================================
  207. //--- End of Voting Period --> End of Time ---
  208. //============================================
  209. /*
  210. * A proposal cannot be neither executed nor canceled before proposal's deadline
  211. */
  212. rule noExecuteOrCancelBeforeDeadline(uint256 pId, method f){
  213. env e;
  214. requireInvariant voteStartBeforeVoteEnd(pId);
  215. require !isExecuted(pId) && !isCanceled(pId);
  216. calldataarg arg;
  217. f(e, arg);
  218. assert e.block.number < proposalDeadline(pId) => (!isExecuted(pId) && !isCanceled(pId)), "executed/cancelled before deadline";
  219. }
  220. ////////////////////////////////////////////////////////////////////////////////
  221. ////////////////////// Integrity Of Functions (Unit Tests) /////////////////////
  222. ////////////////////////////////////////////////////////////////////////////////
  223. /**
  224. * Check hashProposal hashing is reliable (different inputs lead to different buffers hashed)
  225. */
  226. /*
  227. rule checkHashProposal {
  228. address[] t1;
  229. address[] t2;
  230. uint256[] v1;
  231. uint256[] v2;
  232. bytes[] c1;
  233. bytes[] c2;
  234. bytes32 d1;
  235. bytes32 d2;
  236. uint256 h1 = hashProposal(t1,v1,c1,d1);
  237. uint256 h2 = hashProposal(t2,v2,c2,d2);
  238. bool equalHashes = h1 == h2;
  239. assert equalHashes => t1.length == t2.length;
  240. assert equalHashes => v1.length == v2.length;
  241. assert equalHashes => c1.length == c2.length;
  242. assert equalHashes => d1 == d2;
  243. }
  244. */
  245. ////////////////////////////////////////////////////////////////////////////////
  246. ////////////////////////////// High Level Rules ////////////////////////////////
  247. ////////////////////////////////////////////////////////////////////////////////
  248. ////////////////////////////////////////////////////////////////////////////////
  249. ///////////////////////////// Not Categorized Yet //////////////////////////////
  250. ////////////////////////////////////////////////////////////////////////////////
  251. /*
  252. * all non-view functions should revert if proposal is executed
  253. */
  254. // summarization - hashProposal => Const - for any set of arguments passed to the function the same value will be returned.
  255. // 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)
  256. // the summarization is not an under estimation in this case, because we want to check that for a specific proposal ID (pId), any
  257. // (non view) function call is reverting. We dont care what happen with other pIds, and dont care how the hash function generates the ID.
  258. rule allFunctionsRevertIfExecuted(method f) filtered { f -> !f.isView && f.selector != 0x7d5e81e2 && !f.isFallback && f.selector != updateQuorumNumerator(uint256).selector && f.selector != 0xa890c910} {
  259. env e; calldataarg args; // ^ ^
  260. uint256 pId; // propose updateTimelock
  261. require(isExecuted(pId));
  262. // requireInvariant proposalInitiated(pId);
  263. requireInvariant noBothExecutedAndCanceled(pId);
  264. callFunctionWithProposal(pId, f);
  265. assert(lastReverted, "Function was not reverted");
  266. }
  267. /*
  268. * all non-view functions should revert if proposal is canceled
  269. */
  270. rule allFunctionsRevertIfCanceled(method f) filtered { f -> !f.isView && f.selector != 0x7d5e81e2 && !f.isFallback && f.selector != updateQuorumNumerator(uint256).selector && f.selector != 0xa890c910} {
  271. env e; calldataarg args; // ^ ^
  272. uint256 pId; // propose updateTimelock
  273. require(isCanceled(pId));
  274. requireInvariant noBothExecutedAndCanceled(pId);
  275. // requireInvariant proposalInitiated(pId);
  276. callFunctionWithProposal(pId, f);
  277. assert(lastReverted, "Function was not reverted");
  278. }
  279. /*
  280. * Shows that executed can only change due to execute()
  281. */
  282. rule executedOnlyAfterExecuteFunc(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash, method f) {
  283. env e; calldataarg args;
  284. uint256 pId;
  285. bool executedBefore = isExecuted(pId);
  286. require(!executedBefore);
  287. callFunctionWithProposal(pId, f);
  288. require(!lastReverted);
  289. // execute(e, targets, values, calldatas, descriptionHash);
  290. bool executedAfter = isExecuted(pId);
  291. assert(executedAfter != executedBefore, "executed property did not change");
  292. }
  293. /*
  294. * User should not be able to affect proposal threshold
  295. */
  296. rule unaffectedThreshhold(method f){
  297. uint256 thresholdBefore = proposalThreshold();
  298. env e;
  299. calldataarg args;
  300. f(e, args);
  301. uint256 thresholdAfter = proposalThreshold();
  302. assert thresholdBefore == thresholdAfter, "threshold was changed";
  303. }