NFT:OpenSea 新协议 Seaport 源码解析_FYZNFT

一、Seaport简介

近期,NFT市场OpenSea宣布推出全新Web3市场协议Seaport协议,用于安全高效地买卖NFT。本文将深度分析其关键业务实现和接口实现。SeaPort官方文档https://docs.opensea.io/v2.0/reference/seaport-overview,可配合查阅,进一步加深理解。

Uniswap用开源去中心化交易改变了加密货币交易的游戏规则,这是我们现在所知的2020年DeFiSummer的开始,也带来了DEX和DeFi的大规模增长和创新。OpenSea的新协议Seaport或许也有改变NFT交易游戏规则的潜力,这也是我们分析Seaport协议的原因。

Seaport是一个市场合约,用于安全有效地创建和执行ERC721和ERC1155代币的订单。每个订单包含任意数量的供应商愿意提供的物品以及任意数量的必须连同其各自的接收者一起接收的物品。Seaport协议的6大关键点,以及它对NFT领域的意义:开源代码:有了Seaport协议,任何人都可以使用该协议构建一个NFT市场,因为它是去中心化和开源的。在未来几年,我们应该会看到更多的NFT市场建立起来。更多的竞争=更好+更快的创新去中心化:OpenSea说这个协议没有合约所有者,任何人都可以更新或生成代码。交易新范式:与一些平台只能用加密货币换取NFT不同,Seaport协议允许用户以一系列新方式获取NFT,投标人可以捆绑不同的资产以换取NFT。交易特定的NFT:当交易NFT时,你也可以设置NFT必须具备的特定“条件”。荷兰式拍卖列表:在Seaport协议中,你可以设置一个开始和结束价格,表明你希望拍卖持续多长时间。该列表将降低价格,直到找到买家。更高的安全性:OpenSea正在进行为期两周的协议审计竞赛,奖金总额为100万美元。任何开发人员都可以审核代码,提交他们发现的评审和错误,并获得奖励。

二、关键业务实现

1、NFT订单

每一个订单都包含11个关键组件:

offerer订单的报价者提供了所有的供应代币并且必须亲自执行订单或者通过签名或列举链上订单来批准订单。

zone订单的区域是附加到订单的可选辅助帐户,具有两个额外的权限:

该区域可以通过调用cancel来取消命名为该区域的订单。。

“受限”订单必须由区域或报价者执行,或者必须通过调用区域上的isValidOrder或isvalidOrderIncludingExtraData视图函数来获得批准。

offer报价包含可以从报价者帐户转移的一系列代币,其中每个代币由以下组件组成:

itemType指定代币类型,有效类型包括Ether、ERC20、ERC721、ERC1155、ERC721、有“条件(criteria)”的ERC721以及有“条件(criteria)”的ERC1155。

token指定代币合约的账户地址。

identifierOrCriteria表示ERC721或ERC1155代币标识符,或者在基于条件的代币类型的情况下,表示由代币的有效代币标识符集合组成的merkle根。对于Ether和ERC20类型,该值会被忽略,并且对于基于条件的代币类型,可以将值设置为0以允许任何标识符。

startAmount表示如果在订单激活时完成订单所需要的相关代币的数量。

endAmount表示如果在订单到期时执行订单所需要的相关代币的数量。如果此值与startAmount不同,则根据订单激活后经历的时间线性计算出实际的数量。

consideration包含为完成订单而必须接收的代币数组。它包含所有与所提供代币相同的组件,并且还包括一个用于接收每个代币的recipient组件。该数组可以由执行者在订单执行时进行扩展,以支持“小费”。

orderType订单类型,根据两个不同的偏好,指定订单的四种类型之一,:

FULL表示不支持部分填充,而PARTIAL允许填充订单中的一部分,注意每个代币必须被提供的分数完全整除。

OPEN表示任意账户都可以提交执行订单的调用,而RESTRICTED则需要订单必须由报价者或订单所在区域执行,或者在区域上调用isValidOrder或isValidOrderIncludingExtraData视图函数时返回表示订单被批准的神奇的值。

