Skip to main content
Guards 解释了最小值如何在模拟期间被产生。本页解释它们如何被报告,以及在 flow 结束时 proxy 仍持有的任何代币如何一并处理。
Flow 描述资源如何在程序中流动。在执行结束时,proxy 仍持有的任何余额都需要一个明确的去向;否则代币会被搁浅。Sweeping 就是这一机制。Amount preparation(数量准备) 则是填入那些依赖链上状态的值。 本页两者都涵盖。它们位于编译流水线的两端(完整序列参见 Execution Model)。

终端资源

当 flow 中没有任何 op 消费某个资源时,该资源就是终端的。有两种到达此状态的方式:
  • 产出型 op 的输出没有其他东西绑定。lifi.zap 存入某个 vault 得到的凭证代币,如果没有后续节点消费它,就是终端的。
  • 悬空的 split 输出。core.split 产生 { a, b } 而只有 a 被下游绑定时,b 就是终端的。关于这被有意为之的典型情形,参见 dust-sweep recipe
编译器在构建时通过遍历绑定图来识别终端资源。每个终端资源都会出现在响应的 producedResources 中,并有资格被 sweep。关于资源生命周期,参见 Resource Model

sweepTo 策略

run.sweepTo 字段告诉后端在执行结束时将终端资源发送到哪里。有三种形式:
  • builder.context.sender Sweep 到交易签名者。大多数 flow 使用这种方式。在线格式上,它序列化为 { "$ref": "context.sender" }
  • 一个字面地址(例如 '0xRecipientAddress')。Sweep 到一个特定地址。当 flow 的目的是把代币交付给签名者以外的某人时使用。
  • 省略。 不 sweep。终端资源留在每个签名者独立的执行 proxy 上。这种情况很少见,主要用于那些有意在多次提交之间于 proxy 上累积余额的 flow。
当设置了 sweepTo 时,编译器会为每个终端资源追加转账指令。这些转账在所有 op 调用之后、并在任何附加的 guard 断言完其不变量之后运行。

什么会被 sweep,什么不会

  • 会: 所有终端资源,包括悬空的 split 输出,以及产出型 op 中无人消费的输出。
  • 不会: 被另一个 op 消费的资源(它们是那个 op 的输入,不是残余)。Handles(类型化标量)。它们不是余额,永远不会被 sweep。
如果某个输入 materialiser 供应的资源多于消费型 op 所用的量,那么未使用的余量也是一个终端资源。这正是让 dust-sweep 起作用的原因:core.split 对一个输入进行分区,只有一个分区被消费,而未被绑定的分区自然变成了终端资源。

不可转移的终端资源

sweepTo 是一个兜底项:一旦设置,它会尝试把每一份残余的 proxy 余额转移到目标地址。大多数代币可以自由移动,但某些终端资源是被绑定的,无法被转出 —— 最常见的是支撑一笔未平仓债务的 aToken(或其他抵押品凭证)。如果移动该 aToken 会使仓位处于抵押不足状态,Aave 的 finalizeTransfer 就会回滚,因此对这样的余额进行一次一刀切的 sweepTo 会使整笔交易回滚 当一个 flow 有意在 proxy 上保留一个仓位时 —— 例如一个保持抵押品在位的借款、杠杆或债务迁移 flow —— 不要设置一刀切的 sweepTo。把抵押品留在持久化的、每个签名者独立的 proxy 上,只对那些真正松散的代币发出显式转账:用 core.balanceOf(owner 默认为 proxy)读取每一份,再用 core.transfer 移动它。
Aave→Morpho 债务迁移 recipe 正是使用了这一模式。

数量准备

大多数数量在构建时是已知的。directDeposit({ amount: '1000000000000000000' }) 供应一个字面值。有些数量只能通过在执行时读取链上状态才能得知:
  • balanceOf materialiser。 在消费某个代币之前,读取 proxy 当前对该代币的余额。
  • call materialiser。 调用一个 view 函数,并将其返回值用作输入数量。
  • Vault 份额价格读取。 当某个下游 op 需要知道”该存入产生了多少份额代币”时,服务会在生成 calldata 之前,从当前链上状态解析该数量。
数量准备步骤(执行模型流水线的第 4 步)会解析这些。它在运行时输入解析之后、降级之前运行。失败会以 preparation_error(HTTP 422)的形式暴露,通常是因为链上读取返回了 0 或发生了回滚。

响应上的模拟数量

模拟之后,每个终端资源都有一个已知的模拟数量。编译器将它们作为 ComposeCompileResult 上的 producedResources 返回:
每一条都携带该资源自身的元数据(kindtokenchainIdowneravailability)以及一个 simulated 块:
  • simulated.amountOut 在当前链状态(最新区块)下的确切模拟数量。在你的 UI 中把它显示为”你将收到 ≈ X”。
  • simulated.amountOutMin 给定附加到该资源输出 port 上的滑点 / 最小输出 guard,得到的最坏情况数量。把它显示为”你将至少收到 X”。当没有附加最小输出 guard 时(例如没有滑点保护的 split 产生的粉尘),amountOutMin 可能等于 amountOut;请检查响应,而不要假定它被省略。
  • 多个 guard。 当多个 guard 针对同一个 port 时,最紧的最小值胜出(最大的 simulated.amountOutMin)。
关于 amountOutMin 如何从某个 guard 的容差推导而来,参见 Guards

另请参阅