Skip to content

Commit

Permalink
feat(x/twap): use exp2 in twapPow; add overflow test (#3809)
Browse files Browse the repository at this point in the history
* feat(x/twap): use exp2 in twapPow; add overflow test

* Update x/twap/strategy_test.go

* tests

* clean up

* Update x/twap/logic.go

Co-authored-by: Dev Ojha <[email protected]>

* one hundred years

Co-authored-by: Dev Ojha <[email protected]>
  • Loading branch information
p0mvn and ValarDragon authored Dec 21, 2022
1 parent 7b056f9 commit 013dee4
Show file tree
Hide file tree
Showing 5 changed files with 61 additions and 39 deletions.
2 changes: 0 additions & 2 deletions x/twap/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ type (
GeometricTwapStrategy = geometric
)

var GeometricTwapMathBase = geometricTwapMathBase

func (k Keeper) StoreNewRecord(ctx sdk.Context, record types.TwapRecord) {
k.storeNewRecord(ctx, record)
}
Expand Down
19 changes: 6 additions & 13 deletions x/twap/logic.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,6 @@ import (
"github.com/osmosis-labs/osmosis/v13/x/twap/types"
)

// geometricTwapMathBase is the base used for geometric twap calculation
// in logarithm and power math functions.
// See twapLog and computeGeometricTwap functions for more details.
var (
geometricTwapMathBase = sdk.NewDec(2)
// TODO: analyze choice.
geometricTwapPowPrecision = sdk.MustNewDecFromStr("0.00000001")
)

func newTwapRecord(k types.AmmInterface, ctx sdk.Context, poolId uint64, denom0, denom1 string) (types.TwapRecord, error) {
denom0, denom1, err := types.LexicographicalOrderDenoms(denom0, denom1)
if err != nil {
Expand Down Expand Up @@ -268,13 +259,15 @@ func computeTwap(startRecord types.TwapRecord, endRecord types.TwapRecord, quote
}

// twapLog returns the logarithm of the given spot price, base 2.
// TODO: basic test
func twapLog(price sdk.Dec) sdk.Dec {
return osmomath.BigDecFromSDKDec(price).LogBase2().SDKDec()
}

// twapPow exponentiates the geometricTwapMathBase to the given exponent.
// TODO: basic test and benchmark.
// twapPow exponentiates 2 to the given exponent.
func twapPow(exponent sdk.Dec) sdk.Dec {
return osmomath.PowApprox(geometricTwapMathBase, exponent, geometricTwapPowPrecision)
exp2 := osmomath.Exp2(osmomath.BigDecFromSDKDec(exponent.Abs()))
if exponent.IsNegative() {
return osmomath.OneDec().Quo(exp2).SDKDec()
}
return exp2.SDKDec()
}
45 changes: 24 additions & 21 deletions x/twap/logic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package twap_test
import (
"errors"
"fmt"
"math"
"testing"
"time"

Expand Down Expand Up @@ -1319,30 +1318,34 @@ func (s *TestSuite) TestComputeArithmeticTwapWithSpotPriceError() {
}
}

func (s *TestSuite) TestTwapLog() {
var expectedErrTolerance = osmomath.MustNewDecFromStr("0.000000000000000100")
// "Twaplog{912648174127941279170121098210.928219201902041311} = 99.525973560175362367"
// From: https://www.wolframalpha.com/input?i2d=true&i=log+base+2+of+912648174127941279170121098210.928219201902041311+with+20+digits
var priceValue = osmomath.MustNewDecFromStr("912648174127941279170121098210.928219201902041311")
var expectedValue = osmomath.MustNewDecFromStr("99.525973560175362367")

result := twap.TwapLog(priceValue.SDKDec())
result_by_customBaseLog := priceValue.CustomBaseLog(osmomath.BigDecFromSDKDec(twap.GeometricTwapMathBase))
s.Require().True(expectedValue.Sub(osmomath.BigDecFromSDKDec(result)).Abs().LTE(expectedErrTolerance))
s.Require().True(result_by_customBaseLog.Sub(osmomath.BigDecFromSDKDec(result)).Abs().LTE(expectedErrTolerance))
// TestTwapLog_CorrectBase tests that the base of 2 is used for the twap log function.
// log_2{16} = 4
func (s *TestSuite) TestTwapLog_CorrectBase() {
logOf := sdk.NewDec(16)
expectedValue := sdk.NewDec(4)

result := twap.TwapLog(logOf)

s.Require().Equal(expectedValue, result)
}

func (s *TestSuite) TestTwapPow() {
var expectedErrTolerance = osmomath.MustNewDecFromStr("0.00000100")
// "TwapPow(0.5) = 1.41421356"
// From: https://www.wolframalpha.com/input?i2d=true&i=power+base+2+exponent+0.5+with+9+digits
exponentValue := osmomath.MustNewDecFromStr("0.5")
expectedValue := osmomath.MustNewDecFromStr("1.41421356")
// TestTwapPow_CorrectBase tests that the base of 2 is used for the twap power function.
// 2^3 = 8
func (s *TestSuite) TestTwapPow_CorrectBase() {
exponentValue := osmomath.NewBigDec(3)
expectedValue := sdk.NewDec(8)

result := twap.TwapPow(exponentValue.SDKDec())
result_by_mathPow := math.Pow(twap.GeometricTwapMathBase.MustFloat64(), exponentValue.SDKDec().MustFloat64())
s.Require().True(expectedValue.Sub(osmomath.BigDecFromSDKDec(result)).Abs().LTE(expectedErrTolerance))
s.Require().True(osmomath.MustNewDecFromStr(fmt.Sprint(result_by_mathPow)).Sub(osmomath.BigDecFromSDKDec(result)).Abs().LTE(expectedErrTolerance))

s.Require().Equal(expectedValue, result)
}

// TestTwapPow_NegativeExponent tests that twap pow can handle a negative exponent
// 2^-1 = 0.5
func (s *TestSuite) TestTwapPow_NegativeExponent() {
expectedResult := sdk.MustNewDecFromStr("0.5")
result := twap.TwapPow(oneDec.Neg())
s.Require().Equal(expectedResult, result)
}

func testCaseFromDeltas(s *TestSuite, startAccum, accumDiff sdk.Dec, timeDelta time.Duration, expectedTwap sdk.Dec) computeTwapTestCase {
Expand Down
13 changes: 10 additions & 3 deletions x/twap/strategy.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ package twap

import (
sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/osmosis-labs/osmosis/v13/osmomath"
"github.com/osmosis-labs/osmosis/v13/x/twap/types"

gammtypes "github.com/osmosis-labs/osmosis/v13/x/gamm/types"
)

// twapStrategy is an interface for computing TWAPs.
Expand Down Expand Up @@ -42,11 +46,14 @@ func (s *geometric) computeTwap(startRecord types.TwapRecord, endRecord types.Tw
timeDelta := endRecord.Time.Sub(startRecord.Time)
arithmeticMeanOfLogPrices := types.AccumDiffDivDuration(accumDiff, timeDelta)

geometricMeanDenom0 := twapPow(arithmeticMeanOfLogPrices)
result := twapPow(arithmeticMeanOfLogPrices)
// N.B.: Geometric mean of recprocals is reciprocal of geometric mean.
// https://proofwiki.org/wiki/Geometric_Mean_of_Reciprocals_is_Reciprocal_of_Geometric_Mean
if quoteAsset == startRecord.Asset1Denom {
return sdk.OneDec().Quo(geometricMeanDenom0)
result = sdk.OneDec().Quo(result)
}
return geometricMeanDenom0

// N.B. we round because this is the max number of significant figures supported
// by the underlying spot price function.
return osmomath.SigFigRound(result, gammtypes.SpotPriceSigFigs)
}
21 changes: 21 additions & 0 deletions x/twap/strategy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (

"github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert"
"github.com/osmosis-labs/osmosis/v13/osmomath"
gammtypes "github.com/osmosis-labs/osmosis/v13/x/gamm/types"
"github.com/osmosis-labs/osmosis/v13/x/twap"
"github.com/osmosis-labs/osmosis/v13/x/twap/types"
)
Expand All @@ -21,6 +22,10 @@ type computeTwapTestCase struct {
expPanic bool
}

var (
oneHundredYears = OneSec.MulInt64(60 * 60 * 24 * 365 * 100)
)

// TestComputeArithmeticTwap tests computeTwap on various inputs.
// The test vectors are structured by setting up different start and records,
// based on time interval, and their accumulator values.
Expand Down Expand Up @@ -300,3 +305,19 @@ func (s *TestSuite) TestComputeGeometricStrategyTwap_ThreeAsset() {
})
}
}

// TestTwapPow_MaxSpotPrice_NoOverflow tests that no overflow occurs at log_2{max spot price values}.
// and that the epsilon is within the tolerated multiplicative error.
func (s *TestSuite) TestTwapLogPow_MaxSpotPrice_NoOverflow() {
errTolerance := osmomath.ErrTolerance{
MultiplicativeTolerance: sdk.OneDec().Quo(sdk.NewDec(10).Power(18)),
RoundingDir: osmomath.RoundDown,
}

oneHundredYearsTimesMaxSpotPrice := oneHundredYears.Mul(gammtypes.MaxSpotPrice)

exponentValue := twap.TwapLog(oneHundredYearsTimesMaxSpotPrice)
finalValue := twap.TwapPow(exponentValue)

s.Require().Equal(0, errTolerance.CompareBigDec(osmomath.BigDecFromSDKDec(oneHundredYearsTimesMaxSpotPrice), osmomath.BigDecFromSDKDec(finalValue)))
}

0 comments on commit 013dee4

Please sign in to comment.