EigenLayer DelegationManager 介绍

  • Layr-Labs
  • 发布于 2025-05-30 20:56
  • 阅读 58

本文档详细介绍了 EigenLayer 协议中 DelegationManager 合约的功能、参数、以及如何与 StrategyManager、EigenPodManager、AllocationManager 等其他合约进行交互。

文件 备注
DelegationManager.sol
DelegationManagerStorage.sol 状态变量
IDelegationManager.sol 接口

库和混合合约:

文件 备注
PermissionControllerMixin.sol 账户委托
SignatureUtils.sol 签名验证
Pausable.sol
SlashingLib.sol 罚没计算
Snapshots.sol 历史状态

前置阅读

概述

DelegationManager 是协议双方的交汇点。它 (i) 允许 staker 委托/取消委托给 operator,(ii) 处理 StrategyManagerEigenPodManager 中资产的提款和提款处理,以及 (iii) 管理 staker 和 operator 罚没相关的计算。

当 operator 被 AVS 罚没时,它会收到来自 AllocationManager 的份额罚没指令。当 staker 使用 StrategyManager/EigenPodManager 存入资产时,它会跟踪份额/委托计算的更改。DelegationManager 将来自协议双方的输入合并到 staker 的“存款缩放因子”中,该因子充当 staker 的原始存款资产和他们可以提取的金额之间的主要转换媒介。

DelegationManager 的职责可以分解为以下概念:

参数化

  • MIN_WITHDRAWAL_DELAY_BLOCKS:提款可以完成之前的区块延迟。
    • 主网:100800 blocks(14 天)。
    • 测试网:50 blocks(10 分钟)。
  • beaconChainETHStrategy:用于在内部表示信标链 ETH 的伪策略。这不是一个真正的合约!
    • 值:0xbeaC0eeEeeeeEEeEeEEEEeeEEeEeeeEeeEEBEaC0

成为 Operator

DelegationManager 在以下映射中跟踪与 operator 相关的状态:

/// @notice 返回 `staker` 委托到的 `operator`,如果未委托,则返回 address(0)。
/// 注意:operator 委托给自己
mapping(address staker => address operator) public delegatedTo;

/// @notice 返回给定 `operator` 的 operator 详细信息。
/// 注意:`OperatorDetails` 字段中的两个已弃用。唯一的关联字段
/// 是 `OperatorDetails.delegationApprover`。
mapping(address operator => OperatorDetails) internal _operatorDetails;

/**
 * @notice 跟踪根据每个 `strategy` 委托给 `operator` 的份额的当前余额。
 * 当 staker 的可委托余额更改时,由 `StrategyManager` 和 `EigenPodManager` 更新,
 * 并且当 `operator` 被罚没时,由 `AllocationManager` 更新。
 *
 * @dev 以下不变式应该适用于每个 `strategy`:
 *
 * operatorShares[operator] = sum(委托给 operator 的所有 staker 的可提取份额)
 */
mapping(address operator => mapping(IStrategy strategy => uint256 shares)) public operatorShares;

方法

registerAsOperator

/**
 * @notice 将调用者注册为 EigenLayer 中的 operator。
 * @param initDelegationApprover 是一个地址,如果设置,则当 staker 委托给
 * operator 时,必须提供签名。
 * @param allocationDelay 分配生效前的延迟。
 * @param metadataURI 是 operator 元数据的 URI,即提供有关 operator
 * 更多详细信息的链接。
 *
 * @dev 一旦 operator 注册,他们就不能“注销”为 operator,并且他们将永远被视为“委托给自己”。
 * @dev 如果调用者已经委托给 operator,则此函数将恢复。
 * @dev 请注意,`metadataURI` 从未存储,仅在 `OperatorMetadataURIUpdated` 事件中发出
 */
function registerAsOperator(
    address initDelegationApprover,
    uint32 allocationDelay,
    string calldata metadataURI
) external nonReentrant;