startTime表示订单激活时的区块链时间。

endTime表示订单到期的区块链时间。该值与startTime与每个代币的startAmount和endAmount一起使用以得出它们的当前数量。

zoneHash表示一个任意的32字节值,当执行受限订单时,该值将提供给区域,该区域在确定是否是授权订单时可以使用该值。

salt表示订单的任意熵源。

conduitKey是一个bytes32类型的值,表示在执行转移时应将哪个渠道(conduit)用作代币批准的来源。默认情况下,报价方将直接向Seaport授予ERC20、ERC721和ERC1155代币批准,以便它可以在执行期间执行订单指定的任何转移。相反,选择使用渠道的报价者将授予与提供的渠道密钥相对应的渠道合约的代币批准,然后Seaport指示该渠道转移相应的代币。

nonce表示必须与给定报价者的当前随机数匹配的值。

2、订单执行

订单通过以下4种方式中的一种来执行:

调用两个“标准”函数fulfillOrder和fulfillAdvancedOrder中的一个,并且构造第二个隐含订单,同时其调用者作为报价者(offerer),已执行订单的对价(consideration)作为报价(offer),已执行订单的报价作为对价。所有报价代币将从订单报价者转移到执行者,然后所有对价代币将从执行者转移到指定的接收者。

调用"基本"函数fulfillBasicOrder,并提供六种基本路线类型中的一种,将从组件子集派生要执行的订单,假设相关订单符合以下条件:

该订单仅包含一个报价代币,并且包含至少一个对价(consideration)代币。

该订单仅包含一个ERC721或ERC1155代币,并且该代币不是基于条件的。

订单的报价者是第一个对价代币的接收者。

所有其他代币都具有相同的以太币或ERC20项目类型和代币。

该订单不提供以以太币作为其项目类型的项目。

每个项目上的startAmount必须与该项目的endAmount匹配。

所有“忽略”的项目字段都设置为空地址或零。

如果订单中有ERC721项目,则该项目的数量为.1

如果订单有多个对价(consideration)项目,且除了第一个对价项目以外的所有对价项目与报价项目的项目类型相同,报价项目数量不小于除了第一个对价项目数量外的所有对价项目数量之和。

调用两个“可用执行”函数中的一个,并且提供一组订单与一组执行声明,其中的执行声明指定哪些报价项目可以聚合到不同的转移中,相应地哪些对价项目可以聚合在一起,以及其中已经取消的订单是因为时间无效,或者已经完全成交的订单将被跳过,而不会导致其余可用订单回滚。此外,一旦锁定maximumFulfilled可用订单,剩余的所有订单都将被跳过。与标准执行函数类似,所有报价项目将从各自的报价者转移到执行者,然后所有对价项目将从执行者转移到指定的接收者。

调用两个“匹配”函数中的一个,并且提供一组明确的订单以及一组执行,该执行指定了哪些报价项目应用于哪些对价项目。请注意,以这种方式执行的订单没有明确的执行者;相反,Seaport将简单地确保每个订单的需求一致。

虽然标准方法在技术上可用于执行任何订单,但在某些情况下存在关键的效率限制:

与简单的“热路径(hotpath)”的基本方法相比,它需要额外的调用数据。

它要求执行者批准每个对价项目,即使对价项目可以使用报价项目来执行。

它可能导致不必要的转移,而在“匹配”情况下,这些转移可以减少到更小的集合。

3、检查余额和批准交易

创建报价时,应检查以下要求以确保订单可以执行:

报价者应在所有报价项目中有足够的余额。

如果订单未指明使用渠道,则报价者应为所有提供的ERC20、ERC721和ERC1155项目的Seaport合约设置足够的批准。

如果订单确实指明了使用渠道,则报价者应为所有提供的ERC20、ERC721和ERC1155项目的相应渠道合约设置足够的批准。

执行基本订单时,需要检查以下要求以确保订单可以执行:

需要执行上述检查以确保报价者仍有足够的余额和批准。

