EigenLayer 合约解读

  • Leo
  • 更新于 2024-09-29 17:50
  • 阅读 554

名词解读AVSAVS是任何需要自己的分布式验证语义进行验证的系统,例如侧链、数据可用性层、新虚拟机、守护者网络、预言机网络、桥接器、阈值加密方案、可信执行环境等。每个AVS都有自己的一套合同,这些合同保存与服务功能相关的状态,例如哪些运营商正在运行该服务以及有多少权益在保护该服务。

名词解读

AVS

AVS 是任何需要自己的分布式验证语义进行验证的系统,例如侧链、数据可用性层、新虚拟机、守护者网络、预言机网络、桥接器、阈值加密方案、可信执行环境等。 每个 AVS 都有自己的一套合同,这些合同保存与服务功能相关的状态,例如哪些运营商正在运行该服务以及有多少权益在保护该服务。

image.png

LSD

LSD 是流动性质押协议的意思。 你质押 ETH 给到Lido。Lido会返还给你 1:1 锚定 ETH 的 stETH 代币。此stETH你可以分享质押的收益,也可以用来兑换ETH,并且还可以参与其他的defi交易。除此之外还可以将stETH质押的EidgenLayer上赚取额外的收益,如下图所示

image.png

ReStaking

再质押体系,浅显的理解就是“兼职”,借助再质押体系,以太坊质押网络现在可以单独承接有安全需求的 dAPP,同时仍可以为以太坊主网提供安全保障,并且申领质押奖励,再质押奖励等。 另外再质押出售的是以太坊的安全性,以往的 L2 Rollup 只能依据以太坊区块空间大小定价,表现为 DA 和 Gas Fee,再质押将以太坊安全性标准化,并且将其 “货币化”,以更廉价的方式提供等同以太坊的安全性。

image.png

ReStaking出现之前,以 DA 等为例,要么用昂贵但安全的以太坊主网,要么用廉价但不正统的 Celestia 等服务。现在利用再质押,可以一面使用以太坊的安全性,一方面减少费用支出,同时既有的多重质押收益和 LRT 再质押代币的流通功能也不受限。

什么是EigenLayer

EigenLayer 是以太坊首个提供可编程信任的通用网络。从技术上讲,它是以太坊上的一组智能合约,以及一组链下节点软件,允许个人质押者、质押服务提供商和 LST 质押者选择运行新的链下分布式系统。

如今,以太坊验证者质押 ETH 是为了做出基于资本的承诺,即他们不会偏离以太坊协议。EigenLayer 扩大了这些质押者可以做出的承诺范围,包括选择积极参与和响应新用例和分布式系统的任务。质押者通过运行额外的节点软件,并授予 EigenLayer 智能合约以可编程的方式对其质押的 ETH 或 LST 施加分布式系统指定的额外削减条件的能力,来选择加入这些系统。

我们讲这些新用例和分布式系统统统称为“主动验证服务”-AVS。 EigenLayer的一个非常强大的优势是,区块提议者可以在以太坊核心共识软件之外做出承诺。无需通过协议升级的情况下尝试区块构建的新想法。

那EigenLayer又做了哪些创新呢?

通过重新质押实现池化安全

EigenLayer 提供了一种新的池化安全机制,允许模块通过重新质押的 ETH 而不是自己的代币进行保护。具体来说,以太坊验证者可以将其信标链提现凭证设置为 EigenLayer 智能合约,并选择加入基于 EigenLayer 构建的新模块。验证者下载并运行这些模块所需的任何其他节点软件。然后,这些模块能够对选择加入该模块的验证者的质押 ETH 施加额外的削减条件。我们称这种机制为重新质押。

开放市场

