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

feat: incentive accumulator migration #7416

Merged
merged 38 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
246e5f1
feat: scaling factor for pool uptime accumulator
p0mvn Feb 2, 2024
99022a2
changelog
p0mvn Feb 3, 2024
213cec6
updates
p0mvn Feb 5, 2024
04a91c6
updates
p0mvn Feb 5, 2024
880e3d1
Merge branch 'main' into roman/pool-incentive-scaling-factor
p0mvn Feb 5, 2024
1367d0c
handle overflows
p0mvn Feb 5, 2024
748edd8
Apply suggestions from code review
p0mvn Feb 5, 2024
3158f90
updates
p0mvn Feb 5, 2024
f76b9cb
Apply suggestions from code review
p0mvn Feb 5, 2024
f254a26
fix test and clean up
p0mvn Feb 5, 2024
e36f4a1
spelling
p0mvn Feb 5, 2024
9e10789
future proof multiplication overflow
p0mvn Feb 6, 2024
de4b9ba
clean up
p0mvn Feb 6, 2024
8a43a0b
comment
p0mvn Feb 6, 2024
bab4173
lint
p0mvn Feb 6, 2024
fea2d75
Merge branch 'main' into roman/pool-incentive-scaling-factor
p0mvn Feb 7, 2024
5ac3e97
rename
p0mvn Feb 7, 2024
2d49b83
feat: incentive accumulator upgrade handler migration
p0mvn Feb 6, 2024
c5decc9
lint
p0mvn Feb 6, 2024
b1ee555
go mod
p0mvn Feb 6, 2024
32aa8c9
fix test
p0mvn Feb 6, 2024
53bd32d
implement TestMigrateAccumulatorToScalingFactor
p0mvn Feb 7, 2024
c65aaa8
basic genesis test
p0mvn Feb 7, 2024
b58ccbf
fix comment
p0mvn Feb 7, 2024
c03d5e8
clean up tests
p0mvn Feb 7, 2024
e1f07d3
clean up helpers
p0mvn Feb 7, 2024
52b389a
add test for get position IDs by pool ID
p0mvn Feb 7, 2024
3f30364
test for getIncentiveScalingFactorForPool
p0mvn Feb 7, 2024
0004a14
upgrade handler test and tick migration
p0mvn Feb 7, 2024
ab7fa11
update spec
p0mvn Feb 7, 2024
99e3a8d
Merge branch 'main' into roman/upgrade-handler-migration
p0mvn Feb 7, 2024
c9bc00f
Merge branch 'main' into roman/upgrade-handler-migration
p0mvn Feb 8, 2024
7778fe1
updates
p0mvn Feb 8, 2024
68020b1
updates
p0mvn Feb 8, 2024
06c3ad1
feat/fix: add more pools to migrate, fix key parsing bug, add more lo…
p0mvn Feb 9, 2024
56b8146
introduce helper
p0mvn Feb 9, 2024
4ef439b
Update x/concentrated-liquidity/pool.go
p0mvn Feb 9, 2024
71b47b6
Merge branch 'main' into roman/upgrade-handler-migration
mergify[bot] Feb 9, 2024
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
Next Next commit
feat: scaling factor for pool uptime accumulator
  • Loading branch information
