Skip to main content

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 in shares map

  • totalPooledEther 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:

NameTypeDescription
treasuryFeeBasisPointsuint16Fee for the treasury. Expressed in basis points, 10000 BP corresponding to 100%.
insuranceFeeBasisPointsuint16Fee for the insurance fund. Expressed in basis points, 10000 BP corresponding to 100%.
operatorsFeeBasisPointsuint16Fee for the node operators. Expressed in basis points, 10000 BP corresponding to 100%.

getWithdrawalCredentials()

Returns current credentials to withdraw ETH on ETH 2.0 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:

NameTypeDescription
depositedValidatorsuint256Number of deposited validators
beaconValidatorsuint256Number of Lido's validators visible in the Beacon state, reported by oracles
beaconBalanceuint256Total 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:

NameTypeDescription
isStakingPausedboolStaking pause state

getCurrentStakeLimit()

Returns how much Ether can be staked in the current block

function getCurrentStakeLimit() public view returns (uint256)

Returns:

NameTypeDescription
stakeLimituint256Currently 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:

NameTypeDescription
isStakingPausedboolStaking pause state (equivalent to return of isStakingPaused())
isStakingLimitSetboolWhether the stake limit is set or not
currentStakeLimituint256Current stake limit (equivalent to return of getCurrentStakeLimit())
maxStakeLimituint256Max stake limit
maxStakeLimitGrowthBlocksuint256Blocks needed to restore max stake limit from the fully exhausted state
prevStakeLimituint256Previously reached stake limit
prevStakeBlockNumberuint256Previously 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:

NameTypeDescription
totalELRewardsCollecteduint256Amount 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:

NameTypeDescription
elRewardsWithdrawalLimituint256Limit 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:

NameTypeDescription
elRewardsVaultaddressVault'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:

NameTypeDescription
_recipientaddressAddress of tokens recipient
_amountuint256Amount 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:

NameTypeDescription
_recipientaddressAddress of shares recipient
_sharesAmountuint256Amount 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:

NameTypeDescription
_owneraddressAddress of owner
_spenderaddressAddress 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:

NameTypeDescription
_spenderaddressAddress of spender
_amountuint256Amount 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:

NameTypeDescription
_senderaddressAddress of spender
_recipientaddressAddress of recipient
_amountuint256Amount 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:

NameTypeDescription
_senderaddressAddress of spender
_addedValueuint256Amount 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:

NameTypeDescription
_senderaddressAddress of spender
_subtractedValueuint256Amount 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) returns (uint256)

Parameters:

NameTypeDescription
_referraladdressOptional 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:

NameTypeDescription
_maxDepositsuint256Number 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

NameTypeDescription
_accountaddressAddress where shares will be burned
_sharesAmountuint256Amount 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:

NameTypeDescription
_maxStakeLimituint256Max stake limit value
_stakeLimitIncreasePerBlockuint256Stake 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:

NameTypeDescription
_executionLayerRewardsVaultaddressExecution 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:

NameTypeDescription
_limitPointsuint16Limit 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

NameTypeDescription
_feeBasisPointsuint16Fee 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

NameTypeDescription
_treasuryFeeBasisPointsuint16Fee for the treasury. Expressed in basis points, 10000 BP corresponding to 100%.
_insuranceFeeBasisPointsuint16Fee for the insurance fund. Expressed in basis points, 10000 BP corresponding to 100%.
_operatorsFeeBasisPointsuint16Fee 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

NameTypeDescription
_oracleaddressAddress of oracle contract
_treasuryaddressAddress of trasury contract
_insuranceFundaddressAddress of insurance fund contract

setWithdrawalCredentials()

Set credentials to withdraw ETH on ETH 2.0 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

NameTypeDescription
_withdrawalCredentialsbytes32Hash 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

NameTypeDescription
_beaconValidatorsuint256Number of Lido's keys in the beacon state
_beaconBalanceuint256Summarized balance of Lido-controlled keys in wei

transferToVault()

Send funds to recovery Vault. Overrides default AragonApp behaviour.

function transferToVault(address _token)

Parameters

NameTypeDescription
_tokenaddressToken to be sent to recovery vault