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

Make CL calculations not update accumulators #7689

Merged
merged 4 commits into from
Mar 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* [#7622](https://github.com/osmosis-labs/osmosis/pull/7622) Remove duplicate CL accumulator update logic.
* [#7665](https://github.com/osmosis-labs/osmosis/pull/7665) feat(x/protorev): Use Transient store to store swap backruns.
* [#7503](https://github.com/osmosis-labs/osmosis/pull/7503) Add IBC wasm light clients module
* [#7689](https://github.com/osmosis-labs/osmosis/pull/7689) Make CL price estimations not cause state writes (speed and gas improvements)

### State Compatible

Expand Down
8 changes: 4 additions & 4 deletions x/concentrated-liquidity/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,9 +70,9 @@ func (k Keeper) ComputeOutAmtGivenIn(
tokenOutDenom string,
spreadFactor osmomath.Dec,
priceLimit osmomath.BigDec,

updateAccumulators bool,
) (swapResult SwapResult, poolUpdates PoolUpdates, err error) {
return k.computeOutAmtGivenIn(ctx, poolId, tokenInMin, tokenOutDenom, spreadFactor, priceLimit)
return k.computeOutAmtGivenIn(ctx, poolId, tokenInMin, tokenOutDenom, spreadFactor, priceLimit, updateAccumulators)
}

func (k Keeper) SwapInAmtGivenOut(
Expand All @@ -93,9 +93,9 @@ func (k Keeper) ComputeInAmtGivenOut(
spreadFactor osmomath.Dec,
priceLimit osmomath.BigDec,
poolId uint64,

updateAccumulators bool,
) (swapResult SwapResult, poolUpdates PoolUpdates, err error) {
return k.computeInAmtGivenOut(ctx, desiredTokenOut, tokenInDenom, spreadFactor, priceLimit, poolId)
return k.computeInAmtGivenOut(ctx, desiredTokenOut, tokenInDenom, spreadFactor, priceLimit, poolId, updateAccumulators)
}

func (k Keeper) InitOrUpdateTick(ctx sdk.Context, poolId uint64, tickIndex int64, liquidityIn osmomath.Dec, upper bool) (tickIsEmpty bool, err error) {
Expand Down
86 changes: 51 additions & 35 deletions x/concentrated-liquidity/swaps.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ func (k Keeper) swapOutAmtGivenIn(
spreadFactor osmomath.Dec,
priceLimit osmomath.BigDec,
) (calcTokenIn, calcTokenOut sdk.Coin, poolUpdates PoolUpdates, err error) {
swapResult, poolUpdates, err := k.computeOutAmtGivenIn(ctx, pool.GetId(), tokenIn, tokenOutDenom, spreadFactor, priceLimit)
swapResult, poolUpdates, err := k.computeOutAmtGivenIn(ctx, pool.GetId(), tokenIn, tokenOutDenom, spreadFactor, priceLimit, true)
if err != nil {
return sdk.Coin{}, sdk.Coin{}, PoolUpdates{}, err
}
Expand Down Expand Up @@ -281,7 +281,7 @@ func (k *Keeper) swapInAmtGivenOut(
spreadFactor osmomath.Dec,
priceLimit osmomath.BigDec,
) (calcTokenIn, calcTokenOut sdk.Coin, poolUpdates PoolUpdates, err error) {
swapResult, poolUpdates, err := k.computeInAmtGivenOut(ctx, desiredTokenOut, tokenInDenom, spreadFactor, priceLimit, pool.GetId())
swapResult, poolUpdates, err := k.computeInAmtGivenOut(ctx, desiredTokenOut, tokenInDenom, spreadFactor, priceLimit, pool.GetId(), true)
if err != nil {
return sdk.Coin{}, sdk.Coin{}, PoolUpdates{}, err
}
Expand Down Expand Up @@ -310,7 +310,7 @@ func (k Keeper) CalcOutAmtGivenIn(
spreadFactor osmomath.Dec,
) (tokenOut sdk.Coin, err error) {
cacheCtx, _ := ctx.CacheContext()
swapResult, _, err := k.computeOutAmtGivenIn(cacheCtx, poolI.GetId(), tokenIn, tokenOutDenom, spreadFactor, osmomath.ZeroBigDec())
swapResult, _, err := k.computeOutAmtGivenIn(cacheCtx, poolI.GetId(), tokenIn, tokenOutDenom, spreadFactor, osmomath.ZeroBigDec(), false)
if err != nil {
return sdk.Coin{}, err
}
Expand All @@ -325,7 +325,7 @@ func (k Keeper) CalcInAmtGivenOut(
spreadFactor osmomath.Dec,
) (sdk.Coin, error) {
cacheCtx, _ := ctx.CacheContext()
swapResult, _, err := k.computeInAmtGivenOut(cacheCtx, tokenOut, tokenInDenom, spreadFactor, osmomath.ZeroBigDec(), poolI.GetId())
swapResult, _, err := k.computeInAmtGivenOut(cacheCtx, tokenOut, tokenInDenom, spreadFactor, osmomath.ZeroBigDec(), poolI.GetId(), false)
if err != nil {
return sdk.Coin{}, err
}
Expand All @@ -335,15 +335,18 @@ func (k Keeper) CalcInAmtGivenOut(
func (k Keeper) swapSetup(ctx sdk.Context,
poolId uint64,
tokenInDenom string,
tokenOutDenom string) (pool types.ConcentratedPoolExtension, spreadAccum *accum.AccumulatorObject, err error) {
tokenOutDenom string,
getAccumulators bool) (pool types.ConcentratedPoolExtension, spreadAccum *accum.AccumulatorObject, err error) {
pool, err = k.getPoolForSwap(ctx, poolId)
if err != nil {
return pool, spreadAccum, err
}
if err := checkDenomValidity(tokenInDenom, tokenOutDenom, pool.GetToken0(), pool.GetToken1()); err != nil {
return pool, spreadAccum, err
}
spreadAccum, err = k.GetSpreadRewardAccumulator(ctx, poolId)
if getAccumulators {
spreadAccum, err = k.GetSpreadRewardAccumulator(ctx, poolId)
}
return pool, spreadAccum, err
}

Expand Down Expand Up @@ -387,8 +390,9 @@ func (k Keeper) computeOutAmtGivenIn(
tokenOutDenom string,
spreadFactor osmomath.Dec,
priceLimit osmomath.BigDec,
updateAccumulators bool,
) (swapResult SwapResult, poolUpdates PoolUpdates, err error) {
p, spreadRewardAccumulator, err := k.swapSetup(ctx, poolId, tokenInMin.Denom, tokenOutDenom)
p, spreadRewardAccumulator, err := k.swapSetup(ctx, poolId, tokenInMin.Denom, tokenOutDenom, updateAccumulators)
if err != nil {
return SwapResult{}, PoolUpdates{}, err
}
Expand Down Expand Up @@ -434,11 +438,13 @@ func (k Keeper) computeOutAmtGivenIn(
return SwapResult{}, PoolUpdates{}, err
}

// Update the spread reward growth for the entire swap using the total spread factors charged.
spreadFactorsAccruedPerUnitOfLiquidity := swapState.updateSpreadRewardGrowthGlobal(spreadRewardCharge)
if updateAccumulators {
// Update the spread reward growth for the entire swap using the total spread factors charged.
spreadFactorsAccruedPerUnitOfLiquidity := swapState.updateSpreadRewardGrowthGlobal(spreadRewardCharge)

// Emit telemetry to detect spread reward truncation.
emitAccumulatorUpdateTelemetry(types.SpreadFactorTruncationTelemetryName, spreadFactorsAccruedPerUnitOfLiquidity, spreadRewardCharge, poolId, swapState.liquidity, "is_out_given_in", "true")
// Emit telemetry to detect spread reward truncation.
emitAccumulatorUpdateTelemetry(types.SpreadFactorTruncationTelemetryName, spreadFactorsAccruedPerUnitOfLiquidity, spreadRewardCharge, poolId, swapState.liquidity, "is_out_given_in", "true")
}

ctx.Logger().Debug("cl calc out given in")
emitSwapDebugLogs(ctx, swapState, computedSqrtPrice, amountIn, amountOut, spreadRewardCharge)
Expand All @@ -454,7 +460,7 @@ func (k Keeper) computeOutAmtGivenIn(
// bucket has been consumed and we must move on to the next bucket to complete the swap
if nextInitializedTickSqrtPrice.Equal(computedSqrtPrice) {
swapState, err = k.swapCrossTickLogic(ctx, swapState, swapStrategy,
nextInitializedTick, nextInitTickIter, p, spreadRewardAccumulator, &uptimeAccums, tokenInMin.Denom)
nextInitializedTick, nextInitTickIter, p, spreadRewardAccumulator, &uptimeAccums, tokenInMin.Denom, updateAccumulators)
if err != nil {
return SwapResult{}, PoolUpdates{}, err
}
Expand Down Expand Up @@ -489,8 +495,10 @@ func (k Keeper) computeOutAmtGivenIn(
}

// Add spread reward growth per share to the pool-global spread reward accumulator.
spreadRewardGrowth := sdk.DecCoin{Denom: tokenInMin.Denom, Amount: swapState.globalSpreadRewardGrowthPerUnitLiquidity}
spreadRewardAccumulator.AddToAccumulator(sdk.NewDecCoins(spreadRewardGrowth))
if updateAccumulators {
spreadRewardGrowth := sdk.DecCoin{Denom: tokenInMin.Denom, Amount: swapState.globalSpreadRewardGrowthPerUnitLiquidity}
spreadRewardAccumulator.AddToAccumulator(sdk.NewDecCoins(spreadRewardGrowth))
}

// Coin amounts require int values
// Round amountIn up to avoid under charging
Expand Down Expand Up @@ -519,8 +527,9 @@ func (k Keeper) computeInAmtGivenOut(
spreadFactor osmomath.Dec,
priceLimit osmomath.BigDec,
poolId uint64,
updateAccumulators bool,
) (swapResult SwapResult, poolUpdates PoolUpdates, err error) {
p, spreadRewardAccumulator, err := k.swapSetup(ctx, poolId, tokenInDenom, desiredTokenOut.Denom)
p, spreadRewardAccumulator, err := k.swapSetup(ctx, poolId, tokenInDenom, desiredTokenOut.Denom, updateAccumulators)
if err != nil {
return SwapResult{}, PoolUpdates{}, err
}
Expand Down Expand Up @@ -564,10 +573,12 @@ func (k Keeper) computeInAmtGivenOut(
return SwapResult{}, PoolUpdates{}, err
}

spreadFactorsAccruedPerUnitOfLiquidity := swapState.updateSpreadRewardGrowthGlobal(spreadRewardChargeTotal)
if updateAccumulators {
spreadFactorsAccruedPerUnitOfLiquidity := swapState.updateSpreadRewardGrowthGlobal(spreadRewardChargeTotal)

// Emit telemetry to detect spread reward truncation.
emitAccumulatorUpdateTelemetry(types.SpreadFactorTruncationTelemetryName, spreadFactorsAccruedPerUnitOfLiquidity, spreadRewardChargeTotal, poolId, swapState.liquidity, "is_out_given_in", "false")
// Emit telemetry to detect spread reward truncation.
emitAccumulatorUpdateTelemetry(types.SpreadFactorTruncationTelemetryName, spreadFactorsAccruedPerUnitOfLiquidity, spreadRewardChargeTotal, poolId, swapState.liquidity, "is_out_given_in", "false")
}

ctx.Logger().Debug("cl calc in given out")
emitSwapDebugLogs(ctx, swapState, computedSqrtPrice, amountIn, amountOut, spreadRewardChargeTotal)
Expand All @@ -581,7 +592,7 @@ func (k Keeper) computeInAmtGivenOut(
// bucket has been consumed and we must move on to the next bucket by crossing a tick to complete the swap
if nextInitializedTickSqrtPrice.Equal(computedSqrtPrice) {
swapState, err = k.swapCrossTickLogic(ctx, swapState, swapStrategy,
nextInitializedTick, nextInitTickIter, p, spreadRewardAccumulator, &uptimeAccums, tokenInDenom)
nextInitializedTick, nextInitTickIter, p, spreadRewardAccumulator, &uptimeAccums, tokenInDenom, updateAccumulators)
if err != nil {
return SwapResult{}, PoolUpdates{}, err
}
Expand Down Expand Up @@ -615,7 +626,9 @@ func (k Keeper) computeInAmtGivenOut(
}

// Add spread reward growth per share to the pool-global spread reward accumulator.
spreadRewardAccumulator.AddToAccumulator(sdk.NewDecCoins(sdk.NewDecCoinFromDec(tokenInDenom, swapState.globalSpreadRewardGrowthPerUnitLiquidity)))
if updateAccumulators {
spreadRewardAccumulator.AddToAccumulator(sdk.NewDecCoins(sdk.NewDecCoinFromDec(tokenInDenom, swapState.globalSpreadRewardGrowthPerUnitLiquidity)))
}

// coin amounts require int values
// Round amount in up to avoid under charging the user.
Expand Down Expand Up @@ -652,29 +665,32 @@ func (k Keeper) swapCrossTickLogic(ctx sdk.Context,
nextInitializedTick int64, nextTickIter db.Iterator,
p types.ConcentratedPoolExtension,
spreadRewardAccum *accum.AccumulatorObject, uptimeAccums *[]*accum.AccumulatorObject,
tokenInDenom string) (SwapState, error) {
tokenInDenom string, updateAccumulators bool) (SwapState, error) {
Comment on lines 667 to +668
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in a subsequent PR I think I'll package these accum vars into their own struct.

nextInitializedTickInfo, err := ParseTickFromBz(nextTickIter.Value())
if err != nil {
return swapState, err
}
if *uptimeAccums == nil {
uptimeAccumsRaw, err := k.GetUptimeAccumulators(ctx, p.GetId())
if err != nil {
return swapState, err
if updateAccumulators {
if *uptimeAccums == nil {
uptimeAccumsRaw, err := k.GetUptimeAccumulators(ctx, p.GetId())
if err != nil {
return swapState, err
}
uptimeAccums = &uptimeAccumsRaw
}
uptimeAccums = &uptimeAccumsRaw
}

if err := k.updateGivenPoolUptimeAccumulatorsToNow(ctx, p, *uptimeAccums); err != nil {
return swapState, err
}
if err := k.updateGivenPoolUptimeAccumulatorsToNow(ctx, p, *uptimeAccums); err != nil {
return swapState, err
}

// Retrieve the liquidity held in the next closest initialized tick
spreadRewardGrowth := sdk.DecCoin{Denom: tokenInDenom, Amount: swapState.globalSpreadRewardGrowthPerUnitLiquidity}
liquidityNet, err := k.crossTick(ctx, p.GetId(), nextInitializedTick, &nextInitializedTickInfo, spreadRewardGrowth, spreadRewardAccum.GetValue(), *uptimeAccums)
if err != nil {
return swapState, err
// Retrieve the liquidity held in the next closest initialized tick
spreadRewardGrowth := sdk.DecCoin{Denom: tokenInDenom, Amount: swapState.globalSpreadRewardGrowthPerUnitLiquidity}
_, err := k.crossTick(ctx, p.GetId(), nextInitializedTick, &nextInitializedTickInfo, spreadRewardGrowth, spreadRewardAccum.GetValue(), *uptimeAccums)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a subsequent PR I'll refactor crossTick to not return LiquidityNet, it just reads that from one of the args.

if err != nil {
return swapState, err
}
}
liquidityNet := nextInitializedTickInfo.LiquidityNet

// Move next tick iterator to the next tick as the tick is crossed.
nextTickIter.Next()
Expand Down
8 changes: 4 additions & 4 deletions x/concentrated-liquidity/swaps_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2027,7 +2027,7 @@ func (s *KeeperTestSuite) TestComputeAndSwapOutAmtGivenIn() {
cacheCtx,
pool.GetId(),
test.TokenIn, test.TokenOutDenom,
test.SpreadFactor, test.PriceLimit)
test.SpreadFactor, test.PriceLimit, true)

if test.ExpectErr {
s.Require().Error(err)
Comment on lines 2027 to 2033
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📝 NOTE
This review was outside the diff hunks, and no overlapping diff hunk was found. Original lines [203-203]

The function populateSwapTestFields modifies the test cases in place to ensure that certain fields are not nil. Consider adding a comment to clarify the purpose of this function and why it's necessary to modify the test cases in this manner. This will improve the readability and maintainability of the test suite.

+ // populateSwapTestFields ensures that all test cases have non-nil expected values for certain fields.

Expand Down Expand Up @@ -2171,7 +2171,7 @@ func (s *KeeperTestSuite) TestComputeAndSwapInAmtGivenOut() {
swapResult, poolUpdates, err := s.App.ConcentratedLiquidityKeeper.ComputeInAmtGivenOut(
cacheCtx,
test.TokenOut, test.TokenInDenom,
test.SpreadFactor, test.PriceLimit, pool.GetId())
test.SpreadFactor, test.PriceLimit, pool.GetId(), true)
if test.ExpectErr {
s.Require().Error(err)
} else {
Expand Down Expand Up @@ -2671,7 +2671,7 @@ func (s *KeeperTestSuite) TestComputeOutAmtGivenIn() {
s.Ctx,
poolBeforeCalc.GetId(),
test.TokenIn, test.TokenOutDenom,
test.SpreadFactor, test.PriceLimit)
test.SpreadFactor, test.PriceLimit, true)
s.Require().NoError(err)

// check that the pool has not been modified after performing calc
Expand Down Expand Up @@ -2756,7 +2756,7 @@ func (s *KeeperTestSuite) TestComputeInAmtGivenOut() {
_, _, err := s.App.ConcentratedLiquidityKeeper.ComputeInAmtGivenOut(
s.Ctx,
test.TokenOut, test.TokenInDenom,
test.SpreadFactor, test.PriceLimit, poolBeforeCalc.GetId())
test.SpreadFactor, test.PriceLimit, poolBeforeCalc.GetId(), true)
s.Require().NoError(err)

// check that the pool has not been modified after performing calc
Expand Down