EigenLayer 提供了一种开放市场机制,该机制控制其池化证券如何由验证者提供以及如何由 AVS 使用。EigenLayer 创建了一个市场,验证者可以在其中选择是否加入或退出基于 EigenLayer 构建的每个模块。各种模块将需要充分激励验证者将重新质押的 Eth 分配给他们的模块,并且验证者将帮助确定哪些模块值得分配这种额外的池化证券,因为可能会有额外的削减。EigenLayer 的选择加入动态有两个重要的好处:(1)核心区块链的稳定、保守治理与快速、高效的自由市场治理结构相得益彰,以推出新的辅助功能;(2)选择加入验证使新的区块链模块能够利用验证者之间的异构资源,从而更好地平衡安全性和性能。

EigenLayer 之前AVS生态系统的四大缺陷

新 AVS 的引导问题

希望开发新 AVS 的创新者必须引导新的信任网络才能获得安全性。

价值泄漏

由于每个 AVS 都开发了自己的信任池,用户除了向以太坊支付交易费外,还必须向这些池支付费用。费用流的转移导致以太坊的价值泄漏。

资本成本负担

质押以保护新 AVS 的验证者必须承担资本成本,这相当于在新的系统中质押的机会成本和价格风险。因此,AVS 必须提供足够高的质押回报才能覆盖这笔成本。对于当今运行的大多数 AVS 来说,质押的资本成本远远超过任何运营成本。例如,考虑一个有 100 亿美元质押的数据可用性层,并假设质押者预期的年百分比回报率 (APR) 为 5%。为了补偿资本成本,这个 AVS 每年需要向质押者偿还至少 5 亿美元。这远远高于与数据存储或网络成本相关的运营成本。

DApps 的信任模型较低

DApps 的信任模型较低。当前的 AVS 生态系统产生了一种非常不受欢迎的安全动态:一般来说,DApp 的任何一个中间件依赖项都可能成为攻击的目标。因此,DApp 的损坏成本(参见第 3.1 节的描述)通常必须被认为不超过破坏其至少一个依赖项的最低成本。参见图 3a 的说明。在一个应用程序依赖于关键模块(例如 Oracle)并用少量股份保护它的世界中,以太坊提供的强大经济安全保障可能意义不大,因为攻击 Oracle 的成本远低于攻击以太坊的成本。

EidgenLayer的解决方案

新 AVS 的引导问题

image.png 如果没有构造安全池子,攻击者只需要攻击最小的那个点,即有10亿美金就可以达到目的。但是如果构造了安全池子,则攻击者必须要对整个池子的安全资金进行攻击,难度极大的增加。

价值泄漏

EigenLayer 为 ETH 质押者提供了几个他们可以参与的额外收入来源,并且由于高度安全的 AVS 生态系统的存在

资本成本负担

由于 ETH 质押者在多个服务中重复使用其资本,因此其资本成本得到了摊销。

DApps 的信任模型较低

因为重新质押的资金池更大,信任模型会更好

EigenLayer 的委托模式

单独质押

在此模型中,原生重新质押的单独质押者有两种参与 EigenLayer 的选择:(1)单独质押者可以选择加入 EigenLayer 上的 AVS,并直接为其提供验证服务;或(2)单独质押者可以将 EigenLayer 操作委托给其他实体,同时继续自己为以太坊进行验证。后一种选择允许具有不可升级/轻量级设置的家庭质押者继续为以太坊做出去中心化和抗审查贡献(并且不与任何运营商分享核心以太坊收益),同时通过委托给另一个运营商通过 EigenLayer 获得额外奖励。我们设想将有许多基于 EigenLayer 构建的服务被设计为轻量级,适合不想将其质押委托给其他运营商的家庭质押者。例如,考虑一个去中心化的价格预言机,它在计算上易于运行,但需要高度信任。

委托模式

EigenLayer 中的委托模型要求将他们的权益委托给运营商。如果他们的运营商没有履行其参与的 EigenLayer 模块中的义务,那么他们存入的权益将受到削减。将他们的权益委托给该运营商的重新持有者也将被削减。因此,EigenLayer 重新持有者应该只委托给有成功履行义务记录的可信运营商。EigenLayer 中没有内置委托激励措施,但其他人可以构建创新的委托框架在 EigenLayer 之上。

EigenLayer 支持的质押方式

种类

