DEFI - 标题 - Umaprotocol

该UMIP提议在DVM中支持ACROSS-V2价格标识符,用于验证提交给以太坊主网的与桥相关的交易包的有效性。文章详细解释了Across V2的架构、如何构建和验证bundle,包括寻找有效的中继器(relays)、慢中继器以及如何构建PoolRebalanceRoot、RelayerRefundRoot和SlowRelayRoot。此外,还定义了在判断bundle有效性时需要考虑的多个条件。

UMIP-157
UMIP 标题 添加 ACROSS-V2 作为支持的价格标识符
作者 Matt Rice
状态 已弃用 (关于 Across v3, 参见 UMIP-179)
创建日期 2022/03/30
Discourse 链接

注意

除非在 UMIP-179 中另有规定,否则本 UMIP 将被 UMIP-179 取代。

概要

DVM 应该支持 ACROSS-V2 价格标识符。

Across V2 架构

Across V2 的基本架构是位于以太坊主网上的一个 LP(“流动性提供者”)池,连接到部署在 各种链上的多个“spoke pools”,以方便用户“存款”。存款是从“源”链到不同“目标”链的跨链转移请求,当“relayer”在所需的目标链上向存款人发送其所需的转移金额(扣除费用)时,该请求即被履行。

Relayer 通过 spokes 履行用户的存款来向 Across V2 系统借出资金,并最终由 LP 池偿还。“Bundles”包含许多此类还款,这些还款由 Optimistic Oracle ("OO") 一起验证。除了验证单个还款指令外,OO 还验证再平衡指令,这些指令告诉 LP 池如何将资金转移到 spoke pools 以及从 spoke pools 转移资金,以执行还款并将存款资金从 spoke 转移到 LP 池。

如果没有 relayer 可以为给定的存款请求提供所有资金,则会执行“slow relay”(或“slow fill”),其中资金从 LP 池发送到目标 spoke 以履行存款。这些 slow fill 请求也包含在上述 bundles 中。

Bundles 在链上实现为 Merkle Roots,它唯一地标识特定区块范围内所有还款和再平衡指令的集合。因此,Across V2 通过由 OO 验证的定期 bundles 来转移资金以偿还 relayers 并履行桥接请求。

本 UMIP 准确地解释了如何构建和验证 bundle。

动机

ACROSS-V2 价格标识符旨在供 Across v2 合约使用,以验证提交给主网的一组与桥接相关的 交易是否有效。

数据规范和实施

注意 1:以下详细信息通常会引用 Across V2 repo, 提交哈希值为:a8ab11fef3d15604c46bba6439291432db17e745。这允许 UMIP 具有恒定的引用,而不是 依赖于不断变化的存储库。

注意 2:在引用“later”或“earlier”事件时,主要排序应基于区块号,次要 排序应基于 transactionIndex,第三级排序应基于 logIndex。有关更多详细信息,请参阅 按时间顺序比较事件 部分。

注意 3:在未指定的情况下,排序应默认为升序,而不是降序。

注意 4:所有事件数据应由至少两个独立的、信誉良好的 RPC 提供商以相同的方式返回,以确保数据的完整性。

定义

按时间顺序比较事件

智能合约交易可以发出符合这些 docs 的“Returns”部分中描述的规范的事件。具体来说,事件应具有 blockNumbertransactionIndexlogIndex 的唯一组合。要按时间顺序比较事件 e1e2,我们可以说 如果 e1.blockNumber < e2.blockNumber,或者如果 e1.blockNumber == e2.blockNumber && e1.transactionIndex < e2.transactionIndex,或者如果 e1.blockNumber == e2.blockNumber && e1.transactionIndex == e2.transactionIndex && e1.logIndex < e2.logIndex,则 e1e2 “更早”。

因此,“earlier”事件具有较低的区块号、交易索引或日志索引,我们应该按该顺序比较事件属性。

有效的 bundle 提案

