158 lines
9.9 KiB
Ruby
158 lines
9.9 KiB
Ruby
import "erc20.spec"
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Ghost & hooks: sum of all balances │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
ghost sumOfBalances() returns uint256 {
|
|
init_state axiom sumOfBalances() == 0;
|
|
}
|
|
|
|
hook Sstore _balances[KEY address addr] uint256 newValue (uint256 oldValue) STORAGE {
|
|
havoc sumOfBalances assuming sumOfBalances@new() == sumOfBalances@old() + newValue - oldValue;
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Invariant: totalSupply is the sum of all balances │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
invariant totalSupplyIsSumOfBalances()
|
|
totalSupply() == sumOfBalances()
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Invariant: balance of address(0) is 0 │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
invariant zeroAddressNoBalance()
|
|
balanceOf(0) == 0
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rules: totalSupply only changes through mint or burn │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule noChangeTotalSupply() {
|
|
requireInvariant totalSupplyIsSumOfBalances();
|
|
|
|
env e;
|
|
method f;
|
|
calldataarg args;
|
|
|
|
uint256 totalSupplyBefore = totalSupply();
|
|
f(e, args);
|
|
uint256 totalSupplyAfter = totalSupply();
|
|
|
|
assert (totalSupplyAfter > totalSupplyBefore) => (f.selector == mint(address,uint256).selector);
|
|
assert (totalSupplyAfter < totalSupplyBefore) => (f.selector == burn(address,uint256).selector);
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rules: totalSupply change matches minted or burned amount │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule mintIncreasesTotalSupply() {
|
|
requireInvariant totalSupplyIsSumOfBalances();
|
|
|
|
env e;
|
|
address to;
|
|
uint256 amount;
|
|
|
|
uint256 totalSupplyBefore = totalSupply();
|
|
mint(e, to, amount);
|
|
uint256 totalSupplyAfter = totalSupply();
|
|
|
|
assert totalSupplyAfter == totalSupplyBefore + amount;
|
|
}
|
|
|
|
rule burnDecreasesTotalSupply() {
|
|
requireInvariant totalSupplyIsSumOfBalances();
|
|
|
|
env e;
|
|
address from;
|
|
uint256 amount;
|
|
|
|
uint256 totalSupplyBefore = totalSupply();
|
|
burn(e, from, amount);
|
|
uint256 totalSupplyAfter = totalSupply();
|
|
|
|
assert totalSupplyAfter == totalSupplyBefore - amount;
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rules: Balance of an account can only decrease if the tx was sent by owner or by approved │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule onlyAuthorizedCanTransfer() {
|
|
requireInvariant totalSupplyIsSumOfBalances();
|
|
|
|
env e;
|
|
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 == burn(address,uint256).selector ||
|
|
e.msg.sender == account ||
|
|
balanceBefore - balanceAfter <= allowanceBefore
|
|
);
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: transfer moves correct amount from sender to receiver │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule transferAmount() {
|
|
requireInvariant totalSupplyIsSumOfBalances();
|
|
|
|
env e;
|
|
address holder = e.msg.sender;
|
|
address recipient;
|
|
uint256 amount;
|
|
|
|
uint256 holderBalanceBefore = balanceOf(holder);
|
|
uint256 recipientBalanceBefore = balanceOf(recipient);
|
|
|
|
transfer(e, recipient, amount);
|
|
|
|
assert balanceOf(holder) == holderBalanceBefore - (holder == recipient ? 0 : amount);
|
|
assert balanceOf(recipient) == recipientBalanceBefore + (holder == recipient ? 0 : amount);
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: transferFrom moves correct amount from holder to receiver, and updates allowance │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule transferFomAmountAndApproval() {
|
|
requireInvariant totalSupplyIsSumOfBalances();
|
|
|
|
env e;
|
|
address holder;
|
|
address recipient;
|
|
uint256 amount;
|
|
|
|
uint256 allowanceBefore = allowance(holder, e.msg.sender);
|
|
uint256 holderBalanceBefore = balanceOf(holder);
|
|
uint256 recipientBalanceBefore = balanceOf(recipient);
|
|
|
|
transferFrom(e, holder, recipient, amount);
|
|
|
|
assert allowanceBefore >= amount;
|
|
assert allowance(holder, e.msg.sender) == (allowanceBefore == max_uint256 ? to_uint256(max_uint256) : allowanceBefore - amount);
|
|
assert balanceOf(holder) == holderBalanceBefore - (holder == recipient ? 0 : amount);
|
|
assert balanceOf(recipient) == recipientBalanceBefore + (holder == recipient ? 0 : amount);
|
|
}
|