@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,276 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
import {LimitOrderBitmap} from "../../src/libs/LimitOrderBitmap.sol";
|
|
6
|
+
import {LimitOrderTypes} from "../../src/libs/LimitOrderTypes.sol";
|
|
7
|
+
import {TickBitmap} from "@uniswap/v4-core/src/libraries/TickBitmap.sol";
|
|
8
|
+
|
|
9
|
+
/// @notice Direct unit tests for LimitOrderBitmap library functions
|
|
10
|
+
contract LimitOrderBitmapUnitTest is Test {
|
|
11
|
+
using LimitOrderBitmap for mapping(int16 => uint256);
|
|
12
|
+
|
|
13
|
+
mapping(int16 => uint256) internal bitmap;
|
|
14
|
+
mapping(int24 => LimitOrderTypes.Queue) internal poolQueue;
|
|
15
|
+
|
|
16
|
+
int24 constant TICK_SPACING = 200;
|
|
17
|
+
|
|
18
|
+
function setUp() public {
|
|
19
|
+
// Clear bitmap before each test
|
|
20
|
+
// Note: Can't easily clear all, but tests use fresh ticks
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/// @notice Tests setIfFirst when sizeBefore is 0 (should set bit)
|
|
24
|
+
function test_setIfFirst_zeroSizeBefore_setsBit() public {
|
|
25
|
+
int24 tick = 1000;
|
|
26
|
+
uint256 sizeBefore = 0; // First order at this tick
|
|
27
|
+
|
|
28
|
+
// Verify bit not set initially
|
|
29
|
+
assertFalse(_isTickSet(tick), "bit should not be set initially");
|
|
30
|
+
|
|
31
|
+
// Call setIfFirst with sizeBefore = 0
|
|
32
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick, TICK_SPACING, sizeBefore);
|
|
33
|
+
|
|
34
|
+
// Verify bit is now set
|
|
35
|
+
assertTrue(_isTickSet(tick), "bit should be set after setIfFirst");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// @notice Tests setIfFirst when sizeBefore > 0 (should NOT set bit)
|
|
39
|
+
function test_setIfFirst_nonZeroSizeBefore_doesNotSetBit() public {
|
|
40
|
+
int24 tick = 2000;
|
|
41
|
+
uint256 sizeBefore = 100; // Already has orders
|
|
42
|
+
|
|
43
|
+
// Verify bit not set initially
|
|
44
|
+
assertFalse(_isTickSet(tick), "bit should not be set initially");
|
|
45
|
+
|
|
46
|
+
// Call setIfFirst with sizeBefore > 0
|
|
47
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick, TICK_SPACING, sizeBefore);
|
|
48
|
+
|
|
49
|
+
// Verify bit is still not set (early return path)
|
|
50
|
+
assertFalse(_isTickSet(tick), "bit should not be set when sizeBefore > 0");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/// @notice Tests clearIfEmpty when sizeAfter is 0 (should clear bit)
|
|
54
|
+
function test_clearIfEmpty_zeroSizeAfter_clearsBit() public {
|
|
55
|
+
int24 tick = 3000;
|
|
56
|
+
|
|
57
|
+
// First set the bit
|
|
58
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick, TICK_SPACING, 0);
|
|
59
|
+
assertTrue(_isTickSet(tick), "bit should be set initially");
|
|
60
|
+
|
|
61
|
+
// Call clearIfEmpty with sizeAfter = 0
|
|
62
|
+
uint256 sizeAfter = 0; // No more orders at this tick
|
|
63
|
+
LimitOrderBitmap.clearIfEmpty(bitmap, tick, TICK_SPACING, sizeAfter);
|
|
64
|
+
|
|
65
|
+
// Verify bit is cleared
|
|
66
|
+
assertFalse(_isTickSet(tick), "bit should be cleared after clearIfEmpty");
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/// @notice Tests clearIfEmpty when sizeAfter > 0 (should NOT clear bit)
|
|
70
|
+
function test_clearIfEmpty_nonZeroSizeAfter_doesNotClearBit() public {
|
|
71
|
+
int24 tick = 4000;
|
|
72
|
+
|
|
73
|
+
// First set the bit
|
|
74
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick, TICK_SPACING, 0);
|
|
75
|
+
assertTrue(_isTickSet(tick), "bit should be set initially");
|
|
76
|
+
|
|
77
|
+
// Call clearIfEmpty with sizeAfter > 0
|
|
78
|
+
uint256 sizeAfter = 50; // Still has orders
|
|
79
|
+
LimitOrderBitmap.clearIfEmpty(bitmap, tick, TICK_SPACING, sizeAfter);
|
|
80
|
+
|
|
81
|
+
// Verify bit is still set (early return path)
|
|
82
|
+
assertTrue(_isTickSet(tick), "bit should remain set when sizeAfter > 0");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/// @notice Tests getExecutableTicks with zeroForOne = true (downward price movement)
|
|
86
|
+
function test_getExecutableTicks_zeroForOne_findsInitializedTicks() public {
|
|
87
|
+
// Set up ticks: 10000, 10200, 10400 (spaced by TICK_SPACING)
|
|
88
|
+
int24 tick1 = 10000;
|
|
89
|
+
int24 tick2 = 10200;
|
|
90
|
+
int24 tick3 = 10400;
|
|
91
|
+
|
|
92
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick1, TICK_SPACING, 0);
|
|
93
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick2, TICK_SPACING, 0);
|
|
94
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick3, TICK_SPACING, 0);
|
|
95
|
+
|
|
96
|
+
// Set queue lengths to non-zero (simulates orders exist)
|
|
97
|
+
poolQueue[tick1].length = 1;
|
|
98
|
+
poolQueue[tick2].length = 1;
|
|
99
|
+
poolQueue[tick3].length = 1;
|
|
100
|
+
|
|
101
|
+
// Swap from 10800 down to 9800 (crosses all three ticks)
|
|
102
|
+
int24 tickBefore = 10800;
|
|
103
|
+
int24 tickAfter = 9800;
|
|
104
|
+
bool zeroForOne = true;
|
|
105
|
+
|
|
106
|
+
int24[] memory executableTicks = LimitOrderBitmap.getExecutableTicks(bitmap, poolQueue, TICK_SPACING, zeroForOne, tickBefore, tickAfter);
|
|
107
|
+
|
|
108
|
+
// Should find all 3 initialized ticks
|
|
109
|
+
assertEq(executableTicks.length, 3, "should find 3 executable ticks");
|
|
110
|
+
assertEq(executableTicks[0], tick3, "should find tick3 first (highest)");
|
|
111
|
+
assertEq(executableTicks[1], tick2, "should find tick2 second");
|
|
112
|
+
assertEq(executableTicks[2], tick1, "should find tick1 last (lowest)");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/// @notice Tests getExecutableTicks with zeroForOne = false (upward price movement)
|
|
116
|
+
function test_getExecutableTicks_oneForZero_findsInitializedTicks() public {
|
|
117
|
+
// Set up ticks: 5000, 5200, 5400
|
|
118
|
+
int24 tick1 = 5000;
|
|
119
|
+
int24 tick2 = 5200;
|
|
120
|
+
int24 tick3 = 5400;
|
|
121
|
+
|
|
122
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick1, TICK_SPACING, 0);
|
|
123
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick2, TICK_SPACING, 0);
|
|
124
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick3, TICK_SPACING, 0);
|
|
125
|
+
|
|
126
|
+
poolQueue[tick1].length = 1;
|
|
127
|
+
poolQueue[tick2].length = 1;
|
|
128
|
+
poolQueue[tick3].length = 1;
|
|
129
|
+
|
|
130
|
+
// Swap from 4800 up to 5600 (crosses all three ticks)
|
|
131
|
+
int24 tickBefore = 4800;
|
|
132
|
+
int24 tickAfter = 5600;
|
|
133
|
+
bool zeroForOne = false;
|
|
134
|
+
|
|
135
|
+
int24[] memory executableTicks = LimitOrderBitmap.getExecutableTicks(bitmap, poolQueue, TICK_SPACING, zeroForOne, tickBefore, tickAfter);
|
|
136
|
+
|
|
137
|
+
// Should find all 3 initialized ticks in ascending order
|
|
138
|
+
assertEq(executableTicks.length, 3, "should find 3 executable ticks");
|
|
139
|
+
assertEq(executableTicks[0], tick1, "should find tick1 first (lowest)");
|
|
140
|
+
assertEq(executableTicks[1], tick2, "should find tick2 second");
|
|
141
|
+
assertEq(executableTicks[2], tick3, "should find tick3 last (highest)");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// @notice Tests getExecutableTicks skips ticks with empty queues
|
|
145
|
+
function test_getExecutableTicks_skipsEmptyQueues() public {
|
|
146
|
+
// Set up 3 ticks, but only 2 have orders
|
|
147
|
+
int24 tick1 = 6000;
|
|
148
|
+
int24 tick2 = 6200; // This one will have empty queue
|
|
149
|
+
int24 tick3 = 6400;
|
|
150
|
+
|
|
151
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick1, TICK_SPACING, 0);
|
|
152
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick2, TICK_SPACING, 0);
|
|
153
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick3, TICK_SPACING, 0);
|
|
154
|
+
|
|
155
|
+
poolQueue[tick1].length = 1;
|
|
156
|
+
poolQueue[tick2].length = 0; // Empty queue - should skip
|
|
157
|
+
poolQueue[tick3].length = 1;
|
|
158
|
+
|
|
159
|
+
int24 tickBefore = 6600;
|
|
160
|
+
int24 tickAfter = 5800;
|
|
161
|
+
bool zeroForOne = true;
|
|
162
|
+
|
|
163
|
+
int24[] memory executableTicks = LimitOrderBitmap.getExecutableTicks(bitmap, poolQueue, TICK_SPACING, zeroForOne, tickBefore, tickAfter);
|
|
164
|
+
|
|
165
|
+
// Should find only 2 ticks (skip tick2 with empty queue)
|
|
166
|
+
assertEq(executableTicks.length, 2, "should find only 2 executable ticks");
|
|
167
|
+
assertEq(executableTicks[0], tick3, "should find tick3");
|
|
168
|
+
assertEq(executableTicks[1], tick1, "should find tick1");
|
|
169
|
+
// tick2 should not be in the array
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// @notice Tests getExecutableTicks with no movement (tickBefore == tickAfter)
|
|
173
|
+
function test_getExecutableTicks_noMovement_returnsEmpty() public {
|
|
174
|
+
int24 tick = 7000;
|
|
175
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick, TICK_SPACING, 0);
|
|
176
|
+
poolQueue[tick].length = 1;
|
|
177
|
+
|
|
178
|
+
int24 tickBefore = 7000;
|
|
179
|
+
int24 tickAfter = 7000; // No movement
|
|
180
|
+
bool zeroForOne = true;
|
|
181
|
+
|
|
182
|
+
int24[] memory executableTicks = LimitOrderBitmap.getExecutableTicks(bitmap, poolQueue, TICK_SPACING, zeroForOne, tickBefore, tickAfter);
|
|
183
|
+
|
|
184
|
+
// Should return empty array (no ticks crossed)
|
|
185
|
+
assertEq(executableTicks.length, 0, "should return empty array for no movement");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/// @notice Tests getExecutableTicks stops at target even if more ticks initialized beyond
|
|
189
|
+
function test_getExecutableTicks_stopsAtTarget() public {
|
|
190
|
+
// Set up ticks: 8000, 8200, 8400, 8600
|
|
191
|
+
int24 tick1 = 8000;
|
|
192
|
+
int24 tick2 = 8200;
|
|
193
|
+
int24 tick3 = 8400;
|
|
194
|
+
int24 tick4 = 8600;
|
|
195
|
+
|
|
196
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick1, TICK_SPACING, 0);
|
|
197
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick2, TICK_SPACING, 0);
|
|
198
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick3, TICK_SPACING, 0);
|
|
199
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick4, TICK_SPACING, 0);
|
|
200
|
+
|
|
201
|
+
poolQueue[tick1].length = 1;
|
|
202
|
+
poolQueue[tick2].length = 1;
|
|
203
|
+
poolQueue[tick3].length = 1;
|
|
204
|
+
poolQueue[tick4].length = 1;
|
|
205
|
+
|
|
206
|
+
// Swap only crosses tick4 and tick3, stops before tick2
|
|
207
|
+
int24 tickBefore = 8800;
|
|
208
|
+
int24 tickAfter = 8300; // Stops between tick3 and tick2
|
|
209
|
+
bool zeroForOne = true;
|
|
210
|
+
|
|
211
|
+
int24[] memory executableTicks = LimitOrderBitmap.getExecutableTicks(bitmap, poolQueue, TICK_SPACING, zeroForOne, tickBefore, tickAfter);
|
|
212
|
+
|
|
213
|
+
// Should find only tick4 and tick3 (stops at target)
|
|
214
|
+
assertEq(executableTicks.length, 2, "should find only 2 ticks before target");
|
|
215
|
+
assertEq(executableTicks[0], tick4, "should find tick4");
|
|
216
|
+
assertEq(executableTicks[1], tick3, "should find tick3");
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/// @notice Tests word boundaries (ticks at 256 * spacing intervals)
|
|
220
|
+
function test_setIfFirst_wordBoundary() public {
|
|
221
|
+
// Ticks at word boundaries
|
|
222
|
+
int24 tick1 = 0; // word 0, bit 0
|
|
223
|
+
int24 tick2 = 255 * TICK_SPACING; // word 0, bit 255
|
|
224
|
+
int24 tick3 = 256 * TICK_SPACING; // word 1, bit 0
|
|
225
|
+
int24 tick4 = -256 * TICK_SPACING; // word -1, bit 0
|
|
226
|
+
|
|
227
|
+
// Set all at boundaries
|
|
228
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick1, TICK_SPACING, 0);
|
|
229
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick2, TICK_SPACING, 0);
|
|
230
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick3, TICK_SPACING, 0);
|
|
231
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick4, TICK_SPACING, 0);
|
|
232
|
+
|
|
233
|
+
// Verify all set
|
|
234
|
+
assertTrue(_isTickSet(tick1), "tick1 should be set");
|
|
235
|
+
assertTrue(_isTickSet(tick2), "tick2 should be set");
|
|
236
|
+
assertTrue(_isTickSet(tick3), "tick3 should be set");
|
|
237
|
+
assertTrue(_isTickSet(tick4), "tick4 should be set");
|
|
238
|
+
|
|
239
|
+
// Clear all
|
|
240
|
+
LimitOrderBitmap.clearIfEmpty(bitmap, tick1, TICK_SPACING, 0);
|
|
241
|
+
LimitOrderBitmap.clearIfEmpty(bitmap, tick2, TICK_SPACING, 0);
|
|
242
|
+
LimitOrderBitmap.clearIfEmpty(bitmap, tick3, TICK_SPACING, 0);
|
|
243
|
+
LimitOrderBitmap.clearIfEmpty(bitmap, tick4, TICK_SPACING, 0);
|
|
244
|
+
|
|
245
|
+
// Verify all cleared
|
|
246
|
+
assertFalse(_isTickSet(tick1), "tick1 should be cleared");
|
|
247
|
+
assertFalse(_isTickSet(tick2), "tick2 should be cleared");
|
|
248
|
+
assertFalse(_isTickSet(tick3), "tick3 should be cleared");
|
|
249
|
+
assertFalse(_isTickSet(tick4), "tick4 should be cleared");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// @notice Tests negative ticks
|
|
253
|
+
function test_setIfFirst_negativeTicks() public {
|
|
254
|
+
int24 tick1 = -5000;
|
|
255
|
+
int24 tick2 = -10000;
|
|
256
|
+
|
|
257
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick1, TICK_SPACING, 0);
|
|
258
|
+
LimitOrderBitmap.setIfFirst(bitmap, tick2, TICK_SPACING, 0);
|
|
259
|
+
|
|
260
|
+
assertTrue(_isTickSet(tick1), "negative tick1 should be set");
|
|
261
|
+
assertTrue(_isTickSet(tick2), "negative tick2 should be set");
|
|
262
|
+
|
|
263
|
+
LimitOrderBitmap.clearIfEmpty(bitmap, tick1, TICK_SPACING, 0);
|
|
264
|
+
LimitOrderBitmap.clearIfEmpty(bitmap, tick2, TICK_SPACING, 0);
|
|
265
|
+
|
|
266
|
+
assertFalse(_isTickSet(tick1), "negative tick1 should be cleared");
|
|
267
|
+
assertFalse(_isTickSet(tick2), "negative tick2 should be cleared");
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
function _isTickSet(int24 tick) internal view returns (bool) {
|
|
271
|
+
int24 compressed = TickBitmap.compress(tick, TICK_SPACING);
|
|
272
|
+
(int16 wordPos, uint8 bitPos) = TickBitmap.position(compressed);
|
|
273
|
+
uint256 word = bitmap[wordPos];
|
|
274
|
+
return (word & (1 << bitPos)) != 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity ^0.8.13;
|
|
3
|
+
|
|
4
|
+
import {Test} from "forge-std/Test.sol";
|
|
5
|
+
import {IZoraLimitOrderBook} from "../../src/IZoraLimitOrderBook.sol";
|
|
6
|
+
|
|
7
|
+
/// @notice Wrapper contract to expose private functions for testing
|
|
8
|
+
/// @dev This is a helper contract, not a test contract
|
|
9
|
+
contract LimitOrderCreateWrapper {
|
|
10
|
+
/// @notice Exposes _validateOrderInputs for testing
|
|
11
|
+
function validateOrderInputs(uint256[] memory orderSizes, int24[] memory orderTicks, address maker) external pure returns (uint256 total) {
|
|
12
|
+
// We can't directly call the private function, so we need to test through public functions
|
|
13
|
+
// Instead, we'll recreate the validation logic here for unit testing
|
|
14
|
+
require(maker != address(0), IZoraLimitOrderBook.ZeroMaker());
|
|
15
|
+
|
|
16
|
+
uint256 length = orderSizes.length;
|
|
17
|
+
require(length == orderTicks.length, IZoraLimitOrderBook.ArrayLengthMismatch());
|
|
18
|
+
|
|
19
|
+
for (uint256 i; i < length; ) {
|
|
20
|
+
uint256 size = orderSizes[i];
|
|
21
|
+
require(size != 0, IZoraLimitOrderBook.ZeroOrderSize());
|
|
22
|
+
total += size;
|
|
23
|
+
unchecked {
|
|
24
|
+
++i;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/// @notice Helper to check pullFunds path - only checks which path, doesn't validate
|
|
30
|
+
function checkPullFundsPath(address coin) external pure returns (string memory path) {
|
|
31
|
+
if (coin == address(0)) {
|
|
32
|
+
return "ETH";
|
|
33
|
+
} else {
|
|
34
|
+
return "ERC20";
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/// @notice Helper to validate pullFunds ETH requirements
|
|
39
|
+
function validatePullFundsETH(uint256 msgValue, uint256 total) external pure {
|
|
40
|
+
require(msgValue == total, IZoraLimitOrderBook.NativeValueMismatch());
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/// @notice Helper to validate pullFunds ERC20 requirements
|
|
44
|
+
function validatePullFundsERC20(uint256 msgValue) external pure {
|
|
45
|
+
require(msgValue == 0, IZoraLimitOrderBook.NativeValueMismatch());
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/// @notice Helper to test tick calculation branches
|
|
49
|
+
function calculateTicks(bool isCurrency0, int24 orderTick, int24 spacing) external pure returns (int24 tickLower, int24 tickUpper) {
|
|
50
|
+
if (isCurrency0) {
|
|
51
|
+
tickLower = orderTick;
|
|
52
|
+
tickUpper = orderTick + spacing;
|
|
53
|
+
} else {
|
|
54
|
+
tickLower = orderTick - spacing;
|
|
55
|
+
tickUpper = orderTick;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/// @notice Helper to test realized size calculation branches
|
|
60
|
+
function calculateRealizedSize(bool isCurrency0, int128 amount0, int128 amount1) external pure returns (uint128 realizedSize) {
|
|
61
|
+
if (isCurrency0) {
|
|
62
|
+
realizedSize = amount0 < 0 ? uint128(uint256(int256(-amount0))) : 0;
|
|
63
|
+
} else {
|
|
64
|
+
realizedSize = amount1 < 0 ? uint128(uint256(int256(-amount1))) : 0;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/// @notice Helper to test refund calculation branch
|
|
69
|
+
function calculateRefund(uint128 realizedSize, uint128 requestedSize) external pure returns (uint128 refunded) {
|
|
70
|
+
require(realizedSize != 0, IZoraLimitOrderBook.ZeroRealizedOrder());
|
|
71
|
+
|
|
72
|
+
if (realizedSize < requestedSize) {
|
|
73
|
+
refunded = requestedSize - realizedSize;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/// @notice Direct unit tests for LimitOrderCreate library functions
|
|
79
|
+
contract LimitOrderCreateUnitTest is Test {
|
|
80
|
+
LimitOrderCreateWrapper internal wrapper;
|
|
81
|
+
|
|
82
|
+
function setUp() public {
|
|
83
|
+
wrapper = new LimitOrderCreateWrapper();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// @notice Tests validation with zero maker address
|
|
87
|
+
function test_validateOrderInputs_zeroMaker_reverts() public {
|
|
88
|
+
uint256[] memory sizes = new uint256[](1);
|
|
89
|
+
int24[] memory ticks = new int24[](1);
|
|
90
|
+
sizes[0] = 1000;
|
|
91
|
+
ticks[0] = 100;
|
|
92
|
+
|
|
93
|
+
vm.expectRevert(IZoraLimitOrderBook.ZeroMaker.selector);
|
|
94
|
+
wrapper.validateOrderInputs(sizes, ticks, address(0));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/// @notice Tests validation with mismatched array lengths
|
|
98
|
+
function test_validateOrderInputs_lengthMismatch_reverts() public {
|
|
99
|
+
uint256[] memory sizes = new uint256[](3);
|
|
100
|
+
int24[] memory ticks = new int24[](2); // Mismatch
|
|
101
|
+
|
|
102
|
+
vm.expectRevert(IZoraLimitOrderBook.ArrayLengthMismatch.selector);
|
|
103
|
+
wrapper.validateOrderInputs(sizes, ticks, address(0x1234));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/// @notice Tests validation with zero order size
|
|
107
|
+
function test_validateOrderInputs_zeroSize_reverts() public {
|
|
108
|
+
uint256[] memory sizes = new uint256[](2);
|
|
109
|
+
int24[] memory ticks = new int24[](2);
|
|
110
|
+
sizes[0] = 1000;
|
|
111
|
+
sizes[1] = 0; // Zero size
|
|
112
|
+
ticks[0] = 100;
|
|
113
|
+
ticks[1] = 200;
|
|
114
|
+
|
|
115
|
+
vm.expectRevert(IZoraLimitOrderBook.ZeroOrderSize.selector);
|
|
116
|
+
wrapper.validateOrderInputs(sizes, ticks, address(0x1234));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/// @notice Tests validation with valid single order
|
|
120
|
+
function test_validateOrderInputs_singleOrder_success() public {
|
|
121
|
+
uint256[] memory sizes = new uint256[](1);
|
|
122
|
+
int24[] memory ticks = new int24[](1);
|
|
123
|
+
sizes[0] = 1000;
|
|
124
|
+
ticks[0] = 100;
|
|
125
|
+
|
|
126
|
+
uint256 total = wrapper.validateOrderInputs(sizes, ticks, address(0x1234));
|
|
127
|
+
assertEq(total, 1000, "total should be 1000");
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// @notice Tests validation with multiple orders (loop iterations)
|
|
131
|
+
function test_validateOrderInputs_multipleOrders_sumsTotal() public {
|
|
132
|
+
uint256[] memory sizes = new uint256[](5);
|
|
133
|
+
int24[] memory ticks = new int24[](5);
|
|
134
|
+
|
|
135
|
+
for (uint256 i = 0; i < 5; i++) {
|
|
136
|
+
sizes[i] = (i + 1) * 1000; // 1000, 2000, 3000, 4000, 5000
|
|
137
|
+
ticks[i] = int24(int256((i + 1) * 100));
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
uint256 total = wrapper.validateOrderInputs(sizes, ticks, address(0x1234));
|
|
141
|
+
assertEq(total, 15000, "total should be 15000");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/// @notice Tests validation loop with many iterations
|
|
145
|
+
function test_validateOrderInputs_manyOrders_loopIterates() public {
|
|
146
|
+
uint256[] memory sizes = new uint256[](10);
|
|
147
|
+
int24[] memory ticks = new int24[](10);
|
|
148
|
+
|
|
149
|
+
for (uint256 i = 0; i < 10; i++) {
|
|
150
|
+
sizes[i] = 500;
|
|
151
|
+
ticks[i] = int24(int256(i * 100));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
uint256 total = wrapper.validateOrderInputs(sizes, ticks, address(0x1234));
|
|
155
|
+
assertEq(total, 5000, "total should be 5000");
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/// @notice Tests pullFunds path detection for ETH (coin == address(0))
|
|
159
|
+
function test_pullFunds_ethPath_detected() public view {
|
|
160
|
+
address coin = address(0);
|
|
161
|
+
string memory path = wrapper.checkPullFundsPath(coin);
|
|
162
|
+
assertEq(path, "ETH", "should detect ETH path");
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/// @notice Tests pullFunds path detection for ERC20 (coin != address(0))
|
|
166
|
+
function test_pullFunds_erc20Path_detected() public view {
|
|
167
|
+
address coin = address(0x1234);
|
|
168
|
+
string memory path = wrapper.checkPullFundsPath(coin);
|
|
169
|
+
assertEq(path, "ERC20", "should detect ERC20 path");
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/// @notice Tests pullFunds ETH validation with correct value
|
|
173
|
+
function test_pullFunds_ethValidation_correctValue_succeeds() public view {
|
|
174
|
+
uint256 msgValue = 1000;
|
|
175
|
+
uint256 total = 1000;
|
|
176
|
+
|
|
177
|
+
wrapper.validatePullFundsETH(msgValue, total);
|
|
178
|
+
// No revert means success
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/// @notice Tests pullFunds ETH validation with incorrect value
|
|
182
|
+
function test_pullFunds_ethValidation_wrongValue_reverts() public {
|
|
183
|
+
uint256 msgValue = 999;
|
|
184
|
+
uint256 total = 1000;
|
|
185
|
+
|
|
186
|
+
vm.expectRevert(IZoraLimitOrderBook.NativeValueMismatch.selector);
|
|
187
|
+
wrapper.validatePullFundsETH(msgValue, total);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/// @notice Tests pullFunds ERC20 validation with zero msg.value
|
|
191
|
+
function test_pullFunds_erc20Validation_zeroValue_succeeds() public view {
|
|
192
|
+
uint256 msgValue = 0;
|
|
193
|
+
|
|
194
|
+
wrapper.validatePullFundsERC20(msgValue);
|
|
195
|
+
// No revert means success
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// @notice Tests pullFunds ERC20 validation with non-zero msg.value
|
|
199
|
+
function test_pullFunds_erc20Validation_nonZeroValue_reverts() public {
|
|
200
|
+
uint256 msgValue = 100;
|
|
201
|
+
|
|
202
|
+
vm.expectRevert(IZoraLimitOrderBook.NativeValueMismatch.selector);
|
|
203
|
+
wrapper.validatePullFundsERC20(msgValue);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/// @notice Tests tick calculation for currency0
|
|
207
|
+
function test_calculateTicks_currency0_setsCorrectRange() public {
|
|
208
|
+
bool isCurrency0 = true;
|
|
209
|
+
int24 orderTick = 1000;
|
|
210
|
+
int24 spacing = 200;
|
|
211
|
+
|
|
212
|
+
(int24 tickLower, int24 tickUpper) = wrapper.calculateTicks(isCurrency0, orderTick, spacing);
|
|
213
|
+
|
|
214
|
+
assertEq(tickLower, 1000, "tickLower should be orderTick");
|
|
215
|
+
assertEq(tickUpper, 1200, "tickUpper should be orderTick + spacing");
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/// @notice Tests tick calculation for currency1
|
|
219
|
+
function test_calculateTicks_currency1_setsCorrectRange() public {
|
|
220
|
+
bool isCurrency0 = false;
|
|
221
|
+
int24 orderTick = 1000;
|
|
222
|
+
int24 spacing = 200;
|
|
223
|
+
|
|
224
|
+
(int24 tickLower, int24 tickUpper) = wrapper.calculateTicks(isCurrency0, orderTick, spacing);
|
|
225
|
+
|
|
226
|
+
assertEq(tickLower, 800, "tickLower should be orderTick - spacing");
|
|
227
|
+
assertEq(tickUpper, 1000, "tickUpper should be orderTick");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/// @notice Tests tick calculation with negative ticks
|
|
231
|
+
function test_calculateTicks_negativeTicks_handlesCorrectly() public {
|
|
232
|
+
bool isCurrency0 = true;
|
|
233
|
+
int24 orderTick = -1000;
|
|
234
|
+
int24 spacing = 200;
|
|
235
|
+
|
|
236
|
+
(int24 tickLower, int24 tickUpper) = wrapper.calculateTicks(isCurrency0, orderTick, spacing);
|
|
237
|
+
|
|
238
|
+
assertEq(tickLower, -1000, "tickLower should be -1000");
|
|
239
|
+
assertEq(tickUpper, -800, "tickUpper should be -800");
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// @notice Tests realized size for currency0 with negative amount0
|
|
243
|
+
function test_calculateRealizedSize_currency0_negativeAmount_returnsSize() public {
|
|
244
|
+
bool isCurrency0 = true;
|
|
245
|
+
int128 amount0 = -1000;
|
|
246
|
+
int128 amount1 = 500;
|
|
247
|
+
|
|
248
|
+
uint128 realizedSize = wrapper.calculateRealizedSize(isCurrency0, amount0, amount1);
|
|
249
|
+
assertEq(realizedSize, 1000, "should return absolute value of amount0");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/// @notice Tests realized size for currency0 with positive amount0
|
|
253
|
+
function test_calculateRealizedSize_currency0_positiveAmount_returnsZero() public {
|
|
254
|
+
bool isCurrency0 = true;
|
|
255
|
+
int128 amount0 = 1000; // Positive
|
|
256
|
+
int128 amount1 = -500;
|
|
257
|
+
|
|
258
|
+
uint128 realizedSize = wrapper.calculateRealizedSize(isCurrency0, amount0, amount1);
|
|
259
|
+
assertEq(realizedSize, 0, "should return 0 for positive amount0");
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/// @notice Tests realized size for currency1 with negative amount1
|
|
263
|
+
function test_calculateRealizedSize_currency1_negativeAmount_returnsSize() public {
|
|
264
|
+
bool isCurrency0 = false;
|
|
265
|
+
int128 amount0 = 500;
|
|
266
|
+
int128 amount1 = -1000;
|
|
267
|
+
|
|
268
|
+
uint128 realizedSize = wrapper.calculateRealizedSize(isCurrency0, amount0, amount1);
|
|
269
|
+
assertEq(realizedSize, 1000, "should return absolute value of amount1");
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/// @notice Tests realized size for currency1 with positive amount1
|
|
273
|
+
function test_calculateRealizedSize_currency1_positiveAmount_returnsZero() public {
|
|
274
|
+
bool isCurrency0 = false;
|
|
275
|
+
int128 amount0 = -500;
|
|
276
|
+
int128 amount1 = 1000; // Positive
|
|
277
|
+
|
|
278
|
+
uint128 realizedSize = wrapper.calculateRealizedSize(isCurrency0, amount0, amount1);
|
|
279
|
+
assertEq(realizedSize, 0, "should return 0 for positive amount1");
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/// @notice Tests realized size with zero amounts
|
|
283
|
+
function test_calculateRealizedSize_zeroAmounts_returnsZero() public {
|
|
284
|
+
bool isCurrency0 = true;
|
|
285
|
+
int128 amount0 = 0;
|
|
286
|
+
int128 amount1 = 0;
|
|
287
|
+
|
|
288
|
+
uint128 realizedSize = wrapper.calculateRealizedSize(isCurrency0, amount0, amount1);
|
|
289
|
+
assertEq(realizedSize, 0, "should return 0 for zero amounts");
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/// @notice Tests refund when realized < requested
|
|
293
|
+
function test_calculateRefund_partialRealization_returnsRefund() public {
|
|
294
|
+
uint128 realizedSize = 800;
|
|
295
|
+
uint128 requestedSize = 1000;
|
|
296
|
+
|
|
297
|
+
uint128 refunded = wrapper.calculateRefund(realizedSize, requestedSize);
|
|
298
|
+
assertEq(refunded, 200, "should refund difference");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/// @notice Tests refund when realized == requested (no refund)
|
|
302
|
+
function test_calculateRefund_fullRealization_noRefund() public {
|
|
303
|
+
uint128 realizedSize = 1000;
|
|
304
|
+
uint128 requestedSize = 1000;
|
|
305
|
+
|
|
306
|
+
uint128 refunded = wrapper.calculateRefund(realizedSize, requestedSize);
|
|
307
|
+
assertEq(refunded, 0, "should have no refund");
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/// @notice Tests refund when realized > requested (edge case)
|
|
311
|
+
function test_calculateRefund_overRealization_noRefund() public {
|
|
312
|
+
uint128 realizedSize = 1200;
|
|
313
|
+
uint128 requestedSize = 1000;
|
|
314
|
+
|
|
315
|
+
uint128 refunded = wrapper.calculateRefund(realizedSize, requestedSize);
|
|
316
|
+
assertEq(refunded, 0, "should have no refund");
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/// @notice Tests refund calculation reverts on zero realized size
|
|
320
|
+
function test_calculateRefund_zeroRealized_reverts() public {
|
|
321
|
+
uint128 realizedSize = 0;
|
|
322
|
+
uint128 requestedSize = 1000;
|
|
323
|
+
|
|
324
|
+
vm.expectRevert(IZoraLimitOrderBook.ZeroRealizedOrder.selector);
|
|
325
|
+
wrapper.calculateRefund(realizedSize, requestedSize);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/// @notice Tests validation with empty arrays
|
|
329
|
+
function test_validateOrderInputs_emptyArrays_success() public {
|
|
330
|
+
uint256[] memory sizes = new uint256[](0);
|
|
331
|
+
int24[] memory ticks = new int24[](0);
|
|
332
|
+
|
|
333
|
+
uint256 total = wrapper.validateOrderInputs(sizes, ticks, address(0x1234));
|
|
334
|
+
assertEq(total, 0, "total should be 0 for empty arrays");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/// @notice Tests tick calculation with zero spacing (edge case)
|
|
338
|
+
function test_calculateTicks_zeroSpacing_noChange() public {
|
|
339
|
+
bool isCurrency0 = true;
|
|
340
|
+
int24 orderTick = 1000;
|
|
341
|
+
int24 spacing = 0;
|
|
342
|
+
|
|
343
|
+
(int24 tickLower, int24 tickUpper) = wrapper.calculateTicks(isCurrency0, orderTick, spacing);
|
|
344
|
+
|
|
345
|
+
assertEq(tickLower, 1000, "tickLower should be orderTick");
|
|
346
|
+
assertEq(tickUpper, 1000, "tickUpper should equal tickLower with zero spacing");
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/// @notice Tests realized size calculation with large negative values
|
|
350
|
+
function test_calculateRealizedSize_largeNegativeValue_handlesCorrectly() public {
|
|
351
|
+
bool isCurrency0 = true;
|
|
352
|
+
int128 amount0 = -1000000000; // Large negative value
|
|
353
|
+
int128 amount1 = 0;
|
|
354
|
+
|
|
355
|
+
uint128 realizedSize = wrapper.calculateRealizedSize(isCurrency0, amount0, amount1);
|
|
356
|
+
assertEq(realizedSize, 1000000000, "should handle large negative value");
|
|
357
|
+
}
|
|
358
|
+
}
|