最近研究了 LooksRare 的合约代码,他们的代码写得比较简单易懂,同时文档内容也比较丰富。学习了几天,基本算是把整个合约代码都研究明白了,因此写篇文章来做做笔记,同时也希望能够帮助到有需要的朋友。
ask
代表卖家卖出,bid
代表买家买入。maker
代表主动挂单的人,例如卖家主动挂单卖 NFT ,此时卖家为 maker。或者买家对某 NFT 出价,此时买家为 maker。taker
代表撮合完成订单的人,例如买家看到某 NFT 的价格合适,对其买入,此时买家为 taker。或者卖家看到某买家的出价合适,对其卖出,此时卖家为 taker。结合上面的描述,合约中一共有四个角色,分别是:
makerAsk
,挂单的卖家makerBid
,出价的买家takerAsk
,撮合完成订单的卖家takerBid
,撮合完成订单的买家挂单(出价)行为是在链下完成的,即 maker
操作不上链,只是在链下签名。
成单行为是在链上完成的,即 taker
操作上链。因此代码中只有成单,没有挂单的逻辑。
LooksRareExchange
,主合约,用户的所有操作都在这里CurrencyManager
,管理协议支持的支付币种ExecutionManager
,管理协议支持的交易策略RoyaltyFeeManager
,管理 NFT 对应的版税信息ITransferManagerNFT
TransferManagerERC721
,执行 ERC-721 的转移操作(使用 safeTransferFrom
)TransferManagerERC1155
,执行 ERC-1155 的转移操作TransferManagerNonCompliantERC721
,执行 ERC-721 的转移操作(不使用 safeTransferFrom
,而是 transferFrom
)OrderTypes
,包含 MakerOrder
与 TakerOrder
的订单数据结构TransferSelectorNFT
,管理 NFT 对应的 TransferManagerStrategyStandardSaleForFixedPrice
,标准固定价格交易StrategyPrivateSale
,卖家指定的买家才能购买StrategyDutchAuction
,荷兰拍卖StrategyAnyItemFromCollectionForFixedPrice
,买家出价购买一个 NFT 合集中任意一项。例如,买家想购买 BAYC,任意一个都可以StrategyAnyItemInASetForFixedPrice
,买家出价购买一个 NFT 合集中某些特定 tokenId 中任意一项。例如,买家只想购买蓝色背景的 BAYCRoyaltyFeeRegistry
,设置 NFT 的版税信息,存储版税信息RoyaltyFeeSetter
,设置 NFT 的版税信息,该合约为入口,调用上面的合约看到这么多合约,是不是已经晕了。不用怕,我们要关心的只有 LooksRareExchange
合约,其他的合约都是为了它服务的。
注:由于 Mirror 的排版原因,长代码的阅读性很差,因此为了统一起见,所有代码将会截图展示。同时,只着重于主要业务逻辑,对于例如 setter 等比较简单的部分,不再介绍。
包含 MakerOrder
与 TakerOrder
,分别为:
我们知道,挂单是在通过签名在链下进行的,每个挂单都包含 nonce。但是如果要取消订单,必须要上链,为什么呢?因为在成单的时候,要用到挂单的签名信息,而一旦签名了,签名信息是一直存在的。即使链下再怎么操作,链上也可以把这份签名拿过来用。因此 maker 需要在链上将这个 nonce 的订单取消,让其在链上失效。这样即使有人拿着签名信息来用,那么在链上这个 nonce 的订单已经失效了。
对于上面两个函数,cancelAllOrdersForSender
属于一刀切,传入一个 nonce,该 nonce 以下的订单就全部失效。cancelMultipleMakerOrders
则是传入特定的 nonce 列表,只有这些 nonce 的订单失效。
买家发起撮合,使用 WETH 购买
买家发起撮合,使用指定币种(makerAsk.currency)购买
可以看到上面两个函数的逻辑大同小异,区别比较大的地方就是使用 WETH 购买的函数中,需要对 msg.value
以及 ETH 的转换进行处理。
卖家发起撮合
可以看到,与上一个方法也是大同小异,只是方向不同。
用户所有的操作就是这些,我们再来小结一下:
cancelAllOrdersForSender
→ 取消指定 nonce 以下所有订单cancelMultipleMakerOrders
→ 批量取消指定订单matchAskWithTakerBidUsingETHAndWETH
→ 买家发起撮合,使用 WETH 购买matchAskWithTakerBid
→ 买家发起撮合,使用指定币种(makerAsk.currency)购买matchBidWithTakerAsk
→ 卖家发起撮合读懂这些逻辑,我们就已经掌握了 LooksRare 合约的核心内容。
接下来,我们看看几个重要的内部函数。
分发买家的款项(非 ETH 支付)
分发买家的款项(WETH 支付)
我们可以看到,上面两个函数的内容基本相同,主要逻辑都是对买家的款项进行了分发,计算了协议费用和版税费用,最后将剩余款项转给卖家。
还记得我们前面看到的 MakerOrder
中的 minPercentageToAsk
字段吗,当时看是不是有点迷茫,这个字段是什么意思?因为有版税和协议费用的存在,且它俩都是变量,可能卖家在挂单之后,各种费用的值被管理员修改了,那么在挂单的时候就需要设置一个最小可接受的值,如果最后盈利的数量小于该值,那么就不交易了,类似于 DEX 中滑点的概念。
注意到两个函数最大的区别就是转账这块,在非 ETH 函数中,使用的是 safeTransferFrom
,而在 ETH 函数中,使用的是 safeTransfer
。因为一个是 ERC20,一个是原生货币,后者在调用函数的同时就已经转入款项了。同时这也是为什么我们前面的入口函数那里,ETH 支付的函数需要对 msg.value
进行处理,而 ERC20 不需要这一步。
转账 NFT
由于目前 NFT 流行的标准有 ERC-721 和 ERC-1155,两者的转账方法略有不同。因此,这里会根据 NFT 合集对应的标准,选取相应的转账管理器来进行转账。转账管理器的内部逻辑其实很简单,我们后面再介绍,这里先着重于主逻辑。
校验订单信息
这里,先校验订单的 nonce 是否有效,我们最开始看到的两个关于 nonce 的数据结构可以用来验证。然后再根据 maker 的签名和 makerOrder 的哈希值来判断其是否为正确的订单信息,这里利用了 EIP-712 的相关内容,不熟悉的朋友可以看看我之前的写的这篇文章。
到这里,所有的主逻辑我们就已经看完了,是不是还算比较简单。
我们先休息一下,没有完全看懂的朋友可以再多看两遍消化一下。这篇文章就先介绍到这里,看完这篇我们基本上就已经对 LooksRare 的主要逻辑了解了个大概了。剩下的合约基本上都是辅助合约了,例如各种管理器合约,我们放在下篇文章介绍。
欢迎和我交流