@zoralabs/limit-orders 0.2.0 → 0.2.2
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.
- package/.turbo/turbo-build$colon$js.log +50 -49
- package/CHANGELOG.md +73 -0
- package/abis/ISetLimitOrderConfig.json +27 -0
- package/abis/IWETH.json +118 -0
- package/abis/IZoraLimitOrderBook.json +5 -0
- package/abis/LimitOrderLiquidity.json +7 -0
- package/abis/LimitOrderViews.json +62 -0
- package/abis/{SimpleAccessManaged.json → Ownable.json} +29 -10
- package/abis/Ownable2Step.json +115 -0
- package/abis/PermittedCallers.json +181 -0
- package/abis/SwapWithLimitOrders.json +134 -14
- package/abis/ZoraLimitOrderBook.json +187 -35
- package/cache/solidity-files-cache.json +1 -1
- package/dist/index.cjs +219 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +219 -34
- package/dist/index.js.map +1 -1
- package/dist/wagmiGenerated.d.ts +254 -41
- package/dist/wagmiGenerated.d.ts.map +1 -1
- package/out/BalanceDelta.sol/BalanceDeltaLibrary.json +1 -1
- package/out/BeforeSwapDelta.sol/BeforeSwapDeltaLibrary.json +1 -1
- package/out/BitMath.sol/BitMath.json +1 -1
- package/out/BytesLib.sol/BytesLib.json +1 -1
- package/out/CoinCommon.sol/CoinCommon.json +1 -1
- package/out/CoinConfigurationVersions.sol/CoinConfigurationVersions.json +1 -1
- package/out/CoinConstants.sol/CoinConstants.json +1 -1
- package/out/Context.sol/Context.json +1 -1
- package/out/Currency.sol/CurrencyLibrary.json +1 -1
- package/out/CurrencyReserves.sol/CurrencyReserves.json +1 -1
- package/out/CustomRevert.sol/CustomRevert.json +1 -1
- package/out/DopplerMath.sol/DopplerMath.json +1 -1
- package/out/FixedPoint128.sol/FixedPoint128.json +1 -1
- package/out/FixedPoint96.sol/FixedPoint96.json +1 -1
- package/out/FullMath.sol/FullMath.json +1 -1
- package/out/IAllowanceTransfer.sol/IAllowanceTransfer.json +1 -1
- package/out/ICoin.sol/ICoin.json +1 -1
- package/out/ICoin.sol/IHasCoinType.json +1 -1
- package/out/ICoin.sol/IHasPoolKey.json +1 -1
- package/out/ICoin.sol/IHasSwapPath.json +1 -1
- package/out/ICoin.sol/IHasTotalSupplyForPositions.json +1 -1
- package/out/IDeployedCoinVersionLookup.sol/IDeployedCoinVersionLookup.json +1 -1
- package/out/IDopplerErrors.sol/IDopplerErrors.json +1 -1
- package/out/IEIP712.sol/IEIP712.json +1 -1
- package/out/IERC1363.sol/IERC1363.json +1 -1
- package/out/IERC165.sol/IERC165.json +1 -1
- package/out/IERC20.sol/IERC20.json +1 -1
- package/out/IERC20Minimal.sol/IERC20Minimal.json +1 -1
- package/out/IERC6909Claims.sol/IERC6909Claims.json +1 -1
- package/out/IERC7572.sol/IERC7572.json +1 -1
- package/out/IExtsload.sol/IExtsload.json +1 -1
- package/out/IExttload.sol/IExttload.json +1 -1
- package/out/IHasRewardsRecipients.sol/IHasRewardsRecipients.json +1 -1
- package/out/IHooks.sol/IHooks.json +1 -1
- package/out/IMsgSender.sol/IMsgSender.json +1 -1
- package/out/IPoolManager.sol/IPoolManager.json +1 -1
- package/out/IProtocolFees.sol/IProtocolFees.json +1 -1
- package/out/ISetLimitOrderConfig.sol/ISetLimitOrderConfig.json +1 -0
- package/out/ISupportsLimitOrderFill.sol/ISupportsLimitOrderFill.json +1 -1
- package/out/ISwapPathRouter.sol/ISwapPathRouter.json +1 -1
- package/out/ISwapRouter.sol/ISwapRouter.json +1 -1
- package/out/IUniswapV3SwapCallback.sol/IUniswapV3SwapCallback.json +1 -1
- package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4Hook.json +1 -1
- package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4HookWithUpdateableFee.json +1 -1
- package/out/IUpgradeableV4Hook.sol/IUpgradeableV4Hook.json +1 -1
- package/out/IWETH.sol/IWETH.json +1 -0
- package/out/IZoraHookRegistry.sol/IZoraHookRegistry.json +1 -1
- package/out/IZoraLimitOrderBook.sol/IZoraLimitOrderBook.json +1 -1
- package/out/IZoraLimitOrderBookCoinsInterface.sol/IZoraLimitOrderBookCoinsInterface.json +1 -1
- package/out/IZoraV4CoinHook.sol/IZoraV4CoinHook.json +1 -1
- package/out/LimitOrderBitmap.sol/LimitOrderBitmap.json +1 -1
- package/out/LimitOrderCommon.sol/LimitOrderCommon.json +1 -1
- package/out/LimitOrderCreate.sol/LimitOrderCreate.json +1 -1
- package/out/LimitOrderFill.sol/LimitOrderFill.json +1 -1
- package/out/LimitOrderLiquidity.sol/LimitOrderLiquidity.json +1 -1
- package/out/LimitOrderQueues.sol/LimitOrderQueues.json +1 -1
- package/out/LimitOrderStorage.sol/LimitOrderStorage.json +1 -1
- package/out/LimitOrderTypes.sol/LimitOrderTypes.json +1 -1
- package/out/LimitOrderViews.sol/LimitOrderViews.json +1 -0
- package/out/LimitOrderWithdraw.sol/LimitOrderWithdraw.json +1 -1
- package/out/LiquidityAmounts.sol/LiquidityAmounts.json +1 -1
- package/out/LiquidityMath.sol/LiquidityMath.json +1 -1
- package/out/Lock.sol/Lock.json +1 -1
- package/out/NonzeroDeltaCount.sol/NonzeroDeltaCount.json +1 -1
- package/out/Ownable.sol/Ownable.json +1 -0
- package/out/Ownable2Step.sol/Ownable2Step.json +1 -0
- package/out/Path.sol/Path.json +1 -1
- package/out/PathKey.sol/PathKeyLibrary.json +1 -1
- package/out/Permit2Payments.sol/Permit2Payments.json +1 -1
- package/out/PermittedCallers.sol/PermittedCallers.json +1 -0
- package/out/PoolId.sol/PoolIdLibrary.json +1 -1
- package/out/Position.sol/Position.json +1 -1
- package/out/SafeCast.sol/SafeCast.json +1 -1
- package/out/SafeCast160.sol/SafeCast160.json +1 -1
- package/out/SafeERC20.sol/SafeERC20.json +1 -1
- package/out/SqrtPriceMath.sol/SqrtPriceMath.json +1 -1
- package/out/StateLibrary.sol/StateLibrary.json +1 -1
- package/out/SwapLimitOrders.sol/SwapLimitOrders.json +1 -1
- package/out/SwapWithLimitOrders.sol/SwapWithLimitOrders.json +1 -1
- package/out/TickBitmap.sol/TickBitmap.json +1 -1
- package/out/TickMath.sol/TickMath.json +1 -1
- package/out/TransientSlot.sol/TransientSlot.json +1 -1
- package/out/TransientStateLibrary.sol/TransientStateLibrary.json +1 -1
- package/out/UniV4SwapToCurrency.sol/UniV4SwapToCurrency.json +1 -1
- package/out/UnsafeMath.sol/UnsafeMath.json +1 -1
- package/out/V3ToV4SwapLib.sol/V3ToV4SwapLib.json +1 -1
- package/out/ZoraLimitOrderBook.sol/ZoraLimitOrderBook.json +1 -1
- package/out/build-info/37e0124d88d60569.json +1 -0
- package/out/uniswap/BitMath.sol/BitMath.json +1 -1
- package/out/uniswap/CustomRevert.sol/CustomRevert.json +1 -1
- package/out/uniswap/FullMath.sol/FullMath.json +1 -1
- package/out/uniswap/SafeCast.sol/SafeCast.json +1 -1
- package/out/uniswap/TickMath.sol/TickMath.json +1 -1
- package/package/wagmiGenerated.ts +218 -33
- package/package.json +1 -1
- package/src/IZoraLimitOrderBook.sol +5 -5
- package/src/ZoraLimitOrderBook.sol +24 -41
- package/src/access/PermittedCallers.sol +41 -0
- package/src/libs/LimitOrderBitmap.sol +0 -51
- package/src/libs/LimitOrderCommon.sol +48 -30
- package/src/libs/LimitOrderCreate.sol +5 -18
- package/src/libs/LimitOrderFill.sol +32 -161
- package/src/libs/LimitOrderLiquidity.sol +92 -71
- package/src/libs/LimitOrderViews.sol +168 -0
- package/src/libs/LimitOrderWithdraw.sol +13 -4
- package/src/libs/SwapLimitOrders.sol +14 -7
- package/src/router/ISetLimitOrderConfig.sol +12 -0
- package/src/router/SwapWithLimitOrders.sol +46 -33
- package/test/LimitOrderAccessControl.t.sol +173 -156
- package/test/LimitOrderBitmap.t.sol +13 -7
- package/test/LimitOrderFill.t.sol +42 -4
- package/test/LimitOrderLibraries.t.sol +18 -10
- package/test/LimitOrderLiquidityPayouts.t.sol +280 -3
- package/test/LimitOrderWithdraw.t.sol +28 -1
- package/test/SwapWithLimitOrders.t.sol +3 -5
- package/test/SwapWithLimitOrdersRouter.t.sol +108 -13
- package/test/gas/LimitOrderFillGas.t.sol +0 -7
- package/test/gas/LimitOrderSwapGas.t.sol +0 -6
- package/test/unit/LimitOrderBitmapUnit.t.sol +0 -134
- package/test/unit/LimitOrderCreateUnit.t.sol +32 -0
- package/test/unit/SwapLimitOrdersUnit.t.sol +231 -33
- package/test/unit/SwapLimitOrdersValidation.t.sol +28 -42
- package/test/unit/SwapWithLimitOrdersUnit.t.sol +21 -88
- package/test/utils/BaseTest.sol +34 -22
- package/test/utils/MockWETH.sol +39 -0
- package/test/utils/TestableZoraLimitOrderBook.sol +5 -7
- package/abis/IAuthority.json +0 -31
- package/abis/SimpleAccessManager.json +0 -351
- package/out/IAuthority.sol/IAuthority.json +0 -1
- package/out/SimpleAccessManaged.sol/SimpleAccessManaged.json +0 -1
- package/out/SimpleAccessManager.sol/SimpleAccessManager.json +0 -1
- package/out/build-info/69718f10d1dc37f0.json +0 -1
- package/src/access/SimpleAccessManaged.sol +0 -76
- package/src/access/SimpleAccessManager.sol +0 -268
- package/test/SimpleAccessManager.t.sol +0 -420
|
@@ -11,7 +11,6 @@ import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
|
|
|
11
11
|
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
12
12
|
import {TickBitmap} from "@uniswap/v4-core/src/libraries/TickBitmap.sol";
|
|
13
13
|
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
|
|
14
|
-
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
|
|
15
14
|
import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
|
|
16
15
|
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
17
16
|
|
|
@@ -20,36 +19,17 @@ import {IZoraLimitOrderBook} from "../IZoraLimitOrderBook.sol";
|
|
|
20
19
|
import {LimitOrderTypes} from "./LimitOrderTypes.sol";
|
|
21
20
|
import {LimitOrderLiquidity} from "./LimitOrderLiquidity.sol";
|
|
22
21
|
import {LimitOrderCommon} from "./LimitOrderCommon.sol";
|
|
22
|
+
import {LimitOrderViews} from "./LimitOrderViews.sol";
|
|
23
23
|
import {CoinCommon} from "@zoralabs/coins/src/libs/CoinCommon.sol";
|
|
24
24
|
import {IDeployedCoinVersionLookup} from "@zoralabs/coins/src/interfaces/IDeployedCoinVersionLookup.sol";
|
|
25
25
|
|
|
26
26
|
library LimitOrderFill {
|
|
27
27
|
using PoolIdLibrary for PoolKey;
|
|
28
28
|
|
|
29
|
-
int24 internal constant TICK_SENTINEL = type(int24).max;
|
|
30
|
-
|
|
31
29
|
struct Context {
|
|
32
30
|
IPoolManager poolManager;
|
|
33
31
|
IDeployedCoinVersionLookup versionLookup;
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
function validateTickRange(
|
|
37
|
-
LimitOrderStorage.Layout storage state,
|
|
38
|
-
Context memory ctx,
|
|
39
|
-
PoolKey calldata providedKey,
|
|
40
|
-
bool isCurrency0,
|
|
41
|
-
int24 startTick,
|
|
42
|
-
int24 endTick
|
|
43
|
-
) internal view returns (PoolKey memory canonicalKey, int24 resolvedStart, int24 resolvedEnd) {
|
|
44
|
-
bytes32 poolKeyHash = CoinCommon.hashPoolKey(providedKey);
|
|
45
|
-
canonicalKey = state.poolKeys[poolKeyHash];
|
|
46
|
-
if (canonicalKey.tickSpacing == 0) {
|
|
47
|
-
canonicalKey = providedKey;
|
|
48
|
-
if (canonicalKey.tickSpacing == 0) revert IZoraLimitOrderBook.InvalidPoolKey();
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
(resolvedStart, resolvedEnd) = _resolveTickRange(ctx.poolManager, canonicalKey, isCurrency0, startTick, endTick);
|
|
52
|
-
_validateTickRange(isCurrency0, resolvedStart, resolvedEnd);
|
|
32
|
+
address weth;
|
|
53
33
|
}
|
|
54
34
|
|
|
55
35
|
function handleFillCallback(LimitOrderStorage.Layout storage state, Context memory ctx, bytes memory callbackData) internal {
|
|
@@ -68,10 +48,8 @@ library LimitOrderFill {
|
|
|
68
48
|
if (key.tickSpacing == 0) revert IZoraLimitOrderBook.InvalidPoolKey();
|
|
69
49
|
}
|
|
70
50
|
|
|
71
|
-
int24 currentTick = _currentPoolTick(ctx.poolManager, key);
|
|
72
|
-
|
|
73
51
|
if (data.orderIds.length == 0) {
|
|
74
|
-
_fillAcrossRange(state, ctx, data
|
|
52
|
+
_fillAcrossRange(state, ctx, data);
|
|
75
53
|
return;
|
|
76
54
|
}
|
|
77
55
|
|
|
@@ -89,9 +67,12 @@ library LimitOrderFill {
|
|
|
89
67
|
order.status == LimitOrderTypes.OrderStatus.OPEN &&
|
|
90
68
|
order.poolKeyHash == poolKeyHash &&
|
|
91
69
|
order.isCurrency0 == isCurrency0 &&
|
|
92
|
-
order.createdEpoch < currentEpoch
|
|
93
|
-
_hasCrossed(order, currentTick)
|
|
70
|
+
order.createdEpoch < currentEpoch
|
|
94
71
|
) {
|
|
72
|
+
if (!_hasCrossed(order, _currentPoolTick(ctx.poolManager, key))) {
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
|
|
95
76
|
int24 orderTick = LimitOrderCommon.getOrderTick(order);
|
|
96
77
|
LimitOrderTypes.Queue storage tickQueue = tickQueues[orderTick];
|
|
97
78
|
|
|
@@ -104,12 +85,7 @@ library LimitOrderFill {
|
|
|
104
85
|
}
|
|
105
86
|
}
|
|
106
87
|
|
|
107
|
-
function _fillAcrossRange(
|
|
108
|
-
LimitOrderStorage.Layout storage state,
|
|
109
|
-
Context memory ctx,
|
|
110
|
-
IZoraLimitOrderBook.FillCallbackData memory data,
|
|
111
|
-
int24 currentTick
|
|
112
|
-
) private {
|
|
88
|
+
function _fillAcrossRange(LimitOrderStorage.Layout storage state, Context memory ctx, IZoraLimitOrderBook.FillCallbackData memory data) private {
|
|
113
89
|
if (data.maxFillCount == 0) {
|
|
114
90
|
return;
|
|
115
91
|
}
|
|
@@ -122,6 +98,7 @@ library LimitOrderFill {
|
|
|
122
98
|
mapping(int24 => LimitOrderTypes.Queue) storage tickQueues = state.tickQueues[poolKeyHash][coin];
|
|
123
99
|
mapping(bytes32 => LimitOrderTypes.LimitOrder) storage orders = state.limitOrders;
|
|
124
100
|
uint256 currentEpoch = state.poolEpochs[poolKeyHash];
|
|
101
|
+
|
|
125
102
|
bytes32 ordersSlot;
|
|
126
103
|
assembly ("memory-safe") {
|
|
127
104
|
ordersSlot := orders.slot
|
|
@@ -133,13 +110,16 @@ library LimitOrderFill {
|
|
|
133
110
|
int24 cursor = data.startTick;
|
|
134
111
|
int24 target = data.endTick;
|
|
135
112
|
int24 tickSpacing = data.poolKey.tickSpacing;
|
|
136
|
-
|
|
137
113
|
while (processed < fillCap) {
|
|
138
|
-
if (zeroDirection ? cursor <= target : cursor >= target)
|
|
114
|
+
if (zeroDirection ? cursor <= target : cursor >= target) {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
139
117
|
|
|
140
118
|
(int24 nextTick, bool initialized) = TickBitmap.nextInitializedTickWithinOneWord(bitmap, cursor, tickSpacing, zeroDirection);
|
|
141
119
|
bool crossesTarget = zeroDirection ? nextTick <= target : nextTick > target;
|
|
142
|
-
if (crossesTarget)
|
|
120
|
+
if (crossesTarget) {
|
|
121
|
+
break;
|
|
122
|
+
}
|
|
143
123
|
|
|
144
124
|
if (!initialized) {
|
|
145
125
|
cursor = zeroDirection ? nextTick - 1 : nextTick;
|
|
@@ -177,10 +157,11 @@ library LimitOrderFill {
|
|
|
177
157
|
orderId = nextOrderId;
|
|
178
158
|
continue;
|
|
179
159
|
}
|
|
180
|
-
if (order.createdEpoch == currentEpoch)
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
160
|
+
if (order.createdEpoch == currentEpoch) {
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
if (!_hasCrossed(order, _currentPoolTick(ctx.poolManager, data.poolKey))) {
|
|
164
|
+
return;
|
|
184
165
|
}
|
|
185
166
|
|
|
186
167
|
_fillOrder(ctx, state, data.poolKey, tickQueue, order, orderId, data.fillReferral);
|
|
@@ -207,23 +188,29 @@ library LimitOrderFill {
|
|
|
207
188
|
) private {
|
|
208
189
|
order.status = LimitOrderTypes.OrderStatus.FILLED;
|
|
209
190
|
|
|
210
|
-
|
|
191
|
+
// Get both input and output currencies
|
|
192
|
+
address coinIn = LimitOrderCommon.getOrderCoin(key, order.isCurrency0);
|
|
193
|
+
address coinOut = LimitOrderCommon.getOrderCoin(key, !order.isCurrency0);
|
|
211
194
|
|
|
195
|
+
// Pass output currency to burnAndPayout (not input currency)
|
|
196
|
+
// This ensures payout uses the OUTPUT coin's configured payout path
|
|
212
197
|
(Currency coinOutCurrency, uint128 makerAmount, uint128 referralAmount) = LimitOrderLiquidity.burnAndPayout(
|
|
213
198
|
ctx.poolManager,
|
|
214
199
|
key,
|
|
215
200
|
order,
|
|
216
201
|
orderId,
|
|
217
202
|
fillReferral,
|
|
218
|
-
|
|
219
|
-
ctx.versionLookup
|
|
203
|
+
coinOut,
|
|
204
|
+
ctx.versionLookup,
|
|
205
|
+
ctx.weth
|
|
220
206
|
);
|
|
221
207
|
|
|
222
|
-
|
|
208
|
+
// Use input currency for removing from order book
|
|
209
|
+
int24 orderTick = LimitOrderCommon.removeOrder(state, key, coinIn, tickQueue, order);
|
|
223
210
|
|
|
224
211
|
emit IZoraLimitOrderBook.LimitOrderFilled(
|
|
225
212
|
order.maker,
|
|
226
|
-
|
|
213
|
+
coinIn,
|
|
227
214
|
Currency.unwrap(coinOutCurrency),
|
|
228
215
|
order.orderSize,
|
|
229
216
|
makerAmount,
|
|
@@ -235,122 +222,6 @@ library LimitOrderFill {
|
|
|
235
222
|
);
|
|
236
223
|
}
|
|
237
224
|
|
|
238
|
-
/**
|
|
239
|
-
* @notice Derives concrete tick bounds from user input and current pool state.
|
|
240
|
-
* @dev Callers may pass sentinel values (`-TICK_SENTINEL` / `TICK_SENTINEL`) to mean
|
|
241
|
-
* “start at the current tick” or “extend one spacing away”. This helper translates
|
|
242
|
-
* those sentinels into real ticks by snapping to the pool’s aligned tick grid and
|
|
243
|
-
* offsetting one spacing in the appropriate direction so fills never include
|
|
244
|
-
* orders created in the same transaction.
|
|
245
|
-
*
|
|
246
|
-
* @param poolManager Pool manager used to read the live tick.
|
|
247
|
-
* @param key Pool whose tick spacing/current tick drive alignment.
|
|
248
|
-
* @param isCurrency0 True when targeting currency0 orders (prices above anchor).
|
|
249
|
-
* @param startTick User-provided start tick or sentinel.
|
|
250
|
-
* @param endTick User-provided end tick or sentinel.
|
|
251
|
-
* @return resolvedStart Concrete start tick after resolving sentinels.
|
|
252
|
-
* @return resolvedEnd Concrete end tick after resolving sentinels.
|
|
253
|
-
*/
|
|
254
|
-
function _resolveTickRange(
|
|
255
|
-
IPoolManager poolManager,
|
|
256
|
-
PoolKey memory key,
|
|
257
|
-
bool isCurrency0,
|
|
258
|
-
int24 startTick,
|
|
259
|
-
int24 endTick
|
|
260
|
-
) private view returns (int24 resolvedStart, int24 resolvedEnd) {
|
|
261
|
-
int24 spacing = key.tickSpacing;
|
|
262
|
-
|
|
263
|
-
bool startSentinel = startTick == -TICK_SENTINEL;
|
|
264
|
-
bool endSentinel = endTick == TICK_SENTINEL;
|
|
265
|
-
|
|
266
|
-
(int24 anchorTick, int24 nextAligned, int24 prevAligned) = _alignedTicks(poolManager, key, spacing);
|
|
267
|
-
|
|
268
|
-
if (startSentinel) {
|
|
269
|
-
// Treat sentinel start as anchoring the window at the aligned tick.
|
|
270
|
-
resolvedStart = anchorTick;
|
|
271
|
-
resolvedEnd = endSentinel ? (isCurrency0 ? nextAligned : prevAligned) : endTick;
|
|
272
|
-
return (resolvedStart, resolvedEnd);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
if (endSentinel) {
|
|
276
|
-
resolvedStart = startTick;
|
|
277
|
-
resolvedEnd = isCurrency0 ? nextAligned : anchorTick;
|
|
278
|
-
return (resolvedStart, resolvedEnd);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
resolvedStart = startTick;
|
|
282
|
-
resolvedEnd = endTick;
|
|
283
|
-
return (resolvedStart, resolvedEnd);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* @notice Snaps the current pool tick to the nearest aligned tick and returns its neighbors.
|
|
288
|
-
* @dev Uniswap v4 ticks must lie on multiples of `tickSpacing`. We round the live
|
|
289
|
-
* tick down to the nearest aligned value (handling negatives), clamp it inside
|
|
290
|
-
* the pool’s usable range, then compute the next/previous aligned ticks for
|
|
291
|
-
* callers that need to build deterministic tick windows.
|
|
292
|
-
*
|
|
293
|
-
* @param poolManager Pool manager used to read slot0.
|
|
294
|
-
* @param key Pool key describing the pair and spacing.
|
|
295
|
-
* @param spacing Tick spacing for this pool.
|
|
296
|
-
* @return anchorTick Current tick rounded down to the aligned grid.
|
|
297
|
-
* @return nextAligned Next aligned tick above the anchor (clamped to max usable).
|
|
298
|
-
* @return prevAligned Previous aligned tick below the anchor (clamped to min usable).
|
|
299
|
-
*/
|
|
300
|
-
function _alignedTicks(
|
|
301
|
-
IPoolManager poolManager,
|
|
302
|
-
PoolKey memory key,
|
|
303
|
-
int24 spacing
|
|
304
|
-
) private view returns (int24 anchorTick, int24 nextAligned, int24 prevAligned) {
|
|
305
|
-
int24 currentTick = _currentPoolTick(poolManager, key);
|
|
306
|
-
int256 remainder;
|
|
307
|
-
assembly ("memory-safe") {
|
|
308
|
-
remainder := smod(currentTick, spacing)
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
if (remainder == 0) {
|
|
312
|
-
anchorTick = currentTick;
|
|
313
|
-
} else if (currentTick >= 0) {
|
|
314
|
-
anchorTick = int24(int256(currentTick) - remainder);
|
|
315
|
-
} else {
|
|
316
|
-
anchorTick = int24(int256(currentTick) - remainder - spacing);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
int24 minUsable = TickMath.minUsableTick(spacing);
|
|
320
|
-
int24 maxUsable = TickMath.maxUsableTick(spacing);
|
|
321
|
-
|
|
322
|
-
if (anchorTick < minUsable) {
|
|
323
|
-
anchorTick = minUsable;
|
|
324
|
-
} else if (anchorTick > maxUsable) {
|
|
325
|
-
anchorTick = maxUsable;
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
nextAligned = anchorTick + spacing;
|
|
329
|
-
if (nextAligned > maxUsable) {
|
|
330
|
-
nextAligned = maxUsable;
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
prevAligned = anchorTick - spacing;
|
|
334
|
-
if (prevAligned < minUsable) {
|
|
335
|
-
prevAligned = minUsable;
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
function _validateTickRange(bool isCurrency0, int24 startTick, int24 endTick) private pure {
|
|
340
|
-
if (startTick < TickMath.MIN_TICK || startTick > TickMath.MAX_TICK) {
|
|
341
|
-
revert IZoraLimitOrderBook.InvalidFillWindow(startTick, endTick, isCurrency0);
|
|
342
|
-
}
|
|
343
|
-
if (endTick < TickMath.MIN_TICK || endTick > TickMath.MAX_TICK) {
|
|
344
|
-
revert IZoraLimitOrderBook.InvalidFillWindow(startTick, endTick, isCurrency0);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
if (isCurrency0) {
|
|
348
|
-
if (startTick > endTick) revert IZoraLimitOrderBook.InvalidFillWindow(startTick, endTick, isCurrency0);
|
|
349
|
-
} else {
|
|
350
|
-
if (startTick < endTick) revert IZoraLimitOrderBook.InvalidFillWindow(startTick, endTick, isCurrency0);
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
225
|
function _currentPoolTick(IPoolManager poolManager, PoolKey memory key) private view returns (int24 tick) {
|
|
355
226
|
(, tick, , ) = StateLibrary.getSlot0(poolManager, key.toId());
|
|
356
227
|
}
|
|
@@ -13,26 +13,19 @@ import {Currency, CurrencyLibrary} from "@uniswap/v4-core/src/types/Currency.sol
|
|
|
13
13
|
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
14
14
|
import {ModifyLiquidityParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
|
|
15
15
|
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
|
|
16
|
-
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
|
|
17
16
|
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
|
|
18
|
-
import {LiquidityAmounts} from "@zoralabs/coins/src/utils/uniswap/LiquidityAmounts.sol";
|
|
19
17
|
|
|
20
18
|
import {IDeployedCoinVersionLookup} from "@zoralabs/coins/src/interfaces/IDeployedCoinVersionLookup.sol";
|
|
21
19
|
import {LimitOrderTypes} from "./LimitOrderTypes.sol";
|
|
22
20
|
import {IHasSwapPath} from "@zoralabs/coins/src/interfaces/ICoin.sol";
|
|
23
21
|
import {UniV4SwapToCurrency} from "@zoralabs/coins/src/libs/UniV4SwapToCurrency.sol";
|
|
22
|
+
import {PathKey} from "@uniswap/v4-periphery/src/libraries/PathKey.sol";
|
|
23
|
+
import {IWETH} from "@zoralabs/coins/src/interfaces/IWETH.sol";
|
|
24
24
|
|
|
25
25
|
library LimitOrderLiquidity {
|
|
26
26
|
using CurrencyLibrary for Currency;
|
|
27
27
|
|
|
28
|
-
|
|
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
|
-
}
|
|
28
|
+
error WethTransferFailed();
|
|
36
29
|
|
|
37
30
|
function refundResidual(PoolKey memory key, bool isCurrency0, address maker, uint128 amount) internal {
|
|
38
31
|
if (amount == 0) {
|
|
@@ -59,16 +52,18 @@ library LimitOrderLiquidity {
|
|
|
59
52
|
bytes32 orderId,
|
|
60
53
|
address feeRecipient,
|
|
61
54
|
address coinIn,
|
|
62
|
-
IDeployedCoinVersionLookup coinLookup
|
|
55
|
+
IDeployedCoinVersionLookup coinLookup,
|
|
56
|
+
address weth
|
|
63
57
|
) internal returns (Currency makerCoinOut, uint128 makerAmountOut, uint128 referralAmountOut) {
|
|
64
|
-
|
|
58
|
+
// Note: callerDelta is a sum of both fee and liquidity deltas
|
|
59
|
+
(BalanceDelta callerDelta, BalanceDelta feesDelta) = poolManager.modifyLiquidity(
|
|
65
60
|
key,
|
|
66
61
|
ModifyLiquidityParams({tickLower: order.tickLower, tickUpper: order.tickUpper, liquidityDelta: -int256(uint256(order.liquidity)), salt: orderId}),
|
|
67
62
|
""
|
|
68
63
|
);
|
|
69
64
|
|
|
70
|
-
int128 liquidity0 =
|
|
71
|
-
int128 liquidity1 =
|
|
65
|
+
int128 liquidity0 = callerDelta.amount0();
|
|
66
|
+
int128 liquidity1 = callerDelta.amount1();
|
|
72
67
|
int128 fee0Initial = feesDelta.amount0();
|
|
73
68
|
int128 fee1Initial = feesDelta.amount1();
|
|
74
69
|
|
|
@@ -77,15 +72,14 @@ library LimitOrderLiquidity {
|
|
|
77
72
|
int128 referralShareLiquidity0 = feeRecipient == address(0) ? int128(0) : int128(fee0Initial);
|
|
78
73
|
int128 referralShareLiquidity1 = feeRecipient == address(0) ? int128(0) : int128(fee1Initial);
|
|
79
74
|
|
|
80
|
-
|
|
75
|
+
Currency payoutCurrency = order.isCurrency0 ? key.currency1 : key.currency0;
|
|
76
|
+
IHasSwapPath.PayoutSwapPath memory payoutPath = _resolvePayoutPath(coinIn, coinLookup, key, payoutCurrency);
|
|
81
77
|
|
|
82
|
-
(makerCoinOut, makerAmountOut) = _payoutRecipient(poolManager,
|
|
78
|
+
(makerCoinOut, makerAmountOut) = _payoutRecipient(poolManager, order.maker, makerShareLiquidity0, makerShareLiquidity1, payoutPath, weth);
|
|
83
79
|
|
|
84
80
|
if (referralShareLiquidity0 > 0 || referralShareLiquidity1 > 0) {
|
|
85
|
-
(, referralAmountOut) = _payoutRecipient(poolManager,
|
|
81
|
+
(, referralAmountOut) = _payoutRecipient(poolManager, feeRecipient, referralShareLiquidity0, referralShareLiquidity1, payoutPath, weth);
|
|
86
82
|
}
|
|
87
|
-
|
|
88
|
-
_settleNegativeDeltas(poolManager, key, liquidity0 + fee0Initial, liquidity1 + fee1Initial);
|
|
89
83
|
}
|
|
90
84
|
|
|
91
85
|
function burnAndRefund(
|
|
@@ -96,19 +90,25 @@ library LimitOrderLiquidity {
|
|
|
96
90
|
uint128 liquidity,
|
|
97
91
|
bytes32 salt,
|
|
98
92
|
address recipient,
|
|
99
|
-
bool isCurrency0
|
|
93
|
+
bool isCurrency0,
|
|
94
|
+
address weth
|
|
100
95
|
) internal returns (uint128 amountOut) {
|
|
101
96
|
(int128 amount0, int128 amount1) = _burnLiquidity(poolManager, key, tickLower, tickUpper, liquidity, salt);
|
|
102
97
|
|
|
103
|
-
if (
|
|
104
|
-
|
|
105
|
-
poolManager
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
98
|
+
if (amount0 > 0) {
|
|
99
|
+
uint128 amount0Out = uint128(amount0);
|
|
100
|
+
_takeCurrency(poolManager, key.currency0, recipient, amount0Out, weth);
|
|
101
|
+
if (isCurrency0) {
|
|
102
|
+
amountOut = amount0Out;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (amount1 > 0) {
|
|
106
|
+
uint128 amount1Out = uint128(amount1);
|
|
107
|
+
_takeCurrency(poolManager, key.currency1, recipient, amount1Out, weth);
|
|
108
|
+
if (!isCurrency0) {
|
|
109
|
+
amountOut = amount1Out;
|
|
110
|
+
}
|
|
109
111
|
}
|
|
110
|
-
|
|
111
|
-
_settleNegativeDeltas(poolManager, key, amount0, amount1);
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
function settleDeltas(IPoolManager poolManager, PoolKey memory key, int256 d0, int256 d1, address payout0, address payout1) internal {
|
|
@@ -116,18 +116,28 @@ library LimitOrderLiquidity {
|
|
|
116
116
|
poolManager.take(key.currency0, payout0, uint256(d0));
|
|
117
117
|
}
|
|
118
118
|
if (d0 < 0) {
|
|
119
|
+
uint256 amount = uint256(uint256(-d0));
|
|
119
120
|
poolManager.sync(key.currency0);
|
|
120
|
-
key.currency0.
|
|
121
|
-
|
|
121
|
+
if (key.currency0.isAddressZero()) {
|
|
122
|
+
poolManager.settle{value: amount}();
|
|
123
|
+
} else {
|
|
124
|
+
key.currency0.transfer(address(poolManager), amount);
|
|
125
|
+
poolManager.settle();
|
|
126
|
+
}
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
if (d1 > 0 && payout1 != address(0)) {
|
|
125
130
|
poolManager.take(key.currency1, payout1, uint256(d1));
|
|
126
131
|
}
|
|
127
132
|
if (d1 < 0) {
|
|
133
|
+
uint256 amount = uint256(uint256(-d1));
|
|
128
134
|
poolManager.sync(key.currency1);
|
|
129
|
-
key.currency1.
|
|
130
|
-
|
|
135
|
+
if (key.currency1.isAddressZero()) {
|
|
136
|
+
poolManager.settle{value: amount}();
|
|
137
|
+
} else {
|
|
138
|
+
key.currency1.transfer(address(poolManager), amount);
|
|
139
|
+
poolManager.settle();
|
|
140
|
+
}
|
|
131
141
|
}
|
|
132
142
|
}
|
|
133
143
|
|
|
@@ -150,22 +160,28 @@ library LimitOrderLiquidity {
|
|
|
150
160
|
|
|
151
161
|
function _resolvePayoutPath(
|
|
152
162
|
address coinIn,
|
|
153
|
-
IDeployedCoinVersionLookup coinLookup
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
163
|
+
IDeployedCoinVersionLookup coinLookup,
|
|
164
|
+
PoolKey memory key,
|
|
165
|
+
Currency payoutCurrency
|
|
166
|
+
) private view returns (IHasSwapPath.PayoutSwapPath memory payoutPath) {
|
|
167
|
+
// Try to get multi-hop path from coin
|
|
168
|
+
if (coinIn != address(0)) {
|
|
169
|
+
try coinLookup.getVersionForDeployedCoin(coinIn) returns (uint8 version) {
|
|
170
|
+
if (version >= 4 && _supportsSwapPath(coinIn)) {
|
|
171
|
+
payoutPath = IHasSwapPath(coinIn).getPayoutSwapPath(coinLookup);
|
|
172
|
+
// Validate first hop matches expected payout currency
|
|
173
|
+
if (payoutPath.path.length > 0 && payoutPath.path[0].intermediateCurrency == payoutCurrency) {
|
|
174
|
+
return payoutPath;
|
|
175
|
+
}
|
|
164
176
|
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
177
|
+
} catch {}
|
|
178
|
+
}
|
|
167
179
|
|
|
168
|
-
|
|
180
|
+
// Fallback: construct simple single-hop path
|
|
181
|
+
Currency coinCurrency = payoutCurrency == key.currency0 ? key.currency1 : key.currency0;
|
|
182
|
+
payoutPath.currencyIn = coinCurrency;
|
|
183
|
+
payoutPath.path = new PathKey[](1);
|
|
184
|
+
payoutPath.path[0] = PathKey({intermediateCurrency: payoutCurrency, fee: key.fee, tickSpacing: key.tickSpacing, hooks: key.hooks, hookData: bytes("")});
|
|
169
185
|
}
|
|
170
186
|
|
|
171
187
|
function _supportsSwapPath(address coin) private view returns (bool) {
|
|
@@ -185,38 +201,43 @@ library LimitOrderLiquidity {
|
|
|
185
201
|
}
|
|
186
202
|
}
|
|
187
203
|
|
|
188
|
-
function
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
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);
|
|
204
|
+
function _takeCurrency(IPoolManager poolManager, Currency currency, address recipient, uint128 amount, address weth) private {
|
|
205
|
+
if (!currency.isAddressZero()) {
|
|
206
|
+
poolManager.take(currency, recipient, amount);
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
poolManager.take(currency, address(this), amount);
|
|
211
|
+
IWETH(weth).deposit{value: amount}();
|
|
212
|
+
if (!IWETH(weth).transfer(recipient, amount)) {
|
|
213
|
+
revert WethTransferFailed();
|
|
202
214
|
}
|
|
203
215
|
}
|
|
204
216
|
|
|
205
|
-
function
|
|
217
|
+
function _payoutRecipient(
|
|
206
218
|
IPoolManager poolManager,
|
|
207
|
-
|
|
219
|
+
address recipient,
|
|
208
220
|
int128 amount0,
|
|
209
221
|
int128 amount1,
|
|
210
|
-
|
|
222
|
+
IHasSwapPath.PayoutSwapPath memory payoutPath,
|
|
223
|
+
address weth
|
|
211
224
|
) private returns (Currency coinOut, uint128 amountOut) {
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
225
|
+
// Convert to uint128, treating negative/zero as zero
|
|
226
|
+
uint128 amt0 = amount0 > 0 ? uint128(amount0) : 0;
|
|
227
|
+
uint128 amt1 = amount1 > 0 ? uint128(amount1) : 0;
|
|
228
|
+
|
|
229
|
+
// Use swapToPath which handles all cases:
|
|
230
|
+
// - Single positive delta: returns that currency
|
|
231
|
+
// - Dual positive deltas: swaps one to the other and returns combined amount
|
|
232
|
+
// - Multi-hop paths: handles coin -> backingCoin -> backingCoin's currency
|
|
233
|
+
(coinOut, amountOut) = UniV4SwapToCurrency.swapToPath(poolManager, amt0, amt1, payoutPath.currencyIn, payoutPath.path);
|
|
234
|
+
|
|
235
|
+
if (amountOut > 0) {
|
|
236
|
+
Currency payoutCurrency = coinOut;
|
|
237
|
+
_takeCurrency(poolManager, payoutCurrency, recipient, amountOut, weth);
|
|
238
|
+
if (payoutCurrency.isAddressZero()) {
|
|
239
|
+
coinOut = Currency.wrap(weth);
|
|
240
|
+
}
|
|
220
241
|
}
|
|
221
242
|
}
|
|
222
243
|
}
|