一 流动性质押 验证者可以通过质押他们的 LST,来获取额外的收益 二 重新质押 通过将其提款凭证指向 EigenLayer 合约来原生重新质押其质押的 ETH。这相当于 L1 → EigenLayer 收益堆叠 (当然,EigenLayer还有超流体质押。ETH LP质押这里先不展开赘述)

image.png

LSD 大致流程

image.png

质押 LST 核心步骤

第一步 用户操作质押 第二步 调用StrategyManager操作质押 第三步 系统会根据你的策略和代币,选择不同的StrategyBaseTVLLimits 第四步 StrategyBaseTVLLimits计算出可以获得的份额 第五步 将结算结果存入到StrategyManagerStorage中

结合着源码来看

function depositIntoStrategy(
    IStrategy strategy,
    IERC20 token,
    uint256 amount
) external onlyWhenNotPaused(PAUSED_DEPOSITS) nonReentrant returns (uint256 shares) {
    // 执行内部方法
    shares = _depositIntoStrategy(msg.sender, strategy, token, amount);
}
function _depositIntoStrategy(
    address staker,
    IStrategy strategy,
    IERC20 token,
    uint256 amount
) internal onlyStrategiesWhitelistedForDeposit(strategy) returns (uint256 shares) {
    // 转移代币到指定的策略地址
    token.safeTransferFrom(msg.sender, address(strategy), amount);

    // 执行deposit操作,计算份额(下面会介绍)
    shares = strategy.deposit(token, amount);

    // 并把增加的份额加入到质押者的名下
    _addShares(staker, strategy, shares);

    // 向代理增加份额
    delegation.increaseDelegatedShares(staker, strategy, shares);

    emit Deposit(staker, token, strategy, shares);
    return shares;
}
function deposit(
    IERC20 token,
    uint256 amount
) external virtual override onlyWhenNotPaused(PAUSED_DEPOSITS) onlyStrategyManager returns (uint256 newShares) {
    // 在执行前先调用验证流程
    _beforeDeposit(token, amount);

    require(token == underlyingToken, "StrategyBase.deposit: Can only deposit underlyingToken");

    uint256 priorTotalShares = totalShares;

    // 计算需要新增加的份额
    uint256 virtualShareAmount = priorTotalShares + SHARES_OFFSET;
    uint256 virtualTokenBalance = _tokenBalance() + BALANCE_OFFSET;
    uint256 virtualPriorTokenBalance = virtualTokenBalance - amount;
    newShares = (amount * virtualShareAmount) / virtualPriorTokenBalance;

    // 校验新增份额不能为0,避免异常
    require(newShares != 0, "StrategyBase.deposit: newShares cannot be zero");

    // 更新total 份额
    totalShares = (priorTotalShares + newShares);
    return newShares;
}

解质押逻辑

第一步 用户操作解质押 第二步 调用DelegationManager执行unDelegate操作 第三步 调用StrategyManager执行removeShares(中间涉及到份额移除和资产的提取) 第四步 DelegationManagerStorage

