123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297 |
- import "GovernorBase.spec"
- using ERC20VotesHarness as erc20votes
- methods {
- ghost_sum_vote_power_by_id(uint256) returns uint256 envfree
- // castVote(uint256, uint8) returns uint256
- // castVoteBySig(uint256,uint8,uint8,bytes32,bytes32) returns uint256
-
- snapshot(uint256) returns uint64 envfree
- quorum(uint256) returns uint256
- proposalVotes(uint256) returns (uint256, uint256, uint256) envfree
- quorumNumerator() returns uint256
- _executor() returns address
- erc20votes._getPastVotes(address, uint256) returns uint256 envfree
- }
- //////////////////////////////////////////////////////////////////////////////
- ///////////////////////////////// GHOSTS /////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
- ghost hasVoteGhost(uint256) returns uint256 {
- init_state axiom forall uint256 pId. hasVoteGhost(pId) == 0;
- }
- hook Sstore _proposalVotes[KEY uint256 pId].hasVoted[KEY address user] bool current_voting_State (bool old_voting_state) STORAGE{
- havoc hasVoteGhost assuming forall uint256 p. ((p == pId && current_voting_State && !old_voting_state) ? (hasVoteGhost@new(p) == hasVoteGhost@old(p) + 1) :
- (hasVoteGhost@new(p) == hasVoteGhost@old(p)));
- }
- ghost sum_all_votes_power() returns uint256 {
- init_state axiom sum_all_votes_power() == 0;
- }
- hook Sstore ghost_sum_vote_power_by_id [KEY uint256 pId] uint256 current_power(uint256 old_power) STORAGE {
- havoc sum_all_votes_power assuming sum_all_votes_power@new() == sum_all_votes_power@old() - old_power + current_power;
- }
- ghost tracked_weight(uint256) returns uint256 {
- init_state axiom forall uint256 p. tracked_weight(p) == 0;
- }
- ghost sum_tracked_weight() returns uint256 {
- init_state axiom sum_tracked_weight() == 0;
- }
- ghost votesAgainst() returns uint256 {
- init_state axiom votesAgainst() == 0;
- }
- ghost votesFor() returns uint256 {
- init_state axiom votesFor() == 0;
- }
- ghost votesAbstain() returns uint256 {
- init_state axiom votesAbstain() == 0;
- }
- hook Sstore _proposalVotes [KEY uint256 pId].againstVotes uint256 votes(uint256 old_votes) STORAGE {
- havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) &&
- (p != pId => tracked_weight@new(p) == tracked_weight@old(p));
- havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes;
- havoc votesAgainst assuming votesAgainst@new() == votesAgainst@old() - old_votes + votes;
- }
- hook Sstore _proposalVotes [KEY uint256 pId].forVotes uint256 votes(uint256 old_votes) STORAGE {
- havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) &&
- (p != pId => tracked_weight@new(p) == tracked_weight@old(p));
- havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes;
- havoc votesFor assuming votesFor@new() == votesFor@old() - old_votes + votes;
- }
- hook Sstore _proposalVotes [KEY uint256 pId].abstainVotes uint256 votes(uint256 old_votes) STORAGE {
- havoc tracked_weight assuming forall uint256 p.(p == pId => tracked_weight@new(p) == tracked_weight@old(p) - old_votes + votes) &&
- (p != pId => tracked_weight@new(p) == tracked_weight@old(p));
- havoc sum_tracked_weight assuming sum_tracked_weight@new() == sum_tracked_weight@old() - old_votes + votes;
- havoc votesAbstain assuming votesAbstain@new() == votesAbstain@old() - old_votes + votes;
- }
- ghost totalVotesPossible(uint256) returns uint256 {
- init_state axiom forall uint256 bn. totalVotesPossible(bn) == 0;
- }
- // Was done with _getVotes by mistake. Should check GovernorBasicHarness but _getVotes is from GovernorHarness
- hook Sstore erc20votes._getPastVotes[KEY address uId][KEY uint256 blockNumber] uint256 voteWeight(uint256 old_voteWeight) STORAGE {
- havoc totalVotesPossible assuming forall uint256 bn.
- (bn == blockNumber => totalVotesPossible@new(bn) == totalVotesPossible@old(bn) - old_voteWeight + voteWeight)
- && (bn != blockNumber => totalVotesPossible@new(bn) == totalVotesPossible@old(bn));
- }
- invariant checkGetVotesGhost(address uId, uint256 blockNumber)
- erc20votes._getPastVotes(uId, blockNumber) == getPastVotes(uId, blockNumber)
- //////////////////////////////////////////////////////////////////////////////
- ////////////////////////////// INVARIANTS ////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
- /*
- * sum of all votes casted is equal to the sum of voting power of those who voted, per each proposal
- */
- invariant SumOfVotesCastEqualSumOfPowerOfVotedPerProposal(uint256 pId)
- tracked_weight(pId) == ghost_sum_vote_power_by_id(pId)
-
- /*
- * sum of all votes casted is equal to the sum of voting power of those who voted
- */
- invariant SumOfVotesCastEqualSumOfPowerOfVoted()
- sum_tracked_weight() == sum_all_votes_power()
-
- /*
- * totalVoted >= vote(id)
- */
- invariant OneIsNotMoreThanAll(uint256 pId)
- sum_all_votes_power() >= tracked_weight(pId)
- //NEED GHOST FIX
- /*
- * totalVotesPossible >= votePower(id)
- */
- invariant possibleTotalVotes(uint256 pId)
- tracked_weight(pId) <= totalVotesPossible(snapshot(pId))
- /*
- * totalVotesPossible >= votePower(id)
- */
- // invariant possibleTotalVotes(uint pId)
- //invariant trackedVsTotal(uint256 pId)
- // tracked_weight(pId) <= possibleMaxOfVoters(pId)
- /*
- rule someOtherRuleToRemoveLater(uint256 num){
- env e; calldataarg args; method f;
- uint256 x = hasVoteGhost(num);
- f(e, args);
- assert(false);
- }
- */
- /*
- * 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)
- */
- 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} {
- env e; calldataarg args; // ^ ^
- uint256 ghost_Before = hasVoteGhost(pId); // propose castVoteWithReason
- if (f.selector == castVote(uint256, uint8).selector)
- {
- castVote(e, pId, sup);
- } /*else if (f.selector == 0x7b3c71d3) {
- require(_pId_Harness() == proposalId);
- f(e,args);
- }*/ else if (f.selector == castVoteBySig(uint256, uint8, uint8, bytes32, bytes32).selector) {
- uint8 v; bytes32 r; bytes32 s;
- castVoteBySig(e, pId, sup, v, r, s);
- } else{
- f(e, args);
- }
- uint256 ghost_After = hasVoteGhost(pId);
- assert(ghost_After == ghost_Before + 1, "Raised by more than 1");
- }
- /*
- * Checks that in any call to cast vote functions only the sender's value is updated
- */
- rule noVoteForSomeoneElse(uint256 pId, uint8 support){
- env e;
- address voter = e.msg.sender;
- bool hasVotedBefore = hasVoted(e, pId, voter);
- require(!hasVotedBefore);
- castVote(e, pId, support);
- bool hasVotedAfter = hasVoted(e, pId, voter);
- assert(hasVotedBefore != hasVotedAfter => (forall address user. user != voter => !hasVoted(e, pId, user)));
- }
- // ok
- rule votingWeightMonotonicity(method f){
- uint256 votingWeightBefore = sum_tracked_weight();
- env e;
- calldataarg args;
- f(e, args);
- uint256 votingWeightAfter = sum_tracked_weight();
- assert votingWeightBefore <= votingWeightAfter, "Voting weight was decreased somehow";
- }
- // ok with this branch: thomas/bool-hook-values
- // can't use link because contracts are abstract, they don't have bytecode/constructor
- // add implementation harness
- rule quorumMonotonicity(method f, uint256 blockNumber){
- env e;
-
- uint256 quorumBefore = quorum(e, blockNumber);
-
- calldataarg args;
- f(e, args);
- uint256 quorumAfter = quorum(e, blockNumber);
- assert quorumBefore <= quorumAfter, "Quorum was decreased somehow";
- }
- function callFunctionWithParams(method f, uint256 proposalId, env e) {
- address[] targets;
- uint256[] values;
- bytes[] calldatas;
- bytes32 descriptionHash;
- uint8 support;
- uint8 v; bytes32 r; bytes32 s;
- if (f.selector == callPropose(address[], uint256[], bytes[]).selector) {
- uint256 result = callPropose(e, targets, values, calldatas);
- require result == proposalId;
- } else if (f.selector == execute(address[], uint256[], bytes[], bytes32).selector) {
- uint256 result = execute(e, targets, values, calldatas, descriptionHash);
- require result == proposalId;
- } else if (f.selector == castVote(uint256, uint8).selector) {
- castVote(e, proposalId, support);
- //} else if (f.selector == castVoteWithReason(uint256, uint8, string).selector) {
- // castVoteWithReason(e, proposalId, support, reason);
- } else if (f.selector == castVoteBySig(uint256, uint8,uint8, bytes32, bytes32).selector) {
- castVoteBySig(e, proposalId, support, v, r, s);
- } else {
- calldataarg args;
- f(e,args);
- }
- }
- // getVotes() returns different results.
- // 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
- // votesBefore and votesAfter give different results but shoudn't
- // are we assuming that a person with 0 votes can vote? are we assuming that a person may have 0 votes
- // it seems like a weight can be 0. At least there is no check for it
- // If it can be 0 then < should be changed to <= but it gives less coverage
- // run on ALex's branch to avoid tiomeouts: --staging alex/external-timeout-for-solvers
- // --staging shelly/forSasha
- // implement ERC20Votes as a harness
- rule hasVotedCorrelation(uint256 pId, method f, env e, uint256 bn) filtered {f -> f.selector != 0x7d5e81e2}{
- uint256 againstBefore = votesAgainst();
- uint256 forBefore = votesFor();
- uint256 abstainBefore = votesAbstain();
- //againstBefore, forBefore, abstainBefore = proposalVotes(pId);
- address acc = e.msg.sender;
- bool hasVotedBefore = hasVoted(e, pId, acc);
- uint256 votesBefore = getVotes(e, acc, bn);
- require votesBefore > 0;
- //calldataarg args;
- //f(e, args);
- callFunctionWithParams(f, pId, e);
- uint256 againstAfter = votesAgainst();
- uint256 forAfter = votesFor();
- uint256 abstainAfter = votesAbstain();
- //againstAfter, forAfter, abstainAfter = proposalVotes(pId);
- uint256 votesAfter = getVotes(e, acc, bn);
- bool hasVotedAfter = hasVoted(e, pId, acc);
- assert hasVotedBefore != hasVotedAfter => againstBefore <= againstAfter || forBefore <= forAfter || abstainBefore <= abstainAfter, "no correlation";
- }
- /*
- * Check privileged operations
- */
- // NO NEED FOR SPECIFIC CHANGES
- // how to check executor()?
- // to make it public instead of internal is not best idea, I think.
- // currentContract gives a violation in
- // maybe need harness implementation for one of the contracts
- rule privilegedOnly(method f){
- env e;
- calldataarg arg;
- uint256 quorumNumBefore = quorumNumerator(e);
- f(e, arg);
- uint256 quorumNumAfter = quorumNumerator(e);
- address executorCheck = currentContract;
- // address executorCheck = _executor(e);
- assert quorumNumBefore != quorumNumAfter => e.msg.sender == executorCheck, "non priveleged user changed quorum numerator";
- }
|