@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.
- package/.turbo/turbo-build$colon$js.log +85 -0
- package/AUDIT_NOTES.md +33 -0
- package/AUDIT_RFP.md +408 -0
- package/CHANGELOG.md +25 -0
- package/GAS_COMPARISON_RESULTS.md +194 -0
- package/LICENSE +21 -0
- package/README.md +650 -0
- package/SPEC.md +291 -0
- package/abis/BalanceDeltaLibrary.json +15 -0
- package/abis/BeforeSwapDeltaLibrary.json +15 -0
- package/abis/CurrencyLibrary.json +25 -0
- package/abis/CustomRevert.json +28 -0
- package/abis/IAllowanceTransfer.json +486 -0
- package/abis/IAuthority.json +31 -0
- package/abis/ICoin.json +1074 -0
- package/abis/IDeployedCoinVersionLookup.json +21 -0
- package/abis/IDopplerErrors.json +44 -0
- package/abis/IEIP712.json +15 -0
- package/abis/IERC1363.json +373 -0
- package/abis/IERC165.json +21 -0
- package/abis/IERC20.json +185 -0
- package/abis/IERC20Minimal.json +172 -0
- package/abis/IERC6909Claims.json +288 -0
- package/abis/IERC7572.json +21 -0
- package/abis/IExtsload.json +64 -0
- package/abis/IExttload.json +40 -0
- package/abis/IHasCoinType.json +15 -0
- package/abis/IHasPoolKey.json +42 -0
- package/abis/IHasRewardsRecipients.json +54 -0
- package/abis/IHasSwapPath.json +60 -0
- package/abis/IHasTotalSupplyForPositions.json +15 -0
- package/abis/IHooks.json +789 -0
- package/abis/IMsgSender.json +15 -0
- package/abis/IPoolManager.json +1286 -0
- package/abis/IProtocolFees.json +174 -0
- package/abis/ISupportsLimitOrderFill.json +15 -0
- package/abis/ISwapPathRouter.json +92 -0
- package/abis/ISwapRouter.json +219 -0
- package/abis/IUniswapV3SwapCallback.json +25 -0
- package/abis/IUpgradeableDestinationV4Hook.json +84 -0
- package/abis/IUpgradeableDestinationV4HookWithUpdateableFee.json +95 -0
- package/abis/IUpgradeableV4Hook.json +112 -0
- package/abis/IZoraHookRegistry.json +188 -0
- package/abis/IZoraLimitOrderBook.json +623 -0
- package/abis/IZoraLimitOrderBookCoinsInterface.json +67 -0
- package/abis/IZoraV4CoinHook.json +610 -0
- package/abis/Permit2Payments.json +7 -0
- package/abis/Position.json +7 -0
- package/abis/SafeCast.json +7 -0
- package/abis/SafeCast160.json +7 -0
- package/abis/SafeERC20.json +34 -0
- package/abis/SimpleAccessManaged.json +57 -0
- package/abis/SimpleAccessManager.json +351 -0
- package/abis/SqrtPriceMath.json +22 -0
- package/abis/StateLibrary.json +80 -0
- package/abis/SwapLimitOrders.json +22 -0
- package/abis/SwapWithLimitOrders.json +457 -0
- package/abis/TickBitmap.json +18 -0
- package/abis/TickMath.json +24 -0
- package/abis/V3ToV4SwapLib.json +28 -0
- package/abis/ZoraLimitOrderBook.json +771 -0
- package/cache/solidity-files-cache.json +1 -0
- package/dist/index.cjs +760 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +731 -0
- package/dist/index.js.map +1 -0
- package/dist/wagmiGenerated.d.ts +1012 -0
- package/dist/wagmiGenerated.d.ts.map +1 -0
- package/foundry.toml +29 -0
- package/gas_comparison.py +49 -0
- package/out/BalanceDelta.sol/BalanceDeltaLibrary.json +1 -0
- package/out/BeforeSwapDelta.sol/BeforeSwapDeltaLibrary.json +1 -0
- package/out/BitMath.sol/BitMath.json +1 -0
- package/out/BytesLib.sol/BytesLib.json +1 -0
- package/out/CoinCommon.sol/CoinCommon.json +1 -0
- package/out/CoinConfigurationVersions.sol/CoinConfigurationVersions.json +1 -0
- package/out/CoinConstants.sol/CoinConstants.json +1 -0
- package/out/Context.sol/Context.json +1 -0
- package/out/Currency.sol/CurrencyLibrary.json +1 -0
- package/out/CurrencyReserves.sol/CurrencyReserves.json +1 -0
- package/out/CustomRevert.sol/CustomRevert.json +1 -0
- package/out/DopplerMath.sol/DopplerMath.json +1 -0
- package/out/FixedPoint128.sol/FixedPoint128.json +1 -0
- package/out/FixedPoint96.sol/FixedPoint96.json +1 -0
- package/out/FullMath.sol/FullMath.json +1 -0
- package/out/IAllowanceTransfer.sol/IAllowanceTransfer.json +1 -0
- package/out/IAuthority.sol/IAuthority.json +1 -0
- package/out/ICoin.sol/ICoin.json +1 -0
- package/out/ICoin.sol/IHasCoinType.json +1 -0
- package/out/ICoin.sol/IHasPoolKey.json +1 -0
- package/out/ICoin.sol/IHasSwapPath.json +1 -0
- package/out/ICoin.sol/IHasTotalSupplyForPositions.json +1 -0
- package/out/IDeployedCoinVersionLookup.sol/IDeployedCoinVersionLookup.json +1 -0
- package/out/IDopplerErrors.sol/IDopplerErrors.json +1 -0
- package/out/IEIP712.sol/IEIP712.json +1 -0
- package/out/IERC1363.sol/IERC1363.json +1 -0
- package/out/IERC165.sol/IERC165.json +1 -0
- package/out/IERC20.sol/IERC20.json +1 -0
- package/out/IERC20Minimal.sol/IERC20Minimal.json +1 -0
- package/out/IERC6909Claims.sol/IERC6909Claims.json +1 -0
- package/out/IERC7572.sol/IERC7572.json +1 -0
- package/out/IExtsload.sol/IExtsload.json +1 -0
- package/out/IExttload.sol/IExttload.json +1 -0
- package/out/IHasRewardsRecipients.sol/IHasRewardsRecipients.json +1 -0
- package/out/IHooks.sol/IHooks.json +1 -0
- package/out/IMsgSender.sol/IMsgSender.json +1 -0
- package/out/IPoolManager.sol/IPoolManager.json +1 -0
- package/out/IProtocolFees.sol/IProtocolFees.json +1 -0
- package/out/ISupportsLimitOrderFill.sol/ISupportsLimitOrderFill.json +1 -0
- package/out/ISwapPathRouter.sol/ISwapPathRouter.json +1 -0
- package/out/ISwapRouter.sol/ISwapRouter.json +1 -0
- package/out/IUniswapV3SwapCallback.sol/IUniswapV3SwapCallback.json +1 -0
- package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4Hook.json +1 -0
- package/out/IUpgradeableV4Hook.sol/IUpgradeableDestinationV4HookWithUpdateableFee.json +1 -0
- package/out/IUpgradeableV4Hook.sol/IUpgradeableV4Hook.json +1 -0
- package/out/IZoraHookRegistry.sol/IZoraHookRegistry.json +1 -0
- package/out/IZoraLimitOrderBook.sol/IZoraLimitOrderBook.json +1 -0
- package/out/IZoraLimitOrderBookCoinsInterface.sol/IZoraLimitOrderBookCoinsInterface.json +1 -0
- package/out/IZoraV4CoinHook.sol/IZoraV4CoinHook.json +1 -0
- package/out/LimitOrderBitmap.sol/LimitOrderBitmap.json +1 -0
- package/out/LimitOrderCommon.sol/LimitOrderCommon.json +1 -0
- package/out/LimitOrderCreate.sol/LimitOrderCreate.json +1 -0
- package/out/LimitOrderFill.sol/LimitOrderFill.json +1 -0
- package/out/LimitOrderLiquidity.sol/LimitOrderLiquidity.json +1 -0
- package/out/LimitOrderQueues.sol/LimitOrderQueues.json +1 -0
- package/out/LimitOrderStorage.sol/LimitOrderStorage.json +1 -0
- package/out/LimitOrderTypes.sol/LimitOrderTypes.json +1 -0
- package/out/LimitOrderWithdraw.sol/LimitOrderWithdraw.json +1 -0
- package/out/LiquidityAmounts.sol/LiquidityAmounts.json +1 -0
- package/out/LiquidityMath.sol/LiquidityMath.json +1 -0
- package/out/Lock.sol/Lock.json +1 -0
- package/out/NonzeroDeltaCount.sol/NonzeroDeltaCount.json +1 -0
- package/out/Path.sol/Path.json +1 -0
- package/out/PathKey.sol/PathKeyLibrary.json +1 -0
- package/out/Permit2Payments.sol/Permit2Payments.json +1 -0
- package/out/PoolId.sol/PoolIdLibrary.json +1 -0
- package/out/Position.sol/Position.json +1 -0
- package/out/SafeCast.sol/SafeCast.json +1 -0
- package/out/SafeCast160.sol/SafeCast160.json +1 -0
- package/out/SafeERC20.sol/SafeERC20.json +1 -0
- package/out/SimpleAccessManaged.sol/SimpleAccessManaged.json +1 -0
- package/out/SimpleAccessManager.sol/SimpleAccessManager.json +1 -0
- package/out/SqrtPriceMath.sol/SqrtPriceMath.json +1 -0
- package/out/StateLibrary.sol/StateLibrary.json +1 -0
- package/out/SwapLimitOrders.sol/SwapLimitOrders.json +1 -0
- package/out/SwapWithLimitOrders.sol/SwapWithLimitOrders.json +1 -0
- package/out/TickBitmap.sol/TickBitmap.json +1 -0
- package/out/TickMath.sol/TickMath.json +1 -0
- package/out/TransientSlot.sol/TransientSlot.json +1 -0
- package/out/TransientStateLibrary.sol/TransientStateLibrary.json +1 -0
- package/out/UniV4SwapToCurrency.sol/UniV4SwapToCurrency.json +1 -0
- package/out/UnsafeMath.sol/UnsafeMath.json +1 -0
- package/out/V3ToV4SwapLib.sol/V3ToV4SwapLib.json +1 -0
- package/out/ZoraLimitOrderBook.sol/ZoraLimitOrderBook.json +1 -0
- package/out/build-info/69718f10d1dc37f0.json +1 -0
- package/out/uniswap/BitMath.sol/BitMath.json +1 -0
- package/out/uniswap/CustomRevert.sol/CustomRevert.json +1 -0
- package/out/uniswap/FullMath.sol/FullMath.json +1 -0
- package/out/uniswap/SafeCast.sol/SafeCast.json +1 -0
- package/out/uniswap/TickMath.sol/TickMath.json +1 -0
- package/package/index.ts +1 -0
- package/package/wagmiGenerated.ts +738 -0
- package/package.json +57 -0
- package/remappings.txt +11 -0
- package/src/IZoraLimitOrderBook.sol +195 -0
- package/src/ZoraLimitOrderBook.sol +220 -0
- package/src/access/SimpleAccessManaged.sol +76 -0
- package/src/access/SimpleAccessManager.sol +268 -0
- package/src/libs/LimitOrderBitmap.sol +84 -0
- package/src/libs/LimitOrderCommon.sol +91 -0
- package/src/libs/LimitOrderCreate.sol +277 -0
- package/src/libs/LimitOrderFill.sol +362 -0
- package/src/libs/LimitOrderLiquidity.sol +222 -0
- package/src/libs/LimitOrderQueues.sol +101 -0
- package/src/libs/LimitOrderStorage.sol +34 -0
- package/src/libs/LimitOrderTypes.sol +41 -0
- package/src/libs/LimitOrderWithdraw.sol +100 -0
- package/src/libs/Permit2Payments.sol +41 -0
- package/src/libs/SwapLimitOrders.sol +209 -0
- package/src/router/SwapWithLimitOrders.sol +454 -0
- package/test/LimitOrderAccessControl.t.sol +461 -0
- package/test/LimitOrderBitmap.t.sol +194 -0
- package/test/LimitOrderCreate.t.sol +348 -0
- package/test/LimitOrderFill.t.sol +1005 -0
- package/test/LimitOrderLibraries.t.sol +354 -0
- package/test/LimitOrderLiquidityPayouts.t.sol +333 -0
- package/test/LimitOrderV4Pools.t.sol +157 -0
- package/test/LimitOrderWithdraw.t.sol +653 -0
- package/test/SimpleAccessManager.t.sol +420 -0
- package/test/SwapWithLimitOrders.t.sol +107 -0
- package/test/SwapWithLimitOrdersRouter.t.sol +1073 -0
- package/test/gas/LimitOrderFillGas.t.sol +1008 -0
- package/test/gas/LimitOrderSwapGas.t.sol +403 -0
- package/test/gas/logs/gas_benchmarks_fill_20251201.log +30 -0
- package/test/gas/logs/gas_benchmarks_swap_20251201.log +27 -0
- package/test/unit/LimitOrderBitmapUnit.t.sol +276 -0
- package/test/unit/LimitOrderCreateUnit.t.sol +358 -0
- package/test/unit/SwapLimitOrdersUnit.t.sol +672 -0
- package/test/unit/SwapLimitOrdersValidation.t.sol +423 -0
- package/test/unit/SwapWithLimitOrdersUnit.t.sol +321 -0
- package/test/utils/BaseTest.sol +793 -0
- package/test/utils/TestableZoraLimitOrderBook.sol +54 -0
- package/tsconfig.build.json +10 -0
- package/tsconfig.json +9 -0
- package/tsup.config.ts +11 -0
- package/wagmi.config.ts +18 -0
|
@@ -0,0 +1,277 @@
|
|
|
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 {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
|
|
13
|
+
import {ModifyLiquidityParams} from "@uniswap/v4-core/src/types/PoolOperation.sol";
|
|
14
|
+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
15
|
+
|
|
16
|
+
import {LimitOrderStorage} from "./LimitOrderStorage.sol";
|
|
17
|
+
import {IZoraLimitOrderBook} from "../IZoraLimitOrderBook.sol";
|
|
18
|
+
import {LimitOrderLiquidity} from "./LimitOrderLiquidity.sol";
|
|
19
|
+
import {LimitOrderCommon} from "./LimitOrderCommon.sol";
|
|
20
|
+
import {CoinCommon} from "@zoralabs/coins/src/libs/CoinCommon.sol";
|
|
21
|
+
import {TransientStateLibrary} from "@uniswap/v4-core/src/libraries/TransientStateLibrary.sol";
|
|
22
|
+
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
|
|
23
|
+
import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
|
|
24
|
+
|
|
25
|
+
library LimitOrderCreate {
|
|
26
|
+
struct CreateContext {
|
|
27
|
+
bytes32 poolKeyHash;
|
|
28
|
+
int24 currentTick;
|
|
29
|
+
address coin;
|
|
30
|
+
uint256 epoch;
|
|
31
|
+
address maker;
|
|
32
|
+
bool isCurrency0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
struct MintParams {
|
|
36
|
+
PoolKey key;
|
|
37
|
+
int24 tickLower;
|
|
38
|
+
int24 tickUpper;
|
|
39
|
+
uint128 liquidity;
|
|
40
|
+
uint128 requestedSize;
|
|
41
|
+
bytes32 orderId;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function create(
|
|
45
|
+
LimitOrderStorage.Layout storage state,
|
|
46
|
+
IPoolManager poolManager,
|
|
47
|
+
PoolKey memory key,
|
|
48
|
+
bool isCurrency0,
|
|
49
|
+
uint256[] memory orderSizes,
|
|
50
|
+
int24[] memory orderTicks,
|
|
51
|
+
address maker
|
|
52
|
+
) internal returns (bytes32[] memory) {
|
|
53
|
+
require(maker != address(0), IZoraLimitOrderBook.ZeroMaker());
|
|
54
|
+
|
|
55
|
+
(IZoraLimitOrderBook.CreateCallbackData memory createData, uint256 totalSize) = _prepareCreateData(key, isCurrency0, orderSizes, orderTicks, maker);
|
|
56
|
+
|
|
57
|
+
// Pull funds from specified address
|
|
58
|
+
_pullFunds(createData.key, createData.isCurrency0, totalSize, msg.sender, address(this));
|
|
59
|
+
|
|
60
|
+
// Check if we're already in an unlock callback to avoid double-unlock
|
|
61
|
+
if (TransientStateLibrary.isUnlocked(poolManager)) {
|
|
62
|
+
// Already unlocked - execute directly without calling unlock
|
|
63
|
+
return _create(state, poolManager, createData);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Not in unlock - need to unlock
|
|
67
|
+
bytes memory result = poolManager.unlock(abi.encode(IZoraLimitOrderBook.CallbackId.CREATE, abi.encode(createData)));
|
|
68
|
+
|
|
69
|
+
return abi.decode(result, (bytes32[]));
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handleCreateCallback(LimitOrderStorage.Layout storage state, IPoolManager poolManager, bytes memory payload) internal returns (bytes memory) {
|
|
73
|
+
IZoraLimitOrderBook.CreateCallbackData memory data = abi.decode(payload, (IZoraLimitOrderBook.CreateCallbackData));
|
|
74
|
+
_validateOrderInputs(data.orderSizes, data.orderTicks, data.maker);
|
|
75
|
+
|
|
76
|
+
bytes32[] memory orderIds = _create(state, poolManager, data);
|
|
77
|
+
|
|
78
|
+
return abi.encode(orderIds);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function _validateOrderInputs(uint256[] memory orderSizes, int24[] memory orderTicks, address maker) private pure returns (uint256 total) {
|
|
82
|
+
require(maker != address(0), IZoraLimitOrderBook.ZeroMaker());
|
|
83
|
+
|
|
84
|
+
uint256 length = orderSizes.length;
|
|
85
|
+
require(length == orderTicks.length, IZoraLimitOrderBook.ArrayLengthMismatch());
|
|
86
|
+
|
|
87
|
+
for (uint256 i; i < length; ) {
|
|
88
|
+
uint256 size = orderSizes[i];
|
|
89
|
+
require(size != 0, IZoraLimitOrderBook.ZeroOrderSize());
|
|
90
|
+
total += size;
|
|
91
|
+
unchecked {
|
|
92
|
+
++i;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function _pullFunds(PoolKey memory key, bool isCurrency0, uint256 total, address payer, address book) private {
|
|
98
|
+
address coin = LimitOrderCommon.getOrderCoin(key, isCurrency0);
|
|
99
|
+
|
|
100
|
+
if (coin == address(0)) {
|
|
101
|
+
require(msg.value == total, IZoraLimitOrderBook.NativeValueMismatch());
|
|
102
|
+
} else {
|
|
103
|
+
require(msg.value == 0, IZoraLimitOrderBook.NativeValueMismatch());
|
|
104
|
+
|
|
105
|
+
uint256 beforeBalance = IERC20(coin).balanceOf(book);
|
|
106
|
+
|
|
107
|
+
require(IERC20(coin).transferFrom(payer, book, total), IZoraLimitOrderBook.InsufficientTransferFunds());
|
|
108
|
+
require(IERC20(coin).balanceOf(book) == beforeBalance + total, IZoraLimitOrderBook.InsufficientTransferFunds());
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function _create(
|
|
113
|
+
LimitOrderStorage.Layout storage state,
|
|
114
|
+
IPoolManager poolManager,
|
|
115
|
+
IZoraLimitOrderBook.CreateCallbackData memory data
|
|
116
|
+
) private returns (bytes32[] memory orderIds) {
|
|
117
|
+
PoolKey memory key = data.key;
|
|
118
|
+
bytes32 poolKeyHash = CoinCommon.hashPoolKey(key);
|
|
119
|
+
|
|
120
|
+
// If this is the first order for this pool, store the pool key
|
|
121
|
+
if (state.poolKeys[poolKeyHash].tickSpacing == 0) {
|
|
122
|
+
state.poolKeys[poolKeyHash] = key;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
CreateContext memory ctx = CreateContext({
|
|
126
|
+
poolKeyHash: poolKeyHash,
|
|
127
|
+
currentTick: _getCurrentPoolTick(poolManager, key),
|
|
128
|
+
coin: LimitOrderCommon.getOrderCoin(key, data.isCurrency0),
|
|
129
|
+
epoch: state.poolEpochs[poolKeyHash],
|
|
130
|
+
maker: data.maker,
|
|
131
|
+
isCurrency0: data.isCurrency0
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
orderIds = new bytes32[](data.orderSizes.length);
|
|
135
|
+
|
|
136
|
+
for (uint256 i; i < data.orderSizes.length; ) {
|
|
137
|
+
orderIds[i] = _createSingleOrder(state, poolManager, key, ctx, data.orderSizes, data.orderTicks, i);
|
|
138
|
+
unchecked {
|
|
139
|
+
++i;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function _createSingleOrder(
|
|
145
|
+
LimitOrderStorage.Layout storage state,
|
|
146
|
+
IPoolManager poolManager,
|
|
147
|
+
PoolKey memory key,
|
|
148
|
+
CreateContext memory ctx,
|
|
149
|
+
uint256[] memory orderSizes,
|
|
150
|
+
int24[] memory orderTicks,
|
|
151
|
+
uint256 index
|
|
152
|
+
) private returns (bytes32 orderId) {
|
|
153
|
+
uint256 orderSize;
|
|
154
|
+
int24 orderTick;
|
|
155
|
+
|
|
156
|
+
assembly ("memory-safe") {
|
|
157
|
+
orderSize := mload(add(add(orderSizes, 0x20), mul(index, 0x20)))
|
|
158
|
+
orderTick := mload(add(add(orderTicks, 0x20), mul(index, 0x20)))
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
(int24 tickLower, int24 tickUpper) = _calculateTickRange(ctx.isCurrency0, orderTick, key.tickSpacing);
|
|
162
|
+
|
|
163
|
+
uint128 liquidity = LimitOrderLiquidity.liquidityForOrder(ctx.isCurrency0, orderSize, tickLower, tickUpper);
|
|
164
|
+
require(liquidity != 0, IZoraLimitOrderBook.ZeroRealizedOrder());
|
|
165
|
+
|
|
166
|
+
orderId = _generateOrderId(ctx.poolKeyHash, ctx.coin, orderTick, ctx.maker, ++state.makerNonces[ctx.maker]);
|
|
167
|
+
|
|
168
|
+
MintParams memory mintParams = MintParams({
|
|
169
|
+
key: key,
|
|
170
|
+
tickLower: tickLower,
|
|
171
|
+
tickUpper: tickUpper,
|
|
172
|
+
liquidity: liquidity,
|
|
173
|
+
requestedSize: uint128(orderSize),
|
|
174
|
+
orderId: orderId
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
_mintAndRecordOrder(state, poolManager, orderTick, ctx, mintParams);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function _calculateTickRange(bool isCurrency0, int24 orderTick, int24 spacing) private pure returns (int24 tickLower, int24 tickUpper) {
|
|
181
|
+
if (isCurrency0) {
|
|
182
|
+
tickLower = orderTick;
|
|
183
|
+
tickUpper = orderTick + spacing;
|
|
184
|
+
} else {
|
|
185
|
+
tickLower = orderTick - spacing;
|
|
186
|
+
tickUpper = orderTick;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
function _generateOrderId(bytes32 poolKeyHash, address coin, int24 orderTick, address maker, uint256 nonce) private pure returns (bytes32 orderId) {
|
|
191
|
+
assembly ("memory-safe") {
|
|
192
|
+
let ptr := mload(0x40)
|
|
193
|
+
mstore(ptr, poolKeyHash)
|
|
194
|
+
mstore(add(ptr, 0x20), coin)
|
|
195
|
+
mstore(add(ptr, 0x40), orderTick)
|
|
196
|
+
mstore(add(ptr, 0x60), maker)
|
|
197
|
+
mstore(add(ptr, 0x80), nonce)
|
|
198
|
+
orderId := keccak256(ptr, 0xa0)
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function _mintAndRecordOrder(
|
|
203
|
+
LimitOrderStorage.Layout storage state,
|
|
204
|
+
IPoolManager poolManager,
|
|
205
|
+
int24 orderTick,
|
|
206
|
+
CreateContext memory ctx,
|
|
207
|
+
MintParams memory mintParams
|
|
208
|
+
) private {
|
|
209
|
+
(uint128 realized, uint128 refunded) = _mintLiquidity(poolManager, ctx.isCurrency0, mintParams);
|
|
210
|
+
|
|
211
|
+
if (refunded != 0) {
|
|
212
|
+
LimitOrderLiquidity.refundResidual(mintParams.key, ctx.isCurrency0, ctx.maker, refunded);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
LimitOrderCommon.recordCreation(
|
|
216
|
+
state,
|
|
217
|
+
mintParams.key,
|
|
218
|
+
ctx.poolKeyHash,
|
|
219
|
+
mintParams.orderId,
|
|
220
|
+
ctx.maker,
|
|
221
|
+
ctx.coin,
|
|
222
|
+
ctx.isCurrency0,
|
|
223
|
+
orderTick,
|
|
224
|
+
ctx.currentTick,
|
|
225
|
+
ctx.epoch,
|
|
226
|
+
mintParams.liquidity,
|
|
227
|
+
realized,
|
|
228
|
+
mintParams.tickLower,
|
|
229
|
+
mintParams.tickUpper
|
|
230
|
+
);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function _mintLiquidity(IPoolManager poolManager, bool isCurrency0, MintParams memory params) private returns (uint128 realizedSize, uint128 refunded) {
|
|
234
|
+
(BalanceDelta delta, ) = poolManager.modifyLiquidity(
|
|
235
|
+
params.key,
|
|
236
|
+
ModifyLiquidityParams({
|
|
237
|
+
tickLower: params.tickLower,
|
|
238
|
+
tickUpper: params.tickUpper,
|
|
239
|
+
liquidityDelta: int256(uint256(params.liquidity)),
|
|
240
|
+
salt: params.orderId
|
|
241
|
+
}),
|
|
242
|
+
""
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
int128 amount0 = delta.amount0();
|
|
246
|
+
int128 amount1 = delta.amount1();
|
|
247
|
+
|
|
248
|
+
if (isCurrency0) {
|
|
249
|
+
realizedSize = amount0 < 0 ? uint128(uint256(int256(-amount0))) : 0;
|
|
250
|
+
} else {
|
|
251
|
+
realizedSize = amount1 < 0 ? uint128(uint256(int256(-amount1))) : 0;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
require(realizedSize != 0, IZoraLimitOrderBook.ZeroRealizedOrder());
|
|
255
|
+
|
|
256
|
+
if (realizedSize < params.requestedSize) {
|
|
257
|
+
refunded = params.requestedSize - realizedSize;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
LimitOrderLiquidity.settleAfterCreate(poolManager, params.key, isCurrency0);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function _prepareCreateData(
|
|
264
|
+
PoolKey memory key,
|
|
265
|
+
bool isCurrency0,
|
|
266
|
+
uint256[] memory orderSizes,
|
|
267
|
+
int24[] memory orderTicks,
|
|
268
|
+
address maker
|
|
269
|
+
) private pure returns (IZoraLimitOrderBook.CreateCallbackData memory data, uint256 totalSize) {
|
|
270
|
+
totalSize = _validateOrderInputs(orderSizes, orderTicks, maker);
|
|
271
|
+
data = IZoraLimitOrderBook.CreateCallbackData({key: key, isCurrency0: isCurrency0, orderSizes: orderSizes, orderTicks: orderTicks, maker: maker});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function _getCurrentPoolTick(IPoolManager poolManager, PoolKey memory key) private view returns (int24 tick) {
|
|
275
|
+
(, tick, , ) = StateLibrary.getSlot0(poolManager, PoolIdLibrary.toId(key));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
@@ -0,0 +1,362 @@
|
|
|
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 {TickBitmap} from "@uniswap/v4-core/src/libraries/TickBitmap.sol";
|
|
13
|
+
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
|
|
14
|
+
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
|
|
15
|
+
import {PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
|
|
16
|
+
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
17
|
+
|
|
18
|
+
import {LimitOrderStorage} from "./LimitOrderStorage.sol";
|
|
19
|
+
import {IZoraLimitOrderBook} from "../IZoraLimitOrderBook.sol";
|
|
20
|
+
import {LimitOrderTypes} from "./LimitOrderTypes.sol";
|
|
21
|
+
import {LimitOrderLiquidity} from "./LimitOrderLiquidity.sol";
|
|
22
|
+
import {LimitOrderCommon} from "./LimitOrderCommon.sol";
|
|
23
|
+
import {CoinCommon} from "@zoralabs/coins/src/libs/CoinCommon.sol";
|
|
24
|
+
import {IDeployedCoinVersionLookup} from "@zoralabs/coins/src/interfaces/IDeployedCoinVersionLookup.sol";
|
|
25
|
+
|
|
26
|
+
library LimitOrderFill {
|
|
27
|
+
using PoolIdLibrary for PoolKey;
|
|
28
|
+
|
|
29
|
+
int24 internal constant TICK_SENTINEL = type(int24).max;
|
|
30
|
+
|
|
31
|
+
struct Context {
|
|
32
|
+
IPoolManager poolManager;
|
|
33
|
+
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);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleFillCallback(LimitOrderStorage.Layout storage state, Context memory ctx, bytes memory callbackData) internal {
|
|
56
|
+
IZoraLimitOrderBook.FillCallbackData memory data = abi.decode(callbackData, (IZoraLimitOrderBook.FillCallbackData));
|
|
57
|
+
executeFill(state, ctx, data);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function executeFill(LimitOrderStorage.Layout storage state, Context memory ctx, IZoraLimitOrderBook.FillCallbackData memory data) internal {
|
|
61
|
+
// Bump the pool's epoch to ensure that the execution has a clean snapshot and orders created mid-fill are wait for a future price movement before being processed
|
|
62
|
+
bytes32 poolKeyHash = CoinCommon.hashPoolKey(data.poolKey);
|
|
63
|
+
uint256 currentEpoch = ++state.poolEpochs[poolKeyHash];
|
|
64
|
+
|
|
65
|
+
PoolKey memory key = state.poolKeys[poolKeyHash];
|
|
66
|
+
if (key.tickSpacing == 0) {
|
|
67
|
+
key = data.poolKey;
|
|
68
|
+
if (key.tickSpacing == 0) revert IZoraLimitOrderBook.InvalidPoolKey();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
int24 currentTick = _currentPoolTick(ctx.poolManager, key);
|
|
72
|
+
|
|
73
|
+
if (data.orderIds.length == 0) {
|
|
74
|
+
_fillAcrossRange(state, ctx, data, currentTick);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
bool isCurrency0 = data.isCurrency0;
|
|
79
|
+
mapping(int24 => LimitOrderTypes.Queue) storage tickQueues = state.tickQueues[poolKeyHash][LimitOrderCommon.getOrderCoin(key, isCurrency0)];
|
|
80
|
+
mapping(bytes32 => LimitOrderTypes.LimitOrder) storage orders = state.limitOrders;
|
|
81
|
+
address fillReferral = data.fillReferral;
|
|
82
|
+
|
|
83
|
+
uint256 length = data.orderIds.length;
|
|
84
|
+
for (uint256 i; i < length; ) {
|
|
85
|
+
bytes32 orderId = data.orderIds[i];
|
|
86
|
+
LimitOrderTypes.LimitOrder storage order = orders[orderId];
|
|
87
|
+
|
|
88
|
+
if (
|
|
89
|
+
order.status == LimitOrderTypes.OrderStatus.OPEN &&
|
|
90
|
+
order.poolKeyHash == poolKeyHash &&
|
|
91
|
+
order.isCurrency0 == isCurrency0 &&
|
|
92
|
+
order.createdEpoch < currentEpoch &&
|
|
93
|
+
_hasCrossed(order, currentTick)
|
|
94
|
+
) {
|
|
95
|
+
int24 orderTick = LimitOrderCommon.getOrderTick(order);
|
|
96
|
+
LimitOrderTypes.Queue storage tickQueue = tickQueues[orderTick];
|
|
97
|
+
|
|
98
|
+
_fillOrder(ctx, state, key, tickQueue, order, orderId, fillReferral);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
unchecked {
|
|
102
|
+
++i;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function _fillAcrossRange(
|
|
108
|
+
LimitOrderStorage.Layout storage state,
|
|
109
|
+
Context memory ctx,
|
|
110
|
+
IZoraLimitOrderBook.FillCallbackData memory data,
|
|
111
|
+
int24 currentTick
|
|
112
|
+
) private {
|
|
113
|
+
if (data.maxFillCount == 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
bytes32 poolKeyHash = CoinCommon.hashPoolKey(data.poolKey);
|
|
118
|
+
bool zeroForOne = !data.isCurrency0;
|
|
119
|
+
address coin = Currency.unwrap(zeroForOne ? data.poolKey.currency1 : data.poolKey.currency0);
|
|
120
|
+
|
|
121
|
+
mapping(int16 => uint256) storage bitmap = state.tickBitmaps[poolKeyHash][coin];
|
|
122
|
+
mapping(int24 => LimitOrderTypes.Queue) storage tickQueues = state.tickQueues[poolKeyHash][coin];
|
|
123
|
+
mapping(bytes32 => LimitOrderTypes.LimitOrder) storage orders = state.limitOrders;
|
|
124
|
+
uint256 currentEpoch = state.poolEpochs[poolKeyHash];
|
|
125
|
+
bytes32 ordersSlot;
|
|
126
|
+
assembly ("memory-safe") {
|
|
127
|
+
ordersSlot := orders.slot
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
uint256 processed;
|
|
131
|
+
uint256 fillCap = data.maxFillCount;
|
|
132
|
+
bool zeroDirection = zeroForOne;
|
|
133
|
+
int24 cursor = data.startTick;
|
|
134
|
+
int24 target = data.endTick;
|
|
135
|
+
int24 tickSpacing = data.poolKey.tickSpacing;
|
|
136
|
+
|
|
137
|
+
while (processed < fillCap) {
|
|
138
|
+
if (zeroDirection ? cursor <= target : cursor >= target) break;
|
|
139
|
+
|
|
140
|
+
(int24 nextTick, bool initialized) = TickBitmap.nextInitializedTickWithinOneWord(bitmap, cursor, tickSpacing, zeroDirection);
|
|
141
|
+
bool crossesTarget = zeroDirection ? nextTick <= target : nextTick > target;
|
|
142
|
+
if (crossesTarget) break;
|
|
143
|
+
|
|
144
|
+
if (!initialized) {
|
|
145
|
+
cursor = zeroDirection ? nextTick - 1 : nextTick;
|
|
146
|
+
continue;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
LimitOrderTypes.Queue storage tickQueue;
|
|
150
|
+
bytes32 head;
|
|
151
|
+
assembly ("memory-safe") {
|
|
152
|
+
mstore(0x00, nextTick)
|
|
153
|
+
mstore(0x20, tickQueues.slot)
|
|
154
|
+
let queueSlot := keccak256(0x00, 0x40)
|
|
155
|
+
tickQueue.slot := queueSlot
|
|
156
|
+
head := sload(queueSlot)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
if (head == bytes32(0)) {
|
|
160
|
+
cursor = zeroDirection ? nextTick - 1 : nextTick;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
bytes32 orderId = head;
|
|
165
|
+
while (orderId != bytes32(0) && processed < fillCap) {
|
|
166
|
+
LimitOrderTypes.LimitOrder storage order;
|
|
167
|
+
bytes32 nextOrderId;
|
|
168
|
+
assembly ("memory-safe") {
|
|
169
|
+
mstore(0x00, orderId)
|
|
170
|
+
mstore(0x20, ordersSlot)
|
|
171
|
+
let orderSlot := keccak256(0x00, 0x40)
|
|
172
|
+
order.slot := orderSlot
|
|
173
|
+
nextOrderId := sload(orderSlot)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (order.status != LimitOrderTypes.OrderStatus.OPEN) {
|
|
177
|
+
orderId = nextOrderId;
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
if (order.createdEpoch == currentEpoch) break;
|
|
181
|
+
if (!_hasCrossed(order, currentTick)) {
|
|
182
|
+
orderId = nextOrderId;
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
_fillOrder(ctx, state, data.poolKey, tickQueue, order, orderId, data.fillReferral);
|
|
187
|
+
|
|
188
|
+
unchecked {
|
|
189
|
+
++processed;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
orderId = nextOrderId;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
cursor = zeroDirection ? nextTick - 1 : nextTick;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function _fillOrder(
|
|
200
|
+
Context memory ctx,
|
|
201
|
+
LimitOrderStorage.Layout storage state,
|
|
202
|
+
PoolKey memory key,
|
|
203
|
+
LimitOrderTypes.Queue storage tickQueue,
|
|
204
|
+
LimitOrderTypes.LimitOrder storage order,
|
|
205
|
+
bytes32 orderId,
|
|
206
|
+
address fillReferral
|
|
207
|
+
) private {
|
|
208
|
+
order.status = LimitOrderTypes.OrderStatus.FILLED;
|
|
209
|
+
|
|
210
|
+
address coin = LimitOrderCommon.getOrderCoin(key, order.isCurrency0);
|
|
211
|
+
|
|
212
|
+
(Currency coinOutCurrency, uint128 makerAmount, uint128 referralAmount) = LimitOrderLiquidity.burnAndPayout(
|
|
213
|
+
ctx.poolManager,
|
|
214
|
+
key,
|
|
215
|
+
order,
|
|
216
|
+
orderId,
|
|
217
|
+
fillReferral,
|
|
218
|
+
coin,
|
|
219
|
+
ctx.versionLookup
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
int24 orderTick = LimitOrderCommon.removeOrder(state, key, coin, tickQueue, order);
|
|
223
|
+
|
|
224
|
+
emit IZoraLimitOrderBook.LimitOrderFilled(
|
|
225
|
+
order.maker,
|
|
226
|
+
coin,
|
|
227
|
+
Currency.unwrap(coinOutCurrency),
|
|
228
|
+
order.orderSize,
|
|
229
|
+
makerAmount,
|
|
230
|
+
fillReferral,
|
|
231
|
+
referralAmount,
|
|
232
|
+
order.poolKeyHash,
|
|
233
|
+
orderTick,
|
|
234
|
+
orderId
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
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
|
+
function _currentPoolTick(IPoolManager poolManager, PoolKey memory key) private view returns (int24 tick) {
|
|
355
|
+
(, tick, , ) = StateLibrary.getSlot0(poolManager, key.toId());
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/// @dev Returns true if the pool tick has fully crossed the order's range.
|
|
359
|
+
function _hasCrossed(LimitOrderTypes.LimitOrder storage order, int24 currentTick) private view returns (bool) {
|
|
360
|
+
return order.isCurrency0 ? currentTick >= order.tickUpper : currentTick <= order.tickLower;
|
|
361
|
+
}
|
|
362
|
+
}
|