@zoralabs/limit-orders 0.2.0

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 (208) hide show
  1. package/.turbo/turbo-build$colon$js.log +85 -0
  2. package/AUDIT_NOTES.md +33 -0
  3. package/AUDIT_RFP.md +408 -0
  4. package/CHANGELOG.md +25 -0
  5. package/GAS_COMPARISON_RESULTS.md +194 -0
  6. package/LICENSE +21 -0
  7. package/README.md +650 -0
  8. package/SPEC.md +291 -0
  9. package/abis/BalanceDeltaLibrary.json +15 -0
  10. package/abis/BeforeSwapDeltaLibrary.json +15 -0
  11. package/abis/CurrencyLibrary.json +25 -0
  12. package/abis/CustomRevert.json +28 -0
  13. package/abis/IAllowanceTransfer.json +486 -0
  14. package/abis/IAuthority.json +31 -0
  15. package/abis/ICoin.json +1074 -0
  16. package/abis/IDeployedCoinVersionLookup.json +21 -0
  17. package/abis/IDopplerErrors.json +44 -0
  18. package/abis/IEIP712.json +15 -0
  19. package/abis/IERC1363.json +373 -0
  20. package/abis/IERC165.json +21 -0
  21. package/abis/IERC20.json +185 -0
  22. package/abis/IERC20Minimal.json +172 -0
  23. package/abis/IERC6909Claims.json +288 -0
  24. package/abis/IERC7572.json +21 -0
  25. package/abis/IExtsload.json +64 -0
  26. package/abis/IExttload.json +40 -0
  27. package/abis/IHasCoinType.json +15 -0
  28. package/abis/IHasPoolKey.json +42 -0
  29. package/abis/IHasRewardsRecipients.json +54 -0
  30. package/abis/IHasSwapPath.json +60 -0
  31. package/abis/IHasTotalSupplyForPositions.json +15 -0
  32. package/abis/IHooks.json +789 -0
  33. package/abis/IMsgSender.json +15 -0
  34. package/abis/IPoolManager.json +1286 -0
  35. package/abis/IProtocolFees.json +174 -0
  36. package/abis/ISupportsLimitOrderFill.json +15 -0
  37. package/abis/ISwapPathRouter.json +92 -0
  38. package/abis/ISwapRouter.json +219 -0
  39. package/abis/IUniswapV3SwapCallback.json +25 -0
  40. package/abis/IUpgradeableDestinationV4Hook.json +84 -0
  41. package/abis/IUpgradeableDestinationV4HookWithUpdateableFee.json +95 -0
  42. package/abis/IUpgradeableV4Hook.json +112 -0
  43. package/abis/IZoraHookRegistry.json +188 -0
  44. package/abis/IZoraLimitOrderBook.json +623 -0
  45. package/abis/IZoraLimitOrderBookCoinsInterface.json +67 -0
  46. package/abis/IZoraV4CoinHook.json +610 -0
  47. package/abis/Permit2Payments.json +7 -0
  48. package/abis/Position.json +7 -0
  49. package/abis/SafeCast.json +7 -0
  50. package/abis/SafeCast160.json +7 -0
  51. package/abis/SafeERC20.json +34 -0
  52. package/abis/SimpleAccessManaged.json +57 -0
  53. package/abis/SimpleAccessManager.json +351 -0
  54. package/abis/SqrtPriceMath.json +22 -0
  55. package/abis/StateLibrary.json +80 -0
  56. package/abis/SwapLimitOrders.json +22 -0
  57. package/abis/SwapWithLimitOrders.json +457 -0
  58. package/abis/TickBitmap.json +18 -0
  59. package/abis/TickMath.json +24 -0
  60. package/abis/V3ToV4SwapLib.json +28 -0
  61. package/abis/ZoraLimitOrderBook.json +771 -0
  62. package/cache/solidity-files-cache.json +1 -0
  63. package/dist/index.cjs +760 -0
  64. package/dist/index.cjs.map +1 -0
  65. package/dist/index.d.ts +2 -0
  66. package/dist/index.d.ts.map +1 -0
  67. package/dist/index.js +731 -0
  68. package/dist/index.js.map +1 -0
  69. package/dist/wagmiGenerated.d.ts +1012 -0
  70. package/dist/wagmiGenerated.d.ts.map +1 -0
  71. package/foundry.toml +29 -0
  72. package/gas_comparison.py +49 -0
  73. package/out/BalanceDelta.sol/BalanceDeltaLibrary.json +1 -0
  74. package/out/BeforeSwapDelta.sol/BeforeSwapDeltaLibrary.json +1 -0
  75. package/out/BitMath.sol/BitMath.json +1 -0
  76. package/out/BytesLib.sol/BytesLib.json +1 -0
  77. package/out/CoinCommon.sol/CoinCommon.json +1 -0
  78. package/out/CoinConfigurationVersions.sol/CoinConfigurationVersions.json +1 -0
  79. package/out/CoinConstants.sol/CoinConstants.json +1 -0
  80. package/out/Context.sol/Context.json +1 -0
  81. package/out/Currency.sol/CurrencyLibrary.json +1 -0
  82. package/out/CurrencyReserves.sol/CurrencyReserves.json +1 -0
  83. package/out/CustomRevert.sol/CustomRevert.json +1 -0
  84. package/out/DopplerMath.sol/DopplerMath.json +1 -0
  85. package/out/FixedPoint128.sol/FixedPoint128.json +1 -0
  86. package/out/FixedPoint96.sol/FixedPoint96.json +1 -0
  87. package/out/FullMath.sol/FullMath.json +1 -0
  88. package/out/IAllowanceTransfer.sol/IAllowanceTransfer.json +1 -0
  89. package/out/IAuthority.sol/IAuthority.json +1 -0
  90. package/out/ICoin.sol/ICoin.json +1 -0
  91. package/out/ICoin.sol/IHasCoinType.json +1 -0
  92. package/out/ICoin.sol/IHasPoolKey.json +1 -0
  93. package/out/ICoin.sol/IHasSwapPath.json +1 -0
  94. package/out/ICoin.sol/IHasTotalSupplyForPositions.json +1 -0
  95. package/out/IDeployedCoinVersionLookup.sol/IDeployedCoinVersionLookup.json +1 -0
  96. package/out/IDopplerErrors.sol/IDopplerErrors.json +1 -0
  97. package/out/IEIP712.sol/IEIP712.json +1 -0
  98. package/out/IERC1363.sol/IERC1363.json +1 -0
  99. package/out/IERC165.sol/IERC165.json +1 -0
  100. package/out/IERC20.sol/IERC20.json +1 -0
  101. package/out/IERC20Minimal.sol/IERC20Minimal.json +1 -0
  102. package/out/IERC6909Claims.sol/IERC6909Claims.json +1 -0
  103. package/out/IERC7572.sol/IERC7572.json +1 -0
  104. package/out/IExtsload.sol/IExtsload.json +1 -0
  105. package/out/IExttload.sol/IExttload.json +1 -0
  106. package/out/IHasRewardsRecipients.sol/IHasRewardsRecipients.json +1 -0
  107. package/out/IHooks.sol/IHooks.json +1 -0
  108. package/out/IMsgSender.sol/IMsgSender.json +1 -0
  109. package/out/IPoolManager.sol/IPoolManager.json +1 -0
  110. package/out/IProtocolFees.sol/IProtocolFees.json +1 -0
  111. package/out/ISupportsLimitOrderFill.sol/ISupportsLimitOrderFill.json +1 -0
  112. package/out/ISwapPathRouter.sol/ISwapPathRouter.json +1 -0
  113. package/out/ISwapRouter.sol/ISwapRouter.json +1 -0
  114. package/out/IUniswapV3SwapCallback.sol/IUniswapV3SwapCallback.json +1 -0
  115. package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4Hook.json +1 -0
  116. package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4HookWithUpdateableFee.json +1 -0
  117. package/out/IUpgradeableV4Hook.sol/IUpgradeableV4Hook.json +1 -0
  118. package/out/IZoraHookRegistry.sol/IZoraHookRegistry.json +1 -0
  119. package/out/IZoraLimitOrderBook.sol/IZoraLimitOrderBook.json +1 -0
  120. package/out/IZoraLimitOrderBookCoinsInterface.sol/IZoraLimitOrderBookCoinsInterface.json +1 -0
  121. package/out/IZoraV4CoinHook.sol/IZoraV4CoinHook.json +1 -0
  122. package/out/LimitOrderBitmap.sol/LimitOrderBitmap.json +1 -0
  123. package/out/LimitOrderCommon.sol/LimitOrderCommon.json +1 -0
  124. package/out/LimitOrderCreate.sol/LimitOrderCreate.json +1 -0
  125. package/out/LimitOrderFill.sol/LimitOrderFill.json +1 -0
  126. package/out/LimitOrderLiquidity.sol/LimitOrderLiquidity.json +1 -0
  127. package/out/LimitOrderQueues.sol/LimitOrderQueues.json +1 -0
  128. package/out/LimitOrderStorage.sol/LimitOrderStorage.json +1 -0
  129. package/out/LimitOrderTypes.sol/LimitOrderTypes.json +1 -0
  130. package/out/LimitOrderWithdraw.sol/LimitOrderWithdraw.json +1 -0
  131. package/out/LiquidityAmounts.sol/LiquidityAmounts.json +1 -0
  132. package/out/LiquidityMath.sol/LiquidityMath.json +1 -0
  133. package/out/Lock.sol/Lock.json +1 -0
  134. package/out/NonzeroDeltaCount.sol/NonzeroDeltaCount.json +1 -0
  135. package/out/Path.sol/Path.json +1 -0
  136. package/out/PathKey.sol/PathKeyLibrary.json +1 -0
  137. package/out/Permit2Payments.sol/Permit2Payments.json +1 -0
  138. package/out/PoolId.sol/PoolIdLibrary.json +1 -0
  139. package/out/Position.sol/Position.json +1 -0
  140. package/out/SafeCast.sol/SafeCast.json +1 -0
  141. package/out/SafeCast160.sol/SafeCast160.json +1 -0
  142. package/out/SafeERC20.sol/SafeERC20.json +1 -0
  143. package/out/SimpleAccessManaged.sol/SimpleAccessManaged.json +1 -0
  144. package/out/SimpleAccessManager.sol/SimpleAccessManager.json +1 -0
  145. package/out/SqrtPriceMath.sol/SqrtPriceMath.json +1 -0
  146. package/out/StateLibrary.sol/StateLibrary.json +1 -0
  147. package/out/SwapLimitOrders.sol/SwapLimitOrders.json +1 -0
  148. package/out/SwapWithLimitOrders.sol/SwapWithLimitOrders.json +1 -0
  149. package/out/TickBitmap.sol/TickBitmap.json +1 -0
  150. package/out/TickMath.sol/TickMath.json +1 -0
  151. package/out/TransientSlot.sol/TransientSlot.json +1 -0
  152. package/out/TransientStateLibrary.sol/TransientStateLibrary.json +1 -0
  153. package/out/UniV4SwapToCurrency.sol/UniV4SwapToCurrency.json +1 -0
  154. package/out/UnsafeMath.sol/UnsafeMath.json +1 -0
  155. package/out/V3ToV4SwapLib.sol/V3ToV4SwapLib.json +1 -0
  156. package/out/ZoraLimitOrderBook.sol/ZoraLimitOrderBook.json +1 -0
  157. package/out/build-info/69718f10d1dc37f0.json +1 -0
  158. package/out/uniswap/BitMath.sol/BitMath.json +1 -0
  159. package/out/uniswap/CustomRevert.sol/CustomRevert.json +1 -0
  160. package/out/uniswap/FullMath.sol/FullMath.json +1 -0
  161. package/out/uniswap/SafeCast.sol/SafeCast.json +1 -0
  162. package/out/uniswap/TickMath.sol/TickMath.json +1 -0
  163. package/package/index.ts +1 -0
  164. package/package/wagmiGenerated.ts +738 -0
  165. package/package.json +57 -0
  166. package/remappings.txt +11 -0
  167. package/src/IZoraLimitOrderBook.sol +195 -0
  168. package/src/ZoraLimitOrderBook.sol +220 -0
  169. package/src/access/SimpleAccessManaged.sol +76 -0
  170. package/src/access/SimpleAccessManager.sol +268 -0
  171. package/src/libs/LimitOrderBitmap.sol +84 -0
  172. package/src/libs/LimitOrderCommon.sol +91 -0
  173. package/src/libs/LimitOrderCreate.sol +277 -0
  174. package/src/libs/LimitOrderFill.sol +362 -0
  175. package/src/libs/LimitOrderLiquidity.sol +222 -0
  176. package/src/libs/LimitOrderQueues.sol +101 -0
  177. package/src/libs/LimitOrderStorage.sol +34 -0
  178. package/src/libs/LimitOrderTypes.sol +41 -0
  179. package/src/libs/LimitOrderWithdraw.sol +100 -0
  180. package/src/libs/Permit2Payments.sol +41 -0
  181. package/src/libs/SwapLimitOrders.sol +209 -0
  182. package/src/router/SwapWithLimitOrders.sol +454 -0
  183. package/test/LimitOrderAccessControl.t.sol +461 -0
  184. package/test/LimitOrderBitmap.t.sol +194 -0
  185. package/test/LimitOrderCreate.t.sol +348 -0
  186. package/test/LimitOrderFill.t.sol +1005 -0
  187. package/test/LimitOrderLibraries.t.sol +354 -0
  188. package/test/LimitOrderLiquidityPayouts.t.sol +333 -0
  189. package/test/LimitOrderV4Pools.t.sol +157 -0
  190. package/test/LimitOrderWithdraw.t.sol +653 -0
  191. package/test/SimpleAccessManager.t.sol +420 -0
  192. package/test/SwapWithLimitOrders.t.sol +107 -0
  193. package/test/SwapWithLimitOrdersRouter.t.sol +1073 -0
  194. package/test/gas/LimitOrderFillGas.t.sol +1008 -0
  195. package/test/gas/LimitOrderSwapGas.t.sol +403 -0
  196. package/test/gas/logs/gas_benchmarks_fill_20251201.log +30 -0
  197. package/test/gas/logs/gas_benchmarks_swap_20251201.log +27 -0
  198. package/test/unit/LimitOrderBitmapUnit.t.sol +276 -0
  199. package/test/unit/LimitOrderCreateUnit.t.sol +358 -0
  200. package/test/unit/SwapLimitOrdersUnit.t.sol +672 -0
  201. package/test/unit/SwapLimitOrdersValidation.t.sol +423 -0
  202. package/test/unit/SwapWithLimitOrdersUnit.t.sol +321 -0
  203. package/test/utils/BaseTest.sol +793 -0
  204. package/test/utils/TestableZoraLimitOrderBook.sol +54 -0
  205. package/tsconfig.build.json +10 -0
  206. package/tsconfig.json +9 -0
  207. package/tsup.config.ts +11 -0
  208. package/wagmi.config.ts +18 -0
