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.
199 lines
11 KiB
199 lines
11 KiB
2 years ago
|
import "helpers.spec"
|
||
|
import "ERC20.spec"
|
||
|
|
||
|
methods {
|
||
|
underlying() returns(address) envfree
|
||
|
underlyingTotalSupply() returns(uint256) envfree
|
||
|
underlyingBalanceOf(address) returns(uint256) envfree
|
||
|
underlyingAllowanceToThis(address) returns(uint256) envfree
|
||
|
|
||
|
depositFor(address, uint256) returns(bool)
|
||
|
withdrawTo(address, uint256) returns(bool)
|
||
|
recover(address) returns(uint256)
|
||
|
}
|
||
|
|
||
|
use invariant totalSupplyIsSumOfBalances
|
||
|
|
||
|
/*
|
||
|
┌─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
||
|
│ Helper: consequence of `totalSupplyIsSumOfBalances` applied to underlying │
|
||
|
└─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
||
|
*/
|
||
|
function underlyingBalancesLowerThanUnderlyingSupply(address a) returns bool {
|
||
|
return underlyingBalanceOf(a) <= underlyingTotalSupply();
|
||
|
}
|
||
|
|
||
|
function sumOfUnderlyingBalancesLowerThanUnderlyingSupply(address a, address b) returns bool {
|
||
|
return a != b => underlyingBalanceOf(a) + underlyingBalanceOf(b) <= 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 != 0 && // invalid receiver
|
||
|
amount <= senderUnderlyingBalanceBefore && // deposit doesn't exceed balance
|
||
|
amount <= senderUnderlyingAllowanceBefore // deposit doesn't exceed allowance
|
||
|
);
|
||
|
|
||
|
// effects
|
||
|
assert success => (
|
||
|
balanceOf(receiver) == balanceBefore + amount &&
|
||
|
totalSupply() == supplyBefore + amount &&
|
||
|
underlyingBalanceOf(currentContract) == wrapperUnderlyingBalanceBefore + amount &&
|
||
|
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 != 0 && // invalid receiver
|
||
|
amount <= balanceBefore // withdraw doesn't exceed balance
|
||
|
);
|
||
|
|
||
|
// effects
|
||
|
assert success => (
|
||
|
balanceOf(sender) == balanceBefore - amount &&
|
||
|
totalSupply() == supplyBefore - amount &&
|
||
|
underlyingBalanceOf(currentContract) == wrapperUnderlyingBalanceBefore - (currentContract != receiver ? amount : 0) &&
|
||
|
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;
|
||
|
|
||
|
uint256 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 => (
|
||
|
balanceOf(receiver) == balanceBefore + value &&
|
||
|
totalSupply() == supplyBefore + value &&
|
||
|
totalSupply() == underlyingBalanceOf(currentContract)
|
||
|
);
|
||
|
|
||
|
// no side effect
|
||
|
assert underlyingBalanceOf(other) == otherUnderlyingBalanceBefore;
|
||
|
assert balanceOf(other) != otherBalanceBefore => other == receiver;
|
||
|
}
|