最近研究了 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
交易策略合约
看到这么多合约,是不是已经晕了。不用怕,我们要关心的只有 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 的主要逻辑了解了个大概了。剩下的合约基本上都是辅助合约了,例如各种管理器合约,我们放在下篇文章介绍。
欢迎和我交流
StrategyStandardSaleForFixedPrice,标准固定价格交易
StrategyPrivateSale,卖家指定的买家才能购买
StrategyDutchAuction,荷兰拍卖
StrategyAnyItemFromCollectionForFixedPrice,买家出价购买一个 NFT 合集中任意一项。例如,买家想购买 BAYC,任意一个都可以
StrategyAnyItemInASetForFixedPrice,买家出价购买一个 NFT 合集中某些特定 tokenId 中任意一项。例如,买家只想购买蓝色背景的 BAYC
RoyaltyFeeRegistry,设置 NFT 的版税信息,存储版税信息
RoyaltyFeeSetter,设置 NFT 的版税信息,该合约为入口,调用上面的合约
EIP-712 使用详解
之前的文章我们介绍过如何对数据进行签名,利用签名技术我们可以实现一些功能例如白名单校验等。但是这种签名技术的应用场景比较简单,一般就是给一串字符串,或者一串哈希签名,如果我们想为更复杂的数据签名就无法实现了。 EIP-712 的出现就是为了解决这个问题,利用 EIP-712,我们可以对更大的数据集,例如对结构体进行签名。那么这种签名格式有什么实际的应用场景呢。使用过 Uniswap,PancakeSwap 等 DEX 的朋友应该有印象,在移除 LP 流动性的时候,我们需要先签名,然后再发送一笔交易移除流动性。正常情况下,其实应该我们先调用 LP 代币的授权方法,授权 DEX 合约可以转移我们的 LP,然后再去移除流动性。而这种二合一的实现正是应用了 EIP-712。它帮助我们仅仅签名一次,就可以将两步交易合并为一步交易,从而节省 Gas 费用。这篇文章我们就来看看 EIP-712 到底是怎么使用的。基本结构EIP712Domain顾名思义,是一个与域相关的结构体,总共包含五个字段:name,合约或者协议的名称version,合约的版本chainId,合约部署的链 Id,一般使用 ...
流动性挖矿-合约原理详解
流动性挖矿应该是上个牛市最火热的内容,基本上整个 DeFi 都是在围绕着流动性挖矿展开的,今天我们就来看看它到底是什么以及合约代码层面是怎么实现的。流动性挖矿简介首先我们先从用户的角度来理解一下流动性挖矿是什么,实际上就是用户通过在合约中质押一个 token 从而赚取另一个 token 的过程。例如,SushiSwap 最初推出的 DEX 流动性挖矿,用户可以通过将 SushiSwap 的 LP token 质押到合约中赚取 Sushi token。那么这个奖励具体是怎么发放以及如何实现的呢?我们今天就来研究一下这部分内容。 先来看几个例子: 一:假设有一个流动性挖矿的合约,可以质押 A token 赚取 B token。它在 0 秒时开始活动,每秒奖励 R 个 B token。此时有用户 Alice 在第 3 秒时质押了 2 个 A token,并且之后没有其他人参与,在第 8 秒时取出 token,图示:那么他在此时获得的收益就是:5R = (2 / 2) * (8 - 3) * R 其中,第一个 2 是用户 A 质押的数量,第二个 2 是合约中质押的总量,(8-3)是用户 ...
CREATE2 操作码使用方法详解
CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么:这段代码是 Uniswap v2-core 里面的工厂合约代码,使用 create2 操作码创建了 pair 合约,返回值是 pair 的地址,这样就可以逻辑中直接使用其地址进行接下来的操作。 那么 create2 到底是怎么使用呢,根据官方 EIP 文档,create2 一共接收四个参数,分别是:endowment(创建合约时往合约中打的 ETH 数量)memory_start(代码在内存中的起始位置,一般固定为 add(bytecode, 0x20) )memory_length(代码长度,一般固定为 mload(bytecode) )salt(随机数盐)这里要注意的是第一个参数如果大于 0 的话,需要待部署合约的构造方法带有 payable。随机数盐是由用户自定,须为 bytes32 格式,例如在上面 Uniswap 的例子中,salt 为:bytes32 salt = keccak256(abi.encodePacked(token0, token1)); create2 还有一个优点,相...
Smart Contract Developer
最近研究了 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 等比较简单的部分,不再介绍。
包含 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 的主要逻辑了解了个大概了。剩下的合约基本上都是辅助合约了,例如各种管理器合约,我们放在下篇文章介绍。
欢迎和我交流
EIP-712 使用详解
之前的文章我们介绍过如何对数据进行签名,利用签名技术我们可以实现一些功能例如白名单校验等。但是这种签名技术的应用场景比较简单,一般就是给一串字符串,或者一串哈希签名,如果我们想为更复杂的数据签名就无法实现了。 EIP-712 的出现就是为了解决这个问题,利用 EIP-712,我们可以对更大的数据集,例如对结构体进行签名。那么这种签名格式有什么实际的应用场景呢。使用过 Uniswap,PancakeSwap 等 DEX 的朋友应该有印象,在移除 LP 流动性的时候,我们需要先签名,然后再发送一笔交易移除流动性。正常情况下,其实应该我们先调用 LP 代币的授权方法,授权 DEX 合约可以转移我们的 LP,然后再去移除流动性。而这种二合一的实现正是应用了 EIP-712。它帮助我们仅仅签名一次,就可以将两步交易合并为一步交易,从而节省 Gas 费用。这篇文章我们就来看看 EIP-712 到底是怎么使用的。基本结构EIP712Domain顾名思义,是一个与域相关的结构体,总共包含五个字段:name,合约或者协议的名称version,合约的版本chainId,合约部署的链 Id,一般使用 ...
流动性挖矿-合约原理详解
流动性挖矿应该是上个牛市最火热的内容,基本上整个 DeFi 都是在围绕着流动性挖矿展开的,今天我们就来看看它到底是什么以及合约代码层面是怎么实现的。流动性挖矿简介首先我们先从用户的角度来理解一下流动性挖矿是什么,实际上就是用户通过在合约中质押一个 token 从而赚取另一个 token 的过程。例如,SushiSwap 最初推出的 DEX 流动性挖矿,用户可以通过将 SushiSwap 的 LP token 质押到合约中赚取 Sushi token。那么这个奖励具体是怎么发放以及如何实现的呢?我们今天就来研究一下这部分内容。 先来看几个例子: 一:假设有一个流动性挖矿的合约,可以质押 A token 赚取 B token。它在 0 秒时开始活动,每秒奖励 R 个 B token。此时有用户 Alice 在第 3 秒时质押了 2 个 A token,并且之后没有其他人参与,在第 8 秒时取出 token,图示:那么他在此时获得的收益就是:5R = (2 / 2) * (8 - 3) * R 其中,第一个 2 是用户 A 质押的数量,第二个 2 是合约中质押的总量,(8-3)是用户 ...
CREATE2 操作码使用方法详解
CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么:这段代码是 Uniswap v2-core 里面的工厂合约代码,使用 create2 操作码创建了 pair 合约,返回值是 pair 的地址,这样就可以逻辑中直接使用其地址进行接下来的操作。 那么 create2 到底是怎么使用呢,根据官方 EIP 文档,create2 一共接收四个参数,分别是:endowment(创建合约时往合约中打的 ETH 数量)memory_start(代码在内存中的起始位置,一般固定为 add(bytecode, 0x20) )memory_length(代码长度,一般固定为 mload(bytecode) )salt(随机数盐)这里要注意的是第一个参数如果大于 0 的话,需要待部署合约的构造方法带有 payable。随机数盐是由用户自定,须为 bytes32 格式,例如在上面 Uniswap 的例子中,salt 为:bytes32 salt = keccak256(abi.encodePacked(token0, token1)); create2 还有一个优点,相...
Smart Contract Developer

Subscribe to xyyme.eth

Subscribe to xyyme.eth
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
No activity yet