@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,222 @@
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
8
+ pragma solidity ^0.8.23;
9
+
10
+ import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
11
+ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
12
+ import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol";
13
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
14
+ import {ModifyLiquidityParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
15
+ import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
16
+ import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
17
+ import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
18
+ import {LiquidityAmounts} from "@zoralabs/coins/src/utils/uniswap/LiquidityAmounts.sol";
19
+
20
+ import {IDeployedCoinVersionLookup} from "@zoralabs/coins/src/interfaces/IDeployedCoinVersionLookup.sol";
21
+ import {LimitOrderTypes} from "./LimitOrderTypes.sol";
22
+ import {IHasSwapPath} from "@zoralabs/coins/src/interfaces/ICoin.sol";
23
+ import {UniV4SwapToCurrency} from "@zoralabs/coins/src/libs/UniV4SwapToCurrency.sol";
24
+
25
+ library LimitOrderLiquidity {
26
+ using CurrencyLibrary for Currency;
27
+
28
+ function liquidityForOrder(bool isCurrency0, uint256 size, int24 tickLower, int24 tickUpper) internal pure returns (uint128) {
29
+ uint160 sqrtPriceLower = TickMath.getSqrtPriceAtTick(tickLower);
30
+ uint160 sqrtPriceUpper = TickMath.getSqrtPriceAtTick(tickUpper);
31
+ return
32
+ isCurrency0
33
+ ? LiquidityAmounts.getLiquidityForAmount0(sqrtPriceLower, sqrtPriceUpper, size)
34
+ : LiquidityAmounts.getLiquidityForAmount1(sqrtPriceLower, sqrtPriceUpper, size);
35
+ }
36
+
37
+ function refundResidual(PoolKey memory key, bool isCurrency0, address maker, uint128 amount) internal {
38
+ if (amount == 0) {
39
+ return;
40
+ }
41
+ Currency currency = isCurrency0 ? key.currency0 : key.currency1;
42
+ currency.transfer(maker, amount);
43
+ }
44
+
45
+ function settleAfterCreate(IPoolManager poolManager, PoolKey memory key, bool isCurrency0) internal {
46
+ int256 delta0 = TransientStateLibrary.currencyDelta(poolManager, address(this), key.currency0);
47
+ int256 delta1 = TransientStateLibrary.currencyDelta(poolManager, address(this), key.currency1);
48
+
49
+ address payout0 = isCurrency0 ? address(0) : address(this);
50
+ address payout1 = isCurrency0 ? address(this) : address(0);
51
+
52
+ settleDeltas(poolManager, key, delta0, delta1, payout0, payout1);
53
+ }
54
+
55
+ function burnAndPayout(
56
+ IPoolManager poolManager,
57
+ PoolKey memory key,
58
+ LimitOrderTypes.LimitOrder storage order,
59
+ bytes32 orderId,
60
+ address feeRecipient,
61
+ address coinIn,
62
+ IDeployedCoinVersionLookup coinLookup
63
+ ) internal returns (Currency makerCoinOut, uint128 makerAmountOut, uint128 referralAmountOut) {
64
+ (BalanceDelta liqDelta, BalanceDelta feesDelta) = poolManager.modifyLiquidity(
65
+ key,
66
+ ModifyLiquidityParams({tickLower: order.tickLower, tickUpper: order.tickUpper, liquidityDelta: -int256(uint256(order.liquidity)), salt: orderId}),
67
+ ""
68
+ );
69
+
70
+ int128 liquidity0 = liqDelta.amount0();
71
+ int128 liquidity1 = liqDelta.amount1();
72
+ int128 fee0Initial = feesDelta.amount0();
73
+ int128 fee1Initial = feesDelta.amount1();
74
+
75
+ int128 makerShareLiquidity0 = feeRecipient == address(0) ? liquidity0 : liquidity0 - fee0Initial;
76
+ int128 makerShareLiquidity1 = feeRecipient == address(0) ? liquidity1 : liquidity1 - fee1Initial;
77
+ int128 referralShareLiquidity0 = feeRecipient == address(0) ? int128(0) : int128(fee0Initial);
78
+ int128 referralShareLiquidity1 = feeRecipient == address(0) ? int128(0) : int128(fee1Initial);
79
+
80
+ (bool usePath, IHasSwapPath.PayoutSwapPath memory payoutPath) = _resolvePayoutPath(coinIn, coinLookup);
81
+
82
+ (makerCoinOut, makerAmountOut) = _payoutRecipient(poolManager, key, order.maker, makerShareLiquidity0, makerShareLiquidity1, usePath, payoutPath);
83
+
84
+ if (referralShareLiquidity0 > 0 || referralShareLiquidity1 > 0) {
85
+ (, referralAmountOut) = _payoutRecipient(poolManager, key, feeRecipient, referralShareLiquidity0, referralShareLiquidity1, usePath, payoutPath);
86
+ }
87
+
88
+ _settleNegativeDeltas(poolManager, key, liquidity0 + fee0Initial, liquidity1 + fee1Initial);
89
+ }
90
+
91
+ function burnAndRefund(
92
+ IPoolManager poolManager,
93
+ PoolKey memory key,
94
+ int24 tickLower,
95
+ int24 tickUpper,
96
+ uint128 liquidity,
97
+ bytes32 salt,
98
+ address recipient,
99
+ bool isCurrency0
100
+ ) internal returns (uint128 amountOut) {
101
+ (int128 amount0, int128 amount1) = _burnLiquidity(poolManager, key, tickLower, tickUpper, liquidity, salt);
102
+
103
+ if (isCurrency0 && amount0 > 0) {
104
+ amountOut = uint128(amount0);
105
+ poolManager.take(key.currency0, recipient, amountOut);
106
+ } else if (!isCurrency0 && amount1 > 0) {
107
+ amountOut = uint128(amount1);
108
+ poolManager.take(key.currency1, recipient, amountOut);
109
+ }
110
+
111
+ _settleNegativeDeltas(poolManager, key, amount0, amount1);
112
+ }
113
+
114
+ function settleDeltas(IPoolManager poolManager, PoolKey memory key, int256 d0, int256 d1, address payout0, address payout1) internal {
115
+ if (d0 > 0 && payout0 != address(0)) {
116
+ poolManager.take(key.currency0, payout0, uint256(d0));
117
+ }
118
+ if (d0 < 0) {
119
+ poolManager.sync(key.currency0);
120
+ key.currency0.transfer(address(poolManager), uint256(uint256(-d0)));
121
+ poolManager.settle();
122
+ }
123
+
124
+ if (d1 > 0 && payout1 != address(0)) {
125
+ poolManager.take(key.currency1, payout1, uint256(d1));
126
+ }
127
+ if (d1 < 0) {
128
+ poolManager.sync(key.currency1);
129
+ key.currency1.transfer(address(poolManager), uint256(uint256(-d1)));
130
+ poolManager.settle();
131
+ }
132
+ }
133
+
134
+ function _burnLiquidity(
135
+ IPoolManager poolManager,
136
+ PoolKey memory key,
137
+ int24 tickLower,
138
+ int24 tickUpper,
139
+ uint128 liquidity,
140
+ bytes32 salt
141
+ ) private returns (int128 amount0, int128 amount1) {
142
+ (BalanceDelta delta, ) = poolManager.modifyLiquidity(
143
+ key,
144
+ ModifyLiquidityParams({tickLower: tickLower, tickUpper: tickUpper, liquidityDelta: -int256(uint256(liquidity)), salt: salt}),
145
+ ""
146
+ );
147
+ amount0 = delta.amount0();
148
+ amount1 = delta.amount1();
149
+ }
150
+
151
+ function _resolvePayoutPath(
152
+ address coinIn,
153
+ IDeployedCoinVersionLookup coinLookup
154
+ ) private view returns (bool hasPath, IHasSwapPath.PayoutSwapPath memory payoutPath) {
155
+ if (coinIn == address(0)) {
156
+ return (false, payoutPath);
157
+ }
158
+
159
+ try coinLookup.getVersionForDeployedCoin(coinIn) returns (uint8 version) {
160
+ if (version >= 4 && _supportsSwapPath(coinIn)) {
161
+ payoutPath = IHasSwapPath(coinIn).getPayoutSwapPath(coinLookup);
162
+ if (payoutPath.path.length > 0) {
163
+ return (true, payoutPath);
164
+ }
165
+ }
166
+ } catch {}
167
+
168
+ return (false, payoutPath);
169
+ }
170
+
171
+ function _supportsSwapPath(address coin) private view returns (bool) {
172
+ try IERC165(coin).supportsInterface(type(IHasSwapPath).interfaceId) returns (bool supported) {
173
+ return supported;
174
+ } catch {
175
+ return false;
176
+ }
177
+ }
178
+
179
+ function _settleNegativeDeltas(IPoolManager poolManager, PoolKey memory key, int128 amount0, int128 amount1) private {
180
+ int256 repay0 = amount0 < 0 ? int256(amount0) : int256(0);
181
+ int256 repay1 = amount1 < 0 ? int256(amount1) : int256(0);
182
+
183
+ if (repay0 != 0 || repay1 != 0) {
184
+ settleDeltas(poolManager, key, repay0, repay1, address(0), address(0));
185
+ }
186
+ }
187
+
188
+ function _payoutRecipient(
189
+ IPoolManager poolManager,
190
+ PoolKey memory key,
191
+ address recipient,
192
+ int128 amount0,
193
+ int128 amount1,
194
+ bool usePath,
195
+ IHasSwapPath.PayoutSwapPath memory payoutPath
196
+ ) private returns (Currency coinOut, uint128 amountOut) {
197
+ if (usePath) {
198
+ (coinOut, amountOut) = UniV4SwapToCurrency.swapToPath(poolManager, uint128(amount0), uint128(amount1), payoutPath.currencyIn, payoutPath.path);
199
+ poolManager.take(coinOut, recipient, amountOut);
200
+ } else {
201
+ (coinOut, amountOut) = _payCounterAsset(poolManager, key, amount0, amount1, recipient);
202
+ }
203
+ }
204
+
205
+ function _payCounterAsset(
206
+ IPoolManager poolManager,
207
+ PoolKey memory key,
208
+ int128 amount0,
209
+ int128 amount1,
210
+ address recipient
211
+ ) private returns (Currency coinOut, uint128 amountOut) {
212
+ if (amount0 > 0) {
213
+ coinOut = key.currency0;
214
+ amountOut = uint128(amount0);
215
+ poolManager.take(coinOut, recipient, amountOut);
216
+ } else if (amount1 > 0) {
217
+ coinOut = key.currency1;
218
+ amountOut = uint128(amount1);
219
+ poolManager.take(coinOut, recipient, amountOut);
220
+ }
221
+ }
222
+ }
@@ -0,0 +1,101 @@
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
8
+ pragma solidity ^0.8.23;
9
+
10
+ import {LimitOrderTypes} from "./LimitOrderTypes.sol";
11
+
12
+ library LimitOrderQueues {
13
+ using LimitOrderQueues for LimitOrderTypes.Queue;
14
+
15
+ // Slot offsets for doubly-linked list pointers in LimitOrder struct
16
+ uint256 private constant NEXT_SLOT = 0;
17
+ uint256 private constant PREV_SLOT = 1;
18
+
19
+ /// @notice Appends an order to the tail of a tick's order queue
20
+ function enqueue(LimitOrderTypes.Queue storage q, mapping(bytes32 => LimitOrderTypes.LimitOrder) storage orders, bytes32 id) internal {
21
+ bytes32 tail = q.tail;
22
+
23
+ if (tail == bytes32(0)) {
24
+ // Empty tick queue: new order becomes head
25
+ q.head = id;
26
+ } else {
27
+ // Link current tail to new order
28
+ _writePointer(orders, tail, NEXT_SLOT, id);
29
+ }
30
+
31
+ // Link new order back to old tail (or 0 if empty)
32
+ _writePointer(orders, id, PREV_SLOT, tail);
33
+ q.tail = id;
34
+
35
+ unchecked {
36
+ q.length++;
37
+ }
38
+ }
39
+
40
+ /// @notice Removes an order from a tick's queue and relinks its neighbors
41
+ /// @return nextId The next order in the queue (for iteration during fills)
42
+ function unlink(
43
+ LimitOrderTypes.Queue storage q,
44
+ mapping(bytes32 => LimitOrderTypes.LimitOrder) storage orders,
45
+ LimitOrderTypes.LimitOrder storage order
46
+ ) internal returns (bytes32 nextId) {
47
+ bytes32 prevId;
48
+ uint256 baseSlot;
49
+
50
+ // Read prev/next pointers directly from storage slots for gas efficiency
51
+ assembly ("memory-safe") {
52
+ // Get the base storage slot for this order struct
53
+ baseSlot := order.slot
54
+ // Load nextId from slot 0 (first field in struct)
55
+ nextId := sload(add(baseSlot, NEXT_SLOT))
56
+ // Load prevId from slot 1 (second field in struct)
57
+ prevId := sload(add(baseSlot, PREV_SLOT))
58
+ }
59
+
60
+ // Relink previous order (or update head if this was first)
61
+ if (prevId == bytes32(0)) {
62
+ q.head = nextId;
63
+ } else {
64
+ _writePointer(orders, prevId, NEXT_SLOT, nextId);
65
+ }
66
+
67
+ // Relink next order (or update tail if this was last)
68
+ if (nextId == bytes32(0)) {
69
+ q.tail = prevId;
70
+ } else {
71
+ _writePointer(orders, nextId, PREV_SLOT, prevId);
72
+ }
73
+
74
+ if (q.length > 0) {
75
+ unchecked {
76
+ q.length--;
77
+ }
78
+ }
79
+ }
80
+
81
+ /// @notice Clears the linked list pointers of an order after removal
82
+ function clearLinks(LimitOrderTypes.LimitOrder storage order) internal {
83
+ uint256 baseSlot;
84
+ assembly ("memory-safe") {
85
+ baseSlot := order.slot
86
+ sstore(add(baseSlot, NEXT_SLOT), 0)
87
+ sstore(add(baseSlot, PREV_SLOT), 0)
88
+ }
89
+ }
90
+
91
+ /// @notice Writes a pointer value to a specific slot offset in an order
92
+ function _writePointer(mapping(bytes32 => LimitOrderTypes.LimitOrder) storage orders, bytes32 id, uint256 slotOffset, bytes32 value) private {
93
+ assembly ("memory-safe") {
94
+ // Compute storage slot: keccak256(id . orders.slot) + slotOffset
95
+ mstore(0x00, id) // Store id at memory[0:32]
96
+ mstore(0x20, orders.slot) // Store mapping slot at memory[32:64]
97
+ let base := keccak256(0x00, 0x40)
98
+ sstore(add(base, slotOffset), value)
99
+ }
100
+ }
101
+ }
@@ -0,0 +1,34 @@
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
8
+ pragma solidity ^0.8.28;
9
+
10
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
11
+ import {LimitOrderTypes} from "./LimitOrderTypes.sol";
12
+
13
+ library LimitOrderStorage {
14
+ // keccak256("zora.limit.order.book.storage")
15
+ bytes32 internal constant STORAGE_SLOT = 0x98b43bb10ca7bc310641b07883d9e14c04b3983640df6b07dd1c99d10a3c6cec;
16
+
17
+ struct Layout {
18
+ uint256 maxFillCount;
19
+ mapping(bytes32 orderId => LimitOrderTypes.LimitOrder) limitOrders;
20
+ mapping(address maker => uint256 nonce) makerNonces;
21
+ mapping(bytes32 poolKeyHash => PoolKey) poolKeys;
22
+ mapping(bytes32 poolKeyHash => uint256 epoch) poolEpochs;
23
+ mapping(bytes32 poolKeyHash => mapping(address coin => mapping(int16 wordPosition => uint256 bitmap))) tickBitmaps;
24
+ mapping(bytes32 poolKeyHash => mapping(address coin => mapping(int24 tick => LimitOrderTypes.Queue))) tickQueues;
25
+ mapping(address maker => mapping(address coin => uint256 balance)) makerBalances;
26
+ }
27
+
28
+ function layout() internal pure returns (Layout storage l) {
29
+ bytes32 slot = STORAGE_SLOT;
30
+ assembly {
31
+ l.slot := slot
32
+ }
33
+ }
34
+ }
@@ -0,0 +1,41 @@
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
8
+ pragma solidity ^0.8.23;
9
+
10
+ interface LimitOrderTypes {
11
+ enum OrderStatus {
12
+ INACTIVE,
13
+ OPEN,
14
+ FILLED
15
+ }
16
+
17
+ struct LimitOrder {
18
+ // Linked-list pointers
19
+ bytes32 nextId;
20
+ bytes32 prevId;
21
+ // Pool binding
22
+ bytes32 poolKeyHash;
23
+ // Amounts (packed)
24
+ uint128 orderSize;
25
+ uint128 liquidity;
26
+ // Small fields + address (packed into one slot)
27
+ int24 tickLower;
28
+ int24 tickUpper;
29
+ uint32 createdEpoch;
30
+ OrderStatus status;
31
+ bool isCurrency0;
32
+ address maker;
33
+ }
34
+
35
+ struct Queue {
36
+ bytes32 head;
37
+ bytes32 tail;
38
+ uint128 length;
39
+ uint128 balance;
40
+ }
41
+ }
@@ -0,0 +1,100 @@
1
+ // SPDX-License-Identifier: ZORA-DELAYED-OSL-v1
2
+ // This software is licensed under the Zora Delayed Open Source License.
3
+ // Under this license, you may use, copy, modify, and distribute this software for
4
+ // non-commercial purposes only. Commercial use and competitive products are prohibited
5
+ // until the "Open Date" (3 years from first public distribution or earlier at Zora's discretion),
6
+ // at which point this software automatically becomes available under the MIT License.
7
+ // Full license terms available at: https://docs.zora.co/coins/license
8
+ pragma solidity ^0.8.28;
9
+
10
+ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
11
+ import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
12
+
13
+ import {LimitOrderStorage} from "./LimitOrderStorage.sol";
14
+ import {IZoraLimitOrderBook} from "../IZoraLimitOrderBook.sol";
15
+ import {LimitOrderTypes} from "./LimitOrderTypes.sol";
16
+ import {LimitOrderCommon} from "./LimitOrderCommon.sol";
17
+ import {LimitOrderLiquidity} from "./LimitOrderLiquidity.sol";
18
+
19
+ library LimitOrderWithdraw {
20
+ function handleWithdrawOrdersCallback(LimitOrderStorage.Layout storage state, IPoolManager poolManager, bytes memory payload) internal {
21
+ IZoraLimitOrderBook.WithdrawOrdersCallbackData memory data = abi.decode(payload, (IZoraLimitOrderBook.WithdrawOrdersCallbackData));
22
+
23
+ withdrawOrders(state, poolManager, data.maker, data.orderIds, data.coin, data.minAmountOut, data.recipient);
24
+ }
25
+
26
+ function withdrawOrders(
27
+ LimitOrderStorage.Layout storage state,
28
+ IPoolManager poolManager,
29
+ address maker,
30
+ bytes32[] memory orderIds,
31
+ address coin,
32
+ uint256 minAmountOut,
33
+ address recipient
34
+ ) internal {
35
+ require(recipient != address(0), IZoraLimitOrderBook.AddressZero());
36
+ uint256 orderCount = orderIds.length;
37
+ require(orderCount != 0, IZoraLimitOrderBook.InvalidOrder());
38
+
39
+ uint256 totalWithdrawn;
40
+
41
+ for (uint256 i; i < orderCount; ++i) {
42
+ bytes32 orderId = orderIds[i];
43
+ LimitOrderTypes.LimitOrder storage order = state.limitOrders[orderId];
44
+
45
+ require(order.maker != address(0), IZoraLimitOrderBook.InvalidOrder());
46
+ require(order.maker == maker, IZoraLimitOrderBook.OrderNotMaker());
47
+ require(order.status == LimitOrderTypes.OrderStatus.OPEN, IZoraLimitOrderBook.OrderClosed());
48
+
49
+ uint128 orderSize = order.orderSize; // Cache before cancellation
50
+ address currentCoin = _cancelOrder(state, poolManager, maker, orderId, order, recipient);
51
+
52
+ // Validate order coin matches expected coin
53
+ if (currentCoin != coin) {
54
+ revert IZoraLimitOrderBook.CoinMismatch(orderId, coin, currentCoin);
55
+ }
56
+
57
+ totalWithdrawn += orderSize;
58
+
59
+ // Early termination if threshold reached
60
+ if (minAmountOut > 0 && totalWithdrawn >= minAmountOut) {
61
+ break;
62
+ }
63
+ }
64
+
65
+ // Revert if threshold not reached
66
+ require(totalWithdrawn >= minAmountOut, IZoraLimitOrderBook.MinAmountNotReached(totalWithdrawn, minAmountOut));
67
+ }
68
+
69
+ function _cancelOrder(
70
+ LimitOrderStorage.Layout storage state,
71
+ IPoolManager poolManager,
72
+ address maker,
73
+ bytes32 orderId,
74
+ LimitOrderTypes.LimitOrder storage order,
75
+ address recipient
76
+ ) private returns (address coin) {
77
+ PoolKey memory key = state.poolKeys[order.poolKeyHash];
78
+ require(key.tickSpacing != 0, IZoraLimitOrderBook.InvalidOrder());
79
+
80
+ int24 orderTick = LimitOrderCommon.getOrderTick(order);
81
+ coin = LimitOrderCommon.getOrderCoin(key, order.isCurrency0);
82
+
83
+ LimitOrderTypes.Queue storage tickQueue = state.tickQueues[order.poolKeyHash][coin][orderTick];
84
+
85
+ // Cache values needed after state changes
86
+ int24 tickLower = order.tickLower;
87
+ int24 tickUpper = order.tickUpper;
88
+ uint128 liquidity = order.liquidity;
89
+ bool isCurrency0 = order.isCurrency0;
90
+
91
+ // Effects before interactions (CEI pattern)
92
+ order.status = LimitOrderTypes.OrderStatus.INACTIVE;
93
+ LimitOrderCommon.removeOrder(state, key, coin, tickQueue, order);
94
+
95
+ // External call after state is updated
96
+ LimitOrderLiquidity.burnAndRefund(poolManager, key, tickLower, tickUpper, liquidity, orderId, recipient, isCurrency0);
97
+
98
+ emit IZoraLimitOrderBook.LimitOrderUpdated(maker, coin, order.poolKeyHash, isCurrency0, orderTick, 0, orderId, true);
99
+ }
100
+ }
@@ -0,0 +1,41 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.28;
3
+
4
+ import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
5
+ import {SafeCast160} from "permit2/src/libraries/SafeCast160.sol";
6
+
7
+ /// @title Payments through Permit2
8
+ /// @notice Performs interactions with Permit2 to transfer tokens
9
+ /// @dev Based on Uniswap's universal-router Permit2Payments module
10
+ abstract contract Permit2Payments {
11
+ using SafeCast160 for uint256;
12
+
13
+ /// @notice The Permit2 contract address (immutable, same on all chains)
14
+ IAllowanceTransfer internal immutable PERMIT2;
15
+
16
+ error FromAddressIsNotOwner();
17
+
18
+ constructor(address permit2_) {
19
+ PERMIT2 = IAllowanceTransfer(permit2_);
20
+ }
21
+
22
+ /// @notice Performs a transferFrom on Permit2
23
+ /// @param token The token to transfer
24
+ /// @param from The address to transfer from
25
+ /// @param to The recipient of the transfer
26
+ /// @param amount The amount to transfer
27
+ function permit2TransferFrom(address token, address from, address to, uint160 amount) internal {
28
+ PERMIT2.transferFrom(from, to, amount, token);
29
+ }
30
+
31
+ /// @notice Performs a batch transferFrom on Permit2
32
+ /// @param batchDetails An array detailing each of the transfers that should occur
33
+ /// @param owner The address that should be the owner of all transfers
34
+ function permit2TransferFrom(IAllowanceTransfer.AllowanceTransferDetails[] calldata batchDetails, address owner) internal {
35
+ uint256 batchLength = batchDetails.length;
36
+ for (uint256 i = 0; i < batchLength; ++i) {
37
+ if (batchDetails[i].from != owner) revert FromAddressIsNotOwner();
38
+ }
39
+ PERMIT2.transferFrom(batchDetails);
40
+ }
41
+ }