Skip to content

Commit

Permalink
startTick == endTick (#143)
Browse files Browse the repository at this point in the history
* feat: allow equal starting end ending ticks

* fix: assetAvailable bug

* fix: maxDutchAuction test
  • Loading branch information
kinrezC authored Oct 22, 2024
1 parent 630a799 commit d5cfa35
Show file tree
Hide file tree
Showing 5 changed files with 38 additions and 46 deletions.
29 changes: 16 additions & 13 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,9 +814,9 @@ contract Doppler is BaseHook {

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

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

BalanceDelta finalDelta;

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);
}
}

0 comments on commit d5cfa35

Please sign in to comment.