@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
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "@zoralabs/limit-orders",
3
+ "version": "0.2.0",
4
+ "type": "module",
5
+ "main": "./dist/index.cjs",
6
+ "module": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "sideEffects": false,
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "default": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "dependencies": {
17
+ "@openzeppelin/contracts": "5.4.0",
18
+ "@openzeppelin/contracts-upgradeable": "5.4.0"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^20.1.2",
22
+ "@wagmi/cli": "^1.0.1",
23
+ "@solidity-parser/parser": "0.19.0",
24
+ "@uniswap/v4-core": "https://github.com/Uniswap/v4-core#a7cf038cd568801a79a9b4cf92cd5b52c95c8585",
25
+ "@uniswap/v4-periphery": "https://github.com/Uniswap/v4-periphery#eeb3eff28dd5f5f17aa94180fa3610ff59b0e1c8",
26
+ "@uniswap/universal-router": "https://github.com/Uniswap/universal-router#3663f6db6e2fe121753cd2d899699c2dc75dca86",
27
+ "@uniswap/permit2": "https://github.com/Uniswap/permit2#cc56ad0f3439c502c246fc5cfcc3db92bb8b7219",
28
+ "ds-test": "https://github.com/dapphub/ds-test#cd98eff28324bfac652e63a239a60632a761790b",
29
+ "forge-std": "https://github.com/foundry-rs/forge-std#v1.9.1",
30
+ "prettier": "^3.0.3",
31
+ "prettier-plugin-solidity": "^1.4.1",
32
+ "solady": "0.0.132",
33
+ "solmate": "6.8.0",
34
+ "tsup": "^7.2.0",
35
+ "tsx": "^3.13.0",
36
+ "typescript": "^5.2.2",
37
+ "viem": "^2.21.18",
38
+ "@zoralabs/shared-contracts": "^0.0.5",
39
+ "@zoralabs/shared-scripts": "^0.0.0",
40
+ "@zoralabs/coins": "^2.4.1",
41
+ "@zoralabs/tsconfig": "^0.0.1"
42
+ },
43
+ "scripts": {
44
+ "build": "forge build",
45
+ "build:contracts:minimal": "forge build src/ --no-metadata",
46
+ "build:js": "pnpm run wagmi:generate && pnpm run copy-abis && pnpm run prettier:write && tsup",
47
+ "build:sizes": "forge build src/ --sizes",
48
+ "copy-abis": "pnpm exec bundle-abis",
49
+ "coverage": "forge coverage --report lcov --ir-minimum --no-match-coverage '(test/)'",
50
+ "prettier:check": "prettier --check 'src/**/*.sol' 'test/**/*.sol'",
51
+ "prettier:write": "prettier --write 'src/**/*.sol' 'test/**/*.sol'",
52
+ "test": "forge test -vv",
53
+ "test-gas": "forge test --gas-report",
54
+ "update-contract-version": "pnpm exec update-contract-version",
55
+ "wagmi:generate": "pnpm run build:contracts:minimal && wagmi generate && pnpm exec rename-generated-abi-casing ./package/wagmiGenerated.ts"
56
+ }
57
+ }
package/remappings.txt ADDED
@@ -0,0 +1,11 @@
1
+ ds-test/=node_modules/ds-test/src/
2
+ forge-std/=node_modules/forge-std/src/
3
+ @openzeppelin/=node_modules/@openzeppelin/
4
+ @zoralabs/shared-contracts/=node_modules/@zoralabs/shared-contracts/src/
5
+ solady/=node_modules/solady/src/
6
+ @uniswap/v4-core/=node_modules/@uniswap/v4-core/
7
+ @uniswap/v4-periphery/=node_modules/@uniswap/v4-periphery/
8
+ permit2/src/=node_modules/@uniswap/permit2/src/
9
+ @uniswap/universal-router/contracts/=node_modules/@uniswap/universal-router/contracts/
10
+ solmate/=node_modules/solmate/src/
11
+ @zoralabs/coins/=node_modules/@zoralabs/coins/
@@ -0,0 +1,195 @@
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 {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
11
+ import {IZoraLimitOrderBookCoinsInterface} from "@zoralabs/coins/src/interfaces/IZoraLimitOrderBookCoinsInterface.sol";
12
+
13
+ interface IZoraLimitOrderBook is IZoraLimitOrderBookCoinsInterface {
14
+ struct OrderBatch {
15
+ PoolKey key;
16
+ bool isCurrency0;
17
+ bytes32[] orderIds;
18
+ }
19
+
20
+ /// @dev Callback ids for the V4 pool manager
21
+ enum CallbackId {
22
+ CREATE,
23
+ FILL,
24
+ WITHDRAW_ORDERS
25
+ }
26
+
27
+ /// @dev Data echoed back to hooks when create flows resolve
28
+ struct CreateCallbackData {
29
+ PoolKey key;
30
+ bool isCurrency0;
31
+ uint256[] orderSizes;
32
+ int24[] orderTicks;
33
+ address maker;
34
+ }
35
+
36
+ /// @dev Data forwarded when the pool manager triggers a fill.
37
+ struct FillCallbackData {
38
+ PoolKey poolKey;
39
+ bool isCurrency0;
40
+ int24 startTick;
41
+ int24 endTick;
42
+ uint256 maxFillCount;
43
+ address fillReferral;
44
+ bytes32[] orderIds;
45
+ }
46
+
47
+ /// @dev Data used to withdraw from a maker's individual orders
48
+ struct WithdrawOrdersCallbackData {
49
+ address maker;
50
+ bytes32[] orderIds;
51
+ address coin;
52
+ uint256 minAmountOut;
53
+ address recipient;
54
+ }
55
+
56
+ /// @notice Emitted when a new order joins a tick queue.
57
+ event LimitOrderCreated(
58
+ address indexed maker,
59
+ address indexed coin,
60
+ bytes32 poolKeyHash,
61
+ bool isCurrency0,
62
+ int24 orderTick, // The tick at which the limit order is placed
63
+ int24 currentTick, // The current tick of the pool when the order was created
64
+ uint128 orderSize,
65
+ bytes32 orderId
66
+ );
67
+
68
+ /// @notice Emitted when an order is amended or marked inactive.
69
+ event LimitOrderUpdated(
70
+ address indexed maker,
71
+ address indexed coin,
72
+ bytes32 poolKeyHash,
73
+ bool isCurrency0,
74
+ int24 tick,
75
+ uint128 orderSize,
76
+ bytes32 orderId,
77
+ bool isCancelled
78
+ );
79
+
80
+ /// @notice Emitted when an order is filled and removed from the book.
81
+ event LimitOrderFilled(
82
+ address indexed maker,
83
+ address indexed coinIn,
84
+ address coinOut,
85
+ uint128 amountIn,
86
+ uint128 amountOut,
87
+ address fillReferral,
88
+ uint128 fillReferralAmount,
89
+ bytes32 poolKeyHash,
90
+ int24 tick,
91
+ bytes32 orderId
92
+ );
93
+
94
+ /// @notice Emitted when a maker's aggregate balance for a coin changes.
95
+ event MakerBalanceUpdated(address indexed maker, address indexed coin, uint256 newBalance);
96
+
97
+ /// @notice Caller must be a registered Zora hook.
98
+ error OnlyZoraHook();
99
+ /// @notice Caller must be the configured pool manager.
100
+ error NotPoolManager();
101
+ /// @notice Input arrays must have equal length.
102
+ error ArrayLengthMismatch();
103
+ /// @notice Orders must specify a non-zero size.
104
+ error ZeroOrderSize();
105
+ /// @notice Maker address cannot be zero.
106
+ error ZeroMaker();
107
+ /// @notice Supplied ETH must match expected amount.
108
+ error NativeValueMismatch();
109
+ /// @notice Forwarded funds from hook were insufficient.
110
+ error InsufficientForwardedFunds();
111
+ /// @notice Transfer in of funds failed or was insufficient.
112
+ error InsufficientTransferFunds();
113
+ /// @notice Fill requests must cap the number of orders processed.
114
+ error MaxFillCountCannotBeZero();
115
+ /// @notice Referenced pool key hash is unknown.
116
+ error InvalidPoolKey();
117
+ /// @notice Tick range inputs were invalid or misaligned.
118
+ error InvalidFillWindow(int24 startTick, int24 endTick, bool isCurrency0);
119
+ /// @notice Address argument was zero.
120
+ error AddressZero();
121
+ /// @notice Order id was not found.
122
+ error InvalidOrder();
123
+ /// @notice Operation attempted by non-maker.
124
+ error OrderNotMaker();
125
+ /// @notice Order is no longer open.
126
+ error OrderClosed();
127
+ /// @notice Callback realized zero fills when one was expected.
128
+ error ZeroRealizedOrder();
129
+ /// @notice Unlock callback id was not recognized.
130
+ error UnknownCallback();
131
+ /// @notice Router caller failed to expose the original message sender.
132
+ error RouterMsgSenderInvalid();
133
+ /// @notice Non-hook caller attempted to fill orders while pool is unlocked.
134
+ error UnlockedFillNotAllowed();
135
+ /// @notice Withdrawal did not reach minimum amount threshold.
136
+ error MinAmountNotReached(uint256 withdrawn, uint256 minAmountOut);
137
+ /// @notice Order coin does not match expected coin for batch withdrawal.
138
+ error CoinMismatch(bytes32 orderId, address expectedCoin, address actualCoin);
139
+
140
+ /// @notice Creates limit orders, pulling funds from msg.sender when pool manager is locked, or using funds already in manager when unlocked.
141
+ /// @dev This function is access-controlled via OpenZeppelin's AccessManager. The caller must have the appropriate role
142
+ /// as configured in the AccessManager contract. Initially, this can be set to PUBLIC_ROLE to allow anyone to create orders,
143
+ /// or it can be restricted to specific addresses/roles for permissioned operation.
144
+ /// @param key Pool key specifying currency pair and tick spacing.
145
+ /// @param isCurrency0 Whether the orders are denominated in currency0.
146
+ /// @param orderSizes Order liquidity sizes.
147
+ /// @param orderTicks Corresponding ticks.
148
+ /// @param maker Address of the maker who will own the orders.
149
+ /// @return orderIds Deterministic order identifiers.
150
+ function create(
151
+ PoolKey memory key,
152
+ bool isCurrency0,
153
+ uint256[] memory orderSizes,
154
+ int24[] memory orderTicks,
155
+ address maker
156
+ ) external payable returns (bytes32[] memory orderIds);
157
+
158
+ /// @notice Fills limit orders within a tick window.
159
+ /// @param key Pool key whose orders should be processed.
160
+ /// @param isCurrency0 Whether currency0 orders are targeted; otherwise currency1.
161
+ /// @param startTick Inclusive starting tick. Use `-type(int24).max` for the default lower bound.
162
+ /// @param endTick Inclusive ending tick. Use `type(int24).max` for the default upper bound.
163
+ /// @param maxFillCount Maximum orders to fill in this pass.
164
+ /// @param fillReferral Address to receive accrued LP fees; use address(0) to give fees to maker.
165
+ function fill(PoolKey calldata key, bool isCurrency0, int24 startTick, int24 endTick, uint256 maxFillCount, address fillReferral) external;
166
+
167
+ /// @notice Fills explicit order ids grouped by pool.
168
+ /// @param batches Array of per-pool batches containing order ids to process.
169
+ /// @param fillReferral Address to receive accrued LP fees; use address(0) to give fees to maker.
170
+ function fill(OrderBatch[] calldata batches, address fillReferral) external;
171
+
172
+ /// @notice Cancels orders and withdraws resulting funds to a recipient.
173
+ /// @dev Orders are cancelled sequentially until minAmountOut is reached.
174
+ /// If minAmountOut is 0, all provided orders are cancelled.
175
+ /// Reverts if total withdrawn is less than minAmountOut.
176
+ /// @param orderIds Order identifiers to cancel.
177
+ /// @param coin The coin address that all orders must match (use address(0) for native ETH).
178
+ /// @param minAmountOut Minimum amount to withdraw (0 = cancel all provided orders).
179
+ /// @param recipient Destination for returned assets.
180
+ function withdraw(bytes32[] calldata orderIds, address coin, uint256 minAmountOut, address recipient) external;
181
+
182
+ /// @notice Returns the aggregate balance of open orders for a maker/coin pair.
183
+ /// @param maker Address of the maker.
184
+ /// @param coin Address of the coin (use address(0) for native ETH).
185
+ /// @return balance Total value of open orders.
186
+ function balanceOf(address maker, address coin) external view returns (uint256 balance);
187
+
188
+ function getMaxFillCount() external view returns (uint256);
189
+
190
+ /// @notice Sets the maximum number of orders that can be filled in a single fill operation.
191
+ /// @dev This function is access-controlled via OpenZeppelin's AccessManager. The caller must have the appropriate role
192
+ /// as configured in the AccessManager contract.
193
+ /// @param maxFillCount The new maximum fill count value.
194
+ function setMaxFillCount(uint256 maxFillCount) external;
195
+ }
@@ -0,0 +1,220 @@
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
+ import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
13
+ import {SimpleAccessManaged} from "./access/SimpleAccessManaged.sol";
14
+
15
+ import {IDeployedCoinVersionLookup} from "@zoralabs/coins/src/interfaces/IDeployedCoinVersionLookup.sol";
16
+ import {IZoraHookRegistry} from "@zoralabs/coins/src/interfaces/IZoraHookRegistry.sol";
17
+ import {IZoraLimitOrderBook} from "./IZoraLimitOrderBook.sol";
18
+ import {LimitOrderStorage} from "./libs/LimitOrderStorage.sol";
19
+ import {LimitOrderCreate} from "./libs/LimitOrderCreate.sol";
20
+ import {LimitOrderFill} from "./libs/LimitOrderFill.sol";
21
+ import {LimitOrderWithdraw} from "./libs/LimitOrderWithdraw.sol";
22
+ import {LimitOrderTypes} from "./libs/LimitOrderTypes.sol";
23
+
24
+ contract ZoraLimitOrderBook is IZoraLimitOrderBook, SimpleAccessManaged {
25
+ IPoolManager public immutable poolManager;
26
+ IDeployedCoinVersionLookup public immutable zoraCoinVersionLookup;
27
+ IZoraHookRegistry public immutable zoraHookRegistry;
28
+
29
+ constructor(address poolManager_, address zoraCoinVersionLookup_, address zoraHookRegistry_, address authority_) SimpleAccessManaged(authority_) {
30
+ poolManager = IPoolManager(poolManager_);
31
+ zoraCoinVersionLookup = IDeployedCoinVersionLookup(zoraCoinVersionLookup_);
32
+ zoraHookRegistry = IZoraHookRegistry(zoraHookRegistry_);
33
+ }
34
+
35
+ function tickQueueBalance(bytes32 poolKeyHash, address coin, int24 tick) internal view returns (uint256) {
36
+ return LimitOrderStorage.layout().tickQueues[poolKeyHash][coin][tick].balance;
37
+ }
38
+
39
+ /// @inheritdoc IZoraLimitOrderBook
40
+ function balanceOf(address maker, address coin) external view override returns (uint256) {
41
+ return LimitOrderStorage.layout().makerBalances[maker][coin];
42
+ }
43
+
44
+ function getOrder(bytes32 id) internal view returns (LimitOrderTypes.LimitOrder memory) {
45
+ return LimitOrderStorage.layout().limitOrders[id];
46
+ }
47
+
48
+ function getTickQueue(bytes32 poolKeyHash, address coin, int24 tick) internal view returns (LimitOrderTypes.Queue memory) {
49
+ return LimitOrderStorage.layout().tickQueues[poolKeyHash][coin][tick];
50
+ }
51
+
52
+ function getPoolKey(bytes32 poolKeyHash) internal view returns (PoolKey memory) {
53
+ return LimitOrderStorage.layout().poolKeys[poolKeyHash];
54
+ }
55
+
56
+ function getPoolEpoch(bytes32 poolKeyHash) internal view returns (uint256) {
57
+ return LimitOrderStorage.layout().poolEpochs[poolKeyHash];
58
+ }
59
+
60
+ function getMakerNonce(address maker) internal view returns (uint256) {
61
+ return LimitOrderStorage.layout().makerNonces[maker];
62
+ }
63
+
64
+ function getOrderId(bytes32 poolKeyHash, address coin, int24 tick, address maker, uint256 nonce) internal pure returns (bytes32) {
65
+ return keccak256(abi.encodePacked(poolKeyHash, coin, tick, maker, nonce));
66
+ }
67
+
68
+ /// @inheritdoc IZoraLimitOrderBook
69
+ function create(
70
+ PoolKey memory key,
71
+ bool isCurrency0,
72
+ uint256[] memory orderSizes,
73
+ int24[] memory orderTicks,
74
+ address maker
75
+ ) external payable override returns (bytes32[] memory) {
76
+ _checkCanCall(this.create.selector);
77
+ return LimitOrderCreate.create(LimitOrderStorage.layout(), poolManager, key, isCurrency0, orderSizes, orderTicks, maker);
78
+ }
79
+
80
+ /// @inheritdoc IZoraLimitOrderBook
81
+ function fill(PoolKey calldata key, bool isCurrency0, int24 startTick, int24 endTick, uint256 maxFillCount, address fillReferral) external override {
82
+ if (maxFillCount == 0) {
83
+ maxFillCount = getMaxFillCount();
84
+ }
85
+
86
+ bool isUnlocked = TransientStateLibrary.isUnlocked(poolManager);
87
+
88
+ // Only known Zora hooks can fill while unlocked
89
+ if (isUnlocked) {
90
+ require(zoraHookRegistry.isRegisteredHook(msg.sender), UnlockedFillNotAllowed());
91
+ }
92
+
93
+ LimitOrderStorage.Layout storage state = LimitOrderStorage.layout();
94
+ LimitOrderFill.Context memory ctx = _fillContext();
95
+
96
+ (PoolKey memory canonicalKey, int24 resolvedStart, int24 resolvedEnd) = LimitOrderFill.validateTickRange(
97
+ state,
98
+ ctx,
99
+ key,
100
+ isCurrency0,
101
+ startTick,
102
+ endTick
103
+ );
104
+
105
+ IZoraLimitOrderBook.FillCallbackData memory fillData = _fillData(
106
+ canonicalKey,
107
+ isCurrency0,
108
+ resolvedStart,
109
+ resolvedEnd,
110
+ maxFillCount,
111
+ fillReferral,
112
+ new bytes32[](0)
113
+ );
114
+
115
+ if (isUnlocked) {
116
+ // fill while already unlocked
117
+ LimitOrderFill.executeFill(state, ctx, fillData);
118
+ return;
119
+ }
120
+
121
+ // unlock and fill
122
+ _unlock(IZoraLimitOrderBook.CallbackId.FILL, abi.encode(fillData));
123
+ }
124
+
125
+ function fill(OrderBatch[] calldata batches, address fillReferral) external override {
126
+ uint256 length = batches.length;
127
+ for (uint256 i = 0; i < length; ++i) {
128
+ OrderBatch calldata batch = batches[i];
129
+
130
+ if (batch.orderIds.length != 0) {
131
+ bytes32[] memory orderIds = batch.orderIds;
132
+ _unlock(
133
+ IZoraLimitOrderBook.CallbackId.FILL,
134
+ abi.encode(_fillData(batch.key, batch.isCurrency0, 0, 0, orderIds.length, fillReferral, orderIds))
135
+ );
136
+ }
137
+ }
138
+ }
139
+
140
+ /// @inheritdoc IZoraLimitOrderBook
141
+ function withdraw(bytes32[] calldata orderIds, address coin, uint256 minAmountOut, address recipient) external override {
142
+ bytes32[] memory orderIdsCopy = orderIds;
143
+ _unlock(CallbackId.WITHDRAW_ORDERS, _encodeWithdrawOrders(msg.sender, orderIdsCopy, coin, minAmountOut, recipient));
144
+ }
145
+
146
+ /// @notice Processes pool-manager unlock callbacks and routes them to the correct handler.
147
+ /// @param data ABI encoded callback identifier and callback data.
148
+ /// @return result Optional return data for create flows.
149
+ function unlockCallback(bytes calldata data) external returns (bytes memory) {
150
+ require(msg.sender == address(poolManager), NotPoolManager());
151
+
152
+ (CallbackId callbackId, bytes memory callbackData) = abi.decode(data, (CallbackId, bytes));
153
+ LimitOrderStorage.Layout storage state = LimitOrderStorage.layout();
154
+
155
+ if (callbackId == CallbackId.CREATE) {
156
+ return LimitOrderCreate.handleCreateCallback(state, poolManager, callbackData);
157
+ }
158
+
159
+ if (callbackId == CallbackId.FILL) {
160
+ LimitOrderFill.handleFillCallback(state, _fillContext(), callbackData);
161
+ } else if (callbackId == CallbackId.WITHDRAW_ORDERS) {
162
+ LimitOrderWithdraw.handleWithdrawOrdersCallback(state, poolManager, callbackData);
163
+ } else {
164
+ revert UnknownCallback();
165
+ }
166
+
167
+ return bytes("");
168
+ }
169
+
170
+ function getMaxFillCount() public view returns (uint256) {
171
+ return LimitOrderStorage.layout().maxFillCount;
172
+ }
173
+
174
+ function setMaxFillCount(uint256 maxFillCount) external {
175
+ _checkCanCall(this.setMaxFillCount.selector);
176
+
177
+ LimitOrderStorage.layout().maxFillCount = maxFillCount;
178
+ }
179
+
180
+ receive() external payable {
181
+ require(msg.sender == address(poolManager), NotPoolManager());
182
+ }
183
+
184
+ function _fillContext() private view returns (LimitOrderFill.Context memory ctx) {
185
+ ctx.poolManager = poolManager;
186
+ ctx.versionLookup = zoraCoinVersionLookup;
187
+ }
188
+
189
+ function _fillData(
190
+ PoolKey memory key,
191
+ bool isCurrency0,
192
+ int24 startTick,
193
+ int24 endTick,
194
+ uint256 maxFillCount,
195
+ address fillReferral,
196
+ bytes32[] memory orderIds
197
+ ) private pure returns (IZoraLimitOrderBook.FillCallbackData memory data) {
198
+ data.poolKey = key;
199
+ data.isCurrency0 = isCurrency0;
200
+ data.startTick = startTick;
201
+ data.endTick = endTick;
202
+ data.maxFillCount = maxFillCount;
203
+ data.fillReferral = fillReferral;
204
+ data.orderIds = orderIds;
205
+ }
206
+
207
+ function _encodeWithdrawOrders(
208
+ address maker,
209
+ bytes32[] memory orderIds,
210
+ address coin,
211
+ uint256 minAmountOut,
212
+ address recipient
213
+ ) private pure returns (bytes memory) {
214
+ return abi.encode(WithdrawOrdersCallbackData({maker: maker, orderIds: orderIds, coin: coin, minAmountOut: minAmountOut, recipient: recipient}));
215
+ }
216
+
217
+ function _unlock(CallbackId callbackId, bytes memory payload) private {
218
+ poolManager.unlock(abi.encode(callbackId, payload));
219
+ }
220
+ }
@@ -0,0 +1,76 @@
1
+ // SPDX-License-Identifier: MIT
2
+ pragma solidity ^0.8.20;
3
+
4
+ import {IAuthority} from "@openzeppelin/contracts/access/manager/IAuthority.sol";
5
+
6
+ /**
7
+ * @dev This contract module makes a contract "access managed", allowing an authority
8
+ * contract to control access to specific functions.
9
+ *
10
+ * This is a simplified version of OpenZeppelin's AccessManaged that removes time-based
11
+ * access control features (delays, scheduled operations) and only supports immediate
12
+ * access checks.
13
+ *
14
+ * The authority is set during construction and can only be changed by the current authority.
15
+ */
16
+ abstract contract SimpleAccessManaged {
17
+ /// @dev Thrown when an unauthorized caller attempts to access a restricted function
18
+ error AccessManagedUnauthorized();
19
+
20
+ /// @dev Thrown when trying to set an invalid authority (e.g., not a contract)
21
+ error AccessManagedInvalidAuthority(address authority);
22
+
23
+ address private _authority;
24
+
25
+ /// @dev Emitted when the authority is updated
26
+ event AuthorityUpdated(address authority);
27
+
28
+ /**
29
+ * @dev Initializes the contract with an initial authority.
30
+ * @param initialAuthority The address of the authority contract
31
+ */
32
+ constructor(address initialAuthority) {
33
+ _setAuthority(initialAuthority);
34
+ }
35
+
36
+ /**
37
+ * @dev Returns the current authority address.
38
+ * @return The address of the authority contract
39
+ */
40
+ function authority() public view virtual returns (address) {
41
+ return _authority;
42
+ }
43
+
44
+ /**
45
+ * @dev Transfers control of the contract to a new authority.
46
+ * Can only be called by the current authority.
47
+ * @param newAuthority The address of the new authority contract
48
+ */
49
+ function setAuthority(address newAuthority) public virtual {
50
+ if (msg.sender != authority()) {
51
+ revert AccessManagedUnauthorized();
52
+ }
53
+ if (newAuthority.code.length == 0) {
54
+ revert AccessManagedInvalidAuthority(newAuthority);
55
+ }
56
+ _setAuthority(newAuthority);
57
+ }
58
+
59
+ /**
60
+ * @dev Internal function to set the authority without access checks.
61
+ * @param newAuthority The address of the new authority contract
62
+ */
63
+ function _setAuthority(address newAuthority) internal virtual {
64
+ _authority = newAuthority;
65
+ emit AuthorityUpdated(newAuthority);
66
+ }
67
+
68
+ /**
69
+ * @dev Internal function to check if a caller can execute a function.
70
+ * Reverts with AccessManagedUnauthorized if the caller is not authorized.
71
+ * @param selector The function selector being called
72
+ */
73
+ function _checkCanCall(bytes4 selector) internal view virtual {
74
+ require(IAuthority(authority()).canCall(msg.sender, address(this), selector), AccessManagedUnauthorized());
75
+ }
76
+ }