# Looksrare合约讲解 **Published by:** [point](https://paragraph.com/@point/) **Published on:** 2022-05-02 **URL:** https://paragraph.com/@point/looksrare ## Content 因为做了一个NFT市场,所以对opensea,rarible,looksrare合约都研究了下,记录如下 合约架构 https://etherscan.io/accounts/label/looksrare 三家的合约高度相似,只不过是将代码重构的好看一些。再加些最近才有的EIP。looksare基本上是把能拆的都拆了,比如手续费、版权(EIP2981)、transfer,对比下另外两家,舒服很多 业务逻辑基本都是链下操作,只有在真正mint nft,撮合交易成功,取消交易才会上链。甚至现在mint nft都不上链,第一次transfer时才会mint nft。进一步减少gas消耗。创建订单:使用订单参数+salt进行EIP712 signature,transfer时在合约中验证签名,这样能减少Gas和保证订单没问题。。。。。才怪,open就出了有人拿到signature后,等订单过期了再去调用合约,把人家的nft低价撸走的bug。取消订单:链上不保存创建订单。就像上面说的,防止bug出现,就要链上保存取消的订单,但是opensea没有判断 currentTime>expireTime 所以拿过期的signature还是可以完成交易取消所有订单,减少gas,理论上也不是取消所有,一次的上限是500000版权费:使用EIP2981,原因是像opensea,有版权费上限,如果我要收100%的版权费,没辙,所以要一个链上保存版权费的功能代码结构共两个cancelOrder三个撮合函数好几个update周边合约函数两个transfer token函数一个transfer nft函数一个校验函数//取消订单 cancelAllOrdersForSender cancelMultipleMakerOrders //撮合函数 //卖家挂单 matchAskWithTakerBidUsingETHAndWETH matchAskWithTakerBid //买家询价 matchBidWithTakerAsk //转钱 _transferFeesAndFunds _transferFeesAndFundsWithWETH //转nft _transferNonFungibleToken //校验订单 _validateOrder 从上面函数名可以看出,好几个都是相似的内容,所以合约还是比较容易看懂的Exchange合约注释// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; // OpenZeppelin contracts import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol"; import {IERC20, SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; // LooksRare interfaces import {ICurrencyManager} from "./interfaces/ICurrencyManager.sol"; import {IExecutionManager} from "./interfaces/IExecutionManager.sol"; import {IExecutionStrategy} from "./interfaces/IExecutionStrategy.sol"; import {IRoyaltyFeeManager} from "./interfaces/IRoyaltyFeeManager.sol"; import {ILooksRareExchange} from "./interfaces/ILooksRareExchange.sol"; import {ITransferManagerNFT} from "./interfaces/ITransferManagerNFT.sol"; import {ITransferSelectorNFT} from "./interfaces/ITransferSelectorNFT.sol"; import {IWETH} from "./interfaces/IWETH.sol"; // LooksRare libraries import {OrderTypes} from "./libraries/OrderTypes.sol"; import {SignatureChecker} from "./libraries/SignatureChecker.sol"; contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable { using SafeERC20 for IERC20; //订单的卖家和买家信息 using OrderTypes for OrderTypes.MakerOrder; using OrderTypes for OrderTypes.TakerOrder; //weth的地址 address public immutable WETH; 、、EIP712的信息 bytes32 public immutable DOMAIN_SEPARATOR; //手续费地址 address public protocolFeeRecipient; //looksrare的周边合约地址 ICurrencyManager public currencyManager; IExecutionManager public executionManager; IRoyaltyFeeManager public royaltyFeeManager; ITransferSelectorNFT public transferSelectorNFT; //用于取消订单和校验订单是否合法的逻辑 mapping(address => uint256) public userMinOrderNonce; mapping(address => mapping(uint256 => bool)) private _isUserOrderNonceExecutedOrCancelled; event CancelAllOrders(address indexed user, uint256 newMinNonce); event CancelMultipleOrders(address indexed user, uint256[] orderNonces); event NewCurrencyManager(address indexed currencyManager); event NewExecutionManager(address indexed executionManager); event NewProtocolFeeRecipient(address indexed protocolFeeRecipient); event NewRoyaltyFeeManager(address indexed royaltyFeeManager); event NewTransferSelectorNFT(address indexed transferSelectorNFT); event RoyaltyPayment( address indexed collection, uint256 indexed tokenId, address indexed royaltyRecipient, address currency, uint256 amount ); event TakerAsk( bytes32 orderHash, // bid hash of the maker order uint256 orderNonce, // user order nonce address indexed taker, // sender address for the taker ask order address indexed maker, // maker address of the initial bid order address indexed strategy, // strategy that defines the execution address currency, // currency address address collection, // collection address uint256 tokenId, // tokenId transferred uint256 amount, // amount of tokens transferred uint256 price // final transacted price ); event TakerBid( bytes32 orderHash, // ask hash of the maker order uint256 orderNonce, // user order nonce address indexed taker, // sender address for the taker bid order address indexed maker, // maker address of the initial ask order address indexed strategy, // strategy that defines the execution address currency, // currency address address collection, // collection address uint256 tokenId, // tokenId transferred uint256 amount, // amount of tokens transferred uint256 price // final transacted price ); /** * @notice Constructor 保存好签名校验需要的参数,保存周边合约的地址 * @param _currencyManager currency manager address * @param _executionManager execution manager address * @param _royaltyFeeManager royalty fee manager address * @param _WETH wrapped ether address (for other chains, use wrapped native asset) * @param _protocolFeeRecipient protocol fee recipient */ constructor( address _currencyManager, address _executionManager, address _royaltyFeeManager, address _WETH, address _protocolFeeRecipient ) { // Calculate the domain separator DOMAIN_SEPARATOR = keccak256( abi.encode( 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f, // keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") 0xda9101ba92939daf4bb2e18cd5f942363b9297fbc3232c9dd964abb1fb70ed71, // keccak256("LooksRareExchange") 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6, // keccak256(bytes("1")) for versionId = 1 block.chainid, address(this) ) ); currencyManager = ICurrencyManager(_currencyManager); executionManager = IExecutionManager(_executionManager); royaltyFeeManager = IRoyaltyFeeManager(_royaltyFeeManager); WETH = _WETH; protocolFeeRecipient = _protocolFeeRecipient; } /** * @notice Cancel all pending orders for a sender 取消所有订单 * @param minNonce minimum user nonce */ function cancelAllOrdersForSender(uint256 minNonce) external { require(minNonce > userMinOrderNonce[msg.sender], "Cancel: Order nonce lower than current"); require(minNonce < userMinOrderNonce[msg.sender] + 500000, "Cancel: Cannot cancel more orders"); userMinOrderNonce[msg.sender] = minNonce; emit CancelAllOrders(msg.sender, minNonce); } /** * @notice Cancel maker orders 根据需要取消订单 * @param orderNonces array of order nonces */ function cancelMultipleMakerOrders(uint256[] calldata orderNonces) external { require(orderNonces.length > 0, "Cancel: Cannot be empty"); for (uint256 i = 0; i < orderNonces.length; i++) { require(orderNonces[i] >= userMinOrderNonce[msg.sender], "Cancel: Order nonce lower than current"); _isUserOrderNonceExecutedOrCancelled[msg.sender][orderNonces[i]] = true; } emit CancelMultipleOrders(msg.sender, orderNonces); } /** * @notice Match ask with a taker bid order using ETH 核心exchange业务逻辑并且eth和weth混合支付,可能你的weth不足,不要紧,再用点eth,这里面会将eth换成weth,就不用你先去兑换了,节省gas * @param takerBid taker bid order * @param makerAsk maker ask order */ function matchAskWithTakerBidUsingETHAndWETH( OrderTypes.TakerOrder calldata takerBid, OrderTypes.MakerOrder calldata makerAsk ) external payable override nonReentrant { //确定是卖家挂单才行,因为买家询价是不能用eth支付的 require((makerAsk.isOrderAsk) && (!takerBid.isOrderAsk), "Order: Wrong sides"); require(makerAsk.currency == WETH, "Order: Currency must be WETH"); require(msg.sender == takerBid.taker, "Order: Taker must be the sender"); //eth不足,先将weth转移给卖家 // If not enough ETH to cover the price, use WETH if (takerBid.price > msg.value) { IERC20(WETH).safeTransferFrom(msg.sender, address(this), (takerBid.price - msg.value)); } else { require(takerBid.price == msg.value, "Order: Msg.value too high"); } //将eth兑换成weth来交易 // Wrap ETH sent to this contract IWETH(WETH).deposit{value: msg.value}(); //使用eip712校验订单 // Check the maker ask order bytes32 askHash = makerAsk.hash(); _validateOrder(makerAsk, askHash); //挂单,拍卖有不同的交易费费率,从这取出来 // Retrieve execution parameters (bool isExecutionValid, uint256 tokenId, uint256 amount) = IExecutionStrategy(makerAsk.strategy) .canExecuteTakerBid(takerBid, makerAsk); require(isExecutionValid, "Strategy: Execution invalid"); // Update maker ask order status to true (prevents replay) //先将订单置为已完成,防止重入 _isUserOrderNonceExecutedOrCancelled[makerAsk.signer][makerAsk.nonce] = true; // Execution part 1/2 //真正开始交易的地方,分两步,转移钱,再转移nft _transferFeesAndFundsWithWETH( makerAsk.strategy, makerAsk.collection, tokenId, makerAsk.signer, takerBid.price, makerAsk.minPercentageToAsk ); // Execution part 2/2 _transferNonFungibleToken(makerAsk.collection, makerAsk.signer, takerBid.taker, tokenId, amount); emit TakerBid( askHash, makerAsk.nonce, takerBid.taker, makerAsk.signer, makerAsk.strategy, makerAsk.currency, makerAsk.collection, tokenId, amount, takerBid.price ); } /** * @notice Match a takerBid with a matchAsk 基础撮合订单,和上面的区别就是这里只能用weth * @param takerBid taker bid order * @param makerAsk maker ask order */ function matchAskWithTakerBid(OrderTypes.TakerOrder calldata takerBid, OrderTypes.MakerOrder calldata makerAsk) external override nonReentrant { require((makerAsk.isOrderAsk) && (!takerBid.isOrderAsk), "Order: Wrong sides"); require(msg.sender == takerBid.taker, "Order: Taker must be the sender"); // Check the maker ask order bytes32 askHash = makerAsk.hash(); _validateOrder(makerAsk, askHash); (bool isExecutionValid, uint256 tokenId, uint256 amount) = IExecutionStrategy(makerAsk.strategy) .canExecuteTakerBid(takerBid, makerAsk); require(isExecutionValid, "Strategy: Execution invalid"); // Update maker ask order status to true (prevents replay) _isUserOrderNonceExecutedOrCancelled[makerAsk.signer][makerAsk.nonce] = true; // Execution part 1/2 _transferFeesAndFunds( makerAsk.strategy, makerAsk.collection, tokenId, makerAsk.currency, msg.sender, makerAsk.signer, takerBid.price, makerAsk.minPercentageToAsk ); // Execution part 2/2 _transferNonFungibleToken(makerAsk.collection, makerAsk.signer, takerBid.taker, tokenId, amount); emit TakerBid( askHash, makerAsk.nonce, takerBid.taker, makerAsk.signer, makerAsk.strategy, makerAsk.currency, makerAsk.collection, tokenId, amount, takerBid.price ); } /** * @notice Match a takerAsk with a makerBid 撮合订单,这里和上面一个的区别是这里是买家询价,上面是卖家挂单,这里的签名者是买家,上面的是卖家 * @param takerAsk taker ask order * @param makerBid maker bid order */ function matchBidWithTakerAsk(OrderTypes.TakerOrder calldata takerAsk, OrderTypes.MakerOrder calldata makerBid) external override nonReentrant { require((!makerBid.isOrderAsk) && (takerAsk.isOrderAsk), "Order: Wrong sides"); require(msg.sender == takerAsk.taker, "Order: Taker must be the sender"); // Check the maker bid order bytes32 bidHash = makerBid.hash(); _validateOrder(makerBid, bidHash); (bool isExecutionValid, uint256 tokenId, uint256 amount) = IExecutionStrategy(makerBid.strategy) .canExecuteTakerAsk(takerAsk, makerBid); require(isExecutionValid, "Strategy: Execution invalid"); // Update maker bid order status to true (prevents replay) _isUserOrderNonceExecutedOrCancelled[makerBid.signer][makerBid.nonce] = true; // Execution part 1/2 _transferNonFungibleToken(makerBid.collection, msg.sender, makerBid.signer, tokenId, amount); // Execution part 2/2 _transferFeesAndFunds( makerBid.strategy, makerBid.collection, tokenId, makerBid.currency, makerBid.signer, takerAsk.taker, takerAsk.price, takerAsk.minPercentageToAsk ); emit TakerAsk( bidHash, makerBid.nonce, takerAsk.taker, makerBid.signer, makerBid.strategy, makerBid.currency, makerBid.collection, tokenId, amount, takerAsk.price ); } //修改周边合约的地址,用于升级或者出bug用 /** * @notice Update currency manager * @param _currencyManager new currency manager address */ function updateCurrencyManager(address _currencyManager) external onlyOwner { require(_currencyManager != address(0), "Owner: Cannot be null address"); currencyManager = ICurrencyManager(_currencyManager); emit NewCurrencyManager(_currencyManager); } /** * @notice Update execution manager * @param _executionManager new execution manager address */ function updateExecutionManager(address _executionManager) external onlyOwner { require(_executionManager != address(0), "Owner: Cannot be null address"); executionManager = IExecutionManager(_executionManager); emit NewExecutionManager(_executionManager); } /** * @notice Update protocol fee and recipient * @param _protocolFeeRecipient new recipient for protocol fees */ function updateProtocolFeeRecipient(address _protocolFeeRecipient) external onlyOwner { protocolFeeRecipient = _protocolFeeRecipient; emit NewProtocolFeeRecipient(_protocolFeeRecipient); } /** * @notice Update royalty fee manager * @param _royaltyFeeManager new fee manager address */ function updateRoyaltyFeeManager(address _royaltyFeeManager) external onlyOwner { require(_royaltyFeeManager != address(0), "Owner: Cannot be null address"); royaltyFeeManager = IRoyaltyFeeManager(_royaltyFeeManager); emit NewRoyaltyFeeManager(_royaltyFeeManager); } /** * @notice Update transfer selector NFT * @param _transferSelectorNFT new transfer selector address */ function updateTransferSelectorNFT(address _transferSelectorNFT) external onlyOwner { require(_transferSelectorNFT != address(0), "Owner: Cannot be null address"); transferSelectorNFT = ITransferSelectorNFT(_transferSelectorNFT); emit NewTransferSelectorNFT(_transferSelectorNFT); } //订单是否取消 /** * @notice Check whether user order nonce is executed or cancelled * @param user address of user * @param orderNonce nonce of the order */ function isUserOrderNonceExecutedOrCancelled(address user, uint256 orderNonce) external view returns (bool) { return _isUserOrderNonceExecutedOrCancelled[user][orderNonce]; } /** N个费用处理,转移钱,用传入的代币交易 * @notice Transfer fees and funds to royalty recipient, protocol, and seller * @param strategy address of the execution strategy * @param collection non fungible token address for the transfer * @param tokenId tokenId * @param currency currency being used for the purchase (e.g., WETH/USDC) * @param from sender of the funds * @param to seller's recipient * @param amount amount being transferred (in currency) * @param minPercentageToAsk minimum percentage of the gross amount that goes to ask */ function _transferFeesAndFunds( address strategy, address collection, uint256 tokenId, address currency, address from, address to, uint256 amount, uint256 minPercentageToAsk ) internal { // Initialize the final amount that is transferred to seller //最终的给卖家的钱,会经过一层又一层的吃拿卡要 uint256 finalSellerAmount = amount; // 1. Protocol fee 官方手续费 { uint256 protocolFeeAmount = _calculateProtocolFee(strategy, amount); // Check if the protocol fee is different than 0 for this strategy if ((protocolFeeRecipient != address(0)) && (protocolFeeAmount != 0)) { IERC20(currency).safeTransferFrom(from, protocolFeeRecipient, protocolFeeAmount); finalSellerAmount -= protocolFeeAmount; } } // 2. Royalty fee 版权费 { (address royaltyFeeRecipient, uint256 royaltyFeeAmount) = royaltyFeeManager .calculateRoyaltyFeeAndGetRecipient(collection, tokenId, amount); // Check if there is a royalty fee and that it is different to 0 if ((royaltyFeeRecipient != address(0)) && (royaltyFeeAmount != 0)) { IERC20(currency).safeTransferFrom(from, royaltyFeeRecipient, royaltyFeeAmount); finalSellerAmount -= royaltyFeeAmount; emit RoyaltyPayment(collection, tokenId, royaltyFeeRecipient, currency, royaltyFeeAmount); } } //保证给卖家的钱的最小值,要有人性,手动狗头 require((finalSellerAmount * 10000) >= (minPercentageToAsk * amount), "Fees: Higher than expected"); // 3. Transfer final amount (post-fees) to seller { IERC20(currency).safeTransferFrom(from, to, finalSellerAmount); } } /** 和上面的类似,只是用了weth * @notice Transfer fees and funds to royalty recipient, protocol, and seller * @param strategy address of the execution strategy * @param collection non fungible token address for the transfer * @param tokenId tokenId * @param to seller's recipient * @param amount amount being transferred (in currency) * @param minPercentageToAsk minimum percentage of the gross amount that goes to ask */ function _transferFeesAndFundsWithWETH( address strategy, address collection, uint256 tokenId, address to, uint256 amount, uint256 minPercentageToAsk ) internal { // Initialize the final amount that is transferred to seller uint256 finalSellerAmount = amount; // 1. Protocol fee { uint256 protocolFeeAmount = _calculateProtocolFee(strategy, amount); // Check if the protocol fee is different than 0 for this strategy if ((protocolFeeRecipient != address(0)) && (protocolFeeAmount != 0)) { IERC20(WETH).safeTransfer(protocolFeeRecipient, protocolFeeAmount); finalSellerAmount -= protocolFeeAmount; } } // 2. Royalty fee { (address royaltyFeeRecipient, uint256 royaltyFeeAmount) = royaltyFeeManager .calculateRoyaltyFeeAndGetRecipient(collection, tokenId, amount); // Check if there is a royalty fee and that it is different to 0 if ((royaltyFeeRecipient != address(0)) && (royaltyFeeAmount != 0)) { IERC20(WETH).safeTransfer(royaltyFeeRecipient, royaltyFeeAmount); finalSellerAmount -= royaltyFeeAmount; emit RoyaltyPayment(collection, tokenId, royaltyFeeRecipient, address(WETH), royaltyFeeAmount); } } require((finalSellerAmount * 10000) >= (minPercentageToAsk * amount), "Fees: Higher than expected"); // 3. Transfer final amount (post-fees) to seller { IERC20(WETH).safeTransfer(to, finalSellerAmount); } } /** * @notice Transfer NFT 那到官方规定的NFT转移合约,转移NFT * @param collection address of the token collection * @param from address of the sender * @param to address of the recipient * @param tokenId tokenId * @param amount amount of tokens (1 for ERC721, 1+ for ERC1155) * @dev For ERC721, amount is not used */ function _transferNonFungibleToken( address collection, address from, address to, uint256 tokenId, uint256 amount ) internal { // Retrieve the transfer manager address address transferManager = transferSelectorNFT.checkTransferManagerForToken(collection); // If no transfer manager found, it returns address(0) require(transferManager != address(0), "Transfer: No NFT transfer manager available"); // If one is found, transfer the token ITransferManagerNFT(transferManager).transferNonFungibleToken(collection, from, to, tokenId, amount); } /** * @notice Calculate protocol fee for an execution strategy * @param executionStrategy strategy * @param amount amount to transfer */ function _calculateProtocolFee(address executionStrategy, uint256 amount) internal view returns (uint256) { uint256 protocolFee = IExecutionStrategy(executionStrategy).viewProtocolFee(); return (protocolFee * amount) / 10000; } /** 校验订单 * @notice Verify the validity of the maker order * @param makerOrder maker order * @param orderHash computed hash for the order */ function _validateOrder(OrderTypes.MakerOrder calldata makerOrder, bytes32 orderHash) internal view { // Verify whether order nonce has expired require( //订单是否取消,并且订单的nonce>合约记录的订单签名者的nonce值,因为可能其他人恶意取消订单,这里取签名者保证别人恶意取消没用 (!_isUserOrderNonceExecutedOrCancelled[makerOrder.signer][makerOrder.nonce]) && (makerOrder.nonce >= userMinOrderNonce[makerOrder.signer]), "Order: Matching order expired" ); // Verify the signer is not address(0) require(makerOrder.signer != address(0), "Order: Invalid signer"); // Verify the amount is not 0 require(makerOrder.amount > 0, "Order: Amount cannot be 0"); // Verify the validity of the signature require( SignatureChecker.verify( orderHash, makerOrder.signer, makerOrder.v, makerOrder.r, makerOrder.s, DOMAIN_SEPARATOR ), "Signature: Invalid" ); // Verify whether the currency is whitelisted require(currencyManager.isCurrencyWhitelisted(makerOrder.currency), "Currency: Not whitelisted"); // Verify whether strategy can be executed require(executionManager.isStrategyWhitelisted(makerOrder.strategy), "Strategy: Not whitelisted"); } } ## Publication Information - [point](https://paragraph.com/@point/): Publication homepage - [All Posts](https://paragraph.com/@point/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@point): Subscribe to updates