# Looksrare合约讲解

By [point](https://paragraph.com/@point) · 2022-05-02

---

因为做了一个NFT市场，所以对opensea，rarible，looksrare合约都研究了下，记录如下

合约架构

[https://etherscan.io/accounts/label/looksrare](https://etherscan.io/accounts/label/looksrare)

三家的合约高度相似，只不过是将代码重构的好看一些。再加些最近才有的EIP。looksare基本上是把能拆的都拆了，比如手续费、版权（EIP2981）、transfer，对比下另外两家，舒服很多

业务逻辑

1.  基本都是链下操作，只有在真正mint nft，撮合交易成功，取消交易才会上链。甚至现在mint nft都不上链，第一次transfer时才会mint nft。进一步减少gas消耗。
    
2.  创建订单：使用订单参数+salt进行EIP712 signature，transfer时在合约中验证签名，这样能减少Gas和保证订单没问题。。。。。才怪，open就出了有人拿到signature后，等订单过期了再去调用合约，把人家的nft低价撸走的bug。
    
3.  取消订单：链上不保存创建订单。就像上面说的，防止bug出现，就要链上保存取消的订单，但是opensea没有判断 currentTime>expireTime 所以拿过期的signature还是可以完成交易
    
4.  取消所有订单，减少gas，理论上也不是取消所有，一次的上限是500000
    
5.  版权费：使用EIP2981，原因是像opensea，有版权费上限，如果我要收100%的版权费，没辙，所以要一个链上保存版权费的功能
    

代码结构

1.  共两个cancelOrder
    
2.  三个撮合函数
    
3.  好几个update周边合约函数
    
4.  两个transfer token函数
    
5.  一个transfer nft函数
    
6.  一个校验函数
    

    //取消订单
    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");
        }
    }

---

*Originally published on [point](https://paragraph.com/@point/looksrare)*
