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计算公式:
1shares[account] = balanceOf(account) * totalShares / totalPooledEther
shares
建议集成dapp时存储和操作shares, 而不stETH余额,因为余额会变动,而shares不会。
shares通过 getSharesByPooledEth(uint256)获取. 逆操作getPooledEthByShares。
如果使用变化的stETH不合适,可以集成wstETH。
staking 集成
使用lido contract的submit(_referral)方法,传入奖励接收地址,同时包含以太币即可参与质押。
1import Web3 from "web3"; 2 3// using partial ABI for simplicity 4const LIDO_ABI = [ 5 { 6 constant: false, 7 inputs: [{ name: "_referral", type: "address" }], 8 name: "submit", 9 outputs: [{ name: "", type: "uint256" }], 10 payable: true, 11 stateMutability: "payable", 12 type: "function" 13 } 14]; 15 16const LIDO_ADDRESS = "0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84" 17 18const MY_REWARDS_ADDRESS = "YOUR_REWARDS_ADDRESS" 19 20async function stakeWithLido() { 21 // enable provider 22 const [user] = await window.ethereum.request({ 23 method: "eth_requestAccounts" 24 }); 25 26 // init web3 27 const web3 = new Web3(window.ethereum); 28 web3.eth.Contract.setProvider(web3); 29 30 // init Lido 31 const lidoContract = new web3.eth.Contract(LIDO_ABI, LIDO_ADDRESS); 32 33 // stake 34 const tx = await lidoContract.methods 35 .submit(MY_REWARDS_ADDRESS) 36 .send({ from: user, value: web3.utils.toWei("0.1") }); 37 38 console.log(tx); 39}
相关链上事件:
1Submitted (address sender, uint256 amount, address referral), 2Transfer (address from, address to, uint256 value), 3TransferShares (address from, address to, uint256 sharesValue).
合约里的 _submit function
1/** 2* @dev Process user deposit, mints liquid tokens and increase the pool buffer 3* @param _referral address of referral. 4* @return amount of StETH shares generated 5*/ 6function _submit(address _referral) internal returns (uint256) { 7 require(msg.value != 0, "ZERO_DEPOSIT"); 8 9 StakeLimitState.Data memory stakeLimitData = STAKING_STATE_POSITION.getStorageStakeLimitStruct(); 10 require(!stakeLimitData.isStakingPaused(), "STAKING_PAUSED"); 11 12 // 检查是否超过每日stake limit 13 if (stakeLimitData.isStakingLimitSet()) { 14 uint256 currentStakeLimit = stakeLimitData.calculateCurrentStakeLimit(); 15 16 require(msg.value <= currentStakeLimit, "STAKE_LIMIT"); 17 18 STAKING_STATE_POSITION.setStorageStakeLimitStruct( 19 stakeLimitData.updatePrevStakeLimit(currentStakeLimit - msg.value) 20 ); 21 } 22 23 uint256 sharesAmount = getSharesByPooledEth(msg.value); 24 if (sharesAmount == 0) { 25 // 首次stake, shares : eth = 1 : 1 26 sharesAmount = msg.value; 27 } 28 29 _mintShares(msg.sender, sharesAmount); 30 31 BUFFERED_ETHER_POSITION.setStorageUint256(_getBufferedEther().add(msg.value)); 32 emit Submitted(msg.sender, msg.value, _referral); 33 34 _emitTransferAfterMintingShares(msg.sender, sharesAmount); 35 return sharesAmount; 36}