function undelegate(address staker) external onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE) returns (bytes32) {
    require(isDelegated(staker), "DelegationManager.undelegate: staker must be delegated to undelegate");
    address operator = delegatedTo[staker];
    require(!isOperator(staker), "DelegationManager.undelegate: operators cannot be undelegated");
    require(staker != address(0), "DelegationManager.undelegate: cannot undelegate zero address");
    require(
        msg.sender == staker ||
            msg.sender == operator ||
            msg.sender == _operatorDetails[operator].delegationApprover,
        "DelegationManager.undelegate: caller cannot undelegate staker"
    );

    // 根据当前质押者,获取指定的策略和份额
    (IStrategy[] memory strategies, uint256[] memory shares)
        = getDelegatableShares(staker);

    // 更新 StakeRegistry 的操作者
    _pushOperatorStakeUpdate(operator);

    // 如果操作人员是运营商,需要发送事件
    if (msg.sender != staker) {
        emit StakerForceUndelegated(staker, operator);
    }

    // 对staker 进行解代理
    emit StakerUndelegated(staker, operator);
    delegatedTo[staker] = address(0);

    // 执行移除份额的操作
    return _removeSharesAndQueueWithdrawal({
        staker: staker,
        operator: operator,
        withdrawer: staker,
        strategies: strategies,
        shares: shares
    });
}
function _removeSharesAndQueueWithdrawal(
    address staker, 
    address operator,
    address withdrawer,
    IStrategy[] memory strategies, 
    uint256[] memory shares
) internal returns (bytes32) {
    require(staker != address(0), "DelegationManager._removeSharesAndQueueWithdrawal: staker cannot be zero address");

    // 依次遍历针对不同策略下,不同份额的逻辑
    for (uint256 i = 0; i < strategies.length;) {
        // Similar to `isDelegated` logic
        if (operator != address(0)) {
            _decreaseOperatorShares({
                operator: operator,
                staker: staker,
                strategy: strategies[i],
                shares: shares[i]
            });

            // 更新Operator
            _pushOperatorStakeUpdate(operator);
        }

        // 判断走原生ETH的份额移除 / LST的份额移除
        if (strategies[i] == beaconChainETHStrategy) {
            /**
             * 如果导致质押者的虚拟以太坊链ETH股份降低到0一下,则该笔交易会被回退
             * 以防止质押者不当排队撤回过多股份给他们所委托的操作员
             */
            eigenPodManager.removeShares(staker, shares[i]);
        } else {
            // 走LST的份额移除
            strategyManager.removeShares(staker, strategies[i], shares[i]);
        }

        unchecked { ++i; }
    }

    // nonce值加1
    uint256 nonce = cumulativeWithdrawalsQueued[staker];
    cumulativeWithdrawalsQueued[staker]++;

    Withdrawal memory withdrawal = Withdrawal({
        staker: staker,
        delegatedTo: operator,
        withdrawer: withdrawer,
        nonce: nonce,
        startBlock: uint32(block.number),
        strategies: strategies,
        shares: shares
    });

    bytes32 withdrawalRoot = calculateWithdrawalRoot(withdrawal);

    // 存储Withdrawals记录到storage
    pendingWithdrawals[withdrawalRoot] = true;

    emit WithdrawalQueued(withdrawalRoot, withdrawal);
    return withdrawalRoot;
}
function _removeShares(
    address staker,
    IStrategy strategy,
    uint256 shareAmount
) internal returns (bool) {

    require(shareAmount != 0, "StrategyManager._removeShares: shareAmount should not be zero!");

    //确保提取的份额要小于等于质押的份额
    uint256 userShares = stakerStrategyShares[staker][strategy];
    require(shareAmount <= userShares, "StrategyManager._removeShares: shareAmount too high");

    unchecked {
        userShares = userShares - shareAmount;
    }

    // 更新质押者的份额数量
    stakerStrategyShares[staker][strategy] = userShares;

    // 如果份额数量为0,则将对应的策略从_removeStrategyFromStakerStrategyList之中移除掉
    if (userShares == 0) {
        _removeStrategyFromStakerStrategyList(staker, strategy);

        // 移除掉了就返回true
        return true;
    }
    // 还有剩余份额,就返回false
    return false;
}

至此LST 质押的逻辑到此结束。接下来看看restaking的逻辑

Native-Restaking 大致流程

image.png

原生Restaking核心步骤

