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