可以通过调用 HubPool.proposeRootBundle() 来提出 root bundle,它将发出一个 ProposedRootBundle 事件。

一旦 所有PoolRebalanceLeaves 通过 HubPool.executeRootBundle() 执行,root bundle 才是有效的,该函数只能在提出的 root bundle 的 challengePeriodEndTimestamp 过去后才能调用。

针对不同源链按时间顺序比较存款事件

每个存款都会发出一个 quoteTimestamp 参数。此时间戳应在以太坊网络的上下文中进行评估,并且应映射到以太坊区块,该区块的 timestamp 最接近 deposit.quoteTimestamp 但不大于(即 block.timestamp 最接近且 <= deposit.quoteTimestamp)。

将 L1 token 匹配到运行余额或净发送金额

RootBundleExecuted 事件和 [PoolRebalanceLeaf] 结构都包含等长的数组:l1TokensnetSendAmountsbundleLpFeesrunningBalancesl1Tokens 中的每个 l1Token 值都是一个地址,对应于部署在以太坊主网上的 ERC20 token。它应映射到其他三个数组(netSendAmountsbundleLpFeesrunningBalances)中与数组中相同索引的值。

例如,如果 l1Tokens 是“[0x123,0x456,0x789]”,而 netSendAmounts 是“[1,2,3]”,则地址为“0x456”的 token 的“净发送金额”等于“2”。

版本

ConfigStoreglobalConfig 中存储一个“VERSION”值。这用于保护 relayers 和数据工作人员在与 Across 交互时免受使用过时代码的影响。“VERSION”应映射到一个整数字符串,该字符串只能随时间增加。“VERSION”通过调用 updateGlobalConfig 来更新,因此它作为链上事件发出。“VERSION”包含的事件的区块时间表示该“VERSION”何时变为活动状态。Relayers 应该支持存款的报价时间戳的版本,否则他们可能会发送无效的 fill。Proposers 和 Disputers 应该支持 bundle 区块范围的最新版本,以验证或提出新的 bundle。此版本独立于 加速存款签名 中包含的版本。

辅助数据规范

辅助数据只需要一个字段:ooRequester,它应该是从 OO 请求价格的合约。由于该合约应包含足够的信息,供投票者解决 relay 的有效性,因此不需要额外的辅助数据。

示例:

ooRequester:0x69CA24D3084a2eea77E061E2D7aF9b76D107b4f6

配置常量

全局常量

以下常量应反映存储在部署在 Etherscan 上的 AcrossConfigStore 合约中的内容。此合约由 Across 治理拥有,并充当以下变量的真实来源。本 UMIP 当前存储在上述合约中的全局变量包括:

  • "MAX_POOL_REBALANCE_LEAF_SIZE"
  • "MAX_RELAYER_REPAYMENT_LEAF_SIZE"
  • "VERSION"
    • Across 协议版本号。支持的实现应根据其实现中定义的值查询此值,以确定与当前协议版本的兼容性。未能正确评估版本号可能意味着已 fill 的 relays 不会从 HubPool 退款,因此可能会导致资金损失。有关更多信息,请转到 此处
  • "DISABLED_CHAINS"
    • 这必须是链 ID 数字的字符串化列表。这不能包含链 ID “1” 或 HubPool 链 ID。此处的链必须包含在 CHAIN_ID_INDICES 中。
  • "CHAIN_ID_INDICES"
    • 对于早于首次发布此全局变量的任何区块,这应默认为值 [1,10,137,288,42161]。这是为了考虑此 UMIP 的初始版本,该版本在 UMIP 而不是在 ConfigStore 合约中定义了此 ID 列表。只能将链添加到此列表才能有效。

要查询上述任何常量的值,应使用变量名称的十六进制值调用 AcrossConfigStore 合约的 globalConfig(bytes32) 函数。例如,可以通过调用 globalConfig(toHex("MAX_POOL_REBALANCE_LEAF_SIZE")) 来查询“MAX_POOL_REBALANCE_LEAF_SIZE”,这等效于 globalConfig("0x4d41585f504f4f4c5f524542414c414e43455f4c4541465f53495a45")。例如,这可能会返回

