@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,423 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import {Test} from "forge-std/Test.sol";
5
+ import {SwapLimitOrders, LimitOrderConfig, Orders} from "../../src/libs/SwapLimitOrders.sol";
6
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
7
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
8
+ import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
9
+ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
10
+ import {SwapLimitOrdersWrapper} from "./SwapLimitOrdersUnit.t.sol";
11
+
12
+ contract SwapLimitOrdersValidationTest is Test {
13
+ using SwapLimitOrders for *;
14
+
15
+ PoolKey internal testKey;
16
+ int24 constant TICK_SPACING = 10;
17
+ SwapLimitOrdersWrapper internal wrapper;
18
+
19
+ function setUp() public {
20
+ // Create a minimal valid PoolKey for testing
21
+ testKey = PoolKey({
22
+ currency0: Currency.wrap(address(0x1)),
23
+ currency1: Currency.wrap(address(0x2)),
24
+ fee: 3000,
25
+ tickSpacing: TICK_SPACING,
26
+ hooks: IHooks(address(0))
27
+ });
28
+
29
+ // Deploy wrapper for testing library reverts
30
+ wrapper = new SwapLimitOrdersWrapper();
31
+ }
32
+
33
+ function test_isLimitOrder_NotCoinBuy() public {
34
+ LimitOrderConfig memory params = _createValidParams(makeAddr("maker"), 1);
35
+
36
+ bool result = wrapper.isLimitOrder(
37
+ false, // not a coin buy
38
+ makeAddr("testSwapper"),
39
+ int128(uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE)),
40
+ params
41
+ );
42
+
43
+ assertFalse(result, "should return false for non-buy swaps");
44
+ }
45
+
46
+ function test_isLimitOrder_NoOrders() public {
47
+ LimitOrderConfig memory params = _createEmptyParams(makeAddr("maker"));
48
+
49
+ bool result = wrapper.isLimitOrder(true, makeAddr("testSwapper"), int128(uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE)), params);
50
+
51
+ assertFalse(result, "should return false when no orders configured");
52
+ }
53
+
54
+ function test_isLimitOrder_ZeroSwapper() public {
55
+ LimitOrderConfig memory params = _createValidParams(makeAddr("maker"), 1);
56
+
57
+ bool result = wrapper.isLimitOrder(
58
+ true,
59
+ address(0), // zero swapper
60
+ int128(uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE)),
61
+ params
62
+ );
63
+
64
+ assertFalse(result, "should return false for zero swapper");
65
+ }
66
+
67
+ function test_isLimitOrder_NegativeCoinDelta() public {
68
+ LimitOrderConfig memory params = _createValidParams(makeAddr("maker"), 1);
69
+
70
+ bool result = wrapper.isLimitOrder(
71
+ true,
72
+ makeAddr("testSwapper"),
73
+ -1, // negative delta
74
+ params
75
+ );
76
+
77
+ assertFalse(result, "should return false for negative coinDelta");
78
+ }
79
+
80
+ function test_isLimitOrder_ZeroCoinDelta() public {
81
+ LimitOrderConfig memory params = _createValidParams(makeAddr("maker"), 1);
82
+
83
+ bool result = wrapper.isLimitOrder(
84
+ true,
85
+ makeAddr("testSwapper"),
86
+ 0, // zero delta
87
+ params
88
+ );
89
+
90
+ assertFalse(result, "should return false for zero coinDelta");
91
+ }
92
+
93
+ function test_isLimitOrder_BelowMinimumSize() public {
94
+ LimitOrderConfig memory params = _createValidParams(makeAddr("maker"), 1);
95
+
96
+ bool result = wrapper.isLimitOrder(
97
+ true,
98
+ makeAddr("testSwapper"),
99
+ int128(uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE - 1)), // dust amount
100
+ params
101
+ );
102
+
103
+ assertFalse(result, "should return false for dust amounts below minimum");
104
+ }
105
+
106
+ function test_isLimitOrder_ValidCase() public {
107
+ LimitOrderConfig memory params = _createValidParams(makeAddr("maker"), 1);
108
+
109
+ bool result = wrapper.isLimitOrder(true, makeAddr("testSwapper"), int128(uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE)), params);
110
+
111
+ assertTrue(result, "should return true when all conditions met");
112
+ }
113
+
114
+ function test_validate_LengthMismatch() public {
115
+ LimitOrderConfig memory params;
116
+ params.multiples = new uint256[](2);
117
+ params.percentages = new uint256[](1); // mismatched length
118
+
119
+ params.multiples[0] = 2e18;
120
+ params.multiples[1] = 4e18;
121
+ params.percentages[0] = 10000;
122
+
123
+ vm.expectRevert(SwapLimitOrders.LengthMismatch.selector);
124
+ wrapper.validate(params);
125
+ }
126
+
127
+ function test_validate_ZeroPercent() public {
128
+ LimitOrderConfig memory params;
129
+ params.multiples = new uint256[](2);
130
+ params.percentages = new uint256[](2);
131
+
132
+ params.multiples[0] = 2e18;
133
+ params.multiples[1] = 4e18;
134
+ params.percentages[0] = 5000;
135
+ params.percentages[1] = 0; // zero percent - invalid
136
+
137
+ vm.expectRevert(SwapLimitOrders.InvalidPercent.selector);
138
+ wrapper.validate(params);
139
+ }
140
+
141
+ function test_validate_PercentOverflow() public {
142
+ LimitOrderConfig memory params;
143
+ params.multiples = new uint256[](2);
144
+ params.percentages = new uint256[](2);
145
+
146
+ params.multiples[0] = 2e18;
147
+ params.multiples[1] = 4e18;
148
+ params.percentages[0] = 6000;
149
+ params.percentages[1] = 5000; // total = 11000 > 10000
150
+
151
+ vm.expectRevert(SwapLimitOrders.PercentOverflow.selector);
152
+ wrapper.validate(params);
153
+ }
154
+
155
+ function test_validate_InvalidMultiple() public {
156
+ LimitOrderConfig memory params;
157
+ params.multiples = new uint256[](2);
158
+ params.percentages = new uint256[](2);
159
+
160
+ params.multiples[0] = 2e18;
161
+ params.multiples[1] = 1e18; // 1.0x - not strictly greater
162
+ params.percentages[0] = 5000;
163
+ params.percentages[1] = 5000;
164
+
165
+ vm.expectRevert(SwapLimitOrders.InvalidMultiple.selector);
166
+ wrapper.validate(params);
167
+ }
168
+
169
+ function test_validate_UnderOneHundredPercent() public {
170
+ LimitOrderConfig memory params;
171
+ params.multiples = new uint256[](2);
172
+ params.percentages = new uint256[](2);
173
+
174
+ params.multiples[0] = 2e18;
175
+ params.multiples[1] = 4e18;
176
+ params.percentages[0] = 3000; // 30%
177
+ params.percentages[1] = 5000; // 50% - total 80%
178
+
179
+ uint256 totalPercent = SwapLimitOrders.validate(params);
180
+ assertEq(totalPercent, 8000, "should allow undershoot");
181
+ }
182
+
183
+ function test_computeOrders_BelowMinimum() public {
184
+ LimitOrderConfig memory params = _createValidParams(makeAddr("maker"), 2);
185
+ uint128 dustSize = uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE - 1);
186
+
187
+ (Orders memory orders, uint128 allocated, uint128 unallocated) = SwapLimitOrders.computeOrders(
188
+ testKey,
189
+ true, // isCurrency0
190
+ uint128(dustSize),
191
+ 0, // baseTick
192
+ TickMath.getSqrtPriceAtTick(0),
193
+ params
194
+ );
195
+
196
+ assertEq(orders.sizes.length, 0, "should return empty orders");
197
+ assertEq(orders.ticks.length, 0, "should return empty ticks");
198
+ assertEq(allocated, 0, "should have zero allocated");
199
+ assertEq(unallocated, uint128(dustSize), "all should be unallocated");
200
+ }
201
+
202
+ function test_computeOrders_SkipsZeroSizeOrders() public {
203
+ LimitOrderConfig memory params;
204
+ params.multiples = new uint256[](4);
205
+ params.percentages = new uint256[](4);
206
+
207
+ // Use a small total size so that tiny percentages round to zero
208
+ // With 10000 wei total: 1 bp = 1 wei, so we need very small percentages
209
+ params.multiples[0] = 2e18;
210
+ params.multiples[1] = 4e18;
211
+ params.multiples[2] = 8e18;
212
+ params.multiples[3] = 16e18;
213
+
214
+ // First two get most of allocation, last two get tiny amounts that might round to zero
215
+ params.percentages[0] = 4999; // ~50%
216
+ params.percentages[1] = 4999; // ~50%
217
+ params.percentages[2] = 1; // 0.01% - may round to zero with small size
218
+ params.percentages[3] = 1; // 0.01% - may round to zero with small size
219
+
220
+ // Use exactly MIN size which is 1e18
221
+ uint128 totalSize = uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE);
222
+
223
+ (Orders memory orders, uint128 allocated, uint128 unallocated) = SwapLimitOrders.computeOrders(
224
+ testKey,
225
+ true,
226
+ uint128(totalSize),
227
+ 0,
228
+ TickMath.getSqrtPriceAtTick(0),
229
+ params
230
+ );
231
+
232
+ // With MIN_LIMIT_ORDER_SIZE (1e18) and 1bp = 1e14, orders should not be skipped
233
+ // Let's just verify we get at least one order
234
+ assertGt(orders.sizes.length, 0, "should create at least one order");
235
+ assertEq(orders.sizes.length, orders.ticks.length, "sizes and ticks should match");
236
+ }
237
+
238
+ function test_computeOrders_ClampsToMaxTick() public {
239
+ LimitOrderConfig memory params;
240
+ params.multiples = new uint256[](1);
241
+ params.percentages = new uint256[](1);
242
+
243
+ // Extremely high multiple to exceed MAX_TICK
244
+ params.multiples[0] = 1000000e18; // 1,000,000x
245
+ params.percentages[0] = 10000;
246
+
247
+ int24 maxTick = TickMath.maxUsableTick(TICK_SPACING);
248
+
249
+ (Orders memory orders, , ) = SwapLimitOrders.computeOrders(
250
+ testKey,
251
+ true,
252
+ uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE),
253
+ 0,
254
+ TickMath.getSqrtPriceAtTick(0),
255
+ params
256
+ );
257
+
258
+ assertEq(orders.ticks.length, 1, "should create one order");
259
+ // Should clamp to max usable tick - the function applies clamping logic
260
+ // including minimum separation from base tick
261
+ assertLe(orders.ticks[0], maxTick, "should not exceed maxTick");
262
+ // The function enforces at least tickSpacing separation from base tick
263
+ // With such a high multiple, we expect to be at or near max tick
264
+ assertGt(orders.ticks[0], 0, "should be positive tick for buy orders");
265
+ }
266
+
267
+ function test_computeOrders_ClampsToMinTick() public {
268
+ LimitOrderConfig memory params;
269
+ params.multiples = new uint256[](1);
270
+ params.percentages = new uint256[](1);
271
+
272
+ // High multiple for token1 (inverted) to approach MIN_TICK
273
+ params.multiples[0] = 1000000e18;
274
+ params.percentages[0] = 10000;
275
+
276
+ int24 minTick = -TickMath.maxUsableTick(TICK_SPACING);
277
+ int24 startTick = TickMath.MAX_TICK - 1000;
278
+
279
+ (Orders memory orders, , ) = SwapLimitOrders.computeOrders(
280
+ testKey,
281
+ false, // isCurrency0 = false means selling, inverts multiple
282
+ uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE),
283
+ startTick,
284
+ TickMath.getSqrtPriceAtTick(startTick),
285
+ params
286
+ );
287
+
288
+ assertEq(orders.ticks.length, 1, "should create one order");
289
+ assertLe(orders.ticks[0], TickMath.maxUsableTick(TICK_SPACING), "should be within valid range");
290
+ }
291
+
292
+ function test_computeOrders_MinimumSeparationCurrency0() public {
293
+ LimitOrderConfig memory params;
294
+ params.multiples = new uint256[](1);
295
+ params.percentages = new uint256[](1);
296
+
297
+ // Very small multiple - barely above 1x
298
+ params.multiples[0] = 1.001e18; // 1.001x
299
+ params.percentages[0] = 10000;
300
+
301
+ int24 baseTick = 0;
302
+
303
+ (Orders memory orders, , ) = SwapLimitOrders.computeOrders(
304
+ testKey,
305
+ true, // isCurrency0
306
+ uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE),
307
+ baseTick,
308
+ TickMath.getSqrtPriceAtTick(baseTick),
309
+ params
310
+ );
311
+
312
+ assertEq(orders.ticks.length, 1, "should create one order");
313
+ // Should be at least tickSpacing away
314
+ assertGe(orders.ticks[0], baseTick + TICK_SPACING, "should maintain minimum separation");
315
+ }
316
+
317
+ function test_computeOrders_MinimumSeparationCurrency1() public {
318
+ LimitOrderConfig memory params;
319
+ params.multiples = new uint256[](1);
320
+ params.percentages = new uint256[](1);
321
+
322
+ // Very small multiple
323
+ params.multiples[0] = 1.001e18; // 1.001x
324
+ params.percentages[0] = 10000;
325
+
326
+ int24 baseTick = 0;
327
+
328
+ (Orders memory orders, , ) = SwapLimitOrders.computeOrders(
329
+ testKey,
330
+ false, // not isCurrency0
331
+ uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE),
332
+ baseTick,
333
+ TickMath.getSqrtPriceAtTick(baseTick),
334
+ params
335
+ );
336
+
337
+ assertEq(orders.ticks.length, 1, "should create one order");
338
+ // Should be at least tickSpacing away (negative direction)
339
+ assertLe(orders.ticks[0], baseTick - TICK_SPACING, "should maintain minimum separation");
340
+ }
341
+
342
+ function test_computeOrders_MultiplierInversionForCurrency1() public {
343
+ LimitOrderConfig memory params;
344
+ params.multiples = new uint256[](1);
345
+ params.percentages = new uint256[](1);
346
+
347
+ // Use a meaningful multiple
348
+ params.multiples[0] = 2e18; // 2x
349
+ params.percentages[0] = 10000;
350
+
351
+ int24 baseTick = 0;
352
+
353
+ // For isCurrency0 = false, multiplier gets inverted
354
+ (Orders memory orders, , ) = SwapLimitOrders.computeOrders(
355
+ testKey,
356
+ false, // not isCurrency0 - triggers inversion
357
+ uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE),
358
+ baseTick,
359
+ TickMath.getSqrtPriceAtTick(baseTick),
360
+ params
361
+ );
362
+
363
+ assertEq(orders.ticks.length, 1, "should create one order");
364
+ // For !isCurrency0, the tick should be below baseTick
365
+ assertLt(orders.ticks[0], baseTick, "inverted multiplier should place tick below base");
366
+ }
367
+
368
+ function test_computeOrders_AllBoundaryClampingBranches() public {
369
+ LimitOrderConfig memory params;
370
+
371
+ // Test 1: aligned > maxTick (line 167)
372
+ params.multiples = new uint256[](1);
373
+ params.percentages = new uint256[](1);
374
+ params.multiples[0] = 1000000e18;
375
+ params.percentages[0] = 10000;
376
+
377
+ (Orders memory orders1, , ) = SwapLimitOrders.computeOrders(
378
+ testKey,
379
+ true,
380
+ uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE),
381
+ 0,
382
+ TickMath.getSqrtPriceAtTick(0),
383
+ params
384
+ );
385
+ assertLe(orders1.ticks[0], TickMath.maxUsableTick(TICK_SPACING), "should clamp to maxTick");
386
+
387
+ // Test 2: aligned < minTick (line 168)
388
+ params.multiples[0] = 1000000e18;
389
+ int24 veryHighTick = TickMath.MAX_TICK - 100;
390
+ (Orders memory orders2, , ) = SwapLimitOrders.computeOrders(
391
+ testKey,
392
+ false, // inverted, goes negative
393
+ uint128(SwapLimitOrders.MIN_LIMIT_ORDER_SIZE),
394
+ veryHighTick,
395
+ TickMath.getSqrtPriceAtTick(veryHighTick),
396
+ params
397
+ );
398
+ assertGe(orders2.ticks[0], -TickMath.maxUsableTick(TICK_SPACING), "should clamp to minTick");
399
+
400
+ // Test 3 & 4: minAway enforcement tested in previous minimum separation tests
401
+ }
402
+
403
+ function _createValidParams(address maker, uint256 numOrders) internal pure returns (LimitOrderConfig memory) {
404
+ LimitOrderConfig memory params;
405
+ params.multiples = new uint256[](numOrders);
406
+ params.percentages = new uint256[](numOrders);
407
+
408
+ uint256 pctPerOrder = 10000 / numOrders;
409
+ for (uint256 i; i < numOrders; ++i) {
410
+ params.multiples[i] = (2 ** (i + 1)) * 1e18; // 2x, 4x, 8x, etc.
411
+ params.percentages[i] = pctPerOrder;
412
+ }
413
+
414
+ return params;
415
+ }
416
+
417
+ function _createEmptyParams(address maker) internal pure returns (LimitOrderConfig memory) {
418
+ LimitOrderConfig memory params;
419
+ params.multiples = new uint256[](0);
420
+ params.percentages = new uint256[](0);
421
+ return params;
422
+ }
423
+ }