Myths BlogNever forget to improve yourself

Lido 以太坊质押协议分析

2023-03-31

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;
}

reference

lido-contract lido-js-sdk

Copyright © 2023 Powered by myt0.com