Skip to content

Commit 9d6dd49

Browse files
committed
fix autocompound bug, use custodied and unclaimed fees in the autocompound
1 parent 4d3218e commit 9d6dd49

File tree

6 files changed

+45
-55
lines changed

6 files changed

+45
-55
lines changed
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
190044
1+
190026
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
173212
1+
176386
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
148794
1+
151968
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
468501
1+
471675

contracts/base/BaseLiquidityManagement.sol

Lines changed: 36 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ contract BaseLiquidityManagement is IBaseLiquidityManagement, SafeCallback {
106106
LiquidityRange memory range,
107107
uint256 liquidityToAdd,
108108
bytes memory hookData
109-
) internal returns (BalanceDelta) {
109+
) internal returns (BalanceDelta, BalanceDelta) {
110110
// Note that the liquidityDelta includes totalFeesAccrued. The totalFeesAccrued is returned separately for accounting purposes.
111111
(BalanceDelta liquidityDelta, BalanceDelta totalFeesAccrued) =
112112
_modifyLiquidity(owner, range, liquidityToAdd.toInt256(), hookData);
@@ -135,52 +135,31 @@ contract BaseLiquidityManagement is IBaseLiquidityManagement, SafeCallback {
135135
// Calculate the accurate tokens owed to the caller.
136136
// If the totalFeesAccrued equals the callerFeesAccrued then the total owed to the caller is just the liquidityDelta.
137137
// If the totalFeesAccrued is greater than the callerFeesAccrued, we must account for the difference.
138+
// TODO: If totalFeesAccrued == callerFeesAccrued, I think we can just apply the entire delta onto the caller, even if this implicitly collects on behalf of another user in the same range.
138139
(int128 callerDelta0, int128 callerDelta1) = totalFeesAccrued != callerFeesAccrued
139140
? _calculateCallerDeltas(liquidityDelta, totalFeesAccrued, callerFeesAccrued)
140141
: (liquidityDelta.amount0(), liquidityDelta.amount1());
141142

142-
// An edge case:
143-
// assume alice and bob are on the same range
144-
// and 20 token fee revenue is posted (i.e. swap revenue or donate)
145-
146-
// bob calls collects()
147-
// liquidityDelta: 20
148-
// totalFeesAccrued: 20
149-
// callerFeesAccrued: 5
150-
// (5 tokens sent to bob, and posm caches the 15 tokens for alice)
151-
152-
// assume another 20 token fee revenue is posted (a net new 5 tokens for bob and 15 new tokens for alice)
153-
// alice now has 30 tokens of fee revenue, half of custodied by posm and the other half unclaimed in the PM
154-
155-
// when alice increases her liquidity, using exactly 30 tokens (autocompound):
156-
// liquidityDelta: -10
157-
// totalFeesAccrued: 20 (new fees: 5 for bob, 15 for alice)
158-
// callerFeesAccrued: 30 (alice's fees, per feeGrowthInside)
159-
160-
// naively resolving deltas:
161-
// posm: take 5 tokens (bob's fees), liquidityDelta is now: -15
162-
// posm: pay PM the 15 tokens (alice's cached fees)
163-
// alice: pay nothing, as its a pure and exact autocompound
164-
165-
// to solve:
166-
// we need a way to discern cached fees and use them against liquidityDelta
167-
168-
// Update position storage, sanitizing the tokensOwed and callerDelta values first.
169-
// if callerDelta > 0, then even after re-investing old fees, the caller still has some amount to collect that were not added into the position so they are accounted.
170-
// if callerDelta <= 0, then tokensOwed0 and tokensOwed1 should be zero'd out as all fees were re-invested into a new position.
143+
// Update position storage, flushing the callerDelta value to tokensOwed first if necessary.
144+
// If callerDelta > 0, then even after investing callerFeesAccrued, the caller still has some amount to collect that were not added into the position so they are accounted to tokensOwed and removed from the final callerDelta returned.
171145
uint128 tokensOwed0 = 0;
172146
uint128 tokensOwed1 = 0;
173147
(tokensOwed0, callerDelta0) = callerDelta0 > 0 ? (uint128(callerDelta0), int128(0)) : (uint128(0), callerDelta0);
174148
(tokensOwed1, callerDelta1) = callerDelta1 > 0 ? (uint128(callerDelta1), int128(0)) : (uint128(0), callerDelta1);
175149

176-
position.updateTokensOwed(tokensOwed0, tokensOwed1);
150+
position.addTokensOwed(tokensOwed0, tokensOwed1);
177151
position.addLiquidity(liquidityToAdd);
178152
position.updateFeeGrowthInside(feeGrowthInside0X128, feeGrowthInside1X128);
179153

180-
return toBalanceDelta(callerDelta0, callerDelta1);
154+
// The delta owed or credited by this contract.
155+
// TODO @sauce check that if callerDelta == 0 (zerod out from above), then this line just credits the posm to takes on behalf of the caller
156+
int128 thisDelta0 = liquidityDelta.amount0() - callerDelta0;
157+
int128 thisDelta1 = liquidityDelta.amount1() - callerDelta1;
158+
159+
return (toBalanceDelta(callerDelta0, callerDelta1), toBalanceDelta(thisDelta0, thisDelta1));
181160
}
182161

183-
// Returns the new sanitized delta for the caller.
162+
// Returns the delta paid/credited by/to the caller.
184163
function _calculateCallerDeltas(
185164
BalanceDelta liquidityDelta,
186165
BalanceDelta totalFeesAccrued,
@@ -191,13 +170,8 @@ contract BaseLiquidityManagement is IBaseLiquidityManagement, SafeCallback {
191170
(int128 callerFeesAccrued0, int128 callerFeesAccrued1) =
192171
(callerFeesAccrued.amount0(), callerFeesAccrued.amount1());
193172

194-
callerDelta0 = totalFeesAccrued0 > callerFeesAccrued0
195-
? _calculateCallerDelta(liquidityDelta.amount0(), totalFeesAccrued0, callerFeesAccrued0)
196-
: liquidityDelta0;
197-
198-
callerDelta1 = totalFeesAccrued1 > callerFeesAccrued1
199-
? _calculateCallerDelta(liquidityDelta.amount1(), totalFeesAccrued1, callerFeesAccrued1)
200-
: liquidityDelta1;
173+
callerDelta0 = _calculateCallerDelta(liquidityDelta0, totalFeesAccrued0, callerFeesAccrued0);
174+
callerDelta1 = _calculateCallerDelta(liquidityDelta1, totalFeesAccrued1, callerFeesAccrued1);
201175
}
202176

203177
function _calculateCallerDelta(int128 liquidityDelta, int128 totalFeesAccrued, int128 callerFeesAccrued)
@@ -206,8 +180,11 @@ contract BaseLiquidityManagement is IBaseLiquidityManagement, SafeCallback {
206180
returns (int128 callerDelta)
207181
{
208182
unchecked {
209-
int128 feesAccruedOutsideCaller = totalFeesAccrued - callerFeesAccrued;
210-
callerDelta = liquidityDelta - feesAccruedOutsideCaller;
183+
// The principle delta owed/debited to the caller before any LP fees are deducted.
184+
int128 principleDelta = liquidityDelta - totalFeesAccrued;
185+
// The new caller delta is this principle delta plus the callerFeesAccrued which consists of
186+
// the custodied fees by posm and unclaimed fees from the modifyLiq call.
187+
callerDelta = principleDelta + callerFeesAccrued;
211188
}
212189
}
213190

@@ -218,13 +195,28 @@ contract BaseLiquidityManagement is IBaseLiquidityManagement, SafeCallback {
218195
bytes memory hookData,
219196
bool claims
220197
) internal returns (BalanceDelta callerDelta) {
221-
callerDelta = _increaseLiquidity(owner, range, liquidityToAdd, hookData);
198+
BalanceDelta thisDelta;
199+
// TODO move callerDelta and thisDelta to transient storage?
200+
(callerDelta, thisDelta) = _increaseLiquidity(owner, range, liquidityToAdd, hookData);
222201
_closeCallerDeltas(callerDelta, range.poolKey.currency0, range.poolKey.currency1, owner, claims);
223-
_closeAllDeltas(range.poolKey.currency0, range.poolKey.currency1);
202+
_closeThisDeltas(thisDelta, range.poolKey.currency0, range.poolKey.currency1);
224203
}
225204

226205
// When chaining many actions, this should be called at the very end to close out any open deltas owed to or by this contract for other users on the same range.
227206
// This is safe because any amounts the caller should not pay or take have already been accounted for in closeCallerDeltas.
207+
function _closeThisDeltas(BalanceDelta delta, Currency currency0, Currency currency1) internal {
208+
int128 delta0 = delta.amount0();
209+
int128 delta1 = delta.amount1();
210+
211+
// Mint a receipt for the tokens owed to this address.
212+
if (delta0 > 0) currency0.take(manager, address(this), uint128(delta0), true);
213+
if (delta1 > 0) currency1.take(manager, address(this), uint128(delta1), true);
214+
// Burn the receipt for tokens owed to this address.
215+
if (delta0 < 0) currency0.settle(manager, address(this), uint256(int256(-delta0)), true);
216+
if (delta1 < 0) currency1.settle(manager, address(this), uint256(int256(-delta1)), true);
217+
}
218+
219+
//TODO @sara deprecate when moving to _closeThisDeltas for decreaes and collect
228220
function _closeAllDeltas(Currency currency0, Currency currency1) internal {
229221
(BalanceDelta delta) = manager.currencyDeltas(address(this), currency0, currency1);
230222
int128 delta0 = delta.amount0();

contracts/libraries/Position.sol

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,11 @@ import {IBaseLiquidityManagement} from "../interfaces/IBaseLiquidityManagement.s
66
// Updates Position storage
77
library PositionLibrary {
88
// TODO ensure this is one sstore.
9-
function updateTokensOwed(
10-
IBaseLiquidityManagement.Position storage position,
11-
uint128 tokensOwed0,
12-
uint128 tokensOwed1
13-
) internal {
14-
position.tokensOwed0 = tokensOwed0;
15-
position.tokensOwed1 = tokensOwed1;
9+
function addTokensOwed(IBaseLiquidityManagement.Position storage position, uint128 tokensOwed0, uint128 tokensOwed1)
10+
internal
11+
{
12+
position.tokensOwed0 += tokensOwed0;
13+
position.tokensOwed1 += tokensOwed1;
1614
}
1715

1816
function addLiquidity(IBaseLiquidityManagement.Position storage position, uint256 liquidity) internal {

0 commit comments

Comments
 (0)