第一步 创建一个EigenPod,并与之绑定 第二步 质押32个ETH,让自己成为验证者 第三步 等待验证器在链上处于活动状态 第四步 将提款地址配置指向EigenPod地址(确认https ://beaconcha.in/validator/[validator_index]#deposits) 第五步 触发操作restaking 第六步 委托给指定的运营商

代码逻辑如下

function createPod() external {
    require(!hasPod(msg.sender), "EigenPodManager.createPod: Sender already has a pod");
    // 创建一个新的pod实例
    _deployPod();
}
function _deployPod() internal onlyWhenNotPaused(PAUSED_NEW_EIGENPODS) returns (IEigenPod) {
    // check that the limit of EigenPods has not been hit, and increment the EigenPod count
    require(numPods + 1 <= maxPods, "EigenPodManager._deployPod: pod limit reached");
    ++numPods;
    // create the pod
    IEigenPod pod = IEigenPod(
        Create2.deploy(
            0,
            bytes32(uint256(uint160(msg.sender))),
            // set the beacon address to the eigenPodBeacon and initialize it
            abi.encodePacked(beaconProxyBytecode, abi.encode(eigenPodBeacon, ""))
        )
    );
    pod.initialize(msg.sender);
    // 与创建完成的pod完成绑定
    ownerToPod[msg.sender] = pod;
    emit PodDeployed(address(pod), msg.sender);
    return pod;
}
function stake(bytes calldata pubkey, bytes calldata signature, bytes32 depositDataRoot) external payable {
    IEigenPod pod = ownerToPod[msg.sender];
    if (address(pod) == address(0)) {
        //  如果还没有创建pod,则会创建一个pod
        pod = _deployPod();
    }
    // 质押32个ETH,使验证者成为活跃状态
    pod.stake{value: msg.value}(pubkey, signature, depositDataRoot);
}

激活重新质押操作

function activateRestaking()
    external
    onlyWhenNotPaused(PAUSED_EIGENPODS_VERIFY_CREDENTIALS)
    onlyEigenPodOwner
    hasNeverRestaked
{
    hasRestaked = true;
    // 从 pod 中提取所有的资产
    _processWithdrawalBeforeRestaking(podOwner);

    emit RestakingActivated(podOwner);
}

将资产委托给运营商

function delegateTo(
    address operator,
    SignatureWithExpiry memory approverSignatureAndExpiry,
    bytes32 approverSalt
) external {
    // 进行委托
    _delegate(msg.sender, operator, approverSignatureAndExpiry, approverSalt);
}

另外,用户启动检查点的频率不应高于每两周一次(大约)。执行检查点之前等待的时间越长,用户节省的 gas 就越多。无论将证明多少共识奖励,检查点的 gas 成本都是相同的。每个用户都应确定最适合其 gas 成本和重新质押收益需求的间隔。 根据以太坊协议,共识奖励大约每 8 天从信标链转移到您的 EigenPod 一次。检查点间隔超过 8 天不会给用户带来任何好处。

时序图说明

image.png

提取原生的质押奖励

第一步 触发 “队列提款” 第二步 选择需要提取的金额并继续 第三步 检查点证明已启动,使用web3钱包签署交易 第四步 等待托管期结束(为了防止有验证者作恶,然后直接退出) 第五步 完成提款

源码

function withdrawNonBeaconChainETHBalanceWei(
    address recipient,
    uint256 amountToWithdraw
) external onlyEigenPodOwner {
    require(
        // 确保提款金额要 小于等于 除信标链外的余额
        amountToWithdraw <= nonBeaconChainETHBalanceWei,
        "EigenPod.withdrawnonBeaconChainETHBalanceWei: amountToWithdraw is greater than nonBeaconChainETHBalanceWei"
    );
    nonBeaconChainETHBalanceWei -= amountToWithdraw;
    emit NonBeaconChainETHWithdrawn(recipient, amountToWithdraw);
    _sendETH_AsDelayedWithdrawal(recipient, amountToWithdraw);
}
function _sendETH_AsDelayedWithdrawal(address recipient, uint256 amountWei) internal {
    // 正式进入到提款队列中
    delayedWithdrawalRouter.createDelayedWithdrawal{value: amountWei}(podOwner, recipient);
}
function createDelayedWithdrawal(
    address podOwner,
    address recipient
) external payable onlyEigenPod(podOwner) onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) {
    require(
        recipient != address(0),
        "DelayedWithdrawalRouter.createDelayedWithdrawal: recipient cannot be zero address"
    );
    uint224 withdrawalAmount = uint224(msg.value);
    // 将提款信息组合成结构体,保存到mapping中
    if (withdrawalAmount != 0) {
        DelayedWithdrawal memory delayedWithdrawal = DelayedWithdrawal({
            amount: withdrawalAmount,
            blockCreated: uint32(block.number)
        });
        _userWithdrawals[recipient].delayedWithdrawals.push(delayedWithdrawal);
        emit DelayedWithdrawalCreated(
            podOwner,
            recipient,
            withdrawalAmount,
            _userWithdrawals[recipient].delayedWithdrawals.length - 1
        );
    }
}

