@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,793 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.13;
3
+
4
+ import {Vm} from "forge-std/Vm.sol";
5
+ import {AccessManager} from "@openzeppelin/contracts/access/manager/AccessManager.sol";
6
+ import {V4TestSetup} from "@zoralabs/coins/test/utils/V4TestSetup.sol";
7
+ import {IZoraLimitOrderBook} from "../../src/IZoraLimitOrderBook.sol";
8
+ import {TestableZoraLimitOrderBook} from "./TestableZoraLimitOrderBook.sol";
9
+ import {SwapWithLimitOrders} from "../../src/router/SwapWithLimitOrders.sol";
10
+ import {IMsgSender} from "@zoralabs/coins/src/interfaces/IMsgSender.sol";
11
+ import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
12
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
13
+ import {CreatorCoin} from "@zoralabs/coins/src/CreatorCoin.sol";
14
+ import {ContentCoin} from "@zoralabs/coins/src/ContentCoin.sol";
15
+ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
16
+ import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
17
+ import {LimitOrderConfig} from "../../src/libs/SwapLimitOrders.sol";
18
+ import {CoinConfigurationVersions} from "@zoralabs/coins/src/libs/CoinConfigurationVersions.sol";
19
+ import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
20
+ import {UniV4SwapHelper} from "@zoralabs/coins/src/libs/UniV4SwapHelper.sol";
21
+ import {LimitOrderCommon} from "../../src/libs/LimitOrderCommon.sol";
22
+ import {LimitOrderStorage} from "../../src/libs/LimitOrderStorage.sol";
23
+ import {LimitOrderTypes} from "../../src/libs/LimitOrderTypes.sol";
24
+ import {TickBitmap} from "@uniswap/v4-core/src/libraries/TickBitmap.sol";
25
+ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
26
+ import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
27
+ import {AddressConstants} from "@zoralabs/coins/test/utils/hookmate/constants/AddressConstants.sol";
28
+ import {ICoin} from "@zoralabs/coins/src/interfaces/ICoin.sol";
29
+
30
+ /**
31
+ * @title BaseTest
32
+ * @notice Limit-orders-specific test utilities extending V4TestSetup
33
+ * @dev This contract adds limit-order-specific setup on top of shared V4 infrastructure
34
+ */
35
+ contract BaseTest is V4TestSetup, IMsgSender {
36
+ using PoolIdLibrary for PoolKey;
37
+ TestableZoraLimitOrderBook internal limitOrderBook = TestableZoraLimitOrderBook(payable(makeAddr("limitOrderBook")));
38
+ AccessManager internal accessManager;
39
+ SwapWithLimitOrders internal swapWithLimitOrders;
40
+
41
+ function setUp() public virtual {
42
+ setUpNonForked();
43
+ }
44
+
45
+ function setUpWithBlockNumber(uint256 forkBlockNumber) public virtual {
46
+ // First set up limit order book (needs to be done before _setUpWithBlockNumber which calls _deployHooks)
47
+ _setUpWithBlockNumber(forkBlockNumber, address(limitOrderBook));
48
+ _setupLimitOrderBook();
49
+ }
50
+
51
+ function setUpNonForked() public virtual {
52
+ // For non-forked tests, use a mock that doesn't access transient storage
53
+ // since the pool manager doesn't have the same transient state as in fork tests
54
+ // mockLimitOrderBookForHook = new MockZoraLimitOrderBookNoTransientStorage();
55
+ _setUpNonForked(address(limitOrderBook));
56
+ _setupLimitOrderBook();
57
+ }
58
+
59
+ function _setupLimitOrderBook() internal {
60
+ // Deploy AccessManager with this contract as admin
61
+ accessManager = new AccessManager(address(this));
62
+
63
+ deployCodeTo(
64
+ "TestableZoraLimitOrderBook.sol:TestableZoraLimitOrderBook",
65
+ abi.encode(address(poolManager), address(factory), address(zoraHookRegistry), address(accessManager)),
66
+ address(limitOrderBook)
67
+ );
68
+ require(limitOrderBook.authority() == address(accessManager), "ZoraLimitOrderBook authority is not the access manager");
69
+
70
+ // Set create() and setMaxFillCount() functions to PUBLIC_ROLE to allow anyone to call them initially
71
+ bytes4[] memory selectors = new bytes4[](2);
72
+ selectors[0] = IZoraLimitOrderBook.create.selector;
73
+ selectors[1] = IZoraLimitOrderBook.setMaxFillCount.selector;
74
+ accessManager.setTargetFunctionRole(address(limitOrderBook), selectors, accessManager.PUBLIC_ROLE());
75
+
76
+ limitOrderBook.setMaxFillCount(50);
77
+
78
+ vm.label(address(limitOrderBook), "LIMIT_ORDER_BOOK");
79
+
80
+ swapWithLimitOrders = new SwapWithLimitOrders(poolManager, limitOrderBook, swapRouter, AddressConstants.getPermit2Address());
81
+ vm.label(address(swapWithLimitOrders), "SWAP_WITH_LIMIT_ORDERS");
82
+ // Now create the real ZoraLimitOrderBook for tests that need it
83
+ _deployTestCoins();
84
+ }
85
+
86
+ function _getLimitOrderBookAddress() internal view virtual override returns (address) {
87
+ return address(limitOrderBook);
88
+ }
89
+
90
+ // Alias for compatibility with test suite
91
+ uint256 internal constant DEFAULT_LIMIT_ORDER_FILL_COUNT = 50;
92
+ uint256 internal constant DEFAULT_LIMIT_ORDER_AMOUNT = 100e18;
93
+
94
+ bytes32 internal constant LIMIT_ORDER_CREATED_TOPIC = keccak256("LimitOrderCreated(address,address,bytes32,bool,int24,int24,uint128,bytes32)");
95
+ bytes32 internal constant LIMIT_ORDER_FILLED_TOPIC =
96
+ keccak256("LimitOrderFilled(address,address,address,uint128,uint128,address,uint128,bytes32,int24,bytes32)");
97
+ bytes32 internal constant LIMIT_ORDER_UPDATED_TOPIC = keccak256("LimitOrderUpdated(address,address,bytes32,bool,int24,uint128,bytes32,bool)");
98
+ bytes32 internal constant SWAP_WITH_LIMIT_ORDERS_EXECUTED_TOPIC =
99
+ keccak256("SwapWithLimitOrdersExecuted(address,address,(address,address,uint24,int24,address),int256,int24,int24,(bytes32,uint256,uint256)[])");
100
+
101
+ struct QueueSnapshot {
102
+ bytes32 head;
103
+ bytes32 tail;
104
+ uint128 length;
105
+ uint128 balance;
106
+ }
107
+
108
+ struct CreatedOrderLog {
109
+ address maker;
110
+ address coin;
111
+ bytes32 poolKeyHash;
112
+ bool isCurrency0;
113
+ int24 tick;
114
+ int24 currentTick;
115
+ uint128 size;
116
+ bytes32 orderId;
117
+ }
118
+
119
+ struct FilledOrderLog {
120
+ address maker;
121
+ address coinIn;
122
+ address coinOut;
123
+ uint128 amountIn;
124
+ uint128 amountOut;
125
+ address fillReferral;
126
+ uint128 fillReferralAmount;
127
+ bytes32 poolKeyHash;
128
+ int24 tick;
129
+ bytes32 orderId;
130
+ }
131
+
132
+ struct UpdatedOrderLog {
133
+ address maker;
134
+ address coin;
135
+ bytes32 poolKeyHash;
136
+ bool isCurrency0;
137
+ int24 tick;
138
+ uint128 newSize;
139
+ bytes32 orderId;
140
+ bool isCancelled;
141
+ }
142
+
143
+ struct CreatedOrder {
144
+ bytes32 orderId;
145
+ uint256 multiple;
146
+ uint256 percentage;
147
+ }
148
+
149
+ struct SwapExecutedLog {
150
+ address sender;
151
+ address recipient;
152
+ PoolKey poolKey;
153
+ int256 delta;
154
+ int24 tickBefore;
155
+ int24 tickAfter;
156
+ CreatedOrder[] orders;
157
+ }
158
+
159
+ address internal routerMsgSender;
160
+ CreatorCoin internal creatorCoin;
161
+ ContentCoin internal contentCoin;
162
+
163
+ function msgSender() external view override returns (address) {
164
+ return routerMsgSender;
165
+ }
166
+
167
+ function _setRouterMsgSender(address newSender) internal {
168
+ routerMsgSender = newSender;
169
+ }
170
+
171
+ function _deployTestCoins() internal {
172
+ creatorCoin = _deployCreatorCoin();
173
+ contentCoin = _deployContentCoin(address(creatorCoin));
174
+ }
175
+
176
+ function _deployCreatorCoin() internal returns (CreatorCoin deployed) {
177
+ bytes memory poolConfig = _creatorCoinPoolConfig();
178
+
179
+ vm.prank(users.creator);
180
+ address coinAddress = factory.deployCreatorCoin(
181
+ users.creator,
182
+ _getDefaultOwners(),
183
+ "https://testcreatorcoin.com",
184
+ "TestCreatorCoin",
185
+ "TESTCREATORCOIN",
186
+ poolConfig,
187
+ address(0),
188
+ bytes32(0)
189
+ );
190
+
191
+ deployed = CreatorCoin(coinAddress);
192
+ vm.label(coinAddress, "TEST_CREATOR_COIN");
193
+ }
194
+
195
+ function _deployContentCoin(address currency) internal returns (ContentCoin deployed) {
196
+ bytes memory poolConfig = _contentCoinPoolConfig(currency);
197
+
198
+ vm.prank(users.creator);
199
+ (address coinAddress, ) = factory.deploy(
200
+ users.creator,
201
+ _getDefaultOwners(),
202
+ "https://testcontentcoin.com",
203
+ "TestContentCoin",
204
+ "TESTCONTENTCOIN",
205
+ poolConfig,
206
+ address(0),
207
+ address(0),
208
+ bytes(""),
209
+ bytes32(0)
210
+ );
211
+
212
+ deployed = ContentCoin(coinAddress);
213
+ vm.label(coinAddress, "TEST_CONTENT_COIN");
214
+ }
215
+
216
+ function _creatorCoinPoolConfig() internal view returns (bytes memory) {
217
+ int24[] memory tickLower = new int24[](3);
218
+ int24[] memory tickUpper = new int24[](3);
219
+ uint16[] memory numDiscoveryPositions = new uint16[](3);
220
+ uint256[] memory maxDiscoverySupplyShare = new uint256[](3);
221
+
222
+ tickLower[0] = -103_000;
223
+ tickUpper[0] = -74_000;
224
+ numDiscoveryPositions[0] = 11;
225
+ maxDiscoverySupplyShare[0] = 0.075e18;
226
+
227
+ tickLower[1] = -88_000;
228
+ tickUpper[1] = -66_000;
229
+ numDiscoveryPositions[1] = 11;
230
+ maxDiscoverySupplyShare[1] = 0.125e18;
231
+
232
+ tickLower[2] = -76_000;
233
+ tickUpper[2] = -66_000;
234
+ numDiscoveryPositions[2] = 11;
235
+ maxDiscoverySupplyShare[2] = 0.175e18;
236
+
237
+ return
238
+ abi.encode(
239
+ CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION,
240
+ address(zoraToken),
241
+ tickLower,
242
+ tickUpper,
243
+ numDiscoveryPositions,
244
+ maxDiscoverySupplyShare
245
+ );
246
+ }
247
+
248
+ function _contentCoinPoolConfig(address currency) internal pure returns (bytes memory) {
249
+ int24[] memory tickLower = new int24[](4);
250
+ int24[] memory tickUpper = new int24[](4);
251
+ uint16[] memory numDiscoveryPositions = new uint16[](4);
252
+ uint256[] memory maxDiscoverySupplyShare = new uint256[](4);
253
+
254
+ tickLower[0] = -54_000;
255
+ tickUpper[0] = -7000;
256
+ numDiscoveryPositions[0] = 11;
257
+ maxDiscoverySupplyShare[0] = 0.1e18;
258
+
259
+ tickLower[1] = -30_000;
260
+ tickUpper[1] = 7000;
261
+ numDiscoveryPositions[1] = 11;
262
+ maxDiscoverySupplyShare[1] = 0.2e18;
263
+
264
+ tickLower[2] = -39_000;
265
+ tickUpper[2] = 7000;
266
+ numDiscoveryPositions[2] = 11;
267
+ maxDiscoverySupplyShare[2] = 0.1e18;
268
+
269
+ tickLower[3] = -85_000;
270
+ tickUpper[3] = 7000;
271
+ numDiscoveryPositions[3] = 11;
272
+ maxDiscoverySupplyShare[3] = 0.05e18;
273
+
274
+ return
275
+ abi.encode(
276
+ CoinConfigurationVersions.DOPPLER_MULTICURVE_UNI_V4_POOL_VERSION,
277
+ currency,
278
+ tickLower,
279
+ tickUpper,
280
+ numDiscoveryPositions,
281
+ maxDiscoverySupplyShare
282
+ );
283
+ }
284
+
285
+ function _defaultMultiples() internal pure returns (uint256[] memory multiples) {
286
+ multiples = new uint256[](5);
287
+ multiples[0] = 2e18;
288
+ multiples[1] = 4e18;
289
+ multiples[2] = 8e18;
290
+ multiples[3] = 16e18;
291
+ multiples[4] = 32e18;
292
+ }
293
+
294
+ function _defaultPercentages() internal pure returns (uint256[] memory percentages) {
295
+ percentages = new uint256[](5);
296
+ percentages[0] = 2000; // 20%
297
+ percentages[1] = 2000; // 20%
298
+ percentages[2] = 2000; // 20%
299
+ percentages[3] = 2000; // 20%
300
+ percentages[4] = 2000; // 20%
301
+ }
302
+
303
+ function _prepareLimitOrderParams(
304
+ address, // maker - unused, kept for compatibility
305
+ uint256[] memory multiples,
306
+ uint256[] memory percentages
307
+ ) internal pure returns (LimitOrderConfig memory params) {
308
+ params.multiples = multiples;
309
+ params.percentages = percentages;
310
+ }
311
+
312
+ function _executeSingleHopSwapWithLimitOrders(
313
+ address trader,
314
+ PoolKey memory poolKey,
315
+ uint256 amountIn,
316
+ uint256[] memory multiples,
317
+ uint256[] memory percentages
318
+ ) internal returns (LimitOrderConfig memory params) {
319
+ params = _prepareLimitOrderParams(trader, multiples, percentages);
320
+
321
+ // Use SwapWithLimitOrders router to create autosell orders
322
+ PoolKey[] memory route = new PoolKey[](1);
323
+ route[0] = poolKey;
324
+ _executeSwapWithLimitOrders(trader, amountIn, route, params);
325
+ }
326
+
327
+ function _executeMultiHopSwapWithLimitOrders(
328
+ address trader,
329
+ PoolKey[] memory keys,
330
+ uint256 amountIn,
331
+ uint256[] memory multiples,
332
+ uint256[] memory percentages
333
+ ) internal returns (LimitOrderConfig memory params) {
334
+ params = _prepareLimitOrderParams(trader, multiples, percentages);
335
+
336
+ // Use SwapWithLimitOrders router to create autosell orders
337
+ _executeSwapWithLimitOrders(trader, amountIn, keys, params);
338
+ }
339
+
340
+ function _executeSwapWithLimitOrders(address trader, uint256 amountIn, PoolKey[] memory keys, LimitOrderConfig memory params) internal {
341
+ deal(address(zoraToken), trader, amountIn);
342
+
343
+ vm.startPrank(trader);
344
+ // Approve Permit2 to spend tokens (matching universal-router pattern)
345
+ address permit2 = AddressConstants.getPermit2Address();
346
+ IERC20(address(zoraToken)).approve(permit2, type(uint256).max);
347
+
348
+ // Approve swapWithLimitOrders as spender in Permit2
349
+ // Use uint48 max for expiration to never expire during tests
350
+ IAllowanceTransfer(permit2).approve(address(zoraToken), address(swapWithLimitOrders), uint160(type(uint160).max), type(uint48).max);
351
+
352
+ SwapWithLimitOrders.SwapWithLimitOrdersParams memory swapParams = SwapWithLimitOrders.SwapWithLimitOrdersParams({
353
+ recipient: trader,
354
+ limitOrderConfig: params,
355
+ inputCurrency: address(zoraToken),
356
+ inputAmount: amountIn,
357
+ v3Route: bytes(""),
358
+ v4Route: keys,
359
+ minAmountOut: 0
360
+ });
361
+
362
+ swapWithLimitOrders.swapWithLimitOrders(swapParams);
363
+ vm.stopPrank();
364
+ }
365
+
366
+ function _executeSingleHopSwap(address trader, uint256 amountIn, PoolKey memory poolKey, bytes memory hookData) internal {
367
+ deal(address(zoraToken), trader, amountIn);
368
+
369
+ address currencyOut = Currency.unwrap(poolKey.currency0) == address(zoraToken)
370
+ ? Currency.unwrap(poolKey.currency1)
371
+ : Currency.unwrap(poolKey.currency0);
372
+
373
+ (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputSingleSwapCommand(
374
+ address(zoraToken),
375
+ uint128(amountIn),
376
+ currencyOut,
377
+ 0,
378
+ poolKey,
379
+ hookData
380
+ );
381
+
382
+ vm.startPrank(trader);
383
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), uint128(amountIn), uint48(block.timestamp + 1 days));
384
+ router.execute(commands, inputs, block.timestamp + 1 days);
385
+ vm.stopPrank();
386
+ }
387
+
388
+ function _executeMultiHopSwap(address trader, uint256 amountIn, PoolKey[] memory poolKeys, bytes[] memory hookDatas) internal {
389
+ deal(address(zoraToken), trader, amountIn);
390
+
391
+ (bytes memory commands, bytes[] memory inputs) = UniV4SwapHelper.buildExactInputMultiSwapCommand(
392
+ address(zoraToken),
393
+ uint128(amountIn),
394
+ poolKeys,
395
+ 0,
396
+ hookDatas
397
+ );
398
+
399
+ vm.startPrank(trader);
400
+ UniV4SwapHelper.approveTokenWithPermit2(permit2, address(router), address(zoraToken), uint128(amountIn), uint48(block.timestamp + 1 days));
401
+ router.execute(commands, inputs, block.timestamp + 1 days);
402
+ vm.stopPrank();
403
+ }
404
+
405
+ function _makerNonce(address maker) internal view returns (uint256) {
406
+ return limitOrderBook.exposedMakerNonce(maker);
407
+ }
408
+
409
+ function _makerBalance(address maker, address coin) internal view returns (uint256) {
410
+ return limitOrderBook.balanceOf(maker, coin);
411
+ }
412
+
413
+ function _poolEpoch(bytes32 poolKeyHash) internal view returns (uint256) {
414
+ return limitOrderBook.exposedPoolEpoch(poolKeyHash);
415
+ }
416
+
417
+ function _orderIds(CreatedOrderLog[] memory created) internal pure returns (bytes32[] memory ids) {
418
+ ids = new bytes32[](created.length);
419
+ for (uint256 i; i < created.length; ++i) {
420
+ ids[i] = created[i].orderId;
421
+ }
422
+ }
423
+
424
+ function _sumOrderSizes(CreatedOrderLog[] memory created) internal pure returns (uint256 total) {
425
+ for (uint256 i; i < created.length; ++i) {
426
+ total += created[i].size;
427
+ }
428
+ }
429
+
430
+ function _orderCoin(PoolKey memory key, bool isCurrency0) internal pure returns (address) {
431
+ return LimitOrderCommon.getOrderCoin(key, isCurrency0);
432
+ }
433
+
434
+ function _assertEpochIncrement(bytes32 poolKeyHash, uint256 previousEpoch) internal view {
435
+ uint256 current = _poolEpoch(poolKeyHash);
436
+ assertGt(current, previousEpoch, "pool epoch should increment after fills");
437
+ }
438
+
439
+ function _tickWindow(CreatedOrderLog[] memory created, PoolKey memory key) internal pure returns (int24 startTick, int24 endTick) {
440
+ if (created.length == 0) {
441
+ return (0, 0);
442
+ }
443
+
444
+ int24 minTick = created[0].tick;
445
+ int24 maxTick = created[0].tick;
446
+ for (uint256 i = 1; i < created.length; ++i) {
447
+ if (created[i].tick < minTick) minTick = created[i].tick;
448
+ if (created[i].tick > maxTick) maxTick = created[i].tick;
449
+ }
450
+
451
+ if (created[0].isCurrency0) {
452
+ startTick = minTick - key.tickSpacing;
453
+ endTick = maxTick + key.tickSpacing;
454
+ } else {
455
+ startTick = maxTick + key.tickSpacing;
456
+ endTick = minTick - key.tickSpacing;
457
+ }
458
+ }
459
+
460
+ function _alignedTickForOrder(bool isCurrency0, int24 baseTick, int24 spacing, uint256 index) internal pure returns (int24) {
461
+ int24 offset = int24(int256(spacing) * int256(index + 1));
462
+ // For currency0 sell orders: place above current (contain currency0)
463
+ // For currency1 sell orders: place below current (contain currency1)
464
+ return isCurrency0 ? baseTick + offset : baseTick - offset;
465
+ }
466
+
467
+ function _buildDeterministicOrders(
468
+ PoolKey memory key,
469
+ bool isCurrency0,
470
+ uint256 rungCount,
471
+ uint256 orderSize
472
+ ) internal view returns (uint256[] memory sizes, int24[] memory ticks) {
473
+ sizes = new uint256[](rungCount);
474
+ ticks = new int24[](rungCount);
475
+
476
+ int24 baseTick = _alignedTick(_currentTick(key), key.tickSpacing);
477
+ for (uint256 i; i < rungCount; ++i) {
478
+ sizes[i] = orderSize;
479
+ ticks[i] = _alignedTickForOrder(isCurrency0, baseTick, key.tickSpacing, i);
480
+ }
481
+ }
482
+
483
+ function _assertOpenOrderState(address, address coin, bytes32 poolKeyHash, CreatedOrderLog[] memory orders, int24 tickSpacing) internal view {
484
+ uint256 orderCount = orders.length;
485
+
486
+ for (uint256 i; i < orderCount; ++i) {
487
+ bool seen;
488
+ for (uint256 j; j < i; ++j) {
489
+ if (orders[j].coin == orders[i].coin && orders[j].tick == orders[i].tick) {
490
+ seen = true;
491
+ break;
492
+ }
493
+ }
494
+ if (seen) continue;
495
+
496
+ uint256 tickCount;
497
+ uint256 tickSize;
498
+ for (uint256 k = i; k < orderCount; ++k) {
499
+ if (orders[k].coin == orders[i].coin && orders[k].tick == orders[i].tick) {
500
+ ++tickCount;
501
+ tickSize += orders[k].size;
502
+ }
503
+ }
504
+
505
+ QueueSnapshot memory tickQueue = _tickQueueSnapshot(poolKeyHash, coin, orders[i].tick);
506
+ assertEq(uint256(tickQueue.length), tickCount, "tick queue length mismatch");
507
+ assertEq(uint256(tickQueue.balance), tickSize, "tick queue balance mismatch");
508
+ assertTrue(_isTickInitialized(poolKeyHash, coin, orders[i].tick, tickSpacing), "tick bitmap missing");
509
+ }
510
+ }
511
+
512
+ function _setOrderCreatedEpoch(bytes32 orderId, uint32 newEpoch) internal {
513
+ bytes32 layoutSlot = LimitOrderStorage.STORAGE_SLOT;
514
+ bytes32 limitOrdersSlot = keccak256(abi.encode(uint256(0), layoutSlot));
515
+ bytes32 orderSlot = keccak256(abi.encode(orderId, limitOrdersSlot));
516
+ bytes32 metaSlot = bytes32(uint256(orderSlot) + 4); // Slot 4 contains packed metadata (tickLower, tickUpper, createdEpoch, status, isCurrency0, maker)
517
+
518
+ uint256 slotValue = uint256(vm.load(address(limitOrderBook), metaSlot));
519
+ uint256 mask = ~(uint256(0xffffffff) << 48);
520
+ slotValue = (slotValue & mask) | (uint256(newEpoch) << 48);
521
+ vm.store(address(limitOrderBook), metaSlot, bytes32(slotValue));
522
+ }
523
+
524
+ function _tickQueueSnapshot(bytes32 poolKeyHash, address coin, int24 tick) internal view returns (QueueSnapshot memory snapshot) {
525
+ LimitOrderTypes.Queue memory queue = limitOrderBook.exposedTickQueue(poolKeyHash, coin, tick);
526
+ snapshot.head = queue.head;
527
+ snapshot.tail = queue.tail;
528
+ snapshot.length = queue.length;
529
+ snapshot.balance = queue.balance;
530
+ }
531
+
532
+ function _bitmapWord(bytes32 poolKeyHash, address coin, int16 wordPos) internal view returns (uint256) {
533
+ return limitOrderBook.exposedTickBitmap(poolKeyHash, coin, wordPos);
534
+ }
535
+
536
+ function _topicAddress(bytes32 topic) internal pure returns (address) {
537
+ return address(uint160(uint256(topic)));
538
+ }
539
+
540
+ function _isTickInitialized(bytes32 poolKeyHash, address coin, int24 tick, int24 tickSpacing) internal view returns (bool) {
541
+ int24 compressed = tick / tickSpacing;
542
+ (int16 wordPos, uint8 bitPos) = TickBitmap.position(compressed);
543
+ uint256 word = _bitmapWord(poolKeyHash, coin, wordPos);
544
+ return (word & (1 << bitPos)) != 0;
545
+ }
546
+
547
+ function _decodeCreatedLogs(Vm.Log[] memory logs) internal pure returns (CreatedOrderLog[] memory created) {
548
+ uint256 count;
549
+ for (uint256 i; i < logs.length; ++i) {
550
+ if (logs[i].topics.length == 0) continue;
551
+ if (logs[i].topics[0] != LIMIT_ORDER_CREATED_TOPIC) continue;
552
+ ++count;
553
+ }
554
+
555
+ created = new CreatedOrderLog[](count);
556
+ uint256 idx;
557
+ for (uint256 i; i < logs.length; ++i) {
558
+ Vm.Log memory log = logs[i];
559
+ if (log.topics.length == 0 || log.topics[0] != LIMIT_ORDER_CREATED_TOPIC) continue;
560
+
561
+ (bytes32 poolKeyHash, bool isCurrency0, int24 tick, int24 currentTick, uint128 size, bytes32 orderId) = abi.decode(
562
+ log.data,
563
+ (bytes32, bool, int24, int24, uint128, bytes32)
564
+ );
565
+
566
+ CreatedOrderLog memory entry;
567
+ entry.maker = _topicAddress(log.topics[1]);
568
+ entry.coin = _topicAddress(log.topics[2]);
569
+ entry.poolKeyHash = poolKeyHash;
570
+ entry.isCurrency0 = isCurrency0;
571
+ entry.tick = tick;
572
+ entry.currentTick = currentTick;
573
+ entry.size = size;
574
+ entry.orderId = orderId;
575
+
576
+ created[idx] = entry;
577
+ ++idx;
578
+ }
579
+ }
580
+
581
+ function _decodeFilledLogs(Vm.Log[] memory logs) internal pure returns (FilledOrderLog[] memory fills) {
582
+ uint256 count;
583
+ for (uint256 i; i < logs.length; ++i) {
584
+ if (logs[i].topics.length == 0) continue;
585
+ if (logs[i].topics[0] != LIMIT_ORDER_FILLED_TOPIC) continue;
586
+ ++count;
587
+ }
588
+
589
+ fills = new FilledOrderLog[](count);
590
+ uint256 idx;
591
+ for (uint256 i; i < logs.length; ++i) {
592
+ Vm.Log memory log = logs[i];
593
+ if (log.topics.length == 0 || log.topics[0] != LIMIT_ORDER_FILLED_TOPIC) continue;
594
+
595
+ (
596
+ address coinOut,
597
+ uint128 amountIn,
598
+ uint128 amountOut,
599
+ address fillReferral,
600
+ uint128 fillReferralAmount,
601
+ bytes32 poolKeyHash,
602
+ int24 tick,
603
+ bytes32 orderId
604
+ ) = abi.decode(log.data, (address, uint128, uint128, address, uint128, bytes32, int24, bytes32));
605
+
606
+ FilledOrderLog memory entry;
607
+ entry.maker = _topicAddress(log.topics[1]);
608
+ entry.coinIn = _topicAddress(log.topics[2]);
609
+ entry.coinOut = coinOut;
610
+ entry.amountIn = amountIn;
611
+ entry.amountOut = amountOut;
612
+ entry.fillReferral = fillReferral;
613
+ entry.fillReferralAmount = fillReferralAmount;
614
+ entry.poolKeyHash = poolKeyHash;
615
+ entry.tick = tick;
616
+ entry.orderId = orderId;
617
+
618
+ fills[idx] = entry;
619
+ ++idx;
620
+ }
621
+ }
622
+
623
+ function _decodeUpdatedLogs(Vm.Log[] memory logs) internal pure returns (UpdatedOrderLog[] memory updates) {
624
+ uint256 count;
625
+ for (uint256 i; i < logs.length; ++i) {
626
+ if (logs[i].topics.length == 0) continue;
627
+ if (logs[i].topics[0] != LIMIT_ORDER_UPDATED_TOPIC) continue;
628
+ ++count;
629
+ }
630
+
631
+ updates = new UpdatedOrderLog[](count);
632
+ uint256 idx;
633
+ for (uint256 i; i < logs.length; ++i) {
634
+ Vm.Log memory log = logs[i];
635
+ if (log.topics.length == 0 || log.topics[0] != LIMIT_ORDER_UPDATED_TOPIC) continue;
636
+
637
+ (bytes32 poolKeyHash, bool isCurrency0, int24 tick, uint128 newSize, bytes32 orderId, bool isCancelled) = abi.decode(
638
+ log.data,
639
+ (bytes32, bool, int24, uint128, bytes32, bool)
640
+ );
641
+
642
+ updates[idx] = UpdatedOrderLog({
643
+ maker: address(uint160(uint256(log.topics[1]))),
644
+ coin: address(uint160(uint256(log.topics[2]))),
645
+ poolKeyHash: poolKeyHash,
646
+ isCurrency0: isCurrency0,
647
+ tick: tick,
648
+ newSize: newSize,
649
+ orderId: orderId,
650
+ isCancelled: isCancelled
651
+ });
652
+ ++idx;
653
+ }
654
+ }
655
+
656
+ function _decodeSwapExecutedLogs(Vm.Log[] memory logs) internal pure returns (SwapExecutedLog[] memory swaps) {
657
+ uint256 count;
658
+ for (uint256 i; i < logs.length; ++i) {
659
+ if (logs[i].topics.length == 0) continue;
660
+ if (logs[i].topics[0] != SWAP_WITH_LIMIT_ORDERS_EXECUTED_TOPIC) continue;
661
+ ++count;
662
+ }
663
+
664
+ swaps = new SwapExecutedLog[](count);
665
+ uint256 idx;
666
+ for (uint256 i; i < logs.length; ++i) {
667
+ Vm.Log memory log = logs[i];
668
+ if (log.topics.length == 0 || log.topics[0] != SWAP_WITH_LIMIT_ORDERS_EXECUTED_TOPIC) continue;
669
+
670
+ (PoolKey memory poolKey, int256 delta, int24 tickBefore, int24 tickAfter, CreatedOrder[] memory orders) = abi.decode(
671
+ log.data,
672
+ (PoolKey, int256, int24, int24, CreatedOrder[])
673
+ );
674
+
675
+ swaps[idx] = SwapExecutedLog({
676
+ sender: address(uint160(uint256(log.topics[1]))),
677
+ recipient: address(uint160(uint256(log.topics[2]))),
678
+ poolKey: poolKey,
679
+ delta: delta,
680
+ tickBefore: tickBefore,
681
+ tickAfter: tickAfter,
682
+ orders: orders
683
+ });
684
+ ++idx;
685
+ }
686
+ }
687
+
688
+ function _setPoolTick(PoolKey memory key, int24 newTick) internal {
689
+ bytes32 poolId = PoolId.unwrap(key.toId());
690
+ bytes32 slot0Slot = keccak256(abi.encodePacked(poolId, StateLibrary.POOLS_SLOT));
691
+ bytes32 slot0Value = vm.load(address(poolManager), slot0Slot);
692
+ uint256 data = uint256(slot0Value);
693
+
694
+ uint256 protocolFee = (data >> 184) & 0xFFFFFF;
695
+ uint256 lpFee = (data >> 208) & 0xFFFFFF;
696
+ uint256 upperBits = data & ~((uint256(1) << 232) - 1);
697
+
698
+ uint256 tickBits;
699
+ assembly {
700
+ tickBits := and(newTick, 0xffffff)
701
+ }
702
+
703
+ uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(newTick);
704
+
705
+ uint256 newData = upperBits;
706
+ newData |= uint256(sqrtPriceX96);
707
+ newData |= tickBits << 160;
708
+ newData |= protocolFee << 184;
709
+ newData |= lpFee << 208;
710
+
711
+ vm.store(address(poolManager), slot0Slot, bytes32(newData));
712
+ }
713
+
714
+ function _fillFromHook(PoolKey memory key, bool zeroForOne, int24 tickBefore, int24 tickAfter) internal {
715
+ vm.prank(address(hook));
716
+ limitOrderBook.fill(key, !zeroForOne, tickBefore, tickAfter, 10, address(0));
717
+ }
718
+
719
+ function _approveOrderBook(address owner, address coin, uint256 amount) internal {
720
+ vm.startPrank(owner);
721
+ if (coin != address(0)) {
722
+ IERC20(coin).approve(address(limitOrderBook), amount);
723
+ }
724
+ vm.stopPrank();
725
+ }
726
+
727
+ function _currentTick(PoolKey memory key) internal view returns (int24 tick) {
728
+ (, tick, , ) = StateLibrary.getSlot0(poolManager, key.toId());
729
+ }
730
+
731
+ function _alignedTick(int24 tick, int24 spacing) internal pure returns (int24) {
732
+ int24 remainder = tick % spacing;
733
+ if (remainder == 0) return tick;
734
+ if (tick >= 0) {
735
+ return tick - remainder;
736
+ } else {
737
+ return tick - (spacing + remainder);
738
+ }
739
+ }
740
+
741
+ function _movePriceBeyondTicks(CreatedOrderLog[] memory created) internal virtual {
742
+ if (created.length == 0) return;
743
+
744
+ address mover = makeAddr("price-mover");
745
+ // Pool has significant liquidity, need large swap to move tick past all orders
746
+ uint256 swapAmount = 50_000_000e18;
747
+
748
+ // Check if coin is currency0 or currency1 in the pool
749
+ PoolKey memory key = creatorCoin.getPoolKey();
750
+ bool coinIsCurrency0 = Currency.unwrap(key.currency0) == address(creatorCoin);
751
+ bool isCurrency0Order = created[0].isCurrency0;
752
+
753
+ // For currency0 orders: need tick UP (currentTick >= tickUpper)
754
+ // For currency1 orders: need tick DOWN (currentTick <= tickLower)
755
+ //
756
+ // When coin is currency1 (token1):
757
+ // - Sell coin → tick increases (add token1, price of token0 rises)
758
+ // - Buy coin → tick decreases
759
+ // When coin is currency0 (token0):
760
+ // - Sell coin → tick decreases (add token0, price of token0 drops)
761
+ // - Buy coin → tick increases
762
+
763
+ bool needTickUp = isCurrency0Order;
764
+ bool sellCoinIncreaseTick = !coinIsCurrency0; // selling token1 increases tick
765
+
766
+ if (needTickUp == sellCoinIncreaseTick) {
767
+ // Sell coin to move tick in required direction
768
+ deal(address(creatorCoin), mover, swapAmount);
769
+ _swapSomeCoinForCurrency(ICoin(address(creatorCoin)), address(zoraToken), uint128(swapAmount), mover);
770
+ } else {
771
+ // Buy coin to move tick in required direction
772
+ deal(address(zoraToken), mover, swapAmount);
773
+ _swapSomeCurrencyForCoin(ICoin(address(creatorCoin)), address(zoraToken), uint128(swapAmount), mover);
774
+ }
775
+ }
776
+
777
+ function _movePriceBeyondTicksWithAutoFillDisabled(CreatedOrderLog[] memory created) internal virtual {
778
+ uint256 previousMax = _disableAutoFill();
779
+ _movePriceBeyondTicks(created);
780
+ _restoreAutoFill(previousMax);
781
+ }
782
+
783
+ function _disableAutoFill() internal returns (uint256 previousMaxFillCount) {
784
+ previousMaxFillCount = limitOrderBook.getMaxFillCount();
785
+ vm.prank(users.factoryOwner);
786
+ limitOrderBook.setMaxFillCount(0);
787
+ }
788
+
789
+ function _restoreAutoFill(uint256 previousMaxFillCount) internal {
790
+ vm.prank(users.factoryOwner);
791
+ limitOrderBook.setMaxFillCount(previousMaxFillCount);
792
+ }
793
+ }