现代DEX - 1inch限价订单协议、Fusion和Fusion+ 是如何构建的

  • mixbytes
  • 发布于 2024-12-25 16:53
  • 阅读 28

本文深入探讨了1inch的Limit Order Protocol,Fusion和Fusion+协议,着重分析了其原理、实现及在去中心化金融中的应用,尤其是在订单填充、跨链交换和荷兰拍卖等方面的创新和技术细节,提供了深刻的技术见解和丰富的实例说明。

简介

本文将继续我们关于现代去中心化交易所(DEX)的系列介绍。今天,我们将重点关注1inch的另一个DEX生态系统。我们将回顾三个1inch协议,因为它们都是相互构建的:限价订单协议(Limit Order Protocol)和Fusion/Fusion+协议。之前,我们已经评审了一系列“直接兑换”协议,例如 Uniswap V4Balancer V3Fluid DEX,现在我们将重点关注“基于订单”的项目。我们上一篇文章讨论了 CoW Swap

1inch限价订单协议(LOP)在本系列中继续采用“基于订单”的方法。限价订单是一种兑换订单,不仅包含代币的输入和输出金额,还包括额外的条件,如订单截止日期、目标价格限制或外部调用的数据。这允许实现几乎任何程序化逻辑,以确定一个订单是否可以被解决者(resolver)执行。这些订单在以下情况下极为有用:“如果市场在接下来的10天内达到这个价格则执行交易”或“为我的订单发起荷兰拍卖”,等等。LOP中的特殊“模式”和订单类型也可以在其他协议中使用,以实现高级DeFi机制,如拍卖和跨链兑换。

此外,“基于订单”的兑换方法可以降低费用,并提供出色的用户体验,使用户能够绕过原生代币的费用。此外,由于Active使用了离链签名,通过离链解析者处理订单,而仅在链上进行“结算”逻辑,因此“基于订单”的兑换解决方案对于跨链兑换非常有用。在这些情况下,我们可以在订单验证逻辑中包含对外部链的“资金提供证明”的检查,从而为单链和跨链兑换维护类似的基础设施。

好了,不再说标准营销话术了,让我们进入代码吧!

Limit Order 限价订单协议

核心

该协议的主要代码库在 这里,核心功能存在于 OrderMixin.sol 合约中。

限价订单协议(LOP)最重要的方面是订单的验证。用户(创建者)创建一个订单,设置限价条件,签署该订单并发布。接单者(taker)接受此订单,验证它并通过提供所需的资产量来履行它。接单者无法在链上填充订单,除非满足所有创建者的要求,该过程将在订单履行过程中在链上进行验证。在创建订单时,创建者可以:

  • 设置订单的到期日期。
  • 指定接收地址(如果不同于创建者的地址)。
  • 指定特定接单者的地址(用于私密订单)。
  • 选择代币授予的批准方法(approve、permit、permit2)。
  • 允许或不允许部分和多次填充。
  • 在兑换之前或之后添加ETH/WETH的包裹/解包。
  • 定义必须在订单执行之前满足的条件(例如,止损、止盈订单)。
  • 在订单填充之前和之后,指定与创建者代码之间的任意交互。
  • 为订单设置一个“nonce或epoch”,允许通过epoch或nonce批量使订单失效。这对于希望通过更改epoch同时使一组订单失效的dApp来说是必需的,并将这些dApp分开。
  • 定义用于转移资产(如ERC-721或ERC-1155)或进行多资产兑换的自定义代理。
  • 定义功能,以动态计算在链上为创建者和接单者资产的汇率,允许进行荷兰拍卖或区间订单(取决于已填充的交易量)。
  • 任何其他可编程逻辑

此功能不仅允许直接使用LOP进行交易,还允许其作为其他协议的结算层,特别是那些在结算之前处理离链订单的协议。

该协议的主要实体是 Order 结构体。订单标识符(bytes32 orderHash)在大多数与订单相关的功能中使用,是通过对其序列化的内存表示进行 哈希 确定性的推导出的。关键字段当然是出单者/接单者资产和金额。此外,还有一个特殊的salt参数用于维护附加限价订单数据的完整性,我们稍后会提到。订单的附加条件编码于 MakerTraits 结构体中,该结构体在MakerTraitsLib.sol中描述,见 这里

