From 87bd021808992b4a46e6cf1e03290e407c443651 Mon Sep 17 00:00:00 2001 From: Dev Ojha Date: Wed, 21 Dec 2022 17:02:19 -0600 Subject: [PATCH] Move osmoassert into osmoutils, remove osmoutils twap dependence (#3817) * Move osmoassert into osmoutils * Remove twaptypes dependence * Add changelog (cherry picked from commit 4f04e735d1b683c9de2f0f3549ca5d2f01137f64) # Conflicts: # CHANGELOG.md # x/twap/logic_test.go # x/twap/strategy_test.go --- CHANGELOG.md | 6 + osmoutils/module_account_test.go | 2 +- .../osmoassert/assertions.go | 0 osmoutils/store_helper_test.go | 19 +- tests/e2e/e2e_test.go | 2 +- x/gamm/keeper/pool_service_test.go | 2 +- .../pool-models/balancer/pool_suite_test.go | 2 +- x/gamm/pool-models/balancer/pool_test.go | 2 +- x/gamm/pool-models/stableswap/amm_test.go | 2 +- x/gamm/pool-models/stableswap/pool_test.go | 2 +- x/mint/keeper/genesis_test.go | 2 +- x/mint/keeper/hooks_test.go | 2 +- x/mint/keeper/keeper_test.go | 2 +- x/twap/keeper_test.go | 2 +- x/twap/logic_test.go | 5 + x/twap/strategy_test.go | 323 ++++++++++++++++++ x/twap/types/utils_test.go | 2 +- 17 files changed, 355 insertions(+), 22 deletions(-) rename {app/apptesting => osmoutils}/osmoassert/assertions.go (100%) create mode 100644 x/twap/strategy_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d0f375f6f9..8e788400fd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -70,6 +70,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### API breaks * [#3763](https://github.com/osmosis-labs/osmosis/pull/3763) Move binary search and error tolerance code from `osmoutils` into `osmomath` +<<<<<<< HEAD +======= +* [#376x](https://github.com/osmosis-labs/osmosis/pull/3763) Remove Osmosis gamm and twap `bindings` that were previously supported as custom wasm plugins. +* [#3817](https://github.com/osmosis-labs/osmosis/pull/3817) Move osmoassert from `app/apptesting/osmoassert` to `osmoutils/osmoassert`. + +>>>>>>> 4f04e735 (Move osmoassert into osmoutils, remove osmoutils twap dependence (#3817)) ### Bug fixes diff --git a/osmoutils/module_account_test.go b/osmoutils/module_account_test.go index 1166f0b0a2b..cad3d5633b8 100644 --- a/osmoutils/module_account_test.go +++ b/osmoutils/module_account_test.go @@ -6,8 +6,8 @@ import ( "github.com/cosmos/cosmos-sdk/types/address" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" "github.com/osmosis-labs/osmosis/v13/osmoutils" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" ) func (s *TestSuite) TestCreateModuleAccount() { diff --git a/app/apptesting/osmoassert/assertions.go b/osmoutils/osmoassert/assertions.go similarity index 100% rename from app/apptesting/osmoassert/assertions.go rename to osmoutils/osmoassert/assertions.go diff --git a/osmoutils/store_helper_test.go b/osmoutils/store_helper_test.go index f059576767a..67333b3ac1a 100644 --- a/osmoutils/store_helper_test.go +++ b/osmoutils/store_helper_test.go @@ -16,10 +16,9 @@ import ( paramskeeper "github.com/cosmos/cosmos-sdk/x/params/keeper" paramstypes "github.com/cosmos/cosmos-sdk/x/params/types" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" "github.com/osmosis-labs/osmosis/v13/osmoutils" "github.com/osmosis-labs/osmosis/v13/osmoutils/noapptest" - twaptypes "github.com/osmosis-labs/osmosis/v13/x/twap/types" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" ) // We need to setup a test suite with account keeper @@ -800,7 +799,7 @@ func (s *TestSuite) TestMustGet() { expectPanic: true, }, - "invalid proto Dec vs TwapRecord- error": { + "invalid proto Dec vs AuthParams- error": { preSetKeyValues: map[string]proto.Message{ keyA: &sdk.DecProto{Dec: sdk.OneDec()}, }, @@ -809,7 +808,7 @@ func (s *TestSuite) TestMustGet() { keyA: &sdk.DecProto{Dec: sdk.OneDec()}, }, - actualResultProto: &twaptypes.TwapRecord{}, + actualResultProto: &authtypes.Params{}, expectPanic: true, }, @@ -884,7 +883,7 @@ func (s *TestSuite) TestGet() { expectErr: false, }, - "invalid proto Dec vs TwapRecord - found but Unmarshal err": { + "invalid proto Dec vs AuthParams - found but Unmarshal err": { preSetKeyValues: map[string]proto.Message{ keyA: &sdk.DecProto{Dec: sdk.OneDec()}, }, @@ -893,7 +892,7 @@ func (s *TestSuite) TestGet() { keyA: &sdk.DecProto{Dec: sdk.OneDec()}, }, - actualResultProto: &twaptypes.TwapRecord{}, + actualResultProto: &authtypes.Params{}, expectFound: true, @@ -951,13 +950,13 @@ func (s *TestSuite) TestMustSet() { actualResultProto: &sdk.DecProto{}, }, - "basic valid TwapRecord test": { + "basic valid AuthParams test": { setKey: keyA, - setValue: &twaptypes.TwapRecord{ - PoolId: 2, + setValue: &authtypes.Params{ + MaxMemoCharacters: 600, }, - actualResultProto: &twaptypes.TwapRecord{}, + actualResultProto: &authtypes.Params{}, }, "invalid set value": { setKey: keyA, diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index bc6331cb35a..5f522b25fa5 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -16,8 +16,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" coretypes "github.com/tendermint/tendermint/rpc/core/types" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" appparams "github.com/osmosis-labs/osmosis/v13/app/params" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" "github.com/osmosis-labs/osmosis/v13/tests/e2e/configurer/config" "github.com/osmosis-labs/osmosis/v13/tests/e2e/initialization" ) diff --git a/x/gamm/keeper/pool_service_test.go b/x/gamm/keeper/pool_service_test.go index 23a67796f6e..e5648541967 100644 --- a/x/gamm/keeper/pool_service_test.go +++ b/x/gamm/keeper/pool_service_test.go @@ -6,7 +6,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" "github.com/osmosis-labs/osmosis/v13/x/gamm/pool-models/balancer" balancertypes "github.com/osmosis-labs/osmosis/v13/x/gamm/pool-models/balancer" "github.com/osmosis-labs/osmosis/v13/x/gamm/types" diff --git a/x/gamm/pool-models/balancer/pool_suite_test.go b/x/gamm/pool-models/balancer/pool_suite_test.go index 9bd8ad3a2f1..977c11e174a 100644 --- a/x/gamm/pool-models/balancer/pool_suite_test.go +++ b/x/gamm/pool-models/balancer/pool_suite_test.go @@ -12,8 +12,8 @@ import ( "github.com/stretchr/testify/suite" "github.com/osmosis-labs/osmosis/v13/app/apptesting" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" v10 "github.com/osmosis-labs/osmosis/v13/app/upgrades/v10" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" "github.com/osmosis-labs/osmosis/v13/x/gamm/pool-models/balancer" "github.com/osmosis-labs/osmosis/v13/x/gamm/types" ) diff --git a/x/gamm/pool-models/balancer/pool_test.go b/x/gamm/pool-models/balancer/pool_test.go index 0ff0e56fdea..0d37da27ded 100644 --- a/x/gamm/pool-models/balancer/pool_test.go +++ b/x/gamm/pool-models/balancer/pool_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" "github.com/osmosis-labs/osmosis/osmomath" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" "github.com/osmosis-labs/osmosis/v13/x/gamm/pool-models/balancer" "github.com/osmosis-labs/osmosis/v13/x/gamm/pool-models/internal/test_helpers" "github.com/osmosis-labs/osmosis/v13/x/gamm/types" diff --git a/x/gamm/pool-models/stableswap/amm_test.go b/x/gamm/pool-models/stableswap/amm_test.go index ea7590d7579..26cdc0df337 100644 --- a/x/gamm/pool-models/stableswap/amm_test.go +++ b/x/gamm/pool-models/stableswap/amm_test.go @@ -11,7 +11,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/osmosis-labs/osmosis/osmomath" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" sdkrand "github.com/osmosis-labs/osmosis/v13/simulation/simtypes/random" "github.com/osmosis-labs/osmosis/v13/x/gamm/pool-models/internal/cfmm_common" "github.com/osmosis-labs/osmosis/v13/x/gamm/pool-models/internal/test_helpers" diff --git a/x/gamm/pool-models/stableswap/pool_test.go b/x/gamm/pool-models/stableswap/pool_test.go index 0c68445bada..b97f82b786d 100644 --- a/x/gamm/pool-models/stableswap/pool_test.go +++ b/x/gamm/pool-models/stableswap/pool_test.go @@ -10,7 +10,7 @@ import ( "github.com/tendermint/tendermint/crypto/ed25519" "github.com/osmosis-labs/osmosis/osmomath" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" "github.com/osmosis-labs/osmosis/v13/x/gamm/pool-models/internal/cfmm_common" "github.com/osmosis-labs/osmosis/v13/x/gamm/types" ) diff --git a/x/mint/keeper/genesis_test.go b/x/mint/keeper/genesis_test.go index 2bdeb09d160..74439b88ec2 100644 --- a/x/mint/keeper/genesis_test.go +++ b/x/mint/keeper/genesis_test.go @@ -4,7 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" "github.com/osmosis-labs/osmosis/v13/x/mint/keeper" "github.com/osmosis-labs/osmosis/v13/x/mint/types" ) diff --git a/x/mint/keeper/hooks_test.go b/x/mint/keeper/hooks_test.go index 0357831b24b..503d7a3837d 100644 --- a/x/mint/keeper/hooks_test.go +++ b/x/mint/keeper/hooks_test.go @@ -7,7 +7,7 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" osmoapp "github.com/osmosis-labs/osmosis/v13/app" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" "github.com/osmosis-labs/osmosis/v13/x/mint/keeper" "github.com/osmosis-labs/osmosis/v13/x/mint/types" diff --git a/x/mint/keeper/keeper_test.go b/x/mint/keeper/keeper_test.go index c1d5b124016..0c8ccf5cc1b 100644 --- a/x/mint/keeper/keeper_test.go +++ b/x/mint/keeper/keeper_test.go @@ -14,7 +14,7 @@ import ( tmproto "github.com/tendermint/tendermint/proto/tendermint/types" "github.com/osmosis-labs/osmosis/v13/app/apptesting" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" "github.com/osmosis-labs/osmosis/v13/x/mint/keeper" "github.com/osmosis-labs/osmosis/v13/x/mint/types" poolincentivestypes "github.com/osmosis-labs/osmosis/v13/x/pool-incentives/types" diff --git a/x/twap/keeper_test.go b/x/twap/keeper_test.go index f2c37d87f76..7f2f6e3053a 100644 --- a/x/twap/keeper_test.go +++ b/x/twap/keeper_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/suite" "github.com/osmosis-labs/osmosis/v13/app/apptesting" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" "github.com/osmosis-labs/osmosis/v13/x/twap" "github.com/osmosis-labs/osmosis/v13/x/twap/types" ) diff --git a/x/twap/logic_test.go b/x/twap/logic_test.go index 243bb082589..fcffdd56cdc 100644 --- a/x/twap/logic_test.go +++ b/x/twap/logic_test.go @@ -9,8 +9,13 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" +<<<<<<< HEAD "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" +======= + "github.com/osmosis-labs/osmosis/osmomath" +>>>>>>> 4f04e735 (Move osmoassert into osmoutils, remove osmoutils twap dependence (#3817)) "github.com/osmosis-labs/osmosis/v13/osmoutils" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" 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" diff --git a/x/twap/strategy_test.go b/x/twap/strategy_test.go new file mode 100644 index 00000000000..96112db3ae4 --- /dev/null +++ b/x/twap/strategy_test.go @@ -0,0 +1,323 @@ +package twap_test + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/osmosis-labs/osmosis/osmomath" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" + 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" +) + +type computeTwapTestCase struct { + startRecord types.TwapRecord + endRecord types.TwapRecord + twapStrategies []twap.TwapStrategy + quoteAsset string + expTwap sdk.Dec + expErr bool + 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. +// Then an expected TWAP is provided in each test case, to compare against computed. +func (s *TestSuite) TestComputeTwap() { + arithStrategy := &twap.ArithmeticTwapStrategy{ + TwapKeeper: *s.App.TwapKeeper, + } + + geomStrategy := &twap.GeometricTwapStrategy{ + TwapKeeper: *s.App.TwapKeeper, + } + + tests := map[string]computeTwapTestCase{ + "arithmetic only, basic: spot price = 1 for one second, 0 init accumulator": { + startRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true), + endRecord: newOneSidedRecord(tPlusOne, OneSec, true), + quoteAsset: denom0, + twapStrategies: []twap.TwapStrategy{ + arithStrategy, + }, + expTwap: sdk.OneDec(), + }, + // this test just shows what happens in case the records are reversed. + // It should return the correct result, even though this is incorrect internal API usage + "arithmetic only: invalid call: reversed records of above": { + startRecord: newOneSidedRecord(tPlusOne, OneSec, true), + endRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true), + quoteAsset: denom0, + twapStrategies: []twap.TwapStrategy{ + arithStrategy, + }, + expTwap: sdk.OneDec(), + }, + "same record: denom0, end spot price = 0": { + startRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true), + endRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true), + quoteAsset: denom0, + twapStrategies: []twap.TwapStrategy{ + arithStrategy, + geomStrategy, + }, + expTwap: sdk.ZeroDec(), + }, + "same record: denom1, end spot price = 1": { + startRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true), + endRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true), + quoteAsset: denom1, + twapStrategies: []twap.TwapStrategy{ + arithStrategy, + geomStrategy, + }, + expTwap: sdk.OneDec(), + }, + "arithmetic only: accumulator = 10*OneSec, t=5s. 0 base accum": testCaseFromDeltas( + s, sdk.ZeroDec(), tenSecAccum, 5*time.Second, sdk.NewDec(2)), + "arithmetic only: accumulator = 10*OneSec, t=100s. 0 base accum (asset 1)": testCaseFromDeltasAsset1(s, sdk.ZeroDec(), OneSec.MulInt64(10), 100*time.Second, sdk.NewDecWithPrec(1, 1)), + "geometric only: accumulator = log(10)*OneSec, t=5s. 0 base accum": geometricTestCaseFromDeltas0( + s, sdk.ZeroDec(), geometricTenSecAccum, 5*time.Second, twap.TwapPow(geometricTenSecAccum.QuoInt64(5*1000))), + "geometric only: accumulator = log(10)*OneSec, t=100s. 0 base accum (asset 1)": geometricTestCaseFromDeltas1(s, sdk.ZeroDec(), geometricTenSecAccum, 100*time.Second, sdk.OneDec().Quo(twap.TwapPow(geometricTenSecAccum.QuoInt64(100*1000)))), + "three asset same record: asset1, end spot price = 1": { + startRecord: newThreeAssetOneSidedRecord(baseTime, sdk.ZeroDec(), true)[1], + endRecord: newThreeAssetOneSidedRecord(baseTime, sdk.ZeroDec(), true)[1], + quoteAsset: denom2, + expTwap: sdk.OneDec(), + twapStrategies: []twap.TwapStrategy{ + arithStrategy, + geomStrategy, + }, + }, + } + for name, test := range tests { + s.Run(name, func() { + for _, twapStrategy := range test.twapStrategies { + actualTwap, err := twap.ComputeTwap(test.startRecord, test.endRecord, test.quoteAsset, twapStrategy) + s.Require().NoError(err) + osmoassert.DecApproxEq(s.T(), test.expTwap, actualTwap, osmomath.GetPowPrecision()) + } + }) + } +} + +// TestComputeArithmeticStrategyTwap tests arithmetic strategy's computeTwap +// Contrary to computeTwap function (logic.go) that handles the cases with zero delta correctly, +// this function should panic in case of zero delta. +func (s *TestSuite) TestComputeArithmeticStrategyTwap() { + pointOneAccum := OneSec.QuoInt64(10) + tests := map[string]computeTwapTestCase{ + "basic: spot price = 1 for one second, 0 init accumulator": { + startRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true), + endRecord: newOneSidedRecord(tPlusOne, OneSec, true), + quoteAsset: denom0, + expTwap: sdk.OneDec(), + }, + // this test just shows what happens in case the records are reversed. + // It should return the correct result, even though this is incorrect internal API usage + "invalid call: reversed records of above": { + startRecord: newOneSidedRecord(tPlusOne, OneSec, true), + endRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true), + quoteAsset: denom0, + expTwap: sdk.OneDec(), + }, + "same record (zero time delta), division by 0 - panic": { + startRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true), + endRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true), + quoteAsset: denom0, + expPanic: true, + }, + "accumulator = 10*OneSec, t=5s. 0 base accum": testCaseFromDeltas( + s, sdk.ZeroDec(), tenSecAccum, 5*time.Second, sdk.NewDec(2)), + "accumulator = 10*OneSec, t=3s. 0 base accum": testCaseFromDeltas( + s, sdk.ZeroDec(), tenSecAccum, 3*time.Second, ThreePlusOneThird), + "accumulator = 10*OneSec, t=100s. 0 base accum": testCaseFromDeltas( + s, sdk.ZeroDec(), tenSecAccum, 100*time.Second, sdk.NewDecWithPrec(1, 1)), + + // test that base accum has no impact + "accumulator = 10*OneSec, t=5s. 10 base accum": testCaseFromDeltas( + s, sdk.NewDec(10), tenSecAccum, 5*time.Second, sdk.NewDec(2)), + "accumulator = 10*OneSec, t=3s. 10*second base accum": testCaseFromDeltas( + s, tenSecAccum, tenSecAccum, 3*time.Second, ThreePlusOneThird), + "accumulator = 10*OneSec, t=100s. .1*second base accum": testCaseFromDeltas( + s, pointOneAccum, tenSecAccum, 100*time.Second, sdk.NewDecWithPrec(1, 1)), + + "accumulator = 10*OneSec, t=100s. 0 base accum (asset 1)": testCaseFromDeltasAsset1(s, sdk.ZeroDec(), OneSec.MulInt64(10), 100*time.Second, sdk.NewDecWithPrec(1, 1)), + } + for name, test := range tests { + s.Run(name, func() { + osmoassert.ConditionalPanic(s.T(), test.expPanic, func() { + arithmeticStrategy := &twap.ArithmeticTwapStrategy{TwapKeeper: *s.App.TwapKeeper} + actualTwap := arithmeticStrategy.ComputeTwap(test.startRecord, test.endRecord, test.quoteAsset) + s.Require().Equal(test.expTwap, actualTwap) + }) + }) + } +} + +// TestComputeGeometricStrategyTwap tests geometric strategy's computeTwap +// Contrary to computeTwap function (logic.go) that handles the cases with zero delta correctly, +// this function should panic in case of zero delta. +func (s *TestSuite) TestComputeGeometricStrategyTwap() { + tests := map[string]computeTwapTestCase{ + // basic test for both denom with zero start accumulator + "basic denom0: spot price = 1 for one second, 0 init accumulator": { + startRecord: newOneSidedGeometricRecord(baseTime, sdk.ZeroDec()), + endRecord: newOneSidedGeometricRecord(tPlusOne, geometricTenSecAccum), + quoteAsset: denom0, + expTwap: sdk.NewDec(10), + }, + "basic denom1: spot price = 1 for one second, 0 init accumulator": { + startRecord: newOneSidedGeometricRecord(baseTime, sdk.ZeroDec()), + endRecord: newOneSidedGeometricRecord(tPlusOne, geometricTenSecAccum), + quoteAsset: denom1, + expTwap: sdk.OneDec().Quo(sdk.NewDec(10)), + }, + + // basic test for both denom with non-zero start accumulator + "denom0: start accumulator of 10 * 1s, end accumulator 10 * 1s + 20 * 2s = 20": { + startRecord: newOneSidedGeometricRecord(baseTime, geometricTenSecAccum), + endRecord: newOneSidedGeometricRecord(baseTime.Add(time.Second*2), geometricTenSecAccum.Add(OneSec.MulInt64(2).Mul(twap.TwapLog(sdk.NewDec(20))))), + quoteAsset: denom0, + expTwap: sdk.NewDec(20), + }, + "denom1 start accumulator of 10 * 1s, end accumulator 10 * 1s + 20 * 2s = 20": { + startRecord: newOneSidedGeometricRecord(baseTime, geometricTenSecAccum), + endRecord: newOneSidedGeometricRecord(baseTime.Add(time.Second*2), geometricTenSecAccum.Add(OneSec.MulInt64(2).Mul(twap.TwapLog(sdk.NewDec(20))))), + quoteAsset: denom1, + expTwap: sdk.OneDec().Quo(sdk.NewDec(20)), + }, + + // toggle time delta. + "accumulator = log(10)*OneSec, t=5s. 0 base accum": geometricTestCaseFromDeltas0( + s, sdk.ZeroDec(), geometricTenSecAccum, 5*time.Second, twap.TwapPow(geometricTenSecAccum.QuoInt64(5*1000))), + "accumulator = log(10)*OneSec, t=3s. 0 base accum": geometricTestCaseFromDeltas0( + s, sdk.ZeroDec(), geometricTenSecAccum, 3*time.Second, twap.TwapPow(geometricTenSecAccum.QuoInt64(3*1000))), + "accumulator = log(10)*OneSec, t=100s. 0 base accum": geometricTestCaseFromDeltas0( + s, sdk.ZeroDec(), geometricTenSecAccum, 100*time.Second, twap.TwapPow(geometricTenSecAccum.QuoInt64(100*1000))), + + // test that base accum has no impact + "accumulator = log(10)*OneSec, t=5s. 10 base accum": geometricTestCaseFromDeltas0( + s, logTen, geometricTenSecAccum, 5*time.Second, twap.TwapPow(geometricTenSecAccum.QuoInt64(5*1000))), + "accumulator = log(10)*OneSec, t=3s. 10*second base accum": geometricTestCaseFromDeltas0( + s, OneSec.MulInt64(10).Mul(logTen), geometricTenSecAccum, 3*time.Second, twap.TwapPow(geometricTenSecAccum.QuoInt64(3*1000))), + "accumulator = 10*OneSec, t=100s. .1*second base accum": geometricTestCaseFromDeltas0( + s, OneSec.MulInt64(10).Mul(logOneOverTen), geometricTenSecAccum, 100*time.Second, twap.TwapPow(geometricTenSecAccum.QuoInt64(100*1000))), + + // TODO: this is the highest price we currently support with the given precision bounds. + // Need to choose better base and potentially improve math functions to mitigate. + "price of 1_000_000 for an hour": { + startRecord: newOneSidedGeometricRecord(baseTime, sdk.ZeroDec()), + endRecord: newOneSidedGeometricRecord(baseTime.Add(time.Hour), OneSec.MulInt64(60*60).Mul(twap.TwapLog(sdk.NewDec(1_000_000)))), + quoteAsset: denom0, + expTwap: sdk.NewDec(1_000_000), + }, + // TODO: overflow tests + // - max spot price + // - large time delta + // - both + + // TODO: hand calculated tests + } + + for name, tc := range tests { + s.Run(name, func() { + osmoassert.ConditionalPanic(s.T(), tc.expPanic, func() { + geometricStrategy := &twap.GeometricTwapStrategy{TwapKeeper: *s.App.TwapKeeper} + actualTwap := geometricStrategy.ComputeTwap(tc.startRecord, tc.endRecord, tc.quoteAsset) + osmoassert.DecApproxEq(s.T(), tc.expTwap, actualTwap, osmomath.GetPowPrecision()) + }) + }) + } +} + +func (s *TestSuite) TestComputeArithmeticStrategyTwap_ThreeAsset() { + tenSecAccum := OneSec.MulInt64(10) + pointOneAccum := OneSec.QuoInt64(10) + tests := map[string]computeThreeAssetArithmeticTwapTestCase{ + "three asset basic: spot price = 1 for one second, 0 init accumulator": { + startRecord: newThreeAssetOneSidedRecord(baseTime, sdk.ZeroDec(), true), + endRecord: newThreeAssetOneSidedRecord(tPlusOne, OneSec, true), + quoteAsset: []string{denom0, denom0, denom1}, + expTwap: []sdk.Dec{sdk.OneDec(), sdk.OneDec(), sdk.OneDec()}, + }, + "three asset. accumulator = 10*OneSec, t=5s. 0 base accum": testThreeAssetCaseFromDeltas( + sdk.ZeroDec(), tenSecAccum, 5*time.Second, sdk.NewDec(2)), + + // test that base accum has no impact + "three asset. accumulator = 10*OneSec, t=5s. 10 base accum": testThreeAssetCaseFromDeltas( + sdk.NewDec(10), tenSecAccum, 5*time.Second, sdk.NewDec(2)), + "three asset. accumulator = 10*OneSec, t=100s. .1*second base accum": testThreeAssetCaseFromDeltas( + pointOneAccum, tenSecAccum, 100*time.Second, sdk.NewDecWithPrec(1, 1)), + } + for name, test := range tests { + s.Run(name, func() { + for i, startRec := range test.startRecord { + arithmeticStrategy := &twap.ArithmeticTwapStrategy{TwapKeeper: *s.App.TwapKeeper} + actualTwap := arithmeticStrategy.ComputeTwap(startRec, test.endRecord[i], test.quoteAsset[i]) + s.Require().Equal(test.expTwap[i], actualTwap) + } + }) + } +} + +func (s *TestSuite) TestComputeGeometricStrategyTwap_ThreeAsset() { + var ( + five = sdk.NewDec(5) + fiveFor3Sec = OneSec.MulInt64(3).Mul(twap.TwapLog(five)) + + ten = five.MulInt64(2) + tenFor100Sec = OneSec.MulInt64(100).Mul(twap.TwapLog(ten)) + + errTolerance = sdk.MustNewDecFromStr("0.00000001") + ) + + tests := map[string]computeThreeAssetArithmeticTwapTestCase{ + "three asset basic: spot price = 10 for one second, 0 init accumulator": { + startRecord: newThreeAssetOneSidedRecord(baseTime, sdk.ZeroDec(), true), + endRecord: newThreeAssetOneSidedRecord(tPlusOne, geometricTenSecAccum, true), + quoteAsset: []string{denom0, denom0, denom1}, + expTwap: []sdk.Dec{sdk.NewDec(10), sdk.NewDec(10), sdk.NewDec(10)}, + }, + "three asset. accumulator = 5*3Sec, t=3s, no start accum": testThreeAssetCaseFromDeltas( + sdk.ZeroDec(), fiveFor3Sec, 3*time.Second, five), + + // test that base accum has no impact + "three asset. accumulator = 5*3Sec, t=3s. 10 base accum": testThreeAssetCaseFromDeltas( + geometricTenSecAccum, fiveFor3Sec, 3*time.Second, five), + "three asset. accumulator = 100*100s, t=100s. .1*second base accum": testThreeAssetCaseFromDeltas( + twap.TwapLog(OneSec.Quo(ten)), tenFor100Sec, 100*time.Second, ten), + } + for name, test := range tests { + s.Run(name, func() { + for i, startRec := range test.startRecord { + geometricStrategy := &twap.GeometricTwapStrategy{TwapKeeper: *s.App.TwapKeeper} + actualTwap := geometricStrategy.ComputeTwap(startRec, test.endRecord[i], test.quoteAsset[i]) + osmoassert.DecApproxEq(s.T(), test.expTwap[i], actualTwap, errTolerance) + } + }) + } +} + +// 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))) +} diff --git a/x/twap/types/utils_test.go b/x/twap/types/utils_test.go index b2af7f12959..b784f37a985 100644 --- a/x/twap/types/utils_test.go +++ b/x/twap/types/utils_test.go @@ -6,7 +6,7 @@ import ( "github.com/stretchr/testify/require" - "github.com/osmosis-labs/osmosis/v13/app/apptesting/osmoassert" + "github.com/osmosis-labs/osmosis/v13/osmoutils/osmoassert" "github.com/osmosis-labs/osmosis/v13/x/gamm/types" )