GovernorCountingSimple.spec 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  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. quorum(uint256) returns uint256
  8. proposalVotes(uint256) returns (uint256, uint256, uint256) envfree
  9. quorumNumerator() returns uint256
  10. _executor() returns address
  11. erc20votes._getPastVotes(address, uint256) returns uint256
  12. getExecutor() 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. invariant checkGetVotesGhost(address uId, uint256 blockNumber, env e)
  73. erc20votes._getPastVotes(e, uId, blockNumber) == erc20votes.getPastVotes(e, uId, blockNumber)
  74. rule whatCahnges(uint256 blockNumber, method f) {
  75. env e;
  76. calldataarg args;
  77. uint256 ghostBefore = totalVotesPossible(blockNumber);
  78. f(e,args);
  79. uint256 ghostAfter = totalVotesPossible(blockNumber);
  80. assert ghostAfter == ghostBefore, "ghost was changed";
  81. }
  82. //////////////////////////////////////////////////////////////////////////////
  83. ////////////////////////////// INVARIANTS ////////////////////////////////////
  84. //////////////////////////////////////////////////////////////////////////////
  85. /*
  86. * sum of all votes casted is equal to the sum of voting power of those who voted, per each proposal
  87. */
  88. invariant SumOfVotesCastEqualSumOfPowerOfVotedPerProposal(uint256 pId)
  89. tracked_weight(pId) == ghost_sum_vote_power_by_id(pId)
  90. /*
  91. * sum of all votes casted is equal to the sum of voting power of those who voted
  92. */
  93. invariant SumOfVotesCastEqualSumOfPowerOfVoted()
  94. sum_tracked_weight() == sum_all_votes_power()
  95. /*
  96. * totalVoted >= vote(id)
  97. */
  98. invariant OneIsNotMoreThanAll(uint256 pId)
  99. sum_all_votes_power() >= tracked_weight(pId)
  100. //NEED GHOST FIX
  101. /*
  102. * totalVotesPossible >= votePower(id)
  103. */
  104. //invariant possibleTotalVotes(uint256 pId, env e)
  105. // tracked_weight(pId) <= erc20votes.getPastTotalSupply(e, proposalSnapshot(pId))
  106. rule possibleTotalVotes(uint256 pId, env e, method f) {
  107. require tracked_weight(pId) <= erc20votes.getPastTotalSupply(e, proposalSnapshot(pId));
  108. calldataarg args;
  109. f(e, args);
  110. assert tracked_weight(pId) <= erc20votes.getPastTotalSupply(e, proposalSnapshot(pId)), "bla bla bla";
  111. }
  112. //invariant voteGettersCheck(uint256 pId, address acc, env e)
  113. // erc20votes.getPastVotes(e, acc, proposalSnapshot(pId)) <= erc20votes.getPastTotalSupply(e, proposalSnapshot(pId))
  114. rule voteGettersCheck(uint256 pId, address acc, env e, method f){
  115. address[] targets;
  116. uint256[] values;
  117. bytes[] calldatas;
  118. require erc20votes.getPastVotes(e, acc, proposalSnapshot(pId)) <= erc20votes.getPastTotalSupply(e, proposalSnapshot(pId));
  119. uint256 result = callPropose(e, targets, values, calldatas);
  120. require result == pId;
  121. assert erc20votes.getPastVotes(e, acc, proposalSnapshot(pId)) <= erc20votes.getPastTotalSupply(e, proposalSnapshot(pId)),
  122. "getPastVotes is greater";
  123. }
  124. /*
  125. * totalVotesPossible >= votePower(id)
  126. */
  127. // invariant possibleTotalVotes(uint pId)
  128. //invariant trackedVsTotal(uint256 pId)
  129. // tracked_weight(pId) <= possibleMaxOfVoters(pId)
  130. /*
  131. rule someOtherRuleToRemoveLater(uint256 num){
  132. env e; calldataarg args; method f;
  133. uint256 x = hasVoteGhost(num);
  134. f(e, args);
  135. assert(false);
  136. }
  137. */
  138. /*
  139. * 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)
  140. */
  141. rule oneUserVotesInCast(uint256 pId, uint8 sup, method f) filtered {f -> f.selector == castVote(uint256, uint8).selector ||
  142. f.selector == castVoteBySig(uint256, uint8, uint8, bytes32, bytes32).selector} {
  143. env e; calldataarg args;
  144. uint256 ghost_Before = hasVoteGhost(pId);
  145. if (f.selector == castVote(uint256, uint8).selector)
  146. {
  147. castVote(e, pId, sup);
  148. } /*else if (f.selector == 0x7b3c71d3) {
  149. require(_pId_Harness() == proposalId);
  150. f(e,args);
  151. }*/ else if (f.selector == castVoteBySig(uint256, uint8, uint8, bytes32, bytes32).selector) {
  152. uint8 v; bytes32 r; bytes32 s;
  153. castVoteBySig(e, pId, sup, v, r, s);
  154. } else{
  155. f(e, args);
  156. }
  157. uint256 ghost_After = hasVoteGhost(pId);
  158. assert(ghost_After == ghost_Before + 1, "Raised by more than 1");
  159. }
  160. /*
  161. * Checks that in any call to cast vote functions only the sender's value is updated
  162. */
  163. rule noVoteForSomeoneElse(uint256 pId, uint8 sup, method f) filtered {f -> f.selector == castVote(uint256, uint8).selector ||
  164. f.selector == castVoteBySig(uint256, uint8, uint8, bytes32, bytes32).selector} {
  165. env e; calldataarg args;
  166. address voter = e.msg.sender;
  167. address user;
  168. bool hasVotedBefore_Voter = hasVoted(e, pId, voter);
  169. bool hasVotedBefore_User = hasVoted(e, pId, user);
  170. require(!hasVotedBefore_Voter);
  171. if (f.selector == castVote(uint256, uint8).selector)
  172. {
  173. castVote(e, pId, sup);
  174. } /*else if (f.selector == 0x7b3c71d3) {
  175. require(_pId_Harness() == proposalId);
  176. f(e,args);
  177. }*/ else if (f.selector == castVoteBySig(uint256, uint8, uint8, bytes32, bytes32).selector) {
  178. uint8 v; bytes32 r; bytes32 s;
  179. castVoteBySig(e, pId, sup, v, r, s);
  180. } else{
  181. f(e, args);
  182. }
  183. bool hasVotedAfter_Voter = hasVoted(e, pId, voter);
  184. bool hasVotedAfter_User = hasVoted(e, pId, user);
  185. assert hasVotedBefore_Voter != hasVotedAfter_Voter => (user != voter => hasVotedBefore_User == hasVotedAfter_User);
  186. }
  187. rule votingWeightMonotonicity(method f){
  188. uint256 votingWeightBefore = sum_tracked_weight();
  189. env e;
  190. calldataarg args;
  191. f(e, args);
  192. uint256 votingWeightAfter = sum_tracked_weight();
  193. assert votingWeightBefore <= votingWeightAfter, "Voting weight was decreased somehow";
  194. }
  195. function callFunctionWithParams(method f, uint256 proposalId, env e) {
  196. address[] targets;
  197. uint256[] values;
  198. bytes[] calldatas;
  199. bytes32 descriptionHash;
  200. uint8 support;
  201. uint8 v; bytes32 r; bytes32 s;
  202. if (f.selector == callPropose(address[], uint256[], bytes[]).selector) {
  203. uint256 result = callPropose(e, targets, values, calldatas);
  204. require result == proposalId;
  205. } else if (f.selector == execute(address[], uint256[], bytes[], bytes32).selector) {
  206. uint256 result = execute(e, targets, values, calldatas, descriptionHash);
  207. require result == proposalId;
  208. } else if (f.selector == castVote(uint256, uint8).selector) {
  209. castVote(e, proposalId, support);
  210. //} else if (f.selector == castVoteWithReason(uint256, uint8, string).selector) {
  211. // castVoteWithReason(e, proposalId, support, reason);
  212. } else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector) {
  213. castVoteBySig(e, proposalId, support, v, r, s);
  214. } else {
  215. calldataarg args;
  216. f(e,args);
  217. }
  218. }
  219. // run on ALex's branch to avoid tiomeouts: --staging alex/external-timeout-for-solvers
  220. // --staging shelly/forSasha
  221. rule hasVotedCorrelation(uint256 pId, method f, env e, uint256 bn) filtered {f -> f.selector != 0x7d5e81e2}{
  222. uint256 againstBefore = votesAgainst();
  223. uint256 forBefore = votesFor();
  224. uint256 abstainBefore = votesAbstain();
  225. //againstBefore, forBefore, abstainBefore = proposalVotes(pId);
  226. address acc = e.msg.sender;
  227. bool hasVotedBefore = hasVoted(e, pId, acc);
  228. uint256 votesBefore = getVotes(e, acc, bn);
  229. require votesBefore > 0;
  230. //calldataarg args;
  231. //f(e, args);
  232. callFunctionWithParams(f, pId, e);
  233. uint256 againstAfter = votesAgainst();
  234. uint256 forAfter = votesFor();
  235. uint256 abstainAfter = votesAbstain();
  236. //againstAfter, forAfter, abstainAfter = proposalVotes(pId);
  237. uint256 votesAfter = getVotes(e, acc, bn);
  238. bool hasVotedAfter = hasVoted(e, pId, acc);
  239. assert hasVotedBefore != hasVotedAfter => againstBefore <= againstAfter || forBefore <= forAfter || abstainBefore <= abstainAfter, "no correlation";
  240. }
  241. /*
  242. * Check privileged operations
  243. */
  244. rule privilegedOnly(method f, uint256 newQuorumNumerator){
  245. env e;
  246. calldataarg arg;
  247. uint256 quorumNumBefore = quorumNumerator(e);
  248. //require e.msg.sender == currentContract;
  249. f(e, arg);
  250. //updateQuorumNumerator(e, newQuorumNumerator);
  251. uint256 quorumNumAfter = quorumNumerator(e);
  252. address executorCheck = getExecutor(e);
  253. // address executorCheck = _executor(e);
  254. assert quorumNumBefore != quorumNumAfter => e.msg.sender == executorCheck, "non priveleged user changed quorum numerator";
  255. }