将调用者注册为 EigenLayer 中的 Operator。新 operator 提供以下输入参数:

  • address initDelegationApprover(可选)如果设置为非零地址,则此地址必须签名并批准从 staker 到此 operator 的新委托(请参阅 delegateTo
  • uint32 allocationDelay:slashable stake 分配生效之前的延迟(以区块为单位)。这被传递给 AllocationManager(请参阅 AllocationManager.md#setAllocationDelay
  • string calldata metadataURI:在事件 OperatorMetadataURIUpdated 中发出此输入。不在任何地方存储该值。

registerAsOperator 将 operator 的委托批准者和分配延迟固化在存储中,并将 operator 自我委托给自己 - 永久地将调用者标记为 operator。他们不能“注销”为 operator - 但是,如果他们已存入资金,他们仍然可以提取它们(请参阅 委托和提款)。

影响

  • 设置 _operatorDetails[operator].delegationApprover。请注意,其他 OperatorDetails 字段已弃用;仅使用 delegationApprover
  • 将 operator 委托给自己
    • 统计 EigenPodManagerStrategyManager 中所有已存入的份额,并将这些份额委托给自己
    • 对于 operator 持有资产的每种策略,更新该策略的 operator 的 depositScalingFactor

要求

  • 调用者必须尚未被委托
  • 暂停状态必须未设置:PAUSED_NEW_DELEGATION
  • 对于 operator 持有资产的每种策略,该策略的 slashingFactor 必须非零。

modifyOperatorDetails

/**
 * @notice 更新 operator 存储的 `delegationApprover`。
 * @param operator 是要更新 delegationApprover 的 operator
 * @param newDelegationApprover 是 operator 的新 delegationApprover
 *
 * @dev 调用者必须先前已注册为 EigenLayer 中的 operator。
 */
function modifyOperatorDetails(
    address operator, 
    address newDelegationApprover
) 
    external 
    checkCanCall(operator)
    nonReentrant

注意:此方法可以直接由 operator 调用,也可以由 operator 授权的调用者调用。有关详细信息,请参阅 PermissionController.md

允许 operator 更新其存储的 delegationApprover

要求

  • address operator 必须已经是一个 operator。
  • 调用者必须获得授权:operator 本身或管理员/受托人(请参阅 PermissionController.md

updateOperatorMetadataURI

/**
 * @notice 由 operator 调用以发出一个 `OperatorMetadataURIUpdated` 事件,指示信息已更新。
 * @param operator 要更新元数据的 operator
 * @param metadataURI 与 operator 关联的元数据的 URI
 * @dev 请注意,`metadataURI` 从未存储,仅在 `OperatorMetadataURIUpdated` 事件中发出
 */
function updateOperatorMetadataURI(
    address operator, 
    string calldata metadataURI
) 
    external 
    checkCanCall(operator)

注意:此方法可以直接由 operator 调用,也可以由 operator 授权的调用者调用。有关详细信息,请参阅 PermissionController.md

允许 operator 发出一个 OperatorMetadataURIUpdated 事件。不发生其他状态更改。

要求

  • address operator 必须已经是一个 operator。
  • 调用者必须获得授权:operator 本身或管理员/受托人(请参阅 PermissionController.md

委托和提款

概念

方法

传统和罚没后提款

DelegationManager 在以下映射中跟踪与提款相关的状态:

/**
 * @dev 表示现有排队提款的结构。在提款延迟时间过去后,可以通过 `completeQueuedWithdrawal` 完成此提款。
 * 当调用 `queueWithdrawals` 时,`DelegationManager` 会创建一个 `Withdrawal`。`queueWithdrawals` 返回的 `withdrawalRoots` 哈希值可用于
 * 从存储中获取相应的 `Withdrawal`(通过 `queuedWithdrawals`)。
 *
 * @param staker 排队提款的地址
 * @param delegatedTo 在排队提款时,staker 委托到的地址。用于确定在此提款变为可完成之前是否发生了其他罚没。
 * @param withdrawer 将调用合约以完成提款的地址。请注意,这始终等于 `staker`;目前不支持备用提款人。
 * @param nonce 排队时 staker 的 `cumulativeWithdrawalsQueued`。用于确保提款具有唯一的哈希值。
 * @param startBlock 排队提款时的区块号。
 * @param strategies 在排队提款时请求提款的策略
 * @param scaledShares staker 的存款份额,请求提款,按 staker 的 `depositScalingFactor` 缩放。完成后,这些将按
 * 提款可完成区块时的适当罚没因子缩放。结果是实际可提款的金额。
 */
struct Withdrawal {
    address staker;
    address delegatedTo;
    address withdrawer;
    uint256 nonce;
    uint32 startBlock;
    IStrategy[] strategies;
    uint256[] scaledShares;
}

/// @dev 返回给定 `withdrawalRoot` 的提款是否挂起。
/// @dev 此变量将在将来弃用,应仅读取或删除值。
mapping(bytes32 withdrawalRoot => bool pending) public pendingWithdrawals;

/// @notice 返回已为给定 `staker` 排队提款的总数。
/// @dev 这仅增加(不减少),并且用于帮助确保其他相同的提款具有唯一的哈希值。
mapping(address staker => uint256 totalQueued) public cumulativeWithdrawalsQueued;

/// @notice 返回给定 `staker` 的排队提款列表。
/// @dev 提款完成后,将删除条目。
/// @dev 此变量仅反映在罚没发布后进行的提款。
mapping(address staker => EnumerableSet.Bytes32Set withdrawalRoots) internal _stakerQueuedWithdrawalRoots;

/// @notice 返回给定 `withdrawalRoot` 的排队提款的详细信息。
/// @dev 此变量仅反映在罚没发布后进行的提款。
mapping(bytes32 withdrawalRoot => Withdrawal withdrawal) public queuedWithdrawals;

/// @notice 包含 operator 和给定策略的总累积 staker 提款的历史记录。
/// 用于在 operator 被罚没时计算已销毁/重新分配的 StrategyManager 份额。
/// @dev 存储 scaledShares 而不是总提款份额,以跟踪当前的 slashable shares,具体取决于 maxMagnitude
mapping(address operator => mapping(IStrategy strategy => Snapshots.DefaultZeroHistory)) internal
    _cumulativeScaledSharesHistory;

在发布罚没之前,提款仅作为哈希值存储在 pendingWithdrawals 映射中。

随着罚没的发布,提款现在完全存储在状态中,并且已添加了两个新的映射来支持此功能:

  • _stakedQueuedWithdrawalRoots:属于 staker 的所有当前排队的提款哈希值的列表
  • queuedWithdrawals:将排队的提款哈希值映射到 Withdrawal 结构

传统提款仍然可以使用与新提款相同的方法完成。两者之间的主要区别在于,无法为传统提款哈希查询相应的 Withdrawal 结构。在确定要提供给合约以完成传统提款的 Withdrawal 结构时,调用者将需要推导出排队提款时生成的原始 Withdrawal 结构。

罚没因子和缩放份额

有关更全面的解释和示例,请参阅 份额计算 文档。

在整个 DelegationManager 中,staker 的存款份额可以通过应用两个因子转换为他们当前的可提取份额罚没因子存款缩放因子。这两个值是缩放份额时作为分子的缩放因子。默认情况下,这些值从 1 WAD (1e18) 开始。1 WAD 也充当缩放时的分母。

/// @dev 所有缩放因子都具有 `1e18` 作为初始/默认值。此值由常量 `WAD` 表示,该常量用于在使用 uint256 数学运算时保持精度。
///
/// 在应用缩放因子时,它们通常乘以/除以 `WAD`,从而允许此常量在数学公式中充当“1”。
uint64 constant WAD = 1e18;

存款缩放因子DelegationManager 存储中表示,并且可以被认为是使用当前罚没因子对新存入的份额进行归一化的一种方式,以便如果罚没因子已更改,则可以适当地缩放未来提款:

/*
 * 有 2 种份额:
 *      1. 存款份额
 *          - 给定策略,这些可以转换为一定数量的 token
 *              - 通过在策略地址上调用 `sharesToUnderlying`(在 EigenPod 的情况下,它们已经是 token)
 *          - 这些存在于 EigenPodManager 和各个 StrategyManager 策略的存储中
 *      2. 可提取份额
 *          - 对于 staker,这是他们可以提取的份额数量
 *          - 对于 operator,委托给他们的份额等于其 staker 的
 *            可提取份额的总和
 *
 * 除了罚没因子之外,DepositScalingFactor 用于在两种份额类型之间进行转换。
 */
struct DepositScalingFactor {
    uint256 _scalingFactor;
}

/// @notice 返回应用于给定 `strategy` 的 `staker` 的缩放因子
mapping(address staker => mapping(IStrategy strategy => DepositScalingFactor)) internal _depositScalingFactor;

罚没因子 的计算因所讨论的策略而异。对于所有策略,罚没因子是 staker 委托的 operatorAllocationManager 中的最大幅度(请参阅 最大值与受限幅度)。如果 staker 未被委托,则该值为 WAD(又名“1”)。

对于 beaconChainETHStrategy,罚没因子还包括 staker 的 beaconChainSlashingFactor,它的作用类似于 operator 的 最大幅度,只是针对 staker 的信标链资产。这意味着,对于专门的 beaconChainETHStrategy,由于以下任何一个/两个原因,罚没因子可以被应用**:

  • operator 因 AVS 的此策略而被罚没
  • staker 在信标链上被罚没

来自 DelegationManager.sol

/// @dev 计算应用于 staker 份额的罚没金额
function _getSlashingFactor(
    address staker,
    IStrategy strategy,
    uint64 operatorMaxMagnitude
) internal view returns (uint256) {
    if (strategy == beaconChainETHStrategy) {
        uint64 beaconChainSlashingFactor = eigenPodManager.beaconChainSlashingFactor(staker);
        return operatorMaxMagnitude.mulWad(beaconChainSlashingFactor);
    }

    return operatorMaxMagnitude;
}

delegateTo

// @notice 结构将签名和签名的到期时间捆绑在一起。主要用于堆栈管理。
struct SignatureWithExpiry {
    // 签名本身,格式化为单个字节对象
    bytes signature;
    // 签名的过期时间戳 (UTC)
    uint256 expiry;
}

/**
 * @notice 调用者将其 stake 委托给一个 operator。
 * @param operator 该账户 (`msg.sender`) 将其资产委托给该账户,以用于服务于基于 EigenLayer 构建的应用程序。
 * @param approverSignatureAndExpiry (可选) 验证 operator 是否批准此委托
 * @param approverSalt (可选) 与单个签名相关联的唯一一次性使用值。
 * @dev 仅当 operator 配置了 delegationApprover 时,才使用签名/salt。
 * 如果没有,这些参数可以留空。
 */
function delegateTo(
    address operator, 
    SignatureWithExpiry memory approverSignatureAndExpiry, 
    bytes32 approverSalt
) 
    external
    nonReentrant

允许 staker 将其资产委托给 operator。委托是全有或全无的:当 staker 委托给 operator 时,他们会委托所有资产。Staker 一次只能委托给一个 operator。

注意:委托给幅度非常低(wei 量级)的 operator 可能会导致资金损失。

对于 staker 拥有存款份额的每种策略,DelegationManager 将:

  • StrategyManager/EigenPodManager 查询 staker 的存款份额
  • 获取此 (staker, operator, strategy) 的罚没因子,并使用它来更新 staker 的存款缩放因子(请参阅 罚没因子和缩放份额
  • 将存款份额直接添加到 operator 的 operatorShares 中。请注意,初始委托给 operator 是一种特殊情况,其中存款份额 == 可提取份额。

影响

  • 将调用者委托给 operator
    • 统计 EigenPodManagerStrategyManager 中所有已存入的份额,并将这些份额委托给 operator
    • 对于调用者持有资产的每种策略,更新该策略的调用者的 depositScalingFactor
  • 委托后,如果 operator 已将 staker 策略的 slashable stake 分配给 operatorset,则 staker 的所有资金将立即被罚没

要求

  • 调用者必须尚未委托给 operator
  • operator 必须已经是一个 operator
  • 如果 operator 具有 delegationApprover,则调用者必须提供有效的 approverSignatureAndExpiryapproverSalt
  • 暂停状态必须未设置:PAUSED_NEW_DELEGATION
  • 对于 staker 持有资产的每种策略,该策略的 slashingFactor 必须非零。

undelegate

/**
 * @notice 从其 operator 取消委托 staker,并为其所有份额排队提款
 * @param staker 要取消委托的账户
 * @return withdrawalRoots 新排队提款的根,如果排队了提款。如果没有排队,则返回
 * 一个空数组。
 *
 * @dev 如果 `staker` 也是 operator,则恢复,因为不允许 operator 从自己取消委托。
 * @dev 如果调用者不是 staker,也不是 staker 委托到的 operator,也不是 operator 指定的 delegationApprover,则恢复
 * @dev 如果 `staker` 未委托给 operator,则恢复
 */
function undelegate(
    address staker
) 
    external
    nonReentrant
    returns (bytes32[] memory withdrawalRoots);

注意:此方法可以直接由 operator 调用,也可以由 operator 授权的调用者调用。有关详细信息,请参阅 PermissionController.md

undelegate 可以由 staker 调用以取消委托自己,也可以由 operator 调用以强制取消委托 staker。如果 operator 具有 delegationApprover,强制取消委托主要很有用,因为此角色是防止 staker 在强制取消委托后委托回 operator 的唯一方法。

取消委托立即将 staker 委托的 operator 设置为 0,减少先前 operator 委托的份额,并为所有 staker 存入的资产排队提款。出于 UX 原因,为 staker 存入资产的每种策略排队一个提款。排队的提款模仿 queueWithdrawals 方法的行为;有关详细信息,请参阅该方法的文档。

与正常的排队提款一样,这些提款可以在 MIN_WITHDRAWAL_DELAY_BLOCKS 之后由 staker 完成。这些提款不需要 staker“完全退出”系统 - staker 可以在完成提款后选择将其资产保留在系统中(有关详细信息,请参阅 completeQueuedWithdrawal)。

影响

  • staker 从其 operator 取消委托
  • 如果 staker 没有存款份额,则没有排队的提款或进一步的影响
  • 对于 staker 持有的每种策略,都会排队一个 Withdrawal
    • 存款份额 从 staker 的存款份额余额中移除
    • 存款份额 转换为 可提取份额(请参阅 罚没因子和缩放份额)。这些值从 operator 委托的份额中递减。
    • 存款份额 转换为 缩放份额(请参阅 份额计算 - 排队提款),这些份额存储在 Withdrawal 结构中
    • 缩放份额 被推送到 _cumulativeScaledSharesHistory,用于燃烧或重新分配罚没份额
    • Withdrawal 保存到存储中
      • Withdrawal 的哈希值标记为“挂起”
      • Withdrawal 的哈希值在映射中设置为 Withdrawal 结构本身
      • Withdrawal 的哈希值被推送到 _stakerQueuedWithdrawalRoots
    • 对于每个 Withdrawal,staker 的提款 nonce 都会增加 1

要求

redelegate

/**
 * @notice 从其当前 operator 取消委托 staker,并重新委托给 `newOperator`
 * 为 staker 的所有可提取份额排队提款。这些份额仅在
 * 完成提款后才委托给 `newOperator`。
 * @dev 此方法的作用类似于调用 `undelegate`,然后调用 `delegateTo`
 * @param newOperator 将委托所有资产的新 operator
 * @dev 注意:仅当 `newOperator` 具有 `delegationApprover` 时,才检查以下 2 个参数。
 * 如果没有,它们可以留空。
 * @param newOperatorApproverSig 来自 operator 的 `delegationApprover` 的签名
 * @param approverSalt 与批准者的签名相关联的唯一一次性使用值
 */
 function redelegate(
    address newOperator,
    SignatureWithExpiry memory newOperatorApproverSig,
    bytes32 approverSalt
) 
    external 
    returns (bytes32[] memory withdrawalRoots);

redelegate 是一种便利方法,它结合了 undelegatedelegateTo 的效果。redelegate 允许 staker 通过一次调用将其委托的 operator 切换到 newOperator注意,但是,在完成此方法取消委托部分期间排队的提款之前,staker 的资产将不会完全委托给 newOperator

影响

要求

queueWithdrawals

/**
 * @param strategies 要从中提款的策略
 * @param depositShares 对于每种策略,要提款的存款份额数量。可以通过 `getDepositedShares` 查询存款份额。
 * 注意:如果在完成提款时 staker 或其委托的 operator 经历了罚没,则最终收到的份额数量可能会低于 depositShares
 * 。
 * @param __deprecated_withdrawer 此字段将被忽略。唯一可以完成提款的一方是最初对其进行排队的 staker。不支持备用提款人。
 */
struct QueuedWithdrawalParams {
    IStrategy[] strategies;
    uint256[] depositShares;
    address __deprecated_withdrawer;
}

/**
 * @notice 允许 staker 对其存款份额进行排队提款。可以通过任一 completeQueuedWithdrawal 方法在 MIN_WITHDRAWAL_DELAY_BLOCKS 之后完成提款。
 *
 * 在队列中时,这些份额将从 staker 的余额以及从其 operator 委托的份额余额(如果适用)中移除。请注意,虽然在队列中,存款份额仍然受制于罚没
 * 。如果发生任何罚没,则收到的份额可能少于排队的存款份额。
 *
 * @dev 要查看可以排队提款的所有 staker 的策略/存款份额,请参阅 `getDepositedShares`
 * @dev 要查看 staker 的存款份额与可提取份额之间的当前转换,请参阅 `getWithdrawableShares`
 */
function queueWithdrawals(
    QueuedWithdrawalParams[] calldata queuedWithdrawalParams
) 
    external 
    onlyWhenNotPaused(PAUSED_ENTER_WITHDRAWAL_QUEUE)
    nonReentrant
    returns (bytes32[] memory)
```允许调用者将其存款份额排队,以便跨任何策略进行提取。提取可以在 `MIN_WITHDRAWAL_DELAY_BLOCKS` 之后完成,通过调用 [`completeQueuedWithdrawal`](#completequeuedwithdrawal)。此方法接受**存款份额**作为输入 - 但是,如果在完成时,staker 经历了 slashing,则收到的数量可能会更低(请参阅[份额会计](https://learnblockchain.cn/article/20134)和[Slashing 因子和 Scaling Shares](#slashing-factors-and-scaling-shares))。

对于作为输入传递的每个 `QueuedWithdrawalParams`,将在存储中创建一个 `Withdrawal`(有关结构和查询的详细信息,请参阅[传统和 Slashing 后的提取](#legacy-and-post-slashing-withdrawals))。排队提取涉及对 staker 的**存款份额**的多次转换,服务于几个不同的目的:
* 原始的**存款份额**将从 staker 在相应份额管理器(`EigenPodManager` 或 `StrategyManager`)中的存款份额余额中移除。
* **Scaled shares**(按比例计算的份额)通过将 staker 的**存款缩放因子**应用于其**存款份额**来计算。Scaled shares:
    * 存储在 `Withdrawal` 本身中,并在提取完成期间使用
    * 添加到 operator 的`cumulativeScaledSharesHistory`中,如果在提取在队列中时发生 slashing,则可以在其中销毁或重新分配
* **Withdrawable shares**(可提取的份额)通过将 staker 的**存款缩放因子**和任何适当的**slashing 因子**应用于 staker 的**存款份额**来计算。这些“当前可提取的份额”将从 operator 的 delegated shares 中移除(如果适用)。

请注意,`QueuedWithdrawalParams.__deprecated_withdrawer` 字段将被忽略。最初,这用于创建可以由第三方完成的提取。由于对此提出的网络钓鱼风险越来越关注,此功能在 M2 版本中被删除。在 slashing 发布之前,此字段会显式检查与 `msg.sender` 的等效性;但是,目前它被忽略。所有 `Withdrawals` 都是使用 `withdrawer == staker` 创建的,而不管此字段的值如何。

*影响*:
* 对于每个 `QueuedWithdrawalParams` 元素:
    * **存款份额**将从 staker 的存款份额余额中移除
        * 请参阅 [`EigenPodManager.removeDepositShares`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/1fc7ae4126918e914f08ae5c686e8a602436c75f/docs/core/EigenPodManager.md#removedepositshares)
        * 请参阅 [`StrategyManager.removeDepositShares`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/1fc7ae4126918e914f08ae5c686e8a602436c75f/docs/core/StrategyManager.md#removedepositshares)
    * **存款份额**将转换为**可提取的份额**(请参阅[Slashing 因子和 Scaling Deposits](#slashing-factors-and-scaling-shares))。这些将从其 operator 的 delegated shares 中递减(如果适用)
    * **存款份额**将转换为 **scaled shares**(按比例计算的份额)(请参阅[份额会计 - 队列提取](https://github.com/Layr-Labs/eigenlayer-contracts/blob/1fc7ae4126918e914f08ae5c686e8a602436c75f/docs/core/accounting/SharesAccounting.md#queue-withdrawal)),这些份额存储在 `Withdrawal` 结构中
    * 如果调用者已委派给 operator,则 **scaled shares**(按比例计算的份额) 将推送到该 operator 的 `_cumulativeScaledSharesHistory`,如果在发生 slashing 时,可以将其销毁或重新分配。
    * `Withdrawal` 保存到存储
        * `Withdrawal` 的哈希被标记为“pending”
        * `Withdrawal` 的哈希被设置到映射到 `Withdrawal` 结构本身的映射中
        * `Withdrawal` 的哈希被推送到 `_stakerQueuedWithdrawalRoots`
    * staker 的提取 nonce 增加 1

*要求*:
* 暂停状态不能设置为:`PAUSED_ENTER_WITHDRAWAL_QUEUE`
* 对于每个 `QueuedWithdrawalParams` 元素:
    * `strategies.length` 必须等于 `depositShares.length`
    * `withdrawer` 必须等于 `msg.sender`
    * `strategies.length` 不能等于 0
    * 请参阅 [`EigenPodManager.removeDepositShares`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/1fc7ae4126918e914f08ae5c686e8a602436c75f/docs/core/EigenPodManager.md#removedepositshares)
    * 请参阅 [`StrategyManager.removeDepositShares`](https://github.com/Layr-Labs/eigenlayer-contracts/blob/1fc7ae4126918e914f08ae5c686e8a602436c75f/docs/core/StrategyManager.md#removedepositshares)

#### `completeQueuedWithdrawal`

```solidity
/**
 * @dev 表示现有排队提取的结构。在提取延迟时间过去后,可以通过 `completeQueuedWithdrawal` 完成此提取。
 * 当调用 `queueWithdrawals` 时,`DelegationManager` 会创建一个 `Withdrawal`。由 `queueWithdrawals` 返回的 `withdrawalRoots` 哈希可用于
 * 从存储中获取相应的 `Withdrawal`(通过 `getQueuedWithdrawal`)。
 *
 * @param staker 排队提取的地址
 * @param delegatedTo 在排队提取时,staker 委派到的地址。用于确定在此提取变得可完成之前是否发生了其他 slashing。
 * @param withdrawer 将调用合约以完成提取的地址。请注意,这将始终等于 `staker`;目前不支持备用提取者。
 * @param nonce 排队时 staker 的 `cumulativeWithdrawalsQueued`。用于确保提取具有唯一的哈希。
 * @param startBlock 排队提取时,区块编号。
 * @param strategies 排队提取时请求提取的策略
 * @param scaledShares staker 请求提取的存款份额,按 staker 的 `depositScalingFactor` 进行缩放。完成后, 这些将按提款的可完成区块的适当 slashing 因子进行缩放。结果是实际可提取的内容。
 */
struct Withdrawal {
    address staker;
    address delegatedTo;
    address withdrawer;
    uint256 nonce;
    uint32 startBlock;
    IStrategy[] strategies;
    uint256[] scaledShares;
}

/**
 * @notice 用于完成排队的提取
 * @param withdrawal 要完成的提取
 * @param tokens i-th 条目指定 `withdrawal.strategies` 数组中第 i-th 个策略的“withdraw”函数的 `token` 输入的数组。
 * @param tokens 对于每个 `withdrawal.strategies`,策略的底层 token
 * 注意:如果 `receiveAsTokens` 为 false,则 `tokens` 数组未使用,可以用默认值填充。但是,`tokens.length` 仍然必须等于 `withdrawal.strategies.length`。
 * 注意:对于 `beaconChainETHStrategy`,相应的 `tokens` 值将被忽略(可以为 0)。
 * @param receiveAsTokens 如果为 true,则提取的份额将转换为 token 并发送给调用者。如果为 false,则调用者收到可以委派给 operator 的份额。
 * 注意:如果调用者收到份额并且当前已委派给 operator,则收到的份额将自动委派给调用者当前的 operator。
 */
function completeQueuedWithdrawal(
    Withdrawal calldata withdrawal,
    IERC20[] calldata tokens,
    bool receiveAsTokens
) 
    external 
    onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE)
    nonReentrant

在排队 MIN_WITHDRAWAL_DELAY_BLOCKS 之后,staker 可以通过调用此方法来完成 Withdrawal。Staker 可以根据 receiveAsTokens 参数的值选择接收 tokens 或份额。

在处理提取之前,此方法将根据提取排队时委派到的 operator(withdrawal.delegatedTo),计算提取完成区块(withdrawal.startBlock + MIN_WITHDRAWAL_DELAY_BLOCKS)处的 slashing 因子。此 slashing 因子用于确定在提取在队列中时是否发生了其他 slashing。如果是这样,现在将应用此 slashing。

对于每个 Withdrawalwithdrawal.scaledShares 将转换为可提取的份额,同时考虑到在提取期间发生的任何 slashing(请参阅份额会计 - 完成提取)。

如果 staker 选择作为 tokens 接收提取,则可提取的份额将通过相应的份额管理器(EigenPodManager / StrategyManager)转换为 tokens,并发送给调用者。

如果 staker 选择作为份额接收提取,则可提取的份额将通过相应的份额管理器(EigenPodManager / StrategyManager)记入 staker 的帐户。此外,如果调用者已委派给 operator,则给定的 (staker, operator, strategy) 的新 slashing 因子将确定向 operator 授予多少份额(以及如何更新 staker 的存款缩放因子)(请参阅Slashing 因子和 Scaling Shares)。在作为份额接收提取时,此金额将作为 staker 的存款份额记入。由于已知的舍入误差,完成提取后可提取的份额数量可能略小于最初可提取的数量。

注意: 如果 staker (i) 作为份额接收提取,(ii) 在 StrategyManager 中具有 MAX_STAKER_STRATEGY_LIST_LENGTH 唯一的存款策略,并且 (iii) 提取到他们当前没有份额的 StrategyManager 策略,这将恢复。Staker 不能以使其 stakerStrategyList 长度超过最大值的方式进行提取;此提取必须改为作为 tokens 完成。

注意: 如果 staker 作为 tokens 接收 beaconChainETHStrategy 提取,则 staker 的 EigenPod 必须具有足够的 withdrawableExecutionLayerGwei 才能兑现提取。

注意: 如果策略不在 StrategyManager 的白名单中,则无论它是作为份额还是 token 完成,提取都将成功。

影响

要求

  • 暂停状态不能设置为:PAUSED_EXIT_WITHDRAWAL_QUEUE
  • tokens.length 必须等于 withdrawal.strategies.length
  • 调用者必须是 Withdrawal 中指定的 staker/withdrawer
  • 在调用 completeQueuedWithdrawal 之前,必须至少经过 MIN_WITHDRAWAL_DELAY_BLOCKS
  • 传入 of 的哈希值Withdrawal 必须对应于待处理的提款
  • 如果 receiveAsTokens
  • 如果 !receiveAsTokens

completeQueuedWithdrawals

/**
 * @notice 用于完成多个排队的提取
 * @param withdrawals 要完成的 Withdrawals 数组。有关单个 Withdrawal 的用法,请参见 `completeQueuedWithdrawal`。
 * @param tokens 每个 Withdrawal 的 tokens 数组。有关单个数组的用法,请参见 `completeQueuedWithdrawal`。
 * @param receiveAsTokens 是否将每个提取作为 tokens 完成。有关单个布尔值的用法,请参见 `completeQueuedWithdrawal`。
 * @dev 有关相关的开发标签,请参见 `completeQueuedWithdrawal`
 */
function completeQueuedWithdrawals(
    Withdrawal[] calldata withdrawals,
    IERC20[][] calldata tokens,
    bool[] calldata receiveAsTokens
) 
    external 
    onlyWhenNotPaused(PAUSED_EXIT_WITHDRAWAL_QUEUE) 
    nonReentrant

此方法是 completeQueuedWithdrawal 的复数版本。


Slashing 和会计

这些方法都由其他系统合约调用:AllocationManager 在 slashing 期间调用 slashOperatorShares,当 staker 的存款份额减少(或发生信标链余额减少时),EigenPodManager/StrategyManager 调用 increaseDelegatedShares/decreaseDelegatedShares

方法

slashOperatorShares

/**
 * @notice 在 slash 之后减少存储中运营商的份额,并通过调用 StrategyManager 或 EigenPodManager(如果该策略是 beaconChainETH)来增加烧毁或可重新分配的份额。
 * @param operator 要减少份额的运营商
 * @param operatorSet 要减少份额的 OperatorSet
 * @param slashID 要减少份额的 slashID
 * @param strategy 要减少份额的策略
 * @param prevMaxMagnitude 运营商之前的 maxMagnitude
 * @param newMaxMagnitude 运营商新的 maxMagnitude
 * @dev 只能由 AllocationManager 调用
 * @dev 注意:假设 `prevMaxMagnitude <= newMaxMagnitude`。此不变量在 AllocationManager 中维护。
 * @return depositSharesToSlash 要 slashing 的总存款份额(烧毁或重新分配)。
 */
function slashOperatorShares(
    address operator,
    OperatorSet calldata operatorSet,
    uint256 slashId,
    IStrategy strategy,
    uint64 prevMaxMagnitude,
    uint64 newMaxMagnitude
) 
    external
    onlyAllocationManager
    nonReentrant
    returns (uint256 depositSharesToSlash)

请参阅份额会计 - Slashing 以获取此方法中会计的说明。

此方法由 AllocationManager 在处理 AVS 对 operator 的 slash 时调用。Slashing 会立即发生,此方法直接减少 operator 与 slash 成比例的 delegated shares。

此外,提款队列中的任何可 slashing 的份额都会被标记为根据相同的 slashing 比例进行烧毁或重新分配(提款队列中的份额在 MIN_WITHDRAWAL_DELAY_BLOCKS 内仍然可 slashing)。对于被 slashing 的策略,调用相应的份额管理器(EigenPodManager/StrateyManager),增加该 operatorSet、slashId 和策略组合的烧毁或可重新分配的份额。

注意:原生 ETH 目前没有烧毁/重新分配机制,因为这需要 Pectra 能够强制退出验证器。目前,beaconChainETHStrategy 的 slashing 通过修改 staker 能够提取的数量来实现。

影响

要求

  • 从 operator slashing 的金额不得导致给定 strategyoperatorShares 下溢

increaseDelegatedShares

/**
 * @notice 当 staker 在策略中的存款份额余额增加时,由份额管理器调用。
 * 此方法将任何新份额委派给 operator(如果适用),并更新 staker 的
 * 存款缩放因子(无论如何)。
 * @param staker 其存款份额已增加的地址
 * @param strategy 已存入份额的策略
 * @param prevDepositShares 在增加之前 staker 在策略中拥有的存款份额数
 * @param addedShares staker 添加的存款份额数
 *
 * @dev 请注意,如果 staker 当前的 operator 已对 `strategy` 造成 100% 的 slashing,或者
 * staker 已在信标链上造成 100% 的 slashing,以致计算出的 slashing 因子为 0,则此
 * 方法将恢复。
 */
function increaseDelegatedShares(
    address staker,
    IStrategy strategy,
    uint256 prevDepositShares,
    uint256 addedShares
) 
    external
    onlyStrategyManagerOrEigenPodManager
    nonReentrant

当一个或多个策略的 staker 存款份额增加时,由 StrategyManagerEigenPodManager 调用。

如果 staker 已委派给 operator,则新的存款份额将直接添加到该 operator 的 operatorShares 中。无论委派状态如何,都将更新 staker 的存款缩放因子。此外,如果 operator 已为策略分配了可 slashing 的 stake,则 operatorSet 可以立即 slashing staker 的存款。

注意,如果 staker 的当前 operator 已对 strategy 造成 100% 的 slashing,或者 staker 已在信标链上造成 100% 的 slashing,以致计算出的 slashing 因子为 0,则此方法将恢复。有关详细信息,请参阅份额会计 - 完全被Slashing。如果 staker 对 slashing 因子为 0 的策略有 0 个存款份额,这不会阻止委派给 operator,但是任何后续调用 increaseDelegatedShares 的存款都会从完全被 slashing 的边缘情况恢复。

影响

  • 如果 staker 已委派给 operator,则 addedShares 将添加到 operator 的份额中
  • staker 的存款缩放因子已更新

要求

  • 调用者必须是 StrategyManagerEigenPodManager

decreaseDelegatedShares

/**
 * @notice 如果 staker 已委派,则响应于信标链ETH策略中余额的减少,减少其 operator的份额
 * @param staker 要减少其 operator 余额的 staker
 * @param curDepositShares staker 当前持有的存款份额
 * @param beaconChainSlashingFactorDecrease staker 的 beaconChainSlashingFactor 减少的金额
 * @dev 注意:`beaconChainSlashingFactorDecrease` 假定始终 < 1 WAD。
 * 这些不变量在 EigenPodManager 中维护。
 */
function decreaseDelegatedShares(
    address staker,
    uint256 curDepositShares,
    uint64 beaconChainSlashingFactorDecrease
) 
    external
    onlyEigenPodManager
    nonReentrant

当 staker 的份额因信标链上的检查点余额减少而减少时,由 EigenPodManager 调用。如果 staker 已委派给 operator,则 operator 的份额也会减少。否则,此方法不执行任何操作。

影响:如果所讨论的 staker 已委派给 operator,则 beaconChainETHStrategy 的 operator 份额将减少 staker 的可提取份额减少的金额

  • 如果 staker 未委派给 operator,则此方法不执行任何操作。

要求

  • 调用者必须是 EigenPodManager
  • 原文链接: github.com/Layr-Labs/eig...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

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