GovernorCountingSimple.spec 11 KB

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