Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/sanity checks soft limit (Sanity Checks Registry subPR) #535

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 3 additions & 66 deletions contracts/0.4.24/Lido.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import "@aragon/os/contracts/apps/AragonApp.sol";
import "@aragon/os/contracts/lib/math/SafeMath.sol";

import "./lib/StakeLimitUtils.sol";
import "./lib/PositiveTokenRebaseLimiter.sol";

import "./StETHPermit.sol";

Expand Down Expand Up @@ -64,7 +63,6 @@ contract Lido is StETHPermit, AragonApp {
using UnstructuredStorage for bytes32;
using StakeLimitUnstructuredStorage for bytes32;
using StakeLimitUtils for StakeLimitState.Data;
using PositiveTokenRebaseLimiter for LimiterState.Data;

/// ACL
bytes32 public constant PAUSE_ROLE = keccak256("PAUSE_ROLE");
Expand All @@ -73,7 +71,6 @@ contract Lido is StETHPermit, AragonApp {
bytes32 public constant STAKING_CONTROL_ROLE = keccak256("STAKING_CONTROL_ROLE");
bytes32 public constant MANAGE_PROTOCOL_CONTRACTS_ROLE = keccak256("MANAGE_PROTOCOL_CONTRACTS_ROLE");
bytes32 public constant BURN_ROLE = keccak256("BURN_ROLE");
bytes32 public constant MANAGE_MAX_POSITIVE_TOKEN_REBASE_ROLE = keccak256("MANAGE_MAX_POSITIVE_TOKEN_REBASE_ROLE");

uint256 private constant DEPOSIT_SIZE = 32 ether;
uint256 public constant TOTAL_BASIS_POINTS = 10000;
Expand All @@ -97,9 +94,6 @@ contract Lido is StETHPermit, AragonApp {
/// @dev number of Lido's validators available in the Consensus Layer state
// "beacon" in the `keccak256()` parameter is staying here for compatibility reason
bytes32 internal constant CL_VALIDATORS_POSITION = keccak256("lido.Lido.beaconValidators");
/// @dev positive token rebase allowed per single LidoOracle report
/// uses 1e9 precision, e.g.: 1e6 - 0.1%; 1e9 - 100%, see `setMaxPositiveTokenRebase()`
bytes32 internal constant MAX_POSITIVE_TOKEN_REBASE_POSITION = keccak256("lido.Lido.MaxPositiveTokenRebase");
/// @dev Just a counter of total amount of execution layer rewards received by Lido contract. Not used in the logic.
bytes32 internal constant TOTAL_EL_REWARDS_COLLECTED_POSITION = keccak256("lido.Lido.totalELRewardsCollected");
/// @dev version of contract
Expand All @@ -124,9 +118,6 @@ contract Lido is StETHPermit, AragonApp {
// The amount of ETH withdrawn from LidoExecutionLayerRewardsVault contract to Lido contract
event ELRewardsReceived(uint256 amount);

// Max positive token rebase set (see `setMaxPositiveTokenRebase()`)
event MaxPositiveTokenRebaseSet(uint256 maxPositiveTokenRebase);

// Records a deposit made by a user
event Submitted(address indexed sender, uint256 amount, address referral);

Expand Down Expand Up @@ -460,36 +451,6 @@ contract Lido is StETHPermit, AragonApp {
_setProtocolContracts(_oracle, _treasury, _executionLayerRewardsVault);
}

/**
* @dev Set max positive rebase allowed per single oracle report
* token rebase happens on total supply adjustment,
* huge positive rebase can incur oracle report sandwitching.
*
* stETH balance for the `account` defined as:
* balanceOf(account) = shares[account] * totalPooledEther / totalShares = shares[account] * shareRate
*
* Suppose shareRate changes when oracle reports (see `handleOracleReport`)
* which means that token rebase happens:
*
* preShareRate = preTotalPooledEther() / preTotalShares()
* postShareRate = postTotalPooledEther() / postTotalShares()
* R = (postShareRate - preShareRate) / preShareRate
*
* R > 0 corresponds to the relative positive rebase value (i.e., instant APR)
*
* NB: The value is not set by default (explicit initialization required),
* the recommended sane values are from 0.05% to 0.1%.
*
* @param _maxTokenPositiveRebase max positive token rebase value with 1e9 precision:
* e.g.: 1e6 - 0.1%; 1e9 - 100%
* - passing zero value is prohibited
* - to allow unlimited rebases, pass max uint256, i.e.: type(uint256).max
*/
function setMaxPositiveTokenRebase(uint256 _maxTokenPositiveRebase) external {
_auth(MANAGE_MAX_POSITIVE_TOKEN_REBASE_ROLE);
_setMaxPositiveTokenRebase(_maxTokenPositiveRebase);
}

/**
* @notice Updates accounting stats, collects EL rewards and distributes collected rewards if beacon balance increased
* @dev periodically called by the Oracle contract
Expand Down Expand Up @@ -522,12 +483,6 @@ contract Lido is StETHPermit, AragonApp {
require(msg.sender == getOracle(), "APP_AUTH_FAILED");
_whenNotStopped();

LimiterState.Data memory tokenRebaseLimiter = PositiveTokenRebaseLimiter.initLimiterState(
getMaxPositiveTokenRebase(),
_getTotalPooledEther(),
_getTotalShares()
);

uint256 preClBalance = CL_BALANCE_POSITION.getStorageUint256();

// update saved CL stats checking its sanity
Expand All @@ -539,9 +494,9 @@ contract Lido is StETHPermit, AragonApp {
uint256 rewardsBase = appearedValidators.mul(DEPOSIT_SIZE).add(preClBalance);
int256 clBalanceDiff = _signedSub(int256(_clBalance), int256(rewardsBase));

tokenRebaseLimiter.applyCLBalanceUpdate(clBalanceDiff);
withdrawals = tokenRebaseLimiter.appendEther(_withdrawalVaultBalance);
elRewards = tokenRebaseLimiter.appendEther(_elRewardsVaultBalance);
// TODO: temporary disable limit
withdrawals = _withdrawalVaultBalance;
elRewards = _elRewardsVaultBalance;

// collect ETH from EL and Withdrawal vaults and send some to WithdrawalQueue if required
_processETHDistribution(withdrawals, elRewards, _requestIdToFinalizeUpTo, _finalizationShareRate);
Expand Down Expand Up @@ -610,14 +565,6 @@ contract Lido is StETHPermit, AragonApp {
return TOTAL_EL_REWARDS_COLLECTED_POSITION.getStorageUint256();
}

/**
* @notice Get max positive token rebase value
* @return max positive token rebase value, nominated id MAX_POSITIVE_REBASE_PRECISION_POINTS (10**9 == 100% = 10000 BP)
*/
function getMaxPositiveTokenRebase() public view returns (uint256) {
return MAX_POSITIVE_TOKEN_REBASE_POSITION.getStorageUint256();
}

/**
* @notice Gets authorized oracle address
* @return address of oracle contract
Expand Down Expand Up @@ -1016,16 +963,6 @@ contract Lido is StETHPermit, AragonApp {
return _stakeLimitData.calculateCurrentStakeLimit();
}

/**
* @dev Set max positive token rebase value
* @param _maxPositiveTokenRebase max positive token rebase, nominated in MAX_POSITIVE_REBASE_PRECISION_POINTS
*/
function _setMaxPositiveTokenRebase(uint256 _maxPositiveTokenRebase) internal {
MAX_POSITIVE_TOKEN_REBASE_POSITION.setStorageUint256(_maxPositiveTokenRebase);

emit MaxPositiveTokenRebaseSet(_maxPositiveTokenRebase);
}

/**
* @dev Size-efficient analog of the `auth(_role)` modifier
* @param _role Permission name
Expand Down
11 changes: 2 additions & 9 deletions contracts/0.4.24/template/LidoTemplate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -370,8 +370,7 @@ contract LidoTemplate is IsContract {

function finalizeDAO(
string _daoName,
uint256 _unvestedTokensAmount,
uint256 _maxPositiveTokenRebase
uint256 _unvestedTokensAmount
)
external onlyOwner
{
Expand All @@ -381,11 +380,6 @@ contract LidoTemplate is IsContract {
require(state.dao != address(0), ERROR_DAO_NOT_DEPLOYED);
require(bytes(_daoName).length > 0, ERROR_INVALID_ID);

bytes32 LIDO_MANAGE_MAX_POSITIVE_TOKEN_REBASE = state.lido.MANAGE_MAX_POSITIVE_TOKEN_REBASE_ROLE();
_createPermissionForTemplate(state.acl, state.lido, LIDO_MANAGE_MAX_POSITIVE_TOKEN_REBASE);
state.lido.setMaxPositiveTokenRebase(_maxPositiveTokenRebase);
_removePermissionFromTemplate(state.acl, state.lido, LIDO_MANAGE_MAX_POSITIVE_TOKEN_REBASE);

if (_unvestedTokensAmount != 0) {
// using issue + assign to avoid setting the additional MINT_ROLE for the template
state.tokenManager.issue(_unvestedTokensAmount);
Expand Down Expand Up @@ -606,8 +600,7 @@ contract LidoTemplate is IsContract {
perms[3] = _state.lido.RESUME_ROLE();
perms[4] = _state.lido.STAKING_PAUSE_ROLE();
perms[5] = _state.lido.STAKING_CONTROL_ROLE();
perms[6] = _state.lido.MANAGE_MAX_POSITIVE_TOKEN_REBASE_ROLE();
for (i = 0; i < 7; ++i) {
for (i = 0; i < 6; ++i) {
_createPermissionForVoting(acl, _state.lido, perms[i], voting);
}
}
Expand Down
1 change: 0 additions & 1 deletion contracts/0.4.24/test_helpers/LidoMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ contract LidoMock is Lido {
function resumeProtocolAndStaking() public {
_resume();
_resumeStaking();
_setMaxPositiveTokenRebase(UNLIMITED_TOKEN_REBASE);
}

/**
Expand Down
1 change: 0 additions & 1 deletion contracts/0.4.24/test_helpers/LidoPushableMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ contract LidoPushableMock is Lido {
function initialize(address _oracle) public onlyInit {
_setProtocolContracts(_oracle, _oracle, address(0));
_resume();
_setMaxPositiveTokenRebase(UNLIMITED_TOKEN_REBASE);
initialized();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
// SPDX-License-Identifier: GPL-3.0

/* See contracts/COMPILERS.md */
pragma solidity 0.4.24;

import {SafeMath} from "@aragon/os/contracts/lib/math/SafeMath.sol";
pragma solidity 0.8.9;

import {Math256} from "../../common/lib/Math256.sol";

Expand Down Expand Up @@ -35,31 +33,29 @@ library LimiterState {
}

library PositiveTokenRebaseLimiter {
using SafeMath for uint256;

/// @dev Precision base for the limiter (e.g.: 1e6 - 0.1%; 1e9 - 100%)
uint256 private constant LIMITER_PRECISION_BASE = 10**9;
/// @dev Disabled limit
uint256 private constant UNLIMITED_REBASE = uint256(-1);
uint256 private constant UNLIMITED_REBASE = type(uint64).max;

/**
* @dev Initialize the new `LimiterState` structure instance
* @param _rebaseLimit max limiter value (saturation point), see `LIMITER_PRECISION_POINTS`
* @param _totalPooledEther total pooled ether, see `Lido.getTotalPooledEther()`
* @param _totalShares total shares, see `Lido.getTotalShares()`
* @return newly initialized limiter structure
* @return limiterState newly initialized limiter structure
*/
function initLimiterState(
uint256 _rebaseLimit,
uint256 _totalPooledEther,
uint256 _totalShares
) internal pure returns (LimiterState.Data memory _limiterState) {
) internal pure returns (LimiterState.Data memory limiterState) {
require(_rebaseLimit > 0, "TOO_LOW_TOKEN_REBASE_MAX");
require(_rebaseLimit <= UNLIMITED_REBASE, "WRONG_REBASE_LIMIT");

_limiterState.totalPooledEther = _totalPooledEther;
_limiterState.totalShares = _totalShares;
_limiterState.rebaseLimit = _rebaseLimit;
limiterState.totalPooledEther = _totalPooledEther;
limiterState.totalShares = _totalShares;
limiterState.rebaseLimit = _rebaseLimit;
}

/**
Expand All @@ -83,9 +79,7 @@ library PositiveTokenRebaseLimiter {
require(_limiterState.accumulatedRebase == 0, "DIRTY_LIMITER_STATE");

if (_clBalanceDiff < 0 && (_limiterState.rebaseLimit != UNLIMITED_REBASE)) {
_limiterState.rebaseLimit = _limiterState.rebaseLimit.add(
uint256(-_clBalanceDiff).mul(LIMITER_PRECISION_BASE).div(_limiterState.totalPooledEther)
);
_limiterState.rebaseLimit += (uint256(-_clBalanceDiff) * LIMITER_PRECISION_BASE) / _limiterState.totalPooledEther;
} else {
appendEther(_limiterState, uint256(_clBalanceDiff));
}
Expand All @@ -95,7 +89,7 @@ library PositiveTokenRebaseLimiter {
* @dev append ether and return value not exceeding the limit
* @param _limiterState limit repr struct
* @param _etherAmount desired ether addition
* @return allowed to add ether to not exceed the limit
* @return appendableEther allowed to add ether to not exceed the limit
*/
function appendEther(LimiterState.Data memory _limiterState, uint256 _etherAmount)
internal
Expand All @@ -104,25 +98,25 @@ library PositiveTokenRebaseLimiter {
{
if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return _etherAmount;

uint256 remainingRebase = _limiterState.rebaseLimit.sub(_limiterState.accumulatedRebase);
uint256 remainingEther = remainingRebase.mul(_limiterState.totalPooledEther).div(LIMITER_PRECISION_BASE);
uint256 remainingRebase = _limiterState.rebaseLimit - _limiterState.accumulatedRebase;
uint256 remainingEther = (remainingRebase * _limiterState.totalPooledEther) / LIMITER_PRECISION_BASE;

appendableEther = Math256.min(remainingEther, _etherAmount);

if (appendableEther == remainingEther) {
_limiterState.accumulatedRebase = _limiterState.rebaseLimit;
} else {
_limiterState.accumulatedRebase = _limiterState.accumulatedRebase.add(
appendableEther.mul(LIMITER_PRECISION_BASE).div(_limiterState.totalPooledEther)
);
_limiterState.accumulatedRebase += (
appendableEther * LIMITER_PRECISION_BASE
) / _limiterState.totalPooledEther;
}
}

/**
* @dev deduct shares and return value not exceeding the limit
* @param _limiterState limit repr struct
* @param _sharesAmount desired shares deduction
* @return allowed to deduct shares to not exceed the limit
* @return deductableShares allowed to deduct shares to not exceed the limit
*/
function deductShares(LimiterState.Data memory _limiterState, uint256 _sharesAmount)
internal
Expand All @@ -131,19 +125,19 @@ library PositiveTokenRebaseLimiter {
{
if (_limiterState.rebaseLimit == UNLIMITED_REBASE) return _sharesAmount;

uint256 remainingRebase = _limiterState.rebaseLimit.sub(_limiterState.accumulatedRebase);
uint256 remainingShares = _limiterState.totalShares.mul(remainingRebase).div(
LIMITER_PRECISION_BASE.add(remainingRebase)
);
uint256 remainingRebase = _limiterState.rebaseLimit - _limiterState.accumulatedRebase;
uint256 remainingShares = (
_limiterState.totalShares * remainingRebase
) / (LIMITER_PRECISION_BASE + remainingRebase);

deductableShares = Math256.min(_sharesAmount, remainingShares);

if (deductableShares == remainingShares) {
_limiterState.accumulatedRebase = _limiterState.rebaseLimit;
} else {
_limiterState.accumulatedRebase = _limiterState.accumulatedRebase.add(
deductableShares.mul(LIMITER_PRECISION_BASE).div(_limiterState.totalShares.sub(deductableShares))
);
_limiterState.accumulatedRebase += (
deductableShares * LIMITER_PRECISION_BASE
) / (_limiterState.totalShares - deductableShares);
}
}

Expand Down
Loading