You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
11 KiB
198 lines
11 KiB
import "helpers/helpers.spec";
|
|
import "ERC20.spec";
|
|
|
|
methods {
|
|
function underlying() external returns(address) envfree;
|
|
function underlyingTotalSupply() external returns(uint256) envfree;
|
|
function underlyingBalanceOf(address) external returns(uint256) envfree;
|
|
function underlyingAllowanceToThis(address) external returns(uint256) envfree;
|
|
|
|
function depositFor(address, uint256) external returns(bool);
|
|
function withdrawTo(address, uint256) external returns(bool);
|
|
function recover(address) external returns(uint256);
|
|
}
|
|
|
|
use invariant totalSupplyIsSumOfBalances;
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Helper: consequence of `totalSupplyIsSumOfBalances` applied to underlying │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
definition underlyingBalancesLowerThanUnderlyingSupply(address a) returns bool =
|
|
underlyingBalanceOf(a) <= underlyingTotalSupply();
|
|
|
|
definition sumOfUnderlyingBalancesLowerThanUnderlyingSupply(address a, address b) returns bool =
|
|
a != b => underlyingBalanceOf(a) + underlyingBalanceOf(b) <= to_mathint(underlyingTotalSupply());
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Invariant: wrapped token can't be undercollateralized (solvency of the wrapper) │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
invariant totalSupplyIsSmallerThanUnderlyingBalance()
|
|
totalSupply() <= underlyingBalanceOf(currentContract) &&
|
|
underlyingBalanceOf(currentContract) <= underlyingTotalSupply() &&
|
|
underlyingTotalSupply() <= max_uint256
|
|
{
|
|
preserved {
|
|
requireInvariant totalSupplyIsSumOfBalances;
|
|
require underlyingBalancesLowerThanUnderlyingSupply(currentContract);
|
|
}
|
|
preserved depositFor(address account, uint256 amount) with (env e) {
|
|
require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(e.msg.sender, currentContract);
|
|
}
|
|
}
|
|
|
|
invariant noSelfWrap()
|
|
currentContract != underlying();
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: depositFor liveness and effects │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule depositFor(env e) {
|
|
require nonpayable(e);
|
|
|
|
address sender = e.msg.sender;
|
|
address receiver;
|
|
address other;
|
|
uint256 amount;
|
|
|
|
// sanity
|
|
requireInvariant noSelfWrap;
|
|
requireInvariant totalSupplyIsSumOfBalances;
|
|
requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
|
|
require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, sender);
|
|
|
|
uint256 balanceBefore = balanceOf(receiver);
|
|
uint256 supplyBefore = totalSupply();
|
|
uint256 senderUnderlyingBalanceBefore = underlyingBalanceOf(sender);
|
|
uint256 senderUnderlyingAllowanceBefore = underlyingAllowanceToThis(sender);
|
|
uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract);
|
|
uint256 underlyingSupplyBefore = underlyingTotalSupply();
|
|
|
|
uint256 otherBalanceBefore = balanceOf(other);
|
|
uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
|
|
|
|
depositFor@withrevert(e, receiver, amount);
|
|
bool success = !lastReverted;
|
|
|
|
// liveness
|
|
assert success <=> (
|
|
sender != currentContract && // invalid sender
|
|
sender != 0 && // invalid sender
|
|
receiver != currentContract && // invalid receiver
|
|
receiver != 0 && // invalid receiver
|
|
amount <= senderUnderlyingBalanceBefore && // deposit doesn't exceed balance
|
|
amount <= senderUnderlyingAllowanceBefore // deposit doesn't exceed allowance
|
|
);
|
|
|
|
// effects
|
|
assert success => (
|
|
to_mathint(balanceOf(receiver)) == balanceBefore + amount &&
|
|
to_mathint(totalSupply()) == supplyBefore + amount &&
|
|
to_mathint(underlyingBalanceOf(currentContract)) == wrapperUnderlyingBalanceBefore + amount &&
|
|
to_mathint(underlyingBalanceOf(sender)) == senderUnderlyingBalanceBefore - amount
|
|
);
|
|
|
|
// no side effect
|
|
assert underlyingTotalSupply() == underlyingSupplyBefore;
|
|
assert balanceOf(other) != otherBalanceBefore => other == receiver;
|
|
assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == sender || other == currentContract);
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: withdrawTo liveness and effects │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule withdrawTo(env e) {
|
|
require nonpayable(e);
|
|
|
|
address sender = e.msg.sender;
|
|
address receiver;
|
|
address other;
|
|
uint256 amount;
|
|
|
|
// sanity
|
|
requireInvariant noSelfWrap;
|
|
requireInvariant totalSupplyIsSumOfBalances;
|
|
requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
|
|
require sumOfUnderlyingBalancesLowerThanUnderlyingSupply(currentContract, receiver);
|
|
|
|
uint256 balanceBefore = balanceOf(sender);
|
|
uint256 supplyBefore = totalSupply();
|
|
uint256 receiverUnderlyingBalanceBefore = underlyingBalanceOf(receiver);
|
|
uint256 wrapperUnderlyingBalanceBefore = underlyingBalanceOf(currentContract);
|
|
uint256 underlyingSupplyBefore = underlyingTotalSupply();
|
|
|
|
uint256 otherBalanceBefore = balanceOf(other);
|
|
uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
|
|
|
|
withdrawTo@withrevert(e, receiver, amount);
|
|
bool success = !lastReverted;
|
|
|
|
// liveness
|
|
assert success <=> (
|
|
sender != 0 && // invalid sender
|
|
receiver != currentContract && // invalid receiver
|
|
receiver != 0 && // invalid receiver
|
|
amount <= balanceBefore // withdraw doesn't exceed balance
|
|
);
|
|
|
|
// effects
|
|
assert success => (
|
|
to_mathint(balanceOf(sender)) == balanceBefore - amount &&
|
|
to_mathint(totalSupply()) == supplyBefore - amount &&
|
|
to_mathint(underlyingBalanceOf(currentContract)) == wrapperUnderlyingBalanceBefore - (currentContract != receiver ? amount : 0) &&
|
|
to_mathint(underlyingBalanceOf(receiver)) == receiverUnderlyingBalanceBefore + (currentContract != receiver ? amount : 0)
|
|
);
|
|
|
|
// no side effect
|
|
assert underlyingTotalSupply() == underlyingSupplyBefore;
|
|
assert balanceOf(other) != otherBalanceBefore => other == sender;
|
|
assert underlyingBalanceOf(other) != otherUnderlyingBalanceBefore => (other == receiver || other == currentContract);
|
|
}
|
|
|
|
/*
|
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
│ Rule: recover liveness and effects │
|
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
*/
|
|
rule recover(env e) {
|
|
require nonpayable(e);
|
|
|
|
address receiver;
|
|
address other;
|
|
|
|
// sanity
|
|
requireInvariant noSelfWrap;
|
|
requireInvariant totalSupplyIsSumOfBalances;
|
|
requireInvariant totalSupplyIsSmallerThanUnderlyingBalance;
|
|
|
|
mathint value = underlyingBalanceOf(currentContract) - totalSupply();
|
|
uint256 supplyBefore = totalSupply();
|
|
uint256 balanceBefore = balanceOf(receiver);
|
|
|
|
uint256 otherBalanceBefore = balanceOf(other);
|
|
uint256 otherUnderlyingBalanceBefore = underlyingBalanceOf(other);
|
|
|
|
recover@withrevert(e, receiver);
|
|
bool success = !lastReverted;
|
|
|
|
// liveness
|
|
assert success <=> receiver != 0;
|
|
|
|
// effect
|
|
assert success => (
|
|
to_mathint(balanceOf(receiver)) == balanceBefore + value &&
|
|
to_mathint(totalSupply()) == supplyBefore + value &&
|
|
totalSupply() == underlyingBalanceOf(currentContract)
|
|
);
|
|
|
|
// no side effect
|
|
assert underlyingBalanceOf(other) == otherUnderlyingBalanceBefore;
|
|
assert balanceOf(other) != otherBalanceBefore => other == receiver;
|
|
}
|
|
|