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. stETH tokens are pegged 1:1 to the Ethers that are held by Lido. stETH token’s balances are updated when the oracle reports change in total stake every day.

Rebasing#

When a rebase occurs the supply of the token is increased or decreased algorithmically, based on the staking rewards(or slashing penalties) in the Eth2 chain. Rebase happens when oracles report beacon stats.

Rebasing mechanism 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.

Balance of account calculated next way:

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.

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)

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.

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()

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%.

setOracle()#

Set authorized oracle contract address to _oracle

function setOracle(address _oracle)

Parameters#

NameTypeDescription
_oracleaddressAddress of oracle contract

setTreasury()#

Set treasury contract address to _treasury. This contract is used to accumulate the protocol treasury fee

function setTreasury(address _treasury)

Parameters#

NameTypeDescription
_treasuryaddressAddress of contract which accumulates treasury fee

setInsuranceFund()#

Set insuranceFund contract address to _insuranceFund. This contract is used to accumulate the protocol insurance fee

function setInsuranceFund(address _insuranceFund)

Parameters#

NameTypeDescription
_insuranceFundaddressAddress of contract which accumulates insurance fee

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

withdraw()#

Issues withdrawal request. Not implemented.

function withdraw(uint256 _amount, bytes32 _pubkeyHash)
note

Will be upgraded to an actual implementation when withdrawals are enabled (Phase 1.5 or 2 of Eth2 launch, likely late 2021 or 2022). At the moment withdrawals are not possible in the beacon chain and there's no workaround

Parameters#

NameTypeDescription
_amountuint256Amount of StETH to withdraw
_pubkeyHashbytes32Receiving address

pushBeacon()#

Updates the number of Lido-controlled keys in the beacon validators set and their total balance.

function pushBeacon(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