DEFI - 标题 - Umaprotocol

该UMIP提议在DVM中支持ACROSS-V2价格标识符,用于验证提交到以太坊主网的桥接相关交易bundle的有效性。文章详细描述了Across V2的架构、数据规范、实现以及如何构建和验证bundle,包括PoolRebalanceRoot、RelayerRefundRoot和SlowRelayRoot的构建方法。同时还涉及到如何确定区块范围,寻找有效的Relay,以及处理慢速Relay等问题。

UMIP-157
UMIP 标题 添加 ACROSS-V2 作为支持的价格标识符
作者 Matt Rice
状态 已批准
创建时间 2022/03/30
Discourse 链接

概要

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

Across V2 架构

Across V2 的基本架构是一个位于以太坊主网上的单一 LP ("Liquidity Provider",流动性提供者) 池,连接到部署在各种链上的多个 "spoke pools"(分支池),以方便用户 "deposits"(存款)。存款是从 "origin"(源)链到不同 "destination"(目标)链的跨链转移请求,当 "relayer"(中继器)在用户所需的目标链上发送给存款人他们想要的转移金额(扣除费用后)时,该请求便被履行。

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

如果没有中继器可以为给定的存款请求提供所有资金,则会执行 "slow relay"(慢速中继)(或 "slow fill"(慢速填充)),其中资金从 LP 池发送到目标 spoke 池以完成存款。这些慢速填充请求也包含在上述捆绑包中。

捆绑包在链上实现为 Merkle Roots,它唯一地标识了特定区块范围内所有还款和再平衡指令的集合。因此,Across V2 通过定期的捆绑包来转移资金以偿还中继器并满足桥接请求,所有这些都由 OO 验证。

此 UMIP 准确解释了如何构建和验证捆绑包。

动机

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

数据规范和实现

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

注 2:当引用 "later"(较晚)或 "earlier"(较早)的事件时,主要排序应基于区块号,次要排序应基于 transactionIndex,第三级排序应基于 logIndex。有关更多详细信息,请参阅 比较事件 的部分。

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

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

定义

按时间顺序比较事件

智能合约交易可以发出符合这些 文档 的 "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"(更早)。

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

有效的捆绑包提案

可以通过调用 HubPool.proposeRootBundle() 来提出根捆绑包,这将发出一个 ProposedRootBundle 事件。

一旦 all(所有) PoolRebalanceLeaves 通过 HubPool.executeRootBundle() 执行,根捆绑包才有效,这只能在提出的根捆绑包的 challengePeriodEndTimestamp 过去后才能调用。

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

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

将 L1 代币与运行余额或净发送金额匹配

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

例如,如果 l1Tokens 是 "[0x123,0x456,0x789]",netSendAmounts 是 "[1,2,3]",则地址为 "0x456" 的代币的 "net send amount"(净发送金额)等于 "2"。

版本

ConfigStoreglobalConfig 中存储一个 "VERSION"(版本)值。这用于保护中继器和数据工作者在与 Across 交互时免于使用过时的代码。"VERSION" 应该映射到一个整数字符串,该字符串只能随着时间的推移而增加。通过调用 updateGlobalConfig 来更新 "VERSION",因此它会作为链上事件发出。包含 "VERSION" 的事件的区块时间指示该 "VERSION" 何时变为活动状态。中继器应支持存款报价时间戳时的版本,否则他们可能会发送无效的填充。提案者和争议者应支持捆绑包区块范围的最新版本,以验证或提出新的捆绑包。此版本独立于 加速存款签名 中包含的版本。

辅助数据规范

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

例子:

ooRequester:0x69CA24D3084a2eea77E061E2D7aF9b76D107b4f6

配置常量

全局常量

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

  • "MAX_POOL_REBALANCE_LEAF_SIZE"
  • "MAX_RELAYER_REPAYMENT_LEAF_SIZE"
  • "VERSION"
    • Across 协议版本号。支持的实现应根据其实现中定义的值查询此值,以确定与当前协议版本的兼容性。未能正确评估版本号可能意味着已填充的中继不会从 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"

