@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,672 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
import {SwapLimitOrders, LimitOrderConfig, Orders} from "../../src/libs/SwapLimitOrders.sol";
|
|
6
|
+
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
|
|
7
|
+
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
|
|
8
|
+
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
|
|
9
|
+
import {TickMath} from "@zoralabs/coins/src/utils/uniswap/TickMath.sol";
|
|
10
|
+
|
|
11
|
+
/// @notice Helper contract to wrap library calls for proper revert testing
|
|
12
|
+
contract SwapLimitOrdersWrapper {
|
|
13
|
+
function validate(LimitOrderConfig memory params) external pure returns (uint256) {
|
|
14
|
+
return SwapLimitOrders.validate(params);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function computeOrders(
|
|
18
|
+
PoolKey memory key,
|
|
19
|
+
bool isCurrency0,
|
|
20
|
+
uint128 totalSize,
|
|
21
|
+
int24 baseTick,
|
|
22
|
+
uint160 sqrtPriceX96,
|
|
23
|
+
LimitOrderConfig memory params
|
|
24
|
+
) external pure returns (Orders memory o, uint128 allocated, uint128 unallocated) {
|
|
25
|
+
return SwapLimitOrders.computeOrders(key, isCurrency0, totalSize, baseTick, sqrtPriceX96, params);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/// @notice Test helper for isLimitOrder logic (not used in production)
|
|
29
|
+
function isLimitOrder(bool isCoinBuy, address swapper, int128 coinDelta, LimitOrderConfig memory params) external pure returns (bool) {
|
|
30
|
+
// Short-circuit early: must be a coin buy with a valid swapper and config
|
|
31
|
+
if (!isCoinBuy || swapper == address(0) || params.multiples.length == 0) {
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Must be positive and above minimum threshold
|
|
36
|
+
return coinDelta > 0 && uint128(coinDelta) >= SwapLimitOrders.MIN_LIMIT_ORDER_SIZE;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/// @notice Direct unit tests for SwapLimitOrders library functions
|
|
41
|
+
contract SwapLimitOrdersUnitTest is Test {
|
|
42
|
+
using SwapLimitOrders for LimitOrderConfig;
|
|
43
|
+
|
|
44
|
+
int24 constant TICK_SPACING = 200;
|
|
45
|
+
uint256 constant MULTIPLE_SCALE = 1e18;
|
|
46
|
+
uint256 constant PERCENT_SCALE = 10_000;
|
|
47
|
+
uint256 constant MIN_LIMIT_ORDER_SIZE = 1e18;
|
|
48
|
+
|
|
49
|
+
PoolKey internal testKey;
|
|
50
|
+
SwapLimitOrdersWrapper internal wrapper;
|
|
51
|
+
|
|
52
|
+
function setUp() public {
|
|
53
|
+
// Create a minimal valid pool key for testing
|
|
54
|
+
testKey = PoolKey({
|
|
55
|
+
currency0: Currency.wrap(address(0x1000)),
|
|
56
|
+
currency1: Currency.wrap(address(0x2000)),
|
|
57
|
+
fee: 3000,
|
|
58
|
+
tickSpacing: TICK_SPACING,
|
|
59
|
+
hooks: IHooks(address(0))
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
wrapper = new SwapLimitOrdersWrapper();
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/// @notice Tests validate with mismatched array lengths
|
|
66
|
+
function test_validate_lengthMismatch_differentLengths() public {
|
|
67
|
+
LimitOrderConfig memory params;
|
|
68
|
+
params.multiples = new uint256[](3);
|
|
69
|
+
params.percentages = new uint256[](2); // mismatch
|
|
70
|
+
|
|
71
|
+
vm.expectRevert();
|
|
72
|
+
wrapper.validate(params);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/// @notice Tests validate with empty arrays (length == 0)
|
|
76
|
+
function test_validate_lengthMismatch_emptyArrays() public {
|
|
77
|
+
LimitOrderConfig memory params;
|
|
78
|
+
params.multiples = new uint256[](0);
|
|
79
|
+
params.percentages = new uint256[](0);
|
|
80
|
+
|
|
81
|
+
vm.expectRevert();
|
|
82
|
+
wrapper.validate(params);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// @notice Tests validate with zero percentage
|
|
86
|
+
function test_validate_invalidPercent_zeroPercent() public {
|
|
87
|
+
LimitOrderConfig memory params;
|
|
88
|
+
params.multiples = new uint256[](2);
|
|
89
|
+
params.percentages = new uint256[](2);
|
|
90
|
+
|
|
91
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
92
|
+
params.multiples[1] = 4 * MULTIPLE_SCALE;
|
|
93
|
+
params.percentages[0] = 5000; // 50%
|
|
94
|
+
params.percentages[1] = 0; // Zero percent - should revert
|
|
95
|
+
|
|
96
|
+
vm.expectRevert();
|
|
97
|
+
wrapper.validate(params);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/// @notice Tests validate with percentages exceeding 100%
|
|
101
|
+
function test_validate_percentOverflow_exceedsMax() public {
|
|
102
|
+
LimitOrderConfig memory params;
|
|
103
|
+
params.multiples = new uint256[](2);
|
|
104
|
+
params.percentages = new uint256[](2);
|
|
105
|
+
|
|
106
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
107
|
+
params.multiples[1] = 4 * MULTIPLE_SCALE;
|
|
108
|
+
params.percentages[0] = 6000; // 60%
|
|
109
|
+
params.percentages[1] = 5000; // 50% (total 110% > 100%)
|
|
110
|
+
|
|
111
|
+
vm.expectRevert();
|
|
112
|
+
wrapper.validate(params);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// @notice Tests validate with multiple at 1x (not strictly above)
|
|
116
|
+
function test_validate_invalidMultiple_equalToOne() public {
|
|
117
|
+
LimitOrderConfig memory params;
|
|
118
|
+
params.multiples = new uint256[](2);
|
|
119
|
+
params.percentages = new uint256[](2);
|
|
120
|
+
|
|
121
|
+
params.multiples[0] = MULTIPLE_SCALE; // 1.0x - should revert
|
|
122
|
+
params.multiples[1] = 4 * MULTIPLE_SCALE;
|
|
123
|
+
params.percentages[0] = 5000;
|
|
124
|
+
params.percentages[1] = 5000;
|
|
125
|
+
|
|
126
|
+
vm.expectRevert();
|
|
127
|
+
wrapper.validate(params);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// @notice Tests validate with multiple below 1x
|
|
131
|
+
function test_validate_invalidMultiple_belowOne() public {
|
|
132
|
+
LimitOrderConfig memory params;
|
|
133
|
+
params.multiples = new uint256[](2);
|
|
134
|
+
params.percentages = new uint256[](2);
|
|
135
|
+
|
|
136
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
137
|
+
params.multiples[1] = MULTIPLE_SCALE / 2; // 0.5x - should revert
|
|
138
|
+
params.percentages[0] = 5000;
|
|
139
|
+
params.percentages[1] = 5000;
|
|
140
|
+
|
|
141
|
+
vm.expectRevert();
|
|
142
|
+
wrapper.validate(params);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/// @notice Tests validate with all valid inputs (success case)
|
|
146
|
+
function test_validate_success_validParams() public {
|
|
147
|
+
LimitOrderConfig memory params;
|
|
148
|
+
params.multiples = new uint256[](3);
|
|
149
|
+
params.percentages = new uint256[](3);
|
|
150
|
+
|
|
151
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE; // 2x
|
|
152
|
+
params.multiples[1] = 4 * MULTIPLE_SCALE; // 4x
|
|
153
|
+
params.multiples[2] = 8 * MULTIPLE_SCALE; // 8x
|
|
154
|
+
params.percentages[0] = 3000; // 30%
|
|
155
|
+
params.percentages[1] = 3000; // 30%
|
|
156
|
+
params.percentages[2] = 3000; // 30% (total 90% <= 100%)
|
|
157
|
+
|
|
158
|
+
uint256 totalPercent = wrapper.validate(params);
|
|
159
|
+
assertEq(totalPercent, 9000, "total percent should be 9000");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/// @notice Tests validate with percentages exactly at 100%
|
|
163
|
+
function test_validate_success_exactlyOneHundredPercent() public {
|
|
164
|
+
LimitOrderConfig memory params;
|
|
165
|
+
params.multiples = new uint256[](2);
|
|
166
|
+
params.percentages = new uint256[](2);
|
|
167
|
+
|
|
168
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
169
|
+
params.multiples[1] = 4 * MULTIPLE_SCALE;
|
|
170
|
+
params.percentages[0] = 5000; // 50%
|
|
171
|
+
params.percentages[1] = 5000; // 50% (total exactly 100%)
|
|
172
|
+
|
|
173
|
+
uint256 totalPercent = wrapper.validate(params);
|
|
174
|
+
assertEq(totalPercent, 10000, "total percent should be 10000");
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// @notice Tests validate loop iterations (line 65 for loop with multiple iterations)
|
|
178
|
+
function test_validate_multipleIterations_checksAllElements() public {
|
|
179
|
+
LimitOrderConfig memory params;
|
|
180
|
+
params.multiples = new uint256[](5); // 5 elements to iterate
|
|
181
|
+
params.percentages = new uint256[](5);
|
|
182
|
+
|
|
183
|
+
for (uint256 i = 0; i < 5; i++) {
|
|
184
|
+
params.multiples[i] = (2 + i) * MULTIPLE_SCALE; // 2x, 3x, 4x, 5x, 6x
|
|
185
|
+
params.percentages[i] = 1000; // 10% each
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
uint256 totalPercent = wrapper.validate(params);
|
|
189
|
+
assertEq(totalPercent, 5000, "total percent should be 5000");
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// @notice Tests validate with single element (loop executes once)
|
|
193
|
+
function test_validate_singleElement_loopExecutesOnce() public {
|
|
194
|
+
LimitOrderConfig memory params;
|
|
195
|
+
params.multiples = new uint256[](1);
|
|
196
|
+
params.percentages = new uint256[](1);
|
|
197
|
+
|
|
198
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
199
|
+
params.percentages[0] = 10000;
|
|
200
|
+
|
|
201
|
+
uint256 totalPercent = wrapper.validate(params);
|
|
202
|
+
assertEq(totalPercent, 10000, "total percent should be 10000");
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/// @notice Tests computeOrders with totalSize below MIN_LIMIT_ORDER_SIZE (dust)
|
|
206
|
+
function test_computeOrders_belowMinSize_returnsEmpty() public {
|
|
207
|
+
LimitOrderConfig memory params;
|
|
208
|
+
params.multiples = new uint256[](2);
|
|
209
|
+
params.percentages = new uint256[](2);
|
|
210
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
211
|
+
params.multiples[1] = 4 * MULTIPLE_SCALE;
|
|
212
|
+
params.percentages[0] = 5000;
|
|
213
|
+
params.percentages[1] = 5000;
|
|
214
|
+
|
|
215
|
+
uint128 totalSize = uint128(MIN_LIMIT_ORDER_SIZE - 1); // Below minimum
|
|
216
|
+
int24 baseTick = 0;
|
|
217
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
218
|
+
|
|
219
|
+
(Orders memory orders, uint128 allocated, uint128 unallocated) = SwapLimitOrders.computeOrders(
|
|
220
|
+
testKey,
|
|
221
|
+
true,
|
|
222
|
+
totalSize,
|
|
223
|
+
baseTick,
|
|
224
|
+
sqrtPriceX96,
|
|
225
|
+
params
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
// Should return empty arrays
|
|
229
|
+
assertEq(orders.sizes.length, 0, "sizes should be empty");
|
|
230
|
+
assertEq(orders.ticks.length, 0, "ticks should be empty");
|
|
231
|
+
assertEq(allocated, 0, "allocated should be 0");
|
|
232
|
+
assertEq(unallocated, totalSize, "unallocated should equal totalSize");
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/// @notice Tests computeOrders with totalSize exactly at MIN_LIMIT_ORDER_SIZE
|
|
236
|
+
function test_computeOrders_exactlyMinSize_createsOrders() public {
|
|
237
|
+
LimitOrderConfig memory params;
|
|
238
|
+
params.multiples = new uint256[](2);
|
|
239
|
+
params.percentages = new uint256[](2);
|
|
240
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
241
|
+
params.multiples[1] = 4 * MULTIPLE_SCALE;
|
|
242
|
+
params.percentages[0] = 5000;
|
|
243
|
+
params.percentages[1] = 5000;
|
|
244
|
+
|
|
245
|
+
uint128 totalSize = uint128(MIN_LIMIT_ORDER_SIZE); // Exactly at minimum
|
|
246
|
+
int24 baseTick = 0;
|
|
247
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
248
|
+
|
|
249
|
+
(Orders memory orders, uint128 allocated, uint128 unallocated) = SwapLimitOrders.computeOrders(
|
|
250
|
+
testKey,
|
|
251
|
+
true,
|
|
252
|
+
totalSize,
|
|
253
|
+
baseTick,
|
|
254
|
+
sqrtPriceX96,
|
|
255
|
+
params
|
|
256
|
+
);
|
|
257
|
+
|
|
258
|
+
// Should create orders
|
|
259
|
+
assertEq(orders.sizes.length, 2, "should create 2 orders");
|
|
260
|
+
assertEq(orders.ticks.length, 2, "should create 2 ticks");
|
|
261
|
+
assertGt(allocated, 0, "should allocate some amount");
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/// @notice Tests computeOrders with multiple orders (verifying skip logic exists even if hard to trigger)
|
|
265
|
+
/// @dev Note: Zero-rounding skip is virtually impossible with MIN_LIMIT_ORDER_SIZE=1e18 and PERCENT_SCALE=10000
|
|
266
|
+
/// since even 1 basis point of 1e18 = 1e14. The skip logic exists for safety in edge cases.
|
|
267
|
+
function test_computeOrders_multipleOrders_createsAll() public {
|
|
268
|
+
LimitOrderConfig memory params;
|
|
269
|
+
params.multiples = new uint256[](2);
|
|
270
|
+
params.percentages = new uint256[](2);
|
|
271
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
272
|
+
params.multiples[1] = 4 * MULTIPLE_SCALE;
|
|
273
|
+
params.percentages[0] = 5000;
|
|
274
|
+
params.percentages[1] = 5000;
|
|
275
|
+
|
|
276
|
+
uint128 totalSize = uint128(MIN_LIMIT_ORDER_SIZE * 10);
|
|
277
|
+
int24 baseTick = 0;
|
|
278
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
279
|
+
|
|
280
|
+
(Orders memory orders, uint128 allocated, uint128 unallocated) = SwapLimitOrders.computeOrders(
|
|
281
|
+
testKey,
|
|
282
|
+
true,
|
|
283
|
+
totalSize,
|
|
284
|
+
baseTick,
|
|
285
|
+
sqrtPriceX96,
|
|
286
|
+
params
|
|
287
|
+
);
|
|
288
|
+
|
|
289
|
+
// Both orders should be created
|
|
290
|
+
assertEq(orders.sizes.length, 2, "should create 2 orders");
|
|
291
|
+
assertEq(orders.ticks.length, 2, "should have 2 ticks");
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/// @notice Tests computeOrders loop with many iterations (line 105 for loop)
|
|
295
|
+
function test_computeOrders_manyOrders_loopIteratesMultipleTimes() public {
|
|
296
|
+
LimitOrderConfig memory params;
|
|
297
|
+
params.multiples = new uint256[](6); // Many orders
|
|
298
|
+
params.percentages = new uint256[](6);
|
|
299
|
+
|
|
300
|
+
for (uint256 i = 0; i < 6; i++) {
|
|
301
|
+
params.multiples[i] = (2 + i) * MULTIPLE_SCALE; // 2x, 3x, 4x, 5x, 6x, 7x
|
|
302
|
+
params.percentages[i] = 1000; // 10% each (60% total)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
uint128 totalSize = uint128(MIN_LIMIT_ORDER_SIZE * 100);
|
|
306
|
+
int24 baseTick = 0;
|
|
307
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
308
|
+
|
|
309
|
+
(Orders memory orders, uint128 allocated, uint128 unallocated) = SwapLimitOrders.computeOrders(
|
|
310
|
+
testKey,
|
|
311
|
+
true,
|
|
312
|
+
totalSize,
|
|
313
|
+
baseTick,
|
|
314
|
+
sqrtPriceX96,
|
|
315
|
+
params
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
// All 6 orders should be created
|
|
319
|
+
assertEq(orders.sizes.length, 6, "should create 6 orders");
|
|
320
|
+
assertEq(orders.ticks.length, 6, "should have 6 ticks");
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/// @notice Tests computeOrders with single order (loop executes once)
|
|
324
|
+
function test_computeOrders_singleOrder_loopExecutesOnce() public {
|
|
325
|
+
LimitOrderConfig memory params;
|
|
326
|
+
params.multiples = new uint256[](1);
|
|
327
|
+
params.percentages = new uint256[](1);
|
|
328
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
329
|
+
params.percentages[0] = 10000;
|
|
330
|
+
|
|
331
|
+
uint128 totalSize = uint128(MIN_LIMIT_ORDER_SIZE * 10);
|
|
332
|
+
int24 baseTick = 0;
|
|
333
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
334
|
+
|
|
335
|
+
(Orders memory orders, uint128 allocated, uint128 unallocated) = SwapLimitOrders.computeOrders(
|
|
336
|
+
testKey,
|
|
337
|
+
true,
|
|
338
|
+
totalSize,
|
|
339
|
+
baseTick,
|
|
340
|
+
sqrtPriceX96,
|
|
341
|
+
params
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
assertEq(orders.sizes.length, 1, "should create 1 order");
|
|
345
|
+
assertEq(orders.ticks.length, 1, "should have 1 tick");
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
/// @notice Tests computeOrders with large totalSize and valid percentages
|
|
349
|
+
function test_computeOrders_largeSize_allocatesCorrectly() public {
|
|
350
|
+
LimitOrderConfig memory params;
|
|
351
|
+
params.multiples = new uint256[](2);
|
|
352
|
+
params.percentages = new uint256[](2);
|
|
353
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
354
|
+
params.multiples[1] = 4 * MULTIPLE_SCALE;
|
|
355
|
+
params.percentages[0] = 6000; // 60%
|
|
356
|
+
params.percentages[1] = 3000; // 30% (total 90%)
|
|
357
|
+
|
|
358
|
+
uint128 totalSize = uint128(1000 * MIN_LIMIT_ORDER_SIZE);
|
|
359
|
+
int24 baseTick = 0;
|
|
360
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
361
|
+
|
|
362
|
+
(Orders memory orders, uint128 allocated, uint128 unallocated) = SwapLimitOrders.computeOrders(
|
|
363
|
+
testKey,
|
|
364
|
+
true,
|
|
365
|
+
totalSize,
|
|
366
|
+
baseTick,
|
|
367
|
+
sqrtPriceX96,
|
|
368
|
+
params
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
assertEq(orders.sizes.length, 2, "should create 2 orders");
|
|
372
|
+
|
|
373
|
+
// Verify order sizes apply percentages sequentially (second rung sized off remaining 40%)
|
|
374
|
+
uint256 expectedFirst = (totalSize * 6000) / PERCENT_SCALE; // 60% of total
|
|
375
|
+
uint256 expectedSecond = ((totalSize - expectedFirst) * 3000) / PERCENT_SCALE; // 30% of remaining
|
|
376
|
+
assertEq(orders.sizes[0], expectedFirst, "first order size should be 60% of total");
|
|
377
|
+
assertEq(orders.sizes[1], expectedSecond, "second order size should be 30% of remaining");
|
|
378
|
+
|
|
379
|
+
// Verify allocated + unallocated = totalSize
|
|
380
|
+
assertEq(uint256(allocated) + uint256(unallocated), totalSize, "allocated + unallocated should equal totalSize");
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/// @notice Tests computeOrders sizes each rung off the remaining balance (geometric sizing)
|
|
384
|
+
function test_computeOrders_percentages_applyToRemaining() public view {
|
|
385
|
+
LimitOrderConfig memory params;
|
|
386
|
+
params.multiples = new uint256[](3);
|
|
387
|
+
params.percentages = new uint256[](3);
|
|
388
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
389
|
+
params.multiples[1] = 3 * MULTIPLE_SCALE;
|
|
390
|
+
params.multiples[2] = 4 * MULTIPLE_SCALE;
|
|
391
|
+
params.percentages[0] = 2000; // 20%
|
|
392
|
+
params.percentages[1] = 2000; // 20% of remaining
|
|
393
|
+
params.percentages[2] = 2000; // 20% of remaining
|
|
394
|
+
|
|
395
|
+
uint128 totalSize = uint128(100 * MIN_LIMIT_ORDER_SIZE); // 100 units for easy math
|
|
396
|
+
int24 baseTick = 0;
|
|
397
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
398
|
+
|
|
399
|
+
(Orders memory orders, uint128 allocated, uint128 unallocated) = SwapLimitOrders.computeOrders(
|
|
400
|
+
testKey,
|
|
401
|
+
true,
|
|
402
|
+
totalSize,
|
|
403
|
+
baseTick,
|
|
404
|
+
sqrtPriceX96,
|
|
405
|
+
params
|
|
406
|
+
);
|
|
407
|
+
|
|
408
|
+
assertEq(orders.sizes.length, 3, "should create 3 orders");
|
|
409
|
+
|
|
410
|
+
uint256 expectedFirst = (totalSize * 2000) / PERCENT_SCALE; // 20 of 100
|
|
411
|
+
uint256 remainingAfterFirst = totalSize - expectedFirst; // 80
|
|
412
|
+
uint256 expectedSecond = (remainingAfterFirst * 2000) / PERCENT_SCALE; // 16
|
|
413
|
+
uint256 remainingAfterSecond = remainingAfterFirst - expectedSecond; // 64
|
|
414
|
+
uint256 expectedThird = (remainingAfterSecond * 2000) / PERCENT_SCALE; // 12
|
|
415
|
+
|
|
416
|
+
assertEq(orders.sizes[0], expectedFirst, "first order size mismatch");
|
|
417
|
+
assertEq(orders.sizes[1], expectedSecond, "second order size mismatch");
|
|
418
|
+
assertEq(orders.sizes[2], expectedThird, "third order size mismatch");
|
|
419
|
+
|
|
420
|
+
// Conservation: allocated + unallocated == totalSize
|
|
421
|
+
assertEq(uint256(allocated) + uint256(unallocated), totalSize, "conservation must hold");
|
|
422
|
+
assertEq(unallocated, uint128(totalSize - expectedFirst - expectedSecond - expectedThird), "unallocated should be remainder");
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/// @notice Tests computeOrders with extreme multiples (tick clamping)
|
|
426
|
+
function test_computeOrders_extremeMultiples_clampsToMaxTick() public {
|
|
427
|
+
LimitOrderConfig memory params;
|
|
428
|
+
params.multiples = new uint256[](1);
|
|
429
|
+
params.percentages = new uint256[](1);
|
|
430
|
+
params.multiples[0] = 1000 * MULTIPLE_SCALE; // 1000x - extremely high
|
|
431
|
+
params.percentages[0] = 10000; // 100%
|
|
432
|
+
|
|
433
|
+
uint128 totalSize = uint128(100 * MIN_LIMIT_ORDER_SIZE);
|
|
434
|
+
int24 baseTick = 0;
|
|
435
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
436
|
+
|
|
437
|
+
(Orders memory orders, , ) = SwapLimitOrders.computeOrders(testKey, true, totalSize, baseTick, sqrtPriceX96, params);
|
|
438
|
+
|
|
439
|
+
int24 maxTick = TickMath.maxUsableTick(TICK_SPACING);
|
|
440
|
+
|
|
441
|
+
// Tick should be clamped to max usable tick
|
|
442
|
+
assertLe(orders.ticks[0], maxTick, "tick should be clamped to max");
|
|
443
|
+
assertGt(orders.ticks[0], baseTick, "tick should be above base tick");
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/// @notice Tests computeOrders for currency1 (isCurrency0 = false) - tests line 147 branch
|
|
447
|
+
function test_computeOrders_currency1_ticksBelowBase() public {
|
|
448
|
+
LimitOrderConfig memory params;
|
|
449
|
+
params.multiples = new uint256[](2);
|
|
450
|
+
params.percentages = new uint256[](2);
|
|
451
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
452
|
+
params.multiples[1] = 4 * MULTIPLE_SCALE;
|
|
453
|
+
params.percentages[0] = 5000;
|
|
454
|
+
params.percentages[1] = 5000;
|
|
455
|
+
|
|
456
|
+
uint128 totalSize = uint128(100 * MIN_LIMIT_ORDER_SIZE);
|
|
457
|
+
int24 baseTick = 10000;
|
|
458
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
459
|
+
|
|
460
|
+
(Orders memory orders, , ) = SwapLimitOrders.computeOrders(testKey, false, totalSize, baseTick, sqrtPriceX96, params);
|
|
461
|
+
|
|
462
|
+
// For currency1 (isCurrency0 = false), ticks should be below baseTick
|
|
463
|
+
assertLt(orders.ticks[0], baseTick, "tick 0 should be below base tick");
|
|
464
|
+
assertLt(orders.ticks[1], baseTick, "tick 1 should be below base tick");
|
|
465
|
+
assertLt(orders.ticks[1], orders.ticks[0], "higher multiple should have lower tick");
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/// @notice Tests computeOrders with sqrt price overflow (line 155 branch: scaled > type(uint160).max)
|
|
469
|
+
function test_computeOrders_sqrtPriceOverflow_clampsToMax() public {
|
|
470
|
+
LimitOrderConfig memory params;
|
|
471
|
+
params.multiples = new uint256[](1);
|
|
472
|
+
params.percentages = new uint256[](1);
|
|
473
|
+
// Use extremely large multiple to trigger overflow
|
|
474
|
+
params.multiples[0] = 1000000 * MULTIPLE_SCALE; // 1,000,000x
|
|
475
|
+
params.percentages[0] = 10000;
|
|
476
|
+
|
|
477
|
+
uint128 totalSize = uint128(100 * MIN_LIMIT_ORDER_SIZE);
|
|
478
|
+
// Use moderate base tick (TickMath has max/min around ±887272)
|
|
479
|
+
int24 baseTick = 100000; // Moderate positive tick
|
|
480
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
481
|
+
|
|
482
|
+
(Orders memory orders, , ) = SwapLimitOrders.computeOrders(testKey, true, totalSize, baseTick, sqrtPriceX96, params);
|
|
483
|
+
|
|
484
|
+
// Should successfully create order with clamped tick
|
|
485
|
+
assertEq(orders.sizes.length, 1, "should create 1 order");
|
|
486
|
+
assertLe(orders.ticks[0], TickMath.maxUsableTick(TICK_SPACING), "tick should be <= max");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
/// @notice Tests computeOrders near minimum tick boundary (line 169 branch: aligned < minTick)
|
|
490
|
+
function test_computeOrders_nearMinTick_clampsToMin() public {
|
|
491
|
+
LimitOrderConfig memory params;
|
|
492
|
+
params.multiples = new uint256[](1);
|
|
493
|
+
params.percentages = new uint256[](1);
|
|
494
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
495
|
+
params.percentages[0] = 10000;
|
|
496
|
+
|
|
497
|
+
uint128 totalSize = uint128(100 * MIN_LIMIT_ORDER_SIZE);
|
|
498
|
+
// Use moderate negative base tick for currency1
|
|
499
|
+
int24 baseTick = -100000; // Moderate negative tick
|
|
500
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
501
|
+
|
|
502
|
+
(Orders memory orders, , ) = SwapLimitOrders.computeOrders(testKey, false, totalSize, baseTick, sqrtPriceX96, params);
|
|
503
|
+
|
|
504
|
+
// Should successfully create order with clamped tick
|
|
505
|
+
assertEq(orders.sizes.length, 1, "should create 1 order");
|
|
506
|
+
assertGe(orders.ticks[0], TickMath.minUsableTick(TICK_SPACING), "tick should be >= min");
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
/// @notice Tests computeOrders with tick too close to base (line 170: isCurrency0 && aligned < minAway)
|
|
510
|
+
function test_computeOrders_currency0_tooCloseToBase_clampsToMinAway() public {
|
|
511
|
+
LimitOrderConfig memory params;
|
|
512
|
+
params.multiples = new uint256[](1);
|
|
513
|
+
params.percentages = new uint256[](1);
|
|
514
|
+
// Use small multiple that would produce tick very close to base
|
|
515
|
+
params.multiples[0] = MULTIPLE_SCALE + (MULTIPLE_SCALE / 100); // 1.01x
|
|
516
|
+
params.percentages[0] = 10000;
|
|
517
|
+
|
|
518
|
+
uint128 totalSize = uint128(100 * MIN_LIMIT_ORDER_SIZE);
|
|
519
|
+
int24 baseTick = 0;
|
|
520
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
521
|
+
|
|
522
|
+
(Orders memory orders, , ) = SwapLimitOrders.computeOrders(testKey, true, totalSize, baseTick, sqrtPriceX96, params);
|
|
523
|
+
|
|
524
|
+
// For currency0, tick should be at least tickSpacing away from base
|
|
525
|
+
assertGe(orders.ticks[0], baseTick + TICK_SPACING, "tick should be >= baseTick + spacing");
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
/// @notice Tests computeOrders with tick too close to base (line 171: !isCurrency0 && aligned > minAway)
|
|
529
|
+
function test_computeOrders_currency1_tooCloseToBase_clampsToMinAway() public {
|
|
530
|
+
LimitOrderConfig memory params;
|
|
531
|
+
params.multiples = new uint256[](1);
|
|
532
|
+
params.percentages = new uint256[](1);
|
|
533
|
+
// Use small multiple that would produce tick very close to base
|
|
534
|
+
params.multiples[0] = MULTIPLE_SCALE + (MULTIPLE_SCALE / 100); // 1.01x
|
|
535
|
+
params.percentages[0] = 10000;
|
|
536
|
+
|
|
537
|
+
uint128 totalSize = uint128(100 * MIN_LIMIT_ORDER_SIZE);
|
|
538
|
+
int24 baseTick = 0;
|
|
539
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
540
|
+
|
|
541
|
+
(Orders memory orders, , ) = SwapLimitOrders.computeOrders(testKey, false, totalSize, baseTick, sqrtPriceX96, params);
|
|
542
|
+
|
|
543
|
+
// For currency1, tick should be at least tickSpacing away from base (below it)
|
|
544
|
+
assertLe(orders.ticks[0], baseTick - TICK_SPACING, "tick should be <= baseTick - spacing");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/// @notice Tests _sqrtMultiple with zero (line 184 branch: result == 0)
|
|
548
|
+
/// @dev This tests the error case by using a multiple that would cause sqrt to fail
|
|
549
|
+
function test_computeOrders_zeroMultiple_reverts() public {
|
|
550
|
+
LimitOrderConfig memory params;
|
|
551
|
+
params.multiples = new uint256[](1);
|
|
552
|
+
params.percentages = new uint256[](1);
|
|
553
|
+
params.multiples[0] = 0; // Zero multiple - should revert in _sqrtMultiple
|
|
554
|
+
params.percentages[0] = 10000;
|
|
555
|
+
|
|
556
|
+
uint128 totalSize = uint128(100 * MIN_LIMIT_ORDER_SIZE);
|
|
557
|
+
int24 baseTick = 0;
|
|
558
|
+
uint160 sqrtPriceX96 = TickMath.getSqrtPriceAtTick(baseTick);
|
|
559
|
+
|
|
560
|
+
vm.expectRevert();
|
|
561
|
+
wrapper.computeOrders(testKey, true, totalSize, baseTick, sqrtPriceX96, params);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
/// @notice Tests isLimitOrder when not a coin buy (first condition in line 48)
|
|
565
|
+
function test_isLimitOrder_notCoinBuy_returnsFalse() public {
|
|
566
|
+
LimitOrderConfig memory params;
|
|
567
|
+
params.multiples = new uint256[](1);
|
|
568
|
+
params.percentages = new uint256[](1);
|
|
569
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
570
|
+
params.percentages[0] = 10000;
|
|
571
|
+
|
|
572
|
+
bool isCoinBuy = false; // NOT isCoinBuy - tests first branch
|
|
573
|
+
address swapper = address(0x1234);
|
|
574
|
+
int128 coinDelta = int128(int256(MIN_LIMIT_ORDER_SIZE * 2));
|
|
575
|
+
|
|
576
|
+
assertFalse(wrapper.isLimitOrder(isCoinBuy, swapper, coinDelta, params), "should return false when not coin buy");
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
/// @notice Tests isLimitOrder when isCoinBuy is true (ensures we go past first condition)
|
|
580
|
+
function test_isLimitOrder_isCoinBuyTrue_checksOtherConditions() public {
|
|
581
|
+
LimitOrderConfig memory params;
|
|
582
|
+
params.multiples = new uint256[](0); // Empty - no orders
|
|
583
|
+
params.percentages = new uint256[](0);
|
|
584
|
+
|
|
585
|
+
bool isCoinBuy = true; // Pass first condition
|
|
586
|
+
address swapper = address(0x1234);
|
|
587
|
+
int128 coinDelta = int128(int256(MIN_LIMIT_ORDER_SIZE * 2));
|
|
588
|
+
|
|
589
|
+
assertFalse(wrapper.isLimitOrder(isCoinBuy, swapper, coinDelta, params), "should return false when no orders");
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/// @notice Tests isLimitOrder when no orders in params
|
|
593
|
+
function test_isLimitOrder_noOrders_returnsFalse() public {
|
|
594
|
+
LimitOrderConfig memory params;
|
|
595
|
+
params.multiples = new uint256[](0);
|
|
596
|
+
params.percentages = new uint256[](0);
|
|
597
|
+
|
|
598
|
+
bool isCoinBuy = true;
|
|
599
|
+
address swapper = address(0x1234);
|
|
600
|
+
// casting to 'int256' is safe because MIN_LIMIT_ORDER_SIZE will not overflow int128
|
|
601
|
+
int128 coinDelta = int128(int256(MIN_LIMIT_ORDER_SIZE * 2));
|
|
602
|
+
|
|
603
|
+
assertFalse(wrapper.isLimitOrder(isCoinBuy, swapper, coinDelta, params), "should return false when no orders");
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/// @notice Tests isLimitOrder when swapper is zero address
|
|
607
|
+
function test_isLimitOrder_zeroSwapper_returnsFalse() public {
|
|
608
|
+
LimitOrderConfig memory params;
|
|
609
|
+
params.multiples = new uint256[](1);
|
|
610
|
+
params.percentages = new uint256[](1);
|
|
611
|
+
|
|
612
|
+
bool isCoinBuy = true;
|
|
613
|
+
address swapper = address(0); // Zero address
|
|
614
|
+
int128 coinDelta = int128(int256(MIN_LIMIT_ORDER_SIZE * 2));
|
|
615
|
+
|
|
616
|
+
assertFalse(wrapper.isLimitOrder(isCoinBuy, swapper, coinDelta, params), "should return false when swapper is zero");
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
/// @notice Tests isLimitOrder when coinDelta is negative
|
|
620
|
+
function test_isLimitOrder_negativeCoinDelta_returnsFalse() public {
|
|
621
|
+
LimitOrderConfig memory params;
|
|
622
|
+
params.multiples = new uint256[](1);
|
|
623
|
+
params.percentages = new uint256[](1);
|
|
624
|
+
|
|
625
|
+
bool isCoinBuy = true;
|
|
626
|
+
address swapper = address(0x1234);
|
|
627
|
+
int128 coinDelta = -100; // Negative delta
|
|
628
|
+
|
|
629
|
+
assertFalse(wrapper.isLimitOrder(isCoinBuy, swapper, coinDelta, params), "should return false for negative delta");
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/// @notice Tests isLimitOrder when coinDelta is zero
|
|
633
|
+
function test_isLimitOrder_zeroCoinDelta_returnsFalse() public {
|
|
634
|
+
LimitOrderConfig memory params;
|
|
635
|
+
params.multiples = new uint256[](1);
|
|
636
|
+
params.percentages = new uint256[](1);
|
|
637
|
+
|
|
638
|
+
bool isCoinBuy = true;
|
|
639
|
+
address swapper = address(0x1234);
|
|
640
|
+
int128 coinDelta = 0; // Zero delta
|
|
641
|
+
|
|
642
|
+
assertFalse(wrapper.isLimitOrder(isCoinBuy, swapper, coinDelta, params), "should return false for zero delta");
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
/// @notice Tests isLimitOrder when coinDelta below MIN_LIMIT_ORDER_SIZE (dust)
|
|
646
|
+
function test_isLimitOrder_belowMinSize_returnsFalse() public {
|
|
647
|
+
LimitOrderConfig memory params;
|
|
648
|
+
params.multiples = new uint256[](1);
|
|
649
|
+
params.percentages = new uint256[](1);
|
|
650
|
+
|
|
651
|
+
bool isCoinBuy = true;
|
|
652
|
+
address swapper = address(0x1234);
|
|
653
|
+
int128 coinDelta = int128(int256(MIN_LIMIT_ORDER_SIZE - 1)); // Below minimum
|
|
654
|
+
|
|
655
|
+
assertFalse(wrapper.isLimitOrder(isCoinBuy, swapper, coinDelta, params), "should return false when below min size");
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
/// @notice Tests isLimitOrder when all conditions are met (success case)
|
|
659
|
+
function test_isLimitOrder_allConditionsMet_returnsTrue() public {
|
|
660
|
+
LimitOrderConfig memory params;
|
|
661
|
+
params.multiples = new uint256[](1);
|
|
662
|
+
params.percentages = new uint256[](1);
|
|
663
|
+
params.multiples[0] = 2 * MULTIPLE_SCALE;
|
|
664
|
+
params.percentages[0] = 10000;
|
|
665
|
+
|
|
666
|
+
bool isCoinBuy = true;
|
|
667
|
+
address swapper = address(0x1234);
|
|
668
|
+
int128 coinDelta = int128(int256(MIN_LIMIT_ORDER_SIZE * 2));
|
|
669
|
+
|
|
670
|
+
assertTrue(wrapper.isLimitOrder(isCoinBuy, swapper, coinDelta, params), "should return true when all conditions met");
|
|
671
|
+
}
|
|
672
|
+
}
|