@@ -0,0 +1,321 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+
6
+ /// @notice Wrapper contract to expose testable logic from SwapWithLimitOrders
7
+ /// @dev We extract and test the core branching logic without requiring full contract integration
8
+ contract SwapWithLimitOrdersLogicWrapper {
9
+ /// @notice Tests _fillOrders maxFillCount == 0 branch (line 394)
10
+ function shouldFill_maxFillCount(uint256 maxFillCount) external pure returns (bool) {
11
+ if (maxFillCount == 0) {
12
+ return false; // Early return, don't fill
13
+ }
14
+ return true; // Would proceed to fill
15
+ }
16
+
17
+ /// @notice Tests tick ordering logic for currency0 (lines 403-406)
18
+ function orderTicks_currency0(int24 tickBeforeSwap, int24 tickAfterSwap) external pure returns (int24 startTick, int24 endTick) {
19
+ bool isCurrency0 = true;
20
+ if (isCurrency0) {
21
+ // Currency0 orders need ascending tick range
22
+ startTick = tickBeforeSwap < tickAfterSwap ? tickBeforeSwap : tickAfterSwap;
23
+ endTick = tickBeforeSwap < tickAfterSwap ? tickAfterSwap : tickBeforeSwap;
24
+ }
25
+ }
26
+
27
+ /// @notice Tests tick ordering logic for currency1 (lines 407-411)
28
+ function orderTicks_currency1(int24 tickBeforeSwap, int24 tickAfterSwap) external pure returns (int24 startTick, int24 endTick) {
29
+ bool isCurrency0 = false;
30
+ if (!isCurrency0) {
31
+ // Currency1 orders need descending tick range
32
+ startTick = tickBeforeSwap > tickAfterSwap ? tickBeforeSwap : tickAfterSwap;
33
+ endTick = tickBeforeSwap > tickAfterSwap ? tickAfterSwap : tickBeforeSwap;
34
+ }
35
+ }
36
+
37
+ /// @notice Tests settlement unallocated branch (line 356)
38
+ function shouldTakeUnallocated(uint128 unallocated) external pure returns (bool) {
39
+ if (unallocated > 0) {
40
+ return true; // Would call poolManager.take()
41
+ }
42
+ return false; // Skip take
43
+ }
44
+
45
+ /// @notice Tests currency type detection for settlement (line 365)
46
+ function isERC20Settlement(address token) external pure returns (bool) {
47
+ if (token != address(0)) {
48
+ return true; // ERC20 path (lines 366-373)
49
+ } else {
50
+ return false; // ETH path (line 375)
51
+ }
52
+ }
53
+
54
+ /// @notice Tests hook fill support check logic (line 198)
55
+ function shouldRouterFill(bool hookSupportsFill, uint256 orderIdsLength, int24 tickBeforeSwap, int24 currentTick) external pure returns (bool) {
56
+ if (!hookSupportsFill && orderIdsLength > 0 && tickBeforeSwap != currentTick) {
57
+ return true; // Router fills
58
+ } else {
59
+ return false; // Hook handles or no fill needed
60
+ }
61
+ }
62
+
63
+ /// @notice Tests V4 route validation (line 150)
64
+ function isValidV4Route(uint256 routeLength) external pure returns (bool) {
65
+ if (routeLength > 0) {
66
+ return true;
67
+ }
68
+ return false; // Would revert with EmptyV4Route
69
+ }
70
+
71
+ /// @notice Tests constructor validation branches (lines 120-122)
72
+ function validateConstructorParams(address poolManager, address orderBook, address router) external pure returns (string memory) {
73
+ if (poolManager == address(0)) {
74
+ return "PoolManager cannot be zero";
75
+ }
76
+ if (orderBook == address(0)) {
77
+ return "ZoraLimitOrderBook cannot be zero";
78
+ }
79
+ if (router == address(0)) {
80
+ return "SwapRouter cannot be zero";
81
+ }
82
+ return "Valid";
83
+ }
84
+ }
85
+
86
+ /// @notice Direct unit tests for SwapWithLimitOrders router logic
87
+ contract SwapWithLimitOrdersUnitTest is Test {
88
+ SwapWithLimitOrdersLogicWrapper internal wrapper;
89
+
90
+ function setUp() public {
91
+ wrapper = new SwapWithLimitOrdersLogicWrapper();
92
+ }
93
+
94
+ /// @notice Tests that maxFillCount == 0 returns early
95
+ function test_shouldFill_maxFillCountZero_returnsFalse() public view {
96
+ bool shouldFill = wrapper.shouldFill_maxFillCount(0);
97
+ assertFalse(shouldFill, "should not fill when maxFillCount is 0");
98
+ }
99
+
100
+ /// @notice Tests that maxFillCount > 0 proceeds to fill
101
+ function test_shouldFill_maxFillCountNonZero_returnsTrue() public view {
102
+ bool shouldFill = wrapper.shouldFill_maxFillCount(10);
103
+ assertTrue(shouldFill, "should fill when maxFillCount > 0");
104
+ }
105
+
106
+ /// @notice Tests with maxFillCount == 1 (boundary)
107
+ function test_shouldFill_maxFillCountOne_returnsTrue() public view {
108
+ bool shouldFill = wrapper.shouldFill_maxFillCount(1);
109
+ assertTrue(shouldFill, "should fill when maxFillCount is 1");
110
+ }
111
+
112
+ /// @notice Tests currency0 tick ordering when tickBefore < tickAfter (ascending)
113
+ function test_orderTicks_currency0_ascending() public view {
114
+ int24 tickBefore = 1000;
115
+ int24 tickAfter = 2000;
116
+
117
+ (int24 startTick, int24 endTick) = wrapper.orderTicks_currency0(tickBefore, tickAfter);
118
+
119
+ assertEq(startTick, 1000, "startTick should be tickBefore");
120
+ assertEq(endTick, 2000, "endTick should be tickAfter");
121
+ }
122
+
123
+ /// @notice Tests currency0 tick ordering when tickBefore > tickAfter (needs swap)
124
+ function test_orderTicks_currency0_descending_swaps() public view {
125
+ int24 tickBefore = 2000;
126
+ int24 tickAfter = 1000;
127
+
128
+ (int24 startTick, int24 endTick) = wrapper.orderTicks_currency0(tickBefore, tickAfter);
129
+
130
+ assertEq(startTick, 1000, "startTick should be minimum");
131
+ assertEq(endTick, 2000, "endTick should be maximum");
132
+ }
133
+
134
+ /// @notice Tests currency0 tick ordering when ticks are equal
135
+ function test_orderTicks_currency0_equal() public view {
136
+ int24 tickBefore = 1500;
137
+ int24 tickAfter = 1500;
138
+
139
+ (int24 startTick, int24 endTick) = wrapper.orderTicks_currency0(tickBefore, tickAfter);
140
+
141
+ assertEq(startTick, 1500, "startTick should equal tickBefore");
142
+ assertEq(endTick, 1500, "endTick should equal tickBefore");
143
+ }
144
+
145
+ /// @notice Tests currency1 tick ordering when tickBefore > tickAfter (descending)
146
+ function test_orderTicks_currency1_descending() public view {
147
+ int24 tickBefore = 2000;
148
+ int24 tickAfter = 1000;
149
+
150
+ (int24 startTick, int24 endTick) = wrapper.orderTicks_currency1(tickBefore, tickAfter);
151
+
152
+ assertEq(startTick, 2000, "startTick should be tickBefore");
153
+ assertEq(endTick, 1000, "endTick should be tickAfter");
154
+ }
155
+
156
+ /// @notice Tests currency1 tick ordering when tickBefore < tickAfter (needs swap)
157
+ function test_orderTicks_currency1_ascending_swaps() public view {
158
+ int24 tickBefore = 1000;
159
+ int24 tickAfter = 2000;
160
+
161
+ (int24 startTick, int24 endTick) = wrapper.orderTicks_currency1(tickBefore, tickAfter);
162
+
163
+ assertEq(startTick, 2000, "startTick should be maximum");
164
+ assertEq(endTick, 1000, "endTick should be minimum");
165
+ }
166
+
167
+ /// @notice Tests currency1 tick ordering with negative ticks
168
+ function test_orderTicks_currency1_negativeTicks() public view {
169
+ int24 tickBefore = -1000;
170
+ int24 tickAfter = -2000;
171
+
172
+ (int24 startTick, int24 endTick) = wrapper.orderTicks_currency1(tickBefore, tickAfter);
173
+
174
+ assertEq(startTick, -1000, "startTick should be -1000");
175
+ assertEq(endTick, -2000, "endTick should be -2000");
176
+ }
177
+
178
+ /// @notice Tests that unallocated > 0 triggers take
179
+ function test_shouldTakeUnallocated_nonZero_returnsTrue() public view {
180
+ bool shouldTake = wrapper.shouldTakeUnallocated(100);
181
+ assertTrue(shouldTake, "should take when unallocated > 0");
182
+ }
183
+
184
+ /// @notice Tests that unallocated == 0 skips take
185
+ function test_shouldTakeUnallocated_zero_returnsFalse() public view {
186
+ bool shouldTake = wrapper.shouldTakeUnallocated(0);
187
+ assertFalse(shouldTake, "should not take when unallocated is 0");
188
+ }
189
+
190
+ /// @notice Tests with boundary value (1)
191
+ function test_shouldTakeUnallocated_one_returnsTrue() public view {
192
+ bool shouldTake = wrapper.shouldTakeUnallocated(1);
193
+ assertTrue(shouldTake, "should take when unallocated is 1");
194
+ }
195
+
196
+ /// @notice Tests ERC20 settlement path (token != address(0))
197
+ function test_isERC20Settlement_nonZeroAddress_returnsTrue() public view {
198
+ bool isERC20 = wrapper.isERC20Settlement(address(0x1234));
199
+ assertTrue(isERC20, "should be ERC20 path for non-zero address");
200
+ }
201
+
202
+ /// @notice Tests ETH settlement path (token == address(0))
203
+ function test_isERC20Settlement_zeroAddress_returnsFalse() public view {
204
+ bool isERC20 = wrapper.isERC20Settlement(address(0));
205
+ assertFalse(isERC20, "should be ETH path for zero address");
206
+ }
207
+
208
+ /// @notice Tests router fill when hook doesn't support fill
209
+ function test_shouldRouterFill_hookDoesNotSupport_returnsTrue() public view {
210
+ bool shouldFill = wrapper.shouldRouterFill(
211
+ false, // hookSupportsFill
212
+ 2, // orderIdsLength > 0
213
+ 1000, // tickBeforeSwap
214
+ 1500 // currentTick (different)
215
+ );
216
+ assertTrue(shouldFill, "router should fill when hook doesn't support");
217
+ }
218
+
219
+ /// @notice Tests no router fill when hook supports fill
220
+ function test_shouldRouterFill_hookSupports_returnsFalse() public view {
221
+ bool shouldFill = wrapper.shouldRouterFill(
222
+ true, // hookSupportsFill
223
+ 2, // orderIdsLength > 0
224
+ 1000, // tickBeforeSwap
225
+ 1500 // currentTick (different)
226
+ );
227
+ assertFalse(shouldFill, "router should not fill when hook supports");
228
+ }
229
+
230
+ /// @notice Tests no router fill when no orders created
231
+ function test_shouldRouterFill_noOrders_returnsFalse() public view {
232
+ bool shouldFill = wrapper.shouldRouterFill(
233
+ false, // hookSupportsFill
234
+ 0, // orderIdsLength == 0 (no orders)
235
+ 1000, // tickBeforeSwap
236
+ 1500 // currentTick (different)
237
+ );
238
+ assertFalse(shouldFill, "router should not fill when no orders");
239
+ }
240
+
241
+ /// @notice Tests no router fill when tick hasn't moved
242
+ function test_shouldRouterFill_tickNotMoved_returnsFalse() public view {
243
+ bool shouldFill = wrapper.shouldRouterFill(
244
+ false, // hookSupportsFill
245
+ 2, // orderIdsLength > 0
246
+ 1000, // tickBeforeSwap
247
+ 1000 // currentTick (same - no movement)
248
+ );
249
+ assertFalse(shouldFill, "router should not fill when tick hasn't moved");
250
+ }
251
+
252
+ /// @notice Tests all conditions must be met for router fill
253
+ function test_shouldRouterFill_allConditionsMet_returnsTrue() public view {
254
+ bool shouldFill = wrapper.shouldRouterFill(
255
+ false, // !hookSupportsFill
256
+ 5, // orderIdsLength > 0
257
+ 1000, // tickBeforeSwap
258
+ 2000 // currentTick != tickBeforeSwap
259
+ );
260
+ assertTrue(shouldFill, "router should fill when all conditions met");
261
+ }
262
+
263
+ /// @notice Tests valid V4 route with length > 0
264
+ function test_isValidV4Route_nonEmpty_returnsTrue() public view {
265
+ bool isValid = wrapper.isValidV4Route(1);
266
+ assertTrue(isValid, "should be valid for non-empty route");
267
+ }
268
+
269
+ /// @notice Tests invalid V4 route with length == 0
270
+ function test_isValidV4Route_empty_returnsFalse() public view {
271
+ bool isValid = wrapper.isValidV4Route(0);
272
+ assertFalse(isValid, "should be invalid for empty route");
273
+ }
274
+
275
+ /// @notice Tests V4 route with multiple pools
276
+ function test_isValidV4Route_multiplePools_returnsTrue() public view {
277
+ bool isValid = wrapper.isValidV4Route(3);
278
+ assertTrue(isValid, "should be valid for multi-hop route");
279
+ }
280
+
281
+ /// @notice Tests constructor with zero poolManager
282
+ function test_validateConstructorParams_zeroPoolManager() public view {
283
+ string memory result = wrapper.validateConstructorParams(address(0), address(0x1), address(0x2));
284
+ assertEq(result, "PoolManager cannot be zero");
285
+ }
286
+
287
+ /// @notice Tests constructor with zero orderBook
288
+ function test_validateConstructorParams_zeroOrderBook() public view {
289
+ string memory result = wrapper.validateConstructorParams(address(0x1), address(0), address(0x2));
290
+ assertEq(result, "ZoraLimitOrderBook cannot be zero");
291
+ }
292
+
293
+ /// @notice Tests constructor with zero router
294
+ function test_validateConstructorParams_zeroRouter() public view {
295
+ string memory result = wrapper.validateConstructorParams(address(0x1), address(0x2), address(0));
296
+ assertEq(result, "SwapRouter cannot be zero");
297
+ }
298
+
299
+ /// @notice Tests constructor with all valid params
300
+ function test_validateConstructorParams_allValid() public view {
301
+ string memory result = wrapper.validateConstructorParams(address(0x1), address(0x2), address(0x3));
302
+ assertEq(result, "Valid");
303
+ }
304
+
305
+ /// @notice Tests tick ordering with max int24 values
306
+ function test_orderTicks_maxValues() public view {
307
+ int24 maxTick = type(int24).max;
308
+ int24 minTick = type(int24).min;
309
+
310
+ (int24 startTick, int24 endTick) = wrapper.orderTicks_currency0(maxTick, minTick);
311
+
312
+ assertEq(startTick, minTick, "should handle max/min correctly");
313
+ assertEq(endTick, maxTick, "should handle max/min correctly");
314
+ }
315
+
316
+ /// @notice Tests unallocated with max uint128 value
317
+ function test_shouldTakeUnallocated_maxValue() public view {
318
+ bool shouldTake = wrapper.shouldTakeUnallocated(type(uint128).max);
319
+ assertTrue(shouldTake, "should take with max value");
320
+ }
321
+ }