Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

startTick == endTick #143

Merged
merged 8 commits into from
Oct 22, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 15 additions & 12 deletions src/Doppler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,15 @@ contract Doppler is BaseHook {
/* Tick checks */
// Starting tick must be greater than ending tick if isToken0
// Ending tick must be greater than starting tick if isToken1
if (_isToken0 && _startingTick <= _endingTick) revert InvalidTickRange();
if (!_isToken0 && _startingTick >= _endingTick) revert InvalidTickRange();
if (_startingTick != _endingTick) {
if (_isToken0 && _startingTick <= _endingTick) revert InvalidTickRange();
if (!_isToken0 && _startingTick >= _endingTick) revert InvalidTickRange();

int24 totalTickDelta = _isToken0 ? _startingTick - _endingTick : _endingTick - _startingTick;
int256 totalEpochs = int256((_endingTime - _startingTime) / _epochLength);
// DA worst case is starting tick - ending tick
if (_gamma * totalEpochs != totalTickDelta) revert InvalidGamma();
}
// Enforce maximum tick spacing
if (_poolKey.tickSpacing > MAX_TICK_SPACING) revert InvalidTickSpacing();

Expand All @@ -116,10 +123,6 @@ contract Doppler is BaseHook {

/* Gamma checks */
// Enforce that the total tick delta is divisible by the total number of epochs
int24 totalTickDelta = _isToken0 ? _startingTick - _endingTick : _endingTick - _startingTick;
int256 totalEpochs = int256((_endingTime - _startingTime) / _epochLength);
// DA worst case is starting tick - ending tick
if (_gamma * totalEpochs != totalTickDelta) revert InvalidGamma();
// Enforce that gamma is divisible by tick spacing
if (_gamma % _poolKey.tickSpacing != 0) revert InvalidGamma();

Expand Down Expand Up @@ -343,7 +346,6 @@ contract Doppler is BaseHook {
* int256(1e18 - FullMath.mulDiv(totalTokensSold_, 1e18, expectedAmountSold)) / 1e18;
} else {
int24 tauTick = startingTick + int24(state.tickAccumulator / 1e18);
Position memory pdSlug = positions[DISCOVERY_SLUG_SALT];

// Safe from overflow since the result is <= gamma which is an int24 already
int24 computedRange = int24(_getGammaShare() * gamma / 1e18);
Expand Down Expand Up @@ -425,9 +427,10 @@ contract Doppler is BaseHook {

SlugData memory lowerSlug =
_computeLowerSlugData(key, requiredProceeds, numeraireAvailable, totalTokensSold_, tickLower, currentTick);
SlugData memory upperSlug = _computeUpperSlugData(key, totalTokensSold_, currentTick, assetAvailable);
(SlugData memory upperSlug, uint256 assetRemaining) = _computeUpperSlugData(key, totalTokensSold_, currentTick, assetAvailable);
SlugData[] memory priceDiscoverySlugs =
_computePriceDiscoverySlugsData(key, upperSlug, tickUpper, assetAvailable);
_computePriceDiscoverySlugsData(key, upperSlug, tickUpper, assetRemaining);

// TODO: If we're not actually modifying liquidity, skip below logic
// TODO: Consider whether we need slippage protection

Expand Down Expand Up @@ -608,7 +611,7 @@ contract Doppler is BaseHook {
uint256 totalTokensSold_,
int24 currentTick,
uint256 assetAvailable
) internal view returns (SlugData memory slug) {
) internal view returns (SlugData memory slug, uint256 assetRemaining) {
int256 tokensSoldDelta = int256(_getExpectedAmountSoldWithEpochOffset(1)) - int256(totalTokensSold_); // compute if we've sold more or less tokens than expected by next epoch

uint256 tokensToLp;
Expand All @@ -633,10 +636,10 @@ contract Doppler is BaseHook {
TickMath.getSqrtPriceAtTick(slug.tickUpper),
tokensToLp
);
assetAvailable -= tokensToLp;
} else {
slug.liquidity = 0;
}
assetRemaining = assetAvailable - tokensToLp;
}

function _computePriceDiscoverySlugsData(
Expand Down Expand Up @@ -811,7 +814,7 @@ contract Doppler is BaseHook {

(, int24 tickUpper) = _getTicksBasedOnState(int24(0), key.tickSpacing);

SlugData memory upperSlug = _computeUpperSlugData(key, 0, tick, numTokensToSell);
(SlugData memory upperSlug, ) = _computeUpperSlugData(key, 0, tick, numTokensToSell);
SlugData[] memory priceDiscoverySlugs =
_computePriceDiscoverySlugsData(key, upperSlug, tickUpper, numTokensToSell);

Expand Down
16 changes: 3 additions & 13 deletions test/integration/Rebalance.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {InvalidTime, SwapBelowRange} from "src/Doppler.sol";
import {BaseTest} from "test/shared/BaseTest.sol";
import {Position} from "../../src/Doppler.sol";


contract RebalanceTest is BaseTest {
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
Expand Down Expand Up @@ -555,7 +556,6 @@ contract RebalanceTest is BaseTest {
vm.warp(hook.getStartingTime());

PoolKey memory poolKey = key;

swapRouter.swap(
// Swap numeraire to asset
// If zeroForOne, we use max price limit (else vice versa)
Expand All @@ -565,7 +565,7 @@ contract RebalanceTest is BaseTest {
""
);

(uint40 lastEpoch, int256 tickAccumulator, uint256 totalTokensSold,, uint256 totalTokensSoldLastEpoch,) =
(uint40 lastEpoch,, uint256 totalTokensSold,, uint256 totalTokensSoldLastEpoch,) =
hook.state();

assertEq(lastEpoch, 1);
Expand Down Expand Up @@ -596,6 +596,7 @@ contract RebalanceTest is BaseTest {
assertEq(totalTokensSoldLastEpoch2, 1 ether);

vm.warp(hook.getStartingTime() + hook.getEpochLength() * 2); // Next epoch
SlugVis.visualizeSlugs(hook, poolKey.toId(), "test", block.timestamp);

// We swap again just to trigger the rebalancing logic in the new epoch
swapRouter.swap(
Expand Down Expand Up @@ -631,10 +632,6 @@ contract RebalanceTest is BaseTest {
// Get global lower and upper ticks
(, int24 tickUpper) = hook.getTicksBasedOnState(tickAccumulator3, key.tickSpacing);

// Get current tick
PoolId poolId = key.toId();
int24 currentTick = hook.getCurrentTick(poolId);

// Slugs must be inline and continuous
assertEq(lowerSlug.tickUpper, upperSlug.tickLower, "lowerSlug.tickUpper != upperSlug.tickLower");

Expand Down Expand Up @@ -662,13 +659,6 @@ contract RebalanceTest is BaseTest {
assertGt(priceDiscoverySlugs[i].liquidity, 0);
}

(,, uint24 protocolFee, uint24 lpFee) = manager.getSlot0(key.toId());
// Lower slug should be unset with ticks at the current price if the fee is 0
if (protocolFee == 0 && lpFee == 0) {
assertEq(lowerSlug.tickLower, lowerSlug.tickUpper, "lowerSlug.tickLower != lowerSlug.tickUpper");
assertEq(lowerSlug.liquidity, 0, "lowerSlug.liquidity != 0");
assertEq(lowerSlug.tickUpper, currentTick, "lowerSlug.tickUpper != currentTick");
}
// Upper and price discovery slugs must be set
assertNotEq(upperSlug.liquidity, 0);
}
Expand Down
4 changes: 2 additions & 2 deletions test/shared/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -152,8 +152,8 @@ contract BaseTest is Test, Deployers {

// isToken0 ? startTick > endTick : endTick > startTick
// In both cases, price(startTick) > price(endTick)
startTick = isToken0 ? DEFAULT_START_TICK : -DEFAULT_START_TICK;
endTick = isToken0 ? -DEFAULT_END_TICK : DEFAULT_END_TICK;
startTick = isToken0 ? int24(vm.envOr("START_TICK", DEFAULT_START_TICK)) : int24(vm.envOr("START_TICK", -DEFAULT_START_TICK));
endTick = isToken0 ? int24(vm.envOr("END_TICK", -DEFAULT_END_TICK)) : int24(vm.envOr("END_TICK", DEFAULT_END_TICK));

// Default to feeless case because it's easier to reason about
config.fee = uint24(vm.envOr("FEE", uint24(0)));
Expand Down
2 changes: 1 addition & 1 deletion test/shared/DopplerImplementation.sol
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ contract DopplerImplementation is Doppler {
uint256 totalTokensSold,
int24 currentTick,
uint256 assetAvailable
) public view returns (SlugData memory) {
) public view returns (SlugData memory, uint256 assetRemaining) {
return _computeUpperSlugData(poolKey, totalTokensSold, currentTick, assetAvailable);
}

Expand Down
33 changes: 16 additions & 17 deletions test/unit/Constructor.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ contract ConstructorTest is BaseTest {
if (selector != 0) {
vm.expectRevert(selector);
}

int24 startTick = _startTick != 0 ? _startTick : isToken0 ? DEFAULT_START_TICK : -DEFAULT_START_TICK;
int24 endTick = _endTick != 0 ? _endTick : isToken0 ? -DEFAULT_END_TICK : DEFAULT_END_TICK;
deployCodeTo(
"DopplerImplementation.sol:DopplerImplementation",
abi.encode(
Expand All @@ -64,8 +67,8 @@ contract ConstructorTest is BaseTest {
config.maximumProceeds,
config.startingTime,
config.endingTime,
_startTick,
_endTick,
startTick,
endTick,
config.epochLength,
config.gamma,
isToken0,
Expand Down Expand Up @@ -124,15 +127,15 @@ contract ConstructorTest is BaseTest {
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
config.tickSpacing = int24(maxTickSpacing + 1);

deployDoppler(InvalidTickSpacing.selector, config, 200, 100, true);
deployDoppler(InvalidTickSpacing.selector, config, 0, 0, true);
}

function testConstructor_RevertsInvalidTimeRange_WhenStartingTimeGreaterThanOrEqualToEndingTime() public {
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
config.startingTime = 1000;
config.endingTime = 1000;

deployDoppler(InvalidTimeRange.selector, config, 200, 100, true);
deployDoppler(InvalidTimeRange.selector, config, DEFAULT_START_TICK, DEFAULT_START_TICK, true);
}

function testConstructor_RevertsInvalidGamma_WhenGammaCalculationZero() public {
Expand All @@ -142,23 +145,21 @@ contract ConstructorTest is BaseTest {
config.epochLength = 1;
config.gamma = 0;

deployDoppler(InvalidGamma.selector, config, 200, 100, true);
deployDoppler(InvalidGamma.selector, config, 0, 0, true);
}

function testConstructor_RevertsInvalidEpochLength_WhenTimeDeltaNotDivisibleByEpochLength() public {
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
config.startingTime = 1000;
config.endingTime = 5000;
config.epochLength = 3000;

deployDoppler(InvalidEpochLength.selector, config, 200, 100, true);
deployDoppler(InvalidEpochLength.selector, config, DEFAULT_START_TICK, DEFAULT_START_TICK, true);
}

function testConstructor_RevertsInvalidGamma_WhenGammaNotDivisibleByTickSpacing() public {
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
config.gamma += 1;

deployDoppler(InvalidGamma.selector, config, 200, 100, true);
deployDoppler(InvalidGamma.selector, config, 0, 0, true);
}

function testConstructor_RevertsInvalidGamma_WhenGammaTimesTotalEpochsNotDivisibleByTotalTickDelta() public {
Expand All @@ -168,45 +169,43 @@ contract ConstructorTest is BaseTest {
config.endingTime = 5000;
config.epochLength = 1000;

deployDoppler(InvalidGamma.selector, config, 200, 100, true);
deployDoppler(InvalidGamma.selector, config, 0, 0, true);
}

function testConstructor_RevertsInvalidGamma_WhenGammaIsNegative() public {
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
config.gamma = -1;

deployDoppler(InvalidGamma.selector, config, 200, 100, true);
deployDoppler(InvalidGamma.selector, config, 0, 0, true);
}

function testConstructor_RevertsInvalidNumPDSlugs_WithZeroSlugs() public {
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
config.numPDSlugs = 0;

deployDoppler(InvalidNumPDSlugs.selector, config, 0, -172_800, true);
deployDoppler(InvalidNumPDSlugs.selector, config, 0, 0, true);
}

function testConstructor_RevertsInvalidNumPDSlugs_GreaterThanMax() public {
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
config.numPDSlugs = MAX_PRICE_DISCOVERY_SLUGS + 1;

deployDoppler(InvalidNumPDSlugs.selector, config, 0, -172_800, true);
deployDoppler(InvalidNumPDSlugs.selector, config, 0, 0, true);
}

function testConstructor_RevertsInvalidProceedLimits_WhenMinimumProceedsGreaterThanMaximumProceeds() public {
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
config.minimumProceeds = 100;
config.maximumProceeds = 0;

deployDoppler(InvalidProceedLimits.selector, config, 0, -172_800, true);
deployDoppler(InvalidProceedLimits.selector, config, 0, 0, true);
}

function testConstructor_Succeeds_WithValidParameters() public {
bool _isToken0 = true;
int24 _startTick = 0;
int24 _endTick = -172_800;

DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;

deployDoppler(0, config, _startTick, _endTick, _isToken0);
deployDoppler(0, config, 0, 0, _isToken0);
}
}
Loading