From 487761b1bd6c18911e9cf214bbaad2a26d8684cd Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 24 Oct 2024 13:41:18 -0400 Subject: [PATCH 1/7] avoid first epoch rebalance --- src/Doppler.sol | 80 +++++++++------------------- test/integration/Rebalance.t.sol | 1 - test/invariant/DopplerInvariants.sol | 36 ++++++------- test/shared/BaseTest.sol | 11 ++-- test/unit/AfterInitialize.t.sol | 7 +-- test/unit/Swap.sol | 14 ++++- 6 files changed, 64 insertions(+), 85 deletions(-) diff --git a/src/Doppler.sol b/src/Doppler.sol index 7a90f571..11eb78c6 100644 --- a/src/Doppler.sol +++ b/src/Doppler.sol @@ -92,6 +92,8 @@ contract Doppler is BaseHook { bool _isToken0, uint256 _numPDSlugs ) BaseHook(_poolManager) { + // Check that the current time is before the starting time + if (block.timestamp > _startingTime) revert InvalidTime(); /* Tick checks */ // Starting tick must be greater than ending tick if isToken0 // Ending tick must be greater than starting tick if isToken1 @@ -877,71 +879,35 @@ contract Doppler is BaseHook { function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { CallbackData memory callbackData = abi.decode(data, (CallbackData)); (PoolKey memory key,, int24 tick) = (callbackData.key, callbackData.sender, callbackData.tick); + int256 accumulatorDelta = _getMaxTickDeltaPerEpoch() * 1; + state.tickAccumulator = state.tickAccumulator + accumulatorDelta; + state.lastEpoch = 1; - (, int24 tickUpper) = _getTicksBasedOnState(int24(0), key.tickSpacing); + int24 currentTick = _alignComputedTickWithTickSpacing(tick + int24(accumulatorDelta / 1e18), key.tickSpacing); + (, int24 tickUpper) = _getTicksBasedOnState(state.tickAccumulator, key.tickSpacing); + uint160 sqrtPriceNext = TickMath.getSqrtPriceAtTick(currentTick); + uint160 sqrtPriceCurrent = TickMath.getSqrtPriceAtTick(tick); - // Compute slugs to place - (SlugData memory upperSlug, uint256 assetRemaining) = _computeUpperSlugData(key, 0, tick, numTokensToSell); + SlugData memory lowerSlug = SlugData({tickLower: tick, tickUpper: tick, liquidity: 0}); + (SlugData memory upperSlug, uint256 assetRemaining) = + _computeUpperSlugData(key, 0, currentTick, numTokensToSell); SlugData[] memory priceDiscoverySlugs = _computePriceDiscoverySlugsData(key, upperSlug, tickUpper, assetRemaining); - BalanceDelta finalDelta; - - // Place upper slug - if (upperSlug.liquidity != 0) { - (BalanceDelta callerDelta,) = poolManager.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: isToken0 ? upperSlug.tickLower : upperSlug.tickUpper, - tickUpper: isToken0 ? upperSlug.tickUpper : upperSlug.tickLower, - liquidityDelta: int128(upperSlug.liquidity), - salt: UPPER_SLUG_SALT - }), - "" - ); - finalDelta = add(finalDelta, callerDelta); - } - - // Place price discovery slug(s) - for (uint256 i; i < priceDiscoverySlugs.length; ++i) { - if (priceDiscoverySlugs[i].liquidity != 0) { - (BalanceDelta callerDelta,) = poolManager.modifyLiquidity( - key, - IPoolManager.ModifyLiquidityParams({ - tickLower: isToken0 ? priceDiscoverySlugs[i].tickLower : priceDiscoverySlugs[i].tickUpper, - tickUpper: isToken0 ? priceDiscoverySlugs[i].tickUpper : priceDiscoverySlugs[i].tickLower, - liquidityDelta: int128(priceDiscoverySlugs[i].liquidity), - salt: bytes32(uint256(3 + i)) - }), - "" - ); - finalDelta = add(finalDelta, callerDelta); - } - } - - // Provide tokens to the pool - if (isToken0) { - poolManager.sync(key.currency0); - key.currency0.transfer(address(poolManager), uint256(int256(-finalDelta.amount0()))); - } else { - poolManager.sync(key.currency1); - key.currency1.transfer(address(poolManager), uint256(int256(-finalDelta.amount1()))); - } - - // Update position storage Position[] memory newPositions = new Position[](2 + numPDSlugs); - newPositions[0] = - Position({tickLower: tick, tickUpper: tick, liquidity: 0, salt: uint8(uint256(LOWER_SLUG_SALT))}); + + newPositions[0] = Position({ + tickLower: lowerSlug.tickLower, + tickUpper: lowerSlug.tickUpper, + liquidity: lowerSlug.liquidity, + salt: uint8(uint256(LOWER_SLUG_SALT)) + }); newPositions[1] = Position({ tickLower: upperSlug.tickLower, tickUpper: upperSlug.tickUpper, liquidity: upperSlug.liquidity, salt: uint8(uint256(UPPER_SLUG_SALT)) }); - - positions[LOWER_SLUG_SALT] = newPositions[0]; - positions[UPPER_SLUG_SALT] = newPositions[1]; - for (uint256 i; i < priceDiscoverySlugs.length; ++i) { newPositions[2 + i] = Position({ tickLower: priceDiscoverySlugs[i].tickLower, @@ -949,12 +915,16 @@ contract Doppler is BaseHook { liquidity: priceDiscoverySlugs[i].liquidity, salt: uint8(3 + i) }); + } + + _update(newPositions, sqrtPriceCurrent, sqrtPriceNext, key); + positions[LOWER_SLUG_SALT] = newPositions[0]; + positions[UPPER_SLUG_SALT] = newPositions[1]; + for (uint256 i; i < numPDSlugs; ++i) { positions[bytes32(uint256(3 + i))] = newPositions[2 + i]; } - poolManager.settle(); - return new bytes(0); } diff --git a/test/integration/Rebalance.t.sol b/test/integration/Rebalance.t.sol index ac4df8e3..cfa783fd 100644 --- a/test/integration/Rebalance.t.sol +++ b/test/integration/Rebalance.t.sol @@ -780,7 +780,6 @@ contract RebalanceTest is BaseTest { upperSlug.liquidity ) * 9 / 10; - uint256 amount0ToSwap = LiquidityAmounts.getAmount0ForLiquidity( TickMath.getSqrtPriceAtTick(upperSlug.tickLower), TickMath.getSqrtPriceAtTick(upperSlug.tickUpper), diff --git a/test/invariant/DopplerInvariants.sol b/test/invariant/DopplerInvariants.sol index a395023e..4649d9f0 100644 --- a/test/invariant/DopplerInvariants.sol +++ b/test/invariant/DopplerInvariants.sol @@ -6,27 +6,27 @@ import {BaseTest} from "test/shared/BaseTest.sol"; import {DopplerHandler} from "test/invariant/DopplerHandler.sol"; contract DopplerInvariantsTest is BaseTest { - // DopplerHandler public handler; +// DopplerHandler public handler; - // function setUp() public override { - // super.setUp(); - // handler = new DopplerHandler(key, hook, router, isToken0, usingEth); +// function setUp() public override { +// super.setUp(); +// handler = new DopplerHandler(key, hook, router, isToken0, usingEth); - // bytes4[] memory selectors = new bytes4[](1); - // selectors[0] = handler.buyExactAmountIn.selector; +// bytes4[] memory selectors = new bytes4[](1); +// selectors[0] = handler.buyExactAmountIn.selector; - // targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); - // targetContract(address(handler)); - // } +// targetSelector(FuzzSelector({addr: address(handler), selectors: selectors})); +// targetContract(address(handler)); +// } - // function afterInvariant() public view { - // console.log("Handler address", address(handler)); - // console.log("Calls: ", handler.totalCalls()); - // console.log("buyExactAmountIn: ", handler.calls(handler.buyExactAmountIn.selector)); - // } +// function afterInvariant() public view { +// console.log("Handler address", address(handler)); +// console.log("Calls: ", handler.totalCalls()); +// console.log("buyExactAmountIn: ", handler.calls(handler.buyExactAmountIn.selector)); +// } - // /// forge-config: default.invariant.fail-on-revert = true - // function invariant_works() public { - // assertTrue(true); - // } +// /// forge-config: default.invariant.fail-on-revert = true +// function invariant_works() public { +// assertTrue(true); +// } } diff --git a/test/shared/BaseTest.sol b/test/shared/BaseTest.sol index 4da29862..be2a88df 100644 --- a/test/shared/BaseTest.sol +++ b/test/shared/BaseTest.sol @@ -366,9 +366,8 @@ contract BaseTest is Test, Deployers { } uint256 approveAmount = uint256(-amount); TestERC20(asset).approve(address(swapRouter), approveAmount); - vm.expectRevert(abi.encodeWithSelector( - Hooks.Wrap__FailedHookCall.selector, hook, abi.encodeWithSelector(selector) - ) + vm.expectRevert( + abi.encodeWithSelector(Hooks.Wrap__FailedHookCall.selector, hook, abi.encodeWithSelector(selector)) ); swapRouter.swap( key, @@ -376,7 +375,6 @@ contract BaseTest is Test, Deployers { PoolSwapTest.TestSettings(true, false), "" ); - } function buyExpectRevert(int256 amount, bytes4 selector) public { @@ -393,9 +391,8 @@ contract BaseTest is Test, Deployers { TestERC20(numeraire).approve(address(swapRouter), uint256(mintAmount)); } - vm.expectRevert(abi.encodeWithSelector( - Hooks.Wrap__FailedHookCall.selector, hook, abi.encodeWithSelector(selector) - ) + vm.expectRevert( + abi.encodeWithSelector(Hooks.Wrap__FailedHookCall.selector, hook, abi.encodeWithSelector(selector)) ); swapRouter.swap{value: usingEth ? mintAmount : 0}( key, diff --git a/test/unit/AfterInitialize.t.sol b/test/unit/AfterInitialize.t.sol index 0e51e092..60ffb389 100644 --- a/test/unit/AfterInitialize.t.sol +++ b/test/unit/AfterInitialize.t.sol @@ -63,9 +63,10 @@ contract AfterInitializeTest is BaseTest { // Assert that upper and price discovery slugs have liquidity assertNotEq(upperSlug.liquidity, 0); - // Assert that lower slug has both ticks as the startingTick - assertEq(lowerSlug.tickLower, hook.getStartingTick()); - assertEq(lowerSlug.tickUpper, hook.getStartingTick()); + // Assert that lower slug has both ticks as the startingTick offset by one max DA + int24 maxTickDelta = int24(hook.getMaxTickDeltaPerEpoch() / 1e18); + assertEq(lowerSlug.tickLower, hook.getStartingTick() - maxTickDelta); + assertEq(lowerSlug.tickUpper, hook.getStartingTick() - maxTickDelta); // Assert that lower slug has no liquidity assertEq(lowerSlug.liquidity, 0); diff --git a/test/unit/Swap.sol b/test/unit/Swap.sol index 30ab00d2..042690e7 100644 --- a/test/unit/Swap.sol +++ b/test/unit/Swap.sol @@ -24,9 +24,10 @@ contract SwapTest is BaseTest { // NOTE: when testing conditions where we expect a revert using buy/sellExpectRevert, // we need to pass in a negative amount to specify an exactIn swap. // otherwise, the quoter will attempt to calculate an exactOut amount, which will fail. + function test_swap_RevertsBeforeStartTime() public { vm.warp(hook.getStartingTime() - 1); // 1 second before the start time - + buyExpectRevert(-1 ether, InvalidTime.selector); } @@ -178,4 +179,15 @@ contract SwapTest is BaseTest { sellExpectRevert(-0.6 ether, SwapBelowRange.selector); } + + function test_swap_DoesNotRebalanceInTheFirstEpoch() public { + (, int256 tickAccumulator,,,,) = hook.state(); + + vm.warp(hook.getStartingTime()); + + buy(1 ether); + (, int256 tickAccumulator2,,,,) = hook.state(); + + assertEq(tickAccumulator, tickAccumulator2); + } } From f2477a2c36f4b2bffd1c97af4ff208ff35cc6e02 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 24 Oct 2024 13:49:27 -0400 Subject: [PATCH 2/7] test: fix afterInitialize --- src/Doppler.sol | 2 +- test/unit/AfterInitialize.t.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Doppler.sol b/src/Doppler.sol index 11eb78c6..532bf28c 100644 --- a/src/Doppler.sol +++ b/src/Doppler.sol @@ -888,7 +888,7 @@ contract Doppler is BaseHook { uint160 sqrtPriceNext = TickMath.getSqrtPriceAtTick(currentTick); uint160 sqrtPriceCurrent = TickMath.getSqrtPriceAtTick(tick); - SlugData memory lowerSlug = SlugData({tickLower: tick, tickUpper: tick, liquidity: 0}); + SlugData memory lowerSlug = SlugData({tickLower: currentTick, tickUpper: currentTick, liquidity: 0}); (SlugData memory upperSlug, uint256 assetRemaining) = _computeUpperSlugData(key, 0, currentTick, numTokensToSell); SlugData[] memory priceDiscoverySlugs = diff --git a/test/unit/AfterInitialize.t.sol b/test/unit/AfterInitialize.t.sol index 60ffb389..71513b6e 100644 --- a/test/unit/AfterInitialize.t.sol +++ b/test/unit/AfterInitialize.t.sol @@ -65,8 +65,8 @@ contract AfterInitializeTest is BaseTest { // Assert that lower slug has both ticks as the startingTick offset by one max DA int24 maxTickDelta = int24(hook.getMaxTickDeltaPerEpoch() / 1e18); - assertEq(lowerSlug.tickLower, hook.getStartingTick() - maxTickDelta); - assertEq(lowerSlug.tickUpper, hook.getStartingTick() - maxTickDelta); + assertEq(lowerSlug.tickLower, hook.getStartingTick() + maxTickDelta); + assertEq(lowerSlug.tickUpper, hook.getStartingTick() + maxTickDelta); // Assert that lower slug has no liquidity assertEq(lowerSlug.liquidity, 0); From b9e2d6174ca0c5d3332b08b073c2df6e30c366cc Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 24 Oct 2024 13:57:21 -0400 Subject: [PATCH 3/7] fix: disallow swaps if block.timestamp == endingTime --- src/Doppler.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Doppler.sol b/src/Doppler.sol index 532bf28c..72431a5c 100644 --- a/src/Doppler.sol +++ b/src/Doppler.sol @@ -176,7 +176,7 @@ contract Doppler is BaseHook { } // Only check proceeds if we're after maturity and we haven't already triggered insufficient proceeds - if (block.timestamp > endingTime && !insufficientProceeds) { + if (block.timestamp >= endingTime && !insufficientProceeds) { // If we haven't raised the minimum proceeds, we allow for all asset tokens to be sold back into // the curve at the average clearing price if (state.totalProceeds < minimumProceeds) { @@ -888,6 +888,7 @@ contract Doppler is BaseHook { uint160 sqrtPriceNext = TickMath.getSqrtPriceAtTick(currentTick); uint160 sqrtPriceCurrent = TickMath.getSqrtPriceAtTick(tick); + // set the tickLower and tickUpper to the current tick as this is the default behavior when requiredProceeds and totalProceeds are 0 SlugData memory lowerSlug = SlugData({tickLower: currentTick, tickUpper: currentTick, liquidity: 0}); (SlugData memory upperSlug, uint256 assetRemaining) = _computeUpperSlugData(key, 0, currentTick, numTokensToSell); From 639729f1915db635c02762be8674bb5d7a2abeb7 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 24 Oct 2024 14:04:09 -0400 Subject: [PATCH 4/7] fix: fullflow last swap --- test/integration/Rebalance.t.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/integration/Rebalance.t.sol b/test/integration/Rebalance.t.sol index cfa783fd..94027aae 100644 --- a/test/integration/Rebalance.t.sol +++ b/test/integration/Rebalance.t.sol @@ -1144,7 +1144,7 @@ contract RebalanceTest is BaseTest { // Go to very end time vm.warp( hook.getStartingTime() - + hook.getEpochLength() * ((hook.getEndingTime() - hook.getStartingTime()) / hook.getEpochLength()) + + hook.getEpochLength() * ((hook.getEndingTime() - hook.getStartingTime()) / hook.getEpochLength()) - 1 ); uint256 numTokensToSell = hook.getNumTokensToSell(); From cd7a75f259881f9ea32b2af9f26df42f5ef41e3b Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 24 Oct 2024 15:06:18 -0400 Subject: [PATCH 5/7] virtual da in 0th epoch --- src/Doppler.sol | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/Doppler.sol b/src/Doppler.sol index 72431a5c..b03bf0a1 100644 --- a/src/Doppler.sol +++ b/src/Doppler.sol @@ -879,8 +879,7 @@ contract Doppler is BaseHook { function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { CallbackData memory callbackData = abi.decode(data, (CallbackData)); (PoolKey memory key,, int24 tick) = (callbackData.key, callbackData.sender, callbackData.tick); - int256 accumulatorDelta = _getMaxTickDeltaPerEpoch() * 1; - state.tickAccumulator = state.tickAccumulator + accumulatorDelta; + int256 accumulatorDelta = _getMaxTickDeltaPerEpoch(); state.lastEpoch = 1; int24 currentTick = _alignComputedTickWithTickSpacing(tick + int24(accumulatorDelta / 1e18), key.tickSpacing); From 1637ddc4c62c005c73f962ec7f5f8aa965904400 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 24 Oct 2024 15:15:01 -0400 Subject: [PATCH 6/7] avoid da in first epoch: --- src/Doppler.sol | 11 +++++------ test/integration/Rebalance.t.sol | 2 +- test/unit/AfterInitialize.t.sol | 6 ++---- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/src/Doppler.sol b/src/Doppler.sol index b03bf0a1..0639589c 100644 --- a/src/Doppler.sol +++ b/src/Doppler.sol @@ -17,6 +17,7 @@ import {FixedPoint96} from "v4-periphery/lib/v4-core/src/libraries/FixedPoint96. import {TransientStateLibrary} from "v4-periphery/lib/v4-core/src/libraries/TransientStateLibrary.sol"; import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol"; import {ProtocolFeeLibrary} from "v4-periphery/lib/v4-core/src/libraries/ProtocolFeeLibrary.sol"; +import "forge-std/console.sol"; struct SlugData { int24 tickLower; @@ -879,18 +880,16 @@ contract Doppler is BaseHook { function _unlockCallback(bytes calldata data) internal override returns (bytes memory) { CallbackData memory callbackData = abi.decode(data, (CallbackData)); (PoolKey memory key,, int24 tick) = (callbackData.key, callbackData.sender, callbackData.tick); - int256 accumulatorDelta = _getMaxTickDeltaPerEpoch(); state.lastEpoch = 1; - int24 currentTick = _alignComputedTickWithTickSpacing(tick + int24(accumulatorDelta / 1e18), key.tickSpacing); - (, int24 tickUpper) = _getTicksBasedOnState(state.tickAccumulator, key.tickSpacing); - uint160 sqrtPriceNext = TickMath.getSqrtPriceAtTick(currentTick); + (, int24 tickUpper) = _getTicksBasedOnState(0, key.tickSpacing); + uint160 sqrtPriceNext = TickMath.getSqrtPriceAtTick(tick); uint160 sqrtPriceCurrent = TickMath.getSqrtPriceAtTick(tick); // set the tickLower and tickUpper to the current tick as this is the default behavior when requiredProceeds and totalProceeds are 0 - SlugData memory lowerSlug = SlugData({tickLower: currentTick, tickUpper: currentTick, liquidity: 0}); + SlugData memory lowerSlug = SlugData({tickLower: tick, tickUpper: tick, liquidity: 0}); (SlugData memory upperSlug, uint256 assetRemaining) = - _computeUpperSlugData(key, 0, currentTick, numTokensToSell); + _computeUpperSlugData(key, 0, tick, numTokensToSell); SlugData[] memory priceDiscoverySlugs = _computePriceDiscoverySlugsData(key, upperSlug, tickUpper, assetRemaining); diff --git a/test/integration/Rebalance.t.sol b/test/integration/Rebalance.t.sol index 94027aae..4fe88dc2 100644 --- a/test/integration/Rebalance.t.sol +++ b/test/integration/Rebalance.t.sol @@ -840,7 +840,7 @@ contract RebalanceTest is BaseTest { int256 maxTickDeltaPerEpoch = hook.getMaxTickDeltaPerEpoch(); // Assert that we've done three epochs worth of max dutch auctioning - assertEq(tickAccumulator, maxTickDeltaPerEpoch * 4, "first swap: tickAccumulator != maxTickDeltaPerEpoch * 4"); + assertEq(tickAccumulator, maxTickDeltaPerEpoch * 3, "first swap: tickAccumulator != maxTickDeltaPerEpoch * 4"); // Get positions Position memory lowerSlug = hook.getPositions(bytes32(uint256(1))); diff --git a/test/unit/AfterInitialize.t.sol b/test/unit/AfterInitialize.t.sol index 71513b6e..1e58d646 100644 --- a/test/unit/AfterInitialize.t.sol +++ b/test/unit/AfterInitialize.t.sol @@ -63,10 +63,8 @@ contract AfterInitializeTest is BaseTest { // Assert that upper and price discovery slugs have liquidity assertNotEq(upperSlug.liquidity, 0); - // Assert that lower slug has both ticks as the startingTick offset by one max DA - int24 maxTickDelta = int24(hook.getMaxTickDeltaPerEpoch() / 1e18); - assertEq(lowerSlug.tickLower, hook.getStartingTick() + maxTickDelta); - assertEq(lowerSlug.tickUpper, hook.getStartingTick() + maxTickDelta); + assertEq(lowerSlug.tickLower, hook.getStartingTick()); + assertEq(lowerSlug.tickUpper, hook.getStartingTick()); // Assert that lower slug has no liquidity assertEq(lowerSlug.liquidity, 0); From 4b8c8e325d9023ecb86019b22978142f1dff93c2 Mon Sep 17 00:00:00 2001 From: kinrezc Date: Thu, 24 Oct 2024 15:16:54 -0400 Subject: [PATCH 7/7] cleanup + fmt --- src/Doppler.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/Doppler.sol b/src/Doppler.sol index 0639589c..9cfaf3af 100644 --- a/src/Doppler.sol +++ b/src/Doppler.sol @@ -17,7 +17,6 @@ import {FixedPoint96} from "v4-periphery/lib/v4-core/src/libraries/FixedPoint96. import {TransientStateLibrary} from "v4-periphery/lib/v4-core/src/libraries/TransientStateLibrary.sol"; import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol"; import {ProtocolFeeLibrary} from "v4-periphery/lib/v4-core/src/libraries/ProtocolFeeLibrary.sol"; -import "forge-std/console.sol"; struct SlugData { int24 tickLower; @@ -226,7 +225,7 @@ contract Doppler is BaseHook { } else { revert InvalidSwapAfterMaturitySufficientProceeds(); } - } + } // If startTime < block.timestamp < endTime and !earlyExit and !insufficientProceeds, we rebalance if (!insufficientProceeds) { _rebalance(key); @@ -888,8 +887,7 @@ contract Doppler is BaseHook { // set the tickLower and tickUpper to the current tick as this is the default behavior when requiredProceeds and totalProceeds are 0 SlugData memory lowerSlug = SlugData({tickLower: tick, tickUpper: tick, liquidity: 0}); - (SlugData memory upperSlug, uint256 assetRemaining) = - _computeUpperSlugData(key, 0, tick, numTokensToSell); + (SlugData memory upperSlug, uint256 assetRemaining) = _computeUpperSlugData(key, 0, tick, numTokensToSell); SlugData[] memory priceDiscoverySlugs = _computePriceDiscoverySlugsData(key, upperSlug, tickUpper, assetRemaining);