xyyme.eth

Posted on Jun 27, 2022Read on Mirror.xyz

LooksRare 合约代码解读(二)

我们继续来学习 LooksRare 的代码,上篇文章中我们学习了它的主合约 LooksRareExchange,这篇文章我们着重于其它的辅助合约,包括各种管理合约以及交易策略合约等。

源码解读

TransferSelectorNFT

上篇文章的最后,我们谈到了 NFT 的转账操作:

NFT 转账

可以看到,需要先根据 NFT 地址获取相应的转账管理器,然后采用对应的管理器来进行转账。其内部的实现为:

获取 NFT 对应的转账管理合约

这段代码中先查询该 NFT 有没有特定的转账管理器。如果没有,则利用 ERC165 规范,查询 NFT 合约实现了哪种规范(ERC721 或 ERC1155),然后使用对应的通用管理器。转账管理器本身的合约代码很简单,例如 ERC721 的转账代码为:

TransferManagerERC721

ERC721 转账管理器

Manager

CurrencyManagerExecutionManager 这两个管理器合约内容比较简单,包含基本的 addremoveview 等方法,我们这里不再介绍。

RoyaltyFeeRegistry

该合约用于存储版税的相关信息,包括设置,读取等方法。

其中设置方法只能由 owner,即 RoyaltyFeeSetter 合约调用。

版税数据结构

版税对应的相关方法

RoyaltyFeeSetter

设置版税信息,可以理解为将上面的 RoyaltyFeeRegistry 合约包装了一层,这里是设置的入口。包含下面几个主要方法:

  • updateRoyaltyInfoForCollectionIfAdmin
  • updateRoyaltyInfoForCollectionIfOwner
  • updateRoyaltyInfoForCollectionIfSetter
  • updateRoyaltyInfoForCollection
  • _updateRoyaltyInfoForCollectionIfOwnerOrAdmin(内部方法,被上面的 IfAdminIfOwner 方法调用)

前四个方法均为设置版税功能,区别在于,前两个是由 NFT 的管理员设置,由于每个 NFT 合约的写法都不同,因此会区分 adminowner 等角色。第三个方法是由前面设置版税时的 setter 设置。第四个是由 RoyaltyFeeSetter 合约本身的 owner 调用。最后一个内部方法调用上面的 registry 合约,进行设置操作。

我对这几个方法的理解是,如果 NFT 合约本身实现了 owner 或者 admin 方法,那么就有自主设置版税的权利。如果没有实现,可以找到系统管理员协助设置,此时同时会设置一个 setter 角色,以后 NFT 的版税就可以通过 setter 地址调用 updateRoyaltyInfoForCollectionIfSetter 来管理。系统更加推荐使用 setter 角色来管理版税。

我们来看看其中一个设置方法:

updateRoyaltyInfoForCollectionIfAdmin

设置版税

逻辑比较简单。要求只有 NFT 的 admin 可以调用。我们看到第一步要求 NFT 不能实现 ERC2981,奇怪了,ERC2981 不就是版税的标准呢,为什么不能实现这个接口呢,我们后面会解答这个问题。

_updateRoyaltyInfoForCollectionIfOwnerOrAdmin

更新版税内部方法

RoyaltyFeeManager

该合约是最外层的接口,主合约在这里获取版税信息,只有一个主要方法:

calculateRoyaltyFeeAndGetRecipient

版税外层接口

我们看到,首先要检查系统中是否设置了该 NFT 的版税信息,如果没有设置,再去检查 NFT 本身是否包含版税信息。反过来想,如果合约本身就已经包含了版税信息,那么我们就没有必要在系统中设置。这也就解答了我们前面提出的问题:在 RoyaltyFeeSetter 合约中设置版税时,要求 NFT 合约本身不能实现 ERC2981。因为实现了 ERC2981 的合约,我们直接使用其自身的版税信息即可,不必再次设置。