代币常量

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

  • "rateModel"
    • 这是一个利率模型参数的 JSON 结构。
    • 这些参数应完全指定此代币的利率模型,如 UMIP 136 中所述。
    • 此结构中的每个字段都应是一个表示为字符串的整数(以允许无限精度)。
    • 只有当 rateModel 结构包含以下所有参数时才有效:UBarR0R1R2
  • "routeRateModel"
    • rateModel 类似,这是一个 originChain-destinationChain 键的字典,映射到对该特定存款路由上的代币优先于 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 将解释如何使用全局和代币特定的配置设置。

预备信息

ooRequester 地址预计是 HubPool 合约 的一个实例。

如果 ooRequester 中的任何预期细节无法以预期形式获得,因为 HubPool 不匹配预期的接口,则标识符应返回 0

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

提案信息

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

  • bundleEvaluationBlockNumbers
  • poolRebalanceRoot
  • relayerRefundRoot
  • slowRelayRoot

确定根捆绑包提案的区块范围

bundleEvaluationBlockNumbers 是此捆绑包的每个目标链的结束区块号的有序数组。哪个索引 对应于哪个链由 global config 中的 "CHAIN_ID_INDICES" 表示。

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

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

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

请注意,上述规则要求每个 chainIdbundleEvaluationBlockNumbers 大于或等于先前 valid proposal's 相同的 chainIdbundleEvaluationBlockNumbers。在链未暂停并且以正常频率生成区块的正常情况下,每个提案的区块范围从先前提案的 bundleEvaluationBlockNumbers 加 1 开始,到下一个 bundleEvaluationBlockNumbers 结束。如果在先前的 bundleEvaluationBlockNumber 之后 latest 区块高度没有前进,那么提案的区块范围将从先前的提案的 bundleEvaluationBlockNumbers 到相同的数字,即 0 的区块范围。

另请注意,如果链 ID 位于 "DISABLED_CHAINS" 列表中,则上述确定结束区块的规则不适用。如果一个链存在于 DISABLED_CHAINS 中,该拟议的捆绑包必须重复使用添加之前最后一个有效提案中的捆绑包结束区块。具体来说,如果一个链在特定提案的 "mainnet" 结束区块(链 ID 1)上存在于 DISABLED_CHAINS 中,那么该链的结束区块应与其在最新执行的捆绑包中的值相同。

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

寻找有效的 Relays(中继)

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

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

我们不能假设使用最新的 SpokePool,这样我们就不会阻止旧的存款被中继。要使用的实际 spoke 池是发生在以太坊上发送存款之前的最后一个 CrossChainContractsSet 事件中的地址。(我们可以使用 这种方法 通过存款的 quoteTimestamp 来确定 CrossChainContractsSet 以太坊 block.timestamp)。

注意:在下面的章节中,如果在任何时候中继被认为是无效的,在构建捆绑包时不得考虑该中继。

对于早些时候找到的每个 FilledRelay 事件, 应该在 originChainId 的其中一个 spoke 池中找到 FundsDeposited 事件,其中以下参数匹配:

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

匹配 L2 代币和 L1 代币

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

  1. 查找最新的 SetRebalanceRoute 事件 在相关 FundsDeposited 事件中,quoteTimestamp 或之前的一个区块时间戳,其中 originChainIdoriginToken 匹配 destinationChainIddestinationToken。从匹配的事件中提取 l1Token 值。如果没有匹配的事件,则中继无效。
  2. SetPoolRebalanceRoute 事件中搜索与 quoteTimestamp 之前或之后的相同 l1TokendestinationChainId。如果在步骤 1 中找到的任何事件晚于步骤 1 中找到的事件,则中继无效。
  3. 使用在步骤 1 中找到的 l1Token 值,在 quoteTimestamp 之前或之后的 l1Token 中搜索最新的 SetRebalanceRoute 事件,并使用匹配 FundsDeposited 事件中 destinationChainId 值的 destinationChainId。如果找到匹配项,则 destinationToken 应匹配 FilledRelay 事件中的 destinationToken 值。如果它们不匹配或未找到匹配事件,则中继无效。

