diff --git a/x/concentrated-liquidity/swapstrategy/export_test.go b/x/concentrated-liquidity/swapstrategy/export_test.go index 25b40459a51..e80823a0f48 100644 --- a/x/concentrated-liquidity/swapstrategy/export_test.go +++ b/x/concentrated-liquidity/swapstrategy/export_test.go @@ -3,9 +3,12 @@ package swapstrategy import "github.com/osmosis-labs/osmosis/osmomath" func ComputeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget bool, amountIn, amountSpecifiedRemaining, spreadFactor osmomath.Dec) osmomath.Dec { - return computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget, amountIn, amountSpecifiedRemaining, spreadFactor) + oneMinusSpreadFactorGetter := func() osmomath.Dec { + return osmomath.OneDec().Sub(spreadFactor) + } + return computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget, amountIn, amountSpecifiedRemaining, spreadFactor, oneMinusSpreadFactorGetter) } func ComputeSpreadRewardChargeFromAmountIn(amountIn, spreadFactor osmomath.Dec) osmomath.Dec { - return computeSpreadRewardChargeFromAmountIn(amountIn, spreadFactor) + return computeSpreadRewardChargeFromAmountIn(amountIn, spreadFactor, oneDec.Sub(spreadFactor)) } diff --git a/x/concentrated-liquidity/swapstrategy/one_for_zero.go b/x/concentrated-liquidity/swapstrategy/one_for_zero.go index a4d24e5c204..1e5e55d4b11 100644 --- a/x/concentrated-liquidity/swapstrategy/one_for_zero.go +++ b/x/concentrated-liquidity/swapstrategy/one_for_zero.go @@ -24,6 +24,9 @@ type oneForZeroStrategy struct { sqrtPriceLimit osmomath.BigDec storeKey storetypes.StoreKey spreadFactor osmomath.Dec + + // oneMinusSpreadFactor is 1 - spreadFactor + oneMinusSpreadFactor osmomath.Dec } var _ SwapStrategy = (*oneForZeroStrategy)(nil) @@ -64,7 +67,7 @@ func (s oneForZeroStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, amountOneIn := math.CalcAmount1Delta(liquidity, sqrtPriceTarget, sqrtPriceCurrent, true) // Calculate sqrtPriceNext on the amount of token remaining after spread reward. - oneMinusTakerFee := oneDec.Sub(s.spreadFactor) + oneMinusTakerFee := s.getOneMinusSpreadFactor() amountOneInRemainingLessSpreadReward := osmomath.NewBigDecFromDecMulDec(amountOneInRemaining, oneMinusTakerFee) var sqrtPriceNext osmomath.BigDec @@ -94,7 +97,7 @@ func (s oneForZeroStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, // Handle spread rewards. // Note that spread reward is always charged on the amount in. - spreadRewardChargeTotal := computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget, amountInDecFinal, amountOneInRemaining, s.spreadFactor) + spreadRewardChargeTotal := computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget, amountInDecFinal, amountOneInRemaining, s.spreadFactor, s.getOneMinusSpreadFactor) // Round down amount out to give user less in pool's favor. return sqrtPriceNext, amountInDecFinal, amountZeroOut.Dec(), spreadRewardChargeTotal @@ -159,7 +162,7 @@ func (s oneForZeroStrategy) ComputeSwapWithinBucketInGivenOut(sqrtPriceCurrent, // Handle spread rewards. // Note that spread reward is always charged on the amount in. - spreadRewardChargeTotal := computeSpreadRewardChargeFromAmountIn(amountOneInFinal, s.spreadFactor) + spreadRewardChargeTotal := computeSpreadRewardChargeFromAmountIn(amountOneInFinal, s.spreadFactor, s.getOneMinusSpreadFactor()) // Cap the output amount to not exceed the remaining output amount. // The reason why we must do this for in given out and NOT out given in is the following: @@ -180,6 +183,13 @@ func (s oneForZeroStrategy) ComputeSwapWithinBucketInGivenOut(sqrtPriceCurrent, return sqrtPriceNext, amountZeroOut.Dec(), amountOneInFinal, spreadRewardChargeTotal } +func (s oneForZeroStrategy) getOneMinusSpreadFactor() osmomath.Dec { + if s.oneMinusSpreadFactor.IsNil() { + s.oneMinusSpreadFactor = oneDec.Sub(s.spreadFactor) + } + return s.oneMinusSpreadFactor +} + // InitializeNextTickIterator returns iterator that seeks to the next tick from the given tickIndex. // In one for zero direction, the search is EXCLUSIVE of the current tick index. // If next tick relative to currentTickIndex is not initialized (does not exist in the store), diff --git a/x/concentrated-liquidity/swapstrategy/spread_rewards.go b/x/concentrated-liquidity/swapstrategy/spread_rewards.go index 47d8b3b54ba..8e76b1f8747 100644 --- a/x/concentrated-liquidity/swapstrategy/spread_rewards.go +++ b/x/concentrated-liquidity/swapstrategy/spread_rewards.go @@ -6,6 +6,8 @@ import ( "github.com/osmosis-labs/osmosis/osmomath" ) +type oneMinusSpreadFactorGetter func() osmomath.Dec + // computeSpreadRewardChargePerSwapStepOutGivenIn returns the total spread factor charge per swap step given the parameters. // Assumes swapping for token out given token in. // @@ -22,7 +24,7 @@ import ( // // If spread factor is negative, it panics. // If spread factor is 0, returns 0. Otherwise, computes and returns the spread factor charge per step. -func computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget bool, amountIn, amountSpecifiedRemaining, spreadFactor osmomath.Dec) osmomath.Dec { +func computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget bool, amountIn, amountSpecifiedRemaining, spreadFactor osmomath.Dec, oneMinSf oneMinusSpreadFactorGetter) osmomath.Dec { if spreadFactor.IsZero() { return osmomath.ZeroDec() } else if spreadFactor.IsNegative() { @@ -37,7 +39,7 @@ func computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget bool, amoun // 2) or sqrtPriceLimit is reached // In both cases, we charge the spread factor on the amount in actually consumed before // hitting the target. - spreadRewardChargeTotal = computeSpreadRewardChargeFromAmountIn(amountIn, spreadFactor) + spreadRewardChargeTotal = computeSpreadRewardChargeFromAmountIn(amountIn, spreadFactor, oneMinSf()) } else { // Otherwise, the current tick had enough liquidity to fulfill the swap // and we ran out of amount remaining before reaching either the next tick or the limit. @@ -59,6 +61,6 @@ func computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget bool, amoun // at precision end. This is necessary to ensure that the spread factor charge is always // rounded in favor of the pool. // TODO: Change this fn to take in 1 - spreadFactor as it should already have been computed. -func computeSpreadRewardChargeFromAmountIn(amountIn osmomath.Dec, spreadFactor osmomath.Dec) osmomath.Dec { - return amountIn.MulRoundUp(spreadFactor).QuoRoundupMut(osmomath.OneDec().SubMut(spreadFactor)) +func computeSpreadRewardChargeFromAmountIn(amountIn osmomath.Dec, spreadFactor, oneMinusSpreadFactor osmomath.Dec) osmomath.Dec { + return amountIn.MulRoundUp(spreadFactor).QuoRoundupMut(oneMinusSpreadFactor) } diff --git a/x/concentrated-liquidity/swapstrategy/zero_for_one.go b/x/concentrated-liquidity/swapstrategy/zero_for_one.go index 3029b5e1978..d84e416f585 100644 --- a/x/concentrated-liquidity/swapstrategy/zero_for_one.go +++ b/x/concentrated-liquidity/swapstrategy/zero_for_one.go @@ -24,6 +24,9 @@ type zeroForOneStrategy struct { sqrtPriceLimit osmomath.BigDec storeKey storetypes.StoreKey spreadFactor osmomath.Dec + + // oneMinusSpreadFactor is 1 - spreadFactor + oneMinusSpreadFactor osmomath.Dec } var _ SwapStrategy = (*zeroForOneStrategy)(nil) @@ -66,7 +69,7 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, amountZeroIn := math.CalcAmount0Delta(liquidity, sqrtPriceTarget, sqrtPriceCurrent, true) // N.B.: if this is false, causes infinite loop // Calculate sqrtPriceNext on the amount of token remaining after spread reward. - oneMinusTakerFee := oneDec.Sub(s.spreadFactor) + oneMinusTakerFee := s.getOneMinusSpreadFactor() amountZeroInRemainingLessSpreadReward := osmomath.NewBigDecFromDecMulDec(amountZeroInRemaining, oneMinusTakerFee) var sqrtPriceNext osmomath.BigDec @@ -96,7 +99,7 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketOutGivenIn(sqrtPriceCurrent, // Handle spread rewards. // Note that spread reward is always charged on the amount in. - spreadRewardChargeTotal := computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget, amountZeroInFinal, amountZeroInRemaining, s.spreadFactor) + spreadRewardChargeTotal := computeSpreadRewardChargePerSwapStepOutGivenIn(hasReachedTarget, amountZeroInFinal, amountZeroInRemaining, s.spreadFactor, s.getOneMinusSpreadFactor) // Round down amount out to give user less in pool's favor. return sqrtPriceNext, amountZeroInFinal, amountOneOut.Dec(), spreadRewardChargeTotal @@ -158,7 +161,7 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketInGivenOut(sqrtPriceCurrent, // Handle spread rewards. // Note that spread reward is always charged on the amount in. - spreadRewardChargeTotal := computeSpreadRewardChargeFromAmountIn(amountZeroInFinal, s.spreadFactor) + spreadRewardChargeTotal := computeSpreadRewardChargeFromAmountIn(amountZeroInFinal, s.spreadFactor, s.getOneMinusSpreadFactor()) // Cap the output amount to not exceed the remaining output amount. // The reason why we must do this for in given out and NOT out given in is the following: @@ -179,6 +182,13 @@ func (s zeroForOneStrategy) ComputeSwapWithinBucketInGivenOut(sqrtPriceCurrent, return sqrtPriceNext, amountOneOut.Dec(), amountZeroInFinal, spreadRewardChargeTotal } +func (s zeroForOneStrategy) getOneMinusSpreadFactor() osmomath.Dec { + if s.oneMinusSpreadFactor.IsNil() { + s.oneMinusSpreadFactor = oneDec.Sub(s.spreadFactor) + } + return s.oneMinusSpreadFactor +} + // InitializeNextTickIterator returns iterator that searches for the next tick given currentTickIndex. // In zero for one direction, the search is INCLUSIVE of the current tick index. // If next tick relative to currentTickIndex is not initialized (does not exist in the store),