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