xyyme.eth

Posted on Jun 23, 2022Read on Mirror.xyz

LooksRare 合约代码解读(一)

最近研究了 LooksRare 的合约代码,他们的代码写得比较简单易懂,同时文档内容也比较丰富。学习了几天,基本算是把整个合约代码都研究明白了,因此写篇文章来做做笔记,同时也希望能够帮助到有需要的朋友。

系统架构

名词解释

  • ask 代表卖家卖出,bid 代表买家买入。
  • maker 代表主动挂单的人,例如卖家主动挂单卖 NFT ,此时卖家为 maker。或者买家对某 NFT 出价,此时买家为 maker。
  • taker 代表撮合完成订单的人,例如买家看到某 NFT 的价格合适,对其买入,此时买家为 taker。或者卖家看到某买家的出价合适,对其卖出,此时卖家为 taker。

结合上面的描述,合约中一共有四个角色,分别是:

  • makerAsk,挂单的卖家
  • makerBid,出价的买家
  • takerAsk,撮合完成订单的卖家
  • takerBid,撮合完成订单的买家

链下

挂单(出价)行为是在链下完成的,即 maker 操作不上链,只是在链下签名。

链上

成单行为是在链上完成的,即 taker 操作上链。因此代码中只有成单,没有挂单的逻辑。

