diff --git a/src/Doppler.sol b/src/Doppler.sol index 7a90f571..9cfaf3af 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 @@ -174,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) { @@ -223,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); @@ -877,71 +879,32 @@ 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); + state.lastEpoch = 1; - (, int24 tickUpper) = _getTicksBasedOnState(int24(0), key.tickSpacing); + (, int24 tickUpper) = _getTicksBasedOnState(0, key.tickSpacing); + uint160 sqrtPriceNext = TickMath.getSqrtPriceAtTick(tick); + uint160 sqrtPriceCurrent = TickMath.getSqrtPriceAtTick(tick); - // Compute slugs to place + // 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 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 +912,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..4fe88dc2 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), @@ -841,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))); @@ -1145,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(); 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..1e58d646 100644 --- a/test/unit/AfterInitialize.t.sol +++ b/test/unit/AfterInitialize.t.sol @@ -63,7 +63,6 @@ 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()); 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); + } }