“25”

Token 常量

以下常量也存储在 AcrossConfigStore 合约中,但特定于以太坊 token 地址。因此,它们通过查询配置商店的 tokenConfig(address) 函数来获取。

  • "rateModel"
    • 这是一个费率模型参数的 JSON 结构。
    • 这些参数应完全指定此 token 的费率模型,如 UMIP 136 中所述。
    • 此结构中的每个字段都应是以字符串表示的整数(以允许无限精度)。
    • 只有当 rateModel 结构包含以下所有参数时,它才有效:UBarR0R1R2
  • "routeRateModel"
    • rateModel 类似,这是一个 originChain-destinationChain 键的字典,映射到费率模型,这些费率模型优先于该特定存款路径上 token 的 rateModel。路由费率模型应遵循与默认 rateModel 相同的 UBarR0R1R2 格式
  • "spokeTargetBalances"
    • 这包含从 chainId 到 JSON 结构的 JSON 映射。
    • 每个结构包含两个子字段,“target”和“threshold”。
    • 每个子字段都是以字符串表示的整数(以允许无限精度)。
    • 这些整数都应为正值。如果任何一个为负数,则在使用时应将其视为“0”。
    • “target”整数应小于“threshold”整数。如果不是,则在使用时应将“theshold”整数视为包含与“target”整数相同的值。
    • 如果“spokeTargetBalances”,则映射中缺少特定 chainId,或者缺少“target”或“threshold”,缺少的“target”和“threshold”应默认为 0。

例如,查询 tokenConfig("0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2") 可能会返回:

"{"rateModel":{"UBar":"750000000000000000","R0":"50000000000000000","R1":"0","R2":"600000000000000000"},"spokeTargetBalances":{"1":{"threshold":"200000000000000000000","target":"100000000000000000000"},"42161":{"threshold":"400000000000000000000","target":"200000000000000000000"}}}"

本 UMIP 稍后将解释如何使用全局和 token 特定的配置设置。

预备信息

ooRequester 地址应为 HubPool 合约 的实例。

如果 ooRequester 中任何预期的详细信息未以预期形式提供,因为 HubPool 不 匹配预期的接口,则标识符应返回 0

为了获取提案数据,投票者应查找匹配 此签名 的事件在 ooRequester 上。描述此提案的事件是具有最高区块号的匹配事件,该区块号的时间戳小于或等于价格请求的时间戳。如果存在两个都 满足此标准的匹配事件,则可以通过两种方式之一解决。如果时间戳与请求时间戳匹配, 则应使用 earlier 事件。如果时间戳早于请求时间戳,则应使用 later 事件

提案信息

从选定的事件中,应该能够收集以下信息:

  • bundleEvaluationBlockNumbers
  • poolRebalanceRoot
  • relayerRefundRoot
  • slowRelayRoot

确定 root bundle 提案的区块范围

bundleEvaluationBlockNumbers 是此 bundle 的每个目标链的结束区块号的有序数组。哪个索引 对应于哪个链由 全局配置 中的 "CHAIN_ID_INDICES" 表示。

要确定每个 chainId 的起始区块号,请搜索最新的 RootBundleExecuted 事件 具有匹配的 chainId,同时仍然早于请求的时间戳。找到该事件后,搜索 ProposeRootBundle 尽可能晚的事件,但早于我们刚刚确定的 RootBundleExecuted 事件。找到此提案事件后,使用 "CHAIN_ID_INDICES" 确定其索引到其 bundleEvaluationBlockNumbers 数组中 chainId 的映射。对于 每个 chainId,它们的起始区块号是此先前 有效提案 事件中该链的 bundleEvaluationBlockNumber + 1 和该链的 latest 区块高度的最小值。

