Timelock, erc20Wrapper and erc20FlashMint verification
This commit is contained in:
37
certora/specs/ERC20FlashMint.spec
Normal file
37
certora/specs/ERC20FlashMint.spec
Normal file
@ -0,0 +1,37 @@
|
||||
import "erc20.spec"
|
||||
|
||||
methods {
|
||||
onFlashLoan(address, address, uint256, uint256, bytes) => HAVOC_ALL // HAVOC_ECF
|
||||
|
||||
_burn(address account, uint256 amount) returns(bool) => specBurn(account, amount);
|
||||
}
|
||||
|
||||
ghost mapping(address => uint256) trackedBurnAmount;
|
||||
|
||||
function specBurn(address account, uint256 amount) returns bool { // retuns needed to overcome current CVL limitations: "could not type expression "specBurn(account,amount)", message: A summary must return a simple type, but specBurn(account,amount) returns 'void'"
|
||||
trackedBurnAmount[account] = amount;
|
||||
return true;
|
||||
}
|
||||
|
||||
// ghost to save args that were passed to burn function
|
||||
// summarize burn
|
||||
// assert ghost == amount + fee
|
||||
|
||||
|
||||
// STATUS - in progress
|
||||
// HAVOC_ALL - everything is havoced => violation
|
||||
// HAVOC_ECF - verified
|
||||
// https://vaas-stg.certora.com/output/3106/8795450b626f2ca53a2b/?anonymousKey=dd774da10cc595e4e38357af9e4f50bf2c0cb02a
|
||||
// fee + flashLoan amount is burned
|
||||
rule letsWatchItBurns(env e){
|
||||
address receiver; address token; uint256 amount; bytes data;
|
||||
require amount > 0;
|
||||
|
||||
uint256 feeBefore = flashFee(e, token, amount);
|
||||
|
||||
flashLoan(e, receiver, token, amount, data);
|
||||
|
||||
uint256 burned = trackedBurnAmount[receiver];
|
||||
|
||||
assert to_mathint(amount + feeBefore) == burned, "cheater";
|
||||
}
|
||||
203
certora/specs/ERC20Wrapper.spec
Normal file
203
certora/specs/ERC20Wrapper.spec
Normal file
@ -0,0 +1,203 @@
|
||||
import "erc20.spec"
|
||||
|
||||
methods {
|
||||
underlying() returns(address) envfree
|
||||
underlyingTotalSupply() returns(uint256) envfree
|
||||
underlyingBalanceOf(address) returns(uint256) envfree
|
||||
|
||||
depositFor(address, uint256) returns(bool)
|
||||
withdrawTo(address, uint256) returns(bool)
|
||||
_recover(address) returns(uint256)
|
||||
}
|
||||
|
||||
|
||||
// STATUS - verified
|
||||
// totalsupply of wrapped should be less than or equal to underlying (assuming no transfer they should be equal) - solvency
|
||||
invariant whatAboutTotal(env e)
|
||||
totalSupply(e) <= underlyingTotalSupply()
|
||||
filtered { f -> f.selector != certorafallback_0().selector }
|
||||
{
|
||||
preserved {
|
||||
require underlyingBalanceOf(currentContract) <= underlyingTotalSupply();
|
||||
require underlying() != currentContract;
|
||||
}
|
||||
preserved depositFor(address account, uint256 amount) with (env e2){
|
||||
require underlyingBalanceOf(currentContract) <= underlyingTotalSupply();
|
||||
require totalSupply(e) + amount <= underlyingTotalSupply();
|
||||
require underlying() != currentContract;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// STATUS - verified
|
||||
// check correct values update by depositFor()
|
||||
rule depositForSpecBasic(env e){
|
||||
address account; uint256 amount;
|
||||
|
||||
require e.msg.sender != currentContract;
|
||||
require underlying() != currentContract;
|
||||
|
||||
uint256 wrapperTotalBefore = totalSupply(e);
|
||||
uint256 underlyingTotalBefore = underlyingTotalSupply();
|
||||
uint256 underlyingThisBalanceBefore = underlyingBalanceOf(currentContract);
|
||||
|
||||
depositFor(e, account, amount);
|
||||
|
||||
uint256 wrapperTotalAfter = totalSupply(e);
|
||||
uint256 underlyingTotalAfter = underlyingTotalSupply();
|
||||
uint256 underlyingThisBalanceAfter = underlyingBalanceOf(currentContract);
|
||||
|
||||
assert wrapperTotalBefore == wrapperTotalAfter - amount, "wrapper total wrong update";
|
||||
assert underlyingTotalBefore == underlyingTotalAfter, "underlying total was updated";
|
||||
assert underlyingThisBalanceBefore == underlyingThisBalanceAfter - amount, "underlying this balance wrong update";
|
||||
}
|
||||
|
||||
|
||||
rule depositForSpecWrapper(env e){
|
||||
address account; uint256 amount;
|
||||
|
||||
require e.msg.sender != currentContract;
|
||||
require underlying() != currentContract;
|
||||
|
||||
uint256 wrapperUserBalanceBefore = balanceOf(e, account);
|
||||
uint256 wrapperSenderBalanceBefore = balanceOf(e, e.msg.sender);
|
||||
|
||||
depositFor(e, account, amount);
|
||||
|
||||
uint256 wrapperUserBalanceAfter = balanceOf(e, account);
|
||||
uint256 wrapperSenderBalanceAfter = balanceOf(e, e.msg.sender);
|
||||
|
||||
assert account == e.msg.sender => wrapperUserBalanceBefore == wrapperSenderBalanceBefore
|
||||
&& wrapperUserBalanceAfter == wrapperSenderBalanceAfter
|
||||
&& wrapperUserBalanceBefore == wrapperUserBalanceAfter - amount, "wrapper balances wrong update";
|
||||
assert account != e.msg.sender => wrapperUserBalanceBefore == wrapperUserBalanceAfter - amount
|
||||
&& wrapperSenderBalanceBefore == wrapperSenderBalanceAfter, "wrapper balances wrong update";
|
||||
}
|
||||
|
||||
|
||||
rule depositForSpecUnderlying(env e){
|
||||
address account; uint256 amount;
|
||||
|
||||
require e.msg.sender != currentContract;
|
||||
require underlying() != currentContract;
|
||||
|
||||
uint256 underlyingSenderBalanceBefore = underlyingBalanceOf(e.msg.sender);
|
||||
uint256 underlyingUserBalanceBefore = underlyingBalanceOf(account);
|
||||
|
||||
depositFor(e, account, amount);
|
||||
|
||||
uint256 underlyingSenderBalanceAfter = underlyingBalanceOf(e.msg.sender);
|
||||
uint256 underlyingUserBalanceAfter = underlyingBalanceOf(account);
|
||||
|
||||
assert account == e.msg.sender => underlyingSenderBalanceBefore == underlyingUserBalanceBefore
|
||||
&& underlyingSenderBalanceAfter == underlyingUserBalanceAfter
|
||||
&& underlyingSenderBalanceBefore == underlyingSenderBalanceAfter + amount, "underlying balances wrong update";
|
||||
|
||||
assert account != e.msg.sender && account == currentContract => underlyingSenderBalanceBefore == underlyingSenderBalanceAfter + amount
|
||||
&& underlyingUserBalanceBefore == underlyingUserBalanceAfter - amount, "underlying balances wrong update";
|
||||
|
||||
assert account != e.msg.sender && account != currentContract => underlyingSenderBalanceBefore == underlyingSenderBalanceAfter + amount
|
||||
&& underlyingUserBalanceBefore == underlyingUserBalanceAfter, "underlying balances wrong update";
|
||||
}
|
||||
|
||||
|
||||
// STATUS - verified
|
||||
// check correct values update by withdrawTo()
|
||||
rule withdrawToSpecBasic(env e){
|
||||
address account; uint256 amount;
|
||||
|
||||
require e.msg.sender != currentContract;
|
||||
require underlying() != currentContract;
|
||||
|
||||
uint256 wrapperTotalBefore = totalSupply(e);
|
||||
uint256 underlyingTotalBefore = underlyingTotalSupply();
|
||||
|
||||
withdrawTo(e, account, amount);
|
||||
|
||||
uint256 wrapperTotalAfter = totalSupply(e);
|
||||
uint256 underlyingTotalAfter = underlyingTotalSupply();
|
||||
|
||||
assert wrapperTotalBefore == wrapperTotalAfter + amount, "wrapper total wrong update";
|
||||
assert underlyingTotalBefore == underlyingTotalAfter, "underlying total was updated";
|
||||
}
|
||||
|
||||
|
||||
rule withdrawToSpecWrapper(env e){
|
||||
address account; uint256 amount;
|
||||
|
||||
require e.msg.sender != currentContract;
|
||||
require underlying() != currentContract;
|
||||
|
||||
uint256 wrapperUserBalanceBefore = balanceOf(e, account);
|
||||
uint256 wrapperSenderBalanceBefore = balanceOf(e, e.msg.sender);
|
||||
|
||||
withdrawTo(e, account, amount);
|
||||
|
||||
uint256 wrapperUserBalanceAfter = balanceOf(e, account);
|
||||
uint256 wrapperSenderBalanceAfter = balanceOf(e, e.msg.sender);
|
||||
|
||||
assert account == e.msg.sender => wrapperUserBalanceBefore == wrapperSenderBalanceBefore
|
||||
&& wrapperUserBalanceAfter == wrapperSenderBalanceAfter
|
||||
&& wrapperUserBalanceBefore == wrapperUserBalanceAfter + amount, "wrapper user balance wrong update";
|
||||
assert account != e.msg.sender => wrapperSenderBalanceBefore == wrapperSenderBalanceAfter + amount
|
||||
&& wrapperUserBalanceBefore == wrapperUserBalanceAfter, "wrapper user balance wrong update";
|
||||
}
|
||||
|
||||
|
||||
rule withdrawToSpecUnderlying(env e){
|
||||
address account; uint256 amount;
|
||||
|
||||
require e.msg.sender != currentContract;
|
||||
require underlying() != currentContract;
|
||||
|
||||
uint256 underlyingSenderBalanceBefore = underlyingBalanceOf(e.msg.sender);
|
||||
uint256 underlyingUserBalanceBefore = underlyingBalanceOf(account);
|
||||
uint256 underlyingThisBalanceBefore = underlyingBalanceOf(currentContract);
|
||||
|
||||
withdrawTo(e, account, amount);
|
||||
|
||||
uint256 underlyingSenderBalanceAfter = underlyingBalanceOf(e.msg.sender);
|
||||
uint256 underlyingUserBalanceAfter = underlyingBalanceOf(account);
|
||||
uint256 underlyingThisBalanceAfter = underlyingBalanceOf(currentContract);
|
||||
|
||||
assert account == e.msg.sender => underlyingSenderBalanceBefore == underlyingUserBalanceBefore
|
||||
&& underlyingSenderBalanceAfter == underlyingUserBalanceAfter
|
||||
&& underlyingUserBalanceBefore == underlyingUserBalanceAfter - amount, "underlying balances wrong update (acc == sender)";
|
||||
|
||||
assert account != e.msg.sender && account == currentContract => underlyingUserBalanceBefore == underlyingUserBalanceAfter
|
||||
&& underlyingSenderBalanceBefore == underlyingSenderBalanceAfter, "underlying balances wrong update (acc == contract)";
|
||||
assert account != e.msg.sender && account != currentContract => underlyingUserBalanceBefore == underlyingUserBalanceAfter - amount
|
||||
&& underlyingSenderBalanceBefore == underlyingSenderBalanceAfter
|
||||
&& underlyingThisBalanceBefore == underlyingThisBalanceAfter + amount, "underlying balances wrong update (acc != contract)";
|
||||
}
|
||||
|
||||
|
||||
// STATUS - verified
|
||||
// check correct values update by _recover()
|
||||
rule recoverSpec(env e){
|
||||
address account; uint256 amount; // e.msg.sender
|
||||
require underlying() != currentContract;
|
||||
|
||||
require e.msg.sender != currentContract;
|
||||
|
||||
uint256 wrapperTotalBefore = totalSupply(e);
|
||||
uint256 wrapperUserBalanceBefore = balanceOf(e, account);
|
||||
uint256 wrapperSenderBalanceBefore = balanceOf(e, e.msg.sender);
|
||||
|
||||
uint256 underlyingThisBalanceBefore = underlyingBalanceOf(currentContract);
|
||||
|
||||
mathint value = underlyingThisBalanceBefore - wrapperTotalBefore;
|
||||
|
||||
_recover(e, account);
|
||||
|
||||
uint256 wrapperTotalAfter = totalSupply(e);
|
||||
uint256 wrapperUserBalanceAfter = balanceOf(e, account);
|
||||
uint256 wrapperSenderBalanceAfter = balanceOf(e, e.msg.sender);
|
||||
|
||||
assert wrapperTotalBefore == wrapperTotalAfter - value, "wrapper total wrong update";
|
||||
assert e.msg.sender == account => wrapperUserBalanceBefore == wrapperSenderBalanceBefore
|
||||
&& wrapperUserBalanceAfter == wrapperSenderBalanceAfter
|
||||
&& wrapperUserBalanceBefore == wrapperUserBalanceAfter - value, "wrapper balances wrong update";
|
||||
assert e.msg.sender != account => wrapperUserBalanceBefore == wrapperUserBalanceAfter - value
|
||||
&& wrapperSenderBalanceBefore == wrapperSenderBalanceAfter, "wrapper balances wrong update";
|
||||
}
|
||||
@ -40,7 +40,7 @@ definition done(bytes32 id) returns bool =
|
||||
|
||||
|
||||
function hashIdCorrelation(bytes32 id, address target, uint256 value, bytes data, bytes32 predecessor, bytes32 salt){
|
||||
require data.length < 3;
|
||||
require data.length < 7;
|
||||
require hashOperation(target, value, data, predecessor, salt) == id;
|
||||
}
|
||||
|
||||
@ -241,16 +241,20 @@ rule cannotCallExecute(method f, env e){
|
||||
}
|
||||
|
||||
|
||||
// STATUS - in progress (need working hash)
|
||||
// STATUS - in progress
|
||||
// in unset() execute() reverts
|
||||
rule executeRevertFromUnset(method f, env e){
|
||||
rule executeRevertFromUnset(method f, env e, env e2){
|
||||
address target; uint256 value; bytes data; bytes32 predecessor; bytes32 salt;
|
||||
bytes32 id;
|
||||
|
||||
hashIdCorrelation(id, target, value, data, predecessor, salt);
|
||||
// hashIdCorrelation(id, target, value, data, predecessor, salt);
|
||||
require data.length < 4;
|
||||
require hashOperation(target, value, data, predecessor, salt) == id;
|
||||
require unset(id);
|
||||
|
||||
execute@withrevert(e, target, value, data, predecessor, salt);
|
||||
scheduleCheck1@withrevert(e, id);
|
||||
|
||||
// execute@withrevert(e, target, value, data, predecessor, salt);
|
||||
|
||||
assert lastReverted, "you go against execution nature";
|
||||
}
|
||||
@ -266,6 +270,7 @@ rule executeRevertEffectCheck(method f, env e){
|
||||
require pending(id) && !ready(id, e);
|
||||
|
||||
execute@withrevert(e, target, value, data, predecessor, salt);
|
||||
bool reverted = lastReverted;
|
||||
|
||||
assert lastReverted => pending(id) && !ready(id, e), "you go against execution nature";
|
||||
}
|
||||
@ -286,7 +291,7 @@ rule cancelledNotExecuted(method f, env e){
|
||||
}
|
||||
|
||||
|
||||
// STATUS - in progress (need working hash)
|
||||
// STATUS - in progress
|
||||
// Only proposers can schedule an operation
|
||||
rule onlyProposer(method f, env e){
|
||||
bytes32 id;
|
||||
|
||||
12
certora/specs/erc20.spec
Normal file
12
certora/specs/erc20.spec
Normal file
@ -0,0 +1,12 @@
|
||||
// erc20 methods
|
||||
methods {
|
||||
name() returns (string) => DISPATCHER(true)
|
||||
symbol() returns (string) => DISPATCHER(true)
|
||||
decimals() returns (string) => DISPATCHER(true)
|
||||
totalSupply() returns (uint256) => DISPATCHER(true)
|
||||
balanceOf(address) returns (uint256) => DISPATCHER(true)
|
||||
allowance(address,address) returns (uint) => DISPATCHER(true)
|
||||
approve(address,uint256) returns (bool) => DISPATCHER(true)
|
||||
transfer(address,uint256) returns (bool) => DISPATCHER(true)
|
||||
transferFrom(address,address,uint256) returns (bool) => DISPATCHER(true)
|
||||
}
|
||||
Reference in New Issue
Block a user