接下来我们看看交易策略,LooksRare 合约库中一共包含 5 个交易策略,分别是:

  • StrategyStandardSaleForFixedPrice,标准固定价格交易
  • StrategyPrivateSale,卖家指定的买家才能购买
  • StrategyDutchAuction,荷兰拍卖
  • StrategyAnyItemFromCollectionForFixedPrice,买家出价购买一个 NFT 合集中任意一项。例如,买家想购买 BAYC,任意一个都可以
  • StrategyAnyItemInASetForFixedPrice,买家出价购买一个 NFT 合集中某些特定 tokenId 中任意一项。例如,买家只想购买蓝色背景的 BAYC

我们回想一下,在上篇文章中我们看到每个 match 成单方法都会调用策略的方法来校验该交易是否有效。例如:

调用策略方法

我们先提前了解一点,每个策略中都有两个主要方法:

  • canExecuteTakerAsk,校验 taker 卖单与 maker 买单能否有效匹配
  • canExecuteTakerBid,校验 taker 买单与 maker 卖单能否有效匹配

接下来我们分别来介绍每个策略的代码,看看他们的具体实现。

StrategyStandardSaleForFixedPrice

最标准的买卖策略,固定价格,买单与卖单均指定 tokenId。

标准策略

可以看到标准策略中需要校验 pricetokenId 是否匹配,并检查时间戳是否有效,逻辑比较简单。只要满足这些规则,就是符合标准策略。

StrategyPrivateSale

当卖家指定由某位固定的买家购买时,使用该策略。

指定买家

卖家指定买家,此时卖单挂单中会指定买家的地址,将其存放在 MakerOrder 的 params 字段。因为这个策略的执行只能由买家触发,所以 canExecuteTakerAsk 方法没有意义,需要返回 false。在下面的 canExecuteTakerBid 中,需要将卖家指定的买家地址从 params 中解析出来,然后进行校验,同时也要校验价格,tokenId,时间戳等信息。

StrategyDutchAuction

荷兰拍卖,价格会不断下降,买家为主动方。

荷兰拍卖

荷兰拍卖同样由买家触发,因此 taker 卖单没有意义,canExecuteTakerAsk 需要返回 false。在 canExecuteTakerBid 方法中,根据开始价格,结束价格,拍卖时常来计算当前实时价格,最后对参数进行校验。

StrategyAnyItemFromCollectionForFixedPrice

买家购买任意一款

买家购买合集中任意一项均可,因此为买家挂单。此时卖家为 taker 主动方,因此 canExecuteTakerBid 没有意义,返回 false。我们看到,在 canExecuteTakerAsk 中仅仅校验了价格与时间,没有 tokenId,这是为何呢?因为买家购买任意一款均可,那么就不用指定 tokenId,只要价格与时间等信息匹配,任意一款 tokenId 都可以,因此这里不用校验。

StrategyAnyItemInASetForFixedPrice

买家购买某些特定 id

买家指定购买某些特定的 tokenId,此时买家挂单,卖家为 taker 主动方,因此 canExecuteTakerBid 没有意义,返回 false。canExecuteTakerAsk 中利用了 Merkle Tree 的技术,maker 买单中的 params 字段存储所有想要购买 tokenId 合集的 Merkle Root,taker 卖单中的 params 存储了该 tokenId 对应的 Proof。最后通过 verify 校验 taker 卖单中的 tokenId 是否匹配。

总结

现在我们就已经介绍完了 LooksRare 的所有主要合约,是不是感觉还挺简单的。我个人认为 LooksRare 的文档和代码写得都比较简洁易懂,比较适合开发者来学习 NFT 市场的原理。希望大家能够结合代码库和我的文章,多理解几遍,必然会对自己的能力有一些提升。

LooksRare 代码系列文章

  1. LooksRare 合约代码解读(一)
  2. LooksRare 合约代码解读(二)

关于我

欢迎和我交流

参考

https://docs.looksrare.org/developers/looksrare-exchange-overview