使用最小值允许区块范围处理自上次提案以来链未推进其区块高度的边缘情况,例如当链正在经历已知的硬分叉时。

使用此机制来确定原始 bundleEvaluationBlockNumbers 中表示的每个 chainId 的起始区块号。

请注意,以上规则要求每个 chainIdbundleEvaluationBlockNumbers 大于或等于先前 有效提案 的相同 chainIdbundleEvaluationBlockNumbers。在链未暂停且以正常频率生成区块的正常情况下,每个提案的区块范围从先前提案的 bundleEvaluationBlockNumbers 加 1 开始,到下一个 bundleEvaluationBlockNumbers。如果 latest 区块高度未超过先前的 bundleEvaluationBlockNumber,则提案的区块范围将从先前提案的 bundleEvaluationBlockNumbers 到相同的数字,即区块范围为 0。

另请注意,如果链 ID 位于 "DISABLED_CHAINS" 列表中,则上述确定结束区块的规则不适用。如果链在特定提案的“主网”结束区块(链 ID 1)中存在于 DISABLED_CHAINS 中,则该链的结束区块应与其在上次有效提案中添加之前的值相同。具体来说,如果链在特定提案的“主网”结束区块(链 ID 1)存在于 DISABLED_CHAINS 中,则该链的结束区块应与其在上次执行的 bundle 中的值相同。

评估 CrossChainContracts HubPool 合约上的方法(传递每个 chainId)在提案的区块号处确定 SpokePool 合约 的地址对于每个目标链。我们将在下一节中使用这些 SpokePool 地址来查询正确的事件数据。

查找有效的 Relay

对于每个目标链,查找其 SpokePool 上介于该链的起始区块号和结束区块号之间的所有 FilledRelay 事件。对于此查询,排除 任何 isSlowRelay 设置为 truefillAmount 等于 0FilledRelay 事件。

对于所有 FilledRelay 事件,可以通过查询 CrossChainContractsSet 来查找存款的源链的 SpokePool 地址,并查找所有匹配的事件,其中 l2ChainId 匹配 FilledRelay 事件中的 originChainId 值。这些事件中的 spokePool 值是此存款可能来自的所有可能的 spoke pools。

我们不能假设使用最新的 SpokePool,这样我们就不会阻止旧的存款被 relay。要使用的实际 spoke pool 是在源链上的存款发送之前在以太坊上发出的最后一个 CrossChainContractsSet 事件中的地址。(我们可以使用 此方法 来识别与存款的 quoteTimestamp 对应的 CrossChainContractsSet 以太坊 block.timestamp)。

注意:在以下部分中,如果 relay 在任何时候被认为是无效的,则在构建 bundle 时不得考虑该 relay。

对于先前找到的每个 FilledRelay 事件,应在 originChainId 的 spoke pools 之一中找到 FundsDeposited 事件,其中以下参数匹配:

  • amount
  • originChainId
  • destinationChainId
  • relayerFeePct
  • depositId
  • recipient
  • depositor

匹配 L2 token 和 L1 token

