using ERC20VotesHarness as erc20votes methods { // functions checkpoints(address, uint32) envfree numCheckpoints(address) returns (uint32) envfree delegates(address) returns (address) envfree getVotes(address) returns (uint256) envfree getPastVotes(address, uint256) returns (uint256) getPastTotalSupply(uint256) returns (uint256) delegate(address) _delegate(address, address) delegateBySig(address, uint256, uint256, uint8, bytes32, bytes32) totalSupply() returns (uint256) envfree _maxSupply() returns (uint224) envfree // harnesss functions ckptFromBlock(address, uint32) returns (uint32) envfree ckptVotes(address, uint32) returns (uint224) envfree mint(address, uint256) burn(address, uint256) // solidity generated getters _delegates(address) returns (address) envfree // external functions } // gets the most recent votes for a user ghost userVotes(address) returns uint224; // sums the total votes for all users ghost totalVotes() returns mathint { axiom totalVotes() > 0; } hook Sstore _checkpoints[KEY address account][INDEX uint32 index].votes uint224 newVotes (uint224 oldVotes) STORAGE { havoc userVotes assuming userVotes@new(account) == newVotes; havoc totalVotes assuming totalVotes@new() == totalVotes@old() + to_mathint(newVotes) - to_mathint(userVotes(account)); } ghost lastFromBlock(address) returns uint32; ghost doubleFromBlock(address) returns bool { init_state axiom forall address a. doubleFromBlock(a) == false; } hook Sstore _checkpoints[KEY address account][INDEX uint32 index].fromBlock uint32 newBlock (uint32 oldBlock) STORAGE { havoc lastFromBlock assuming lastFromBlock@new(account) == newBlock; havoc doubleFromBlock assuming doubleFromBlock@new(account) == (newBlock == oldBlock); } rule sanity(method f) { env e; calldataarg arg; f(e, arg); assert false; } // something stupid just to see invariant sanity_invariant() totalSupply() >= 0 // sum of user balances is >= total amount of delegated votes invariant votes_solvency() to_mathint(totalSupply()) >= totalVotes() // for some checkpoint, the fromBlock is less than the current block number // passes but fails rule sanity from hash on delegate by sig invariant timestamp_constrains_fromBlock(address account, uint32 index, env e) ckptFromBlock(account, index) < e.block.number { preserved { require index < numCheckpoints(account); } } // numCheckpoints are less than maxInt // passes invariant maxInt_constrains_numBlocks(address account) numCheckpoints(account) <= 4294967295 // 2^32 // can't have more checkpoints for a given account than the last from block invariant fromBlock_constrains_numBlocks(address account) numCheckpoints(account) <= lastFromBlock(account) invariant unique_checkpoints(address account) !doubleFromBlock(account) rule transfer_safe() { env e; uint256 amount; address a; address b; require a != b; uint256 votesA_pre = getVotes(a); uint256 votesB_pre = getVotes(b); require votesA_pre == erc20votes.balanceOf(e, a); require votesB_pre == erc20votes.balanceOf(e, b); mathint totalVotes_pre = totalVotes(); erc20votes.transferFrom(e, a, b, amount); mathint totalVotes_post = totalVotes(); uint256 votesA_post = getVotes(a); uint256 votesB_post = getVotes(b); assert totalVotes_pre == totalVotes_post, "transfer changed total supply"; assert votesA_pre - votesA_post == amount, "a lost the proper amount of votes"; assert votesB_post - votesB_pre == amount, "b lost the proper amount of votes"; } rule delegator_votes_removed() { env e; address delegator; address delegatee; require delegator != delegatee; require delegates(delegator) == 0; // has not delegated uint256 pre = getVotes(delegator); _delegate(e, delegator, delegatee); uint256 balance = balanceOf(e, delegator); uint256 post = getVotes(delegator); assert post == pre - balance, "delegator retained votes"; } rule delegatee_receives_votes() { env e; address delegator; address delegatee; require delegator != delegatee; uint256 delegator_bal = balanceOf(e, delegator); uint256 votes_= getVotes(delegatee); _delegate(e, delegator, delegatee); uint256 _votes = getVotes(delegatee); assert _votes == votes_ + delegator_bal, "delegatee did not receive votes"; } rule previous_delegatee_zerod() { env e; address delegator; address delegatee; address third; require delegator != delegatee; require third != delegatee; require third != delegator; uint256 delegator_bal = balanceOf(e, delegator); uint256 votes_ = getVotes(third); _delegate(e, delegator, delegatee); uint256 _votes = getVotes(third); assert votes_ == votes_ - delegator_bal, "votes not removed from the previous delegatee"; } // passes rule delegate_contained() { env e; address delegator; address delegatee; address other; require delegator != delegatee; require other != delegator; require other != delegatee; require other != delegates(delegator); uint256 votes_ = getVotes(other); _delegate(e, delegator, delegatee); uint256 _votes = getVotes(other); assert votes_ == _votes, "votes not contained"; } // checks all of the above rules with front running rule delegate_no_frontrunning(method f) { env e; calldataarg args; address delegator; address delegatee; address third; address other; require delegator != delegatee; require delegates(delegator) == third; require other != third; uint256 delegator_bal = erc20votes.balanceOf(e, delegator); uint256 dr_ = getVotes(delegator); uint256 de_ = getVotes(delegatee); uint256 third_ = getVotes(third); uint256 other_ = getVotes(other); // require third is address for previous delegator f(e, args); _delegate(e, delegator, delegatee); uint256 _dr = getVotes(delegator); uint256 _de = getVotes(delegatee); uint256 _third = getVotes(third); uint256 _other = getVotes(other); // delegator loses all of their votes // delegatee gains that many votes // third loses any votes delegated to them assert _dr == 0, "delegator retained votes"; assert _de == de_ + delegator_bal, "delegatee not received votes"; assert _third != 0 => _third == third_ - delegator_bal, "votes not removed from third"; assert other_ == _other, "delegate not contained"; } // passes rule mint_increases_totalSupply() { env e; uint256 amount; address account; uint256 fromBlock = e.block.number; uint256 totalSupply_ = totalSupply(); mint(e, account, amount); uint256 _totalSupply = totalSupply(); require _totalSupply < _maxSupply(); assert _totalSupply == totalSupply_ + amount, "totalSupply not increased properly"; assert getPastTotalSupply(e, fromBlock) == totalSupply_ , "previous total supply not saved properly"; } // passes rule burn_decreases_totalSupply() { env e; uint256 amount; address account; uint256 fromBlock = e.block.number; uint256 totalSupply_ = totalSupply(); require totalSupply_ > balanceOf(e, account); burn(e, account, amount); uint256 _totalSupply = totalSupply(); require _totalSupply < _maxSupply(); assert _totalSupply == totalSupply_ - amount, "totalSupply not decreased properly"; assert getPastTotalSupply(e, fromBlock) == totalSupply_ , "previous total supply not saved properly"; } // passes rule mint_doesnt_increase_totalVotes() { env e; uint256 amount; address account; mathint totalVotes_ = totalVotes(); mint(e, account, amount); assert totalVotes() == totalVotes_, "totalVotes increased"; } // passes rule burn_doesnt_decrease_totalVotes() { env e; uint256 amount; address account; mathint totalVotes_ = totalVotes(); burn(e, account, amount); assert totalVotes() == totalVotes_, "totalVotes decreased"; } // // fails // rule mint_increases_totalVotes() { // env e; // uint256 amount; address account; // mathint totalVotes_ = totalVotes(); // mint(e, account, amount); // assert totalVotes() == totalVotes_ + to_mathint(amount), "totalVotes not increased"; // } // // fails // rule burn_decreases_totalVotes() { // env e; // uint256 amount; address account; // mathint totalVotes_ = totalVotes(); // burn(e, account, amount); // assert totalVotes() == totalVotes_ - to_mathint(amount), "totalVotes not decreased"; // }