-
Notifications
You must be signed in to change notification settings - Fork 618
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
feat: scaling factor for pool uptime accumulator to avoid truncation #7409
Changes from 6 commits
246e5f1
99022a2
213cec6
04a91c6
880e3d1
1367d0c
748edd8
3158f90
f76b9cb
f254a26
e36f4a1
9e10789
de4b9ba
8a43a0b
bab4173
fea2d75
5ac3e97
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,6 +19,16 @@ import ( | |
"github.com/osmosis-labs/osmosis/v22/x/concentrated-liquidity/types" | ||
) | ||
|
||
// We choose 10^27 to allow sufficient buffer before the accumulator starts getting truncated again. | ||
// Internally, multiply the number of seconds passed since the last liquidity update by the emission rate per second | ||
p0mvn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Then, we scale that value by 10^27 to avoid truncation to zero when dividing by the liquidity in the accumulator. | ||
// As a result, we do not go for a higher scaling factor to allow for enough room before hitting the maximum integer value of 2^256. | ||
p0mvn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// In the intermediary multiplications. | ||
// | ||
// More analysis on the choice of scaling factor can be found here: | ||
// https://hackmd.io/o3oqT8VhSPKAiqNl_mlxXQ | ||
var perUnitLiqScalingFactor = osmomath.NewDec(1e15).MulMut(osmomath.NewDec(1e12)) | ||
|
||
// createUptimeAccumulators creates accumulator objects in store for each supported uptime for the given poolId. | ||
// The accumulators are initialized with the default (zero) values. | ||
func (k Keeper) createUptimeAccumulators(ctx sdk.Context, poolId uint64) error { | ||
|
@@ -240,11 +250,21 @@ func calcAccruedIncentivesForAccum(ctx sdk.Context, accumUptime time.Duration, l | |
} | ||
|
||
// Total amount emitted = time elapsed * emission | ||
totalEmittedAmount := timeElapsed.Mul(incentiveRecordBody.EmissionRate) | ||
totalEmittedAmount := timeElapsed.MulTruncate(incentiveRecordBody.EmissionRate) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. to future proof this a bit more, thoughts on adding comments in line here for the expected range of each term? This is basically whats blocking approve for me rn (trying to rederive these) Actual scale-up change LGTM There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Attempted to address here: Let me know what you think |
||
|
||
// We scale up the remaining rewards to avoid truncation to zero | ||
// when dividing by the liquidity in the accumulator. | ||
scaledTotalEmittedAmount, err := scaleUpTotalEmittedAmount(totalEmittedAmount) | ||
if err != nil { | ||
ctx.Logger().Info(types.IncentiveOverflowPlaceholderName, "pool_id", poolID, "incentive_id", incentiveRecord.IncentiveId, "time_elapsed", timeElapsed, "emission_rate", incentiveRecordBody.EmissionRate, "error", err.Error()) | ||
// Silently ignore the truncated incentive record to avoid halting the entire accumulator update | ||
p0mvn marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// Continue to the next incentive record. | ||
continue | ||
} | ||
|
||
// Incentives to emit per unit of qualifying liquidity = total emitted / liquidityInAccum | ||
// Note that we truncate to ensure we do not overdistribute incentives | ||
incentivesPerLiquidity := totalEmittedAmount.QuoTruncate(liquidityInAccum) | ||
incentivesPerLiquidity := scaledTotalEmittedAmount.QuoTruncate(liquidityInAccum) | ||
|
||
// Emit telemetry for accumulator updates | ||
emitAccumulatorUpdateTelemetry(ctx, types.IncentiveTruncationPlaceholderName, types.IncentiveEmissionPlaceholderName, incentivesPerLiquidity, totalEmittedAmount, poolID, liquidityInAccum) | ||
|
@@ -266,7 +286,18 @@ func calcAccruedIncentivesForAccum(ctx sdk.Context, accumUptime time.Duration, l | |
} else { | ||
// If there are not enough incentives remaining to be emitted, we emit the remaining rewards. | ||
// When the returned records are set in state, all records with remaining rewards of zero will be cleared. | ||
remainingIncentivesPerLiquidity := remainingRewards.QuoTruncate(liquidityInAccum) | ||
|
||
// We scale up the remaining rewards to avoid truncation to zero | ||
// when dividing by the liquidity in the accumulator. | ||
remainingRewardsScaled := remainingRewards.MulTruncate(perUnitLiqScalingFactor) | ||
remainingIncentivesPerLiquidity := remainingRewardsScaled.QuoTruncateMut(liquidityInAccum) | ||
|
||
// Detect truncation and emit telemetry to alert us of the issue. | ||
if remainingIncentivesPerLiquidity.IsZero() && !remainingRewardsScaled.IsZero() { | ||
telemetry.IncrCounter(1, types.IncentiveTruncationPlaceholderName) | ||
ctx.Logger().Error(types.IncentiveTruncationPlaceholderName, "pool_id", poolID, "total_liq", liquidityInAccum, "per_unit_liq", incentivesPerLiquidity, "total_amt", remainingRewards, "total_amt_scaled", remainingRewardsScaled) | ||
} | ||
|
||
emittedIncentivesPerLiquidity = sdk.NewDecCoinFromDec(incentiveRecordBody.RemainingCoin.Denom, remainingIncentivesPerLiquidity) | ||
|
||
// Emit telemetry for accumulator updates | ||
|
@@ -281,6 +312,23 @@ func calcAccruedIncentivesForAccum(ctx sdk.Context, accumUptime time.Duration, l | |
return incentivesToAddToCurAccum, copyPoolIncentiveRecords, nil | ||
} | ||
|
||
// scaleUpTotalEmittedAmount scales up the total emitted amount to avoid truncation to zero. | ||
// Returns error if the total emitted amount is too high and causes overflow when applying scaling factor. | ||
func scaleUpTotalEmittedAmount(totalEmittedAmount osmomath.Dec) (scaledTotalEmittedAmount osmomath.Dec, err error) { | ||
defer func() { | ||
r := recover() | ||
|
||
if r != nil { | ||
telemetry.IncrCounter(1, types.IncentiveOverflowPlaceholderName) | ||
err = types.IncentiveOverflowError{ | ||
PanicMessage: fmt.Sprintf("%v", r), | ||
} | ||
} | ||
}() | ||
|
||
return totalEmittedAmount.MulTruncate(perUnitLiqScalingFactor), nil | ||
} | ||
|
||
// findUptimeIndex finds the uptime index for the passed in min uptime. | ||
// Returns error if uptime index cannot be found. | ||
func findUptimeIndex(uptime time.Duration) (int, error) { | ||
|
@@ -705,11 +753,22 @@ func (k Keeper) prepareClaimAllIncentivesForPosition(ctx sdk.Context, positionId | |
|
||
// If the accumulator contains the position, claim the position's incentives. | ||
if hasPosition { | ||
collectedIncentivesForUptime, _, err := updateAccumAndClaimRewards(uptimeAccum, positionName, uptimeGrowthOutside[uptimeIndex]) | ||
collectedIncentivesForUptimeScaled, _, err := updateAccumAndClaimRewards(uptimeAccum, positionName, uptimeGrowthOutside[uptimeIndex]) | ||
if err != nil { | ||
return sdk.Coins{}, sdk.Coins{}, err | ||
} | ||
|
||
// We scale the uptime per-unit of liquidity accumulator up to avoid truncation to zero. | ||
// However, once we compute the total for the liquidity entitlement, we must scale it back down. | ||
// We always truncate down in the pool's favor. | ||
collectedIncentivesForUptime := sdk.NewCoins() | ||
for _, incentiveCoin := range collectedIncentivesForUptimeScaled { | ||
incentiveCoin.Amount = incentiveCoin.Amount.ToLegacyDec().QuoTruncateMut(perUnitLiqScalingFactor).TruncateInt() | ||
AlpinYukseloglu marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if incentiveCoin.Amount.IsPositive() { | ||
collectedIncentivesForUptime = append(collectedIncentivesForUptime, incentiveCoin) | ||
} | ||
Comment on lines
+798
to
+800
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this to handle the case is which, even after scaling, we still truncate to zero? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah. It doesn't make sense to collect a bunch of zero coins so decided to simply skip them |
||
} | ||
|
||
if positionAge < supportedUptimes[uptimeIndex] { | ||
// If the age of the position is less than the current uptime we are iterating through, then the position's | ||
// incentives are forfeited to the community pool. The parent function does the actual bank send. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: something along the lines of
RequireDecCoinsSlice
seems more accurateThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done