此外,匹配的 relays 应设置其 destinationToken,以便满足以下过程:

  1. 查找最新的 SetRebalanceRoute 事件 [在关联的 FundsDeposited 事件中 [quoteTimestamp] (#[comparing-deposit-events-chronologically-for-different-origin-chains) 或之前具有区块时间戳],其中 originChainIdoriginToken 匹配 destinationChainIddestinationToken。从匹配的事件中提取 l1Token 值 。如果没有匹配的事件,则 relay 无效。
  2. SetPoolRebalanceRoute 事件中搜索与 quoteTimestamp 之前或之时的相同 l1TokendestinationChainId。如果在步骤 1 中找到的事件之后有任何匹配的事件,则 relay 无效。
  3. 使用在步骤 1 中找到的 l1Token 值,在 quoteTimestamp 之前或之时使用该 l1Token 和与 FundsDeposited 事件中的 destinationChainId 值匹配的 destinationChainId 搜索最新的 SetRebalanceRoute 事件。如果找到匹配项,则 destinationToken 应匹配 FilledRelay 事件中的 destinationToken 值。如果它们不匹配或者未找到匹配事件,则 relay 无效。

验证 realizedLpFeePct

要确定 FilledRelay 事件中 realizedLPFeePct 的有效性,使用的过程与 标识符 IS_RELAY_VALID,在 UMIP 136 中指定 中使用的过程完全相同。但是,不使用 RateModelStore 合约来查找存款的费率模型,我们可以使用 AcrossConfigStoretokenConfig查找存款的费率模型。可以通过遵循上面的步骤 2 将已存款的 originToken 映射到 l1Token,该 l1Token 可用于查询 rateModel

此外,不调用 BridgePool 合约上的 liquidityUtilizationCurrentliquidityUtilizationPostRelay(不传递任何参数)来计算费率模型,而是在 HubPool 合约上调用同名的方法,传入一个参数,即上面 3 步过程中派生的 l1Token

如果使用这些方法计算出的 realizedLPFeePctFilledRelay 事件中的 realizedLPFeePct 不匹配,则认为 relay 无效。

所有有效的 FilledRelay 事件应然后存储起来以构建 bundle。

查找 Slow Relay

要确定所有 slow relay,请按照以下过程操作:

  1. 对于上面的所有有效 FilledRelay 事件,按 originChainIddepositId 对它们进行分组。
  2. 删除包含 totalFilledAmount 等于 amountFilledRelay 事件的所有组。这将删除已 100% filled 的存款。
  3. 删除不包含 filledAmount 非零且等于 totalFilledAmount 的事件的所有组。这仅保留最早的 fill 在此时段内的存款。

对于所有剩余的组,它们应存储在 slow relay 组的列表中。

计算 Slow Relay 支付金额

对于 上面 识别的给定 slow relay,我们可以将关联的存款的“未 fill 金额”计算为 deposit.amount - latestFill.totalFilledAmount,其中 latestFill 是存款按时间顺序排列的最后一次 fill。由于每次 fill 都会增加 totalFilledAmount,因此也可以通过对与存款关联的所有 fill 进行排序并保留具有最大 totalFilledAmount 的 fill 来识别 latestFill

注意:由于我们消除了所有 totalFilledAmount == deposit.amount 的 fill,剩余的“最后一次 fill”应具有 totalFilledAmount < deposit.amount 且具有 totalFilledAmount > [所有其他存款的 fill].totaFilledAmount

构建 PoolRebalanceRoot

要构建 poolRebalanceRoot,你需要形成一个再平衡列表。

对于上面的所有有效 FilledRelay 事件,按 repaymentChainId 和上面找到的关联的 l1Token 对它们进行分组。

对于每个组,将 fillAmount 值相加,以获得该组的总 relay 还款。

类似地,将 fillAmount * realizedLPFeePct / 1e18 相加,以获得该组的总 LP 费用。

要确定修改运行余额的金额:

  1. 添加 relayer 退款:对于每个 FilledRelay ,在 0 处初始化运行余额值,并将总 relay 还款添加到 它。每个运行余额值由其 repaymentChainIdl1Token 定义。
  2. 添加 slow fills:对于每个 slow relay 组,将每个 slow relay 的 未 fill 存款金额 添加到该组的运行余额中。
  3. 减去存款:从每个 SpokePool 的区块范围内的所有 SpokePool 上找到所有 FundsDeposited 事件。使用 originChainIdoriginTokenquoteTimestamp 和 HubPool 上的 SetPoolRebalanceRoute 事件,使用 类似于上面的 3 步过程将其映射回 l1Token。对于该 l1TokenoriginChainId, 如果运行余额值尚不存在,则初始化一个运行余额值并从中减去 amount
  4. 减去部分执行的 slow fills 的超额:在以前会将 slow fill 支付发送到 SpokePool 的情况下,但在 slow fill leaf 可以执行之前,relayer 部分地“快速” fill 了存款。之后,执行 slow fill leaf 以完成存款。SpokePool 现在有多余的 token 金额,因为原始 slow fill 支付未完全用于完成存款,因此必须将此超额返回给 Mainnet。因此,此步骤解释了如何识别超额并确定发送回多少(即从运行余额中减去)。查找区块范围内将 isSlowRelay 设置为 true 的所有 FilledRelay 事件。对于每个事件,使用与上面类似的方法 quoteTimestamp 处将此事件映射回 l1Token。使用 destinationChainId 作为 repaymentChainId 以确定此事件应应用于哪个运行余额。对于每个先前 验证的 bundle,请按照此 originChainIddepositId“查找 Slow Relay” 部分中的步骤操作,并查找具有匹配的 originChainIddepositId 的 slow relay。应该只有一项 slow relay 支付与所讨论的 FilledRelay 匹配。这是包含在先前 bundle 中的 slow fill,该 slow fill 已添加到 runningBalance 并最终导致 bundle 将 slow fill 支付发送到 destinationChainId 上的 SpokePool。计算旧 root bundle 中发送的 slow fill 金额。在此当前 FilledRelayisSlowRelay = true)之后的 SpokePool 中剩余的超额金额等于 slow fill 金额 减去 FilledRelay.fillAmount。换句话说,如果 FilledRelay.fillAmount 小于最初在先前 bundle 中发送的 slow fill 金额,则发送回差额。从关联的 l1TokendestinationChainId 的运行余额中减去结果。
  5. 减去未执行的 slow fills 的超额:本节与上面类似,但处理的是 slow fill leaf 永远无法执行的情况。查找区块范围内 totalFilledAmount 等于 amount(即完成存款的 fills)且 fillAmount 小于 amount(即不是存款的第一次 fill)的所有 FilledRelay 事件。如果存款的第一次 fill 完成了存款(fillAmount == amounttotalFilledAmount == amount),那么 spoke pool 中就不会有剩余的金额,因为这不会触发将 slow fill 支付发送到 spoke。首先我们需要查看此当前 fill 的第一次 fill 是否触发了 slow fill。在先前 验证的 bundle 中,识别同一 originChainIddepositId 的所有匹配的 fill。查找最早的此类 fill。使用 此逻辑 获取包含此 fill 的 root bundle 提案的区块范围,以获取 ProposeRootBundle 事件,其中 FilledRelay.destinationChainIdbundleEndBlock 大于或等于 FilledRelay 区块号。在此同一 bundle 区块范围内查找最后一次 fill。该 bundle 的 slow fill 支付应为 FilledRelay.amount - FilledRelay.totalFilledAmount,与 此计算 相同。由于我们知道此即将到来的提案中的当前 FilledRelay 已完全 100% 地 fill 了存款,因此我们知道 slow fill leaf 无法执行,因此必须将整个 slow fill 支付发送回主网。从此运行余额中减去此金额(有效 root bundle 中此 fill 最终完成的上一次 slow fill 支付)。

我们现在需要将先前的运行余额值添加到给定 repaymentChainIdl1Token 的当前值。 对于每个 repaymentChainIdl1Token 组合,应查询较旧的 RootBundleExecuted 事件 以查找先前的 RootBundleExecuted 事件。这意味着识别具有与 repaymentChainId 匹配的 chainId 的最新 RootBundleExecuted 事件并 识别 l1Token 索引处的 runningBalanceValue

对于 l1TokenrepaymentChainId 的每个元组,我们应该已经计算出总运行余额值。以下算法描述了计算运行余额和净发送金额的过程:

spoke_balance_threshold = 此 token 的 `spokeTargetBalances` 中的“threshold”值
spoke_balance_target = 此 token 的 `spokeTargetBalances` 中的“target”值

net_send_amount = 0
## 如果运行余额为正数,则 hub 欠 spoke 资金。
if running_balance > 0:
  net_send_amount = running_balance
  running_balance = 0
## 如果运行余额为负数,则从 spoke 中提取足够的资金到 hub,以将运行余额恢复到其目标
else if abs(running_balance) >= spoke_balance_threshold:
  net_send_amount = min(running_balance + spoke_balance_target, 0)
  running_balance = running_balance - net_send_amount

获取上述运行余额和净发送金额,并仅按 repaymentChainId 对它们进行分组并按 repaymentChainId 排序。在 每个组中,按 l1Token 排序。如果有超过 MAX_POOL_REBALANCE_LEAF_SIZEl1Token,则特定链的 leaf 将 需要拆分为多个 leaf,从 groupIndex 0 开始,每个后续 leaf 将 groupIndex 值递增 1。

现在我们有了排序的 leaf,我们可以为每个 leaf 分配一个唯一的 leafId,从 0 开始。

有了所有这些信息,应该可以以 此处 给出的格式构建每个 leaf。 重要的是,l1TokensbundleLpFeesnetSendAmountsrunningBalances 数组都应具有相同的长度。后三个数组是映射到同一索引的 l1Tokens 条目的值。有关更好地解释如何将 l1Tokens 映射到其他三个数组,请参阅 此章节

构建 leaf 后,可以通过使用 Solidity 的标准过程 keccak256(abi.encode(poolRebalanceLeaf)) 对每个 leaf 数据结构进行哈希处理来构建 merkle root。对 leaf 进行哈希处理后,应以标准方式构建树, 以便可以使用 OpenZeppelin 的 MerkleProof 库进行验证。有关如何构建这些类型的树的示例,请参见 [此处](https://github.com/OpenZeppelin/openzeppelin-contracts/`refundAmountsrefundAddresses仅仅是通过按照relayer对这个组中的中继进行分组,并将每个中继的amount - (amount * lpFeePct / 1e18)相加计算出来的。这些应该按照refundAmounts的降序排列。如果两个refundAmounts相等,那么应该按照relayer` 地址排序。

如果对于特定的 l2TokenAddress 存在多于 MAX_RELAYER_REPAYMENT_LEAF_SIZErefundAddresses,那么这些应该被拆分成 MAX_RELAYER_REPAYMENT_LEAF_SIZE 个元素的叶子(按照上述方式排序),只有特定 l2TokenAddress 的第一个叶子能够包含非零的 amountToReturn。

一旦这些为所有中继计算出来,那么叶子(或者对于 > 25 个元素的组)应该按照 chainId 作为主索引,然后是 l2TokenAddress 作为辅助索引,再然后是 > MAX_RELAYER_REPAYMENT_LEAF_SIZE 个元素的组的单独排序,作为第三级排序。一旦这些被排序,每个叶子可以根据其在组中的索引获得一个 leafId,从 0 开始。

一旦这些叶子被构建,它们可以被用来形成一个 merkle 根,就像在前一节中描述的那样。

构建 SlowRelayRoot

为了构建如 这里 所述的 SlowRelayRoot 叶子,只需要基于在上面的 "Finding Slow Relays" 章节中找到的所有慢中继形成叶子。中继中的信息应该直接映射到叶子数据结构。

它们的主排序索引应该是 originChainId,次排序索引应该是 depositId

然后你可以构建一个 merkle 根,类似于前两节中的做法。

确定结果

必须满足三个条件才能认为提案有效:

  1. 上面计算出的根与提案中的根匹配。
  2. 提案事件中指定的 poolRebalanceLeafCount 与在上面的 PoolRebalanceRoot 中计算的池再平衡叶子数相匹配。
  3. bundleEvaluationBlockNumbers 必须包含所有在提案时具有非零 CrossChainContractsSetchainIds
  4. 没有通过此提案执行明显的恶意行为或操纵系统。

如果提案被认为是无效的,返回 0。如果有效,返回 1。注意:这些值按 1e18 缩放。

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

0 条评论

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