diff --git a/.gas-snapshot b/.gas-snapshot index caa5da413..3ebe957f5 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -1,12 +1,12 @@ -GenericPoolOrderBookV3ArbOrderTakerTest:testMinimumOutput((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,uint256,uint256) (runs: 5096, μ: 333686, ~: 333142) -GenericPoolOrderBookV3ArbOrderTakerTest:testTakeOrdersSender((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 267873, ~: 268089) -GenericPoolOrderBookV3FlashBorrowerTest:testMinimumOutput((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,uint256,uint256) (runs: 5096, μ: 612882, ~: 610848) -GenericPoolOrderBookV3FlashBorrowerTest:testTakeOrdersSender((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 525962, ~: 525966) +GenericPoolOrderBookV3ArbOrderTakerTest:testMinimumOutput((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,uint256,uint256) (runs: 5096, μ: 333691, ~: 333142) +GenericPoolOrderBookV3ArbOrderTakerTest:testTakeOrdersSender((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 267876, ~: 267870) +GenericPoolOrderBookV3FlashBorrowerTest:testMinimumOutput((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,uint256,uint256) (runs: 5096, μ: 612882, ~: 610958) +GenericPoolOrderBookV3FlashBorrowerTest:testTakeOrdersSender((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 525963, ~: 526185) LibOrderTest:testHashEqual((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[])) (runs: 5096, μ: 194389, ~: 191140) LibOrderTest:testHashNotEqual((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[])) (runs: 5096, μ: 298734, ~: 298554) OrderBookAddOrderMockTest:testAddOrderSameAccountWithDifferentConfig(address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address,address) (runs: 5096, μ: 2771808, ~: 2761475) -OrderBookAddOrderMockTest:testAddOrderTwoAccountsWithDifferentConfig(address,address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address,address) (runs: 5096, μ: 2626717, ~: 2600883) -OrderBookAddOrderMockTest:testAddOrderTwoAccountsWithSameConfig(address,address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 2493275, ~: 2492266) +OrderBookAddOrderMockTest:testAddOrderTwoAccountsWithDifferentConfig(address,address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address,address) (runs: 5096, μ: 2626597, ~: 2600883) +OrderBookAddOrderMockTest:testAddOrderTwoAccountsWithSameConfig(address,address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 2493242, ~: 2492266) OrderBookAddOrderMockTest:testAddOrderWithCalculationsInputsAndOutputsSucceeds(address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 1295793, ~: 1281958) OrderBookAddOrderMockTest:testAddOrderWithNonEmptyMetaEmitsMetaV1(address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 1303904, ~: 1290881) OrderBookAddOrderMockTest:testAddOrderWithNonEmptyMetaReverts(address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 703690, ~: 697922) @@ -20,36 +20,43 @@ OrderBookAddOrderTest:testAddOrderRealThreeStackCalculate(address,((address,uint OrderBookAddOrderTest:testAddOrderRealTwoStackCalculateReverts(address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes)) (runs: 5096, μ: 715298, ~: 711943) OrderBookAddOrderTest:testAddOrderRealZeroStackCalculateReverts(address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes)) (runs: 5096, μ: 181518, ~: 180295) OrderBookDepositTest:testDepositEvent(address,uint256,uint256) (runs: 5096, μ: 38710, ~: 38710) -OrderBookDepositTest:testDepositFail(address,uint256,uint256) (runs: 5096, μ: 8937393460516740791, ~: 8937393460516740786) +OrderBookDepositTest:testDepositFail(address,uint256,uint256) (runs: 5096, μ: 8937393460516740789, ~: 8937393460516740786) OrderBookDepositTest:testDepositGas00() (gas: 8176) OrderBookDepositTest:testDepositGas01() (gas: 34620) -OrderBookDepositTest:testDepositMany((address,address,uint256,uint248)[]) (runs: 5096, μ: 5118682, ~: 4755733) +OrderBookDepositTest:testDepositMany((address,address,uint256,uint248)[]) (runs: 5096, μ: 4995652, ~: 4617129) OrderBookDepositTest:testDepositOverflow(address,uint256,uint256,uint256) (runs: 5096, μ: 46645, ~: 46645) -OrderBookDepositTest:testDepositReentrancy(address,uint256,uint256,address,uint256,uint256) (runs: 5096, μ: 495198, ~: 496632) +OrderBookDepositTest:testDepositReentrancy(address,uint256,uint256,address,uint256,uint256) (runs: 5096, μ: 495335, ~: 496632) OrderBookDepositTest:testDepositSimple(address,uint256,uint256) (runs: 5096, μ: 37840, ~: 37840) OrderBookDepositTest:testDepositZero(address,uint256) (runs: 5096, μ: 12639, ~: 12639) OrderBookDepositTest:testVaultBalanceNoDeposits(address,uint256) (runs: 5096, μ: 8263, ~: 8263) -OrderBookDepositTest:testVaultBalanceReentrant(address,uint256,uint256,address,address,uint256) (runs: 5096, μ: 494014, ~: 495964) +OrderBookDepositTest:testVaultBalanceReentrant(address,uint256,uint256,address,address,uint256) (runs: 5096, μ: 494144, ~: 495964) OrderBookRemoveOrderMockTest:testRemoveOrderAddRemoveMulti(address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 7256622, ~: 7139425) OrderBookRemoveOrderMockTest:testRemoveOrderDifferent(address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 5070365, ~: 5046372) -OrderBookRemoveOrderMockTest:testRemoveOrderDifferentOwners(address,address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 4854788, ~: 4841901) +OrderBookRemoveOrderMockTest:testRemoveOrderDifferentOwners(address,address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 4854223, ~: 4841901) OrderBookRemoveOrderMockTest:testRemoveOrderDifferentOwnersDifferent(address,address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 10466985, ~: 10453491) OrderBookRemoveOrderMockTest:testRemoveOrderDoesNotExist(address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 386811, ~: 382063) -OrderBookRemoveOrderMockTest:testRemoveOrderOnlyOwner(address,address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 2603402, ~: 2601080) -OrderBookTakeOrderNoopTest:testTakeOrderNoopNonLiveOrderOne((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,(address,uint256[],bytes)) (runs: 5096, μ: 431665, ~: 427913) -OrderBookTakeOrderNoopTest:testTakeOrderNoopNonLiveOrderTwo((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,uint256,uint256,(address,uint256[],bytes),(address,uint256[],bytes)) (runs: 5096, μ: 851326, ~: 847158) +OrderBookRemoveOrderMockTest:testRemoveOrderOnlyOwner(address,address,((address,uint8,uint256)[],(address,uint8,uint256)[],(address,bytes,uint256[]),bytes),address) (runs: 5096, μ: 2603102, ~: 2601080) +OrderBookTakeOrderMaximumInputTest:testTakeOrderMaximumInputMultipleOrders(uint256,uint256) (runs: 5096, μ: 485915, ~: 503260) +OrderBookTakeOrderMaximumInputTest:testTakeOrderMaximumInputMultipleOrdersMultipleOwners(uint256,uint256,uint256) (runs: 5096, μ: 534417, ~: 560581) +OrderBookTakeOrderMaximumInputTest:testTakeOrderMaximumInputSingleAnyDeposit(uint256,uint256) (runs: 5096, μ: 292768, ~: 298193) +OrderBookTakeOrderMaximumInputTest:testTakeOrderMaximumInputSingleOrderLessThanMaximumInput(uint256,uint256) (runs: 5096, μ: 278017, ~: 280317) +OrderBookTakeOrderMaximumInputTest:testTakeOrderMaximumInputSingleOrderLessThanMaximumOutput(uint256) (runs: 5096, μ: 278220, ~: 278155) +OrderBookTakeOrderMaximumInputTest:testTakeOrderMaximumInputSingleOrderUnlimitedMax(uint256) (runs: 5096, μ: 271824, ~: 271554) +OrderBookTakeOrderMaximumInputTest:testTakeOrderNoopZeroMaxTakerInput((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),(address,uint256[],bytes)) (runs: 5096, μ: 184268, ~: 183180) +OrderBookTakeOrderNoopTest:testTakeOrderNoopNonLiveOrderOne((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,(address,uint256[],bytes)) (runs: 5096, μ: 431689, ~: 427938) +OrderBookTakeOrderNoopTest:testTakeOrderNoopNonLiveOrderTwo((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,uint256,uint256,(address,uint256[],bytes),(address,uint256[],bytes)) (runs: 5096, μ: 851354, ~: 847183) OrderBookTakeOrderNoopTest:testTakeOrderNoopZeroOrders() (gas: 12403) -OrderBookTakeOrderTest:testTakeOrderPrecisionKnownBad01() (gas: 2631522) -OrderBookTakeOrderTokenMismatchDecimalsTest:testTokenMismatchInputs((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 622317, ~: 616897) -OrderBookTakeOrderTokenMismatchDecimalsTest:testTokenMismatchOutputs((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 622684, ~: 616897) -OrderBookTakeOrderTokenMismatchTest:testTokenMismatchInputs((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 618341, ~: 611981) -OrderBookTakeOrderTokenMismatchTest:testTokenMismatchOutputs((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 619601, ~: 613241) +OrderBookTakeOrderPrecisionTest:testTakeOrderPrecisionKnownBad01() (gas: 2631797) +OrderBookTakeOrderTokenMismatchDecimalsTest:testTokenMismatchInputs((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 622309, ~: 616965) +OrderBookTakeOrderTokenMismatchDecimalsTest:testTokenMismatchOutputs((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 622709, ~: 616922) +OrderBookTakeOrderTokenMismatchTest:testTokenMismatchInputs((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,uint256,uint256) (runs: 5096, μ: 618189, ~: 618644) +OrderBookTakeOrderTokenMismatchTest:testTokenMismatchOutputs((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,(address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,uint256,uint256) (runs: 5096, μ: 619539, ~: 619901) OrderBookWithdrawTest:testWithdrawEmptyVault(address,address,uint256,uint256) (runs: 5096, μ: 15251, ~: 15251) -OrderBookWithdrawTest:testWithdrawFailure(address,uint256,uint256,uint256) (runs: 5096, μ: 8937393460516719661, ~: 8937393460516738938) -OrderBookWithdrawTest:testWithdrawFullVault(address,uint256,uint256,uint256) (runs: 5096, μ: 41258, ~: 41256) -OrderBookWithdrawTest:testWithdrawMany((bool,address,address,uint256,uint248)[]) (runs: 5096, μ: 2542990, ~: 2235389) +OrderBookWithdrawTest:testWithdrawFailure(address,uint256,uint256,uint256) (runs: 5096, μ: 8937393460516719287, ~: 8937393460516700418) +OrderBookWithdrawTest:testWithdrawFullVault(address,uint256,uint256,uint256) (runs: 5096, μ: 41257, ~: 41254) +OrderBookWithdrawTest:testWithdrawMany((bool,address,address,uint256,uint248)[]) (runs: 5096, μ: 2543106, ~: 2233585) OrderBookWithdrawTest:testWithdrawPartialVault(address,uint256,uint256,uint256) (runs: 5096, μ: 51929, ~: 51929) -OrderBookWithdrawTest:testWithdrawReentrant(address,uint256,uint256,address,address,uint256) (runs: 5096, μ: 506228, ~: 507997) +OrderBookWithdrawTest:testWithdrawReentrant(address,uint256,uint256,address,address,uint256) (runs: 5096, μ: 506122, ~: 507997) OrderBookWithdrawTest:testWithdrawZero(address,address,uint256) (runs: 5096, μ: 12809, ~: 12809) -RouteProcessorOrderBookV3ArbOrderTakerTest:testMinimumOutput((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,uint256,uint256) (runs: 5096, μ: 333853, ~: 333304) +RouteProcessorOrderBookV3ArbOrderTakerTest:testMinimumOutput((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256,uint256,uint256) (runs: 5096, μ: 333852, ~: 333451) RouteProcessorOrderBookV3ArbOrderTakerTest:testTakeOrdersSender((address,bool,(address,address,address),(address,uint8,uint256)[],(address,uint8,uint256)[]),uint256,uint256) (runs: 5096, μ: 268039, ~: 268033) \ No newline at end of file diff --git a/meta/OrderBook.rain.meta b/meta/OrderBook.rain.meta index 8f567572b..eccb4ad6f 100644 Binary files a/meta/OrderBook.rain.meta and b/meta/OrderBook.rain.meta differ diff --git a/src/concrete/OrderBook.sol b/src/concrete/OrderBook.sol index 0af4432bd..f0c264acd 100644 --- a/src/concrete/OrderBook.sol +++ b/src/concrete/OrderBook.sol @@ -123,7 +123,7 @@ uint256 constant CONTEXT_VAULT_IO_BALANCE_DIFF = 4; uint256 constant CONTEXT_VAULT_IO_ROWS = 5; /// @dev Hash of the caller contract metadata for construction. -bytes32 constant CALLER_META_HASH = bytes32(0x71fe2f4f68f17dfe6ae7aba2bbd6cbfe5a2a48a93ebbc8b1f1900887b978eeee); +bytes32 constant CALLER_META_HASH = bytes32(0x1317ffd909f4ca1cd6402c7dd02501ba5965a5b8787a9627cde7b5f3f8f6f840); /// All information resulting from an order calculation that allows for vault IO /// to be calculated and applied, then the handle IO entrypoint to be dispatched. @@ -349,6 +349,9 @@ contract OrderBook is IOrderBookV3, ReentrancyGuard, Multicall, OrderBookV3Flash Order memory order; uint256 remainingTakerInput = config.maximumInput; + if (remainingTakerInput == 0) { + revert ZeroMaximumInput(); + } while (i < config.orders.length && remainingTakerInput > 0) { takeOrderConfig = config.orders[i]; order = takeOrderConfig.order; diff --git a/src/interface/unstable/IOrderBookV3.sol b/src/interface/unstable/IOrderBookV3.sol index 0d147708b..dbcfb12fe 100644 --- a/src/interface/unstable/IOrderBookV3.sol +++ b/src/interface/unstable/IOrderBookV3.sol @@ -2,7 +2,9 @@ pragma solidity ^0.8.18; import "../ierc3156/IERC3156FlashLender.sol"; -import "lib/rain.interpreter/src/lib/caller/LibEvaluable.sol"; +import { + EvaluableConfigV2, IExpressionDeployerV2, Evaluable +} from "lib/rain.interpreter/src/lib/caller/LibEvaluable.sol"; import "lib/rain.interpreter/src/interface/IInterpreterCallerV2.sol"; /// Import unmodified structures from older versions of `IOrderBook`. @@ -11,6 +13,9 @@ import {IO, Order, TakeOrderConfig, ClearConfig, ClearStateChange} from "../IOrd /// Thrown when take orders is called with no orders. error NoOrders(); +/// Thrown when take orders is called with a zero maximum input. +error ZeroMaximumInput(); + /// Config the order owner may provide to define their order. The `msg.sender` /// that adds an order cannot modify the owner nor bypass the integrity check of /// the expression deployer that they specify. However they MAY specify a diff --git a/test/concrete/OrderBook.takeOrder.maximumInput.t.sol b/test/concrete/OrderBook.takeOrder.maximumInput.t.sol new file mode 100644 index 000000000..1d56f4dff --- /dev/null +++ b/test/concrete/OrderBook.takeOrder.maximumInput.t.sol @@ -0,0 +1,319 @@ +// SPDX-License-Identifier: CAL +pragma solidity =0.8.19; + +import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol"; +import {OrderBookExternalRealTest, Vm} from "test/util/abstract/OrderBookExternalRealTest.sol"; +import { + Order, + SignedContextV1, + TakeOrderConfig, + TakeOrdersConfigV2, + ZeroMaximumInput, + IO, + EvaluableConfigV2, + OrderConfigV2 +} from "src/interface/unstable/IOrderBookV3.sol"; +import {IParserV1} from "rain.interpreter/src/interface/unstable/IParserV1.sol"; + +contract OrderBookTakeOrderMaximumInputTest is OrderBookExternalRealTest { + /// If there is some live order(s) but the maxTakerInput is zero we error as + /// the caller has full control over this, and it would cause none of the + /// orders to be taken. + function testTakeOrderNoopZeroMaxTakerInput(Order memory order, SignedContextV1 memory signedContext) external { + vm.assume(order.validInputs.length > 0); + vm.assume(order.validOutputs.length > 0); + TakeOrderConfig[] memory orders = new TakeOrderConfig[](1); + SignedContextV1[] memory signedContexts = new SignedContextV1[](1); + signedContexts[0] = signedContext; + orders[0] = TakeOrderConfig(order, 0, 0, signedContexts); + TakeOrdersConfigV2 memory config = TakeOrdersConfigV2(0, 0, type(uint256).max, orders, ""); + vm.expectRevert(ZeroMaximumInput.selector); + (uint256 totalTakerInput, uint256 totalTakerOutput) = iOrderbook.takeOrders(config); + (totalTakerInput, totalTakerOutput); + } + + struct TestOrder { + address owner; + bytes orderString; + } + + struct TestVault { + address owner; + address token; + uint256 deposit; + uint256 expect; + } + + function checkTakeOrderMaximumInput( + TestOrder[] memory testOrders, + TestVault[] memory testVaults, + uint256 maximumTakerInput, + uint256 expectedTakerInput, + uint256 expectedTakerOutput + ) internal { + address bob = address(uint160(uint256(keccak256("bob.rain.test")))); + uint256 vaultId = 0; + + Order[] memory orders = new Order[](testOrders.length); + + for (uint256 i = 0; i < testOrders.length; i++) { + { + OrderConfigV2 memory orderConfig; + { + (bytes memory bytecode, uint256[] memory constants) = + IParserV1(address(iDeployer)).parse(testOrders[i].orderString); + IO[] memory inputs = new IO[](1); + inputs[0] = IO(address(iToken0), 18, vaultId); + IO[] memory outputs = new IO[](1); + outputs[0] = IO(address(iToken1), 18, vaultId); + EvaluableConfigV2 memory evaluableConfig = EvaluableConfigV2(iDeployer, bytecode, constants); + orderConfig = OrderConfigV2(inputs, outputs, evaluableConfig, ""); + } + + vm.prank(testOrders[i].owner); + vm.recordLogs(); + iOrderbook.addOrder(orderConfig); + Vm.Log[] memory entries = vm.getRecordedLogs(); + assertEq(entries.length, 3); + (,, Order memory order,) = abi.decode(entries[2].data, (address, address, Order, bytes32)); + orders[i] = order; + } + } + + for (uint256 i = 0; i < testVaults.length; i++) { + if (testVaults[i].deposit > 0) { + // Deposit the amount of tokens required to take the order. + vm.mockCall( + address(iToken1), + abi.encodeWithSelector( + IERC20.transferFrom.selector, testVaults[i].owner, address(iOrderbook), testVaults[i].deposit + ), + abi.encode(true) + ); + vm.expectCall( + address(iToken1), + abi.encodeWithSelector( + IERC20.transferFrom.selector, testVaults[i].owner, address(iOrderbook), testVaults[i].deposit + ), + 1 + ); + uint256 balanceBefore = iOrderbook.vaultBalance(testVaults[i].owner, testVaults[i].token, vaultId); + vm.prank(testVaults[i].owner); + iOrderbook.deposit(testVaults[i].token, vaultId, testVaults[i].deposit); + assertEq( + iOrderbook.vaultBalance(testVaults[i].owner, testVaults[i].token, vaultId), + balanceBefore + testVaults[i].deposit, + "vaultBalance before" + ); + } + } + + TakeOrderConfig[] memory takeOrders = new TakeOrderConfig[](orders.length); + for (uint256 i = 0; i < orders.length; i++) { + takeOrders[i] = TakeOrderConfig(orders[i], 0, 0, new SignedContextV1[](0)); + } + TakeOrdersConfigV2 memory config = TakeOrdersConfigV2(0, maximumTakerInput, type(uint256).max, takeOrders, ""); + + // Mock and expect the token transfers. + vm.mockCall( + address(iToken1), + abi.encodeWithSelector(IERC20.transfer.selector, bob, expectedTakerInput), + abi.encode(true) + ); + vm.expectCall( + address(iToken1), + abi.encodeWithSelector(IERC20.transfer.selector, bob, expectedTakerInput), + expectedTakerInput > 0 ? 1 : 0 + ); + vm.mockCall( + address(iToken0), + abi.encodeWithSelector(IERC20.transferFrom.selector, bob, address(iOrderbook), expectedTakerOutput), + abi.encode(true) + ); + vm.expectCall( + address(iToken0), + abi.encodeWithSelector(IERC20.transferFrom.selector, bob, address(iOrderbook), expectedTakerOutput), + expectedTakerOutput > 0 ? 1 : 0 + ); + + vm.prank(bob); + (uint256 totalTakerInput, uint256 totalTakerOutput) = iOrderbook.takeOrders(config); + assertEq(totalTakerInput, expectedTakerInput, "totalTakerInput"); + assertEq(totalTakerOutput, expectedTakerOutput, "totalTakerOutput"); + + for (uint256 i = 0; i < testVaults.length; i++) { + assertEq( + iOrderbook.vaultBalance(testVaults[i].owner, testVaults[i].token, vaultId), + testVaults[i].expect, + "vaultBalance" + ); + } + } + + /// Add an order with unlimited maximum output and take it with a maximum + /// input. Only the maximum input should be taken. + function testTakeOrderMaximumInputSingleOrderUnlimitedMax(uint256 expectedTakerInput) external { + address owner = address(uint160(uint256(keccak256("owner.rain.test")))); + + expectedTakerInput = bound(expectedTakerInput, 1, type(uint128).max); + uint256 expectedTakerOutput = expectedTakerInput * 2; + + TestOrder[] memory testOrders = new TestOrder[](1); + testOrders[0] = TestOrder(owner, "_ _:max-decimal18-value() 2e18;:;"); + + TestVault[] memory testVaults = new TestVault[](2); + testVaults[0] = TestVault(owner, address(iToken1), expectedTakerInput, 0); + testVaults[1] = TestVault(owner, address(iToken0), 0, expectedTakerOutput); + + checkTakeOrderMaximumInput(testOrders, testVaults, expectedTakerInput, expectedTakerInput, expectedTakerOutput); + } + + /// Add an order with less than the maximum output. Only the limit from the + /// order should be taken. + function testTakeOrderMaximumInputSingleOrderLessThanMaximumOutput(uint256 maximumTakerInput) external { + address owner = address(uint160(uint256(keccak256("owner.rain.test")))); + maximumTakerInput = bound(maximumTakerInput, 1000, type(uint256).max); + uint256 expectedTakerInput = 1000; + uint256 expectedTakerOutput = expectedTakerInput * 2; + + TestOrder[] memory testOrders = new TestOrder[](1); + testOrders[0] = TestOrder(owner, "_ _:1000 2e18;:;"); + + TestVault[] memory testVaults = new TestVault[](2); + testVaults[0] = TestVault(owner, address(iToken1), expectedTakerInput, 0); + testVaults[1] = TestVault(owner, address(iToken0), 0, expectedTakerOutput); + + checkTakeOrderMaximumInput(testOrders, testVaults, maximumTakerInput, expectedTakerInput, expectedTakerOutput); + } + + /// If the vault balance is less than both the maximum input and the order + /// limit, the vault balance should be taken. + function testTakeOrderMaximumInputSingleOrderLessThanMaximumInput( + uint256 ownerDepositAmount, + uint256 maximumTakerInput + ) external { + address owner = address(uint160(uint256(keccak256("owner.rain.test")))); + uint256 orderLimit = 1000; + ownerDepositAmount = bound(ownerDepositAmount, 0, orderLimit - 1); + maximumTakerInput = bound(maximumTakerInput, 1000, type(uint256).max); + uint256 expectedTakerInput = ownerDepositAmount; + uint256 expectedTakerOutput = expectedTakerInput * 2; + + TestOrder[] memory testOrders = new TestOrder[](1); + testOrders[0] = TestOrder(owner, "_ _:1000 2e18;:;"); + + TestVault[] memory testVaults = new TestVault[](2); + testVaults[0] = TestVault(owner, address(iToken1), ownerDepositAmount, 0); + testVaults[1] = TestVault(owner, address(iToken0), 0, expectedTakerOutput); + + checkTakeOrderMaximumInput(testOrders, testVaults, maximumTakerInput, expectedTakerInput, expectedTakerOutput); + } + + /// The deposit amount can be anything actually, the order taking should + /// adjust accordingly, and leave any unspent deposited tokens in the vault. + function testTakeOrderMaximumInputSingleAnyDeposit(uint256 ownerDepositAmount, uint256 maximumTakerInput) + external + { + address owner = address(uint160(uint256(keccak256("owner.rain.test")))); + uint256 orderLimit = 1000; + + TestOrder[] memory testOrders = new TestOrder[](1); + testOrders[0] = TestOrder(owner, "_ _:1000 2e18;:;"); + + maximumTakerInput = bound(maximumTakerInput, 1, type(uint256).max); + // The expected input is the minimum of the maximum input and the order + // limit. + uint256 expectedTakerInput = maximumTakerInput < orderLimit ? maximumTakerInput : orderLimit; + + expectedTakerInput = expectedTakerInput < ownerDepositAmount ? expectedTakerInput : ownerDepositAmount; + uint256 expectedTakerOutput = expectedTakerInput * 2; + + TestVault[] memory testVaults = new TestVault[](2); + testVaults[0] = TestVault(owner, address(iToken1), ownerDepositAmount, ownerDepositAmount - expectedTakerInput); + testVaults[1] = TestVault(owner, address(iToken0), 0, expectedTakerOutput); + + checkTakeOrderMaximumInput(testOrders, testVaults, maximumTakerInput, expectedTakerInput, expectedTakerOutput); + } + + /// The taker input can be sourced from multiple orders. Tests two orders + /// that combined make up the maximum taker input. Both orders have the + /// same owner. + function testTakeOrderMaximumInputMultipleOrders(uint256 ownerDepositAmount, uint256 maximumTakerInput) external { + address owner = address(uint160(uint256(keccak256("owner.rain.test")))); + uint256 orderLimit = 1500; + + TestOrder[] memory testOrders = new TestOrder[](2); + testOrders[0] = TestOrder(owner, "_ _:1000 2e18;:;"); + testOrders[1] = TestOrder(owner, "_ _:500 2e18;:;"); + + maximumTakerInput = bound(maximumTakerInput, 1, type(uint256).max); + // The expected input is the minimum of the maximum input and the order + // limit. + uint256 expectedTakerInput = maximumTakerInput < orderLimit ? maximumTakerInput : orderLimit; + + expectedTakerInput = expectedTakerInput < ownerDepositAmount ? expectedTakerInput : ownerDepositAmount; + uint256 expectedTakerOutput = expectedTakerInput * 2; + + TestVault[] memory testVaults = new TestVault[](2); + testVaults[0] = TestVault(owner, address(iToken1), ownerDepositAmount, ownerDepositAmount - expectedTakerInput); + testVaults[1] = TestVault(owner, address(iToken0), 0, expectedTakerOutput); + + checkTakeOrderMaximumInput(testOrders, testVaults, maximumTakerInput, expectedTakerInput, expectedTakerOutput); + } + + /// The taker input can be source from multiple orders with multiple owners. + /// Tests two orders that combined make up the maximum taker input. Both + /// orders have different owners. + function testTakeOrderMaximumInputMultipleOrdersMultipleOwners( + uint256 ownerOneDepositAmount, + uint256 ownerTwoDepositAmount, + uint256 maximumTakerInput + ) external { + // Avoid information free overflow. + ownerTwoDepositAmount = bound(ownerTwoDepositAmount, 0, type(uint256).max - ownerOneDepositAmount); + + address ownerOne = address(uint160(uint256(keccak256("ownerOne.rain.test")))); + address ownerTwo = address(uint160(uint256(keccak256("ownerTwo.rain.test")))); + + TestOrder[] memory testOrders = new TestOrder[](2); + testOrders[0] = TestOrder(ownerOne, "_ _:1000 2e18;:;"); + testOrders[1] = TestOrder(ownerTwo, "_ _:500 2e18;:;"); + + maximumTakerInput = bound(maximumTakerInput, 1, type(uint256).max); + + // The first owner's deposit is fully used before the second owner's + // deposit is used. + TestVault[] memory testVaults = new TestVault[](2); + + uint256 expectedTakerInput; + uint256 ownerOneTakerInput; + { + // Owner one can't pay more than either their deposit or 1000 set in + // the order. + uint256 ownerOneMaxPayment = ownerOneDepositAmount < 1000 ? ownerOneDepositAmount : 1000; + // taker input from owner one is either the maximum taker input if + // it is less than the max owner one payment, or the max owner one + // payment. + ownerOneTakerInput = maximumTakerInput < ownerOneMaxPayment ? maximumTakerInput : ownerOneMaxPayment; + testVaults[0] = + TestVault(ownerOne, address(iToken1), ownerOneDepositAmount, ownerOneDepositAmount - ownerOneTakerInput); + } + + { + // Owner two can't pay more than either their deposit or 500 set in + // the order. + uint256 ownerTwoMaxPayment = ownerTwoDepositAmount < 500 ? ownerTwoDepositAmount : 500; + // Taker input from owner two is either whatever is remaining after + // owner one's payment, or the max owner two payment. + uint256 ownerTwoTakerInput = + ownerOneTakerInput < maximumTakerInput ? maximumTakerInput - ownerOneTakerInput : 0; + ownerTwoTakerInput = ownerTwoTakerInput < ownerTwoMaxPayment ? ownerTwoTakerInput : ownerTwoMaxPayment; + testVaults[1] = + TestVault(ownerTwo, address(iToken1), ownerTwoDepositAmount, ownerTwoDepositAmount - ownerTwoTakerInput); + expectedTakerInput = ownerOneTakerInput + ownerTwoTakerInput; + } + uint256 expectedTakerOutput = expectedTakerInput * 2; + + checkTakeOrderMaximumInput(testOrders, testVaults, maximumTakerInput, expectedTakerInput, expectedTakerOutput); + } +} diff --git a/test/concrete/OrderBook.takeOrder.t.sol b/test/concrete/OrderBook.takeOrder.precision.t.sol similarity index 97% rename from test/concrete/OrderBook.takeOrder.t.sol rename to test/concrete/OrderBook.takeOrder.precision.t.sol index 56afbfd6a..3ab3606eb 100644 --- a/test/concrete/OrderBook.takeOrder.t.sol +++ b/test/concrete/OrderBook.takeOrder.precision.t.sol @@ -3,9 +3,9 @@ pragma solidity =0.8.19; import "test/util/abstract/OrderBookExternalRealTest.sol"; -/// @title OrderBookTakeOrderTest +/// @title OrderBookTakeOrderPrecisionTest /// @notice A test harness for testing the OrderBook takeOrder function. -contract OrderBookTakeOrderTest is OrderBookExternalRealTest { +contract OrderBookTakeOrderPrecisionTest is OrderBookExternalRealTest { function checkPrecision( bytes memory rainString, uint8 outputTokenDecimals, diff --git a/test/concrete/OrderBook.takeOrder.tokenMismatch.t.sol b/test/concrete/OrderBook.takeOrder.tokenMismatch.t.sol index 14245c293..5f8634621 100644 --- a/test/concrete/OrderBook.takeOrder.tokenMismatch.t.sol +++ b/test/concrete/OrderBook.takeOrder.tokenMismatch.t.sol @@ -19,7 +19,9 @@ contract OrderBookTakeOrderTokenMismatchTest is OrderBookExternalRealTest { uint256 aOutputIOIndex, Order memory b, uint256 bInputIOIndex, - uint256 bOutputIOIndex + uint256 bOutputIOIndex, + uint256 maxTakerInput, + uint256 maxIORatio ) external { vm.assume(a.validInputs.length > 0); aInputIOIndex = bound(aInputIOIndex, 0, a.validInputs.length - 1); @@ -29,6 +31,7 @@ contract OrderBookTakeOrderTokenMismatchTest is OrderBookExternalRealTest { aOutputIOIndex = bound(aOutputIOIndex, 0, a.validOutputs.length - 1); vm.assume(b.validOutputs.length > 0); bOutputIOIndex = bound(bOutputIOIndex, 0, b.validOutputs.length - 1); + maxTakerInput = bound(maxTakerInput, 1, type(uint256).max); // Mismatch on inputs across orders taken. vm.assume(a.validInputs[aInputIOIndex].token != b.validInputs[bInputIOIndex].token); @@ -38,7 +41,7 @@ contract OrderBookTakeOrderTokenMismatchTest is OrderBookExternalRealTest { TakeOrderConfig[] memory orders = new TakeOrderConfig[](2); orders[0] = TakeOrderConfig(a, aInputIOIndex, aOutputIOIndex, new SignedContextV1[](0)); orders[1] = TakeOrderConfig(b, bInputIOIndex, bOutputIOIndex, new SignedContextV1[](0)); - TakeOrdersConfigV2 memory config = TakeOrdersConfigV2(0, type(uint256).max, type(uint256).max, orders, ""); + TakeOrdersConfigV2 memory config = TakeOrdersConfigV2(0, maxTakerInput, maxIORatio, orders, ""); vm.expectRevert( abi.encodeWithSelector( TokenMismatch.selector, b.validInputs[bInputIOIndex].token, a.validInputs[aInputIOIndex].token @@ -55,7 +58,9 @@ contract OrderBookTakeOrderTokenMismatchTest is OrderBookExternalRealTest { uint256 aOutputIOIndex, Order memory b, uint256 bInputIOIndex, - uint256 bOutputIOIndex + uint256 bOutputIOIndex, + uint256 maxTakerInput, + uint256 maxIORatio ) external { vm.assume(a.validInputs.length > 0); aInputIOIndex = bound(aInputIOIndex, 0, a.validInputs.length - 1); @@ -65,6 +70,7 @@ contract OrderBookTakeOrderTokenMismatchTest is OrderBookExternalRealTest { aOutputIOIndex = bound(aOutputIOIndex, 0, a.validOutputs.length - 1); vm.assume(b.validOutputs.length > 0); bOutputIOIndex = bound(bOutputIOIndex, 0, b.validOutputs.length - 1); + maxTakerInput = bound(maxTakerInput, 1, type(uint256).max); // Mismatch on outputs across orders taken. vm.assume(a.validOutputs[aOutputIOIndex].token != b.validOutputs[bOutputIOIndex].token); @@ -74,7 +80,7 @@ contract OrderBookTakeOrderTokenMismatchTest is OrderBookExternalRealTest { TakeOrderConfig[] memory orders = new TakeOrderConfig[](2); orders[0] = TakeOrderConfig(a, aInputIOIndex, aOutputIOIndex, new SignedContextV1[](0)); orders[1] = TakeOrderConfig(b, bInputIOIndex, bOutputIOIndex, new SignedContextV1[](0)); - TakeOrdersConfigV2 memory config = TakeOrdersConfigV2(0, type(uint256).max, type(uint256).max, orders, ""); + TakeOrdersConfigV2 memory config = TakeOrdersConfigV2(0, maxTakerInput, maxIORatio, orders, ""); vm.expectRevert( abi.encodeWithSelector( TokenMismatch.selector, b.validOutputs[bOutputIOIndex].token, a.validOutputs[aOutputIOIndex].token