所有这些功能的附加数据位于订单的 扩展 中 - 这是一组附加数据,伴随着订单。它不包含在“基础”订单结构中,但通过salt参数(其中包含扩展内容的哈希)与相应订单在密码学上是连接的。扩展与订单一同处理,包含不同类型的附加数据,如额外外部调用的参数。

还有一个接单者参数的 TakerTraits 包,创建者会在填充订单的过程中将其添加。我们将稍后进行审查。现在,让我们继续进行填充订单的过程,因为这是协议的关键方面,通过了解这一点,我们可以轻松审查填充订单所需的协议的其他部分。

结算订单

用于结算订单的函数有四个(描述见 这里),它们的操作类似,但在传递的离链签名和额外参数上有所不同。

fillOrder()和 fillOrderArgs() 检查 用户设置的离链签名集 (v, r, s)。fillOrder() 向接单者传递 args(扩展),而 fillOrderArgs() 则 设置 附加参数。

fillContractOrder() 和 fillContractOrderArgs() 在接单者参数(扩展)上工作类似,但检查 bytes calldata 签名,这是合同签订时按照 EIP-1271 创建的签名。

这两个函数组接下来分别调用 _fillOrder() 或 _fillOrderContract(),分别执行 “一级” 检查, 检查 剩余金额(如果订单允许部分填充),并尝试 应用 创建者的权限(用于用户签名的订单)。

两个函数组均以调用主要的 _fill() 函数结束 - 我们的主要目标。

首先,进行了一系列不同的 验证。我们首先检查扩展的 有效性,使用 isValidExtension() 函数,检查扩展内容是否与订单的盐参数相关联,以保护创建者免受接单者对扩展参数的可能更改。

接下来是与订单发送者和过期参数相关的 一系列 检查。

之后是 谓词检查 - 一个由创建者定义的外部调用,用来确定是否可以履行该订单。它使用 _staticCallForUint() 函数对目标进行静态(!)调用,该调用必须返回 “1” 才能继续处理订单。此功能允许创作者对其订单添加几乎任何“限”条件,并按照他们的意愿配置限价订单条件。

下一个部分是处理创建者和接单者的金额。与DEX类似,有 条支路:一条是接单者使用“确切的创建者金额”(类似于 Uniswap 的 swapExactIn()),另一条是“确切的接单者金额”(类似于 swapExactOut())。两个支路都计算出创建者和接单者的目标金额( 这里这里),并检查是否超过 TakingAmountTooHigh 或 MakingAmountTooHigh 的阈值。选择 TakerTraits 中走哪一条支路是接单者的责任,使其能够选择如何填充订单。

最后,进行 检查,以确保金额非零,并且如果订单不允许部分填充,则保证金额对应于完整的订单金额。

然后,通过 BitInvalidator 检查 失效状态。此功能允许创建者为给定的创建者地址设置 失效位图,使其能够使同一创建者所做的其他订单失效。例如,来自一个包的一个已填充订单可以使其他所有订单失效,从而实现“从一个包中填充一个订单”的策略。BitInvalidator 可以使用 nonce 标识符对订单进行分组,或者采用创建者的 epoch 概念,允许交易服务在一个 epoch 中发布多个订单,然后移动 epoch,失效所有“旧”订单,并在下一个 epoch 中继续。有关此类失效的更多信息,可以参考 这里

接下来,根据 剩余 创建者的金额,对订单进行失效,这很简单。

然后是 预交互(如果在 MakerTraits 中设置)。在这里,相关数据从订单的扩展中加载,并调用监听器地址(如果提供),否则调用创建者的地址。我们将检查不同的扩展交互,但在这一部分,我们可以查看 preInteraction() 函数,这简单提供所需的批准。

