@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.
Files changed (154) hide show
  1. package/.turbo/turbo-build$colon$js.log +50 -49
  2. package/CHANGELOG.md +73 -0
  3. package/abis/ISetLimitOrderConfig.json +27 -0
  4. package/abis/IWETH.json +118 -0
  5. package/abis/IZoraLimitOrderBook.json +5 -0
  6. package/abis/LimitOrderLiquidity.json +7 -0
  7. package/abis/LimitOrderViews.json +62 -0
  8. package/abis/{SimpleAccessManaged.json → Ownable.json} +29 -10
  9. package/abis/Ownable2Step.json +115 -0
  10. package/abis/PermittedCallers.json +181 -0
  11. package/abis/SwapWithLimitOrders.json +134 -14
  12. package/abis/ZoraLimitOrderBook.json +187 -35
  13. package/cache/solidity-files-cache.json +1 -1
  14. package/dist/index.cjs +219 -34
  15. package/dist/index.cjs.map +1 -1
  16. package/dist/index.js +219 -34
  17. package/dist/index.js.map +1 -1
  18. package/dist/wagmiGenerated.d.ts +254 -41
  19. package/dist/wagmiGenerated.d.ts.map +1 -1
  20. package/out/BalanceDelta.sol/BalanceDeltaLibrary.json +1 -1
  21. package/out/BeforeSwapDelta.sol/BeforeSwapDeltaLibrary.json +1 -1
  22. package/out/BitMath.sol/BitMath.json +1 -1
  23. package/out/BytesLib.sol/BytesLib.json +1 -1
  24. package/out/CoinCommon.sol/CoinCommon.json +1 -1
  25. package/out/CoinConfigurationVersions.sol/CoinConfigurationVersions.json +1 -1
  26. package/out/CoinConstants.sol/CoinConstants.json +1 -1
  27. package/out/Context.sol/Context.json +1 -1
  28. package/out/Currency.sol/CurrencyLibrary.json +1 -1
  29. package/out/CurrencyReserves.sol/CurrencyReserves.json +1 -1
  30. package/out/CustomRevert.sol/CustomRevert.json +1 -1
  31. package/out/DopplerMath.sol/DopplerMath.json +1 -1
  32. package/out/FixedPoint128.sol/FixedPoint128.json +1 -1
  33. package/out/FixedPoint96.sol/FixedPoint96.json +1 -1
  34. package/out/FullMath.sol/FullMath.json +1 -1
  35. package/out/IAllowanceTransfer.sol/IAllowanceTransfer.json +1 -1
  36. package/out/ICoin.sol/ICoin.json +1 -1
  37. package/out/ICoin.sol/IHasCoinType.json +1 -1
  38. package/out/ICoin.sol/IHasPoolKey.json +1 -1
  39. package/out/ICoin.sol/IHasSwapPath.json +1 -1
  40. package/out/ICoin.sol/IHasTotalSupplyForPositions.json +1 -1
  41. package/out/IDeployedCoinVersionLookup.sol/IDeployedCoinVersionLookup.json +1 -1
  42. package/out/IDopplerErrors.sol/IDopplerErrors.json +1 -1
  43. package/out/IEIP712.sol/IEIP712.json +1 -1
  44. package/out/IERC1363.sol/IERC1363.json +1 -1
  45. package/out/IERC165.sol/IERC165.json +1 -1
  46. package/out/IERC20.sol/IERC20.json +1 -1
  47. package/out/IERC20Minimal.sol/IERC20Minimal.json +1 -1
  48. package/out/IERC6909Claims.sol/IERC6909Claims.json +1 -1
  49. package/out/IERC7572.sol/IERC7572.json +1 -1
  50. package/out/IExtsload.sol/IExtsload.json +1 -1
  51. package/out/IExttload.sol/IExttload.json +1 -1
  52. package/out/IHasRewardsRecipients.sol/IHasRewardsRecipients.json +1 -1
  53. package/out/IHooks.sol/IHooks.json +1 -1
  54. package/out/IMsgSender.sol/IMsgSender.json +1 -1
  55. package/out/IPoolManager.sol/IPoolManager.json +1 -1
  56. package/out/IProtocolFees.sol/IProtocolFees.json +1 -1
  57. package/out/ISetLimitOrderConfig.sol/ISetLimitOrderConfig.json +1 -0
  58. package/out/ISupportsLimitOrderFill.sol/ISupportsLimitOrderFill.json +1 -1
  59. package/out/ISwapPathRouter.sol/ISwapPathRouter.json +1 -1
  60. package/out/ISwapRouter.sol/ISwapRouter.json +1 -1
  61. package/out/IUniswapV3SwapCallback.sol/IUniswapV3SwapCallback.json +1 -1
  62. package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4Hook.json +1 -1
  63. package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4HookWithUpdateableFee.json +1 -1
  64. package/out/IUpgradeableV4Hook.sol/IUpgradeableV4Hook.json +1 -1
  65. package/out/IWETH.sol/IWETH.json +1 -0
  66. package/out/IZoraHookRegistry.sol/IZoraHookRegistry.json +1 -1
  67. package/out/IZoraLimitOrderBook.sol/IZoraLimitOrderBook.json +1 -1
  68. package/out/IZoraLimitOrderBookCoinsInterface.sol/IZoraLimitOrderBookCoinsInterface.json +1 -1
  69. package/out/IZoraV4CoinHook.sol/IZoraV4CoinHook.json +1 -1
  70. package/out/LimitOrderBitmap.sol/LimitOrderBitmap.json +1 -1
  71. package/out/LimitOrderCommon.sol/LimitOrderCommon.json +1 -1
  72. package/out/LimitOrderCreate.sol/LimitOrderCreate.json +1 -1
  73. package/out/LimitOrderFill.sol/LimitOrderFill.json +1 -1
  74. package/out/LimitOrderLiquidity.sol/LimitOrderLiquidity.json +1 -1
  75. package/out/LimitOrderQueues.sol/LimitOrderQueues.json +1 -1
  76. package/out/LimitOrderStorage.sol/LimitOrderStorage.json +1 -1
  77. package/out/LimitOrderTypes.sol/LimitOrderTypes.json +1 -1
  78. package/out/LimitOrderViews.sol/LimitOrderViews.json +1 -0
  79. package/out/LimitOrderWithdraw.sol/LimitOrderWithdraw.json +1 -1
  80. package/out/LiquidityAmounts.sol/LiquidityAmounts.json +1 -1
  81. package/out/LiquidityMath.sol/LiquidityMath.json +1 -1
  82. package/out/Lock.sol/Lock.json +1 -1
  83. package/out/NonzeroDeltaCount.sol/NonzeroDeltaCount.json +1 -1
  84. package/out/Ownable.sol/Ownable.json +1 -0
  85. package/out/Ownable2Step.sol/Ownable2Step.json +1 -0
  86. package/out/Path.sol/Path.json +1 -1
  87. package/out/PathKey.sol/PathKeyLibrary.json +1 -1
  88. package/out/Permit2Payments.sol/Permit2Payments.json +1 -1
  89. package/out/PermittedCallers.sol/PermittedCallers.json +1 -0
  90. package/out/PoolId.sol/PoolIdLibrary.json +1 -1
  91. package/out/Position.sol/Position.json +1 -1
  92. package/out/SafeCast.sol/SafeCast.json +1 -1
  93. package/out/SafeCast160.sol/SafeCast160.json +1 -1
  94. package/out/SafeERC20.sol/SafeERC20.json +1 -1
  95. package/out/SqrtPriceMath.sol/SqrtPriceMath.json +1 -1
  96. package/out/StateLibrary.sol/StateLibrary.json +1 -1
  97. package/out/SwapLimitOrders.sol/SwapLimitOrders.json +1 -1
  98. package/out/SwapWithLimitOrders.sol/SwapWithLimitOrders.json +1 -1
  99. package/out/TickBitmap.sol/TickBitmap.json +1 -1
  100. package/out/TickMath.sol/TickMath.json +1 -1
  101. package/out/TransientSlot.sol/TransientSlot.json +1 -1
  102. package/out/TransientStateLibrary.sol/TransientStateLibrary.json +1 -1
  103. package/out/UniV4SwapToCurrency.sol/UniV4SwapToCurrency.json +1 -1
  104. package/out/UnsafeMath.sol/UnsafeMath.json +1 -1
  105. package/out/V3ToV4SwapLib.sol/V3ToV4SwapLib.json +1 -1
  106. package/out/ZoraLimitOrderBook.sol/ZoraLimitOrderBook.json +1 -1
  107. package/out/build-info/37e0124d88d60569.json +1 -0
  108. package/out/uniswap/BitMath.sol/BitMath.json +1 -1
  109. package/out/uniswap/CustomRevert.sol/CustomRevert.json +1 -1
  110. package/out/uniswap/FullMath.sol/FullMath.json +1 -1
  111. package/out/uniswap/SafeCast.sol/SafeCast.json +1 -1
  112. package/out/uniswap/TickMath.sol/TickMath.json +1 -1
  113. package/package/wagmiGenerated.ts +218 -33
  114. package/package.json +1 -1
  115. package/src/IZoraLimitOrderBook.sol +5 -5
  116. package/src/ZoraLimitOrderBook.sol +24 -41
  117. package/src/access/PermittedCallers.sol +41 -0
  118. package/src/libs/LimitOrderBitmap.sol +0 -51
  119. package/src/libs/LimitOrderCommon.sol +48 -30
  120. package/src/libs/LimitOrderCreate.sol +5 -18
  121. package/src/libs/LimitOrderFill.sol +32 -161
  122. package/src/libs/LimitOrderLiquidity.sol +92 -71
  123. package/src/libs/LimitOrderViews.sol +168 -0
  124. package/src/libs/LimitOrderWithdraw.sol +13 -4
  125. package/src/libs/SwapLimitOrders.sol +14 -7
  126. package/src/router/ISetLimitOrderConfig.sol +12 -0
  127. package/src/router/SwapWithLimitOrders.sol +46 -33
  128. package/test/LimitOrderAccessControl.t.sol +173 -156
  129. package/test/LimitOrderBitmap.t.sol +13 -7
  130. package/test/LimitOrderFill.t.sol +42 -4
  131. package/test/LimitOrderLibraries.t.sol +18 -10
  132. package/test/LimitOrderLiquidityPayouts.t.sol +280 -3
  133. package/test/LimitOrderWithdraw.t.sol +28 -1
  134. package/test/SwapWithLimitOrders.t.sol +3 -5
  135. package/test/SwapWithLimitOrdersRouter.t.sol +108 -13
  136. package/test/gas/LimitOrderFillGas.t.sol +0 -7
  137. package/test/gas/LimitOrderSwapGas.t.sol +0 -6
  138. package/test/unit/LimitOrderBitmapUnit.t.sol +0 -134
  139. package/test/unit/LimitOrderCreateUnit.t.sol +32 -0
  140. package/test/unit/SwapLimitOrdersUnit.t.sol +231 -33
  141. package/test/unit/SwapLimitOrdersValidation.t.sol +28 -42
  142. package/test/unit/SwapWithLimitOrdersUnit.t.sol +21 -88
  143. package/test/utils/BaseTest.sol +34 -22
  144. package/test/utils/MockWETH.sol +39 -0
  145. package/test/utils/TestableZoraLimitOrderBook.sol +5 -7
  146. package/abis/IAuthority.json +0 -31
  147. package/abis/SimpleAccessManager.json +0 -351
  148. package/out/IAuthority.sol/IAuthority.json +0 -1
  149. package/out/SimpleAccessManaged.sol/SimpleAccessManaged.json +0 -1
  150. package/out/SimpleAccessManager.sol/SimpleAccessManager.json +0 -1
  151. package/out/build-info/69718f10d1dc37f0.json +0 -1
  152. package/src/access/SimpleAccessManaged.sol +0 -76
  153. package/src/access/SimpleAccessManager.sol +0 -268
  154. 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 test_limitOrderPlacement_respectsMinSize() public {
241
- // Test that small purchases below MIN_LIMIT_ORDER_SIZE don't create orders
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 MIN_LIMIT_ORDER_SIZE threshold
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
- // Verify fill direction is inverted (!isCoinCurrency0)
311
- // Check correct orders are targeted for filling
312
- // TODO: Requires verifying fill direction logic
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, int256 delta, , , CreatedOrder[] memory orders) = abi.decode(
650
+ (PoolKey memory loggedPoolKey, , , int128 amount0, int128 amount1, uint160 sqrtPriceX96, CreatedOrder[] memory orders) = abi.decode(
590
651
  log.data,
591
- (PoolKey, int256, int24, int24, CreatedOrder[])
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
- assertEq(delta, 0, "returned delta should be zero");
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 emitted with correct params
649
- // Check sender, recipient, poolKey, deltas, ticks, orderIds, ordersFilled
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
  }