GovernorCountingSimple.spec 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. import "GovernorBase.spec"
  2. methods {
  3. ghost_sum_vote_power_by_id(uint256) returns uint256 envfree
  4. // castVote(uint256, uint8) returns uint256
  5. // castVoteBySig(uint256,uint8,uint8,bytes32,bytes32) returns uint256
  6. snapshot(uint256) returns uint64 envfree
  7. quorum(uint256) returns uint256 envfree
  8. proposalVotes(uint256) returns (uint256, uint256, uint256) envfree
  9. quorumNumerator() returns uint256
  10. updateQuorumNumerator(uint256)
  11. _executor() returns address
  12. getVotes(address, uint256) returns uint256 envfree => DISPATCHER(true)
  13. //getVotes(address, uint256) => DISPATCHER(true)
  14. getPastTotalSupply(uint256) returns uint256 envfree => DISPATCHER(true)
  15. //getPastTotalSupply(uint256) => DISPATCHER(true)
  16. getPastVotes(address, uint256) returns uint256 envfree => DISPATCHER(true)
  17. }
  18. //////////////////////////////////////////////////////////////////////////////
  19. ///////////////////////////////// GHOSTS /////////////////////////////////////
  20. //////////////////////////////////////////////////////////////////////////////
  21. ghost hasVoteGhost(uint256) returns uint256 {
  22. init_state axiom forall uint256 pId. hasVoteGhost(pId) == 0;
  23. }
  24. //hook Sstore _proposalVotes[KEY uint256 pId].hasVoted[KEY address user] bool current_voting_State (bool old_voting_state) STORAGE{
  25. // havoc hasVoteGhost assuming forall uint256 p. ((p == pId && current_voting_State && !old_voting_state) ? (hasVoteGhost@new(p) == hasVoteGhost@old(p) + 1) :
  26. // (hasVoteGhost@new(p) == hasVoteGhost@old(p)));
  27. //}
  28. ghost sum_all_votes_power() returns uint256 {
  29. init_state axiom sum_all_votes_power() == 0;
  30. }
  31. //hook Sstore ghost_sum_vote_power_by_id [KEY uint256 pId] uint256 current_power(uint256 old_power) STORAGE {
  32. // havoc sum_all_votes_power assuming sum_all_votes_power@new() == sum_all_votes_power@old() - old_power + current_power;
  33. //}
  34. ghost tracked_weight(uint256) returns uint256 {
  35. init_state axiom forall uint256 p. tracked_weight(p) == 0;
  36. }
  37. ghost sum_tracked_weight() returns uint256 {
  38. init_state axiom sum_tracked_weight() == 0;
  39. }
  40. ghost votesAgainst() returns uint256 {
  41. init_state axiom votesAgainst() == 0;
  42. }
  43. ghost votesFor() returns uint256 {
  44. init_state axiom votesFor() == 0;
  45. }
  46. ghost votesAbstain() returns uint256 {
  47. init_state axiom votesAbstain() == 0;
  48. }
  49. hook Sstore _proposalVotes [KEY uint256 pId].againstVotes uint256 votes(uint256 old_votes) STORAGE {
  50. havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) &&
  51. (p != pId => tracked_weight@new(p) == tracked_weight@old(p));
  52. havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes;
  53. havoc votesAgainst assuming votesAgainst@new() == votesAgainst@old() - old_votes + votes;
  54. }
  55. hook Sstore _proposalVotes [KEY uint256 pId].forVotes uint256 votes(uint256 old_votes) STORAGE {
  56. havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) &&
  57. (p != pId => tracked_weight@new(p) == tracked_weight@old(p));
  58. havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes;
  59. havoc votesFor assuming votesFor@new() == votesFor@old() - old_votes + votes;
  60. }
  61. hook Sstore _proposalVotes [KEY uint256 pId].abstainVotes uint256 votes(uint256 old_votes) STORAGE {
  62. havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) &&
  63. (p != pId => tracked_weight@new(p) == tracked_weight@old(p));
  64. havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes;
  65. havoc votesAbstain assuming votesAbstain@new() == votesAbstain@old() - old_votes + votes;
  66. }
  67. ghost totalVotesPossible(uint256) returns uint256 {
  68. init_state axiom forall uint256 bn. totalVotesPossible(bn) == 0;
  69. }
  70. // Was done with _getVotes by mistake. Should check GovernorBasicHarness but _getVotes is from GovernorHarness
  71. //hook Sstore _getVotes[KEY address uId][KEY uint256 blockNumber] uint256 voteWeight(uint256 old_voteWeight) STORAGE {
  72. // havoc totalVotesPossible assuming forall uint256 bn.
  73. // (bn == blockNumber => totalVotesPossible@new(bn) == totalVotesPossible@old(bn) - old_voteWeight + voteWeight)
  74. // && (bn != blockNumber => totalVotesPossible@new(bn) == totalVotesPossible@old(bn));
  75. //
  76. //}
  77. //////////////////////////////////////////////////////////////////////////////
  78. ////////////////////////////// INVARIANTS ////////////////////////////////////
  79. //////////////////////////////////////////////////////////////////////////////
  80. /*
  81. * sum of all votes casted is equal to the sum of voting power of those who voted, per each proposal
  82. */
  83. invariant SumOfVotesCastEqualSumOfPowerOfVotedPerProposal(uint256 pId)
  84. tracked_weight(pId) == ghost_sum_vote_power_by_id(pId)
  85. /*
  86. * sum of all votes casted is equal to the sum of voting power of those who voted
  87. */
  88. invariant SumOfVotesCastEqualSumOfPowerOfVoted()
  89. sum_tracked_weight() == sum_all_votes_power()
  90. /*
  91. * totalVoted >= vote(id)
  92. */
  93. invariant OneIsNotMoreThanAll(uint256 pId)
  94. sum_all_votes_power() >= tracked_weight(pId)
  95. //NEED GHOST FIX
  96. /*
  97. * totalVotesPossible >= votePower(id)
  98. */
  99. //invariant possibleTotalVotes(uint256 pId)
  100. // tracked_weight(pId) <= totalVotesPossible(snapshot(pId))
  101. /*
  102. * totalVotesPossible >= votePower(id)
  103. */
  104. // invariant possibleTotalVotes(uint pId)
  105. //invariant trackedVsTotal(uint256 pId)
  106. // tracked_weight(pId) <= possibleMaxOfVoters(pId)
  107. /*
  108. rule someOtherRuleToRemoveLater(uint256 num){
  109. env e; calldataarg args; method f;
  110. uint256 x = hasVoteGhost(num);
  111. f(e, args);
  112. assert(false);
  113. }
  114. */
  115. /*
  116. * Checks that only one user is updated in the system when calling cast vote functions (assuming hasVoted is changing correctly, false->true, with every vote cast)
  117. */
  118. rule oneUserVotesInCast(uint256 pId, method f) filtered { f -> ((f.selector == castVote(uint256, uint8).selector) ||
  119. f.selector == castVoteBySig(uint256, uint8, uint8, bytes32, bytes32).selector) }{
  120. env e; calldataarg args;
  121. uint256 ghost_Before = hasVoteGhost(pId);
  122. f(e, args);
  123. uint256 ghost_After = hasVoteGhost(pId);
  124. assert(ghost_After == ghost_Before + 1, "Raised by more than 1");
  125. }
  126. /*
  127. * Checks that in any call to cast vote functions only the sender's value is updated
  128. */
  129. /*
  130. rule noVoteForSomeoneElse(uint256 pId, uint8 support){
  131. env e;
  132. address voter = e.msg.sender;
  133. bool hasVotedBefore = hasVoted(pId, voter);
  134. require(!hasVotedBefore);
  135. castVote(e, pId, support);
  136. bool hasVotedAfter = hasVoted(pId, voter);
  137. assert(hasVotedBefore != hasVotedAfter => forall address user. user != voter);
  138. }
  139. */
  140. // ok
  141. rule votingWeightMonotonicity(method f){
  142. uint256 votingWeightBefore = sum_tracked_weight();
  143. env e;
  144. calldataarg args;
  145. f(e, args);
  146. uint256 votingWeightAfter = sum_tracked_weight();
  147. assert votingWeightBefore <= votingWeightAfter, "Voting weight was decreased somehow";
  148. }
  149. // ok with this branch: thomas/bool-hook-values
  150. // can't use link because contracts are abstract, they don't have bytecode/constructor
  151. // add implementation harness
  152. rule quorumMonotonicity(method f, uint256 blockNumber){
  153. uint256 quorumBefore = quorum(blockNumber);
  154. env e;
  155. calldataarg args;
  156. f(e, args);
  157. uint256 quorumAfter = quorum(blockNumber);
  158. assert quorumBefore <= quorumAfter, "Quorum was decreased somehow";
  159. }
  160. function callFunctionWithParams(method f, uint256 proposalId, env e) {
  161. address[] targets;
  162. uint256[] values;
  163. bytes[] calldatas;
  164. bytes32 descriptionHash;
  165. uint8 support;
  166. uint8 v; bytes32 r; bytes32 s;
  167. if (f.selector == callPropose(address[], uint256[], bytes[]).selector) {
  168. uint256 result = callPropose(e, targets, values, calldatas);
  169. require result == proposalId;
  170. } else if (f.selector == execute(address[], uint256[], bytes[], bytes32).selector) {
  171. uint256 result = execute(e, targets, values, calldatas, descriptionHash);
  172. require result == proposalId;
  173. } else if (f.selector == castVote(uint256, uint8).selector) {
  174. castVote(e, proposalId, support);
  175. //} else if (f.selector == castVoteWithReason(uint256, uint8, string).selector) {
  176. // castVoteWithReason(e, proposalId, support, reason);
  177. } else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector) {
  178. castVoteBySig(e, proposalId, support, v, r, s);
  179. } else {
  180. calldataarg args;
  181. f(e,args);
  182. }
  183. }
  184. // getVotes() returns different results.
  185. // how to ensure that the same acc is used in getVotes() in uint256 votesBefore = getVotes(acc, bn);/uint256 votesAfter = getVotes(acc, bn); and in callFunctionWithParams
  186. // votesBefore and votesAfter give different results but shoudn't
  187. // are we assuming that a person with 0 votes can vote? are we assuming that a person may have 0 votes
  188. // it seems like a weight can be 0. At least there is no check for it
  189. // If it can be 0 then < should be changed to <= but it gives less coverage
  190. // run on ALex's branch to avoid tiomeouts: --staging alex/external-timeout-for-solvers
  191. // --staging shelly/forSasha
  192. // implement ERC20Votes as a harness
  193. rule hasVotedCorrelation(uint256 pId, method f, env e, uint256 bn) filtered {f -> f.selector != 0x7d5e81e2}{
  194. uint256 againstBefore = votesAgainst();
  195. uint256 forBefore = votesFor();
  196. uint256 abstainBefore = votesAbstain();
  197. //againstBefore, forBefore, abstainBefore = proposalVotes(pId);
  198. address acc = e.msg.sender;
  199. bool hasVotedBefore = hasVoted(e, pId, acc);
  200. uint256 votesBefore = getVotes(acc, bn);
  201. require votesBefore > 0;
  202. //calldataarg args;
  203. //f(e, args);
  204. callFunctionWithParams(f, pId, e);
  205. uint256 againstAfter = votesAgainst();
  206. uint256 forAfter = votesFor();
  207. uint256 abstainAfter = votesAbstain();
  208. //againstAfter, forAfter, abstainAfter = proposalVotes(pId);
  209. uint256 votesAfter = getVotes(acc, bn);
  210. bool hasVotedAfter = hasVoted(e, pId, acc);
  211. assert hasVotedBefore != hasVotedAfter => againstBefore < againstAfter || forBefore < forAfter || abstainBefore < abstainAfter, "no correlation";
  212. }
  213. /*
  214. * Check privileged operations
  215. */
  216. // NO NEED FOR SPECIFIC CHANGES
  217. // how to check executor()?
  218. // to make it public instead of internal is not best idea, I think.
  219. // currentContract gives a violation in
  220. rule privilegedOnly(method f){
  221. env e;
  222. calldataarg arg;
  223. uint256 quorumNumBefore = quorumNumerator(e);
  224. f(e, arg);
  225. uint256 quorumNumAfter = quorumNumerator(e);
  226. address executorCheck = currentContract;
  227. // address executorCheck = _executor(e);
  228. assert quorumNumBefore != quorumNumAfter => e.msg.sender == executorCheck, "non priveleged user changed quorum numerator";
  229. }