接下来是 转移 资产从创建者到接单者。这可能涉及到 WETH 的包裹/解包,并使用两种转移方法:

  • 基于 Permit2(详细信息见 这里
  • 基于 transferFrom,但可以选择添加后缀到 transferFrom 参数(如果在扩展中设置),实施 这里。此后缀用于非标准转移,例如 ERC-721 转移,需要将 tokenId 添加到参数中。

然后 进行 TakerInteraction,允许接单者在接收资产后添加任何所需的结算动作。其接口在 这里 中声明。

接下来, 转移 资产从接单者到创建者(或创建者设置的接收者)。这包括处理 WETH 的包裹/解包操作,使用两种转移方式:基于 Permit2 和带后缀的 transferFrom (与前面从创建者到接单者的转移相似)。

最后,后续交互 在订单填充之后,工作与上述描述的预交互相似。

另一种“填充”订单的方法是其取消,实现在 这里。在 BitInvalidator 的情况下,它失效位图范围,使所有同一 epoch 或 nonce 的订单失效。在其他 情况下,它将订单标记为完全填充并使其失效。

订单已填充,所有条件已检查,并转移了代币。现在是时候查看创建订单的过程以及使用 LOP 的协议。

订单的创建

让我们创建不同类型的订单。这部分在 这里 有很好的解释。如前所述,基本的限价订单功能,如定义接单者/创建者资产和截止日期的确切金额,实际上可以相对容易地实现,而不需要额外的扩展。创建和填充多种类型限价订单的展示在测试中表现得非常好;你可以开始从 这里 审查不同案例,继续探索不同的案例。我们不能描述所有的测试,所以我们将专注于一些说明性的例子,但检查许多测试以理解不同的 MakerTraits 和 TakerTraits 的工作原理是非常有益的(别忘了参考 docs 以获得每个选项的描述)。

一些好的“基础”测试包括:全填充两个订单 这里、一个与 WETH/ETH 的示例 这里、部分兑换 这里、与提款的测试包 这里 以及 permit2 这里。另一个重要部分是基于不同失效逻辑检查订单的取消,在这一测试分支中被探讨 这里

LOP 最强大的方面毫无疑问是扩展的使用,将外部调用数据“注入”到订单填充过程中。让我们描述一下扩展的一些特性,文档可以在 这里 找到。例如,它允许使用 MakerAssetSuffix 和 TakerAssetSuffix 来“修改” transferFrom() 调用,使限价订单可以转移不仅是普通的 ERC20 代币,还可以是其他类型的代币,如 ERC721(其在“转移”功能中需要添加 tokenId)。这样的订单示例见 这里,我们在 erc721proxy 中 批准 tokenId=10,然后将 后缀 添加到订单的扩展中,使得 ERC721代币可以从创建者和接单者地址进行转移。

费用

限价订单协议没有协议费用。为什么?1inch LOP 的主要目标之一是以最低的 gas 消耗处理订单,而任何额外的费用都需要额外的代币操作,这会使用户和解析者的兑换成本增加。费用处理的复杂性还会使任何可编程逻辑(如拍卖、部分填充订单等)变得复杂,给用户和解决者增加更多风险。

这种方法并不排除费用的实施,因为dApp在使用LOP时总可以使用代币代理、附加扩展功能并实施他们希望的任何逻辑。

实施细节

LOP具有暂停/恢复 机制,关于 LOP 的链上治理只有这些,因为没有比率、费用和其他治理元素。1inch治理的主要功能是管理解决者,这通过1INCH代币和源自1INCH代币余额的独角兽权力来执行,如 这里 所述。

实现的一个有趣方面在于 PredicateHelper.sol,其中实现了执行逻辑和代数比较的函数,使用静态调用的结果与任意数据。这对于谓词检查是必要的,例如检查某个 oracle 价格是否 大于 某个值(代数“>”),或 至少有一个 静态调用返回真(逻辑“或”)。该模块对构建可编程限价订单非常有用。另一个功能是能够 模拟 一些代理调用,执行并回滚,返回结果。

查看现有的 扩展 对于 LOP 很重要,因为它们包含大量有用的逻辑。例如,MelancholicAuctionCalculator 在Fusion协议中ERC721Proxy,允许 LOP 订单操作 NFT、FeeTaker,用于向订单添加费用等许多其他。

Fusion协议

现在,在审查了限价订单协议后,我们将继续审查使用该协议实施额外功能并扩展 LOP 能力以解决不同 DeFi 问题的协议。接下来要审查的协议是 1inch Fusion,它为 LOP 提供了特殊的订单类型,允许接单者参与荷兰拍卖(第一个以不断降低价格出价者将被接受)。这在 这篇文章 中有高级别的描述。

我们已经看到了扩展的功能,它允许为订单设置可编程条件,而荷兰拍卖是实现这种逻辑的一个绝佳示例。荷兰拍卖本身对于 DeFi 是一个非常有用的原语,因为所需的互动最少,常规拍卖与多个参与者的多个出价不太有效。使用荷兰拍卖允许用户快速响应他们的请求,并让投标者相互竞争,而无需对协议进行多次互动。我们已经在 DeFi 项目中看到了荷兰拍卖,例如 AjnaOrca

这个仓库 中可以找到创建 Fusion 限价订单的功能,其中我们可以找到 FusionOrder ,其中感兴趣的字段是 auctionDetails。它的内容然后被 传递 到 FusionExtension 的构造函数。auctionDetails 包含荷兰拍卖参数,编码一个基于拍卖开始后经过时间的价格函数。在 Fusion 协议中,不仅可以设置价格降低的“速度”,还可以通过多个点对该函数进行编码,从而使其成为分段线性函数(并且当然有下限限制)。Fusion 拍卖参数的编码在 这里 中描述,其中包括开始时间、持续时间、信号率和一个增加价格不变区域的点数数组。拍卖参数的另一个重要部分是与气体相关的参数,包括订单开始时的Gas成本和气体估算,用于覆盖订单填充者的Gas成本。

你可以通过在 此视频 中查看使用 fusion-sdk 与 Fusion 订单一起工作的过程,展示如何将 Fusion 订单功能集成到任何 DeFi 前端。与 Fusion 订单相关的另一个有用代码是荷兰拍卖计算器 扩展

但这并没有结束;Fusion 订单和荷兰拍卖的使用打开了 1inch 协议的另一个扩展 —— 跨链兑换,由协议解析者执行。那么,让我们看看下一个协议。

Fusion+协议

下一个要审查的协议与跨链兑换相关,并继承相同的“基于求解者”的架构,用户在其中下达跨链兑换订单,解析者执行它们,使用源链的 LOP Fusion 订单数据结算目标兑换金额。用于兑换的目标解析者的选择通过荷兰拍卖中描述的“Fusion”模式进行。不同之处在于结算过程,现在需要“另一网络中的资金解锁证明”。这是如何实现的?

Fusion+ 的关键算法是原子兑换,这是一个基于“哈希锁定”的知名协定,用于在两个网络之间兑换资产,支持通过呈现哈希的预像来解锁资金。揭示此预像将允许在两个网络中解锁资金。它几乎适用于任何区块链,包括比特币和其他基于 UTXO 的区块链,且可以轻松实现。原子兑换是一个无需信任的安全算法,仅需要两个没有相互信任的各方,因此似乎是 DeFi 的理想解决方案。然而,基于原子兑换的项目并未获得普及,因为基于哈希锁定的交互需在兑换两端执行至少两笔交易(对于两方在两个网络中的“承诺”和“揭示”),在兑换过程中存储密钥值,而且包含一个时限窗口以确保兑换安全。因此,大多数用户更喜欢具有更好用户体验的解决方案,快速完成一笔交易:资产保管或共识桥。

1inch 的限价订单协议允许实施时间和哈希锁定,具有额外的中继服务和兑换解析器。这些元素的结合使得构建基于原子兑换的协议变得非常方便。该协议在 白皮书 中解释得很好,强烈建议阅读,尤其当你对原子兑换不熟悉时。在 Fusion+ 中,使用来自 Fusion 协议的订单进行兑换,但流程比 Fusion 协议复杂,因为跨链兑换需要解析者执行“预兑换”操作:在目标网络中放置“哈希锁定”资金。在源网络中执行同样的“哈希锁定”放置通过 Fusion 订单中的后续交互完成。该方案中的中继服务检查两侧哈希锁定的有效性,并保证需要的密钥值将被揭示。

让我们通过示例回顾兑换步骤(从简化形式的白皮书中收取),以阐明其工作方式和与 LOP 和 Fusion 的关联。创建者将 100 个 1INCH 代币在以太坊上兑换为 200 个 BNB 代币在 Binance Chain 上:

第一阶段:(创建者,中继者)

  1. 创建者创建、签名并发送一个带有哈希(密钥)权限的 Fusion 限价订单给中继服务,授权支出 1INCH 在 Ethereum 上。

  2. 中继者与所有解析者共享此订单,启动 1inch 服务中的荷兰拍卖。

第二阶段:(中继者,接单者)

  1. 接单者(赢得拍卖的解析者)将 100 个 1INCH 代币从用户转入以太坊的托管合约中。这包括原子兑换参数:哈希(密钥)、定时锁定值和接单者/创建者地址(需要创建者地址以防出现问题而返回资金)。

  2. 接单者在 Binance Chain 中为创建者存入 200 个 BNB 代币到托管合约,提供相同的原子兑换参数和地址。此外,接单者提供了“安全存款”,如果兑换不在给定时间内完成,将被没收。

第三阶段:(中继者,接单者)

  1. 中继者确认两个托管合约都已创建,且两个网络中的链上最终性已达成。

  2. 创建者向中继者(和所有解析者)披露秘密。

  3. 接单者在以太坊中为自己解锁 100 个 1INCH,揭示密钥哈希的预像。

  4. 接单者在 Binance 链中使用相同的密钥值为创建者解锁 200 个 BNB。

这是一个“都好”的方案,但原子兑换的问题在于过程可能会被中断,这并不能窃取资金,但可能会将用户的资金锁定很长时间,迫使用户手动提取。因此,该协议不得不包含恢复程序,以应对中断过程的情况:

第四阶段:恢复:

  • 定时锁超时,锁定资金未被转移;双方均未收到指定资产。
  • (其他)解析者将 100 个 1INCH 代币转回创建者。
  • (其他)解析者将 200 个 BNB 代币转回接单者。
  • (其他)解析者没收不幸接单者提供的安全存款。

托管合约

现在,让我们查看链上的代码;用于跨链兑换的代码库在 这里。托管合约通过工厂部署,如我们所记得的,我们需要部署两个托管合约 - 一个到源链,一个到目标链。

我们从通过 createDstEscrow() 函数在目标链上部署托管合约开始。首先,对安全存款或者安全存款 + 目标金额(以防目标代币是原生代币)进行 检查。然后,对定时锁值的正确性进行 检查,随后进行 托管合约的部署相关代币的转移。必须注意的是,托管合约地址是根据所有托管参数使用 CREATE2 和 salt 确定性推导的(用于部署的 cloneDeterministic() 函数在 这里 中进行了描述) - 我们稍后会再看这个事项。因此,目标和源链的托管地址可以通过 addressOfEscrowSrc()addressOfEscrowDst() 函数轻松计算得出。接下来,让我们部署源链的托管合约。它是通过 _postInteraction() 使用不同的工作流程进行部署的,该工作流程由在荷兰拍卖中获胜的解析器添加到 Fusion 订单中,并且现在在源链上填充 Fusion 订单。首先,我们需要 调用 从 Fusion 订单“继承”来的后续交互,然后需要设置 hashlock。在常规订单中,hashlock 的值是 提供的,但是在允许多次填充的订单中,我们不能披露“完整”的秘密。这部分在 白皮书 的“2.5 部分填充与秘密管理”部分中进行了描述。具有多次填充的订单有多个秘密,每个秘密对应下一个可以填充的部分。例如,如果我们有一个可以拆分为 4 个部分的订单(每部分 25%),我们就会有 4 个秘密,每个秘密“打开”全订单金额的下一个 25%,如果最后一部分没有完全填充,则使用 +1 个秘密。这些秘密在 Merkle 树中组合,对于这个特定的托管, 我们 从树中获取 hashlock。Merkle 树的值和无效化逻辑与 Merkle 证明处理在 MerkleStorageInvalidator.sol 中实现,见 此处

在源链上的部署以 同样的方式 执行,如用于目标链,并且带有所有参数的“盐”。最后,会 执行 对托管地址所需余额的额外检查。

下一步是托管合约本身。核心功能位于 EscrowSrc.sol,非常简单。大部分代码由限制 地址时间 和当然还有 hashlock 组成。这种简单性至关重要,因为这些托管合约的部署成本非常低,并且可以轻松地在任何非 EVM 区块链上实现,包括比特币,这里首次引入了原子兑换。关于 hashlock 的比特币脚本示例请见 这里 – 很简单,不是吗?

这意味着 Fusion+ 可以使用相同的基础设施、Fusion 订单、荷兰拍卖以及竞争解析器在许多不同网络之间进行兑换,尽管这些网络具有完全不同的可编程逻辑,包括提供强安全保障的 ZK 环境,确保原子兑换。

实现细节

值得一提的是在 Fusion+ 中托管合约的部署。由于我们为每笔交易部署这些合约,因此这些操作的最小气体消耗在协议中起着关键作用。常规方案需要部署一个新合约并在存储中初始化参数,但这太昂贵。替代方案是使用“带不可变参数的克隆”(请查看该 仓库),其中部署了一个极简透明代理( EIP-1167),将参数作为不可变值直接存储在代理的字节码中。然后,这个代理只是将这些值附加到对实现的每个调用上,从而使实现能够从 calldata 中读取这些值,从而避免任何存储操作。

Fusion+ 使用将参数通过代理传递到实现的思路,但进一步努力尝试避免甚至在代理的字节码中存储它们,因为每个字节的存储成本为 200 gas,而我们需要数百字节来存储所有参数。因此,唯一可行的选择是将相同的不可变参数与每个(!) 调用传递给代理->托管。对于部署的合约被积极使用的情况,增加交易大小并不是最佳解决方案。然而,在 Fusion+ 的情况下,对托管合约的调用次数非常少(部署和取回)。这种方法完美地工作,因为它导致非常紧凑的字节码,并且仅涉及两个包含所有必要参数的调用,这些参数不能被颠覆。这就是为什么在托管合约中,所有函数( 示例)都包含 Immutables calldata immutables 参数。当然,我们需要确保所有对托管的调用包含相同的 immutables。在这里,Fusion+ 使用了一个有趣的技巧 - 所有调用都执行唯一有效不可变参数的检查,这利用了最小代理的 CREATE2 地址(记住,这个地址是从这些不可变参数中确定性推导出来的)来 检查 不可变参数的有效性。这种方法为托管部署操作节省了数万个额外的 gas。

结论

1inch 限价订单协议和 Fusion/Fusion+ 协议是 DeFi 中组合性的美丽例子。第一级 LOP 提供了一个带有安全保障的订单结算层,Fusion 通过荷兰拍卖扩展这些订单以供解析器使用,而 Fusion+ 将 Fusion 订单扩展到使用原子兑换模式的跨链兑换。

1inch 协议旨在与多个 dApp 集成,这些 dApp 可以轻松使用其 API 来获取用户的限价订单并实施各种不同的 DeFi 策略,创建几乎任意灵活性的订单。LOP 没有任何协议费用,治理功能主要与协议改进和解析器管理相关。对于 DeFi 开发者和审计员来说,绝对值得研究。

我们下一篇文章再见!

  • MixBytes 是谁?

MixBytes 是一个由专家区块链审计员和安全研究人员组成的团队,专注于为 EVM 兼容和基于 Substrate 的项目提供全面的智能合约审计和技术顾问服务。加入我们,关注 X,以便及时了解最新的行业趋势和见解。

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

0 条评论

请先 登录 后评论
mixbytes
mixbytes
Empowering Web3 businesses to build hack-resistant projects.