合约架构

  • LooksRareExchange,主合约,用户的所有操作都在这里
  • 管理合约
    • CurrencyManager,管理协议支持的支付币种
    • ExecutionManager,管理协议支持的交易策略
    • RoyaltyFeeManager,管理 NFT 对应的版税信息
    • TransferManager,均继承于 ITransferManagerNFT
      • TransferManagerERC721,执行 ERC-721 的转移操作(使用 safeTransferFrom
      • TransferManagerERC1155,执行 ERC-1155 的转移操作
      • TransferManagerNonCompliantERC721,执行 ERC-721 的转移操作(使用 safeTransferFrom,而是 transferFrom
  • OrderTypes,包含 MakerOrderTakerOrder 的订单数据结构
  • TransferSelectorNFT,管理 NFT 对应的 TransferManager
  • 交易策略合约
    • StrategyStandardSaleForFixedPrice,标准固定价格交易
    • StrategyPrivateSale,卖家指定的买家才能购买
    • StrategyDutchAuction,荷兰拍卖
    • StrategyAnyItemFromCollectionForFixedPrice,买家出价购买一个 NFT 合集中任意一项。例如,买家想购买 BAYC,任意一个都可以
    • StrategyAnyItemInASetForFixedPrice,买家出价购买一个 NFT 合集中某些特定 tokenId 中任意一项。例如,买家只想购买蓝色背景的 BAYC
  • RoyaltyFeeRegistry,设置 NFT 的版税信息,存储版税信息
  • RoyaltyFeeSetter,设置 NFT 的版税信息,该合约为入口,调用上面的合约

看到这么多合约,是不是已经晕了。不用怕,我们要关心的只有 LooksRareExchange 合约,其他的合约都是为了它服务的。

源码解读

注:由于 Mirror 的排版原因,长代码的阅读性很差,因此为了统一起见,所有代码将会截图展示。同时,只着重于主要业务逻辑,对于例如 setter 等比较简单的部分,不再介绍。

OrderTypes

包含 MakerOrderTakerOrder,分别为:

订单数据结构

LooksRareExchange

nonce 数据结构

取消订单

我们知道,挂单是在通过签名在链下进行的,每个挂单都包含 nonce。但是如果要取消订单,必须要上链,为什么呢?因为在成单的时候,要用到挂单的签名信息,而一旦签名了,签名信息是一直存在的。即使链下再怎么操作,链上也可以把这份签名拿过来用。因此 maker 需要在链上将这个 nonce 的订单取消,让其在链上失效。这样即使有人拿着签名信息来用,那么在链上这个 nonce 的订单已经失效了。

对于上面两个函数,cancelAllOrdersForSender 属于一刀切,传入一个 nonce,该 nonce 以下的订单就全部失效。cancelMultipleMakerOrders 则是传入特定的 nonce 列表,只有这些 nonce 的订单失效。

matchAskWithTakerBidUsingETHAndWETH

买家发起撮合,使用 WETH 购买

买家使用 WETH 购买

matchAskWithTakerBid

买家发起撮合,使用指定币种(makerAsk.currency)购买

买家使用非 WETH 币种购买

可以看到上面两个函数的逻辑大同小异,区别比较大的地方就是使用 WETH 购买的函数中,需要对 msg.value 以及 ETH 的转换进行处理。

matchBidWithTakerAsk

卖家发起撮合

卖家卖出

可以看到,与上一个方法也是大同小异,只是方向不同。

用户所有的操作就是这些,我们再来小结一下:

  • cancelAllOrdersForSender → 取消指定 nonce 以下所有订单
  • cancelMultipleMakerOrders → 批量取消指定订单
  • matchAskWithTakerBidUsingETHAndWETH → 买家发起撮合,使用 WETH 购买
  • matchAskWithTakerBid → 买家发起撮合,使用指定币种(makerAsk.currency)购买
  • matchBidWithTakerAsk → 卖家发起撮合

读懂这些逻辑,我们就已经掌握了 LooksRare 合约的核心内容。

接下来,我们看看几个重要的内部函数。

_transferFeesAndFunds

分发买家的款项(非 ETH 支付)

分发买家的款项(非 ETH 支付)

_transferFeesAndFundsWithWETH

分发买家的款项(WETH 支付)

分发买家的款项(WETH 支付)

我们可以看到,上面两个函数的内容基本相同,主要逻辑都是对买家的款项进行了分发,计算了协议费用和版税费用,最后将剩余款项转给卖家。

还记得我们前面看到的 MakerOrder 中的 minPercentageToAsk 字段吗,当时看是不是有点迷茫,这个字段是什么意思?因为有版税和协议费用的存在,且它俩都是变量,可能卖家在挂单之后,各种费用的值被管理员修改了,那么在挂单的时候就需要设置一个最小可接受的值,如果最后盈利的数量小于该值,那么就不交易了,类似于 DEX 中滑点的概念。

注意到两个函数最大的区别就是转账这块,在非 ETH 函数中,使用的是 safeTransferFrom,而在 ETH 函数中,使用的是 safeTransfer。因为一个是 ERC20,一个是原生货币,后者在调用函数的同时就已经转入款项了。同时这也是为什么我们前面的入口函数那里,ETH 支付的函数需要对 msg.value 进行处理,而 ERC20 不需要这一步。

_transferNonFungibleToken

转账 NFT

转账 NFT

由于目前 NFT 流行的标准有 ERC-721 和 ERC-1155,两者的转账方法略有不同。因此,这里会根据 NFT 合集对应的标准,选取相应的转账管理器来进行转账。转账管理器的内部逻辑其实很简单,我们后面再介绍,这里先着重于主逻辑。

_validateOrder

校验订单信息

校验订单信息

这里,先校验订单的 nonce 是否有效,我们最开始看到的两个关于 nonce 的数据结构可以用来验证。然后再根据 maker 的签名和 makerOrder 的哈希值来判断其是否为正确的订单信息,这里利用了 EIP-712 的相关内容,不熟悉的朋友可以看看我之前的写的这篇文章

小结

到这里,所有的主逻辑我们就已经看完了,是不是还算比较简单。

我们先休息一下,没有完全看懂的朋友可以再多看两遍消化一下。这篇文章就先介绍到这里,看完这篇我们基本上就已经对 LooksRare 的主要逻辑了解了个大概了。剩下的合约基本上都是辅助合约了,例如各种管理器合约,我们放在下篇文章介绍。

LooksRare 代码系列文章

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

关于我

欢迎和我交流

参考

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