Lido
Lido is the core contract which acts as a liquid staking pool. The contract is responsible for Ether deposits and withdrawals, minting and burning liquid tokens, delegating funds to node operators, applying fees, and accepting updates from the oracle contract. Node Operators' logic is extracted to a separate contract, NodeOperatorsRegistry.
Lido also acts as an ERC20 token which represents staked ether, stETH. Tokens are minted upon deposit and burned when redeemed. Despite stETH tokens being pegged 1:1 to the ether that is held by Lido, the market exchange rate between stETH and ETH may vary. stETH holder balances are updated daily with oracle reports.
note
At the moment withdrawals are not possible on the beacon chain and there's no workaround. Lido will be upgraded to an actual implementation when withdrawals are enabled (Phase 1.5 or 2 of Eth2 launch, likely late 2022 or 2023).
Rebasing​
When a rebase occurs, the supply of the token is increased or decreased algorithmically, based on staking rewards (or slashing penalties) on the Beacon chain, and execution layer rewards (starting from the Merge Ethereum upgrade). A rebase happens when oracles report beacon stats.
The rebasing mechanism is implemented via "shares". Instead of storing map with account balances, Lido stores which share owned by account in the total amount of Ether controlled by the protocol.
The balance of an account is calculated as follows:
balanceOf(account) = shares[account] * totalPooledEther / totalShares
shares
- map of user account shares. Every time user deposit ether, it converted to shares and added to current user shares.totalShares
sum of shares of all account inshares
maptotalPooledEther
is a sum of three types of ether owned by protocol:- buffered balance - ether stored on contract and haven't deposited to official Deposit contract yet.
- transient balance - ether submitted to the official Deposit contract but not yet visible in the beacon state.
- beacon balance - total amount of ether on validator accounts. This value reported by oracles and makes strongest impact to stETH total supply change.
For example, assume that we have:
totalShares = 500
totalPooledEther = 10 ETH
sharesOf(Alice) -> 100
sharesOf(Bob) -> 400
Therefore:
balanceOf(Alice) -> 2 tokens which corresponds 2 ETH
balanceOf(Bob) -> 8 tokens which corresponds 8 ETH
Beacon Stats Reporting​
One of the most important parts of protocol, it's precise and steady reported data about current balances of validators. Such reports happen once at defined period of time, called frame. Frame duration set by DAO, current value is 24 hours.
To update stats on main Lido contract oracle demands quorum to be reached. Quorum - is a necessary amount of reports with equal stats from offchain oracle daemons run by protocol participants. Quorum size and members controlled by DAO. If quorum wasn't reached next report can happen only at the first epoch of next frame (after 24 hours).
Report consists of count of validators participated in protocol - beacon validators and total amount of ether on validator accounts - beacon balance. Typically beacon balance growth from report to report, but in exceptional cases it also can drops, because of slashing.
- When beacon balance grown between reports, protocol register profit and distribute reward of fresh minting stETH tokens between stETH holders, node operators, insurance fund and treasury. Fee distribution for node operators, insurance fund and treasury can be set by DAO.
- When frame was ended with slashing and new beacon balance less than previous one total supply of stETH becomes less than in previous report and no rewards distributed.
Execution layer rewards​
Lido implements an architecture design which was proposed in the Lido Improvement Proposal #12 to collect the execution level rewards (starting from the Merge hardfork) and distribute them as part of the Lido Oracle report.
These execution layer rewards are initially accumulaed on the dedicated LidoExecutionLayerRewardsVault
contract and include priority fees and MEV.
Staking rate limiting​
Lido features a safeguard mechanism to prevent huge APR losses facing the post-merge entry queue demand.
New staking requests can be rate-limited with a moving soft cap for the stake amount per desired period.
For details, see the Lido Improvement Proposal #14.
View Methods​
name()​
Returns the name of the token
function name() returns (string)
symbol()​
Returns the symbol of the token, usually a shorter version of the name
function symbol() returns (string)
decimals()​
Returns the number of decimals for getting user representation of a token amount.
function decimals() returns (uint8)
totalSupply()​
Returns the amount of tokens in existence.
function totalSupply() returns (uint256)
note
Always equals to getTotalPooledEther()
since token amount
is pegged to the total amount of Ether controlled by the protocol.
getTotalPooledEther()​
Returns the entire amount of Ether controlled by the protocol
function getTotalPooledEther() returns (uint256)
note
The sum of all ETH balances in the protocol, equals to the total supply of stETH.
balanceOf()​
Returns the amount of tokens owned by the _account
function balanceOf(address _account) returns (uint256)
note
Balances are dynamic and equal the _account
's share in the amount of the
total Ether controlled by the protocol. See sharesOf
.
getTotalShares()​
Returns the total amount of shares in existence.
function getTotalShares() returns (uint256)
sharesOf()​
Returns the amount of shares owned by _account
function sharesOf(address _account) returns (uint256)
getSharesByPooledEth()​
Returns the amount of shares that corresponds to _ethAmount
protocol-controlled Ether
function getSharesByPooledEth(uint256 _ethAmount) returns (uint256)
getPooledEthByShares()​
Returns the amount of Ether that corresponds to _sharesAmount
token shares
function getPooledEthByShares(uint256 _sharesAmount) returns (uint256)
getFee()​
Returns staking rewards fee rate
function getFee() returns (uint16)
Returns:​
Fee in basis points. 10000 BP corresponding to 100%.
getFeeDistribution()​
Returns fee distribution proportion
function getFeeDistribution() returns (
uint16 treasuryFeeBasisPoints,
uint16 insuranceFeeBasisPoints,
uint16 operatorsFeeBasisPoints
)
Returns:​
Name | Type | Description |
---|---|---|
treasuryFeeBasisPoints | uint16 | Fee for the treasury. Expressed in basis points, 10000 BP corresponding to 100%. |
insuranceFeeBasisPoints | uint16 | Fee for the insurance fund. Expressed in basis points, 10000 BP corresponding to 100%. |
operatorsFeeBasisPoints | uint16 | Fee for the node operators. Expressed in basis points, 10000 BP corresponding to 100%. |
getWithdrawalCredentials()​
Returns current credentials to withdraw ETH on Ethereum side after the phase 2 is launched
function getWithdrawalCredentials() returns (bytes32)
getBufferedEther()​
Get the amount of Ether temporary buffered on this contract balance
note
Buffered balance is kept on the contract from the moment the funds are received from user until the moment they are actually sent to the official Deposit contract.
function getBufferedEther() returns (uint256)
Returns:​
Amount of buffered funds in wei
getDepositContract()​
Gets deposit contract handle
function getDepositContract() public view returns (IDepositContract)
Returns:​
Address of deposit contract
getOracle()​
Returns authorized oracle address
function getOracle() returns (address)
getOperators()​
Gets node operators registry interface handle
function getOperators() returns (INodeOperatorsRegistry)
Returns:​
Address of NodeOperatorsRegistry contract
getTreasury()​
Returns the treasury address
function getTreasury() returns (address)
getInsuranceFund()​
Returns the insurance fund address
function getInsuranceFund() returns (address)
getBeaconStat()​
Returns the key values related to Beacon-side
function getBeaconStat() returns (
uint256 depositedValidators,
uint256 beaconValidators,
uint256 beaconBalance
)
Returns:​
Name | Type | Description |
---|---|---|
depositedValidators | uint256 | Number of deposited validators |
beaconValidators | uint256 | Number of Lido's validators visible in the Beacon state, reported by oracles |
beaconBalance | uint256 | Total amount of Beacon-side Ether (sum of all the balances of Lido validators) |
isStakingPaused()​
Returns staking state: whether it's paused or not
function isStakingPaused() external view returns (bool)
Returns:​
Name | Type | Description |
---|---|---|
isStakingPaused | bool | Staking pause state |
getCurrentStakeLimit()​
Returns how much Ether can be staked in the current block
function getCurrentStakeLimit() public view returns (uint256)
Returns:​
Name | Type | Description |
---|---|---|
stakeLimit | uint256 | Currently availble limit for stake request in the current block |
note
Special return values:
2^256 - 1
if staking is unlimited;0
if staking is paused or if limit is exhausted.
getStakeLimitFullInfo()​
Returns full info about current stake limit params and state
function getStakeLimitFullInfo() external view returns (
bool isStakingPaused,
bool isStakingLimitSet,
uint256 currentStakeLimit,
uint256 maxStakeLimit,
uint256 maxStakeLimitGrowthBlocks,
uint256 prevStakeLimit,
uint256 prevStakeBlockNumber
)
Returns:​
Name | Type | Description |
---|---|---|
isStakingPaused | bool | Staking pause state (equivalent to return of isStakingPaused() ) |
isStakingLimitSet | bool | Whether the stake limit is set or not |
currentStakeLimit | uint256 | Current stake limit (equivalent to return of getCurrentStakeLimit() ) |
maxStakeLimit | uint256 | Max stake limit |
maxStakeLimitGrowthBlocks | uint256 | Blocks needed to restore max stake limit from the fully exhausted state |
prevStakeLimit | uint256 | Previously reached stake limit |
prevStakeBlockNumber | uint256 | Previously seen block number |
getTotalELRewardsCollected()​
Get total amount of execution layer rewards collected to Lido contract
note
Ether got through LidoExecutionLayerRewardsVault
is kept on this contract's balance the same way
as other buffered Ether is kept (until it gets deposited).
function getTotalELRewardsCollected() external view returns (uint256)
Returns:​
Name | Type | Description |
---|---|---|
totalELRewardsCollected | uint256 | Amount of funds received as execution layer rewards (in wei) |
getELRewardsWithdrawalLimit()​
Get limit in basis points to amount of ETH to withdraw per LidoOracle
report.
function getELRewardsWithdrawalLimit() external view returns (uint256)
Returns:​
Name | Type | Description |
---|---|---|
elRewardsWithdrawalLimit | uint256 | Limit in basis points to amount of ETH to withdraw per LidoOracle report |
getELRewardsVault()​
Returns address of the contract set as LidoExecutionLayerRewardsVault
.
function getELRewardsVault() public view returns (address)
Returns:​
Name | Type | Description |
---|---|---|
elRewardsVault | address | Vault's address |
Methods​
transfer()​
Moves _amount
tokens from the caller's account to the _recipient
account.
function transfer(address _recipient, uint256 _amount) returns (bool)
note
Requirements:
_recipient
cannot be the zero address.- the caller must have a balance of at least
_amount
. - the contract must not be paused.
Parameters:​
Name | Type | Description |
---|---|---|
_recipient | address | Address of tokens recipient |
_amount | uint256 | Amount of tokens to transfer |
Returns:​
A boolean value indicating whether the operation succeeded.
transferShares()​
Moves token shares from the caller's account to the provided recipient account.
function transferShares(address _recipient, uint256 _sharesAmount) public returns (uint256)
note
Requirements:
_recipient
cannot be the zero address.- the caller must have at least
_sharesAmount
shares. - the contract must not be paused.
Parameters:​
Name | Type | Description |
---|---|---|
_recipient | address | Address of shares recipient |
_sharesAmount | uint256 | Amount of shares to transfer |
Returns:​
Amount of transferred tokens.
allowance()​
Returns the remaining number of tokens that _spender
is allowed to spend
on behalf of _owner
through transferFrom
. This is zero by default.
function allowance(address _owner, address _spender) returns (uint256)
note
This value changes when approve
or transferFrom
is called.
Parameters:​
Name | Type | Description |
---|---|---|
_owner | address | Address of owner |
_spender | address | Address of spender |
approve()​
Sets _amount
as the allowance of _spender
over the caller's tokens
function approve(address _spender, uint256 _amount) returns (bool)
note
Requirements:
_spender
cannot be the zero address.- the contract must not be paused.
Parameters:​
Name | Type | Description |
---|---|---|
_spender | address | Address of spender |
_amount | uint256 | Amount of tokens |
Returns:​
A boolean value indicating whether the operation succeeded
transferFrom()​
Moves _amount
tokens from _sender
to _recipient
using the
allowance mechanism. _amount
is then deducted from the caller's
allowance.
function transferFrom(
address _sender,
address _recipient,
uint256 _amount
) returns (bool)
note
Requirements:
_sender
and_recipient
cannot be the zero addresses._sender
must have a balance of at least_amount
.- the caller must have allowance for
_sender
's tokens of at least_amount
. - the contract must not be paused.
Parameters:​
Name | Type | Description |
---|---|---|
_sender | address | Address of spender |
_recipient | address | Address of recipient |
_amount | uint256 | Amount of tokens |
Returns:​
A boolean value indicating whether the operation succeeded
increaseAllowance()​
Atomically increases the allowance granted to _spender
by the caller by _addedValue
This is an alternative to approve
that can be used as a mitigation for problems described here
function increaseAllowance(address _spender, uint256 _addedValue) returns (bool)
note
Requirements:
_spender
cannot be the the zero address.- the contract must not be paused.
Parameters:​
Name | Type | Description |
---|---|---|
_sender | address | Address of spender |
_addedValue | uint256 | Amount of tokens to increase allowance |
Returns:​
Returns a boolean value indicating whether the operation succeeded
decreaseAllowance()​
Atomically decreases the allowance granted to _spender
by the caller by _subtractedValue
This is an alternative to approve
that can be used as a mitigation for
problems described here
function decreaseAllowance(address _spender, uint256 _subtractedValue) returns (bool)
note
Requirements:
_spender
cannot be the zero address._spender
must have allowance for the caller of at least_subtractedValue
.- the contract must not be paused.
Parameters:​
Name | Type | Description |
---|---|---|
_sender | address | Address of spender |
_subtractedValue | uint256 | Amount of tokens to decrease allowance |
Returns:​
Returns a boolean value indicating whether the operation succeeded
submit()​
Send funds to the pool with optional _referral parameter
function submit(address _referral) payable returns (uint256)
Parameters:​
Name | Type | Description |
---|---|---|
_referral | address | Optional referral address |
Returns:​
Amount of StETH shares generated
depositBufferedEther()​
Deposits buffered ethers to the official DepositContract. If _maxDeposits
provided makes no more than _maxDeposits
deposit calls
function depositBufferedEther()
function depositBufferedEther(uint256 _maxDeposits)
Parameters:​
Name | Type | Description |
---|---|---|
_maxDeposits | uint256 | Number of max deposit calls |
burnShares()​
Destroys _sharesAmount
shares from _account
's holdings, decreasing the total amount of shares.
function burnShares(
address _account,
uint256 _sharesAmount
) returns (uint256 newTotalShares)
note
This doesn't decrease the token total supply.
Requirements:
_account
cannot be the zero address._account
must hold at least_sharesAmount
shares.- the contract must not be paused.
Parameters​
Name | Type | Description |
---|---|---|
_account | address | Address where shares will be burned |
_sharesAmount | uint256 | Amount of shares to burn |
Returns​
Amount of totalShares after tokens burning
stop()​
Stop pool routine operations
function stop()
resume()​
Resume pool routine operations
function resume()
pauseStaking()​
Stops accepting new Ether to the protocol
note
While accepting new Ether is stopped, calls to the submit
function,
as well as to the default payable function, will revert.
function pauseStaking() external
resumeStaking()​
Resumes accepting new Ether to the protocol (if pauseStaking
was called previously)
note
Staking could be rate-limited by imposing a limit on the stake amount at each moment in time,
see setStakingLimit()
and removeStakingLimit()
function resumeStaking() external
setStakingLimit()​
Sets the staking rate limit
note
Reverts if:
_maxStakeLimit
== 0_maxStakeLimit
>= 2^96_maxStakeLimit
<_stakeLimitIncreasePerBlock
_maxStakeLimit
/_stakeLimitIncreasePerBlock
>= 2^32 (only if_stakeLimitIncreasePerBlock
!= 0)
function setStakingLimit(uint256 _maxStakeLimit, uint256 _stakeLimitIncreasePerBlock) external
Parameters:​
Name | Type | Description |
---|---|---|
_maxStakeLimit | uint256 | Max stake limit value |
_stakeLimitIncreasePerBlock | uint256 | Stake limit increase per single block |
Limit explanation scheme:
* â–² Stake limit
* │..... ..... ........ ... .... ... Stake limit = max
* │ . . . . . . . . .
* │ . . . . . . . . .
* │ . . . . .
* │──────────────────────────────────────────────────> Time
* │ ^ ^ ^ ^^^ ^ ^ ^ ^^^ ^ Stake events
removeStakingLimit()​
Removes the staking rate limit
function removeStakingLimit() external
receiveELRewards()​
A payable function for execution layer rewards,
can be called only by the LidoExecutionLayerRewardsVault
contract
function receiveELRewards() external payable
setELRewardsVault()​
Sets the address of LidoExecutionLayerRewardsVault
contract
function setELRewardsVault(address _executionLayerRewardsVault) external
Parameters:​
Name | Type | Description |
---|---|---|
_executionLayerRewardsVault | address | Execution layer rewards vault contract address |
setELRewardsWithdrawalLimit()​
Sets limit on amount of ETH to withdraw from execution layer rewards vault per LidoOracle
report
function setELRewardsWithdrawalLimit(uint16 _limitPoints) external
Parameters:​
Name | Type | Description |
---|---|---|
_limitPoints | uint16 | Limit in basis points to amount of ETH to withdraw per LidoOracle report |
setFee()​
Set fee rate to _feeBasisPoints
basis points. The fees are accrued when oracles report staking results
function setFee(uint16 _feeBasisPoints)
Parameters​
Name | Type | Description |
---|---|---|
_feeBasisPoints | uint16 | Fee expressed in basis points, 10000 BP corresponding to 100%. |
setFeeDistribution()​
Set fee distribution: _treasuryFeeBasisPoints
basis points go to the treasury,
_insuranceFeeBasisPoints
basis points go to the insurance fund,
_operatorsFeeBasisPoints
basis points go to node operators.
The sum has to be 10 000.
function setFeeDistribution(
uint16 _treasuryFeeBasisPoints,
uint16 _insuranceFeeBasisPoints,
uint16 _operatorsFeeBasisPoints
)
Parameters​
Name | Type | Description |
---|---|---|
_treasuryFeeBasisPoints | uint16 | Fee for the treasury. Expressed in basis points, 10000 BP corresponding to 100%. |
_insuranceFeeBasisPoints | uint16 | Fee for the insurance fund. Expressed in basis points, 10000 BP corresponding to 100%. |
_operatorsFeeBasisPoints | uint16 | Fee for the node operators. Expressed in basis points, 10000 BP corresponding to 100%. |
setProtocolContracts()​
Set Lido protocol contracts (oracle, treasury, insurance fund).
Oracle contract specified here is allowed to make periodical updates of beacon stats by calling handleOracleReport
.
Treasury contract specified here is used to accumulate the protocol treasury fee.
Insurance fund contract specified here is used to accumulate the protocol insurance fee.
function setProtocolContracts(address _oracle, address _treasury, address _insuranceFund) external
Parameters​
Name | Type | Description |
---|---|---|
_oracle | address | Address of oracle contract |
_treasury | address | Address of trasury contract |
_insuranceFund | address | Address of insurance fund contract |
setWithdrawalCredentials()​
Set credentials to withdraw ETH on Ethereum side after the phase 2 is launched to _withdrawalCredentials
function setWithdrawalCredentials(bytes32 _withdrawalCredentials)
note
Note that setWithdrawalCredentials
discards all unused signing keys as the signatures are invalidated.
Parameters​
Name | Type | Description |
---|---|---|
_withdrawalCredentials | bytes32 | Hash of withdrawal multisignature key as accepted by the deposit_contract.deposit functione |
handleOracleReport()​
Updates the number of Lido-controlled keys in the beacon validators set and their total balance. The method is called by the Lido oracle to handle its quorum-reached report.
function handleOracleReport(uint256 _beaconValidators, uint256 _beaconBalance)
Parameters​
Name | Type | Description |
---|---|---|
_beaconValidators | uint256 | Number of Lido's keys in the beacon state |
_beaconBalance | uint256 | Summarized balance of Lido-controlled keys in wei |
transferToVault()​
Send funds to recovery Vault. Overrides default AragonApp behaviour.
function transferToVault(address _token)
Parameters​
Name | Type | Description |
---|---|---|
_token | address | Token to be sent to recovery vault |