验证 realizedLpFeePct

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

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

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

然后应存储所有有效的 FilledRelay 事件以构建捆绑包。

查找 Slow Relays(慢速中继)

要确定所有慢速中继,请遵循以下过程:

  1. 对于上述所有有效的 FilledRelay 事件,请按 originChainIddepositId 对其进行分组。
  2. 删除所有包含 FilledRelay 事件的组,其中 totalFilledAmount 等于 amount。这将删除已 100% 填充的存款。
  3. 删除所有不包含 filledAmount 非零且等于 totalFilledAmount 的事件的组。这将仅保留最早的填充在此时间范围内的存款。

对于所有剩余的组,应将其存储在慢速中继组的列表中。

计算 Slow Relay(慢速中继)付款金额

对于 above 标识的给定慢速中继,我们可以将相关存款的 "unfilled amount"(未填充金额)计算为 deposit.amount - latestFill.totalFilledAmount,其中 latestFill 是存款按时间顺序排列的最后一次填充。由于每次填充都会递增 totalFilledAmount,因此也可以通过对与存款关联的所有填充进行排序并保留具有最大 totalFilledAmount 的填充来识别 latestFill

注意:由于我们消除了所有 totalFilledAmount == deposit.amount 的填充,因此剩余的 "last fill" 应具有 totalFilledAmount &lt; deposit.amount AND 具有 totalFilledAmount > [所有其他用于存款的填充].totaFilledAmount

构建 PoolRebalanceRoot

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

对于上述所有有效的 FilledRelay 事件,请按 repaymentChainId 及其关联的 found abovel1Token 对其进行分组。

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

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

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

  1. 添加中继退款:对于每个 FilledRelay group,以 0 初始化一个运行余额值,并将总中继还款额添加到 它。每个运行余额值由其 repaymentChainIdl1Token 定义。
  2. 添加慢速填充:对于每个 slow relay group,将每个慢速中继的 unfilled deposit amount 添加到该组的运行余额中。
  3. 减去存款:在每个 SpokePool 的区块范围内,找到所有先前 SpokePool 上所有 FundsDeposited 事件。使用 originChainIdoriginTokenquoteTimestamp 和 HubPool 上的 SetPoolRebalanceRoute 事件,使用与上述步骤 3 类似的过程将其映射回 l1Token。对于该 l1TokenoriginChainId, 如果运行余额值尚不存在,则初始化一个运行余额值,并从中减去 amount
  4. 扣除部分执行的慢速填充中的剩余额:如果先前向 SpokePool 发送了慢速填充付款,但是在慢速填充 leaf 可以执行之前,中继器部分地 "fast"(快速)填充了存款。之后,执行慢速填充 leaf 以完成存款。现在,SpokePool 具有过多的代币额,因为最初的慢速填充付款未完全用于完成存款,因此必须将此剩余额退回给主网。因此,此步骤说明了如何识别剩余额并确定发送回多少(即从运行余额中扣除)。找到区块范围内所有将 isSlowRelay 设置为 true 的 FilledRelay 事件。对于每个事件,使用与上面类似的方法 map this event back to an l1Token at the quoteTimestamp。使用 destinationChainId 作为 repaymentChainId 以确定此事件应应用于哪个运行余额。对于每个先前 validated bundle,按照 "Finding Slow Relays" 部分中的步骤 对于此 originChainIddepositId,并查找具有匹配的 originChainIddepositId 的慢速中继。应该完全有一个与 FilledRelay 匹配的慢速中继付款。这是包含在先前捆绑包中添加到 runningBalance 并最终导致捆绑包向 destinationChainId 上的 SpokePool 发送慢速填充付款的慢速填充。compute the slow fill amount 计算在旧的根捆绑包中发送的慢速填充金额。在此当前 FilledRelay(其中 isSlowRelay = true)之后,SpokePool 中剩余的剩余额等于 slow fill amount 减去 FilledRelay.fillAmount。换句话说,如果 FilledRelay.fillAmount 小于最初在先前捆绑包中发送的 slow fill amount,则发送回差额。从中减去结果 关联的 l1TokendestinationChainId 的余额。
  5. 扣除未执行的慢速填充中的剩余额:本节与上一节类似,但处理无法执行慢速填充 leaf 的情况。找到区块范围内所有 totalFilledAmount 等于 amount(即完成存款的填充)并且 fillAmount 小于 amount(即不是存款的第一次填充的填充)的 FilledRelay 事件。如果存款的第一次填充完成了存款(fillAmount == amount 并且 totalFilledAmount == amount),则 spoke 池中不可能有剩余额,因为这不会触发向 spoke 发送慢速填充付款。首先,我们需要查看此当前填充的第一次填充是否触发了慢速填充。在先前 validated bundles 中,确定所有相同的 originChainIddepositId 的匹配填充。找到最早的此类填充。使用 this logic 确定包含 FilledRelay.destinationChainIdbundleEndBlock 大于或等于 FilledRelay 区块号的 ProposeRootBundle 事件的根捆绑包提案的区块范围。在此同一捆绑包区块范围内找到最后一次填充。捆绑包的慢速填充付款应为 FilledRelay.amount - FilledRelay.totalFilledAmount,与 this calculation 相同。由于我们知道此即将到来的提案中的当前 FilledRelay 100% 填充了存款,因此我们知道无法执行慢速填充 leaf,因此必须将整个慢速填充付款发送回主网。从运行余额中扣除此金额(来自有效根捆绑包的先前慢速填充付款,该填充最终完成了此填充)。

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

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