等待提款期结束之后,完成提款操作

function claimDelayedWithdrawals(
    address recipient,
    uint256 maxNumberOfDelayedWithdrawalsToClaim
) external nonReentrant onlyWhenNotPaused(PAUSED_DELAYED_WITHDRAWAL_CLAIMS) {
    _claimDelayedWithdrawals(recipient, maxNumberOfDelayedWithdrawalsToClaim);
}
function _claimDelayedWithdrawals(address recipient, uint256 maxNumberOfDelayedWithdrawalsToClaim) internal {
    uint256 amountToSend = 0;
    uint256 delayedWithdrawalsCompletedBefore = _userWithdrawals[recipient].delayedWithdrawalsCompleted;
    uint256 _userWithdrawalsLength = _userWithdrawals[recipient].delayedWithdrawals.length;
    uint256 i = 0;
    while (
        i < maxNumberOfDelayedWithdrawalsToClaim && (delayedWithdrawalsCompletedBefore + i) < _userWithdrawalsLength
    ) {
        // 声明内存对象
        DelayedWithdrawal memory delayedWithdrawal = _userWithdrawals[recipient].delayedWithdrawals[
            delayedWithdrawalsCompletedBefore + i
        ];
        // 校验是否已经解禁结束
        if (block.number < delayedWithdrawal.blockCreated + withdrawalDelayBlocks) {
            break;
        }
        // 累加需要取款的金额
        amountToSend += delayedWithdrawal.amount;
        // increment i to account for the delayedWithdrawal being claimed
        unchecked {
            ++i;
        }
    }
    // 记录当前的提款对象,标记为已完成
    _userWithdrawals[recipient].delayedWithdrawalsCompleted = delayedWithdrawalsCompletedBefore + i;
    // 执行ETH发送
    if (amountToSend != 0) {
        AddressUpgradeable.sendValue(payable(recipient), amountToSend);
    }
    emit DelayedWithdrawalsClaimed(recipient, amountToSend, delayedWithdrawalsCompletedBefore + i);
}

时序图说明

image.png

代码设计思想

我们可以看到,Eidgen Layer 使用的设计模式是,逻辑层和数据层进行分离。这样非常 非常有利于进行后期的升级。我个人看了较多的源码,发现Eidgen Layer的源码设计的非常巧妙, 大家有时间,真的可以好好的读一下。顺便也好好了解一下,在restaking赛道上的领头羊到底是怎么玩的。

总结

  1. EigenLayer 创建了一个去中心化信任的自由市场,由以太坊权益持有者提供给需要权益和验证服务的模块。
  2. 通过 EigenLayer 重新质押,以太坊质押者可以选择为他们选择的模块提供安全和验证服务,要么直接通过操作节点,要么通过委托给其他 EigenLayer 运营商。
  3. 可以在 EigenLayer 上构建各种轻量级和超大规模模块,以便让单个权益持有者广泛参与。
  4. 模块还可以利用利益相关者之间的显著异质性,这些利益相关者可能在计算能力、风险/回报偏好和身份等方面有所不同。
  5. EigenLayer 致力于在区块链上实现更灵活、更分散、无需许可的创新。

参考文献

https://web3caff.com/zh/archives/85864 https://docs.eigenlayer.xyz/eigenlayer/restaking-guides/restaking-user-guide/native-restaking/ https://docs.eigenlayer.xyz/html/EIGEN_Token_Whitepaper-converted-xodo.html https://github.com/Layr-Labs/eigenlayer-contracts/blob/master/docs/experimental/AVS-Guide.md

点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
Leo
Leo
江湖只有他的大名,没有他的介绍。