123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424 |
- import "helpers/helpers.spec";
- import "methods/IERC20.spec";
- import "methods/IERC2612.spec";
- methods {
- // non standard ERC20 functions
- function increaseAllowance(address,uint256) external returns (bool);
- function decreaseAllowance(address,uint256) external returns (bool);
- // exposed for FV
- function mint(address,uint256) external;
- function burn(address,uint256) external;
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Ghost & hooks: sum of all balances โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- ghost mathint sumOfBalances {
- init_state axiom sumOfBalances == 0;
- }
- // Because `balance` has a uint256 type, any balance addition in CVL1 behaved as a `require_uint256()` casting,
- // leaving out the possibility of overflow. This is not the case in CVL2 where casting became more explicit.
- // A counterexample in CVL2 is having an initial state where Alice initial balance is larger than totalSupply, which
- // overflows Alice's balance when receiving a transfer. This is not possible unless the contract is deployed into an
- // already used address (or upgraded from corrupted state).
- // We restrict such behavior by making sure no balance is greater than the sum of balances.
- hook Sload uint256 balance _balances[KEY address addr] STORAGE {
- require sumOfBalances >= to_mathint(balance);
- }
- hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE {
- sumOfBalances = sumOfBalances - oldValue + newValue;
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Invariant: totalSupply is the sum of all balances โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- invariant totalSupplyIsSumOfBalances()
- to_mathint(totalSupply()) == sumOfBalances;
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Invariant: balance of address(0) is 0 โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- invariant zeroAddressNoBalance()
- balanceOf(0) == 0;
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rules: only mint and burn can change total supply โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule noChangeTotalSupply(env e) {
- requireInvariant totalSupplyIsSumOfBalances();
- method f;
- calldataarg args;
- uint256 totalSupplyBefore = totalSupply();
- f(e, args);
- uint256 totalSupplyAfter = totalSupply();
- assert totalSupplyAfter > totalSupplyBefore => f.selector == sig:mint(address,uint256).selector;
- assert totalSupplyAfter < totalSupplyBefore => f.selector == sig:burn(address,uint256).selector;
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rules: only the token holder or an approved third party can reduce an account's balance โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule onlyAuthorizedCanTransfer(env e) {
- requireInvariant totalSupplyIsSumOfBalances();
- method f;
- calldataarg args;
- address account;
- uint256 allowanceBefore = allowance(account, e.msg.sender);
- uint256 balanceBefore = balanceOf(account);
- f(e, args);
- uint256 balanceAfter = balanceOf(account);
- assert (
- balanceAfter < balanceBefore
- ) => (
- f.selector == sig:burn(address,uint256).selector ||
- e.msg.sender == account ||
- balanceBefore - balanceAfter <= to_mathint(allowanceBefore)
- );
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rules: only the token holder (or a permit) can increase allowance. The spender can decrease it by using it โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule onlyHolderOfSpenderCanChangeAllowance(env e) {
- requireInvariant totalSupplyIsSumOfBalances();
- method f;
- calldataarg args;
- address holder;
- address spender;
- uint256 allowanceBefore = allowance(holder, spender);
- f(e, args);
- uint256 allowanceAfter = allowance(holder, spender);
- assert (
- allowanceAfter > allowanceBefore
- ) => (
- (f.selector == sig:approve(address,uint256).selector && e.msg.sender == holder) ||
- (f.selector == sig:increaseAllowance(address,uint256).selector && e.msg.sender == holder) ||
- (f.selector == sig:permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector)
- );
- assert (
- allowanceAfter < allowanceBefore
- ) => (
- (f.selector == sig:transferFrom(address,address,uint256).selector && e.msg.sender == spender) ||
- (f.selector == sig:approve(address,uint256).selector && e.msg.sender == holder ) ||
- (f.selector == sig:decreaseAllowance(address,uint256).selector && e.msg.sender == holder ) ||
- (f.selector == sig:permit(address,address,uint256,uint256,uint8,bytes32,bytes32).selector)
- );
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rules: mint behavior and side effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule mint(env e) {
- requireInvariant totalSupplyIsSumOfBalances();
- require nonpayable(e);
- address to;
- address other;
- uint256 amount;
- // cache state
- uint256 toBalanceBefore = balanceOf(to);
- uint256 otherBalanceBefore = balanceOf(other);
- uint256 totalSupplyBefore = totalSupply();
- // run transaction
- mint@withrevert(e, to, amount);
- // check outcome
- if (lastReverted) {
- assert to == 0 || totalSupplyBefore + amount > max_uint256;
- } else {
- // updates balance and totalSupply
- assert to_mathint(balanceOf(to)) == toBalanceBefore + amount;
- assert to_mathint(totalSupply()) == totalSupplyBefore + amount;
- // no other balance is modified
- assert balanceOf(other) != otherBalanceBefore => other == to;
- }
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rules: burn behavior and side effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule burn(env e) {
- requireInvariant totalSupplyIsSumOfBalances();
- require nonpayable(e);
- address from;
- address other;
- uint256 amount;
- // cache state
- uint256 fromBalanceBefore = balanceOf(from);
- uint256 otherBalanceBefore = balanceOf(other);
- uint256 totalSupplyBefore = totalSupply();
- // run transaction
- burn@withrevert(e, from, amount);
- // check outcome
- if (lastReverted) {
- assert from == 0 || fromBalanceBefore < amount;
- } else {
- // updates balance and totalSupply
- assert to_mathint(balanceOf(from)) == fromBalanceBefore - amount;
- assert to_mathint(totalSupply()) == totalSupplyBefore - amount;
- // no other balance is modified
- assert balanceOf(other) != otherBalanceBefore => other == from;
- }
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: transfer behavior and side effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule transfer(env e) {
- requireInvariant totalSupplyIsSumOfBalances();
- require nonpayable(e);
- address holder = e.msg.sender;
- address recipient;
- address other;
- uint256 amount;
- // cache state
- uint256 holderBalanceBefore = balanceOf(holder);
- uint256 recipientBalanceBefore = balanceOf(recipient);
- uint256 otherBalanceBefore = balanceOf(other);
- // run transaction
- transfer@withrevert(e, recipient, amount);
- // check outcome
- if (lastReverted) {
- assert holder == 0 || recipient == 0 || amount > holderBalanceBefore;
- } else {
- // balances of holder and recipient are updated
- assert to_mathint(balanceOf(holder)) == holderBalanceBefore - (holder == recipient ? 0 : amount);
- assert to_mathint(balanceOf(recipient)) == recipientBalanceBefore + (holder == recipient ? 0 : amount);
- // no other balance is modified
- assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient);
- }
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: transferFrom behavior and side effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule transferFrom(env e) {
- requireInvariant totalSupplyIsSumOfBalances();
- require nonpayable(e);
- address spender = e.msg.sender;
- address holder;
- address recipient;
- address other;
- uint256 amount;
- // cache state
- uint256 allowanceBefore = allowance(holder, spender);
- uint256 holderBalanceBefore = balanceOf(holder);
- uint256 recipientBalanceBefore = balanceOf(recipient);
- uint256 otherBalanceBefore = balanceOf(other);
- // run transaction
- transferFrom@withrevert(e, holder, recipient, amount);
- // check outcome
- if (lastReverted) {
- assert holder == 0 || recipient == 0 || spender == 0 || amount > holderBalanceBefore || amount > allowanceBefore;
- } else {
- // allowance is valid & updated
- assert allowanceBefore >= amount;
- assert to_mathint(allowance(holder, spender)) == (allowanceBefore == max_uint256 ? max_uint256 : allowanceBefore - amount);
- // balances of holder and recipient are updated
- assert to_mathint(balanceOf(holder)) == holderBalanceBefore - (holder == recipient ? 0 : amount);
- assert to_mathint(balanceOf(recipient)) == recipientBalanceBefore + (holder == recipient ? 0 : amount);
- // no other balance is modified
- assert balanceOf(other) != otherBalanceBefore => (other == holder || other == recipient);
- }
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: approve behavior and side effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule approve(env e) {
- require nonpayable(e);
- address holder = e.msg.sender;
- address spender;
- address otherHolder;
- address otherSpender;
- uint256 amount;
- // cache state
- uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
- // run transaction
- approve@withrevert(e, spender, amount);
- // check outcome
- if (lastReverted) {
- assert holder == 0 || spender == 0;
- } else {
- // allowance is updated
- assert allowance(holder, spender) == amount;
- // other allowances are untouched
- assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
- }
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: increaseAllowance behavior and side effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule increaseAllowance(env e) {
- require nonpayable(e);
- address holder = e.msg.sender;
- address spender;
- address otherHolder;
- address otherSpender;
- uint256 amount;
- // cache state
- uint256 allowanceBefore = allowance(holder, spender);
- uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
- // run transaction
- increaseAllowance@withrevert(e, spender, amount);
- // check outcome
- if (lastReverted) {
- assert holder == 0 || spender == 0 || allowanceBefore + amount > max_uint256;
- } else {
- // allowance is updated
- assert to_mathint(allowance(holder, spender)) == allowanceBefore + amount;
- // other allowances are untouched
- assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
- }
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: decreaseAllowance behavior and side effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule decreaseAllowance(env e) {
- require nonpayable(e);
- address holder = e.msg.sender;
- address spender;
- address otherHolder;
- address otherSpender;
- uint256 amount;
- // cache state
- uint256 allowanceBefore = allowance(holder, spender);
- uint256 otherAllowanceBefore = allowance(otherHolder, otherSpender);
- // run transaction
- decreaseAllowance@withrevert(e, spender, amount);
- // check outcome
- if (lastReverted) {
- assert holder == 0 || spender == 0 || allowanceBefore < amount;
- } else {
- // allowance is updated
- assert to_mathint(allowance(holder, spender)) == allowanceBefore - amount;
- // other allowances are untouched
- assert allowance(otherHolder, otherSpender) != otherAllowanceBefore => (otherHolder == holder && otherSpender == spender);
- }
- }
- /*
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- โ Rule: permit behavior and side effects โ
- โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
- rule permit(env e) {
- require nonpayable(e);
- address holder;
- address spender;
- uint256 amount;
- uint256 deadline;
- uint8 v;
- bytes32 r;
- bytes32 s;
- address account1;
- address account2;
- address account3;
- // cache state
- uint256 nonceBefore = nonces(holder);
- uint256 otherNonceBefore = nonces(account1);
- uint256 otherAllowanceBefore = allowance(account2, account3);
- // sanity: nonce overflow, which possible in theory, is assumed to be impossible in practice
- require nonceBefore < max_uint256;
- require otherNonceBefore < max_uint256;
- // run transaction
- permit@withrevert(e, holder, spender, amount, deadline, v, r, s);
- // check outcome
- if (lastReverted) {
- // Without formally checking the signature, we can't verify exactly the revert causes
- assert true;
- } else {
- // allowance and nonce are updated
- assert allowance(holder, spender) == amount;
- assert to_mathint(nonces(holder)) == nonceBefore + 1;
- // deadline was respected
- assert deadline >= e.block.timestamp;
- // no other allowance or nonce is modified
- assert nonces(account1) != otherNonceBefore => account1 == holder;
- assert allowance(account2, account3) != otherAllowanceBefore => (account2 == holder && account3 == spender);
- }
- }
|