执行者应该对所有对价项目有足够的余额,除了那些项目类型与订单提供的项目类型相匹配的项目——例如,如果执行的订单提供ERC20项目,并且要求向报价者提供ERC721项目并且向另一个接受者提供相同的ERC20项目,那么执行者需要拥有ERC721项目,但不需要拥有ERC20项目,因为它将来自报价者。

如果执行者不选择使用渠道,他们需要为已执行订单上所有的ERC20、ERC721和ERC1155对价项目设置足够的Seaport合约批准,项目类型与订单提供的项目类型匹配的ERC20项目除外。

如果执行者确实选择使用渠道,则他们需要为已执行订单上的所有ERC20、ERC721和ERC1155对价项目为其各自的渠道设置足够的批准,项目类型与订单提供的项目类型匹配的ERC20项目除外.

如果已执行的订单将以太币指定为对价项目,则执行者必须能够将这些项目的总金额提供为msg.value

执行标准订单时,需要检查以下要求以确保订单可以执行:

需要执行上述检查以确保报价者有足够的余额和批准。

在收到所有的报价项目后,执行者应该对所有的报价项目有足够的余额——例如,如果执行的订单提供了ERC20项目,并且需要向报价者提供ERC721项目,并且向另一个接收者提供相同的ERC20项目,其数量小于或等于提供的数量,执行者不需要拥有ERC20项目,因为它将最先从报价者处接收到。

如果执行者不选择使用渠道,他们需要为已执行订单上的所有ERC20、ERC721和ERC1155对价项目的Seaport合约设置足够的批准。

如果执行者确实选择使用渠道,则他们需要为已执行订单上的所有ERC20、ERC721和ERC1155对价项目其各自的渠道设置足够的批准。

如果已执行的订单将以太币指定为对价项目,则执行者必须能够将这些项目的总数量提供为msg.value

在执行一组匹配订单时,需要检查以下要求以确保订单可以执行:

作为执行的一部分执行,执行采购ERC20、ERC721或ERC1155项目的每个帐户必须在触发执行时在Seaport或指定的渠道上具有足够的余额和批准。请注意,先前的执行可能会为后续执行提供必要的余衡。

涉及以太币的所有执行的总和必须以msg.value的形式提供.请注意,提供者和接收者是同一帐户的执行将从最终执行集中被过滤掉。

部分成交

在构建订单时,报价者可以选择通过设置适当的订单类型来启用部分成交。然后,支持部分执行的订单可以在相应订单的某一部分中执行,从而允许后续执行绕过签名验证。总结一下部分填充的几个关键点:

当创建支持部分成交的订单或确定这些订单要成交的部分时,订单上的所有项目数量必须能被提供的部分项目数量完全整除。

如果要填写的所需部分会导致要填写的订单数量超过全部订单金额,则该部分将减少为剩余要填写的数量。这适用于部分填充尝试和完全填充尝试。如果不需要这种行为,则执行者可以使用“基本”订单方法,或使用“匹配”订单方法,并明确提供一个要求收到全部所需金额的订单。

举例来说:如果一个执行者尝试执行订单的1/2,但另一个执行者首先执行订单的3/4,则原始执行者最终将执行订单的1/4。

如果部分可成交订单上的任一项目指定了不同的startAmount和endAmount,则在确定当前价格之前,该分数将应用于这两个数量。这确保了在构建订单时可以选择完全可分的金额,而不依赖于最终完成订单的时间。

部分成交可以与基于条件的项目进行组合,以支持构建提供或接收多个项目的订单,否则这些项目将无法部分成交。

举个例子:报价者可以创建一个部分可成交的订单,为给定集合中最多10个ERC721项目提供最多10个ETH;然后,任何执行者都可以执行该订单的一部分,直到它被完全执行。

5、业务关键步骤

5.1执行订单

当通过fulfillOrder或fulfillAdvancedOrder来执行订单时:

计算订单哈希值

计算报价项目和对价项目的哈希值

检索报价者的当前计数器

计算订单哈希值

执行初始化校验

确保当前时间在订单有效时间内

