Skip to content

Commit d5cfa35

Browse files
authored
startTick == endTick (#143)
* feat: allow equal starting end ending ticks * fix: assetAvailable bug * fix: maxDutchAuction test
1 parent 630a799 commit d5cfa35

File tree

5 files changed

+38
-46
lines changed

5 files changed

+38
-46
lines changed

src/Doppler.sol

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,15 @@ contract Doppler is BaseHook {
9696
/* Tick checks */
9797
// Starting tick must be greater than ending tick if isToken0
9898
// Ending tick must be greater than starting tick if isToken1
99-
if (_isToken0 && _startingTick <= _endingTick) revert InvalidTickRange();
100-
if (!_isToken0 && _startingTick >= _endingTick) revert InvalidTickRange();
99+
if (_startingTick != _endingTick) {
100+
if (_isToken0 && _startingTick <= _endingTick) revert InvalidTickRange();
101+
if (!_isToken0 && _startingTick >= _endingTick) revert InvalidTickRange();
102+
103+
int24 totalTickDelta = _isToken0 ? _startingTick - _endingTick : _endingTick - _startingTick;
104+
int256 totalEpochs = int256((_endingTime - _startingTime) / _epochLength);
105+
// DA worst case is starting tick - ending tick
106+
if (_gamma * totalEpochs != totalTickDelta) revert InvalidGamma();
107+
}
101108
// Enforce maximum tick spacing
102109
if (_poolKey.tickSpacing > MAX_TICK_SPACING) revert InvalidTickSpacing();
103110

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

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

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

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

426428
SlugData memory lowerSlug =
427429
_computeLowerSlugData(key, requiredProceeds, numeraireAvailable, totalTokensSold_, tickLower, currentTick);
428-
SlugData memory upperSlug = _computeUpperSlugData(key, totalTokensSold_, currentTick, assetAvailable);
430+
(SlugData memory upperSlug, uint256 assetRemaining) = _computeUpperSlugData(key, totalTokensSold_, currentTick, assetAvailable);
429431
SlugData[] memory priceDiscoverySlugs =
430-
_computePriceDiscoverySlugsData(key, upperSlug, tickUpper, assetAvailable);
432+
_computePriceDiscoverySlugsData(key, upperSlug, tickUpper, assetRemaining);
433+
431434
// TODO: If we're not actually modifying liquidity, skip below logic
432435
// TODO: Consider whether we need slippage protection
433436

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

614617
uint256 tokensToLp;
@@ -633,10 +636,10 @@ contract Doppler is BaseHook {
633636
TickMath.getSqrtPriceAtTick(slug.tickUpper),
634637
tokensToLp
635638
);
636-
assetAvailable -= tokensToLp;
637639
} else {
638640
slug.liquidity = 0;
639641
}
642+
assetRemaining = assetAvailable - tokensToLp;
640643
}
641644

