From 7a7e95157ba58c7a5a77df48c5a5ca75903a009b Mon Sep 17 00:00:00 2001 From: Andres Adjimann Date: Mon, 2 Oct 2023 17:48:00 -0300 Subject: [PATCH] feat: remove BUY and SELL orders --- .../contracts/exchange/ExchangeCore.sol | 86 +++-------- .../contracts/exchange/libraries/LibFill.sol | 39 +++-- .../libraries}/LibMath.sol | 0 .../exchange/libraries/LibOrderData.md | 75 ---------- .../libraries/LibOrderDataGeneric.sol | 87 ----------- .../contracts/lib-order/LibOrder.md | 4 - .../contracts/lib-order/LibOrder.sol | 65 ++------ .../contracts/lib-order/LibOrderData.md | 75 ---------- .../contracts/lib-order/LibOrderData.sol | 30 ---- .../contracts/lib-part/LibPart.sol | 2 +- .../contracts/mocks/LibFillTest.sol | 13 +- .../contracts/mocks/LibOrderTest.sol | 8 - .../transfer-manager/TransferExecutor.sol | 3 - .../transfer-manager/lib/LibTransfer.sol | 10 -- .../test/exchange/Exchange.test.ts | 3 +- .../test/exchange/OrderValidator.test.ts | 3 +- packages/marketplace/test/utils/order.ts | 140 ++++++------------ .../test/utils/raribleTestHelper.ts | 52 ------- packages/marketplace/test/utils/signature.ts | 46 ------ 19 files changed, 111 insertions(+), 630 deletions(-) rename packages/marketplace/contracts/{lib-order => exchange/libraries}/LibMath.sol (100%) delete mode 100644 packages/marketplace/contracts/exchange/libraries/LibOrderData.md delete mode 100644 packages/marketplace/contracts/exchange/libraries/LibOrderDataGeneric.sol delete mode 100644 packages/marketplace/contracts/lib-order/LibOrderData.md delete mode 100644 packages/marketplace/contracts/lib-order/LibOrderData.sol delete mode 100644 packages/marketplace/contracts/transfer-manager/lib/LibTransfer.sol delete mode 100644 packages/marketplace/test/utils/raribleTestHelper.ts delete mode 100644 packages/marketplace/test/utils/signature.ts diff --git a/packages/marketplace/contracts/exchange/ExchangeCore.sol b/packages/marketplace/contracts/exchange/ExchangeCore.sol index 7d9977196e..30ba54b815 100644 --- a/packages/marketplace/contracts/exchange/ExchangeCore.sol +++ b/packages/marketplace/contracts/exchange/ExchangeCore.sol @@ -5,18 +5,18 @@ pragma solidity 0.8.21; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; import {LibFill} from "./libraries/LibFill.sol"; import {IAssetMatcher} from "../interfaces/IAssetMatcher.sol"; -import {TransferExecutor, LibTransfer} from "../transfer-manager/TransferExecutor.sol"; -import {LibDeal, LibAsset} from "../transfer-manager/lib/LibDeal.sol"; +import {TransferExecutor} from "../transfer-manager/TransferExecutor.sol"; import {LibFeeSide} from "../transfer-manager/lib/LibFeeSide.sol"; -import {LibOrderDataGeneric, LibOrder} from "./libraries/LibOrderDataGeneric.sol"; +import {LibDeal} from "../transfer-manager/lib/LibDeal.sol"; +import {LibAsset} from "../lib-asset/LibAsset.sol"; +import {LibOrder} from "../lib-order/LibOrder.sol"; +import {LibPart} from "../lib-part/LibPart.sol"; import {ITransferManager} from "../transfer-manager/interfaces/ITransferManager.sol"; import {IOrderValidator} from "../interfaces/IOrderValidator.sol"; /// @notice ExchangeCore contract /// @dev contains the main functions for the marketplace abstract contract ExchangeCore is Initializable, TransferExecutor, ITransferManager { - using LibTransfer for address payable; - // a list of left/right orders that match each other // left and right are symmetrical except for fees that are taken from left side first. struct ExchangeMatch { @@ -159,99 +159,57 @@ abstract contract ExchangeCore is Initializable, TransferExecutor, ITransferMana orderRight ); - ( - LibOrderDataGeneric.GenericOrderData memory leftOrderData, - LibOrderDataGeneric.GenericOrderData memory rightOrderData, - LibFill.FillResult memory newFill - ) = _parseOrdersSetFillEmitMatch(sender, orderLeft, orderRight); - + LibFill.FillResult memory newFill = _parseOrdersSetFillEmitMatch(sender, orderLeft, orderRight); doTransfers( LibDeal.DealSide({ asset: LibAsset.Asset({assetType: makeMatch, value: newFill.leftValue}), - payouts: leftOrderData.payouts, + payouts: _payToMaker(orderLeft), from: orderLeft.maker }), LibDeal.DealSide({ asset: LibAsset.Asset(takeMatch, newFill.rightValue), - payouts: rightOrderData.payouts, + payouts: _payToMaker(orderRight), from: orderRight.maker }), LibFeeSide.getFeeSide(makeMatch.assetClass, takeMatch.assetClass) ); } + /// @notice create a payout array that pays to maker 100% + /// @param order the order from which the maker is taken + /// @return an array with just one entry that pays to order.maker + function _payToMaker(LibOrder.Order memory order) internal pure returns (LibPart.Part[] memory) { + LibPart.Part[] memory payout = new LibPart.Part[](1); + payout[0].account = order.maker; + payout[0].value = 10000; + return payout; + } + /// @notice parse orders with LibOrderDataGeneric parse() to get the order data, then create a new fill with setFillEmitMatch() /// @param sender the message sender /// @param orderLeft left order /// @param orderRight right order - /// @return leftOrderData generic order data from left order - /// @return rightOrderData generic order data from right order /// @return newFill fill result function _parseOrdersSetFillEmitMatch( address sender, LibOrder.Order calldata orderLeft, LibOrder.Order calldata orderRight - ) - internal - returns ( - LibOrderDataGeneric.GenericOrderData memory leftOrderData, - LibOrderDataGeneric.GenericOrderData memory rightOrderData, - LibFill.FillResult memory newFill - ) - { + ) internal returns (LibFill.FillResult memory newFill) { bytes32 leftOrderKeyHash = LibOrder.hashKey(orderLeft); bytes32 rightOrderKeyHash = LibOrder.hashKey(orderRight); - leftOrderData = LibOrderDataGeneric.parse(orderLeft); - rightOrderData = LibOrderDataGeneric.parse(orderRight); - - newFill = _setFillEmitMatch( - sender, - orderLeft, - orderRight, - leftOrderKeyHash, - rightOrderKeyHash, - leftOrderData.isMakeFill, - rightOrderData.isMakeFill - ); - } - - /// @notice calculates fills for the matched orders and set them in "fills" mapping - /// @param sender the message sender - /// @param orderLeft left order of the match - /// @param orderRight right order of the match - /// @param leftMakeFill true if the left orders uses make-side fills, false otherwise - /// @param rightMakeFill true if the right orders uses make-side fills, false otherwise - /// @return newFill returns change in orders' fills by the match - function _setFillEmitMatch( - address sender, - LibOrder.Order calldata orderLeft, - LibOrder.Order calldata orderRight, - bytes32 leftOrderKeyHash, - bytes32 rightOrderKeyHash, - bool leftMakeFill, - bool rightMakeFill - ) internal returns (LibFill.FillResult memory newFill) { uint256 leftOrderFill = _getOrderFill(orderLeft.salt, leftOrderKeyHash); uint256 rightOrderFill = _getOrderFill(orderRight.salt, rightOrderKeyHash); - newFill = LibFill.fillOrder(orderLeft, orderRight, leftOrderFill, rightOrderFill, leftMakeFill, rightMakeFill); + newFill = LibFill.fillOrder(orderLeft, orderRight, leftOrderFill, rightOrderFill); require(newFill.rightValue > 0 && newFill.leftValue > 0, "nothing to fill"); if (orderLeft.salt != 0) { - if (leftMakeFill) { - fills[leftOrderKeyHash] = leftOrderFill + newFill.leftValue; - } else { - fills[leftOrderKeyHash] = leftOrderFill + newFill.rightValue; - } + fills[leftOrderKeyHash] = leftOrderFill + newFill.rightValue; } if (orderRight.salt != 0) { - if (rightMakeFill) { - fills[rightOrderKeyHash] = rightOrderFill + newFill.rightValue; - } else { - fills[rightOrderKeyHash] = rightOrderFill + newFill.leftValue; - } + fills[rightOrderKeyHash] = rightOrderFill + newFill.leftValue; } emit Match({ diff --git a/packages/marketplace/contracts/exchange/libraries/LibFill.sol b/packages/marketplace/contracts/exchange/libraries/LibFill.sol index 0022baf128..1b40a197ec 100644 --- a/packages/marketplace/contracts/exchange/libraries/LibFill.sol +++ b/packages/marketplace/contracts/exchange/libraries/LibFill.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.21; -import {LibOrder, LibMath} from "../../lib-order/LibOrder.sol"; +import {LibOrder} from "../../lib-order/LibOrder.sol"; +import {LibMath} from "./LibMath.sol"; /// @title This library provides `fillOrder` function. /// @notice It calculates fill of both orders (part of the Order that can be filled). @@ -11,18 +12,11 @@ library LibFill { uint256 rightValue; } - struct IsMakeFill { - bool leftMake; - bool rightMake; - } - /// @notice Should return filled values /// @param leftOrder left order /// @param rightOrder right order /// @param leftOrderFill current fill of the left order (0 if order is unfilled) /// @param rightOrderFill current fill of the right order (0 if order is unfilled) - /// @param leftIsMakeFill true if left orders fill is calculated from the make side, false if from the take side - /// @param rightIsMakeFill true if right orders fill is calculated from the make side, false if from the take side /// @dev We have 3 cases, 1st: left order should be fully filled /// @dev 2nd: right order should be fully filled or 3d: both should be fully filled if required values are the same /// @return the fill result of both orders @@ -30,20 +24,10 @@ library LibFill { LibOrder.Order calldata leftOrder, LibOrder.Order calldata rightOrder, uint256 leftOrderFill, - uint256 rightOrderFill, - bool leftIsMakeFill, - bool rightIsMakeFill + uint256 rightOrderFill ) internal pure returns (FillResult memory) { - (uint256 leftMakeValue, uint256 leftTakeValue) = LibOrder.calculateRemaining( - leftOrder, - leftOrderFill, - leftIsMakeFill - ); - (uint256 rightMakeValue, uint256 rightTakeValue) = LibOrder.calculateRemaining( - rightOrder, - rightOrderFill, - rightIsMakeFill - ); + (uint256 leftMakeValue, uint256 leftTakeValue) = calculateRemaining(leftOrder, leftOrderFill); + (uint256 rightMakeValue, uint256 rightTakeValue) = calculateRemaining(rightOrder, rightOrderFill); if (rightTakeValue > leftMakeValue) { return fillLeft(leftMakeValue, leftTakeValue, rightOrder.makeAsset.value, rightOrder.takeAsset.value); @@ -51,6 +35,19 @@ library LibFill { return fillRight(leftOrder.makeAsset.value, leftOrder.takeAsset.value, rightMakeValue, rightTakeValue); } + /// @notice calculate the remaining fill from orders + /// @param order order that we will calculate the remaining fill + /// @param fill to be subtracted + /// @return makeValue remaining fill from make side + /// @return takeValue remaining fill from take side + function calculateRemaining( + LibOrder.Order calldata order, + uint256 fill + ) internal pure returns (uint256 makeValue, uint256 takeValue) { + takeValue = order.takeAsset.value - fill; + makeValue = LibMath.safeGetPartialAmountFloor(order.makeAsset.value, order.takeAsset.value, takeValue); + } + function fillRight( uint256 leftMakeValue, uint256 leftTakeValue, diff --git a/packages/marketplace/contracts/lib-order/LibMath.sol b/packages/marketplace/contracts/exchange/libraries/LibMath.sol similarity index 100% rename from packages/marketplace/contracts/lib-order/LibMath.sol rename to packages/marketplace/contracts/exchange/libraries/LibMath.sol diff --git a/packages/marketplace/contracts/exchange/libraries/LibOrderData.md b/packages/marketplace/contracts/exchange/libraries/LibOrderData.md deleted file mode 100644 index 1e29d2d9e7..0000000000 --- a/packages/marketplace/contracts/exchange/libraries/LibOrderData.md +++ /dev/null @@ -1,75 +0,0 @@ -# Features - -## Data types, corresponding transfers/fees logic -`Order` data can be generic. `dataType` field defines format of that data. -- `"0xffffffff"` or `"no type"` - - no data - - fees logic - - no fees -- `"V1"` - - fields - - `LibPart.Part[] payouts` - - array of payouts, i.e. how takeAsset of the order is going to be distributed. (usually 100% goes to order.maker, can be something like 50% goes to maker, 50% to someone else. it can be divided in any other way) - - `LibPart.Part[] originFees` - - additional fees (e.g. 5% of the payment goes to additional address) - - fees logic - - `originFees` from buy-order is taken from the buyer, `originFees` from sell-order is taken from the seller. e.g. sell order is `1 ERC721` => `100 ETH`, buy order is `100 ETH` => `1 ERC721`. Buy order has `originFees` = [`{5% to addr1}`,`{10% to addr2}`]. Sell order has `originFees` = [`{5% to addr3}`]. Then, total amount that buyer needs to send is `100 ETH` + `5%*100ETH` + `10%*100ETH` = `115 ETH` (15% more than order value, buy-order `origin fees` are added. So buyer pays for their `origin fees`). From this amount `5 ETH` will be transferred to addr1, `10 ETH` to addr2 (now we have `100ETH` left) and `5ETH` to addr3 (it is seller `origin fee`, so it is taken from their part). - - after that NFT `royalties` are taken - - what's left after that is distributed according to sell-order `payouts` -- `"V2"` - - fields - - `LibPart.Part[] payouts`, works the same as in `V1` orders - - `LibPart.Part[] originFees`, works the same as in `V1` orders - - `bool isMakeFill` - - if false, order's `fill` (what part of order is completed, stored on-chain) is calculated from take side (in `V1` orders it always works like that) - - if true, `fill` is calculated from the make side of the order - - fees logic, works the same as in `V1` orders -- `"V3"` two types of `V3` orders. - - `"V3_BUY"` - - fields - - `uint payouts`, works the same as in `V1` orders, but there is only 1 value and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `uint originFeeFirst`, instead of array there can only be 2 originFee in different variables (originFeeFirst and originFeeSecond), and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `uint originFeeSecond`, instead of array there can only be 2 originFee in different variables (originFeeFirst and originFeeSecond), and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `bytes32 marketplaceMarker`, bytes32 id marketplace, which generate this order - - `"V3_SELL"` - - fields - - `uint payouts`, works the same as in `V1` orders, but there is only 1 value and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `uint originFeeFirst`, instead of array there can only be 2 originFee in different variables (originFeeFirst and originFeeSecond), and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `uint originFeeSecond`, instead of array there can only be 2 originFee in different variables (originFeeFirst and originFeeSecond), and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `uint maxFeesBasePoint` - - maximum amount of fees that can be taken from payment (e.g. 10%) - - chosen by seller, that's why it's only present in `V3_SELL` orders - - `maxFeesBasePoint` should be more than `0` - - `maxFeesBasePoint` should not be bigger than `10%` - - `bytes32 marketplaceMarker`, bytes32 id marketplace, which generate this order - - `V3` orders can only be matched if buy-order is `V3_BUY` and the sell-order is `V3_SELL` - - `V3` orders don't have `isMakeFill` field - - `V3_SELL` orders' fills are always calculated from make side (as if `isMakeFill` = true) - - `V3_BUY` orders' fills are always calculated from take side (as if `isMakeFill` = false) - - fees logic - - `V3` orders' fees work differently from all previous orders types - - `originFees` are taken from seller side only. - - sum of buy-order `originFees` + sell-order `originFees` should not be bigger than `maxFeesBasePoint` - - example: - - sell order is `1 ERC721` => `100 ETH` - - `maxFeesBasePoint` is 10 % - - Sell order has `originFeeFirst` = `{2% to addr3}` - - buy order is `100 ETH` => `1 ERC721` - - Buy order has - - `originFeeFirst` = `{3% to addr1}`, - - `originFeeSecond` = `{2% to addr2}` - - total amount for buyer is not affected by fees. it remains `100 ETH` - - `3% * 100 ETH` + `2% * 100 ETH` is taken as buy order's origin fee, `95 ETH` remaining - - `2% * 100 ETH` is taken as sell order's origin, `93 ETH` remaining - - after that NFT `royalties` are taken (same as with previous orders' types) - - what's left after that is distributed according to sell-order `payouts` (same as with previous orders' types) - - - -## Data parsing - -LibOrderData defines function parse which parses data field (according to dataType) and converts any version of the data to the GenericOrderData struct. -(see [LibOrder](LibOrder.md) `Order.data` field) - - - diff --git a/packages/marketplace/contracts/exchange/libraries/LibOrderDataGeneric.sol b/packages/marketplace/contracts/exchange/libraries/LibOrderDataGeneric.sol deleted file mode 100644 index ce1c1847c4..0000000000 --- a/packages/marketplace/contracts/exchange/libraries/LibOrderDataGeneric.sol +++ /dev/null @@ -1,87 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.21; - -import {LibOrder} from "../../lib-order/LibOrder.sol"; -import {LibOrderData} from "../../lib-order/LibOrderData.sol"; -import {LibPart} from "../../lib-part/LibPart.sol"; - -library LibOrderDataGeneric { - struct GenericOrderData { - LibPart.Part[] payouts; - bool isMakeFill; - } - - function parse(LibOrder.Order memory order) internal pure returns (GenericOrderData memory dataOrder) { - if (order.dataType == LibOrderData.SELL) { - LibOrderData.DataSell memory data = abi.decode(order.data, (LibOrderData.DataSell)); - dataOrder.payouts = parsePayouts(data.payouts); - dataOrder.isMakeFill = true; - } else if (order.dataType == LibOrderData.BUY) { - LibOrderData.DataBuy memory data = abi.decode(order.data, (LibOrderData.DataBuy)); - dataOrder.payouts = parsePayouts(data.payouts); - dataOrder.isMakeFill = false; - // solhint-disable-next-line no-empty-blocks - } else if (order.dataType == 0xffffffff) {} else { - revert("Unknown Order data type"); - } - if (dataOrder.payouts.length == 0) { - dataOrder.payouts = payoutSet(order.maker); - } - } - - function payoutSet(address orderAddress) internal pure returns (LibPart.Part[] memory) { - LibPart.Part[] memory payout = new LibPart.Part[](1); - payout[0].account = payable(orderAddress); - payout[0].value = 10000; - return payout; - } - - function parseOriginFeeData(uint256 dataFirst, uint256 dataSecond) internal pure returns (LibPart.Part[] memory) { - LibPart.Part[] memory originFee; - - if (dataFirst > 0 && dataSecond > 0) { - originFee = new LibPart.Part[](2); - - originFee[0] = uintToLibPart(dataFirst); - originFee[1] = uintToLibPart(dataSecond); - } - - if (dataFirst > 0 && dataSecond == 0) { - originFee = new LibPart.Part[](1); - - originFee[0] = uintToLibPart(dataFirst); - } - - if (dataFirst == 0 && dataSecond > 0) { - originFee = new LibPart.Part[](1); - - originFee[0] = uintToLibPart(dataSecond); - } - - return originFee; - } - - function parsePayouts(uint256 data) internal pure returns (LibPart.Part[] memory) { - LibPart.Part[] memory payouts; - - if (data > 0) { - payouts = new LibPart.Part[](1); - payouts[0] = uintToLibPart(data); - } - - return payouts; - } - - /** - @notice converts uint256 to LibPart.Part - @param data address and value encoded in uint256 (first 12 bytes ) - @return result LibPart.Part - */ - function uintToLibPart(uint256 data) internal pure returns (LibPart.Part memory result) { - if (data > 0) { - result.account = payable(address(uint160(data))); - result.value = uint96(data >> 160); - } - } -} diff --git a/packages/marketplace/contracts/lib-order/LibOrder.md b/packages/marketplace/contracts/lib-order/LibOrder.md index ef76ad8345..1e96f7acd5 100644 --- a/packages/marketplace/contracts/lib-order/LibOrder.md +++ b/packages/marketplace/contracts/lib-order/LibOrder.md @@ -14,8 +14,6 @@ This library contains struct `Order` with some functions for this struct: - `uint` salt - random number to distinguish different maker's Orders - `uint` start - Order can't be matched before this date (optional) - `uint` end - Order can't be matched after this date (optional) -- `bytes4` dataType - type of data, usually hash of some string, e.g.: "v1", "v2" (see more [here](LibOrderData.md)) -- `bytes` data - generic data, can be anything, extendable part of the order (see more [here](LibOrderData.md)) #### Types for EIP-712 signature: ```javascript @@ -36,8 +34,6 @@ const Types = { {name: 'salt', type: 'uint256'}, {name: 'start', type: 'uint256'}, {name: 'end', type: 'uint256'}, - {name: 'dataType', type: 'bytes4'}, - {name: 'data', type: 'bytes'}, ] }; ``` diff --git a/packages/marketplace/contracts/lib-order/LibOrder.sol b/packages/marketplace/contracts/lib-order/LibOrder.sol index 02f4d9e95c..31ad110a76 100644 --- a/packages/marketplace/contracts/lib-order/LibOrder.sol +++ b/packages/marketplace/contracts/lib-order/LibOrder.sol @@ -3,18 +3,15 @@ pragma solidity 0.8.21; import {LibAsset} from "../lib-asset/LibAsset.sol"; -import {LibMath} from "./LibMath.sol"; /// @title library for Order /// @notice contains structs and functions related to Order library LibOrder { bytes32 internal constant ORDER_TYPEHASH = keccak256( - "Order(address maker,Asset makeAsset,address taker,Asset takeAsset,uint256 salt,uint256 start,uint256 end,bytes4 dataType,bytes data)Asset(AssetType assetType,uint256 value)AssetType(uint256 assetClass,bytes data)" + "Order(address maker,Asset makeAsset,address taker,Asset takeAsset,uint256 salt,uint256 start,uint256 end)Asset(AssetType assetType,uint256 value)AssetType(uint256 assetClass,bytes data)" ); - bytes4 internal constant DEFAULT_ORDER_TYPE = 0xffffffff; - struct Order { address maker; LibAsset.Asset makeAsset; @@ -23,57 +20,21 @@ library LibOrder { uint256 salt; uint256 start; uint256 end; - bytes4 dataType; - bytes data; - } - - /// @notice calculate the remaining fill from orders - /// @param order order that we will calculate the remaining fill - /// @param fill to be subtracted - /// @param isMakeFill if true take fill from make side, if false from take - /// @return makeValue remaining fill from make side - /// @return takeValue remaining fill from take side - function calculateRemaining( - Order memory order, - uint256 fill, - bool isMakeFill - ) internal pure returns (uint256 makeValue, uint256 takeValue) { - if (isMakeFill) { - makeValue = order.makeAsset.value - fill; - takeValue = LibMath.safeGetPartialAmountFloor(order.takeAsset.value, order.makeAsset.value, makeValue); - } else { - takeValue = order.takeAsset.value - fill; - makeValue = LibMath.safeGetPartialAmountFloor(order.makeAsset.value, order.takeAsset.value, takeValue); - } } /// @notice calculate hash key from order /// @param order object to be hashed /// @return hash key of order function hashKey(Order calldata order) internal pure returns (bytes32) { - if (order.dataType == DEFAULT_ORDER_TYPE) { - return - keccak256( - abi.encode( - order.maker, - LibAsset.hash(order.makeAsset.assetType), - LibAsset.hash(order.takeAsset.assetType), - order.salt - ) - ); - } else { - //order.data is in hash for V3 and all new order - return - keccak256( - abi.encode( - order.maker, - LibAsset.hash(order.makeAsset.assetType), - LibAsset.hash(order.takeAsset.assetType), - order.salt, - order.data - ) - ); - } + return + keccak256( + abi.encode( + order.maker, + LibAsset.hash(order.makeAsset.assetType), + LibAsset.hash(order.takeAsset.assetType), + order.salt + ) + ); } /// @notice calculate hash from order @@ -91,16 +52,14 @@ library LibOrder { LibAsset.hash(order.takeAsset), order.salt, order.start, - order.end, - order.dataType, - keccak256(order.data) + order.end ) ); } /// @notice validates order time /// @param order whose time we want to validate - function validateOrderTime(LibOrder.Order memory order) internal view { + function validateOrderTime(Order memory order) internal view { // solhint-disable-next-line not-rely-on-time require(order.start == 0 || order.start < block.timestamp, "Order start validation failed"); // solhint-disable-next-line not-rely-on-time diff --git a/packages/marketplace/contracts/lib-order/LibOrderData.md b/packages/marketplace/contracts/lib-order/LibOrderData.md deleted file mode 100644 index 1e29d2d9e7..0000000000 --- a/packages/marketplace/contracts/lib-order/LibOrderData.md +++ /dev/null @@ -1,75 +0,0 @@ -# Features - -## Data types, corresponding transfers/fees logic -`Order` data can be generic. `dataType` field defines format of that data. -- `"0xffffffff"` or `"no type"` - - no data - - fees logic - - no fees -- `"V1"` - - fields - - `LibPart.Part[] payouts` - - array of payouts, i.e. how takeAsset of the order is going to be distributed. (usually 100% goes to order.maker, can be something like 50% goes to maker, 50% to someone else. it can be divided in any other way) - - `LibPart.Part[] originFees` - - additional fees (e.g. 5% of the payment goes to additional address) - - fees logic - - `originFees` from buy-order is taken from the buyer, `originFees` from sell-order is taken from the seller. e.g. sell order is `1 ERC721` => `100 ETH`, buy order is `100 ETH` => `1 ERC721`. Buy order has `originFees` = [`{5% to addr1}`,`{10% to addr2}`]. Sell order has `originFees` = [`{5% to addr3}`]. Then, total amount that buyer needs to send is `100 ETH` + `5%*100ETH` + `10%*100ETH` = `115 ETH` (15% more than order value, buy-order `origin fees` are added. So buyer pays for their `origin fees`). From this amount `5 ETH` will be transferred to addr1, `10 ETH` to addr2 (now we have `100ETH` left) and `5ETH` to addr3 (it is seller `origin fee`, so it is taken from their part). - - after that NFT `royalties` are taken - - what's left after that is distributed according to sell-order `payouts` -- `"V2"` - - fields - - `LibPart.Part[] payouts`, works the same as in `V1` orders - - `LibPart.Part[] originFees`, works the same as in `V1` orders - - `bool isMakeFill` - - if false, order's `fill` (what part of order is completed, stored on-chain) is calculated from take side (in `V1` orders it always works like that) - - if true, `fill` is calculated from the make side of the order - - fees logic, works the same as in `V1` orders -- `"V3"` two types of `V3` orders. - - `"V3_BUY"` - - fields - - `uint payouts`, works the same as in `V1` orders, but there is only 1 value and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `uint originFeeFirst`, instead of array there can only be 2 originFee in different variables (originFeeFirst and originFeeSecond), and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `uint originFeeSecond`, instead of array there can only be 2 originFee in different variables (originFeeFirst and originFeeSecond), and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `bytes32 marketplaceMarker`, bytes32 id marketplace, which generate this order - - `"V3_SELL"` - - fields - - `uint payouts`, works the same as in `V1` orders, but there is only 1 value and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `uint originFeeFirst`, instead of array there can only be 2 originFee in different variables (originFeeFirst and originFeeSecond), and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `uint originFeeSecond`, instead of array there can only be 2 originFee in different variables (originFeeFirst and originFeeSecond), and address + amount are encoded into uint (first 12 bytes for amount, last 20 bytes for address), not using `LibPart.Part` struct - - `uint maxFeesBasePoint` - - maximum amount of fees that can be taken from payment (e.g. 10%) - - chosen by seller, that's why it's only present in `V3_SELL` orders - - `maxFeesBasePoint` should be more than `0` - - `maxFeesBasePoint` should not be bigger than `10%` - - `bytes32 marketplaceMarker`, bytes32 id marketplace, which generate this order - - `V3` orders can only be matched if buy-order is `V3_BUY` and the sell-order is `V3_SELL` - - `V3` orders don't have `isMakeFill` field - - `V3_SELL` orders' fills are always calculated from make side (as if `isMakeFill` = true) - - `V3_BUY` orders' fills are always calculated from take side (as if `isMakeFill` = false) - - fees logic - - `V3` orders' fees work differently from all previous orders types - - `originFees` are taken from seller side only. - - sum of buy-order `originFees` + sell-order `originFees` should not be bigger than `maxFeesBasePoint` - - example: - - sell order is `1 ERC721` => `100 ETH` - - `maxFeesBasePoint` is 10 % - - Sell order has `originFeeFirst` = `{2% to addr3}` - - buy order is `100 ETH` => `1 ERC721` - - Buy order has - - `originFeeFirst` = `{3% to addr1}`, - - `originFeeSecond` = `{2% to addr2}` - - total amount for buyer is not affected by fees. it remains `100 ETH` - - `3% * 100 ETH` + `2% * 100 ETH` is taken as buy order's origin fee, `95 ETH` remaining - - `2% * 100 ETH` is taken as sell order's origin, `93 ETH` remaining - - after that NFT `royalties` are taken (same as with previous orders' types) - - what's left after that is distributed according to sell-order `payouts` (same as with previous orders' types) - - - -## Data parsing - -LibOrderData defines function parse which parses data field (according to dataType) and converts any version of the data to the GenericOrderData struct. -(see [LibOrder](LibOrder.md) `Order.data` field) - - - diff --git a/packages/marketplace/contracts/lib-order/LibOrderData.sol b/packages/marketplace/contracts/lib-order/LibOrderData.sol deleted file mode 100644 index dda7310964..0000000000 --- a/packages/marketplace/contracts/lib-order/LibOrderData.sol +++ /dev/null @@ -1,30 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.21; - -/// @title library for order data types -/// @notice Data types, corresponding transfers/fees logic -library LibOrderData { - /// @notice hash for order data type sell - /// @return SELL hash - bytes4 public constant SELL = bytes4(keccak256("SELL")); - - /// @notice hash for order data type buy - /// @return BUY hash - bytes4 public constant BUY = bytes4(keccak256("BUY")); - - struct DataSell { - uint256 payouts; - uint256 originFeeFirst; - uint256 originFeeSecond; - uint256 maxFeesBasePoint; - bytes32 marketplaceMarker; - } - - struct DataBuy { - uint256 payouts; - uint256 originFeeFirst; - uint256 originFeeSecond; - bytes32 marketplaceMarker; - } -} diff --git a/packages/marketplace/contracts/lib-part/LibPart.sol b/packages/marketplace/contracts/lib-part/LibPart.sol index 380f54992c..f43193a406 100644 --- a/packages/marketplace/contracts/lib-part/LibPart.sol +++ b/packages/marketplace/contracts/lib-part/LibPart.sol @@ -10,7 +10,7 @@ library LibPart { bytes32 public constant TYPE_HASH = keccak256("Part(address account,uint96 value)"); struct Part { - address payable account; + address account; uint96 value; } diff --git a/packages/marketplace/contracts/mocks/LibFillTest.sol b/packages/marketplace/contracts/mocks/LibFillTest.sol index d2d628e270..61de9d9554 100644 --- a/packages/marketplace/contracts/mocks/LibFillTest.sol +++ b/packages/marketplace/contracts/mocks/LibFillTest.sol @@ -10,10 +10,15 @@ contract LibFillTest { LibOrder.Order calldata leftOrder, LibOrder.Order calldata rightOrder, uint256 leftOrderFill, - uint256 rightOrderFill, - bool leftIsMakeFill, - bool rightIsMakeFill + uint256 rightOrderFill ) external pure returns (LibFill.FillResult memory) { - return LibFill.fillOrder(leftOrder, rightOrder, leftOrderFill, rightOrderFill, leftIsMakeFill, rightIsMakeFill); + return LibFill.fillOrder(leftOrder, rightOrder, leftOrderFill, rightOrderFill); + } + + function calculateRemaining( + LibOrder.Order calldata order, + uint256 fill + ) external pure returns (uint256 makeAmount, uint256 takeAmount) { + return LibFill.calculateRemaining(order, fill); } } diff --git a/packages/marketplace/contracts/mocks/LibOrderTest.sol b/packages/marketplace/contracts/mocks/LibOrderTest.sol index bf3747a482..1fb8e661f5 100644 --- a/packages/marketplace/contracts/mocks/LibOrderTest.sol +++ b/packages/marketplace/contracts/mocks/LibOrderTest.sol @@ -5,14 +5,6 @@ pragma solidity 0.8.21; import {LibOrder, LibAsset} from "../lib-order/LibOrder.sol"; contract LibOrderTest { - function calculateRemaining( - LibOrder.Order calldata order, - uint256 fill, - bool isMakeFill - ) external pure returns (uint256 makeAmount, uint256 takeAmount) { - return LibOrder.calculateRemaining(order, fill, isMakeFill); - } - function hashKey(LibOrder.Order calldata order) external pure returns (bytes32) { return LibOrder.hashKey(order); } diff --git a/packages/marketplace/contracts/transfer-manager/TransferExecutor.sol b/packages/marketplace/contracts/transfer-manager/TransferExecutor.sol index aedf8f68c0..84e6357636 100644 --- a/packages/marketplace/contracts/transfer-manager/TransferExecutor.sol +++ b/packages/marketplace/contracts/transfer-manager/TransferExecutor.sol @@ -7,15 +7,12 @@ import {IERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20 import {IERC721Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721Upgradeable.sol"; import {IERC1155Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155Upgradeable.sol"; import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol"; -import {LibTransfer} from "./lib/LibTransfer.sol"; import {LibAsset} from "../lib-asset/LibAsset.sol"; import {ITransferExecutor} from "./interfaces/ITransferExecutor.sol"; /// @title abstract contract for TransferExecutor /// @notice contains transfer functions for any assets as well as ERC20 tokens abstract contract TransferExecutor is Initializable, ITransferExecutor { - using LibTransfer for address payable; - /// @notice limit of assets for each type of bundles uint256 public constant MAX_BUNDLE_LIMIT = 20; diff --git a/packages/marketplace/contracts/transfer-manager/lib/LibTransfer.sol b/packages/marketplace/contracts/transfer-manager/lib/LibTransfer.sol deleted file mode 100644 index 5e29161b6b..0000000000 --- a/packages/marketplace/contracts/transfer-manager/lib/LibTransfer.sol +++ /dev/null @@ -1,10 +0,0 @@ -// SPDX-License-Identifier: MIT - -pragma solidity 0.8.21; - -library LibTransfer { - function transferEth(address payable to, uint256 value) internal { - (bool success, ) = to.call{value: value}(""); - require(success, "transfer failed"); - } -} diff --git a/packages/marketplace/test/exchange/Exchange.test.ts b/packages/marketplace/test/exchange/Exchange.test.ts index 1faedd4839..786707f64e 100644 --- a/packages/marketplace/test/exchange/Exchange.test.ts +++ b/packages/marketplace/test/exchange/Exchange.test.ts @@ -14,10 +14,10 @@ import { hashKey, hashOrder, OrderDefault, + signOrder, UINT256_MAX_VALUE, } from '../utils/order.ts'; import {ZeroAddress} from 'ethers'; -import {signOrder} from '../utils/signature'; describe('Exchange.sol', function () { it('should return the correct value of protocol fee', async function () { @@ -183,7 +183,6 @@ describe('Exchange.sol', function () { const makerAsset = await AssetERC20(ERC20Contract, 200); const takerAsset = await AssetERC20(ERC20Contract2, 100); - const orderLeft = await OrderDefault( maker, makerAsset, diff --git a/packages/marketplace/test/exchange/OrderValidator.test.ts b/packages/marketplace/test/exchange/OrderValidator.test.ts index 57173564a5..030413b13f 100644 --- a/packages/marketplace/test/exchange/OrderValidator.test.ts +++ b/packages/marketplace/test/exchange/OrderValidator.test.ts @@ -3,9 +3,8 @@ import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; import {expect} from 'chai'; import {AssetERC20, AssetERC721} from '../utils/assets.ts'; -import {OrderDefault} from '../utils/order.ts'; +import {OrderDefault, signOrder} from '../utils/order.ts'; import {ZeroAddress} from 'ethers'; -import {signOrder} from '../utils/signature'; // keccak256("TSB_ROLE") const TSBRole = diff --git a/packages/marketplace/test/utils/order.ts b/packages/marketplace/test/utils/order.ts index b8d8e726e2..103e5e72a6 100644 --- a/packages/marketplace/test/utils/order.ts +++ b/packages/marketplace/test/utils/order.ts @@ -1,20 +1,21 @@ // An order represents something offered (asset + who offers) plus what we want in exchange (asset + optionally for whom or everybody) -// SEE: LibOrder.sol and LibOrderData.sol +// SEE: LibOrder.sol import {Asset, hashAsset, hashAssetType} from './assets'; -import {bytes4Keccak} from './signature'; -import {AbiCoder, Numeric, keccak256, Signer, ZeroAddress} from 'ethers'; -import {BytesLike} from 'ethers/src.ts/utils/index'; +import { + AbiCoder, + Contract, + keccak256, + Numeric, + Signer, + ZeroAddress, +} from 'ethers'; export const ORDER_TYPEHASH = keccak256( Buffer.from( - 'Order(address maker,Asset makeAsset,address taker,Asset takeAsset,uint256 salt,uint256 start,uint256 end,bytes4 dataType,bytes data)Asset(AssetType assetType,uint256 value)AssetType(uint256 assetClass,bytes data)' + 'Order(address maker,Asset makeAsset,address taker,Asset takeAsset,uint256 salt,uint256 start,uint256 end)Asset(AssetType assetType,uint256 value)AssetType(uint256 assetClass,bytes data)' ) ); -const ORDER_DATA_BUY = bytes4Keccak('BUY'); -const ORDER_DATA_SELL = bytes4Keccak('SELL'); - -export const DEFAULT_ORDER_TYPE = '0xffffffff'; export const UINT256_MAX_VALUE = 115792089237316195423570985008687907853269984665640564039457584007913129639935n; @@ -26,8 +27,6 @@ export type Order = { salt: Numeric; start: Numeric; end: Numeric; - dataType: string; - data: BytesLike; }; export const OrderDefault = async ( @@ -46,93 +45,16 @@ export const OrderDefault = async ( salt, start, end, - dataType: DEFAULT_ORDER_TYPE, - data: '0x', -}); - -export const OrderSell = async ( - maker: Signer, - makeAsset: Asset, - taker: Signer | ZeroAddress, - takeAsset: Asset, - salt: Numeric, - start: Numeric, - end: Numeric, - payouts: string, // TODO: better type - originFeeFirst: string, // TODO: better type - originFeeSecond: string, // TODO: better type - maxFeesBasePoint: string, // TODO: better type - marketplaceMarker: string // TODO: better type -): Promise => ({ - maker: await maker.getAddress(), - makeAsset, - taker: taker === ZeroAddress ? ZeroAddress : await taker.getAddress(), - takeAsset, - salt, - start, - end, - dataType: ORDER_DATA_SELL, - data: AbiCoder.defaultAbiCoder().encode( - ['uint256', 'uint256', 'uint256', 'uint256', 'bytes32'], - [ - payouts, - originFeeFirst, - originFeeSecond, - maxFeesBasePoint, - marketplaceMarker, - ] - ), -}); - -export const OrderBuy = async ( - maker: Signer, - makeAsset: Asset, - taker: Signer | ZeroAddress, - takeAsset: Asset, - salt: Numeric, - start: Numeric, - end: Numeric, - payouts: string, // TODO: better type - originFeeFirst: string, // TODO: better type - originFeeSecond: string, // TODO: better type - marketplaceMarker: string // TODO: better type -): Promise => ({ - maker: await maker.getAddress(), - makeAsset, - taker: taker === ZeroAddress ? ZeroAddress : await taker.getAddress(), - takeAsset, - salt, - start, - end, - dataType: ORDER_DATA_BUY, - data: AbiCoder.defaultAbiCoder().encode( - ['uint256', 'uint256', 'uint256', 'bytes32'], - [payouts, originFeeFirst, originFeeSecond, marketplaceMarker] - ), }); export function hashKey(order: Order): string { - if (order.dataType === DEFAULT_ORDER_TYPE) { - const encoded = AbiCoder.defaultAbiCoder().encode( - ['address', 'bytes32', 'bytes32', 'uint256'], - [ - order.maker, - hashAssetType(order.makeAsset.assetType), - hashAssetType(order.takeAsset.assetType), - order.salt, - ] - ); - return keccak256(encoded); - } - // TODO: Review this on solidity side, instead of passing order.data maybe is better keccak(data)s const encoded = AbiCoder.defaultAbiCoder().encode( - ['address', 'bytes32', 'bytes32', 'uint256', 'bytes'], + ['address', 'bytes32', 'bytes32', 'uint256'], [ order.maker, hashAssetType(order.makeAsset.assetType), hashAssetType(order.takeAsset.assetType), order.salt, - order.data, ] ); return keccak256(encoded); @@ -171,8 +93,6 @@ export function hashOrder(order: Order): string { 'uint256', 'uint256', 'uint256', - 'bytes4', - 'bytes32', ], [ ORDER_TYPEHASH, @@ -183,9 +103,43 @@ export function hashOrder(order: Order): string { order.salt, order.start, order.end, - order.dataType, - keccak256(order.data), ] ); return keccak256(encoded); } + +export async function signOrder( + order: Order, + account: Signer, + verifyingContract: Contract +) { + const network = await verifyingContract.runner?.provider?.getNetwork(); + return account.signTypedData( + { + name: 'Exchange', + version: '1', + chainId: network.chainId, + verifyingContract: await verifyingContract.getAddress(), + }, + { + AssetType: [ + {name: 'assetClass', type: 'uint256'}, + {name: 'data', type: 'bytes'}, + ], + Asset: [ + {name: 'assetType', type: 'AssetType'}, + {name: 'value', type: 'uint256'}, + ], + Order: [ + {name: 'maker', type: 'address'}, + {name: 'makeAsset', type: 'Asset'}, + {name: 'taker', type: 'address'}, + {name: 'takeAsset', type: 'Asset'}, + {name: 'salt', type: 'uint256'}, + {name: 'start', type: 'uint256'}, + {name: 'end', type: 'uint256'}, + ], + }, + order + ); +} diff --git a/packages/marketplace/test/utils/raribleTestHelper.ts b/packages/marketplace/test/utils/raribleTestHelper.ts deleted file mode 100644 index 8429007fff..0000000000 --- a/packages/marketplace/test/utils/raribleTestHelper.ts +++ /dev/null @@ -1,52 +0,0 @@ -// This replaces the calls to the RaribleTestHelper.sol -import {AbiCoder} from 'ethers'; - -export type SellData = { - payouts: string; // TODO: better type - originFeeFirst: string; // TODO: better type - originFeeSecond: string; // TODO: better type - maxFeesBasePoint: string; // TODO: better type - marketplaceMarker: string; // TODO: better type -}; - -export function encode_SELL(data: SellData): string { - return AbiCoder.defaultAbiCoder().encode( - ['uint256', 'uint256', 'uint256', 'uint256', 'bytes32'], - [ - data.payouts, - data.originFeeFirst, - data.originFeeSecond, - data.maxFeesBasePoint, - data.marketplaceMarker, - ] - ); -} - -export type BuyData = { - payouts: string; // TODO: better type - originFeeFirst: string; // TODO: better type - originFeeSecond: string; // TODO: better type - marketplaceMarker: string; // TODO: better type -}; - -export function encode_BUY(data: BuyData): string { - return AbiCoder.defaultAbiCoder().encode( - ['uint256', 'uint256', 'uint256', 'bytes32'], - [ - data.payouts, - data.originFeeFirst, - data.originFeeSecond, - data.marketplaceMarker, - ] - ); -} - -export function encodeOriginFeeIntoUint( - account: string, - value: string -): bigint { - return BigInt(account) << (160 + BigInt(value)); -} - -// TODO: Implement -// function hashV2( diff --git a/packages/marketplace/test/utils/signature.ts b/packages/marketplace/test/utils/signature.ts deleted file mode 100644 index 8d8d5dc249..0000000000 --- a/packages/marketplace/test/utils/signature.ts +++ /dev/null @@ -1,46 +0,0 @@ -import {Contract, keccak256, Signer} from 'ethers'; -import {Order} from './order'; - -export type HashSignature = string; - -export function bytes4Keccak(str: string): HashSignature { - return keccak256(Buffer.from(str)).substring(0, 10); -} - -export async function signOrder( - order: Order, - account: Signer, - verifyingContract: Contract -) { - const network = await verifyingContract.runner?.provider?.getNetwork(); - return account.signTypedData( - { - name: 'Exchange', - version: '1', - chainId: network.chainId, - verifyingContract: await verifyingContract.getAddress(), - }, - { - AssetType: [ - {name: 'assetClass', type: 'uint256'}, - {name: 'data', type: 'bytes'}, - ], - Asset: [ - {name: 'assetType', type: 'AssetType'}, - {name: 'value', type: 'uint256'}, - ], - Order: [ - {name: 'maker', type: 'address'}, - {name: 'makeAsset', type: 'Asset'}, - {name: 'taker', type: 'address'}, - {name: 'takeAsset', type: 'Asset'}, - {name: 'salt', type: 'uint256'}, - {name: 'start', type: 'uint256'}, - {name: 'end', type: 'uint256'}, - {name: 'dataType', type: 'bytes4'}, - {name: 'data', type: 'bytes'}, - ], - }, - order - ); -}