spoke_balance_threshold = 此代币在 `spokeTargetBalances` 中的 "threshold"(阈值)值
spoke_balance_target = 此代币在 `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_SIZE l1Token,则需要将特定链的 leaf 分解为多个 leaf,从 groupIndex 0 开始,并且每个后续 leaf 将 groupIndex 值递增 1。

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

有了所有这些信息,应该可以以格式化形式构造每个 leaf here。 重要的是,l1TokensbundleLpFeesnetSendAmountsrunningBalances 数组应具有相同的长度。后三个数组是映射到相同索引的 l1Tokens 条目的值。请参阅 此部分 以更好地解释如何将 l1Tokens 映射到其他三个数组。

构造 leaf 后,可以通过使用 Solidity 的标准流程 keccak256(abi.encode(poolRebalanceLeaf))refundAmountsrefundAddresses 只是通过将这个群组中的 relays 按照 relayer 分组,并将每个 relay 的 amount - (amount * lpFeePct / 1e18) 相加计算得出。这些应该按照 refundAmounts 降序排列。如果两个 refundAmounts 相等,那么它们应该按照 relayer 地址排序。

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

一旦这些为所有的 relays 计算出来,这些叶子节点(或者对于 > 25 个元素的叶子节点组)应该按照 chainId 作为主索引,然后 l2TokenAddress 作为辅助索引,以及 > MAX_RELAYER_REPAYMENT_LEAF_SIZE 个元素组的单独排序作为第三级排序。一旦这些被排序好,每个叶子节点可以基于其在群组中的索引被赋予一个 leafId,从 0 开始。

一旦这些叶子节点被构造完成,它们可以被用于形成一个 merkle 根,如前一节所述。

构建 SlowRelayRoot

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

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

然后你可以构造一个 merkle 根,类似于在前两个章节中完成的方式。

确定结果

提案被认为是有效的,必须满足三个条件:

  1. 上面计算出的根与提案中的根匹配。
  2. 提案事件中指定的 poolRebalanceLeafCount 与在上面的 PoolRebalanceRoot 中计算出的池再平衡叶子节点的数量匹配。
  3. bundleEvaluationBlockNumbers 必须包括在提案时具有非零 CrossChainContractsSet 的所有 chainIds
  4. 没有明显的通过此提案进行恶意破坏或操纵系统的情况。

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

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

0 条评论

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