p0mvn committed Feb 2, 2024
commit 246e5f10954385bca374db3f635bf059fb0e4ae5
8 changes: 8 additions & 0 deletions app/apptesting/test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -696,3 +696,11 @@ func (s *KeeperTestHelper) SetupVolumeForPools(poolIDs []uint64, volumesForEachP
func (s *KeeperTestHelper) IncreaseVolumeForPools(poolIDs []uint64, volumesForEachPool []osmomath.Int) {
s.SetupVolumeForPools(poolIDs, volumesForEachPool, map[uint64]osmomath.Int{})
}

// CompareDecCoinsSlice compares two slices of DecCoins
func (s *KeeperTestHelper) CompareDecCoinsSlice(expected, actual []sdk.DecCoins) {
s.Require().Equal(len(expected), len(actual))
for i := range actual {
s.Require().Equal(expected[i].String(), actual[i].String())
}
}
11 changes: 6 additions & 5 deletions x/concentrated-liquidity/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@ const (
)

var (
EmptyCoins = emptyCoins
HundredFooCoins = sdk.NewDecCoin("foo", osmomath.NewInt(100))
HundredBarCoins = sdk.NewDecCoin("bar", osmomath.NewInt(100))
TwoHundredFooCoins = sdk.NewDecCoin("foo", osmomath.NewInt(200))
TwoHundredBarCoins = sdk.NewDecCoin("bar", osmomath.NewInt(200))
EmptyCoins = emptyCoins
HundredFooCoins = sdk.NewDecCoin("foo", osmomath.NewInt(100))
HundredBarCoins = sdk.NewDecCoin("bar", osmomath.NewInt(100))
TwoHundredFooCoins = sdk.NewDecCoin("foo", osmomath.NewInt(200))
TwoHundredBarCoins = sdk.NewDecCoin("bar", osmomath.NewInt(200))
PerUnitLiqScalingFactor = perUnitLiqScalingFactor
)

func (k Keeper) SetPool(ctx sdk.Context, pool types.ConcentratedPoolExtension) error {
Expand Down
39 changes: 33 additions & 6 deletions x/concentrated-liquidity/incentives.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import (
"github.com/osmosis-labs/osmosis/v22/x/concentrated-liquidity/types"
)

var perUnitLiqScalingFactor = osmomath.NewDec(1e18)

// 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 {
Expand Down Expand Up @@ -240,16 +242,19 @@ 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)
// We scale up the remaining rewards to avoid truncation to zero
// when dividing by the liquidity in the accumulator.
scaledTotalEmittedAmount := totalEmittedAmount.MulTruncate(perUnitLiqScalingFactor)

// 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)

// If truncation occurs, we emit events to alert us of the issue.
if incentivesPerLiquidity.IsZero() && !totalEmittedAmount.IsZero() {
if incentivesPerLiquidity.IsZero() && !scaledTotalEmittedAmount.IsZero() {
telemetry.IncrCounter(1, types.IncentiveTruncationPlaceholderName)
ctx.Logger().Error(types.IncentiveTruncationPlaceholderName, "pool_id", poolID, "total_liq", liquidityInAccum, "per_unit_liq", incentivesPerLiquidity, "total_amt", totalEmittedAmount)
ctx.Logger().Error(types.IncentiveTruncationPlaceholderName, "pool_id", poolID, "total_liq", liquidityInAccum, "per_unit_liq", incentivesPerLiquidity, "total_amt", totalEmittedAmount, "total_amt_scaled", scaledTotalEmittedAmount)
}

emittedIncentivesPerLiquidity := sdk.NewDecCoinFromDec(incentiveRecordBody.RemainingCoin.Denom, incentivesPerLiquidity)
Expand All @@ -269,7 +274,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)
incentivesToAddToCurAccum = incentivesToAddToCurAccum.Add(emittedIncentivesPerLiquidity)

Expand Down Expand Up @@ -704,11 +720,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()
if incentiveCoin.Amount.IsPositive() {
collectedIncentivesForUptime = append(collectedIncentivesForUptime, incentiveCoin)
}
}

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.
Expand Down
85 changes: 62 additions & 23 deletions x/concentrated-liquidity/incentives_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func wrapUptimeTrackers(accumValues []sdk.DecCoins) []model.UptimeTracker {

// expectedIncentivesFromRate calculates the amount of incentives we expect to accrue based on the rate and time elapsed
func expectedIncentivesFromRate(denom string, rate osmomath.Dec, timeElapsed time.Duration, qualifyingLiquidity osmomath.Dec) sdk.DecCoin {
timeInSec := osmomath.NewDec(int64(timeElapsed)).Quo(osmomath.MustNewDecFromStr("1000000000"))
timeInSec := osmomath.NewDec(int64(timeElapsed)).Quo(osmomath.MustNewDecFromStr("1000000000")).MulTruncateMut(cl.PerUnitLiqScalingFactor)
amount := rate.Mul(timeInSec).QuoTruncate(qualifyingLiquidity)

return sdk.NewDecCoinFromDec(denom, amount)
Expand Down Expand Up @@ -214,7 +214,7 @@ func addToUptimeAccums(ctx sdk.Context, poolId uint64, clKeeper *cl.Keeper, addV
}

for uptimeIndex, uptimeAccum := range poolUptimeAccumulators {
uptimeAccum.AddToAccumulator(addValues[uptimeIndex])
uptimeAccum.AddToAccumulator(addValues[uptimeIndex].MulDecTruncate(cl.PerUnitLiqScalingFactor))
}

return nil
Expand Down Expand Up @@ -568,7 +568,7 @@ func (s *KeeperTestSuite) TestCalcAccruedIncentivesForAccum() {

// We expect the fully incentive amount to be emitted
expectedResult: sdk.DecCoins{
sdk.NewDecCoinFromDec(incentiveRecordOne.IncentiveRecordBody.RemainingCoin.Denom, incentiveRecordOne.IncentiveRecordBody.RemainingCoin.Amount.QuoTruncate(osmomath.NewDec(123))),
sdk.NewDecCoinFromDec(incentiveRecordOne.IncentiveRecordBody.RemainingCoin.Denom, incentiveRecordOne.IncentiveRecordBody.RemainingCoin.Amount.MulTruncate(cl.PerUnitLiqScalingFactor).QuoTruncate(osmomath.NewDec(123))),
},

// Incentive record should have zero remaining amountosmomath.ZeroDec
Expand Down Expand Up @@ -1372,6 +1372,12 @@ func (s *KeeperTestSuite) TestGetUptimeGrowthRange() {
pool := s.PrepareConcentratedPool()
currentTick := pool.GetCurrentTick()

// Note: we scale all these values up as addToUptimeAccums(...) does the same for the global uptime accums.
tc.lowerTickUptimeGrowthOutside = s.scaleUptimeAccumulators(tc.lowerTickUptimeGrowthOutside)
tc.upperTickUptimeGrowthOutside = s.scaleUptimeAccumulators(tc.upperTickUptimeGrowthOutside)
tc.expectedUptimeGrowthInside = s.scaleUptimeAccumulators(tc.expectedUptimeGrowthInside)
tc.expectedUptimeGrowthOutside = s.scaleUptimeAccumulators(tc.expectedUptimeGrowthOutside)

// Update global uptime accums
err := addToUptimeAccums(s.Ctx, pool.GetId(), s.App.ConcentratedLiquidityKeeper, tc.globalUptimeGrowth)
s.Require().NoError(err)
Expand All @@ -1388,13 +1394,13 @@ func (s *KeeperTestSuite) TestGetUptimeGrowthRange() {
s.Require().NoError(err)

// check if returned uptime growth inside has correct value
s.Require().Equal(tc.expectedUptimeGrowthInside, uptimeGrowthInside)
s.CompareDecCoinsSlice(tc.expectedUptimeGrowthInside, uptimeGrowthInside)

uptimeGrowthOutside, err := s.App.ConcentratedLiquidityKeeper.GetUptimeGrowthOutsideRange(s.Ctx, pool.GetId(), tc.lowerTick, tc.upperTick)
s.Require().NoError(err)

// check if returned uptime growth inside has correct value
s.Require().Equal(tc.expectedUptimeGrowthOutside, uptimeGrowthOutside)
s.CompareDecCoinsSlice(tc.expectedUptimeGrowthOutside, uptimeGrowthOutside)
})
}
})
Expand Down Expand Up @@ -1439,13 +1445,14 @@ func (s *KeeperTestSuite) TestInitOrUpdatePositionUptimeAccumulators() {

"(lower < curr < upper) nonzero uptime trackers": {
positionLiquidity: DefaultLiquidityAmt,
// Note: that we scale uptime tracker values up as addToUptimeAccums(...) does the same for the global uptime accums.
lowerTick: tick{
tickIndex: -50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
upperTick: tick{
tickIndex: 50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
positionId: DefaultPositionId,
currentTickIndex: 0,
Expand All @@ -1455,13 +1462,14 @@ func (s *KeeperTestSuite) TestInitOrUpdatePositionUptimeAccumulators() {
},
"(lower < curr < upper) non-zero uptime trackers (position already existing)": {
positionLiquidity: DefaultLiquidityAmt,
// Note: that we scale uptime tracker values up as addToUptimeAccums(...) does the same for the global uptime accums.
lowerTick: tick{
tickIndex: -50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
upperTick: tick{
tickIndex: 50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
existingPosition: true,
addToGlobalAccums: uptimeHelper.threeHundredTokensMultiDenom,
Expand All @@ -1478,13 +1486,14 @@ func (s *KeeperTestSuite) TestInitOrUpdatePositionUptimeAccumulators() {
},
"(lower < upper < curr) nonzero uptime trackers": {
positionLiquidity: DefaultLiquidityAmt,
// Note: that we scale uptime tracker values up as addToUptimeAccums(...) does the same for the global uptime accums.
lowerTick: tick{
tickIndex: -50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
upperTick: tick{
tickIndex: 50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.threeHundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.threeHundredTokensMultiDenom)),
},
positionId: DefaultPositionId,
currentTickIndex: 51,
Expand All @@ -1494,13 +1503,14 @@ func (s *KeeperTestSuite) TestInitOrUpdatePositionUptimeAccumulators() {
},
"(lower < upper < curr) non-zero uptime trackers (position already existing)": {
positionLiquidity: DefaultLiquidityAmt,
// Note: that we scale uptime tracker values up as addToUptimeAccums(...) does the same for the global uptime accums.
lowerTick: tick{
tickIndex: -50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
upperTick: tick{
tickIndex: 50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.threeHundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.threeHundredTokensMultiDenom)),
},
existingPosition: true,
addToGlobalAccums: uptimeHelper.threeHundredTokensMultiDenom,
Expand All @@ -1517,13 +1527,14 @@ func (s *KeeperTestSuite) TestInitOrUpdatePositionUptimeAccumulators() {
},
"(curr < lower < upper) nonzero uptime trackers": {
positionLiquidity: DefaultLiquidityAmt,
// Note: that we scale uptime tracker values up as addToUptimeAccums(...) does the same for the global uptime accums.
lowerTick: tick{
tickIndex: -50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.threeHundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.threeHundredTokensMultiDenom)),
},
upperTick: tick{
tickIndex: 50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
positionId: DefaultPositionId,
currentTickIndex: -51,
Expand All @@ -1533,13 +1544,14 @@ func (s *KeeperTestSuite) TestInitOrUpdatePositionUptimeAccumulators() {
},
"(curr < lower < upper) nonzero uptime trackers (position already existing)": {
positionLiquidity: DefaultLiquidityAmt,
// Note: that we scale uptime tracker values up as addToUptimeAccums(...) does the same for the global uptime accums.
lowerTick: tick{
tickIndex: -50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.threeHundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.threeHundredTokensMultiDenom)),
},
upperTick: tick{
tickIndex: 50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
existingPosition: true,
addToGlobalAccums: uptimeHelper.threeHundredTokensMultiDenom,
Expand All @@ -1556,13 +1568,14 @@ func (s *KeeperTestSuite) TestInitOrUpdatePositionUptimeAccumulators() {
},
"(lower < curr < upper) nonzero and variable uptime trackers": {
positionLiquidity: DefaultLiquidityAmt,
// Note: that we scale uptime tracker values up as addToUptimeAccums(...) does the same for the global uptime accums.
lowerTick: tick{
tickIndex: -50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.varyingTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.varyingTokensMultiDenom)),
},
upperTick: tick{
tickIndex: 50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
positionId: DefaultPositionId,
currentTickIndex: 0,
Expand Down Expand Up @@ -1613,13 +1626,14 @@ func (s *KeeperTestSuite) TestInitOrUpdatePositionUptimeAccumulators() {
},
"error: negative liquidity for first position": {
positionLiquidity: DefaultLiquidityAmt.Neg(),
// Note: that we scale uptime tracker values up as addToUptimeAccums(...) does the same for the global uptime accums.
lowerTick: tick{
tickIndex: -50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
upperTick: tick{
tickIndex: 50,
uptimeTrackers: wrapUptimeTrackers(uptimeHelper.hundredTokensMultiDenom),
uptimeTrackers: wrapUptimeTrackers(s.scaleUptimeAccumulators(uptimeHelper.hundredTokensMultiDenom)),
},
positionId: DefaultPositionId,
currentTickIndex: 0,
Expand All @@ -1637,6 +1651,10 @@ func (s *KeeperTestSuite) TestInitOrUpdatePositionUptimeAccumulators() {
// Init suite for each test.
s.SetupTest()

// Note: that we scale all these values up as addToUptimeAccums(...) does the same for the global uptime accums.
test.expectedInitAccumValue = s.scaleUptimeAccumulators(test.expectedInitAccumValue)
test.expectedUnclaimedRewards = s.scaleUptimeAccumulators(test.expectedUnclaimedRewards)

// Set blocktime to fixed UTC value for consistency
s.Ctx = s.Ctx.WithBlockTime(DefaultJoinTime)

Expand Down Expand Up @@ -2898,7 +2916,7 @@ func (s *KeeperTestSuite) TestQueryAndClaimAllIncentives() {
for _, growthInside := range tc.growthInside {
expectedCoins = expectedCoins.Add(sdk.NormalizeCoins(growthInside)...)
}
s.Require().Equal(expectedCoins, amountClaimed)
s.Require().Equal(expectedCoins.String(), amountClaimed.String())
s.Require().Equal(sdk.Coins{}, amountForfeited)
}

Expand Down Expand Up @@ -3022,6 +3040,12 @@ func (s *KeeperTestSuite) TestPrepareClaimAllIncentivesForPosition() {
s.Require().NoError(err)

outstandingRewards := accum.GetTotalRewards(uptimeAccum, position)

// Scale down outstanding rewards
for _, reward := range outstandingRewards {
reward.Amount = reward.Amount.QuoTruncateMut(cl.PerUnitLiqScalingFactor)
}

collectedIncentivesForUptime, _ := outstandingRewards.TruncateDecimal()

for _, coin := range collectedIncentivesForUptime {
Expand Down Expand Up @@ -3612,11 +3636,26 @@ func (s *KeeperTestSuite) TestIncentiveTruncation() {
s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(time.Minute * 50))
incentives, _, err := s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, s.TestAccs[0], positionData.ID)
s.Require().NoError(err)
s.Require().True(incentives.IsZero())
s.Require().False(incentives.IsZero())

// Incentives should now be claimed due to lack of truncation
s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(time.Minute * 51))
incentives, _, err = s.App.ConcentratedLiquidityKeeper.CollectIncentives(s.Ctx, s.TestAccs[0], positionData.ID)
s.Require().NoError(err)
s.Require().False(incentives.IsZero())
}

// scaleUptimeAccumulators scales the uptime accumulators by the scaling factor.
// This is to avoid truncation to zero in core logic when the liquidity is large.
func (s *KeeperTestSuite) scaleUptimeAccumulators(uptimeAccumulatorsToScale []sdk.DecCoins) []sdk.DecCoins {
growthCopy := make([]sdk.DecCoins, len(uptimeAccumulatorsToScale))
for i, growth := range uptimeAccumulatorsToScale {
growthCopy[i] = make(sdk.DecCoins, len(growth))
for j, coin := range growth {
growthCopy[i][j].Denom = coin.Denom
growthCopy[i][j].Amount = coin.Amount.MulTruncate(cl.PerUnitLiqScalingFactor)
}
}

return growthCopy
}
Loading