diff --git a/packages/contracts/evm-contracts/contracts/orderbook/IOrderbookDex.sol b/packages/contracts/evm-contracts/contracts/orderbook/IOrderbookDex.sol index c946fe6c..8e3652d9 100644 --- a/packages/contracts/evm-contracts/contracts/orderbook/IOrderbookDex.sol +++ b/packages/contracts/evm-contracts/contracts/orderbook/IOrderbookDex.sol @@ -5,20 +5,34 @@ pragma solidity ^0.8.20; import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; /// @notice Facilitates base-chain trading of an asset that is living on a different app-chain. -/// @dev The contract should never hold any ETH itself. interface IOrderbookDex is IERC1155Receiver { struct Order { + /// @dev The asset's unique token identifier. uint256 assetId; + /// @dev The amount of the asset that is available to be sold. uint256 assetAmount; + /// @dev The price per one unit of asset. uint256 pricePerAsset; } + /// @param seller The seller's address. + /// @param orderId The order's unique identifier. + /// @param assetId The asset's unique token identifier. + /// @param assetAmount The amount of the asset that has been put for sale. + /// @param pricePerAsset The requested price per one unit of asset. event OrderCreated( address indexed seller, uint256 indexed orderId, + uint256 indexed assetId, uint256 assetAmount, uint256 pricePerAsset ); + + /// @param seller The seller's address. + /// @param orderId The order's unique identifier. + /// @param buyer The buyer's address. + /// @param assetAmount The amount of the asset that was traded. + /// @param pricePerAsset The price per one unit of asset that was paid. event OrderFilled( address indexed seller, uint256 indexed orderId, @@ -26,50 +40,51 @@ interface IOrderbookDex is IERC1155Receiver { uint256 assetAmount, uint256 pricePerAsset ); + + /// @param seller The seller's address. + /// @param orderId The order's unique identifier. event OrderCancelled(address indexed seller, uint256 indexed orderId); - /// @notice Returns the address of the asset that is being traded. + /// @notice Returns the address of the asset that is being traded in this DEX contract. function getAsset() external view returns (address); - /// @notice Returns the seller's current `orderId` (index that their new sell order will be mapped to). - function getSellerOrderId(address seller) external view returns (uint256); + /// @notice Returns the `orderId` of the next sell order of `seller`. + function getNextOrderId(address seller) external view returns (uint256); - /// @notice Returns the Order struct information about an order identified by the combination ``. - function getOrder(address seller, uint256 orderId) external view returns (Order memory); + /// @notice Returns the Order struct information about an order identified by the `orderId`. + function getOrder(uint256 orderId) external view returns (Order memory); - /// @notice Creates a sell order with incremental seller-specific `orderId` for the specified `assetAmount` at specified `pricePerAsset`. - /// @dev The order information is saved in a nested mapping `seller address -> orderId -> Order`. + /// @notice Creates a sell order for the `assetAmount` of `assetId` at `pricePerAsset`. + /// @dev The order information is saved in a mapping `orderId -> Order`. + /// orderId SHOULD be created by packing the seller's address (uint160) and their incremental `sellerOrderNonce` (uint96) into uint256. + /// MUST transfer the `assetAmount` of `assetId` from the seller to the contract. /// MUST emit `OrderCreated` event. function createSellOrder(uint256 assetId, uint256 assetAmount, uint256 pricePerAsset) external; - /// @notice Consecutively fills an array of orders identified by the combination ``, + /// @notice Consecutively fills an array of orders identified by the `orderId` of each order, /// by providing an exact amount of ETH and requesting a specific minimum amount of asset to receive. /// @dev Transfers portions of msg.value to the orders' sellers according to the price. /// The sum of asset amounts of filled orders MUST be at least `minimumAsset`. /// If msg.value is more than the sum of orders' prices, it MUST refund the excess back to msg.sender. - /// MUST change the `assetAmount` parameter for the specified order according to how much of it was filled. + /// MUST decrease the `assetAmount` parameter for the specified order according to how much of it was filled, + /// and transfer that amount of the order's `assetId` to the buyer. /// MUST emit `OrderFilled` event for each order accordingly. - function fillOrdersExactEth( - uint256 minimumAsset, - address payable[] memory sellers, - uint256[] memory orderIds - ) external payable; + function fillOrdersExactEth(uint256 minimumAsset, uint256[] memory orderIds) external payable; - /// @notice Consecutively fills an array of orders identified by the combination ``, + /// @notice Consecutively fills an array of orders identified by the `orderId` of each order, /// by providing a possibly surplus amount of ETH and requesting an exact amount of asset to receive. /// @dev Transfers portions of msg.value to the orders' sellers according to the price. /// The sum of asset amounts of filled orders MUST be exactly `assetAmount`. Excess ETH MUST be returned back to `msg.sender`. - /// MUST change the `assetAmount` parameter for the specified order according to how much of it was filled. + /// MUST decrease the `assetAmount` parameter for the specified order according to how much of it was filled, + /// and transfer that amount of the order's `assetId` to the buyer. /// MUST emit `OrderFilled` event for each order accordingly. /// If msg.value is more than the sum of orders' prices, it MUST refund the difference back to msg.sender. - function fillOrdersExactAsset( - uint256 assetAmount, - address payable[] memory sellers, - uint256[] memory orderIds - ) external payable; + function fillOrdersExactAsset(uint256 assetAmount, uint256[] memory orderIds) external payable; - /// @notice Cancels the sell order identified by combination ``, making it unfillable. - /// @dev MUST change the `assetAmount` parameter for the specified order to `0`. + /// @notice Cancels the sell order identified by the `orderId`, transferring the order's assets back to the seller. + /// @dev MUST revert if the seller decoded from the `orderId` is not `msg.sender`. + /// MUST change the `assetAmount` parameter for the specified order to `0`. /// MUST emit `OrderCancelled` event. + /// MUST transfer the `assetAmount` of `assetId` back to the seller. function cancelSellOrder(uint256 orderId) external; } diff --git a/packages/contracts/evm-contracts/contracts/orderbook/OrderbookDex.sol b/packages/contracts/evm-contracts/contracts/orderbook/OrderbookDex.sol index b06bb93b..c5663a17 100644 --- a/packages/contracts/evm-contracts/contracts/orderbook/OrderbookDex.sol +++ b/packages/contracts/evm-contracts/contracts/orderbook/OrderbookDex.sol @@ -10,40 +10,42 @@ import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol import {IInverseProjected1155} from "../token/IInverseProjected1155.sol"; import {IOrderbookDex} from "./IOrderbookDex.sol"; -/// @notice Facilitates trading an asset that is living on a different app-chain. +/// @notice Facilitates base-chain trading of an asset that is living on a different app-chain. contract OrderbookDex is IOrderbookDex, ERC1155Holder, ReentrancyGuard { using Address for address payable; IInverseProjected1155 internal asset; - mapping(address => mapping(uint256 => Order)) internal orders; - mapping(address => uint256) internal sellersOrderId; + mapping(uint256 orderId => Order) internal orders; + mapping(address seller => uint96 nonce) internal sellersOrderNonce; error OrderDoesNotExist(uint256 orderId); error InsufficientEndAmount(uint256 expectedAmount, uint256 actualAmount); error InvalidInput(uint256 input); - error InvalidInputArity(); + error Unauthorized(); constructor(IInverseProjected1155 _asset) { asset = _asset; } - /// @notice Returns the address of the asset that is being traded. + /// @notice Returns the address of the asset that is being traded in this DEX contract. function getAsset() public view virtual returns (address) { return address(asset); } - /// @notice Returns the seller's current `orderId` (index that their new sell order will be mapped to). - function getSellerOrderId(address seller) public view virtual returns (uint256) { - return sellersOrderId[seller]; + /// @notice Returns the `orderId` of the next sell order of `seller`. + function getNextOrderId(address seller) public view virtual returns (uint256) { + return _getOrderId(seller, sellersOrderNonce[seller]); } - /// @notice Returns the Order struct information about an order identified by the combination ``. - function getOrder(address seller, uint256 orderId) public view virtual returns (Order memory) { - return orders[seller][orderId]; + /// @notice Returns the Order struct information about an order identified by the `orderId`. + function getOrder(uint256 orderId) public view virtual returns (Order memory) { + return orders[orderId]; } - /// @notice Creates a sell order with incremental seller-specific `orderId` for the specified `assetAmount` at specified `pricePerAsset`. - /// @dev The order information is saved in a nested mapping `seller address -> orderId -> Order`. + /// @notice Creates a sell order for the `assetAmount` of `assetId` at `pricePerAsset`. + /// @dev The order information is saved in a mapping `orderId -> Order`. + /// orderId SHOULD be created by packing the seller's address (uint160) and their incremental `sellerOrderNonce` (uint96) into uint256. + /// MUST transfer the `assetAmount` of `assetId` from the seller to the contract. /// MUST emit `OrderCreated` event. function createSellOrder( uint256 assetId, @@ -59,37 +61,34 @@ contract OrderbookDex is IOrderbookDex, ERC1155Holder, ReentrancyGuard { assetAmount: assetAmount, pricePerAsset: pricePerAsset }); - uint256 orderId = sellersOrderId[msg.sender]; - orders[msg.sender][orderId] = newOrder; - emit OrderCreated(msg.sender, orderId, assetAmount, pricePerAsset); - sellersOrderId[msg.sender]++; + uint256 orderId = getNextOrderId(msg.sender); + orders[orderId] = newOrder; + emit OrderCreated(msg.sender, orderId, assetId, assetAmount, pricePerAsset); + sellersOrderNonce[msg.sender]++; } - /// @notice Consecutively fills an array of orders identified by the combination ``, + /// @notice Consecutively fills an array of orders identified by the `orderId` of each order, /// by providing an exact amount of ETH and requesting a specific minimum amount of asset to receive. /// @dev Transfers portions of msg.value to the orders' sellers according to the price. /// The sum of asset amounts of filled orders MUST be at least `minimumAsset`. /// If msg.value is more than the sum of orders' prices, it MUST refund the excess back to msg.sender. - /// MUST change the `assetAmount` parameter for the specified order according to how much of it was filled. + /// MUST decrease the `assetAmount` parameter for the specified order according to how much of it was filled, + /// and transfer that amount of the order's `assetId` to the buyer. /// MUST emit `OrderFilled` event for each order accordingly. function fillOrdersExactEth( uint256 minimumAsset, - address payable[] memory sellers, uint256[] memory orderIds ) public payable virtual nonReentrant { - if (sellers.length != orderIds.length) { - revert InvalidInputArity(); - } - uint256 length = sellers.length; + uint256 length = orderIds.length; uint256 remainingEth = msg.value; uint256 totalAssetReceived; for (uint256 i = 0; i < length; i++) { - address payable seller = sellers[i]; uint256 orderId = orderIds[i]; - Order storage order = orders[seller][orderId]; + Order storage order = orders[orderId]; if (order.assetAmount == 0) { continue; } + address payable seller = payable(address(uint160(orderId >> 96))); uint256 assetsToBuy = remainingEth / order.pricePerAsset; if (assetsToBuy == 0) { continue; @@ -97,7 +96,6 @@ contract OrderbookDex is IOrderbookDex, ERC1155Holder, ReentrancyGuard { if (assetsToBuy > order.assetAmount) { assetsToBuy = order.assetAmount; } - seller.sendValue(assetsToBuy * order.pricePerAsset); order.assetAmount -= assetsToBuy; remainingEth -= assetsToBuy * order.pricePerAsset; totalAssetReceived += assetsToBuy; @@ -108,6 +106,7 @@ contract OrderbookDex is IOrderbookDex, ERC1155Holder, ReentrancyGuard { assetsToBuy, bytes("") ); + seller.sendValue(assetsToBuy * order.pricePerAsset); emit OrderFilled(seller, orderId, msg.sender, assetsToBuy, order.pricePerAsset); if (remainingEth == 0) { break; @@ -121,31 +120,28 @@ contract OrderbookDex is IOrderbookDex, ERC1155Holder, ReentrancyGuard { } } - /// @notice Consecutively fills an array of orders identified by the combination ``, + /// @notice Consecutively fills an array of orders identified by the `orderId` of each order, /// by providing a possibly surplus amount of ETH and requesting an exact amount of asset to receive. /// @dev Transfers portions of msg.value to the orders' sellers according to the price. /// The sum of asset amounts of filled orders MUST be exactly `assetAmount`. Excess ETH MUST be returned back to `msg.sender`. - /// MUST change the `assetAmount` parameter for the specified order according to how much of it was filled. + /// MUST decrease the `assetAmount` parameter for the specified order according to how much of it was filled, + /// and transfer that amount of the order's `assetId` to the buyer. /// MUST emit `OrderFilled` event for each order accordingly. /// If msg.value is more than the sum of orders' prices, it MUST refund the difference back to msg.sender. function fillOrdersExactAsset( uint256 assetAmount, - address payable[] memory sellers, uint256[] memory orderIds ) public payable virtual nonReentrant { - if (sellers.length != orderIds.length) { - revert InvalidInputArity(); - } - uint256 length = sellers.length; + uint256 length = orderIds.length; uint256 remainingAsset = assetAmount; uint256 remainingEth = msg.value; for (uint256 i = 0; i < length; i++) { - address payable seller = sellers[i]; uint256 orderId = orderIds[i]; - Order storage order = orders[seller][orderId]; + Order storage order = orders[orderId]; if (order.assetAmount == 0) { continue; } + address payable seller = payable(address(uint160(orderId >> 96))); uint256 assetsToBuy = order.assetAmount; if (assetsToBuy > remainingAsset) { assetsToBuy = remainingAsset; @@ -153,7 +149,6 @@ contract OrderbookDex is IOrderbookDex, ERC1155Holder, ReentrancyGuard { if (assetsToBuy == 0) { continue; } - seller.sendValue(assetsToBuy * order.pricePerAsset); order.assetAmount -= assetsToBuy; remainingEth -= assetsToBuy * order.pricePerAsset; remainingAsset -= assetsToBuy; @@ -164,6 +159,7 @@ contract OrderbookDex is IOrderbookDex, ERC1155Holder, ReentrancyGuard { assetsToBuy, bytes("") ); + seller.sendValue(assetsToBuy * order.pricePerAsset); emit OrderFilled(seller, orderId, msg.sender, assetsToBuy, order.pricePerAsset); if (remainingAsset == 0) { break; @@ -177,11 +173,17 @@ contract OrderbookDex is IOrderbookDex, ERC1155Holder, ReentrancyGuard { } } - /// @notice Cancels the sell order identified by combination ``, making it unfillable. - /// @dev MUST change the `assetAmount` parameter for the specified order to `0`. + /// @notice Cancels the sell order identified by the `orderId`, transferring the order's assets back to the seller. + /// @dev MUST revert if the seller decoded from the `orderId` is not `msg.sender`. + /// MUST change the `assetAmount` parameter for the specified order to `0`. /// MUST emit `OrderCancelled` event. + /// MUST transfer the `assetAmount` of `assetId` back to the seller. function cancelSellOrder(uint256 orderId) public virtual { - Order storage order = orders[msg.sender][orderId]; + address seller = address(uint160(orderId >> 96)); + if (msg.sender != seller) { + revert Unauthorized(); + } + Order storage order = orders[orderId]; uint256 assetAmount = order.assetAmount; if (order.assetAmount == 0) { revert OrderDoesNotExist(orderId); @@ -198,4 +200,8 @@ contract OrderbookDex is IOrderbookDex, ERC1155Holder, ReentrancyGuard { return interfaceId == type(IOrderbookDex).interfaceId || super.supportsInterface(interfaceId); } + + function _getOrderId(address seller, uint96 orderId) internal view virtual returns (uint256) { + return (uint256(uint160(seller)) << 96) ^ orderId; + } } diff --git a/packages/contracts/evm-contracts/test/OrderbookDex.t.sol b/packages/contracts/evm-contracts/test/OrderbookDex.t.sol index 14969dfb..4a01e653 100644 --- a/packages/contracts/evm-contracts/test/OrderbookDex.t.sol +++ b/packages/contracts/evm-contracts/test/OrderbookDex.t.sol @@ -6,6 +6,7 @@ import {CTest} from "../test-lib/ctest.sol"; import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {ERC1155Holder} from "@openzeppelin/contracts/token/ERC1155/utils/ERC1155Holder.sol"; +import {IERC1155Receiver} from "@openzeppelin/contracts/token/ERC1155/IERC1155Receiver.sol"; import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; import {IInverseAppProjected1155} from "../contracts/token/IInverseAppProjected1155.sol"; @@ -35,20 +36,27 @@ contract OrderbookDexTest is CTest, ERC1155Holder { } function test_SupportsInterface() public { + assertTrue(dex.supportsInterface(type(IERC165).interfaceId)); assertTrue(dex.supportsInterface(type(IERC165).interfaceId)); assertTrue(dex.supportsInterface(type(IOrderbookDex).interfaceId)); } function test_CreateOrderSatisfiesRequirements() public { - uint256 orderId = dex.getSellerOrderId(address(this)); uint256 assetAmount = 100; uint256 pricePerAsset = 200; + uint256 orderId = dex.getNextOrderId(address(this)); uint256 assetId = asset.mint(assetAmount, ""); vm.expectEmit(true, true, true, true); - emit IOrderbookDex.OrderCreated(address(this), orderId, assetAmount, pricePerAsset); + emit IOrderbookDex.OrderCreated( + address(this), + orderId, + assetId, + assetAmount, + pricePerAsset + ); dex.createSellOrder(assetId, assetAmount, pricePerAsset); - IOrderbookDex.Order memory order = dex.getOrder(address(this), orderId); + IOrderbookDex.Order memory order = dex.getOrder(orderId); assertEq(order.assetId, assetId); assertEq(order.assetAmount, assetAmount); assertEq(order.pricePerAsset, pricePerAsset); @@ -56,15 +64,15 @@ contract OrderbookDexTest is CTest, ERC1155Holder { } function test_CancelOrderSatisfiesRequirements() public { - uint256 orderId = dex.getSellerOrderId(address(this)); uint256 assetAmount = 100; uint256 assetId = asset.mint(assetAmount, ""); + uint256 orderId = dex.getNextOrderId(address(this)); dex.createSellOrder(assetId, assetAmount, 200); vm.expectEmit(true, true, true, true); emit IOrderbookDex.OrderCancelled(address(this), orderId); dex.cancelSellOrder(orderId); - IOrderbookDex.Order memory order = dex.getOrder(address(this), orderId); + IOrderbookDex.Order memory order = dex.getOrder(orderId); assertEq(order.assetAmount, 0); assertEq(asset.balanceOf(address(dex), assetId), 0); assertEq(asset.balanceOf(address(this), assetId), assetAmount); @@ -81,9 +89,8 @@ contract OrderbookDexTest is CTest, ERC1155Holder { for (uint256 i = 0; i < ordersCount; i++) { address payable seller = payable(vm.addr(uint256(keccak256(abi.encodePacked(i))))); uint256 assetAmount = price / (ordersCount - i); - uint256 orderId = dex.getSellerOrderId(seller); uint256 pricePerAsset = price / assetAmount; - orderIds[i] = orderId; + orderIds[i] = dex.getNextOrderId(seller); sellers[i] = seller; vm.startPrank(seller); uint256 assetId = asset.mint(assetAmount, ""); @@ -100,7 +107,7 @@ contract OrderbookDexTest is CTest, ERC1155Holder { uint256[] memory ordersAssetAmounts = new uint256[](ordersCount); uint256 totalExpectedPayout; for (uint256 i = 0; i < ordersCount; i++) { - IOrderbookDex.Order memory order = dex.getOrder(sellers[i], orderIds[i]); + IOrderbookDex.Order memory order = dex.getOrder(orderIds[i]); expectedPayouts[i] = order.pricePerAsset * order.assetAmount; ordersAssetAmounts[i] = order.assetAmount; totalExpectedPayout += expectedPayouts[i]; @@ -115,15 +122,11 @@ contract OrderbookDexTest is CTest, ERC1155Holder { ); } vm.startPrank(buyer); - dex.fillOrdersExactEth{value: totalExpectedPayout + 1000}( - totalAssetAmount, - sellers, - orderIds - ); + dex.fillOrdersExactEth{value: totalExpectedPayout + 1000}(totalAssetAmount, orderIds); assertEq(buyer.balance, buyerBalanceBefore - totalExpectedPayout); for (uint256 i = 0; i < ordersCount; i++) { - IOrderbookDex.Order memory order = dex.getOrder(sellers[i], orderIds[i]); + IOrderbookDex.Order memory order = dex.getOrder(orderIds[i]); assertEq(sellers[i].balance, sellersBalancesBefore[i] + expectedPayouts[i]); assertEq(order.assetAmount, 0); assertEq(asset.balanceOf(address(dex), order.assetId), 0); @@ -143,9 +146,8 @@ contract OrderbookDexTest is CTest, ERC1155Holder { for (uint256 i = 0; i < ordersCount; i++) { address payable seller = payable(vm.addr(uint256(keccak256(abi.encodePacked(i))))); uint256 assetAmount = price / (ordersCount - i); - uint256 orderId = dex.getSellerOrderId(seller); uint256 pricePerAsset = price / assetAmount; - orderIds[i] = orderId; + orderIds[i] = dex.getNextOrderId(seller); sellers[i] = seller; vm.startPrank(seller); uint256 assetId = asset.mint(assetAmount, ""); @@ -162,7 +164,7 @@ contract OrderbookDexTest is CTest, ERC1155Holder { uint256[] memory ordersAssetAmounts = new uint256[](ordersCount); uint256 totalExpectedPayout; for (uint256 i = 0; i < ordersCount; i++) { - IOrderbookDex.Order memory order = dex.getOrder(sellers[i], orderIds[i]); + IOrderbookDex.Order memory order = dex.getOrder(orderIds[i]); expectedPayouts[i] = order.pricePerAsset * order.assetAmount; ordersAssetAmounts[i] = order.assetAmount; totalExpectedPayout += expectedPayouts[i]; @@ -177,15 +179,11 @@ contract OrderbookDexTest is CTest, ERC1155Holder { ); } vm.startPrank(buyer); - dex.fillOrdersExactAsset{value: totalExpectedPayout + 1000}( - totalAssetAmount, - sellers, - orderIds - ); + dex.fillOrdersExactAsset{value: totalExpectedPayout + 1000}(totalAssetAmount, orderIds); assertEq(buyer.balance, buyerBalanceBefore - totalExpectedPayout); for (uint256 i = 0; i < ordersCount; i++) { - IOrderbookDex.Order memory order = dex.getOrder(sellers[i], orderIds[i]); + IOrderbookDex.Order memory order = dex.getOrder(orderIds[i]); assertEq(sellers[i].balance, sellersBalancesBefore[i] + expectedPayouts[i]); assertEq(order.assetAmount, 0); assertEq(asset.balanceOf(address(dex), order.assetId), 0); @@ -207,28 +205,22 @@ contract OrderbookDexTest is CTest, ERC1155Holder { address buyer = alice; address payable seller = payable(address(this)); vm.deal(buyer, assetAmount * pricePerAsset); - uint256 orderId = dex.getSellerOrderId(address(this)); + uint256 orderId = dex.getNextOrderId(seller); vm.startPrank(seller); uint256 assetId = asset.mint(assetAmount, ""); asset.setApprovalForAll(address(dex), true); dex.createSellOrder(assetId, assetAmount, pricePerAsset); uint256 buyerBalanceBefore = buyer.balance; - uint256 sellerBalanceBefore = address(this).balance; - address payable[] memory sellers = new address payable[](1); - sellers[0] = seller; + uint256 sellerBalanceBefore = seller.balance; uint256[] memory orderIds = new uint256[](1); orderIds[0] = orderId; vm.startPrank(buyer); - dex.fillOrdersExactEth{value: assetAmountToBuy * pricePerAsset}( - assetAmountToBuy, - sellers, - orderIds - ); + dex.fillOrdersExactEth{value: assetAmountToBuy * pricePerAsset}(assetAmountToBuy, orderIds); - IOrderbookDex.Order memory order = dex.getOrder(address(this), orderId); + IOrderbookDex.Order memory order = dex.getOrder(orderId); assertEq(buyer.balance, buyerBalanceBefore - (assetAmountToBuy * pricePerAsset)); - assertEq(address(this).balance, sellerBalanceBefore + (assetAmountToBuy * pricePerAsset)); + assertEq(seller.balance, sellerBalanceBefore + (assetAmountToBuy * pricePerAsset)); assertEq(order.assetAmount, assetAmount - assetAmountToBuy); assertEq(asset.balanceOf(address(dex), assetId), assetAmount - assetAmountToBuy); assertEq(asset.balanceOf(buyer, assetId), assetAmountToBuy); @@ -247,24 +239,22 @@ contract OrderbookDexTest is CTest, ERC1155Holder { address buyer = alice; address payable seller = payable(address(this)); vm.deal(buyer, assetAmount * pricePerAsset); - uint256 orderId = dex.getSellerOrderId(address(this)); + uint256 orderId = dex.getNextOrderId(seller); vm.startPrank(seller); uint256 assetId = asset.mint(assetAmount, ""); asset.setApprovalForAll(address(dex), true); dex.createSellOrder(assetId, assetAmount, pricePerAsset); uint256 buyerBalanceBefore = buyer.balance; - uint256 sellerBalanceBefore = address(this).balance; - address payable[] memory sellers = new address payable[](1); - sellers[0] = seller; + uint256 sellerBalanceBefore = seller.balance; uint256[] memory orderIds = new uint256[](1); orderIds[0] = orderId; vm.startPrank(buyer); - dex.fillOrdersExactAsset{value: buyerBalanceBefore}(assetAmountToBuy, sellers, orderIds); + dex.fillOrdersExactAsset{value: buyerBalanceBefore}(assetAmountToBuy, orderIds); - IOrderbookDex.Order memory order = dex.getOrder(address(this), orderId); + IOrderbookDex.Order memory order = dex.getOrder(orderId); assertEq(buyer.balance, buyerBalanceBefore - (assetAmountToBuy * pricePerAsset)); - assertEq(address(this).balance, sellerBalanceBefore + (assetAmountToBuy * pricePerAsset)); + assertEq(seller.balance, sellerBalanceBefore + (assetAmountToBuy * pricePerAsset)); assertEq(order.assetAmount, assetAmount - assetAmountToBuy); assertEq(asset.balanceOf(address(dex), assetId), assetAmount - assetAmountToBuy); assertEq(asset.balanceOf(buyer, assetId), assetAmountToBuy); @@ -280,17 +270,15 @@ contract OrderbookDexTest is CTest, ERC1155Holder { vm.assume(pricePerAsset < type(uint256).max / assetAmount / multiplier); vm.deal(alice, assetAmount * pricePerAsset * multiplier); - uint256 orderId = dex.getSellerOrderId(address(this)); + uint256 orderId = dex.getNextOrderId(address(this)); uint256 assetId = asset.mint(assetAmount, ""); dex.createSellOrder(assetId, assetAmount, pricePerAsset); uint256 aliceBalanceBefore = alice.balance; - address payable[] memory sellers = new address payable[](1); - sellers[0] = payable(address(this)); uint256[] memory orderIds = new uint256[](1); orderIds[0] = orderId; vm.startPrank(alice); - dex.fillOrdersExactEth{value: alice.balance}(assetAmount, sellers, orderIds); + dex.fillOrdersExactEth{value: alice.balance}(assetAmount, orderIds); assertEq(alice.balance, aliceBalanceBefore - (assetAmount * pricePerAsset)); } @@ -304,38 +292,34 @@ contract OrderbookDexTest is CTest, ERC1155Holder { vm.assume(pricePerAsset < type(uint256).max / assetAmount / multiplier); vm.deal(alice, assetAmount * pricePerAsset * multiplier); - uint256 orderId = dex.getSellerOrderId(address(this)); + uint256 orderId = dex.getNextOrderId(address(this)); uint256 assetId = asset.mint(assetAmount, ""); dex.createSellOrder(assetId, assetAmount, pricePerAsset); uint256 aliceBalanceBefore = alice.balance; - address payable[] memory sellers = new address payable[](1); - sellers[0] = payable(address(this)); uint256[] memory orderIds = new uint256[](1); orderIds[0] = orderId; vm.startPrank(alice); - dex.fillOrdersExactAsset{value: alice.balance}(assetAmount, sellers, orderIds); + dex.fillOrdersExactAsset{value: alice.balance}(assetAmount, orderIds); assertEq(alice.balance, aliceBalanceBefore - (assetAmount * pricePerAsset)); } function test_WontFillOrderIfCancelled() public { uint256 assetAmount = 100; uint256 pricePerAsset = 200; - uint256 orderId = dex.getSellerOrderId(address(this)); + uint256 orderId = dex.getNextOrderId(address(this)); uint256 assetId = asset.mint(assetAmount, ""); dex.createSellOrder(assetId, assetAmount, pricePerAsset); dex.cancelSellOrder(orderId); uint256 aliceBalanceBefore = alice.balance; - address payable[] memory sellers = new address payable[](1); - sellers[0] = payable(address(this)); uint256[] memory orderIds = new uint256[](1); orderIds[0] = orderId; vm.startPrank(alice); - dex.fillOrdersExactEth{value: assetAmount * pricePerAsset}(0, sellers, orderIds); + dex.fillOrdersExactEth{value: assetAmount * pricePerAsset}(0, orderIds); assertEq(alice.balance, aliceBalanceBefore); - dex.fillOrdersExactAsset{value: assetAmount * pricePerAsset}(0, sellers, orderIds); + dex.fillOrdersExactAsset{value: assetAmount * pricePerAsset}(0, orderIds); assertEq(alice.balance, aliceBalanceBefore); } @@ -346,36 +330,33 @@ contract OrderbookDexTest is CTest, ERC1155Holder { dex.createSellOrder(assetId, 0, 100); } - function test_CannotCancelOrderIfDoesNotExist() public { - uint256 orderId = dex.getSellerOrderId(address(this)); - vm.expectRevert(abi.encodeWithSelector(OrderbookDex.OrderDoesNotExist.selector, orderId)); + function test_CannotCancelOrderIfUnauthorized() public { + uint256 orderId = dex.getNextOrderId(address(this)); + uint256 assetId = asset.mint(100, ""); + dex.createSellOrder(assetId, 100, 200); + + vm.startPrank(alice); + vm.expectRevert(OrderbookDex.Unauthorized.selector); dex.cancelSellOrder(orderId); } - function testFuzz_CannotFillOrderWithInvalidInputArity( - address payable[] memory sellers, - uint256[] memory orderIds - ) public { - vm.assume(sellers.length != orderIds.length); - - vm.expectRevert(OrderbookDex.InvalidInputArity.selector); - dex.fillOrdersExactEth(0, sellers, orderIds); - - vm.expectRevert(OrderbookDex.InvalidInputArity.selector); - dex.fillOrdersExactAsset(0, sellers, orderIds); + function test_CannotCancelOrderIfDoesNotExist() public { + uint256 orderId = dex.getNextOrderId(address(this)); + vm.expectRevert(abi.encodeWithSelector(OrderbookDex.OrderDoesNotExist.selector, orderId)); + dex.cancelSellOrder(orderId); } function test_CannotFillOrderIfInsufficientEndAmountExactEth() public { uint256 assetAmount = 10; uint256 pricePerAsset = 100; - uint256 orderId = dex.getSellerOrderId(address(this)); + uint256 orderId = dex.getNextOrderId(address(this)); uint256 assetId = asset.mint(assetAmount, ""); dex.createSellOrder(assetId, assetAmount, pricePerAsset); - uint256[] memory orderIds = new uint256[](1); - orderIds[0] = orderId; address payable[] memory sellers = new address payable[](1); sellers[0] = payable(address(this)); + uint256[] memory orderIds = new uint256[](1); + orderIds[0] = orderId; vm.startPrank(alice); vm.expectRevert( abi.encodeWithSelector( @@ -384,24 +365,20 @@ contract OrderbookDexTest is CTest, ERC1155Holder { assetAmount ) ); - dex.fillOrdersExactEth{value: assetAmount * pricePerAsset}( - assetAmount + 1, - sellers, - orderIds - ); + dex.fillOrdersExactEth{value: assetAmount * pricePerAsset}(assetAmount + 1, orderIds); } function test_CannotFillOrderIfInsufficientEndAmountExactAsset() public { uint256 assetAmount = 10; uint256 pricePerAsset = 100; - uint256 orderId = dex.getSellerOrderId(address(this)); + uint256 orderId = dex.getNextOrderId(address(this)); uint256 assetId = asset.mint(assetAmount, ""); dex.createSellOrder(assetId, assetAmount, pricePerAsset); - uint256[] memory orderIds = new uint256[](1); - orderIds[0] = orderId; address payable[] memory sellers = new address payable[](1); sellers[0] = payable(address(this)); + uint256[] memory orderIds = new uint256[](1); + orderIds[0] = orderId; vm.startPrank(alice); vm.expectRevert( abi.encodeWithSelector( @@ -410,11 +387,7 @@ contract OrderbookDexTest is CTest, ERC1155Holder { assetAmount ) ); - dex.fillOrdersExactAsset{value: assetAmount * pricePerAsset}( - assetAmount + 1, - sellers, - orderIds - ); + dex.fillOrdersExactAsset{value: assetAmount * pricePerAsset}(assetAmount + 1, orderIds); } function test_CannotSendEtherToDex() public {