Files
staking-reward-streamer/certora/specs/StakeManager.spec
r4bbit e251ec5c8f !refactor(RewardsStreamerMP): rename RewardsStreamerMP to StakeManager
This renames `RewardsStreamerMP` to `StakeManager`. The original name
only exists because the project has started with different versions of
the contract. Since the contract is no longer just dealing with MPs but
actually distributes rewards, it makes sense to make this the official
stake manager of the protocol.

**BREAKING CHANGE:**

- `RewardsStreamerMP` is now `StakeManager`
- `StakingManager__*` error selectors are now `StakeManager__*`
  selectors
2025-03-28 12:57:46 +01:00

129 lines
4.2 KiB
Ruby

import "./shared.spec";
using ERC20A as staked;
methods {
function ERC20A.balanceOf(address) external returns (uint256) envfree;
function ERC20A.allowance(address, address) external returns(uint256) envfree;
function ERC20A.totalSupply() external returns(uint256) envfree;
function totalStaked() external returns (uint256) envfree;
function vaultData(address) external returns (uint256, uint256, uint256, uint256, uint256, uint256, uint256) envfree;
function lastMPUpdatedTime() external returns (uint256) envfree;
function updateGlobalState() external;
function updateVault(address vaultAddress) external;
function emergencyModeEnabled() external returns (bool) envfree;
function leave() external;
function Math.mulDiv(uint256 a, uint256 b, uint256 c) internal returns uint256 => mulDivSummary(a,b,c);
}
function mulDivSummary(uint256 a, uint256 b, uint256 c) returns uint256 {
require c != 0;
return require_uint256(a*b/c);
}
ghost mathint sumOfBalances {
init_state axiom sumOfBalances == 0;
}
hook Sstore vaultData[KEY address vault].stakedBalance uint256 newValue (uint256 oldValue) {
sumOfBalances = sumOfBalances - oldValue + newValue;
}
function getVaultMaxMP(address vault) returns uint256 {
uint256 maxMP;
_, _, _, maxMP, _, _, _ = streamer.vaultData(vault);
return maxMP;
}
function getVaultMPAccrued(address vault) returns uint256 {
uint256 vaultMPAccrued;
_, _, vaultMPAccrued, _, _, _, _ = streamer.vaultData(vault);
return vaultMPAccrued;
}
function getVaultLockUntil(address vault) returns uint256 {
uint256 lockUntil;
_, _, _, _, _, lockUntil, _ = streamer.vaultData(vault);
return lockUntil;
}
invariant sumOfBalancesIsTotalStaked()
sumOfBalances == to_mathint(totalStaked())
filtered {
f -> f.selector != sig:upgradeToAndCall(address,bytes).selector
}
invariant vaultMPLessEqualVaultMaxMP(address vault)
to_mathint(getVaultMPAccrued(vault)) <= to_mathint(getVaultMaxMP(vault))
filtered {
f -> f.selector != sig:upgradeToAndCall(address,bytes).selector && f.selector != sig:migrateToVault(address).selector
}
invariant vaultMPGreaterEqualVaultStakedBalance(address vault)
to_mathint(getVaultMPAccrued(vault)) >= to_mathint(getVaultStakedBalance(vault))
filtered {
f -> f.selector != sig:upgradeToAndCall(address,bytes).selector && f.selector != sig:migrateToVault(address).selector
}
rule stakingMintsMultiplierPoints1To1Ratio {
env e;
uint256 amount;
uint256 lockupTime;
uint256 multiplierPointsBefore;
uint256 multiplierPointsAfter;
requireInvariant vaultMPGreaterEqualVaultStakedBalance(e.msg.sender);
require getVaultLockUntil(e.msg.sender) <= e.block.timestamp;
updateVault(e, e.msg.sender);
uint256 t = lastMPUpdatedTime();
multiplierPointsBefore = getVaultMPAccrued(e.msg.sender);
stake(e, amount, lockupTime);
// we need to ensure time has not progressed because that would accrue MP
// which makes it harder to proof this rule
require lastMPUpdatedTime() == t;
multiplierPointsAfter = getVaultMPAccrued(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 = getVaultMPAccrued(e.msg.sender);
stake(e, amount, lockupTime2) at initalStorage;
multiplierPointsAfter2 = getVaultMPAccrued(e.msg.sender);
assert lockupTime1 >= lockupTime2 => to_mathint(multiplierPointsAfter1) >= to_mathint(multiplierPointsAfter2);
satisfy to_mathint(multiplierPointsAfter1) > to_mathint(multiplierPointsAfter2);
}
rule MPsOnlyDecreaseWhenUnstaking(method f) filtered { f -> f.selector != sig:upgradeToAndCall(address,bytes).selector } {
env e;
calldataarg args;
uint256 totalMPBefore = totalMPAccrued(e);
f(e, args);
uint256 totalMPAfter = totalMPAccrued(e);
assert totalMPAfter < totalMPBefore => f.selector == sig:unstake(uint256).selector || f.selector == sig:leave().selector;
}