Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
109 changes: 76 additions & 33 deletions contracts/LooksRareExchange.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ pragma solidity ^0.8.0;
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";
import {Context} from "@openzeppelin/contracts/utils/Context.sol";
import {ERC2771Context} from "@openzeppelin/contracts/metatx/ERC2771Context.sol";

// LooksRare interfaces
import {ICurrencyManager} from "./interfaces/ICurrencyManager.sol";
Expand Down Expand Up @@ -57,7 +59,7 @@ LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRo' 'oLOOKSRARELOOKSRLOOKSRARELOOKS
LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARE,. .,dRELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR
LOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSRARELOOKSRARELOOKSRLOOKSRARELOOKSRARELOOKSR
*/
contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable, ERC2771Context {
using SafeERC20 for IERC20;

using OrderTypes for OrderTypes.MakerOrder;
Expand All @@ -74,7 +76,10 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
ITransferSelectorNFT public transferSelectorNFT;

mapping(address => uint256) public userMinOrderNonce;
mapping(address => mapping(uint256 => bool)) private _isUserOrderNonceExecutedOrCancelled;
// maker => nonce => amount
// it is an executed amount
// amount == type(uint256).max means that an offer was cancelled or fully executed
mapping(address => mapping(uint256 => uint256)) private _isUserOrderNonceExecutedOrCancelled;

event CancelAllOrders(address indexed user, uint256 newMinNonce);
event CancelMultipleOrders(address indexed user, uint256[] orderNonces);
Expand Down Expand Up @@ -131,8 +136,9 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
address _executionManager,
address _royaltyFeeManager,
address _WETH,
address _protocolFeeRecipient
) {
address _protocolFeeRecipient,
address _trustedForwarder
) ERC2771Context(_trustedForwarder) {
// Calculate the domain separator
DOMAIN_SEPARATOR = keccak256(
abi.encode(
Expand All @@ -156,26 +162,29 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
* @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;
address msgSender = _msgSender();
require(minNonce > userMinOrderNonce[msgSender], "Cancel: Order nonce lower than current");
require(minNonce < userMinOrderNonce[msgSender] + 500000, "Cancel: Cannot cancel more orders");
userMinOrderNonce[msgSender] = minNonce;

emit CancelAllOrders(msg.sender, minNonce);
emit CancelAllOrders(msgSender, minNonce);
}

/**
* @notice Cancel maker orders
* @param orderNonces array of order nonces
*/
function cancelMultipleMakerOrders(uint256[] calldata orderNonces) external {
address msgSender = _msgSender();

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;
require(orderNonces[i] >= userMinOrderNonce[msgSender], "Cancel: Order nonce lower than current");
_isUserOrderNonceExecutedOrCancelled[msgSender][orderNonces[i]] = type(uint256).max;
}

emit CancelMultipleOrders(msg.sender, orderNonces);
emit CancelMultipleOrders(msgSender, orderNonces);
}

/**
Expand All @@ -189,11 +198,11 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
) external payable override nonReentrant {
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");
require(_msgSender() == takerBid.taker, "Order: Taker must be the sender");

// 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));
IERC20(WETH).safeTransferFrom(takerBid.taker, address(this), (takerBid.price - msg.value));
} else {
require(takerBid.price == msg.value, "Order: Msg.value too high");
}
Expand All @@ -209,10 +218,17 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
(bool isExecutionValid, uint256 tokenId, uint256 amount) = IExecutionStrategy(makerAsk.strategy)
.canExecuteTakerBid(takerBid, makerAsk);

require(isExecutionValid, "Strategy: Execution invalid");
unchecked {
require(isExecutionValid, "Strategy: Execution invalid");
uint256 newUsedAmount = _isUserOrderNonceExecutedOrCancelled[makerAsk.signer][makerAsk.nonce] + amount;
require(newUsedAmount >= amount, "Strategy: Execution invalid");
require(makerAsk.amount >= newUsedAmount, "Strategy: Execution invalid");

// Update maker ask order status to true (prevents replay)
_isUserOrderNonceExecutedOrCancelled[makerAsk.signer][makerAsk.nonce] = true;
// Update maker ask order status to true (prevents replay)
_isUserOrderNonceExecutedOrCancelled[makerAsk.signer][makerAsk.nonce] = makerAsk.amount == newUsedAmount
? type(uint256).max
: newUsedAmount;
}

// Execution part 1/2
_transferFeesAndFundsWithWETH(
Expand Down Expand Up @@ -252,7 +268,7 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
nonReentrant
{
require((makerAsk.isOrderAsk) && (!takerBid.isOrderAsk), "Order: Wrong sides");
require(msg.sender == takerBid.taker, "Order: Taker must be the sender");
require(_msgSender() == takerBid.taker, "Order: Taker must be the sender");

// Check the maker ask order
bytes32 askHash = makerAsk.hash();
Expand All @@ -263,16 +279,24 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {

require(isExecutionValid, "Strategy: Execution invalid");

// Update maker ask order status to true (prevents replay)
_isUserOrderNonceExecutedOrCancelled[makerAsk.signer][makerAsk.nonce] = true;
unchecked {
uint256 newUsedAmount = _isUserOrderNonceExecutedOrCancelled[makerAsk.signer][makerAsk.nonce] + amount;
require(newUsedAmount >= amount, "Strategy: Execution invalid");
require(makerAsk.amount >= newUsedAmount, "Strategy: Execution invalid");

// Update maker ask order status to true (prevents replay)
_isUserOrderNonceExecutedOrCancelled[makerAsk.signer][makerAsk.nonce] = makerAsk.amount == newUsedAmount
? type(uint256).max
: newUsedAmount;
}

// Execution part 1/2
_transferFeesAndFunds(
makerAsk.strategy,
makerAsk.collection,
tokenId,
makerAsk.currency,
msg.sender,
takerBid.taker,
makerAsk.signer,
takerBid.price,
makerAsk.minPercentageToAsk
Expand Down Expand Up @@ -306,7 +330,7 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
nonReentrant
{
require((!makerBid.isOrderAsk) && (takerAsk.isOrderAsk), "Order: Wrong sides");
require(msg.sender == takerAsk.taker, "Order: Taker must be the sender");
require(_msgSender() == takerAsk.taker, "Order: Taker must be the sender");

// Check the maker bid order
bytes32 bidHash = makerBid.hash();
Expand All @@ -317,11 +341,19 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {

require(isExecutionValid, "Strategy: Execution invalid");

// Update maker bid order status to true (prevents replay)
_isUserOrderNonceExecutedOrCancelled[makerBid.signer][makerBid.nonce] = true;
unchecked {
uint256 newUsedAmount = _isUserOrderNonceExecutedOrCancelled[makerBid.signer][makerBid.nonce] + amount;
require(newUsedAmount >= amount, "Strategy: Excessive amount"); // overflow check
require(makerBid.amount >= newUsedAmount, "Strategy: Excessive amount");

// Update maker bid order status to true (prevents replay)
_isUserOrderNonceExecutedOrCancelled[makerBid.signer][makerBid.nonce] = makerBid.amount == newUsedAmount
? type(uint256).max
: newUsedAmount;
}

// Execution part 1/2
_transferNonFungibleToken(makerBid.collection, msg.sender, makerBid.signer, tokenId, amount);
_transferNonFungibleToken(makerBid.collection, takerAsk.taker, makerBid.signer, tokenId, amount);

// Execution part 2/2
_transferFeesAndFunds(
Expand Down Expand Up @@ -405,6 +437,16 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
* @param orderNonce nonce of the order
*/
function isUserOrderNonceExecutedOrCancelled(address user, uint256 orderNonce) external view returns (bool) {
return _isUserOrderNonceExecutedOrCancelled[user][orderNonce] == type(uint256).max;
}

/**
* @notice Check an executed/used amount of an order.
* type(uint256).max means that the order was cancelled or fully used.
* @param user address of user
* @param orderNonce nonce of the order
*/
function executedAmount(address user, uint256 orderNonce) external view returns (uint256) {
return _isUserOrderNonceExecutedOrCancelled[user][orderNonce];
}

Expand Down Expand Up @@ -562,7 +604,7 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
function _validateOrder(OrderTypes.MakerOrder calldata makerOrder, bytes32 orderHash) internal view {
// Verify whether order nonce has expired
require(
(!_isUserOrderNonceExecutedOrCancelled[makerOrder.signer][makerOrder.nonce]) &&
(_isUserOrderNonceExecutedOrCancelled[makerOrder.signer][makerOrder.nonce] < type(uint256).max) &&
(makerOrder.nonce >= userMinOrderNonce[makerOrder.signer]),
"Order: Matching order expired"
);
Expand All @@ -575,14 +617,7 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {

// Verify the validity of the signature
require(
SignatureChecker.verify(
orderHash,
makerOrder.signer,
makerOrder.v,
makerOrder.r,
makerOrder.s,
DOMAIN_SEPARATOR
),
SignatureChecker.verify(orderHash, makerOrder.signer, makerOrder.signature, DOMAIN_SEPARATOR),
"Signature: Invalid"
);

Expand All @@ -592,4 +627,12 @@ contract LooksRareExchange is ILooksRareExchange, ReentrancyGuard, Ownable {
// Verify whether strategy can be executed
require(executionManager.isStrategyWhitelisted(makerOrder.strategy), "Strategy: Not whitelisted");
}

function _msgData() internal view virtual override(Context, ERC2771Context) returns (bytes calldata) {
return ERC2771Context._msgData();
}

function _msgSender() internal view virtual override(Context, ERC2771Context) returns (address sender) {
return ERC2771Context._msgSender();
}
}
135 changes: 135 additions & 0 deletions contracts/executionStrategies/StrategyPartialSaleForFixedPrice.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {OrderTypes} from "../libraries/OrderTypes.sol";
import {IExecutionStrategy} from "../interfaces/IExecutionStrategy.sol";

/**
* @title StrategyPartialSaleForFixedPrice
* @notice Strategy that executes an order at a fixed price that
* can be taken either by a bid or an ask.
* Partial means that a taker can buy/sell a partial amount.
*/
contract StrategyPartialSaleForFixedPrice is IExecutionStrategy {
uint256 public immutable PROTOCOL_FEE;

/**
* @notice Constructor
* @param _protocolFee protocol fee (200 --> 2%, 400 --> 4%)
*/
constructor(uint256 _protocolFee) {
PROTOCOL_FEE = _protocolFee;
}

/**
* @notice Check whether a taker ask order can be executed against a maker bid
* @param takerAsk taker ask order
* @param makerBid maker bid order
* @return (whether strategy can be executed, tokenId to execute, amount of tokens to execute)
*/
function canExecuteTakerAsk(OrderTypes.TakerOrder calldata takerAsk, OrderTypes.MakerOrder calldata makerBid)
external
view
override
returns (
bool,
uint256,
uint256
)
{
uint256 takerAskAmount = abi.decode(takerAsk.params, (uint256));
return (
(_verifyPrice(takerAsk.price, takerAskAmount, makerBid.price, makerBid.amount) &&
(makerBid.tokenId == takerAsk.tokenId) &&
(makerBid.startTime <= block.timestamp) &&
(makerBid.endTime >= block.timestamp) &&
(takerAskAmount > 0) &&
(makerBid.amount >= takerAskAmount)),
makerBid.tokenId,
takerAskAmount
);
}

/**
* @notice Check whether a taker bid order can be executed against a maker ask
* @param takerBid taker bid order
* @param makerAsk maker ask order
* @return (whether strategy can be executed, tokenId to execute, amount of tokens to execute)
*/
function canExecuteTakerBid(OrderTypes.TakerOrder calldata takerBid, OrderTypes.MakerOrder calldata makerAsk)
external
view
override
returns (
bool,
uint256,
uint256
)
{
uint256 takerBidAmount = abi.decode(takerBid.params, (uint256));
return (
(_verifyPrice(makerAsk.price, makerAsk.amount, takerBid.price, takerBidAmount) &&
(makerAsk.tokenId == takerBid.tokenId) &&
(makerAsk.startTime <= block.timestamp) &&
(makerAsk.endTime >= block.timestamp) &&
(takerBidAmount > 0) &&
(makerAsk.amount >= takerBidAmount)),
makerAsk.tokenId,
takerBidAmount
);
}

/**
* @dev checks that askPrice/askAmount <= bidPrice/bidAmount
* which is askPrice * bidAmount <= bidPrice * askAmount
* note that these are uint256 * uint256 = uint512
* the function supports uint256 overflows and extreme values
* a lot of low level arithmetics
*/
function _verifyPrice(
uint256 askPrice,
uint256 askAmount,
uint256 bidPrice,
uint256 bidAmount
) private pure returns (bool result) {
// none operation oferflows
unchecked {
// low 128 bits
uint256 left = (askPrice & (2**128 - 1)) * (bidAmount & (2**128 - 1));
uint256 leftHigh = left >> 128;
left &= 2**128 - 1;
uint256 right = (bidPrice & (2**128 - 1)) * (askAmount & (2**128 - 1));
uint256 rightHigh = right >> 128;
right &= 2**128 - 1;
result = left <= right;

// middle 128 bits
left = leftHigh + (askPrice >> 128) * (bidAmount & (2**128 - 1));
leftHigh = left >> 128;
left &= 2**128 - 1;
left += (askPrice & (2**128 - 1)) * (bidAmount >> 128);
leftHigh += left >> 128;
left &= 2**128 - 1;
right = rightHigh + (bidPrice >> 128) * (askAmount & (2**128 - 1));
rightHigh = right >> 128;
right &= 2**128 - 1;
right += (bidPrice & (2**128 - 1)) * (askAmount >> 128);
rightHigh += right >> 128;
right &= 2**128 - 1;
result = left < right || (left == right && result);

// high 256 bits
left = leftHigh + (askPrice >> 128) * (bidAmount >> 128);
right = rightHigh + (bidPrice >> 128) * (askAmount >> 128);
result = left < right || (left == right && result);
}
}

/**
* @notice Return protocol fee for this strategy
* @return protocol fee
*/
function viewProtocolFee() external view override returns (uint256) {
return PROTOCOL_FEE;
}
}
6 changes: 2 additions & 4 deletions contracts/libraries/OrderTypes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,12 @@ library OrderTypes {
uint256 endTime; // endTime in timestamp
uint256 minPercentageToAsk; // slippage protection (9000 --> 90% of the final price must return to ask)
bytes params; // additional parameters
uint8 v; // v: parameter (27 or 28)
bytes32 r; // r: parameter
bytes32 s; // s: parameter
bytes signature; // the signature of the above
}

struct TakerOrder {
bool isOrderAsk; // true --> ask / false --> bid
address taker; // msg.sender
address taker; // _msgSender()
uint256 price; // final price for the purchase
uint256 tokenId;
uint256 minPercentageToAsk; // // slippage protection (9000 --> 90% of the final price must return to ask)
Expand Down
Loading