642645
function _computePriceDiscoverySlugsData(
@@ -811,9 +814,9 @@ contract Doppler is BaseHook {
811814

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

814-
SlugData memory upperSlug = _computeUpperSlugData(key, 0, tick, numTokensToSell);
817+
(SlugData memory upperSlug, uint256 assetRemaining) = _computeUpperSlugData(key, 0, tick, numTokensToSell);
815818
SlugData[] memory priceDiscoverySlugs =
816-
_computePriceDiscoverySlugsData(key, upperSlug, tickUpper, numTokensToSell);
819+
_computePriceDiscoverySlugsData(key, upperSlug, tickUpper, assetRemaining);
817820

818821
BalanceDelta finalDelta;
819822

test/integration/Rebalance.t.sol

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import {InvalidTime, SwapBelowRange} from "src/Doppler.sol";
2323
import {BaseTest} from "test/shared/BaseTest.sol";
2424
import {Position} from "../../src/Doppler.sol";
2525

26+
2627
contract RebalanceTest is BaseTest {
2728
using PoolIdLibrary for PoolKey;
2829
using StateLibrary for IPoolManager;
@@ -555,7 +556,6 @@ contract RebalanceTest is BaseTest {
555556
vm.warp(hook.getStartingTime());
556557

557558
PoolKey memory poolKey = key;
558-
559559
swapRouter.swap(
560560
// Swap numeraire to asset
561561
// If zeroForOne, we use max price limit (else vice versa)
@@ -565,7 +565,7 @@ contract RebalanceTest is BaseTest {
565565
""
566566
);
567567

568-
(uint40 lastEpoch, int256 tickAccumulator, uint256 totalTokensSold,, uint256 totalTokensSoldLastEpoch,) =
568+
(uint40 lastEpoch,, uint256 totalTokensSold,, uint256 totalTokensSoldLastEpoch,) =
569569
hook.state();
570570

571571
assertEq(lastEpoch, 1);
@@ -596,6 +596,7 @@ contract RebalanceTest is BaseTest {
596596
assertEq(totalTokensSoldLastEpoch2, 1 ether);
597597

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

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

634-
// Get current tick
635-
PoolId poolId = key.toId();
636-
int24 currentTick = hook.getCurrentTick(poolId);
637-
638635
// Slugs must be inline and continuous
639636
assertEq(lowerSlug.tickUpper, upperSlug.tickLower, "lowerSlug.tickUpper != upperSlug.tickLower");
640637

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

665-
(,, uint24 protocolFee, uint24 lpFee) = manager.getSlot0(key.toId());
666-
// Lower slug should be unset with ticks at the current price if the fee is 0
667-
if (protocolFee == 0 && lpFee == 0) {
668-
assertEq(lowerSlug.tickLower, lowerSlug.tickUpper, "lowerSlug.tickLower != lowerSlug.tickUpper");
669-
assertEq(lowerSlug.liquidity, 0, "lowerSlug.liquidity != 0");
670-
assertEq(lowerSlug.tickUpper, currentTick, "lowerSlug.tickUpper != currentTick");
671-
}
672662
// Upper and price discovery slugs must be set
673663
assertNotEq(upperSlug.liquidity, 0);
674664
}

test/shared/BaseTest.sol

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,8 +152,8 @@ contract BaseTest is Test, Deployers {
152152

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

158158
// Default to feeless case because it's easier to reason about
159159
config.fee = uint24(vm.envOr("FEE", uint24(0)));

test/shared/DopplerImplementation.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ contract DopplerImplementation is Doppler {
143143
uint256 totalTokensSold,
144144
int24 currentTick,
145145
uint256 assetAvailable
146-
) public view returns (SlugData memory) {
146+
) public view returns (SlugData memory, uint256 assetRemaining) {
147147
return _computeUpperSlugData(poolKey, totalTokensSold, currentTick, assetAvailable);
148148
}
149149

test/unit/Constructor.t.sol

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,9 @@ contract ConstructorTest is BaseTest {
5454
if (selector != 0) {
5555
vm.expectRevert(selector);
5656
}
57+
58+
int24 startTick = _startTick != 0 ? _startTick : isToken0 ? DEFAULT_START_TICK : -DEFAULT_START_TICK;
59+
int24 endTick = _endTick != 0 ? _endTick : isToken0 ? -DEFAULT_END_TICK : DEFAULT_END_TICK;
5760
deployCodeTo(
5861
"DopplerImplementation.sol:DopplerImplementation",
5962
abi.encode(
@@ -64,8 +67,8 @@ contract ConstructorTest is BaseTest {
6467
config.maximumProceeds,
6568
config.startingTime,
6669
config.endingTime,
67-
_startTick,
68-
_endTick,
70+
startTick,
71+
endTick,
6972
config.epochLength,
7073
config.gamma,
7174
isToken0,
@@ -124,15 +127,15 @@ contract ConstructorTest is BaseTest {
124127
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
125128
config.tickSpacing = int24(maxTickSpacing + 1);
126129

127-
deployDoppler(InvalidTickSpacing.selector, config, 200, 100, true);
130+
deployDoppler(InvalidTickSpacing.selector, config, 0, 0, true);
128131
}
129132

130133
function testConstructor_RevertsInvalidTimeRange_WhenStartingTimeGreaterThanOrEqualToEndingTime() public {
131134
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
132135
config.startingTime = 1000;
133136
config.endingTime = 1000;
134137

135-
deployDoppler(InvalidTimeRange.selector, config, 200, 100, true);
138+
deployDoppler(InvalidTimeRange.selector, config, DEFAULT_START_TICK, DEFAULT_START_TICK, true);
136139
}
137140

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

145-
deployDoppler(InvalidGamma.selector, config, 200, 100, true);
148+
deployDoppler(InvalidGamma.selector, config, 0, 0, true);
146149
}
147150

148151
function testConstructor_RevertsInvalidEpochLength_WhenTimeDeltaNotDivisibleByEpochLength() public {
149152
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
150-
config.startingTime = 1000;
151-
config.endingTime = 5000;
152153
config.epochLength = 3000;
153154

154-
deployDoppler(InvalidEpochLength.selector, config, 200, 100, true);
155+
deployDoppler(InvalidEpochLength.selector, config, DEFAULT_START_TICK, DEFAULT_START_TICK, true);
155156
}
156157

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

161-
deployDoppler(InvalidGamma.selector, config, 200, 100, true);
162+
deployDoppler(InvalidGamma.selector, config, 0, 0, true);
162163
}
163164

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

171-
deployDoppler(InvalidGamma.selector, config, 200, 100, true);
172+
deployDoppler(InvalidGamma.selector, config, 0, 0, true);
172173
}
173174

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

178-
deployDoppler(InvalidGamma.selector, config, 200, 100, true);
179+
deployDoppler(InvalidGamma.selector, config, 0, 0, true);
179180
}
180181

181182
function testConstructor_RevertsInvalidNumPDSlugs_WithZeroSlugs() public {
182183
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
183184
config.numPDSlugs = 0;
184185

185-
deployDoppler(InvalidNumPDSlugs.selector, config, 0, -172_800, true);
186+
deployDoppler(InvalidNumPDSlugs.selector, config, 0, 0, true);
186187
}
187188

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

192-
deployDoppler(InvalidNumPDSlugs.selector, config, 0, -172_800, true);
193+
deployDoppler(InvalidNumPDSlugs.selector, config, 0, 0, true);
193194
}
194195

195196
function testConstructor_RevertsInvalidProceedLimits_WhenMinimumProceedsGreaterThanMaximumProceeds() public {
196197
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
197198
config.minimumProceeds = 100;
198199
config.maximumProceeds = 0;
199200

200-
deployDoppler(InvalidProceedLimits.selector, config, 0, -172_800, true);
201+
deployDoppler(InvalidProceedLimits.selector, config, 0, 0, true);
201202
}
202203

203204
function testConstructor_Succeeds_WithValidParameters() public {
204205
bool _isToken0 = true;
205-
int24 _startTick = 0;
206-
int24 _endTick = -172_800;
207206

208207
DopplerConfig memory config = DEFAULT_DOPPLER_CONFIG;
209208

210-
deployDoppler(0, config, _startTick, _endTick, _isToken0);
209+
deployDoppler(0, config, 0, 0, _isToken0);
211210
}
212211
}

0 commit comments

Comments
 (0)