确保调用者对于当前订单类型是有效的;如果订单类型收到限制且调用者不是offerer或者zone,调用zone判断订单是否有效

检索并更新订单状态

确保订单未被取消

确保订单没有被全部执行

如果订单是部分执行的,如有必要,减少提供的执行数量,以免订单被过度执行

若订单签名尚未验证,则验证订单签名

根据偏好+可用金额(preference+availableamount)确定要执行的分数

更新订单状态

确定每个项目的金额

比较初始金额startAmount和结束金额endAmount

若相等,将执行分数应用于该金额,确保结果是整数,然后使用该结果

若不等,对这两个金额都应用执行分数,确保两个结果都是整数,然后根据当前时间找到这两个结果的现行拟合值

应用条件解析器

确保每一个条件解析器都应用于一个基于条件的订单项目

如果项目具有一个非零的条件根值(anon-zerocriteriaroot),确保为每个项目提供的标识符是有效的

更新每个项目的类型和标识符

确保所有剩余的项目都不是基于条件的项目

触发OrderFulfilled事件

包括更新的项目

将报价项目由报价者转移到调用者

使用渠道或Seaport直接获得批准,具体取决于订单的类型

将对价项目有调用者转移到对应的接受者

使用渠道或Seaport直接获得批准,具体取决于执行者声明的偏好

5.2匹配订单

当通过matchOrders或者matchAdvancedOrders来匹配一组订单时,步骤1到6几乎完全相同,但针对每个提供的订单执行。从这里开始,执行与上面的标准执行不同:

应用执行

确保每次执行都涉及一个或多个报价项目和一个或多个对价项目,所有这些项目都具有相同的类型和代币,并且每个报价项目具有相同的批准源以及每个对价项目具有相同接受者

将每个报价项目和对价项目的金额减少到零,并跟踪其总减少金额

比较每个项目的总金额,并将剩余金额加回相应订单一侧的第一个项目

为每个成交返回一个执行

扫描每个对价项目并确保没有一个对价项目仍然有非零的剩余金额

作为每次执行的一部分进行转账

根据原始订单类型,直接使用渠道或Seaport获得批准

忽略to==from或amount==0时的每次执行

三、关键接口

Seaport是一个通用的ETH/ERC20/ERC721/ERC1155市场。它最大限度地减少了外部调用,并为普通路由提供了轻量级的方法,以及更灵活的方法来组合高级订单。

ConsiderationInterface包含Seaport的所有外部函数接口。

fulfillBasicOrder

执行基本订单,仅支持Ether与ERC721之间的交易。

实现逻辑

提取订单类型和基本订单路由,并且对其进行校验

准备执行基本订单添加重入锁校验时间正确检验参数正确计算并校验订单的哈希值更新订单状态

若使用了渠道,则根据订单路由导出渠道

根据订单路由,执行原生代币以及ERC721代币的转账,完成订单

删除重入锁

fulfillOrder&fulfillAdvancedOrder

fulfillOrder执行普通订单,不支持订单部分执行,不支持条件解析器;普通订单作为特殊的高级订单进行执行。

fulfillAdvancedOrder执行高级订单。

实现逻辑

添加重入锁

_validateOrderAndUpdateStatus:根据参数,验证订单,更新状态并计算订单哈希值orderHash、需要执行订单的分子numerator和分母denominator时间校验:startTime<=block.time<=endTime分子与分母校验:

numerator<=denominator&&denominator!=0;

若numerator==denominator,需要支持部分执行(SupportPartialFills)

对价项目长度校验以及计算订单哈希值orderHash:

订单长度校验:orderParameters.consideration.length>=orderParameters.totalOriginalConsiderationItems,即参数中consideration数组实际长度要大于或等于参数中直接传递的原始对价项目总数

计算订单哈希值orderHash

校验高级订单的有效性:订单类型为2或3要求zone或offerer是caller或者zone批准。校验订单状态orderStauts=_orderStatus,保证订单没有被取消并且是可执行的

订单未取消,即:保证orderStauts.isCancelled==false

订单可执行,即:若orderStatus.numerator!=0,保证orderStatus.numerator<orderStatus.denominator。

