GovernorBase.spec 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380
  1. //////////////////////////////////////////////////////////////////////////////
  2. ///////////////////// Governor.sol base definitions //////////////////////////
  3. //////////////////////////////////////////////////////////////////////////////
  4. methods {
  5. hashProposal(address[],uint256[],bytes[],bytes32) returns uint256 envfree
  6. state(uint256) returns uint8
  7. proposalThreshold() returns uint256 envfree
  8. proposalSnapshot(uint256) returns uint256 envfree // matches proposalVoteStart
  9. proposalDeadline(uint256) returns uint256 envfree // matches proposalVoteEnd
  10. propose(address[], uint256[], bytes[], string) returns uint256
  11. execute(address[], uint256[], bytes[], bytes32) returns uint256
  12. cancel(address[], uint256[], bytes[], bytes32) returns uint256
  13. getVotes(address, uint256) returns uint256 => DISPATCHER(true)
  14. getVotesWithParams(address, uint256, bytes) returns uint256 => DISPATCHER(true)
  15. castVote(uint256, uint8) returns uint256
  16. castVoteWithReason(uint256, uint8, string) returns uint256
  17. castVoteWithReasonAndParams(uint256, uint8, string, bytes) returns uint256
  18. // GovernorTimelockController
  19. queue(address[], uint256[], bytes[], bytes32) returns uint256
  20. // GovernorCountingSimple
  21. hasVoted(uint256, address) returns bool envfree
  22. updateQuorumNumerator(uint256)
  23. // harness functions
  24. getAgainstVotes(uint256) returns uint256 envfree
  25. getAbstainVotes(uint256) returns uint256 envfree
  26. getForVotes(uint256) returns uint256 envfree
  27. getExecutor(uint256) returns bool envfree
  28. isExecuted(uint256) returns bool envfree
  29. isCanceled(uint256) returns bool envfree
  30. // full harness functions
  31. getPastTotalSupply(uint256) returns uint256 => DISPATCHER(true)
  32. /// getPastVotes(address, uint256) returns uint256 => DISPATCHER(true)
  33. // internal functions made public in harness:
  34. quorumReached(uint256) returns bool
  35. voteSucceeded(uint256) returns bool envfree
  36. }
  37. //////////////////////////////////////////////////////////////////////////////
  38. //////////////////////////////// Definitions /////////////////////////////////
  39. //////////////////////////////////////////////////////////////////////////////
  40. // proposal was created - relation proved in noStartBeforeCreation
  41. definition proposalCreated(uint256 pId) returns bool =
  42. proposalSnapshot(pId) > 0
  43. && proposalDeadline(pId) > 0;
  44. //////////////////////////////////////////////////////////////////////////////
  45. ///////////////////////////// Helper Functions ///////////////////////////////
  46. //////////////////////////////////////////////////////////////////////////////
  47. function helperFunctionsWithRevert(uint256 proposalId, method f, env e) {
  48. address[] targets; uint256[] values; bytes[] calldatas; string reason; bytes32 descriptionHash;
  49. uint8 support; uint8 v; bytes32 r; bytes32 s; bytes params;
  50. if (f.selector == propose(address[], uint256[], bytes[], string).selector)
  51. {
  52. uint256 result = propose@withrevert(e, targets, values, calldatas, reason);
  53. require(result == proposalId);
  54. }
  55. else if (f.selector == execute(address[], uint256[], bytes[], bytes32).selector)
  56. {
  57. uint256 result = execute@withrevert(e, targets, values, calldatas, descriptionHash);
  58. require(result == proposalId);
  59. }
  60. else if (f.selector == queue(address[], uint256[], bytes[], bytes32).selector)
  61. {
  62. uint256 result = queue@withrevert(e, targets, values, calldatas, descriptionHash);
  63. require(result == proposalId);
  64. }
  65. else if (f.selector == cancel(address[], uint256[], bytes[], bytes32).selector)
  66. {
  67. uint256 result = cancel@withrevert(e, targets, values, calldatas, descriptionHash);
  68. require(result == proposalId);
  69. }
  70. else if (f.selector == castVote(uint256, uint8).selector)
  71. {
  72. castVote@withrevert(e, proposalId, support);
  73. }
  74. else if (f.selector == castVoteWithReason(uint256, uint8, string).selector)
  75. {
  76. castVoteWithReason@withrevert(e, proposalId, support, reason);
  77. }
  78. else if (f.selector == castVoteWithReasonAndParams(uint256,uint8,string,bytes).selector)
  79. {
  80. castVoteWithReasonAndParams@withrevert(e, proposalId, support, reason, params);
  81. }
  82. else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector)
  83. {
  84. castVoteBySig@withrevert(e, proposalId, support, v, r, s);
  85. }
  86. else if (f.selector == castVoteWithReasonAndParamsBySig(uint256,uint8,string,bytes,uint8,bytes32,bytes32).selector)
  87. {
  88. castVoteWithReasonAndParamsBySig@withrevert(e, proposalId, support, reason, params, v, r, s);
  89. }
  90. else
  91. {
  92. calldataarg args;
  93. f@withrevert(e, args);
  94. }
  95. }
  96. /*
  97. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  98. ///////////////////////////////////////////////////// State Diagram //////////////////////////////////////////////////////////
  99. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  100. // //
  101. // castVote(s)() //
  102. // ------------- propose() ---------------------- time pass --------------- time passes ----------- //
  103. // | No Proposal | --------> | Before Start (Delay) | --------> | Voting Period | ----------------------> | execute() | //
  104. // ------------- ---------------------- --------------- -> Executed/Canceled ----------- //
  105. // ------------------------------------------------------------|---------------|-------------------------|--------------> //
  106. // t start end timelock //
  107. // //
  108. //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  109. */
  110. ///////////////////////////////////////////////////////////////////////////////////////
  111. ///////////////////////////////// Global Valid States /////////////////////////////////
  112. ///////////////////////////////////////////////////////////////////////////////////////
  113. /*
  114. * Start and end date are either initialized (non zero) or uninitialized (zero) simultaneously
  115. * This invariant assumes that the block number cannot be 0 at any stage of the contract cycle
  116. * This is very safe assumption as usually the 0 block is genesis block which is uploaded with data
  117. * by the developers and will not be valid to raise proposals (at the current way that block chain is functioning)
  118. */
  119. // To use env with general preserved block disable type checking [--disableLocalTypeChecking]
  120. invariant startAndEndDatesNonZero(uint256 pId)
  121. proposalSnapshot(pId) != 0 <=> proposalDeadline(pId) != 0
  122. { preserved with (env e){
  123. require e.block.number > 0;
  124. }}
  125. /*
  126. * If a proposal is canceled it must have a start and an end date
  127. */
  128. // To use env with general preserved block disable type checking [--disableLocalTypeChecking]
  129. invariant canceledImplyStartAndEndDateNonZero(uint pId)
  130. isCanceled(pId) => proposalSnapshot(pId) != 0
  131. {preserved with (env e){
  132. require e.block.number > 0;
  133. }}
  134. /*
  135. * If a proposal is executed it must have a start and an end date
  136. */
  137. // To use env with general preserved block disable type checking [--disableLocalTypeChecking]
  138. invariant executedImplyStartAndEndDateNonZero(uint pId)
  139. isExecuted(pId) => proposalSnapshot(pId) != 0
  140. { preserved with (env e){
  141. requireInvariant startAndEndDatesNonZero(pId);
  142. require e.block.number > 0;
  143. }}
  144. /*
  145. * A proposal starting block number must be less or equal than the proposal end date
  146. */
  147. invariant voteStartBeforeVoteEnd(uint256 pId)
  148. // from < to <= because snapshot and deadline can be the same block number if delays are set to 0
  149. // This is possible before the integration of GovernorSettings.sol to the system.
  150. // After integration of GovernorSettings.sol the invariant expression should be changed from <= to <
  151. (proposalSnapshot(pId) > 0 => proposalSnapshot(pId) <= proposalDeadline(pId))
  152. // (proposalSnapshot(pId) > 0 => proposalSnapshot(pId) <= proposalDeadline(pId))
  153. { preserved {
  154. requireInvariant startAndEndDatesNonZero(pId);
  155. }}
  156. /*
  157. * A proposal cannot be both executed and canceled simultaneously.
  158. */
  159. invariant noBothExecutedAndCanceled(uint256 pId)
  160. !isExecuted(pId) || !isCanceled(pId)
  161. /*
  162. * A proposal could be executed only if quorum was reached and vote succeeded
  163. */
  164. rule executionOnlyIfQuoromReachedAndVoteSucceeded(uint256 pId, env e, method f){
  165. bool isExecutedBefore = isExecuted(pId);
  166. bool quorumReachedBefore = quorumReached(e, pId);
  167. bool voteSucceededBefore = voteSucceeded(pId);
  168. calldataarg args;
  169. f(e, args);
  170. bool isExecutedAfter = isExecuted(pId);
  171. assert (!isExecutedBefore && isExecutedAfter) => (quorumReachedBefore && voteSucceededBefore), "quorum was changed";
  172. }
  173. ///////////////////////////////////////////////////////////////////////////////////////
  174. ////////////////////////////////// In-State Rules /////////////////////////////////////
  175. ///////////////////////////////////////////////////////////////////////////////////////
  176. //==========================================
  177. //------------- Voting Period --------------
  178. //==========================================
  179. /*
  180. * A user cannot vote twice
  181. */
  182. // Checked for castVote only. all 3 castVote functions call _castVote, so the completeness of the verification is counted on
  183. // the fact that the 3 functions themselves makes no changes, but rather call an internal function to execute.
  184. // That means that we do not check those 3 functions directly, however for castVote & castVoteWithReason it is quite trivial
  185. // to understand why this is ok. For castVoteBySig we basically assume that the signature referendum is correct without checking it.
  186. // We could check each function separately and pass the rule, but that would have uglyfied the code with no concrete
  187. // benefit, as it is evident that nothing is happening in the first 2 functions (calling a view function), and we do not desire to check the signature verification.
  188. rule doubleVoting(uint256 pId, uint8 sup, method f) {
  189. env e;
  190. address user = e.msg.sender;
  191. bool votedCheck = hasVoted(pId, user);
  192. castVote@withrevert(e, pId, sup);
  193. assert votedCheck => lastReverted, "double voting occurred";
  194. }
  195. ///////////////////////////////////////////////////////////////////////////////////////
  196. //////////////////////////// State Transitions Rules //////////////////////////////////
  197. ///////////////////////////////////////////////////////////////////////////////////////
  198. //===========================================
  199. //-------- Propose() --> End of Time --------
  200. //===========================================
  201. /*
  202. * Once a proposal is created, voteStart and voteEnd are immutable
  203. */
  204. rule immutableFieldsAfterProposalCreation(uint256 pId, method f) {
  205. uint256 _voteStart = proposalSnapshot(pId);
  206. uint256 _voteEnd = proposalDeadline(pId);
  207. require proposalCreated(pId); // startDate > 0
  208. env e; calldataarg arg;
  209. f(e, arg);
  210. uint256 voteStart_ = proposalSnapshot(pId);
  211. uint256 voteEnd_ = proposalDeadline(pId);
  212. assert _voteStart == voteStart_, "Start date was changed";
  213. assert _voteEnd == voteEnd_, "End date was changed";
  214. }
  215. /*
  216. * Voting cannot start at a block number prior to proposal’s creation block number
  217. */
  218. rule noStartBeforeCreation(uint256 pId) {
  219. uint256 previousStart = proposalSnapshot(pId);
  220. // This line makes sure that we see only cases where start date is changed from 0, i.e. creation of proposal
  221. // We proved in immutableFieldsAfterProposalCreation that once dates set for proposal, it cannot be changed
  222. require !proposalCreated(pId); // previousStart == 0;
  223. env e; calldataarg args;
  224. propose(e, args);
  225. uint256 newStart = proposalSnapshot(pId);
  226. // if created, start is after current block number (creation block)
  227. assert(newStart != previousStart => newStart >= e.block.number);
  228. }
  229. //============================================
  230. //--- End of Voting Period --> End of Time ---
  231. //============================================
  232. /*
  233. * A proposal can neither be executed nor canceled before it ends
  234. */
  235. // By induction it cannot be executed nor canceled before it starts, due to voteStartBeforeVoteEnd
  236. rule noExecuteOrCancelBeforeDeadline(uint256 pId, method f){
  237. require !isExecuted(pId) && !isCanceled(pId);
  238. env e; calldataarg args;
  239. f(e, args);
  240. assert e.block.number < proposalDeadline(pId) => (!isExecuted(pId) && !isCanceled(pId)), "executed/cancelled before deadline";
  241. }
  242. ////////////////////////////////////////////////////////////////////////////////
  243. ////////////////////// Integrity Of Functions (Unit Tests) /////////////////////
  244. ////////////////////////////////////////////////////////////////////////////////
  245. ////////////////////////////////////////////////////////////////////////////////
  246. ////////////////////////////// High Level Rules ////////////////////////////////
  247. ////////////////////////////////////////////////////////////////////////////////
  248. ////////////////////////////////////////////////////////////////////////////////
  249. ///////////////////////////// Not Categorized Yet //////////////////////////////
  250. ////////////////////////////////////////////////////////////////////////////////
  251. /*
  252. * All proposal specific (non-view) functions should revert if proposal is executed
  253. */
  254. // In this rule we show that if a function is executed, i.e. execute() was called on the proposal ID,
  255. // non of the proposal specific functions can make changes again. In executedOnlyAfterExecuteFunc
  256. // we connected the executed attribute to the execute() function, showing that only execute() can
  257. // change it, and that it will always change it.
  258. rule allFunctionsRevertIfExecuted(method f) filtered { f ->
  259. !f.isView && !f.isFallback
  260. && f.selector != updateTimelock(address).selector
  261. && f.selector != updateQuorumNumerator(uint256).selector
  262. && f.selector != queue(address[],uint256[],bytes[],bytes32).selector
  263. && f.selector != relay(address,uint256,bytes).selector
  264. && f.selector != 0xb9a61961 // __acceptAdmin()
  265. && f.selector != onERC721Received(address,address,uint256,bytes).selector
  266. && f.selector != onERC1155Received(address,address,uint256,uint256,bytes).selector
  267. && f.selector != onERC1155BatchReceived(address,address,uint256[],uint256[],bytes).selector
  268. } {
  269. env e; calldataarg args;
  270. uint256 pId;
  271. require(isExecuted(pId));
  272. requireInvariant noBothExecutedAndCanceled(pId);
  273. requireInvariant executedImplyStartAndEndDateNonZero(pId);
  274. helperFunctionsWithRevert(pId, f, e);
  275. assert(lastReverted, "Function was not reverted");
  276. }
  277. /*
  278. * All proposal specific (non-view) functions should revert if proposal is canceled
  279. */
  280. rule allFunctionsRevertIfCanceled(method f) filtered {
  281. f -> !f.isView && !f.isFallback
  282. && f.selector != updateTimelock(address).selector
  283. && f.selector != updateQuorumNumerator(uint256).selector
  284. && f.selector != queue(address[],uint256[],bytes[],bytes32).selector
  285. && f.selector != relay(address,uint256,bytes).selector
  286. && f.selector != 0xb9a61961 // __acceptAdmin()
  287. && f.selector != onERC721Received(address,address,uint256,bytes).selector
  288. && f.selector != onERC1155Received(address,address,uint256,uint256,bytes).selector
  289. && f.selector != onERC1155BatchReceived(address,address,uint256[],uint256[],bytes).selector
  290. } {
  291. env e; calldataarg args;
  292. uint256 pId;
  293. require(isCanceled(pId));
  294. requireInvariant noBothExecutedAndCanceled(pId);
  295. requireInvariant canceledImplyStartAndEndDateNonZero(pId);
  296. helperFunctionsWithRevert(pId, f, e);
  297. assert(lastReverted, "Function was not reverted");
  298. }
  299. /*
  300. * Proposal can be switched to executed only via execute() function
  301. */
  302. rule executedOnlyAfterExecuteFunc(address[] targets, uint256[] values, bytes[] calldatas, bytes32 descriptionHash, method f) {
  303. env e; calldataarg args;
  304. uint256 pId;
  305. bool executedBefore = isExecuted(pId);
  306. require(!executedBefore);
  307. helperFunctionsWithRevert(pId, f, e);
  308. bool executedAfter = isExecuted(pId);
  309. assert(executedAfter != executedBefore => f.selector == execute(address[], uint256[], bytes[], bytes32).selector, "isExecuted only changes in the execute method");
  310. }