GovernorCountingSimple.spec 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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 envfree
  9. proposalVotes(uint256) returns (uint256, uint256, uint256) envfree
  10. quorumNumerator() returns uint256
  11. updateQuorumNumerator(uint256)
  12. _executor() returns address
  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. //////////////////////////////////////////////////////////////////////////////
  73. ////////////////////////////// INVARIANTS ////////////////////////////////////
  74. //////////////////////////////////////////////////////////////////////////////
  75. /*
  76. * sum of all votes casted is equal to the sum of voting power of those who voted, per each proposal
  77. */
  78. invariant SumOfVotesCastEqualSumOfPowerOfVotedPerProposal(uint256 pId)
  79. tracked_weight(pId) == ghost_sum_vote_power_by_id(pId)
  80. /*
  81. * sum of all votes casted is equal to the sum of voting power of those who voted
  82. */
  83. invariant SumOfVotesCastEqualSumOfPowerOfVoted()
  84. sum_tracked_weight() == sum_all_votes_power()
  85. /*
  86. * totalVoted >= vote(id)
  87. */
  88. invariant OneIsNotMoreThanAll(uint256 pId)
  89. sum_all_votes_power() >= tracked_weight(pId)
  90. //NEED GHOST FIX
  91. /*
  92. * totalVotesPossible >= votePower(id)
  93. */
  94. invariant possibleTotalVotes(uint256 pId)
  95. tracked_weight(pId) <= totalVotesPossible(snapshot(pId))
  96. /*
  97. * totalVotesPossible >= votePower(id)
  98. */
  99. // invariant possibleTotalVotes(uint pId)
  100. //invariant trackedVsTotal(uint256 pId)
  101. // tracked_weight(pId) <= possibleMaxOfVoters(pId)
  102. /*
  103. rule someOtherRuleToRemoveLater(uint256 num){
  104. env e; calldataarg args; method f;
  105. uint256 x = hasVoteGhost(num);
  106. f(e, args);
  107. assert(false);
  108. }
  109. */
  110. /*
  111. * 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)
  112. */
  113. rule oneUserVotesInCast(uint256 pId, method f) filtered { f -> ((f.selector == castVote(uint256, uint8).selector) ||
  114. f.selector == castVoteBySig(uint256, uint8, uint8, bytes32, bytes32).selector) }{
  115. env e; calldataarg args;
  116. uint256 ghost_Before = hasVoteGhost(pId);
  117. f(e, args);
  118. uint256 ghost_After = hasVoteGhost(pId);
  119. assert(ghost_After == ghost_Before + 1, "Raised by more than 1");
  120. }
  121. /*
  122. * Checks that in any call to cast vote functions only the sender's value is updated
  123. */
  124. /*
  125. rule noVoteForSomeoneElse(uint256 pId, uint8 support){
  126. env e;
  127. address voter = e.msg.sender;
  128. bool hasVotedBefore = hasVoted(pId, voter);
  129. require(!hasVotedBefore);
  130. castVote(e, pId, support);
  131. bool hasVotedAfter = hasVoted(pId, voter);
  132. assert(hasVotedBefore != hasVotedAfter => forall address user. user != voter);
  133. }
  134. */
  135. // ok
  136. rule votingWeightMonotonicity(method f){
  137. uint256 votingWeightBefore = sum_tracked_weight();
  138. env e;
  139. calldataarg args;
  140. f(e, args);
  141. uint256 votingWeightAfter = sum_tracked_weight();
  142. assert votingWeightBefore <= votingWeightAfter, "Voting weight was decreased somehow";
  143. }
  144. // ok with this branch: thomas/bool-hook-values
  145. // can't use link because contracts are abstract, they don't have bytecode/constructor
  146. // add implementation harness
  147. rule quorumMonotonicity(method f, uint256 blockNumber){
  148. uint256 quorumBefore = quorum(blockNumber);
  149. env e;
  150. calldataarg args;
  151. f(e, args);
  152. uint256 quorumAfter = quorum(blockNumber);
  153. assert quorumBefore <= quorumAfter, "Quorum was decreased somehow";
  154. }
  155. function callFunctionWithParams(method f, uint256 proposalId, env e) {
  156. address[] targets;
  157. uint256[] values;
  158. bytes[] calldatas;
  159. bytes32 descriptionHash;
  160. uint8 support;
  161. uint8 v; bytes32 r; bytes32 s;
  162. if (f.selector == callPropose(address[], uint256[], bytes[]).selector) {
  163. uint256 result = callPropose(e, targets, values, calldatas);
  164. require result == proposalId;
  165. } else if (f.selector == execute(address[], uint256[], bytes[], bytes32).selector) {
  166. uint256 result = execute(e, targets, values, calldatas, descriptionHash);
  167. require result == proposalId;
  168. } else if (f.selector == castVote(uint256, uint8).selector) {
  169. castVote(e, proposalId, support);
  170. //} else if (f.selector == castVoteWithReason(uint256, uint8, string).selector) {
  171. // castVoteWithReason(e, proposalId, support, reason);
  172. } else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector) {
  173. castVoteBySig(e, proposalId, support, v, r, s);
  174. } else {
  175. calldataarg args;
  176. f(e,args);
  177. }
  178. }
  179. // getVotes() returns different results.
  180. // 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
  181. // votesBefore and votesAfter give different results but shoudn't
  182. // are we assuming that a person with 0 votes can vote? are we assuming that a person may have 0 votes
  183. // it seems like a weight can be 0. At least there is no check for it
  184. // If it can be 0 then < should be changed to <= but it gives less coverage
  185. // run on ALex's branch to avoid tiomeouts: --staging alex/external-timeout-for-solvers
  186. // --staging shelly/forSasha
  187. // implement ERC20Votes as a harness
  188. rule hasVotedCorrelation(uint256 pId, method f, env e, uint256 bn) filtered {f -> f.selector != 0x7d5e81e2}{
  189. uint256 againstBefore = votesAgainst();
  190. uint256 forBefore = votesFor();
  191. uint256 abstainBefore = votesAbstain();
  192. //againstBefore, forBefore, abstainBefore = proposalVotes(pId);
  193. address acc = e.msg.sender;
  194. bool hasVotedBefore = hasVoted(e, pId, acc);
  195. uint256 votesBefore = getVotes(acc, bn);
  196. require votesBefore > 0;
  197. //calldataarg args;
  198. //f(e, args);
  199. callFunctionWithParams(f, pId, e);
  200. uint256 againstAfter = votesAgainst();
  201. uint256 forAfter = votesFor();
  202. uint256 abstainAfter = votesAbstain();
  203. //againstAfter, forAfter, abstainAfter = proposalVotes(pId);
  204. uint256 votesAfter = getVotes(acc, bn);
  205. bool hasVotedAfter = hasVoted(e, pId, acc);
  206. assert hasVotedBefore != hasVotedAfter => againstBefore <= againstAfter || forBefore <= forAfter || abstainBefore <= abstainAfter, "no correlation";
  207. }
  208. /*
  209. * Check privileged operations
  210. */
  211. // NO NEED FOR SPECIFIC CHANGES
  212. // how to check executor()?
  213. // to make it public instead of internal is not best idea, I think.
  214. // currentContract gives a violation in
  215. // maybe need harness implementation for one of the contracts
  216. rule privilegedOnly(method f){
  217. env e;
  218. calldataarg arg;
  219. uint256 quorumNumBefore = quorumNumerator(e);
  220. f(e, arg);
  221. uint256 quorumNumAfter = quorumNumerator(e);
  222. address executorCheck = currentContract;
  223. // address executorCheck = _executor(e);
  224. assert quorumNumBefore != quorumNumAfter => e.msg.sender == executorCheck, "non priveleged user changed quorum numerator";
  225. }