@zoralabs/limit-orders 0.2.0 → 0.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.turbo/turbo-build$colon$js.log +50 -49
- package/CHANGELOG.md +73 -0
- package/abis/ISetLimitOrderConfig.json +27 -0
- package/abis/IWETH.json +118 -0
- package/abis/IZoraLimitOrderBook.json +5 -0
- package/abis/LimitOrderLiquidity.json +7 -0
- package/abis/LimitOrderViews.json +62 -0
- package/abis/{SimpleAccessManaged.json → Ownable.json} +29 -10
- package/abis/Ownable2Step.json +115 -0
- package/abis/PermittedCallers.json +181 -0
- package/abis/SwapWithLimitOrders.json +134 -14
- package/abis/ZoraLimitOrderBook.json +187 -35
- package/cache/solidity-files-cache.json +1 -1
- package/dist/index.cjs +219 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +219 -34
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +254 -41
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/out/BalanceDelta.sol/BalanceDeltaLibrary.json +1 -1
- package/out/BeforeSwapDelta.sol/BeforeSwapDeltaLibrary.json +1 -1
- package/out/BitMath.sol/BitMath.json +1 -1
- package/out/BytesLib.sol/BytesLib.json +1 -1
- package/out/CoinCommon.sol/CoinCommon.json +1 -1
- package/out/CoinConfigurationVersions.sol/CoinConfigurationVersions.json +1 -1
- package/out/CoinConstants.sol/CoinConstants.json +1 -1
- package/out/Context.sol/Context.json +1 -1
- package/out/Currency.sol/CurrencyLibrary.json +1 -1
- package/out/CurrencyReserves.sol/CurrencyReserves.json +1 -1
- package/out/CustomRevert.sol/CustomRevert.json +1 -1
- package/out/DopplerMath.sol/DopplerMath.json +1 -1
- package/out/FixedPoint128.sol/FixedPoint128.json +1 -1
- package/out/FixedPoint96.sol/FixedPoint96.json +1 -1
- package/out/FullMath.sol/FullMath.json +1 -1
- package/out/IAllowanceTransfer.sol/IAllowanceTransfer.json +1 -1
- package/out/ICoin.sol/ICoin.json +1 -1
- package/out/ICoin.sol/IHasCoinType.json +1 -1
- package/out/ICoin.sol/IHasPoolKey.json +1 -1
- package/out/ICoin.sol/IHasSwapPath.json +1 -1
- package/out/ICoin.sol/IHasTotalSupplyForPositions.json +1 -1
- package/out/IDeployedCoinVersionLookup.sol/IDeployedCoinVersionLookup.json +1 -1
- package/out/IDopplerErrors.sol/IDopplerErrors.json +1 -1
- package/out/IEIP712.sol/IEIP712.json +1 -1
- package/out/IERC1363.sol/IERC1363.json +1 -1
- package/out/IERC165.sol/IERC165.json +1 -1
- package/out/IERC20.sol/IERC20.json +1 -1
- package/out/IERC20Minimal.sol/IERC20Minimal.json +1 -1
- package/out/IERC6909Claims.sol/IERC6909Claims.json +1 -1
- package/out/IERC7572.sol/IERC7572.json +1 -1
- package/out/IExtsload.sol/IExtsload.json +1 -1
- package/out/IExttload.sol/IExttload.json +1 -1
- package/out/IHasRewardsRecipients.sol/IHasRewardsRecipients.json +1 -1
- package/out/IHooks.sol/IHooks.json +1 -1
- package/out/IMsgSender.sol/IMsgSender.json +1 -1
- package/out/IPoolManager.sol/IPoolManager.json +1 -1
- package/out/IProtocolFees.sol/IProtocolFees.json +1 -1
- package/out/ISetLimitOrderConfig.sol/ISetLimitOrderConfig.json +1 -0
- package/out/ISupportsLimitOrderFill.sol/ISupportsLimitOrderFill.json +1 -1
- package/out/ISwapPathRouter.sol/ISwapPathRouter.json +1 -1
- package/out/ISwapRouter.sol/ISwapRouter.json +1 -1
- package/out/IUniswapV3SwapCallback.sol/IUniswapV3SwapCallback.json +1 -1
- package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4Hook.json +1 -1
- package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4HookWithUpdateableFee.json +1 -1
- package/out/IUpgradeableV4Hook.sol/IUpgradeableV4Hook.json +1 -1
- package/out/IWETH.sol/IWETH.json +1 -0
- package/out/IZoraHookRegistry.sol/IZoraHookRegistry.json +1 -1
- package/out/IZoraLimitOrderBook.sol/IZoraLimitOrderBook.json +1 -1
- package/out/IZoraLimitOrderBookCoinsInterface.sol/IZoraLimitOrderBookCoinsInterface.json +1 -1
- package/out/IZoraV4CoinHook.sol/IZoraV4CoinHook.json +1 -1
- package/out/LimitOrderBitmap.sol/LimitOrderBitmap.json +1 -1
- package/out/LimitOrderCommon.sol/LimitOrderCommon.json +1 -1
- package/out/LimitOrderCreate.sol/LimitOrderCreate.json +1 -1
- package/out/LimitOrderFill.sol/LimitOrderFill.json +1 -1
- package/out/LimitOrderLiquidity.sol/LimitOrderLiquidity.json +1 -1
- package/out/LimitOrderQueues.sol/LimitOrderQueues.json +1 -1
- package/out/LimitOrderStorage.sol/LimitOrderStorage.json +1 -1
- package/out/LimitOrderTypes.sol/LimitOrderTypes.json +1 -1
- package/out/LimitOrderViews.sol/LimitOrderViews.json +1 -0
- package/out/LimitOrderWithdraw.sol/LimitOrderWithdraw.json +1 -1
- package/out/LiquidityAmounts.sol/LiquidityAmounts.json +1 -1
- package/out/LiquidityMath.sol/LiquidityMath.json +1 -1
- package/out/Lock.sol/Lock.json +1 -1
- package/out/NonzeroDeltaCount.sol/NonzeroDeltaCount.json +1 -1
- package/out/Ownable.sol/Ownable.json +1 -0
- package/out/Ownable2Step.sol/Ownable2Step.json +1 -0
- package/out/Path.sol/Path.json +1 -1
- package/out/PathKey.sol/PathKeyLibrary.json +1 -1
- package/out/Permit2Payments.sol/Permit2Payments.json +1 -1
- package/out/PermittedCallers.sol/PermittedCallers.json +1 -0
- package/out/PoolId.sol/PoolIdLibrary.json +1 -1
- package/out/Position.sol/Position.json +1 -1
- package/out/SafeCast.sol/SafeCast.json +1 -1
- package/out/SafeCast160.sol/SafeCast160.json +1 -1
- package/out/SafeERC20.sol/SafeERC20.json +1 -1
- package/out/SqrtPriceMath.sol/SqrtPriceMath.json +1 -1
- package/out/StateLibrary.sol/StateLibrary.json +1 -1
- package/out/SwapLimitOrders.sol/SwapLimitOrders.json +1 -1
- package/out/SwapWithLimitOrders.sol/SwapWithLimitOrders.json +1 -1
- package/out/TickBitmap.sol/TickBitmap.json +1 -1
- package/out/TickMath.sol/TickMath.json +1 -1
- package/out/TransientSlot.sol/TransientSlot.json +1 -1
- package/out/TransientStateLibrary.sol/TransientStateLibrary.json +1 -1
- package/out/UniV4SwapToCurrency.sol/UniV4SwapToCurrency.json +1 -1
- package/out/UnsafeMath.sol/UnsafeMath.json +1 -1
- package/out/V3ToV4SwapLib.sol/V3ToV4SwapLib.json +1 -1
- package/out/ZoraLimitOrderBook.sol/ZoraLimitOrderBook.json +1 -1
- package/out/build-info/37e0124d88d60569.json +1 -0
- package/out/uniswap/BitMath.sol/BitMath.json +1 -1
- package/out/uniswap/CustomRevert.sol/CustomRevert.json +1 -1
- package/out/uniswap/FullMath.sol/FullMath.json +1 -1
- package/out/uniswap/SafeCast.sol/SafeCast.json +1 -1
- package/out/uniswap/TickMath.sol/TickMath.json +1 -1
- package/package/wagmiGenerated.ts +218 -33
- package/package.json +1 -1
- package/src/IZoraLimitOrderBook.sol +5 -5
- package/src/ZoraLimitOrderBook.sol +24 -41
- package/src/access/PermittedCallers.sol +41 -0
- package/src/libs/LimitOrderBitmap.sol +0 -51
- package/src/libs/LimitOrderCommon.sol +48 -30
- package/src/libs/LimitOrderCreate.sol +5 -18
- package/src/libs/LimitOrderFill.sol +32 -161
- package/src/libs/LimitOrderLiquidity.sol +92 -71
- package/src/libs/LimitOrderViews.sol +168 -0
- package/src/libs/LimitOrderWithdraw.sol +13 -4
- package/src/libs/SwapLimitOrders.sol +14 -7
- package/src/router/ISetLimitOrderConfig.sol +12 -0
- package/src/router/SwapWithLimitOrders.sol +46 -33
- package/test/LimitOrderAccessControl.t.sol +173 -156
- package/test/LimitOrderBitmap.t.sol +13 -7
- package/test/LimitOrderFill.t.sol +42 -4
- package/test/LimitOrderLibraries.t.sol +18 -10
- package/test/LimitOrderLiquidityPayouts.t.sol +280 -3
- package/test/LimitOrderWithdraw.t.sol +28 -1
- package/test/SwapWithLimitOrders.t.sol +3 -5
- package/test/SwapWithLimitOrdersRouter.t.sol +108 -13
- package/test/gas/LimitOrderFillGas.t.sol +0 -7
- package/test/gas/LimitOrderSwapGas.t.sol +0 -6
- package/test/unit/LimitOrderBitmapUnit.t.sol +0 -134
- package/test/unit/LimitOrderCreateUnit.t.sol +32 -0
- package/test/unit/SwapLimitOrdersUnit.t.sol +231 -33
- package/test/unit/SwapLimitOrdersValidation.t.sol +28 -42
- package/test/unit/SwapWithLimitOrdersUnit.t.sol +21 -88
- package/test/utils/BaseTest.sol +34 -22
- package/test/utils/MockWETH.sol +39 -0
- package/test/utils/TestableZoraLimitOrderBook.sol +5 -7
- package/abis/IAuthority.json +0 -31
- package/abis/SimpleAccessManager.json +0 -351
- package/out/IAuthority.sol/IAuthority.json +0 -1
- package/out/SimpleAccessManaged.sol/SimpleAccessManaged.json +0 -1
- package/out/SimpleAccessManager.sol/SimpleAccessManager.json +0 -1
- package/out/build-info/69718f10d1dc37f0.json +0 -1
- package/src/access/SimpleAccessManaged.sol +0 -76
- package/src/access/SimpleAccessManager.sol +0 -268
- package/test/SimpleAccessManager.t.sol +0 -420
|
@@ -237,10 +237,10 @@ contract SwapWithLimitOrdersTestNonForked is SwapWithLimitOrdersTestBase {
|
|
|
237
237
|
// TODO: Test unallocated coin handling
|
|
238
238
|
}
|
|
239
239
|
|
|
240
|
-
function
|
|
241
|
-
// Test that
|
|
240
|
+
function test_limitOrderPlacement_zeroSize() public {
|
|
241
|
+
// Test that zero-size purchases don't create orders
|
|
242
242
|
// Should still execute swap but skip limit order ladder creation
|
|
243
|
-
// TODO: Test
|
|
243
|
+
// TODO: Test zero size handling
|
|
244
244
|
}
|
|
245
245
|
|
|
246
246
|
function test_limitOrderPlacement_supportsMultipleOrders() public {
|
|
@@ -307,9 +307,72 @@ contract SwapWithLimitOrdersTestNonForked is SwapWithLimitOrdersTestBase {
|
|
|
307
307
|
}
|
|
308
308
|
|
|
309
309
|
function test_orderFilling_invertedDirection() public {
|
|
310
|
-
//
|
|
311
|
-
//
|
|
312
|
-
//
|
|
310
|
+
// This test verifies the fix for audit issue #16
|
|
311
|
+
// https://github.com/kadenzipfel/zora-autosell-audit/issues/16
|
|
312
|
+
// The router should pass isCoinCurrency0 (not !isCoinCurrency0) to _fillOrders
|
|
313
|
+
|
|
314
|
+
PoolKey memory key = creatorCoin.getPoolKey();
|
|
315
|
+
|
|
316
|
+
// 1. Create first buyer's orders using swapWithLimitOrders
|
|
317
|
+
LimitOrderConfig memory limitOrderConfig = _prepareLimitOrderParams(users.buyer, _defaultMultiples(), _defaultPercentages());
|
|
318
|
+
deal(address(zoraToken), users.buyer, DEFAULT_LIMIT_ORDER_AMOUNT);
|
|
319
|
+
|
|
320
|
+
SwapWithLimitOrders.SwapWithLimitOrdersParams memory params = _buildDirectV4SwapParams(
|
|
321
|
+
users.buyer,
|
|
322
|
+
address(zoraToken),
|
|
323
|
+
DEFAULT_LIMIT_ORDER_AMOUNT,
|
|
324
|
+
key,
|
|
325
|
+
limitOrderConfig
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
vm.recordLogs();
|
|
329
|
+
_executeSwapWithLimitOrders(users.buyer, params);
|
|
330
|
+
CreatedOrderLog[] memory created = _decodeCreatedLogs(vm.getRecordedLogs());
|
|
331
|
+
assertGt(created.length, 0, "expected orders to be created");
|
|
332
|
+
|
|
333
|
+
// Store initial order state
|
|
334
|
+
address orderCoin = created[0].coin;
|
|
335
|
+
uint256 initialMakerBalance = _makerBalance(users.buyer, orderCoin);
|
|
336
|
+
assertGt(initialMakerBalance, 0, "buyer should have orders");
|
|
337
|
+
|
|
338
|
+
// 2. Mock the hook to not support ISupportsLimitOrderFill so router handles fills
|
|
339
|
+
bytes memory callData = abi.encodeWithSelector(IERC165.supportsInterface.selector, type(ISupportsLimitOrderFill).interfaceId);
|
|
340
|
+
vm.mockCall(address(key.hooks), callData, abi.encode(false));
|
|
341
|
+
|
|
342
|
+
// 3. Execute second swap that moves price beyond first orders AND creates new orders
|
|
343
|
+
// This ensures orders.length > 0 (from new orders) and tick moves past first orders
|
|
344
|
+
// Using a much larger swap to ensure we cross the first order ticks
|
|
345
|
+
LimitOrderConfig memory limitOrderConfig2 = _prepareLimitOrderParams(users.seller, _defaultMultiples(), _defaultPercentages());
|
|
346
|
+
uint256 largerSwapAmount = DEFAULT_LIMIT_ORDER_AMOUNT * 100; // 100x larger to move price significantly
|
|
347
|
+
deal(address(zoraToken), users.seller, largerSwapAmount);
|
|
348
|
+
|
|
349
|
+
SwapWithLimitOrders.SwapWithLimitOrdersParams memory params2 = _buildDirectV4SwapParams(
|
|
350
|
+
users.seller,
|
|
351
|
+
address(zoraToken),
|
|
352
|
+
largerSwapAmount,
|
|
353
|
+
key,
|
|
354
|
+
limitOrderConfig2
|
|
355
|
+
);
|
|
356
|
+
|
|
357
|
+
vm.recordLogs();
|
|
358
|
+
_executeSwapWithLimitOrders(users.seller, params2);
|
|
359
|
+
|
|
360
|
+
// 5. Verify fills occurred by checking FilledOrderLog events
|
|
361
|
+
FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
|
|
362
|
+
|
|
363
|
+
// The bug: with !isCoinCurrency0, fills won't happen because wrong direction
|
|
364
|
+
// After fix: fills SHOULD happen
|
|
365
|
+
assertGt(fills.length, 0, "orders should have been filled by router");
|
|
366
|
+
|
|
367
|
+
// 6. Verify orders were filled for correct maker
|
|
368
|
+
for (uint256 i = 0; i < fills.length; i++) {
|
|
369
|
+
assertEq(fills[i].maker, users.buyer, "incorrect maker");
|
|
370
|
+
assertEq(fills[i].coinIn, orderCoin, "incorrect coin");
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// 7. Verify maker balance decreased (orders filled and paid out)
|
|
374
|
+
uint256 finalMakerBalance = _makerBalance(users.buyer, orderCoin);
|
|
375
|
+
assertLt(finalMakerBalance, initialMakerBalance, "maker balance should decrease after fills");
|
|
313
376
|
}
|
|
314
377
|
|
|
315
378
|
function test_reverts_emptyV4Route() public {
|
|
@@ -485,7 +548,6 @@ contract SwapWithLimitOrdersTestNonForked is SwapWithLimitOrdersTestBase {
|
|
|
485
548
|
LimitOrderConfig memory limitOrderConfig = _prepareLimitOrderParams(users.buyer, _defaultMultiples(), _defaultPercentages());
|
|
486
549
|
|
|
487
550
|
uint256 previousMax = limitOrderBook.getMaxFillCount();
|
|
488
|
-
vm.prank(users.factoryOwner);
|
|
489
551
|
limitOrderBook.setMaxFillCount(0);
|
|
490
552
|
|
|
491
553
|
bytes memory callData = abi.encodeWithSelector(IERC165.supportsInterface.selector, type(ISupportsLimitOrderFill).interfaceId);
|
|
@@ -508,7 +570,6 @@ contract SwapWithLimitOrdersTestNonForked is SwapWithLimitOrdersTestBase {
|
|
|
508
570
|
assertEq(ordersFilled, 0, "max fill count zero should short-circuit fills");
|
|
509
571
|
|
|
510
572
|
vm.clearMockedCalls();
|
|
511
|
-
vm.prank(users.factoryOwner);
|
|
512
573
|
limitOrderBook.setMaxFillCount(previousMax);
|
|
513
574
|
}
|
|
514
575
|
|
|
@@ -586,13 +647,14 @@ contract SwapWithLimitOrdersTestNonForked is SwapWithLimitOrdersTestBase {
|
|
|
586
647
|
address recipient = address(uint160(uint256(log.topics[2])));
|
|
587
648
|
assertEq(sender, users.buyer, "sender indexed mismatch");
|
|
588
649
|
assertEq(recipient, users.buyer, "recipient indexed mismatch");
|
|
589
|
-
(PoolKey memory loggedPoolKey,
|
|
650
|
+
(PoolKey memory loggedPoolKey, , , int128 amount0, int128 amount1, uint160 sqrtPriceX96, CreatedOrder[] memory orders) = abi.decode(
|
|
590
651
|
log.data,
|
|
591
|
-
(PoolKey,
|
|
652
|
+
(PoolKey, int24, int24, int128, int128, uint160, CreatedOrder[])
|
|
592
653
|
);
|
|
593
654
|
assertEq(Currency.unwrap(loggedPoolKey.currency0), Currency.unwrap(poolKey.currency0), "pool currency0 mismatch");
|
|
594
655
|
assertEq(Currency.unwrap(loggedPoolKey.currency1), Currency.unwrap(poolKey.currency1), "pool currency1 mismatch");
|
|
595
|
-
|
|
656
|
+
assertTrue(amount0 != 0 || amount1 != 0, "swap amounts should be non-zero");
|
|
657
|
+
assertGt(sqrtPriceX96, 0, "sqrtPriceX96 should be non-zero");
|
|
596
658
|
assertGt(orders.length, 0, "should have created orders");
|
|
597
659
|
sawExecuted = true;
|
|
598
660
|
}
|
|
@@ -645,8 +707,41 @@ contract SwapWithLimitOrdersTestNonForked is SwapWithLimitOrdersTestBase {
|
|
|
645
707
|
}
|
|
646
708
|
|
|
647
709
|
function test_event_SwapWithLimitOrdersExecuted() public {
|
|
648
|
-
// Verify SwapWithLimitOrdersExecuted event
|
|
649
|
-
|
|
710
|
+
// Verify SwapWithLimitOrdersExecuted event emits price data fields
|
|
711
|
+
PoolKey memory poolKey = creatorCoin.getPoolKey();
|
|
712
|
+
LimitOrderConfig memory limitOrderConfig = _prepareLimitOrderParams(users.buyer, _defaultMultiples(), _defaultPercentages());
|
|
713
|
+
|
|
714
|
+
uint256 inputAmount = DEFAULT_LIMIT_ORDER_AMOUNT;
|
|
715
|
+
deal(address(zoraToken), users.buyer, inputAmount);
|
|
716
|
+
|
|
717
|
+
SwapWithLimitOrders.SwapWithLimitOrdersParams memory params = _buildDirectV4SwapParams(
|
|
718
|
+
users.buyer,
|
|
719
|
+
address(zoraToken),
|
|
720
|
+
inputAmount,
|
|
721
|
+
poolKey,
|
|
722
|
+
limitOrderConfig
|
|
723
|
+
);
|
|
724
|
+
|
|
725
|
+
vm.recordLogs();
|
|
726
|
+
_executeSwapWithLimitOrders(users.buyer, params);
|
|
727
|
+
|
|
728
|
+
SwapExecutedLog[] memory swaps = _decodeSwapExecutedLogs(vm.getRecordedLogs());
|
|
729
|
+
assertEq(swaps.length, 1, "expected single swap event");
|
|
730
|
+
|
|
731
|
+
// Verify price data fields are populated
|
|
732
|
+
SwapExecutedLog memory swap = swaps[0];
|
|
733
|
+
assertEq(swap.sender, users.buyer, "sender mismatch");
|
|
734
|
+
assertEq(swap.recipient, users.buyer, "recipient mismatch");
|
|
735
|
+
|
|
736
|
+
// amount0 and amount1 should be non-zero (one negative, one positive for a swap)
|
|
737
|
+
assertTrue(swap.amount0 != 0 || swap.amount1 != 0, "swap amounts should be non-zero");
|
|
738
|
+
assertTrue((swap.amount0 < 0 && swap.amount1 > 0) || (swap.amount0 > 0 && swap.amount1 < 0), "amounts should have opposite signs for a swap");
|
|
739
|
+
|
|
740
|
+
// sqrtPriceX96 should be a valid price (non-zero, reasonable range)
|
|
741
|
+
assertGt(swap.sqrtPriceX96, 0, "sqrtPriceX96 should be non-zero");
|
|
742
|
+
|
|
743
|
+
// Tick movement should be reflected
|
|
744
|
+
assertTrue(swap.tickBefore != swap.tickAfter || swap.amount0 == 0, "tick should move on swap");
|
|
650
745
|
}
|
|
651
746
|
|
|
652
747
|
function test_event_LimitOrdersCreated() public {
|
|
@@ -604,7 +604,6 @@ contract LimitOrderFillGasTest is BaseTest {
|
|
|
604
604
|
/// @dev Tests well beyond recommended maxFillCount to validate safety margins
|
|
605
605
|
function test_gas_user_swap_autofill_75_orders() public {
|
|
606
606
|
// Increase maxFillCount for this test
|
|
607
|
-
vm.prank(users.factoryOwner);
|
|
608
607
|
limitOrderBook.setMaxFillCount(100);
|
|
609
608
|
|
|
610
609
|
PoolKey memory contentKey = contentCoin.getPoolKey();
|
|
@@ -647,7 +646,6 @@ contract LimitOrderFillGasTest is BaseTest {
|
|
|
647
646
|
/// @dev Tests maximum scaling to identify absolute upper limits
|
|
648
647
|
function test_gas_user_swap_autofill_100_orders() public {
|
|
649
648
|
// Increase maxFillCount for this test
|
|
650
|
-
vm.prank(users.factoryOwner);
|
|
651
649
|
limitOrderBook.setMaxFillCount(100);
|
|
652
650
|
|
|
653
651
|
PoolKey memory contentKey = contentCoin.getPoolKey();
|
|
@@ -690,7 +688,6 @@ contract LimitOrderFillGasTest is BaseTest {
|
|
|
690
688
|
/// @dev Tests backend/bot operations - isolated fill() call without user swap overhead
|
|
691
689
|
function test_gas_backend_manual_fill_50_orders() public {
|
|
692
690
|
// Increase maxFillCount for this test
|
|
693
|
-
vm.prank(users.factoryOwner);
|
|
694
691
|
limitOrderBook.setMaxFillCount(100);
|
|
695
692
|
|
|
696
693
|
PoolKey memory contentKey = contentCoin.getPoolKey();
|
|
@@ -738,7 +735,6 @@ contract LimitOrderFillGasTest is BaseTest {
|
|
|
738
735
|
/// @dev Tests large backend batch operations
|
|
739
736
|
function test_gas_backend_manual_fill_100_orders() public {
|
|
740
737
|
// Increase maxFillCount for this test
|
|
741
|
-
vm.prank(users.factoryOwner);
|
|
742
738
|
limitOrderBook.setMaxFillCount(100);
|
|
743
739
|
|
|
744
740
|
PoolKey memory contentKey = contentCoin.getPoolKey();
|
|
@@ -786,7 +782,6 @@ contract LimitOrderFillGasTest is BaseTest {
|
|
|
786
782
|
/// @dev Tests extreme backend batch - likely exceeds reasonable block gas limits
|
|
787
783
|
function test_gas_backend_manual_fill_150_orders() public {
|
|
788
784
|
// Increase maxFillCount for this test
|
|
789
|
-
vm.prank(users.factoryOwner);
|
|
790
785
|
limitOrderBook.setMaxFillCount(200);
|
|
791
786
|
|
|
792
787
|
PoolKey memory contentKey = contentCoin.getPoolKey();
|
|
@@ -853,12 +848,10 @@ contract LimitOrderFillGasTest is BaseTest {
|
|
|
853
848
|
/// @dev Disables hook's auto-fill, executes swap, re-enables auto-fill
|
|
854
849
|
function _movePriceBeyondTicksWithAutoFillDisabled(CreatedOrderLog[] memory created) internal override {
|
|
855
850
|
uint256 previousMaxFillCount = limitOrderBook.getMaxFillCount();
|
|
856
|
-
vm.prank(users.factoryOwner);
|
|
857
851
|
limitOrderBook.setMaxFillCount(0);
|
|
858
852
|
|
|
859
853
|
_movePriceBeyondTicks(created);
|
|
860
854
|
|
|
861
|
-
vm.prank(users.factoryOwner);
|
|
862
855
|
limitOrderBook.setMaxFillCount(previousMaxFillCount);
|
|
863
856
|
}
|
|
864
857
|
|
|
@@ -107,7 +107,6 @@ contract LimitOrderSwapGasTest is BaseTest {
|
|
|
107
107
|
CreatedOrderLog[] memory preloaded = existingOrders > 0 ? _preloadOrders(creatorKey, isCurrency0, existingOrders) : new CreatedOrderLog[](0);
|
|
108
108
|
emit log_named_uint("PRELOADED_ORDERS", preloaded.length);
|
|
109
109
|
|
|
110
|
-
vm.prank(users.factoryOwner);
|
|
111
110
|
limitOrderBook.setMaxFillCount(existingOrders + 10);
|
|
112
111
|
|
|
113
112
|
PoolKey[] memory v4Route = new PoolKey[](1);
|
|
@@ -152,7 +151,6 @@ contract LimitOrderSwapGasTest is BaseTest {
|
|
|
152
151
|
emit log_named_uint("PRELOADED_ORDERS", preloaded.length);
|
|
153
152
|
|
|
154
153
|
// allow enough fills (existing + some headroom for newly in-range orders)
|
|
155
|
-
vm.prank(users.factoryOwner);
|
|
156
154
|
limitOrderBook.setMaxFillCount(existingOrders + 10);
|
|
157
155
|
|
|
158
156
|
// build router params (hop2: ZORA -> creator -> content)
|
|
@@ -198,7 +196,6 @@ contract LimitOrderSwapGasTest is BaseTest {
|
|
|
198
196
|
CreatedOrderLog[] memory preloaded = existingOrders > 0 ? _preloadOrders(contentKey, isCurrency0, existingOrders) : new CreatedOrderLog[](0);
|
|
199
197
|
emit log_named_uint("PRELOADED_ORDERS", preloaded.length);
|
|
200
198
|
|
|
201
|
-
vm.prank(users.factoryOwner);
|
|
202
199
|
limitOrderBook.setMaxFillCount(existingOrders + 10);
|
|
203
200
|
|
|
204
201
|
PoolKey[] memory v4Route = new PoolKey[](2);
|
|
@@ -243,7 +240,6 @@ contract LimitOrderSwapGasTest is BaseTest {
|
|
|
243
240
|
CreatedOrderLog[] memory preloaded = existingOrders > 0 ? _preloadOrders(contentKey, isCurrency0, existingOrders) : new CreatedOrderLog[](0);
|
|
244
241
|
emit log_named_uint("PRELOADED_ORDERS", preloaded.length);
|
|
245
242
|
|
|
246
|
-
vm.prank(users.factoryOwner);
|
|
247
243
|
limitOrderBook.setMaxFillCount(existingOrders + 10);
|
|
248
244
|
|
|
249
245
|
PoolKey[] memory v4Route = new PoolKey[](2);
|
|
@@ -285,7 +281,6 @@ contract LimitOrderSwapGasTest is BaseTest {
|
|
|
285
281
|
PoolKey memory creatorKey = creatorCoin.getPoolKey();
|
|
286
282
|
bool isCurrency0 = Currency.unwrap(creatorKey.currency0) == address(creatorCoin);
|
|
287
283
|
|
|
288
|
-
vm.prank(users.factoryOwner);
|
|
289
284
|
limitOrderBook.setMaxFillCount(10);
|
|
290
285
|
|
|
291
286
|
PoolKey[] memory v4Route = new PoolKey[](1);
|
|
@@ -326,7 +321,6 @@ contract LimitOrderSwapGasTest is BaseTest {
|
|
|
326
321
|
PoolKey memory creatorKey = creatorCoin.getPoolKey();
|
|
327
322
|
bool isCurrency0 = Currency.unwrap(creatorKey.currency0) == address(creatorCoin);
|
|
328
323
|
|
|
329
|
-
vm.prank(users.factoryOwner);
|
|
330
324
|
limitOrderBook.setMaxFillCount(10);
|
|
331
325
|
|
|
332
326
|
PoolKey[] memory v4Route = new PoolKey[](1);
|
|
@@ -82,140 +82,6 @@ contract LimitOrderBitmapUnitTest is Test {
|
|
|
82
82
|
assertTrue(_isTickSet(tick), "bit should remain set when sizeAfter > 0");
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
/// @notice Tests getExecutableTicks with zeroForOne = true (downward price movement)
|
|
86
|
-
function test_getExecutableTicks_zeroForOne_findsInitializedTicks() public {
|
|
87
|
-
// Set up ticks: 10000, 10200, 10400 (spaced by TICK_SPACING)
|
|
88
|
-
int24 tick1 = 10000;
|
|
89
|
-
int24 tick2 = 10200;
|
|
90
|
-
int24 tick3 = 10400;
|
|
91
|
-
|
|
92
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick1, TICK_SPACING, 0);
|
|
93
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick2, TICK_SPACING, 0);
|
|
94
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick3, TICK_SPACING, 0);
|
|
95
|
-
|
|
96
|
-
// Set queue lengths to non-zero (simulates orders exist)
|
|
97
|
-
poolQueue[tick1].length = 1;
|
|
98
|
-
poolQueue[tick2].length = 1;
|
|
99
|
-
poolQueue[tick3].length = 1;
|
|
100
|
-
|
|
101
|
-
// Swap from 10800 down to 9800 (crosses all three ticks)
|
|
102
|
-
int24 tickBefore = 10800;
|
|
103
|
-
int24 tickAfter = 9800;
|
|
104
|
-
bool zeroForOne = true;
|
|
105
|
-
|
|
106
|
-
int24[] memory executableTicks = LimitOrderBitmap.getExecutableTicks(bitmap, poolQueue, TICK_SPACING, zeroForOne, tickBefore, tickAfter);
|
|
107
|
-
|
|
108
|
-
// Should find all 3 initialized ticks
|
|
109
|
-
assertEq(executableTicks.length, 3, "should find 3 executable ticks");
|
|
110
|
-
assertEq(executableTicks[0], tick3, "should find tick3 first (highest)");
|
|
111
|
-
assertEq(executableTicks[1], tick2, "should find tick2 second");
|
|
112
|
-
assertEq(executableTicks[2], tick1, "should find tick1 last (lowest)");
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/// @notice Tests getExecutableTicks with zeroForOne = false (upward price movement)
|
|
116
|
-
function test_getExecutableTicks_oneForZero_findsInitializedTicks() public {
|
|
117
|
-
// Set up ticks: 5000, 5200, 5400
|
|
118
|
-
int24 tick1 = 5000;
|
|
119
|
-
int24 tick2 = 5200;
|
|
120
|
-
int24 tick3 = 5400;
|
|
121
|
-
|
|
122
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick1, TICK_SPACING, 0);
|
|
123
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick2, TICK_SPACING, 0);
|
|
124
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick3, TICK_SPACING, 0);
|
|
125
|
-
|
|
126
|
-
poolQueue[tick1].length = 1;
|
|
127
|
-
poolQueue[tick2].length = 1;
|
|
128
|
-
poolQueue[tick3].length = 1;
|
|
129
|
-
|
|
130
|
-
// Swap from 4800 up to 5600 (crosses all three ticks)
|
|
131
|
-
int24 tickBefore = 4800;
|
|
132
|
-
int24 tickAfter = 5600;
|
|
133
|
-
bool zeroForOne = false;
|
|
134
|
-
|
|
135
|
-
int24[] memory executableTicks = LimitOrderBitmap.getExecutableTicks(bitmap, poolQueue, TICK_SPACING, zeroForOne, tickBefore, tickAfter);
|
|
136
|
-
|
|
137
|
-
// Should find all 3 initialized ticks in ascending order
|
|
138
|
-
assertEq(executableTicks.length, 3, "should find 3 executable ticks");
|
|
139
|
-
assertEq(executableTicks[0], tick1, "should find tick1 first (lowest)");
|
|
140
|
-
assertEq(executableTicks[1], tick2, "should find tick2 second");
|
|
141
|
-
assertEq(executableTicks[2], tick3, "should find tick3 last (highest)");
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/// @notice Tests getExecutableTicks skips ticks with empty queues
|
|
145
|
-
function test_getExecutableTicks_skipsEmptyQueues() public {
|
|
146
|
-
// Set up 3 ticks, but only 2 have orders
|
|
147
|
-
int24 tick1 = 6000;
|
|
148
|
-
int24 tick2 = 6200; // This one will have empty queue
|
|
149
|
-
int24 tick3 = 6400;
|
|
150
|
-
|
|
151
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick1, TICK_SPACING, 0);
|
|
152
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick2, TICK_SPACING, 0);
|
|
153
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick3, TICK_SPACING, 0);
|
|
154
|
-
|
|
155
|
-
poolQueue[tick1].length = 1;
|
|
156
|
-
poolQueue[tick2].length = 0; // Empty queue - should skip
|
|
157
|
-
poolQueue[tick3].length = 1;
|
|
158
|
-
|
|
159
|
-
int24 tickBefore = 6600;
|
|
160
|
-
int24 tickAfter = 5800;
|
|
161
|
-
bool zeroForOne = true;
|
|
162
|
-
|
|
163
|
-
int24[] memory executableTicks = LimitOrderBitmap.getExecutableTicks(bitmap, poolQueue, TICK_SPACING, zeroForOne, tickBefore, tickAfter);
|
|
164
|
-
|
|
165
|
-
// Should find only 2 ticks (skip tick2 with empty queue)
|
|
166
|
-
assertEq(executableTicks.length, 2, "should find only 2 executable ticks");
|
|
167
|
-
assertEq(executableTicks[0], tick3, "should find tick3");
|
|
168
|
-
assertEq(executableTicks[1], tick1, "should find tick1");
|
|
169
|
-
// tick2 should not be in the array
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/// @notice Tests getExecutableTicks with no movement (tickBefore == tickAfter)
|
|
173
|
-
function test_getExecutableTicks_noMovement_returnsEmpty() public {
|
|
174
|
-
int24 tick = 7000;
|
|
175
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick, TICK_SPACING, 0);
|
|
176
|
-
poolQueue[tick].length = 1;
|
|
177
|
-
|
|
178
|
-
int24 tickBefore = 7000;
|
|
179
|
-
int24 tickAfter = 7000; // No movement
|
|
180
|
-
bool zeroForOne = true;
|
|
181
|
-
|
|
182
|
-
int24[] memory executableTicks = LimitOrderBitmap.getExecutableTicks(bitmap, poolQueue, TICK_SPACING, zeroForOne, tickBefore, tickAfter);
|
|
183
|
-
|
|
184
|
-
// Should return empty array (no ticks crossed)
|
|
185
|
-
assertEq(executableTicks.length, 0, "should return empty array for no movement");
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/// @notice Tests getExecutableTicks stops at target even if more ticks initialized beyond
|
|
189
|
-
function test_getExecutableTicks_stopsAtTarget() public {
|
|
190
|
-
// Set up ticks: 8000, 8200, 8400, 8600
|
|
191
|
-
int24 tick1 = 8000;
|
|
192
|
-
int24 tick2 = 8200;
|
|
193
|
-
int24 tick3 = 8400;
|
|
194
|
-
int24 tick4 = 8600;
|
|
195
|
-
|
|
196
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick1, TICK_SPACING, 0);
|
|
197
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick2, TICK_SPACING, 0);
|
|
198
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick3, TICK_SPACING, 0);
|
|
199
|
-
LimitOrderBitmap.setIfFirst(bitmap, tick4, TICK_SPACING, 0);
|
|
200
|
-
|
|
201
|
-
poolQueue[tick1].length = 1;
|
|
202
|
-
poolQueue[tick2].length = 1;
|
|
203
|
-
poolQueue[tick3].length = 1;
|
|
204
|
-
poolQueue[tick4].length = 1;
|
|
205
|
-
|
|
206
|
-
// Swap only crosses tick4 and tick3, stops before tick2
|
|
207
|
-
int24 tickBefore = 8800;
|
|
208
|
-
int24 tickAfter = 8300; // Stops between tick3 and tick2
|
|
209
|
-
bool zeroForOne = true;
|
|
210
|
-
|
|
211
|
-
int24[] memory executableTicks = LimitOrderBitmap.getExecutableTicks(bitmap, poolQueue, TICK_SPACING, zeroForOne, tickBefore, tickAfter);
|
|
212
|
-
|
|
213
|
-
// Should find only tick4 and tick3 (stops at target)
|
|
214
|
-
assertEq(executableTicks.length, 2, "should find only 2 ticks before target");
|
|
215
|
-
assertEq(executableTicks[0], tick4, "should find tick4");
|
|
216
|
-
assertEq(executableTicks[1], tick3, "should find tick3");
|
|
217
|
-
}
|
|
218
|
-
|
|
219
85
|
/// @notice Tests word boundaries (ticks at 256 * spacing intervals)
|
|
220
86
|
function test_setIfFirst_wordBoundary() public {
|
|
221
87
|
// Ticks at word boundaries
|
|
@@ -73,6 +73,24 @@ contract LimitOrderCreateWrapper {
|
|
|
73
73
|
refunded = requestedSize - realizedSize;
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
|
+
|
|
77
|
+
/// @notice Generates orderId using abi.encode (matches getOrderId)
|
|
78
|
+
function getOrderId(bytes32 poolKeyHash, address coin, int24 tick, address maker, uint256 nonce) external pure returns (bytes32) {
|
|
79
|
+
return keccak256(abi.encode(poolKeyHash, coin, tick, maker, nonce));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/// @notice Generates orderId using assembly (matches _generateOrderId in LimitOrderCreate.sol)
|
|
83
|
+
function generateOrderIdAssembly(bytes32 poolKeyHash, address coin, int24 orderTick, address maker, uint256 nonce) external pure returns (bytes32 orderId) {
|
|
84
|
+
assembly ("memory-safe") {
|
|
85
|
+
let ptr := mload(0x40)
|
|
86
|
+
mstore(ptr, poolKeyHash)
|
|
87
|
+
mstore(add(ptr, 0x20), coin)
|
|
88
|
+
mstore(add(ptr, 0x40), orderTick)
|
|
89
|
+
mstore(add(ptr, 0x60), maker)
|
|
90
|
+
mstore(add(ptr, 0x80), nonce)
|
|
91
|
+
orderId := keccak256(ptr, 0xa0)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
76
94
|
}
|
|
77
95
|
|
|
78
96
|
/// @notice Direct unit tests for LimitOrderCreate library functions
|
|
@@ -355,4 +373,18 @@ contract LimitOrderCreateUnitTest is Test {
|
|
|
355
373
|
uint128 realizedSize = wrapper.calculateRealizedSize(isCurrency0, amount0, amount1);
|
|
356
374
|
assertEq(realizedSize, 1000000000, "should handle large negative value");
|
|
357
375
|
}
|
|
376
|
+
|
|
377
|
+
/// @notice Verifies getOrderId and _generateOrderId produce identical hashes
|
|
378
|
+
function test_orderId_abiEncodeMatches() public view {
|
|
379
|
+
bytes32 poolKeyHash = keccak256("test-pool");
|
|
380
|
+
address coin = address(0x1234567890123456789012345678901234567890);
|
|
381
|
+
int24 tick = -12345;
|
|
382
|
+
address maker = address(0xabCDeF0123456789AbcdEf0123456789aBCDEF01);
|
|
383
|
+
uint256 nonce = 42;
|
|
384
|
+
|
|
385
|
+
bytes32 fromAbiEncode = wrapper.getOrderId(poolKeyHash, coin, tick, maker, nonce);
|
|
386
|
+
bytes32 fromAssembly = wrapper.generateOrderIdAssembly(poolKeyHash, coin, tick, maker, nonce);
|
|
387
|
+
|
|
388
|
+
assertEq(fromAbiEncode, fromAssembly, "orderId derivation must match");
|
|
389
|
+
}
|
|
358
390
|
}
|