Skip to content

Commit

Permalink
CL: Speedup TickToPrice by only doing one bigint operation (#8061)
Browse files Browse the repository at this point in the history
  • Loading branch information
ValarDragon authored Apr 20, 2024
1 parent 65e9496 commit fe42d56
Showing 1 changed file with 43 additions and 19 deletions.
62 changes: 43 additions & 19 deletions x/concentrated-liquidity/math/tick.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,35 +76,29 @@ func TickToPrice(tickIndex int64) (osmomath.BigDec, error) {
return types.MinSpotPriceV2, nil
}

// Check that the tick index is between min and max value
if tickIndex < types.MinCurrentTickV2 {
return osmomath.BigDec{}, types.TickIndexMinimumError{MinTick: types.MinCurrentTickV2}
}
if tickIndex > types.MaxTick {
return osmomath.BigDec{}, types.TickIndexMaximumError{MaxTick: types.MaxTick}
numAdditiveTicks, geometricExponentDelta, err := TickToAdditiveGeometricIndices(tickIndex)
if err != nil {
return osmomath.BigDec{}, err
}

// Use floor division to determine what the geometricExponent is now (the delta from the starting exponentAtPriceOne)
geometricExponentDelta := tickIndex / geometricExponentIncrementDistanceInTicks
// price = 10^geometricExponentDelta + numAdditiveTicks * 10^exponentAtCurrentTick
// exponent at current tick = types.ExponentAtPriceOne + geometricExponentDelta + conditional
// where conditional = -1 if tickIndex < 0, 0 otherwise
// so we compute the price as (10**(geometricExponentDelta - exponentAtCurrentTick) + numAdditiveTicks) * 10**exponentAtCurrentTick
// notice that geometricExponentDelta - exponentAtCurrentTick is either 6 or 7
// so we compute this as unscaledPrice = (10**(geometricExponentDelta - exponentAtCurrentTick) + numAdditiveTicks)

// Calculate the exponentAtCurrentTick from the starting exponentAtPriceOne and the geometricExponentDelta
exponentAtCurrentTick := types.ExponentAtPriceOne + geometricExponentDelta
var unscaledPrice int64 = 1_000_000
if tickIndex < 0 {
// We must decrement the exponentAtCurrentTick when entering the negative tick range in order to constantly step up in precision when going further down in ticks
// Otherwise, from tick 0 to tick -(geometricExponentIncrementDistanceInTicks), we would use the same exponent as the exponentAtPriceOne
exponentAtCurrentTick = exponentAtCurrentTick - 1
unscaledPrice *= 10
}

// Knowing what our exponentAtCurrentTick is, we can then figure out what power of 10 this exponent corresponds to
// We need to utilize bigDec here since increments can go beyond the 10^-18 limits set by the sdk
currentAdditiveIncrementInTicks := powTenBigDec(exponentAtCurrentTick)

// Now, starting at the minimum tick of the current increment, we calculate how many ticks in the current geometricExponent we have passed
numAdditiveTicks := tickIndex - (geometricExponentDelta * geometricExponentIncrementDistanceInTicks)
additiveSpacing := currentAdditiveIncrementInTicks.MulInt64(numAdditiveTicks)

// Finally, we can calculate the price
price := additiveSpacing.AddMut(powTenBigDec(geometricExponentDelta))
unscaledPrice += numAdditiveTicks
price := powTenBigDec(exponentAtCurrentTick).MulInt64(unscaledPrice)

// defense in depth, this logic would not be reached due to use having checked if given tick is in between
// min tick and max tick.
Expand All @@ -114,6 +108,36 @@ func TickToPrice(tickIndex int64) (osmomath.BigDec, error) {
return price, nil
}

func TickToAdditiveGeometricIndices(tickIndex int64) (additiveTicks int64, geometricExponentDelta int64, err error) {
if tickIndex == 0 {
return 0, 0, nil
}

// N.B. We special case MinInitializedTickV2 and MinCurrentTickV2 since MinInitializedTickV2
// is the first one that requires taking 10 to the exponent of (-31 + -6) = -37
// Given BigDec's precision of 36, that cannot be supported.
// The fact that MinInitializedTickV2 and MinCurrentTickV2 translate to the same
// price is acceptable since MinCurrentTickV2 cannot be initialized.
if tickIndex == types.MinInitializedTickV2 || tickIndex == types.MinCurrentTickV2 {
return 0, -30, nil
}

// Check that the tick index is between min and max value
if tickIndex < types.MinCurrentTickV2 {
return 0, 0, types.TickIndexMinimumError{MinTick: types.MinCurrentTickV2}
}
if tickIndex > types.MaxTick {
return 0, 0, types.TickIndexMaximumError{MaxTick: types.MaxTick}
}

// Use floor division to determine what the geometricExponent is now (the delta from the starting exponentAtPriceOne)
geometricExponentDelta = tickIndex / geometricExponentIncrementDistanceInTicks

// Now, starting at the minimum tick of the current increment, we calculate how many ticks in the current geometricExponent we have passed
numAdditiveTicks := tickIndex - (geometricExponentDelta * geometricExponentIncrementDistanceInTicks)
return numAdditiveTicks, geometricExponentDelta, nil
}

// RoundDownTickToSpacing rounds the tick index down to the nearest tick spacing if the tickIndex is in between authorized tick values
// Note that this is Euclidean modulus.
// The difference from default Go modulus is that Go default results
Expand Down

0 comments on commit fe42d56

Please sign in to comment.