Lido 以太坊质押协议分析
stETH
Lido
协议是一个流动性质押协议,进行代币质押会得到对应的stToken
, 比如质押ETH
得到stETH
,原生质押会把ETH
锁到beacon chain
上,而stToken
具有流动性,可以参与交易,借贷等。
每天Lido
的预言机都会报告beacon chain
上的ETH
数量以更新stETH
的余额。stETH
的更新会自动反馈到所有相关联的地址。
Lido
还发行一种wstETH
,都是ERC20
,区别在于stETH
会增发, wstETH
不会。在任何时候,任何数量的 stETH 都可以通过trustless wrapper
转换为wstETH
,反之亦然,因此代币有效地共享流动性。例如,Maker 上抵押不足的 wstETH
头寸可以通过解包 wstETH
并将其换成 Curve 上的以太币来清算。
beacon chain oracle
当前有5个DAO选举的oracle
每日监控beacon chain
的变化,3/5
的数目决定是否更新stETH
,所有的余额增减都会反映到stETH
, 所以通过stETH
质押并不保证不会slash
,只是不会将slash
具体到个人而是平均到所有持有者,可以通过oracle dashboard查看Oracle
状态。
Oracle operators: Stakefish, Certus One, Chorus One, Staking Facilities, P2P Validator.
shares计算公式:
shares[account] = balanceOf(account) * totalShares / totalPooledEther
shares
建议集成dapp时存储和操作shares
, 而不stETH
余额,因为余额会变动,而shares
不会。
shares
通过 getSharesByPooledEth(uint256)
获取. 逆操作getPooledEthByShares
。
如果使用变化的stETH不合适,可以集成wstETH。
staking 集成
使用(lido contract)[]的submit(_referral)
方法,传入奖励接收地址,同时包含以太币即可参与质押。
import Web3 from "web3";
// using partial ABI for simplicity
const LIDO_ABI = [
{
constant: false,
inputs: [{ name: "_referral", type: "address" }],
name: "submit",
outputs: [{ name: "", type: "uint256" }],
payable: true,
stateMutability: "payable",
type: "function"
}
];
const LIDO_ADDRESS = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84"
const MY_REWARDS_ADDRESS = "YOUR_REWARDS_ADDRESS"
async function stakeWithLido() {
// enable provider
const [user] = await window.ethereum.request({
method: "eth_requestAccounts"
});
// init web3
const web3 = new Web3(window.ethereum);
web3.eth.Contract.setProvider(web3);
// init Lido
const lidoContract = new web3.eth.Contract(LIDO_ABI, LIDO_ADDRESS);
// stake
const tx = await lidoContract.methods
.submit(MY_REWARDS_ADDRESS)
.send({ from: user, value: web3.utils.toWei("0.1") });
console.log(tx);
}
相关链上事件:
Submitted (address sender, uint256 amount, address referral),
Transfer (address from, address to, uint256 value),
TransferShares (address from, address to, uint256 sharesValue).
合约里的 _submit function
/**
* @dev Process user deposit, mints liquid tokens and increase the pool buffer
* @param _referral address of referral.
* @return amount of StETH shares generated
*/
function _submit(address _referral) internal returns (uint256) {
require(msg.value != 0, "ZERO_DEPOSIT");
StakeLimitState.Data memory stakeLimitData = STAKING_STATE_POSITION.getStorageStakeLimitStruct();
require(!stakeLimitData.isStakingPaused(), "STAKING_PAUSED");
// 检查是否超过每日stake limit
if (stakeLimitData.isStakingLimitSet()) {
uint256 currentStakeLimit = stakeLimitData.calculateCurrentStakeLimit();
require(msg.value <= currentStakeLimit, "STAKE_LIMIT");
STAKING_STATE_POSITION.setStorageStakeLimitStruct(
stakeLimitData.updatePrevStakeLimit(currentStakeLimit - msg.value)
);
}
uint256 sharesAmount = getSharesByPooledEth(msg.value);
if (sharesAmount == 0) {
// 首次stake, shares : eth = 1 : 1
sharesAmount = msg.value;
}
_mintShares(msg.sender, sharesAmount);
BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(msg.value));
emit Submitted(msg.sender, msg.value, _referral);
_emitTransferAfterMintingShares(msg.sender, sharesAmount);
return sharesAmount;
}