mirror of https://github.com/logos-co/staking.git
220 lines
6.7 KiB
Ruby
220 lines
6.7 KiB
Ruby
import "./shared.spec";
|
|
|
|
using ERC20A as staked;
|
|
|
|
methods {
|
|
function staked.balanceOf(address) external returns (uint256) envfree;
|
|
function totalSupplyBalance() external returns (uint256) envfree;
|
|
function totalSupplyMP() external returns (uint256) envfree;
|
|
function previousManager() external returns (address) envfree;
|
|
function _.migrateFrom(address, bool, StakeManager.Account) external => NONDET;
|
|
function _.increaseTotalMP(uint256) external => NONDET;
|
|
function _.migrationInitialize(uint256,uint256,uint256,uint256,uint256,uint256,uint256) external => NONDET;
|
|
function accounts(address) external returns(address, uint256, uint256, uint256, uint256, uint256, uint256, uint256) envfree;
|
|
function Math.mulDiv(uint256 a, uint256 b, uint256 c) internal returns uint256 => mulDivSummary(a,b,c);
|
|
function _._ external => DISPATCH [] default NONDET;
|
|
}
|
|
|
|
function mulDivSummary(uint256 a, uint256 b, uint256 c) returns uint256 {
|
|
require c != 0;
|
|
return require_uint256(a*b/c);
|
|
}
|
|
|
|
function isMigrationfunction(method f) returns bool {
|
|
return
|
|
f.selector == sig:migrateTo(bool).selector ||
|
|
f.selector == sig:transferNonPending().selector;
|
|
}
|
|
|
|
/* assume that migration is zero, causing the verification to take into account only
|
|
cases where it is zero. specifically no externall call to the migration contract */
|
|
function simplification(env e) {
|
|
require currentContract.migration == 0;
|
|
require currentContract.previousManager() == 0;
|
|
require e.msg.sender != 0;
|
|
}
|
|
|
|
ghost mathint sumOfEpochRewards
|
|
{
|
|
init_state axiom sumOfEpochRewards == 0;
|
|
}
|
|
|
|
ghost mathint sumOfMultipliers /* sigma account[u].multiplier forall u */
|
|
{
|
|
init_state axiom sumOfMultipliers == 0;
|
|
}
|
|
|
|
ghost mathint sumOfBalances /* sigma account[u].balance forall u */ {
|
|
init_state axiom sumOfBalances == 0;
|
|
}
|
|
|
|
hook Sstore epochs[KEY uint256 epochId].epochReward uint256 newValue (uint256 oldValue) {
|
|
sumOfEpochRewards = sumOfEpochRewards - oldValue + newValue;
|
|
}
|
|
|
|
hook Sstore accounts[KEY address addr].balance uint256 newValue (uint256 oldValue) {
|
|
sumOfBalances = sumOfBalances - oldValue + newValue;
|
|
}
|
|
|
|
hook Sstore accounts[KEY address addr].totalMP uint256 newValue (uint256 oldValue) {
|
|
sumOfMultipliers = sumOfMultipliers - oldValue + newValue;
|
|
}
|
|
|
|
invariant sumOfBalancesIsTotalSupplyBalance()
|
|
sumOfBalances == to_mathint(totalSupplyBalance())
|
|
filtered {
|
|
m -> !requiresPreviousManager(m) && !requiresNextManager(m)
|
|
}
|
|
|
|
invariant sumOfMultipliersIsMultiplierSupply()
|
|
sumOfMultipliers == to_mathint(totalSupplyMP())
|
|
filtered {
|
|
m -> !requiresPreviousManager(m) && !requiresNextManager(m)
|
|
}
|
|
|
|
invariant sumOfEpochRewardsIsPendingRewards()
|
|
sumOfEpochRewards == to_mathint(currentContract.pendingReward)
|
|
{ preserved {
|
|
requireInvariant highEpochsAreNull(currentContract.currentEpoch);
|
|
}
|
|
}
|
|
|
|
invariant highEpochsAreNull(uint256 epochNumber)
|
|
epochNumber >= currentContract.currentEpoch => currentContract.epochs[epochNumber].epochReward == 0
|
|
filtered {
|
|
m -> !requiresPreviousManager(m) && !requiresNextManager(m)
|
|
}
|
|
|
|
invariant InitialMPIsNeverSmallerThanBalance(address addr)
|
|
to_mathint(getAccountBonusMultiplierPoints(addr)) >= to_mathint(getAccountBalance(addr))
|
|
filtered {
|
|
f -> f.selector != sig:migrateFrom(address,bool,StakeManager.Account).selector
|
|
}
|
|
|
|
invariant CurrentMPIsNeverSmallerThanInitialMP(address addr)
|
|
to_mathint(getAccountCurrentMultiplierPoints(addr)) >= to_mathint(getAccountBonusMultiplierPoints(addr))
|
|
filtered {
|
|
f -> f.selector != sig:migrateFrom(address,bool,StakeManager.Account).selector
|
|
}
|
|
|
|
invariant MPcantBeGreaterThanMaxMP(address addr)
|
|
to_mathint(getAccountCurrentMultiplierPoints(addr)) <= (getAccountBalance(addr) * 8) + getAccountBonusMultiplierPoints(addr)
|
|
filtered {
|
|
f -> f.selector != sig:migrateFrom(address,bool,StakeManager.Account).selector
|
|
}
|
|
{ preserved {
|
|
requireInvariant InitialMPIsNeverSmallerThanBalance(addr);
|
|
requireInvariant CurrentMPIsNeverSmallerThanInitialMP(addr);
|
|
}
|
|
}
|
|
|
|
rule reachability(method f)
|
|
{
|
|
calldataarg args;
|
|
env e;
|
|
f(e,args);
|
|
satisfy true;
|
|
}
|
|
|
|
rule stakingMintsMultiplierPoints1To1Ratio {
|
|
|
|
env e;
|
|
uint256 amount;
|
|
uint256 lockupTime;
|
|
uint256 multiplierPointsBefore;
|
|
uint256 multiplierPointsAfter;
|
|
|
|
requireInvariant InitialMPIsNeverSmallerThanBalance(e.msg.sender);
|
|
requireInvariant CurrentMPIsNeverSmallerThanInitialMP(e.msg.sender);
|
|
|
|
require getAccountLockUntil(e.msg.sender) <= e.block.timestamp;
|
|
|
|
multiplierPointsBefore = getAccountBonusMultiplierPoints(e.msg.sender);
|
|
stake(e, amount, lockupTime);
|
|
multiplierPointsAfter = getAccountBonusMultiplierPoints(e.msg.sender);
|
|
|
|
assert lockupTime == 0 => to_mathint(multiplierPointsAfter) == multiplierPointsBefore + amount;
|
|
assert to_mathint(multiplierPointsAfter) >= multiplierPointsBefore + amount;
|
|
}
|
|
|
|
rule stakingGreaterLockupTimeMeansGreaterMPs {
|
|
|
|
env e;
|
|
uint256 amount;
|
|
uint256 lockupTime1;
|
|
uint256 lockupTime2;
|
|
uint256 multiplierPointsAfter1;
|
|
uint256 multiplierPointsAfter2;
|
|
|
|
storage initalStorage = lastStorage;
|
|
|
|
stake(e, amount, lockupTime1);
|
|
multiplierPointsAfter1 = getAccountBonusMultiplierPoints(e.msg.sender);
|
|
|
|
stake(e, amount, lockupTime2) at initalStorage;
|
|
multiplierPointsAfter2 = getAccountBonusMultiplierPoints(e.msg.sender);
|
|
|
|
assert lockupTime1 >= lockupTime2 => to_mathint(multiplierPointsAfter1) >= to_mathint(multiplierPointsAfter2);
|
|
satisfy to_mathint(multiplierPointsAfter1) > to_mathint(multiplierPointsAfter2);
|
|
}
|
|
|
|
/**
|
|
@title when there is no migration - some functions must revert.
|
|
Other function should have non reverting cases
|
|
**/
|
|
rule revertsWhenNoMigration(method f) {
|
|
calldataarg args;
|
|
env e;
|
|
require currentContract.migration == 0;
|
|
f@withrevert(e,args);
|
|
bool reverted = lastReverted;
|
|
if (!isMigrationfunction(f))
|
|
satisfy !reverted;
|
|
assert isMigrationfunction(f) => reverted;
|
|
}
|
|
|
|
// This rule is commented out as it's just a helper rule to easily see which
|
|
// functions change the balance of the `StakeManager` contract.
|
|
//
|
|
// rule whoChangeERC20Balance( method f ) filtered { f -> f.contract != staked }
|
|
// {
|
|
// address user;
|
|
// uint256 before = staked.balanceOf(user);
|
|
// calldataarg args;
|
|
// env e;
|
|
// f(e,args);
|
|
// assert before == staked.balanceOf(user);
|
|
// }
|
|
|
|
rule epochOnlyIncreases(method f) {
|
|
env e;
|
|
calldataarg args;
|
|
|
|
uint256 epochBefore = currentContract.currentEpoch;
|
|
|
|
f(e, args);
|
|
|
|
assert currentContract.currentEpoch >= epochBefore;
|
|
}
|
|
|
|
|
|
//TODO codehash / isVault
|
|
/*
|
|
ghost mapping(address => bytes32) codehash;
|
|
|
|
hook EXTCODEHASH(address a) bytes32 hash {
|
|
require hash == codehash[a];
|
|
}
|
|
|
|
rule checksCodeHash(method f) filtered {
|
|
f -> requiresVault(f)
|
|
} {
|
|
env e;
|
|
|
|
bool isWhitelisted = isVault(codehash[e.msg.sender]);
|
|
f(e);
|
|
|
|
assert isWhitelisted;
|
|
}
|
|
*/
|