@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,403 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import {BaseTest} from "../utils/BaseTest.sol";
5
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
6
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
7
+ import {LimitOrderCommon} from "../../src/libs/LimitOrderCommon.sol";
8
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
9
+ import {SwapWithLimitOrders} from "../../src/router/SwapWithLimitOrders.sol";
10
+ import {AddressConstants} from "@zoralabs/coins/test/utils/hookmate/constants/AddressConstants.sol";
11
+ import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
12
+
13
+ /**
14
+ * @title LimitOrderSwapGasTest
15
+ * @notice Forked gas benchmarks for a single swap that fills existing orders and creates 5 new ones.
16
+ */
17
+ contract LimitOrderSwapGasTest is BaseTest {
18
+ uint256 internal constant FORK_BLOCK = 38_875_958;
19
+ uint256 internal constant DEFAULT_MAX_FILL_COUNT = 50;
20
+
21
+ uint256 private gasStart;
22
+ uint256 private gasUsed;
23
+
24
+ function setUp() public override {
25
+ setUpWithBlockNumber(FORK_BLOCK);
26
+ limitOrderBook.setMaxFillCount(DEFAULT_MAX_FILL_COUNT);
27
+ }
28
+
29
+ function test_gas_hop2_swap_create5_fill5() public {
30
+ _runHop2Swap(5);
31
+ }
32
+
33
+ function test_gas_hop2_swap_create5_fill10() public {
34
+ _runHop2Swap(10);
35
+ }
36
+
37
+ function test_gas_hop2_swap_create5_fill25() public {
38
+ _runHop2Swap(25);
39
+ }
40
+
41
+ function test_gas_hop2_swap_create5_fill50() public {
42
+ _runHop2Swap(50);
43
+ }
44
+
45
+ function test_gas_hop3_swap_create5_fill5() public {
46
+ _runHop3Swap(5);
47
+ }
48
+
49
+ function test_gas_hop3_swap_create5_fill10() public {
50
+ _runHop3Swap(10);
51
+ }
52
+
53
+ function test_gas_hop3_swap_create5_fill25() public {
54
+ _runHop3Swap(25);
55
+ }
56
+
57
+ function test_gas_hop3_swap_create5_fill50() public {
58
+ _runHop3Swap(50);
59
+ }
60
+
61
+ function test_gas_hop4_swap_create5_fill5() public {
62
+ _runHop4Swap(5);
63
+ }
64
+
65
+ function test_gas_hop4_swap_create5_fill10() public {
66
+ _runHop4Swap(10);
67
+ }
68
+
69
+ function test_gas_hop4_swap_create5_fill25() public {
70
+ _runHop4Swap(25);
71
+ }
72
+
73
+ function test_gas_hop4_swap_create5_fill50() public {
74
+ _runHop4Swap(50);
75
+ }
76
+
77
+ function test_gas_hop1_swap_create5_fill5() public {
78
+ _runHop1Swap(5);
79
+ }
80
+
81
+ function test_gas_hop1_swap_create5_fill10() public {
82
+ _runHop1Swap(10);
83
+ }
84
+
85
+ function test_gas_hop1_swap_create5_fill25() public {
86
+ _runHop1Swap(25);
87
+ }
88
+
89
+ function test_gas_hop1_swap_create5_fill50() public {
90
+ _runHop1Swap(50);
91
+ }
92
+
93
+ function test_gas_hop3_swap_create5_fill0() public {
94
+ _runHop3NoFill();
95
+ }
96
+
97
+ function test_gas_hop4_swap_create5_fill0() public {
98
+ _runHop4NoFill();
99
+ }
100
+
101
+ // ---------------- helpers ----------------
102
+
103
+ function _runHop1Swap(uint256 existingOrders) internal {
104
+ PoolKey memory creatorKey = creatorCoin.getPoolKey();
105
+ bool isCurrency0 = Currency.unwrap(creatorKey.currency0) == address(creatorCoin);
106
+
107
+ CreatedOrderLog[] memory preloaded = existingOrders > 0 ? _preloadOrders(creatorKey, isCurrency0, existingOrders) : new CreatedOrderLog[](0);
108
+ emit log_named_uint("PRELOADED_ORDERS", preloaded.length);
109
+
110
+ vm.prank(users.factoryOwner);
111
+ limitOrderBook.setMaxFillCount(existingOrders + 10);
112
+
113
+ PoolKey[] memory v4Route = new PoolKey[](1);
114
+ v4Route[0] = creatorKey;
115
+ bytes memory v3Route = bytes("");
116
+
117
+ uint256 amountIn = 1_000e18;
118
+ _fundAndApprove(address(zoraToken), amountIn, users.buyer);
119
+
120
+ SwapWithLimitOrders.SwapWithLimitOrdersParams memory params = SwapWithLimitOrders.SwapWithLimitOrdersParams({
121
+ recipient: users.buyer,
122
+ limitOrderConfig: _prepareLimitOrderParams(users.buyer, _defaultMultiples(), _defaultPercentages()),
123
+ inputCurrency: address(zoraToken),
124
+ inputAmount: amountIn,
125
+ v3Route: v3Route,
126
+ v4Route: v4Route,
127
+ minAmountOut: 0
128
+ });
129
+
130
+ vm.recordLogs();
131
+ gasStart = gasleft();
132
+ vm.prank(users.buyer);
133
+ swapWithLimitOrders.swapWithLimitOrders(params);
134
+ gasUsed = gasStart - gasleft();
135
+
136
+ FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
137
+ CreatedOrderLog[] memory created = _decodeCreatedLogs(vm.getRecordedLogs());
138
+
139
+ emit log_named_uint("ROUTER_CREATED_ORDERS", created.length);
140
+ emit log_named_uint("SWAP_FILLED_ORDERS", fills.length);
141
+ emit log_named_uint("SINGLE_SWAP_CREATE_AND_FILL_GAS", gasUsed);
142
+
143
+ address orderCoin = LimitOrderCommon.getOrderCoin(creatorKey, isCurrency0);
144
+ emit log_named_uint("MAKER_BALANCE_AFTER", limitOrderBook.balanceOf(users.buyer, orderCoin));
145
+ }
146
+
147
+ function _runHop2Swap(uint256 existingOrders) internal {
148
+ PoolKey memory contentKey = contentCoin.getPoolKey();
149
+ bool isCurrency0 = Currency.unwrap(contentKey.currency0) == address(contentCoin);
150
+
151
+ CreatedOrderLog[] memory preloaded = existingOrders > 0 ? _preloadOrders(contentKey, isCurrency0, existingOrders) : new CreatedOrderLog[](0);
152
+ emit log_named_uint("PRELOADED_ORDERS", preloaded.length);
153
+
154
+ // allow enough fills (existing + some headroom for newly in-range orders)
155
+ vm.prank(users.factoryOwner);
156
+ limitOrderBook.setMaxFillCount(existingOrders + 10);
157
+
158
+ // build router params (hop2: ZORA -> creator -> content)
159
+ PoolKey[] memory v4Route = new PoolKey[](2);
160
+ v4Route[0] = creatorCoin.getPoolKey();
161
+ v4Route[1] = contentCoin.getPoolKey();
162
+ bytes memory v3Route = bytes("");
163
+
164
+ uint256 amountIn = 1_000e18; // large enough to cross ticks
165
+ _fundAndApprove(address(zoraToken), amountIn, users.buyer);
166
+
167
+ SwapWithLimitOrders.SwapWithLimitOrdersParams memory params = SwapWithLimitOrders.SwapWithLimitOrdersParams({
168
+ recipient: users.buyer,
169
+ limitOrderConfig: _prepareLimitOrderParams(users.buyer, _defaultMultiples(), _defaultPercentages()),
170
+ inputCurrency: address(zoraToken),
171
+ inputAmount: amountIn,
172
+ v3Route: v3Route,
173
+ v4Route: v4Route,
174
+ minAmountOut: 0
175
+ });
176
+
177
+ vm.recordLogs();
178
+ gasStart = gasleft();
179
+ vm.prank(users.buyer);
180
+ swapWithLimitOrders.swapWithLimitOrders(params);
181
+ gasUsed = gasStart - gasleft();
182
+
183
+ FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
184
+ CreatedOrderLog[] memory created = _decodeCreatedLogs(vm.getRecordedLogs());
185
+
186
+ emit log_named_uint("ROUTER_CREATED_ORDERS", created.length);
187
+ emit log_named_uint("SWAP_FILLED_ORDERS", fills.length);
188
+ emit log_named_uint("SINGLE_SWAP_CREATE_AND_FILL_GAS", gasUsed);
189
+
190
+ address orderCoin = LimitOrderCommon.getOrderCoin(contentKey, isCurrency0);
191
+ emit log_named_uint("MAKER_BALANCE_AFTER", limitOrderBook.balanceOf(users.buyer, orderCoin));
192
+ }
193
+
194
+ function _runHop3Swap(uint256 existingOrders) internal {
195
+ PoolKey memory contentKey = contentCoin.getPoolKey();
196
+ bool isCurrency0 = Currency.unwrap(contentKey.currency0) == address(contentCoin);
197
+
198
+ CreatedOrderLog[] memory preloaded = existingOrders > 0 ? _preloadOrders(contentKey, isCurrency0, existingOrders) : new CreatedOrderLog[](0);
199
+ emit log_named_uint("PRELOADED_ORDERS", preloaded.length);
200
+
201
+ vm.prank(users.factoryOwner);
202
+ limitOrderBook.setMaxFillCount(existingOrders + 10);
203
+
204
+ PoolKey[] memory v4Route = new PoolKey[](2);
205
+ v4Route[0] = creatorCoin.getPoolKey();
206
+ v4Route[1] = contentCoin.getPoolKey();
207
+ bytes memory v3Route = abi.encodePacked(USDC_ADDRESS, uint24(10000), ZORA_TOKEN_ADDRESS);
208
+
209
+ uint256 amountIn = 20_000e6;
210
+ _fundAndApprove(USDC_ADDRESS, amountIn, users.buyer);
211
+
212
+ SwapWithLimitOrders.SwapWithLimitOrdersParams memory params = SwapWithLimitOrders.SwapWithLimitOrdersParams({
213
+ recipient: users.buyer,
214
+ limitOrderConfig: _prepareLimitOrderParams(users.buyer, _defaultMultiples(), _defaultPercentages()),
215
+ inputCurrency: USDC_ADDRESS,
216
+ inputAmount: amountIn,
217
+ v3Route: v3Route,
218
+ v4Route: v4Route,
219
+ minAmountOut: 0
220
+ });
221
+
222
+ vm.recordLogs();
223
+ gasStart = gasleft();
224
+ vm.prank(users.buyer);
225
+ swapWithLimitOrders.swapWithLimitOrders(params);
226
+ gasUsed = gasStart - gasleft();
227
+
228
+ FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
229
+ CreatedOrderLog[] memory created = _decodeCreatedLogs(vm.getRecordedLogs());
230
+
231
+ emit log_named_uint("ROUTER_CREATED_ORDERS", created.length);
232
+ emit log_named_uint("SWAP_FILLED_ORDERS", fills.length);
233
+ emit log_named_uint("SINGLE_SWAP_CREATE_AND_FILL_GAS", gasUsed);
234
+
235
+ address orderCoin = LimitOrderCommon.getOrderCoin(contentKey, isCurrency0);
236
+ emit log_named_uint("MAKER_BALANCE_AFTER", limitOrderBook.balanceOf(users.buyer, orderCoin));
237
+ }
238
+
239
+ function _runHop4Swap(uint256 existingOrders) internal {
240
+ PoolKey memory contentKey = contentCoin.getPoolKey();
241
+ bool isCurrency0 = Currency.unwrap(contentKey.currency0) == address(contentCoin);
242
+
243
+ CreatedOrderLog[] memory preloaded = existingOrders > 0 ? _preloadOrders(contentKey, isCurrency0, existingOrders) : new CreatedOrderLog[](0);
244
+ emit log_named_uint("PRELOADED_ORDERS", preloaded.length);
245
+
246
+ vm.prank(users.factoryOwner);
247
+ limitOrderBook.setMaxFillCount(existingOrders + 10);
248
+
249
+ PoolKey[] memory v4Route = new PoolKey[](2);
250
+ v4Route[0] = creatorCoin.getPoolKey();
251
+ v4Route[1] = contentCoin.getPoolKey();
252
+ bytes memory v3Route = abi.encodePacked(WETH_ADDRESS, uint24(500), USDC_ADDRESS, uint24(10000), ZORA_TOKEN_ADDRESS);
253
+
254
+ uint256 amountIn = 10 ether;
255
+ _fundAndApprove(WETH_ADDRESS, amountIn, users.buyer);
256
+
257
+ SwapWithLimitOrders.SwapWithLimitOrdersParams memory params = SwapWithLimitOrders.SwapWithLimitOrdersParams({
258
+ recipient: users.buyer,
259
+ limitOrderConfig: _prepareLimitOrderParams(users.buyer, _defaultMultiples(), _defaultPercentages()),
260
+ inputCurrency: WETH_ADDRESS,
261
+ inputAmount: amountIn,
262
+ v3Route: v3Route,
263
+ v4Route: v4Route,
264
+ minAmountOut: 0
265
+ });
266
+
267
+ vm.recordLogs();
268
+ gasStart = gasleft();
269
+ vm.prank(users.buyer);
270
+ swapWithLimitOrders.swapWithLimitOrders(params);
271
+ gasUsed = gasStart - gasleft();
272
+
273
+ FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
274
+ CreatedOrderLog[] memory created = _decodeCreatedLogs(vm.getRecordedLogs());
275
+
276
+ emit log_named_uint("ROUTER_CREATED_ORDERS", created.length);
277
+ emit log_named_uint("SWAP_FILLED_ORDERS", fills.length);
278
+ emit log_named_uint("SINGLE_SWAP_CREATE_AND_FILL_GAS", gasUsed);
279
+
280
+ address orderCoin = LimitOrderCommon.getOrderCoin(contentKey, isCurrency0);
281
+ emit log_named_uint("MAKER_BALANCE_AFTER", limitOrderBook.balanceOf(users.buyer, orderCoin));
282
+ }
283
+
284
+ function _runHop3NoFill() internal {
285
+ PoolKey memory creatorKey = creatorCoin.getPoolKey();
286
+ bool isCurrency0 = Currency.unwrap(creatorKey.currency0) == address(creatorCoin);
287
+
288
+ vm.prank(users.factoryOwner);
289
+ limitOrderBook.setMaxFillCount(10);
290
+
291
+ PoolKey[] memory v4Route = new PoolKey[](1);
292
+ v4Route[0] = creatorKey;
293
+ bytes memory v3Route = abi.encodePacked(USDC_ADDRESS, uint24(10000), ZORA_TOKEN_ADDRESS);
294
+
295
+ uint256 amountIn = 10_000e6;
296
+ _fundAndApprove(USDC_ADDRESS, amountIn, users.buyer);
297
+
298
+ SwapWithLimitOrders.SwapWithLimitOrdersParams memory params = SwapWithLimitOrders.SwapWithLimitOrdersParams({
299
+ recipient: users.buyer,
300
+ limitOrderConfig: _prepareLimitOrderParams(users.buyer, _defaultMultiples(), _defaultPercentages()),
301
+ inputCurrency: USDC_ADDRESS,
302
+ inputAmount: amountIn,
303
+ v3Route: v3Route,
304
+ v4Route: v4Route,
305
+ minAmountOut: 0
306
+ });
307
+
308
+ vm.recordLogs();
309
+ gasStart = gasleft();
310
+ vm.prank(users.buyer);
311
+ swapWithLimitOrders.swapWithLimitOrders(params);
312
+ gasUsed = gasStart - gasleft();
313
+
314
+ FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
315
+ CreatedOrderLog[] memory created = _decodeCreatedLogs(vm.getRecordedLogs());
316
+
317
+ emit log_named_uint("ROUTER_CREATED_ORDERS", created.length);
318
+ emit log_named_uint("SWAP_FILLED_ORDERS", fills.length);
319
+ emit log_named_uint("SINGLE_SWAP_CREATE_AND_FILL_GAS", gasUsed);
320
+
321
+ address orderCoin = LimitOrderCommon.getOrderCoin(creatorKey, isCurrency0);
322
+ emit log_named_uint("MAKER_BALANCE_AFTER", limitOrderBook.balanceOf(users.buyer, orderCoin));
323
+ }
324
+
325
+ function _runHop4NoFill() internal {
326
+ PoolKey memory creatorKey = creatorCoin.getPoolKey();
327
+ bool isCurrency0 = Currency.unwrap(creatorKey.currency0) == address(creatorCoin);
328
+
329
+ vm.prank(users.factoryOwner);
330
+ limitOrderBook.setMaxFillCount(10);
331
+
332
+ PoolKey[] memory v4Route = new PoolKey[](1);
333
+ v4Route[0] = creatorKey;
334
+ bytes memory v3Route = abi.encodePacked(WETH_ADDRESS, uint24(500), USDC_ADDRESS, uint24(10000), ZORA_TOKEN_ADDRESS);
335
+
336
+ uint256 amountIn = 5 ether;
337
+ _fundAndApprove(WETH_ADDRESS, amountIn, users.buyer);
338
+
339
+ SwapWithLimitOrders.SwapWithLimitOrdersParams memory params = SwapWithLimitOrders.SwapWithLimitOrdersParams({
340
+ recipient: users.buyer,
341
+ limitOrderConfig: _prepareLimitOrderParams(users.buyer, _defaultMultiples(), _defaultPercentages()),
342
+ inputCurrency: WETH_ADDRESS,
343
+ inputAmount: amountIn,
344
+ v3Route: v3Route,
345
+ v4Route: v4Route,
346
+ minAmountOut: 0
347
+ });
348
+
349
+ vm.recordLogs();
350
+ gasStart = gasleft();
351
+ vm.prank(users.buyer);
352
+ swapWithLimitOrders.swapWithLimitOrders(params);
353
+ gasUsed = gasStart - gasleft();
354
+
355
+ FilledOrderLog[] memory fills = _decodeFilledLogs(vm.getRecordedLogs());
356
+ CreatedOrderLog[] memory created = _decodeCreatedLogs(vm.getRecordedLogs());
357
+
358
+ emit log_named_uint("ROUTER_CREATED_ORDERS", created.length);
359
+ emit log_named_uint("SWAP_FILLED_ORDERS", fills.length);
360
+ emit log_named_uint("SINGLE_SWAP_CREATE_AND_FILL_GAS", gasUsed);
361
+
362
+ address orderCoin = LimitOrderCommon.getOrderCoin(creatorKey, isCurrency0);
363
+ emit log_named_uint("MAKER_BALANCE_AFTER", limitOrderBook.balanceOf(users.buyer, orderCoin));
364
+ }
365
+
366
+ function _fundAndApprove(address token, uint256 amount, address trader) internal {
367
+ if (token == address(0)) {
368
+ vm.deal(trader, amount);
369
+ return;
370
+ }
371
+ deal(token, trader, amount);
372
+ address permit2 = AddressConstants.getPermit2Address();
373
+ vm.startPrank(trader);
374
+ IERC20(token).approve(permit2, type(uint256).max);
375
+ IAllowanceTransfer(permit2).approve(token, address(swapWithLimitOrders), type(uint160).max, type(uint48).max);
376
+ vm.stopPrank();
377
+ }
378
+
379
+ function _preloadOrders(PoolKey memory key, bool isCurrency0, uint256 count) internal returns (CreatedOrderLog[] memory created) {
380
+ address orderCoin = LimitOrderCommon.getOrderCoin(key, isCurrency0);
381
+
382
+ uint256 orderSize = 25e18;
383
+ (uint256[] memory sizes, int24[] memory ticks) = _buildDeterministicOrders(key, isCurrency0, count, orderSize);
384
+
385
+ uint256 totalSize;
386
+ for (uint256 i; i < sizes.length; ++i) {
387
+ totalSize += sizes[i];
388
+ }
389
+
390
+ if (orderCoin == address(0)) {
391
+ vm.deal(users.seller, totalSize);
392
+ } else {
393
+ deal(orderCoin, users.seller, totalSize);
394
+ vm.prank(users.seller);
395
+ IERC20(orderCoin).approve(address(limitOrderBook), totalSize);
396
+ }
397
+
398
+ vm.recordLogs();
399
+ vm.prank(users.seller);
400
+ limitOrderBook.create{value: orderCoin == address(0) ? totalSize : 0}(key, isCurrency0, sizes, ticks, users.seller);
401
+ created = _decodeCreatedLogs(vm.getRecordedLogs());
402
+ }
403
+ }
@@ -0,0 +1,30 @@
1
+ No files changed, compilation skipped
2
+
3
+ Ran 23 tests for test/gas/LimitOrderFillGas.t.sol:LimitOrderFillGasTest
4
+ [PASS] test_gas_0hop_single_order_direct_payout() (gas: 3256179)
5
+ [PASS] test_gas_1hop_single_order_fill() (gas: 3256779)
6
+ [PASS] test_gas_1hop_user_swap_autofill_40_orders() (gas: 16707751)
7
+ [PASS] test_gas_1hop_user_swap_autofill_50_orders() (gas: 20375584)
8
+ [PASS] test_gas_2hop_five_orders_fill() (gas: 7792758)
9
+ [PASS] test_gas_2hop_single_order_fill() (gas: 6143613)
10
+ [PASS] test_gas_2hop_ten_orders_fill() (gas: 9872756)
11
+ [PASS] test_gas_2hop_user_swap_triggers_autofill() (gas: 6776579)
12
+ [PASS] test_gas_backend_manual_fill_100_orders() (gas: 47656019)
13
+ [PASS] test_gas_backend_manual_fill_150_orders() (gas: 68957450)
14
+ [PASS] test_gas_backend_manual_fill_50_orders() (gas: 26598104)
15
+ [PASS] test_gas_baseline_swap_no_limit_orders() (gas: 1778873)
16
+ [PASS] test_gas_empty_fill() (gas: 143268)
17
+ [PASS] test_gas_large_swap_with_fee_conversion() (gas: 2008446)
18
+ [PASS] test_gas_max_fillcount_stress_test() (gas: 16128877)
19
+ [PASS] test_gas_mixed_order_sizes() (gas: 7793945)
20
+ [PASS] test_gas_same_tick_orders() (gas: 7444207)
21
+ [PASS] test_gas_user_swap_autofill_100_orders() (gas: 42545248)
22
+ [PASS] test_gas_user_swap_autofill_10_orders() (gas: 8700941)
23
+ [PASS] test_gas_user_swap_autofill_25_orders() (gas: 14201062)
24
+ [PASS] test_gas_user_swap_autofill_40_orders() (gas: 19625345)
25
+ [PASS] test_gas_user_swap_autofill_50_orders() (gas: 23168604)
26
+ [PASS] test_gas_user_swap_autofill_75_orders() (gas: 32899292)
27
+ Suite result: ok. 23 passed; 0 failed; 0 skipped; finished in 926.81ms (462.22ms CPU time)
28
+
29
+
30
+ Ran 1 test suite in 956.61ms (926.81ms CPU time): 23 tests passed, 0 failed, 0 skipped (23 total tests)
@@ -0,0 +1,27 @@
1
+ Compiling 1 files with Solc 0.8.28
2
+ Solc 0.8.28 finished in 28.29s
3
+ Compiler run successful!
4
+
5
+ Ran 18 tests for test/gas/LimitOrderSwapGas.t.sol:LimitOrderSwapGasTest
6
+ [PASS] test_gas_hop1_swap_create5_fill10() (gas: 8103292)
7
+ [PASS] test_gas_hop1_swap_create5_fill25() (gas: 13364745)
8
+ [PASS] test_gas_hop1_swap_create5_fill5() (gas: 6372150)
9
+ [PASS] test_gas_hop1_swap_create5_fill50() (gas: 23312531)
10
+ [PASS] test_gas_hop2_swap_create5_fill10() (gas: 11385994)
11
+ [PASS] test_gas_hop2_swap_create5_fill25() (gas: 17144425)
12
+ [PASS] test_gas_hop2_swap_create5_fill5() (gas: 9485181)
13
+ [PASS] test_gas_hop2_swap_create5_fill50() (gas: 26916911)
14
+ [PASS] test_gas_hop3_swap_create5_fill0() (gas: 5932665)
15
+ [PASS] test_gas_hop3_swap_create5_fill10() (gas: 14088859)
16
+ [PASS] test_gas_hop3_swap_create5_fill25() (gas: 19866108)
17
+ [PASS] test_gas_hop3_swap_create5_fill5() (gas: 12182264)
18
+ [PASS] test_gas_hop3_swap_create5_fill50() (gas: 29670228)
19
+ [PASS] test_gas_hop4_swap_create5_fill0() (gas: 5963338)
20
+ [PASS] test_gas_hop4_swap_create5_fill10() (gas: 14303856)
21
+ [PASS] test_gas_hop4_swap_create5_fill25() (gas: 20082719)
22
+ [PASS] test_gas_hop4_swap_create5_fill5() (gas: 12396378)
23
+ [PASS] test_gas_hop4_swap_create5_fill50() (gas: 29889559)
24
+ Suite result: ok. 18 passed; 0 failed; 0 skipped; finished in 32.68s (143.80s CPU time)
25
+
26
+
27
+ Ran 1 test suite in 32.71s (32.68s CPU time): 18 tests passed, 0 failed, 0 skipped (18 total tests)