校验订单签名,即若orderStatus.isValidated==false,则调用_verifySignature函数计算需要执行订单的分子fillNumerator和分母fillDenominator更新状态变量orderStatus

_applyCriteriaResolvers:用条件解析器,对每个生成的订单类型以及条件解析器的绑定进行校验,确保提交的待执行订单是有效的

_applyFractionsAndTransferEach:以指定的分数值执行每个项目的转账

_emitOrderFulfilledEvent:发出一个表明订单已完成的事件。

删除重入锁

fulfillAvailableOrders&fulfillAvailableAdvancedOrders

fulfillAvailableOrders执行可用普通订单,不支持订单部分执行,不支持条件解析器;可用普通订单作为特殊的可用高级订单进行执行

fulfillAvailableAdvancedOrders执行可用的高级订单

实现逻辑

_validateOrdersAndPrepareToFulfill:校验订单、更新其状态、通过先前填充的分数减少金额、应用条件解析器并触发OrderFulfilled事件。添加重入锁声明并设置一个错误缓冲区变量,指明任何本地报价项目的状态。循环遍历每一个订单,校验订单,更新每一个订单中的参数:

_validateOrderAndUpdateStatus:根据参数,验证订单,更新状态并计算订单哈希值orderHash、需要执行订单的分子numerator和分母denominator

循环遍历订单中的每一个报价项目,更新报价项目中的startAmount和endAmount

循环遍历订单中的每一个对价项目,更新对价项目中的startAmount和endAmount

校验错误缓冲区变量_applyCriteriaResolvers:应用条件解析器,对每个生成的订单类型以及条件解析器的绑定进行校验,确保提交的待执行订单是有效的聚合已使用的报价和对价项目并执行转账触发OrderFulfilled事件

_executeAvailableFulfillments:完全或部分执行通过了校验的订单,每个订单具有任意数量的要约和考虑项目,并执行转移。任何当前未激活、已完全成交或已取消的订单都将被忽略。然后,剩余的报价和考虑项目将在可能的情况下聚合,如所提供的报价和考虑组件数组所示,并且聚合的项目将分别转移到履行者或每个预期的接收者。请注意,失败的项目转移或订单格式问题将导致整个批次失败。为每一个订单的报价和对价项目的执行分配一个Execution结构,构造为一个Execution结构数组executions,任何当前未激活、已完全成交或已取消的无效订单都将被忽略。校验executions的长度对executions中的每一个Execution结构对象进行校验,过滤掉当前未激活、已完全成交或已取消的所有订单。_performFinalChecksAndExecuteOrders:对高级订单以及executions进行最后的校验,然后执行订单,完成执行转账,删除重入锁。

matchOrders&matchAdvancedOrders

matchOrders:对普通订单中的报价和对价项目按参数fulfillments提供的报价组件分配给对价组件的条件求进行匹配然后执行,不支持订单部分执行,不支持条件解析器;普通订单作为特殊的高级订单进行处理

matchAdvancedOrders:对高级订单中的报价和对价项目按参数fulfillments提供的报价组件分配给对价组件的要求进行匹配然后执行

实现逻辑

_validateOrdersAndPrepareToFulfill:校验订单、更新其状态、通过先前填充的分数减少金额、应用条件解析器并触发OrderFulfilled事件。若有无效订单,则回滚。

_fulfillAdvancedOrders:在验证、调整金额和应用条件解析器之后,执行高级订单为每一个订单的报价和对价项目的执行分配一个Execution结构,构造为一个Execution结构数组executions循环遍历参数fulfillments,将executions中的每一个元素对应到fulfillments的每一个元素,若报价人者和接受者是相同的,则跳过。_performFinalChecksAndExecuteOrders:对高级订单以及executions进行最后的校验,然后执行订单,完成执行转账,删除重入锁。

郑重声明: 本文版权归原作者所有, 转载文章仅为传播更多信息之目的, 如作者信息标记有误, 请第一时间联系我们修改或删除, 多谢。

大币网

[0:15ms0-11:451ms