Commit d433c490 authored by Roman's avatar Roman Committed by Mergify
Browse files

refactor/test: improve DecApproxEq, fix misuse in mint hooks, create osmoassert package (#2322)

* refactor/test: improve DecApproxEq, fix misuse in mint hooks, create osmoassert package

* revert osmomath accidental rename

* revert osmomath

(cherry picked from commit 91141514)

# Conflicts:
#	osmomath/math_test.go
#	osmoutils/dec_helper.go
#	osmoutils/test_helpers.go
#	x/gamm/pool-models/internal/test_helpers/test_helpers.go
#	x/gamm/pool-models/stableswap/amm_test.go
#	x/gamm/twap/api_test.go
#	x/gamm/twap/logic_test.go
#	x/gamm/twap/types/utils_test.go
parent 896ce61d
Showing with 626 additions and 25 deletions
+626 -25
package osmoassert
import (
"testing"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
)
// ConditionalPanic checks if expectPanic is true, asserts that sut (system under test)
// panics. If expectPanic is false, asserts that sut does not panic.
// returns true if sut panics and false it it does not
func ConditionalPanic(t *testing.T, expectPanic bool, sut func()) {
if expectPanic {
require.Panics(t, sut)
return
}
require.NotPanics(t, sut)
}
// DecApproxEq is a helper function to compare two decimals.
// It validates the two decimal are within a certain tolerance.
// If not, it fails with a message.
func DecApproxEq(t *testing.T, d1 sdk.Dec, d2 sdk.Dec, tol sdk.Dec) {
diff := d1.Sub(d2).Abs()
require.True(t, diff.LTE(tol), "expected |d1 - d2| <:\t%s\ngot |d1 - d2| = \t\t%s\nd1: %s, d2: %s", tol, diff, d1, d2)
}
......@@ -5,6 +5,11 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
<<<<<<< HEAD
=======
"github.com/osmosis-labs/osmosis/v10/app/apptesting/osmoassert"
>>>>>>> 91141514 (refactor/test: improve DecApproxEq, fix misuse in mint hooks, create osmoassert package (#2322))
"github.com/stretchr/testify/require"
)
......@@ -38,6 +43,7 @@ func TestPowApprox(t *testing.T) {
"expected value & actual value's difference should less than precision",
)
<<<<<<< HEAD
base, err = sdk.NewDecFromStr("0.8")
require.NoError(t, err)
exp = sdk.ZeroDec()
......@@ -52,6 +58,20 @@ func TestPowApprox(t *testing.T) {
expectedDec.Sub(s).Abs().LTE(powPrecision),
"expected value & actual value's difference should less than precision",
)
=======
for i, tc := range testCases {
var actualResult sdk.Dec
osmoassert.ConditionalPanic(t, tc.base.Equal(sdk.ZeroDec()), func() {
fmt.Println(tc.base)
actualResult = PowApprox(tc.base, tc.exp, tc.powPrecision)
require.True(
t,
tc.expectedResult.Sub(actualResult).Abs().LTE(tc.powPrecision),
fmt.Sprintf("test %d failed: expected value & actual value's difference should be less than precision", i),
)
})
}
>>>>>>> 91141514 (refactor/test: improve DecApproxEq, fix misuse in mint hooks, create osmoassert package (#2322))
}
func TestPow(t *testing.T) {
......@@ -64,9 +84,23 @@ func TestPow(t *testing.T) {
expectedDec, err := sdk.NewDecFromStr("1.18058965")
require.NoError(t, err)
<<<<<<< HEAD
require.True(
t,
expectedDec.Sub(s).Abs().LTE(powPrecision),
"expected value & actual value's difference should less than precision",
)
=======
for i, tc := range testCases {
var actualResult sdk.Dec
osmoassert.ConditionalPanic(t, tc.base.Equal(sdk.ZeroDec()), func() {
actualResult = Pow(tc.base, tc.exp)
require.True(
t,
tc.expectedResult.Sub(actualResult).Abs().LTE(powPrecision),
fmt.Sprintf("test %d failed: expected value & actual value's difference should be less than precision", i),
)
})
}
>>>>>>> 91141514 (refactor/test: improve DecApproxEq, fix misuse in mint hooks, create osmoassert package (#2322))
}
......@@ -12,8 +12,8 @@ import (
"github.com/stretchr/testify/suite"
"github.com/osmosis-labs/osmosis/v10/app/apptesting"
"github.com/osmosis-labs/osmosis/v10/app/apptesting/osmoassert"
v10 "github.com/osmosis-labs/osmosis/v10/app/upgrades/v10"
"github.com/osmosis-labs/osmosis/v10/osmoutils"
"github.com/osmosis-labs/osmosis/v10/x/gamm/pool-models/balancer"
"github.com/osmosis-labs/osmosis/v10/x/gamm/types"
)
......@@ -721,7 +721,7 @@ func (suite *KeeperTestSuite) TestCalcJoinPoolShares() {
require.True(t, ok)
assertPoolStateNotModified(t, balancerPool, func() {
osmoutils.ConditionalPanic(t, tc.expectPanic, sut)
osmoassert.ConditionalPanic(t, tc.expectPanic, sut)
})
})
}
......
......@@ -9,7 +9,7 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"github.com/osmosis-labs/osmosis/v10/osmoutils"
"github.com/osmosis-labs/osmosis/v10/app/apptesting/osmoassert"
"github.com/osmosis-labs/osmosis/v10/x/gamm/pool-models/balancer"
"github.com/osmosis-labs/osmosis/v10/x/gamm/types"
)
......@@ -202,7 +202,7 @@ func TestCalcSingleAssetJoin(t *testing.T) {
}
assertPoolStateNotModified(t, balancerPool, func() {
osmoutils.ConditionalPanic(t, tc.expectPanic, sut)
osmoassert.ConditionalPanic(t, tc.expectPanic, sut)
})
})
}
......@@ -738,7 +738,7 @@ func TestCalcSingleAssetInAndOut_InverseRelationship(t *testing.T) {
)
tol := sdk.NewDec(1)
require.True(osmoutils.DecApproxEq(t, initialCalcTokenOut.ToDec(), inverseCalcTokenOut, tol))
osmoassert.DecApproxEq(t, initialCalcTokenOut.ToDec(), inverseCalcTokenOut, tol)
})
}
}
......
package test_helpers
import (
"testing"
"github.com/cosmos/cosmos-sdk/store/rootmulti"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/libs/log"
tmtypes "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"
"github.com/osmosis-labs/osmosis/v10/app/apptesting/osmoassert"
"github.com/osmosis-labs/osmosis/v10/x/gamm/types"
)
// CfmmCommonTestSuite is the common test suite struct of Constant Function Market Maker,
// that pool-models can inherit from.
type CfmmCommonTestSuite struct {
suite.Suite
}
func (suite *CfmmCommonTestSuite) CreateTestContext() sdk.Context {
db := dbm.NewMemDB()
logger := log.NewNopLogger()
ms := rootmulti.NewStore(db, logger)
return sdk.NewContext(ms, tmtypes.Header{}, false, logger)
}
func (suite *CfmmCommonTestSuite) TestCalculateAmountOutAndIn_InverseRelationship(
ctx sdk.Context,
pool types.PoolI,
assetInDenom string,
assetOutDenom string,
initialCalcOut int64,
swapFee sdk.Dec,
) {
initialOut := sdk.NewInt64Coin(assetOutDenom, initialCalcOut)
initialOutCoins := sdk.NewCoins(initialOut)
actualTokenIn, err := pool.CalcInAmtGivenOut(ctx, initialOutCoins, assetInDenom, swapFee)
suite.Require().NoError(err)
inverseTokenOut, err := pool.CalcOutAmtGivenIn(ctx, sdk.NewCoins(actualTokenIn), assetOutDenom, swapFee)
suite.Require().NoError(err)
suite.Require().Equal(initialOut.Denom, inverseTokenOut.Denom)
expected := initialOut.Amount.ToDec()
actual := inverseTokenOut.Amount.ToDec()
// allow a rounding error of up to 1 for this relation
tol := sdk.NewDec(1)
osmoassert.DecApproxEq(suite.T(), expected, actual, tol)
}
func TestCfmmCommonTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(CfmmCommonTestSuite))
}
......@@ -6,7 +6,12 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
<<<<<<< HEAD
"github.com/osmosis-labs/osmosis/v10/osmoutils"
=======
"github.com/osmosis-labs/osmosis/v10/app/apptesting/osmoassert"
"github.com/osmosis-labs/osmosis/v10/x/gamm/pool-models/internal/test_helpers"
>>>>>>> 91141514 (refactor/test: improve DecApproxEq, fix misuse in mint hooks, create osmoassert package (#2322))
)
func TestCFMMInvariantTwoAssets(t *testing.T) {
......@@ -40,15 +45,15 @@ func TestCFMMInvariantTwoAssets(t *testing.T) {
xOut := solveCfmm(test.xReserve, test.yReserve, test.yIn)
fmt.Println("xOut", xOut)
k1 := cfmmConstant(test.xReserve.Sub(xOut), test.yReserve.Add(test.yIn))
osmoutils.DecApproxEq(t, k0, k1, kErrTolerance)
osmoassert.DecApproxEq(t, k0, k1, kErrTolerance)
// using multi-asset cfmm (should be equivalent with u = 1, w = 0)
k2 := cfmmConstantMulti(test.xReserve, test.yReserve, sdk.OneDec(), sdk.ZeroDec())
osmoutils.DecApproxEq(t, k2, k0, kErrTolerance)
osmoassert.DecApproxEq(t, k2, k0, kErrTolerance)
xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, sdk.ZeroDec(), test.yIn)
fmt.Println(xOut2)
k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), sdk.OneDec(), sdk.ZeroDec())
osmoutils.DecApproxEq(t, k2, k3, kErrTolerance)
osmoassert.DecApproxEq(t, k2, k3, kErrTolerance)
}
}
......@@ -90,6 +95,6 @@ func TestCFMMInvariantMultiAssets(t *testing.T) {
xOut2 := solveCfmmMulti(test.xReserve, test.yReserve, test.wSumSquares, test.yIn)
fmt.Println(xOut2)
k3 := cfmmConstantMulti(test.xReserve.Sub(xOut2), test.yReserve.Add(test.yIn), test.uReserve, test.wSumSquares)
osmoutils.DecApproxEq(t, k2, k3, kErrTolerance)
osmoassert.DecApproxEq(t, k2, k3, kErrTolerance)
}
}
package twap_test
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/v10/x/gamm/twap/types"
)
var ThreePlusOneThird sdk.Dec = sdk.MustNewDecFromStr("3.333333333333333333")
func (s *TestSuite) TestGetBeginBlockAccumulatorRecord() {
poolId, denomA, denomB := s.setupDefaultPool()
initStartRecord := newRecord(s.Ctx.BlockTime(), sdk.OneDec(), sdk.ZeroDec(), sdk.ZeroDec())
initStartRecord.PoolId, initStartRecord.Height = poolId, s.Ctx.BlockHeight()
initStartRecord.Asset0Denom, initStartRecord.Asset1Denom = denomA, denomB
zeroAccumTenPoint1Record := recordWithUpdatedSpotPrice(initStartRecord, sdk.NewDec(10), sdk.NewDecWithPrec(1, 1))
tests := map[string]struct {
// if start record is blank, don't do any sets
startRecord types.TwapRecord
// We set it to have the updated time
expRecord types.TwapRecord
time time.Time
poolId uint64
quoteDenom string
baseDenom string
expError bool
}{
"no record (wrong pool ID)": {initStartRecord, initStartRecord, baseTime, 4, denomA, denomB, true},
"default record": {initStartRecord, initStartRecord, baseTime, 1, denomA, denomB, false},
"one second later record": {initStartRecord, recordWithUpdatedAccum(initStartRecord, OneSec, OneSec), tPlusOne, 1, denomA, denomB, false},
"idempotent overwrite": {initStartRecord, initStartRecord, baseTime, 1, denomA, denomB, false},
"idempotent overwrite2": {initStartRecord, recordWithUpdatedAccum(initStartRecord, OneSec, OneSec), tPlusOne, 1, denomA, denomB, false},
"diff spot price": {zeroAccumTenPoint1Record,
recordWithUpdatedAccum(zeroAccumTenPoint1Record, OneSec.MulInt64(10), OneSec.QuoInt64(10)),
tPlusOne, 1, denomA, denomB, false},
// TODO: Overflow
}
for name, tc := range tests {
s.Run(name, func() {
// setup time
s.Ctx = s.Ctx.WithBlockTime(tc.time)
tc.expRecord.Time = tc.time
s.twapkeeper.StoreNewRecord(s.Ctx, tc.startRecord)
actualRecord, err := s.twapkeeper.GetBeginBlockAccumulatorRecord(s.Ctx, tc.poolId, tc.baseDenom, tc.quoteDenom)
if tc.expError {
s.Require().Error(err)
return
}
s.Require().NoError(err)
s.Require().Equal(tc.expRecord, actualRecord)
})
}
}
func (s *TestSuite) TestGetArithmeticTwap() {
type getTwapInput struct {
poolId uint64
quoteAssetDenom string
baseAssetDenom string
startTime time.Time
endTime time.Time
}
makeSimpleTwapInput := func(startTime time.Time, endTime time.Time, isQuoteTokenA bool) getTwapInput {
quoteAssetDenom, baseAssetDenom := denom0, denom1
if isQuoteTokenA {
baseAssetDenom, quoteAssetDenom = quoteAssetDenom, baseAssetDenom
}
return getTwapInput{1, quoteAssetDenom, baseAssetDenom, startTime, endTime}
}
quoteAssetA := true
quoteAssetB := false
// base record is a record with t=baseTime, sp0=10, sp1=.1, accumulators set to 0
baseRecord := newTwapRecordWithDefaults(baseTime, sdk.NewDec(10), sdk.ZeroDec(), sdk.ZeroDec())
// record with t=baseTime+10, sp0=5, sp1=.2, accumulators updated from baseRecord
// accum0 = 10 seconds * (spot price = 10), accum1 = 10 seconds * (spot price = .1)
accum0, accum1 := OneSec.MulInt64(10*10), OneSec
tPlus10sp5Record := newTwapRecordWithDefaults(
baseTime.Add(10*time.Second), sdk.NewDec(5), accum0, accum1)
// TODO: Make use of the below for test cases:
// record with t=baseTime+20, sp0=2, sp1=.5, accumulators updated from tPlus10sp5Record
// tPlus20sp2Record := newTwapRecordWithDefaults(
// baseTime.Add(20*time.Second), sdk.NewDec(2), OneSec.MulInt64(10*10+5*10), OneSec.MulInt64(3))
tests := map[string]struct {
recordsToSet []types.TwapRecord
ctxTime time.Time
input getTwapInput
expTwap sdk.Dec
expErrorStr string
}{
"(1 record) start and end point to same record": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, tPlusOne, quoteAssetA),
expTwap: sdk.NewDec(10),
},
"(1 record) start and end point to same record, use sp1": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, tPlusOne, quoteAssetB),
expTwap: sdk.NewDecWithPrec(1, 1),
},
"(1 record) start and end point to same record, end time = now": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, baseTime.Add(time.Minute), quoteAssetA),
expTwap: sdk.NewDec(10),
},
"(2 record) start and end point to same record": {
recordsToSet: []types.TwapRecord{baseRecord, tPlus10sp5Record},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, tPlusOne, quoteAssetA),
expTwap: sdk.NewDec(10),
},
"(2 record) start and end exact, different records": {
recordsToSet: []types.TwapRecord{baseRecord, tPlus10sp5Record},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, baseTime.Add(10*time.Second), quoteAssetA),
expTwap: sdk.NewDec(10),
},
"(2 record) start exact, end after second record": {
recordsToSet: []types.TwapRecord{baseRecord, tPlus10sp5Record},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, baseTime.Add(20*time.Second), quoteAssetA),
expTwap: sdk.NewDecWithPrec(75, 1), // 10 for 10s, 5 for 10s
},
"(2 record) start exact, end after second record, sp1": {
recordsToSet: []types.TwapRecord{baseRecord, tPlus10sp5Record},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime, baseTime.Add(20*time.Second), quoteAssetB),
expTwap: sdk.NewDecWithPrec(15, 2), // .1 for 10s, .2 for 10s
},
"(2 record) start and end interpolated": {
recordsToSet: []types.TwapRecord{baseRecord, tPlus10sp5Record},
ctxTime: baseTime.Add(time.Minute),
input: makeSimpleTwapInput(baseTime.Add(5*time.Second), baseTime.Add(20*time.Second), quoteAssetA),
// 10 for 5s, 5 for 10s = 100/15 = 6 + 2/3 = 6.66666666
expTwap: ThreePlusOneThird.MulInt64(2),
},
// error catching
"end time in future": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime,
input: makeSimpleTwapInput(baseTime, tPlusOne, quoteAssetA),
expErrorStr: "future",
},
"start time after end time": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime,
input: makeSimpleTwapInput(tPlusOne, baseTime, quoteAssetA),
expErrorStr: "after",
},
"start time too old (end time = now)": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime,
input: makeSimpleTwapInput(baseTime.Add(-time.Hour), baseTime, quoteAssetA),
expErrorStr: "too old",
},
"start time too old": {
recordsToSet: []types.TwapRecord{baseRecord},
ctxTime: baseTime.Add(time.Second),
input: makeSimpleTwapInput(baseTime.Add(-time.Hour), baseTime, quoteAssetA),
expErrorStr: "too old",
},
// TODO: overflow tests, multi-asset pool handling, make more record interpolation cases
}
for name, test := range tests {
s.Run(name, func() {
s.SetupTest()
for _, record := range test.recordsToSet {
s.twapkeeper.StoreNewRecord(s.Ctx, record)
}
s.Ctx = s.Ctx.WithBlockTime(test.ctxTime)
twap, err := s.twapkeeper.GetArithmeticTwap(s.Ctx, test.input.poolId,
test.input.quoteAssetDenom, test.input.baseAssetDenom,
test.input.startTime, test.input.endTime)
if test.expErrorStr != "" {
s.Require().Error(err)
s.Require().Contains(err.Error(), test.expErrorStr)
return
}
s.Require().NoError(err)
s.Require().Equal(test.expTwap, twap)
})
}
}
package twap_test
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
"github.com/osmosis-labs/osmosis/v10/x/gamm/twap"
"github.com/osmosis-labs/osmosis/v10/x/gamm/twap/types"
)
var zeroDec = sdk.ZeroDec()
var oneDec = sdk.OneDec()
var twoDec = oneDec.Add(oneDec)
var OneSec = sdk.NewDec(1e9)
func newRecord(t time.Time, sp0, accum0, accum1 sdk.Dec) types.TwapRecord {
return types.TwapRecord{
Asset0Denom: defaultUniV2Coins[0].Denom,
Asset1Denom: defaultUniV2Coins[1].Denom,
Time: t,
P0LastSpotPrice: sp0,
P1LastSpotPrice: sdk.OneDec().Quo(sp0),
// make new copies
P0ArithmeticTwapAccumulator: accum0.Add(sdk.ZeroDec()),
P1ArithmeticTwapAccumulator: accum1.Add(sdk.ZeroDec()),
}
}
// make an expected record for math tests, we adjust other values in the test runner.
func newExpRecord(accum0, accum1 sdk.Dec) types.TwapRecord {
return types.TwapRecord{
Asset0Denom: defaultUniV2Coins[0].Denom,
Asset1Denom: defaultUniV2Coins[1].Denom,
// make new copies
P0ArithmeticTwapAccumulator: accum0.Add(sdk.ZeroDec()),
P1ArithmeticTwapAccumulator: accum1.Add(sdk.ZeroDec()),
}
}
func TestRecordWithUpdatedAccumulators(t *testing.T) {
tests := map[string]struct {
record types.TwapRecord
interpolateTime time.Time
expRecord types.TwapRecord
}{
"0accum": {
record: newRecord(time.Unix(1, 0), sdk.NewDec(10), zeroDec, zeroDec),
interpolateTime: time.Unix(2, 0),
expRecord: newExpRecord(OneSec.MulInt64(10), OneSec.QuoInt64(10)),
},
"small starting accumulators": {
record: newRecord(time.Unix(1, 0), sdk.NewDec(10), oneDec, twoDec),
interpolateTime: time.Unix(2, 0),
expRecord: newExpRecord(oneDec.Add(OneSec.MulInt64(10)), twoDec.Add(OneSec.QuoInt64(10))),
},
"larger time interval": {
record: newRecord(time.Unix(11, 0), sdk.NewDec(10), oneDec, twoDec),
interpolateTime: time.Unix(55, 0),
expRecord: newExpRecord(oneDec.Add(OneSec.MulInt64(44*10)), twoDec.Add(OneSec.MulInt64(44).QuoInt64(10))),
},
"same time": {
record: newRecord(time.Unix(1, 0), sdk.NewDec(10), oneDec, twoDec),
interpolateTime: time.Unix(1, 0),
expRecord: newExpRecord(oneDec, twoDec),
},
// TODO: Overflow tests
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
// correct expected record based off copy/paste values
test.expRecord.Time = test.interpolateTime
test.expRecord.P0LastSpotPrice = test.record.P0LastSpotPrice
test.expRecord.P1LastSpotPrice = test.record.P1LastSpotPrice
gotRecord := twap.RecordWithUpdatedAccumulators(test.record, test.interpolateTime)
require.Equal(t, test.expRecord, gotRecord)
})
}
}
func (s *TestSuite) TestUpdateTwap() {
poolId := s.PrepareUni2PoolWithAssets(defaultUniV2Coins[0], defaultUniV2Coins[1])
newSp := sdk.OneDec()
tests := map[string]struct {
record types.TwapRecord
updateTime time.Time
expRecord types.TwapRecord
}{
"0 accum start": {
record: newRecord(time.Unix(1, 0), sdk.NewDec(10), zeroDec, zeroDec),
updateTime: time.Unix(2, 0),
expRecord: newExpRecord(OneSec.MulInt64(10), OneSec.QuoInt64(10)),
},
}
for name, test := range tests {
s.Run(name, func() {
// setup common, block time, pool Id, expected spot prices
s.Ctx = s.Ctx.WithBlockTime(test.updateTime.UTC())
test.record.PoolId = poolId
test.expRecord.PoolId = poolId
test.expRecord.P0LastSpotPrice = newSp
test.expRecord.P1LastSpotPrice = newSp
test.expRecord.Height = s.Ctx.BlockHeight()
test.expRecord.Time = s.Ctx.BlockTime()
newRecord := s.twapkeeper.UpdateRecord(s.Ctx, test.record)
s.Require().Equal(test.expRecord, newRecord)
})
}
}
// TestComputeArithmeticTwap tests ComputeArithmeticTwap 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 TestComputeArithmeticTwap(t *testing.T) {
newOneSidedRecord := func(time time.Time, accum sdk.Dec, useP0 bool) types.TwapRecord {
record := types.TwapRecord{Time: time, Asset0Denom: denom0, Asset1Denom: denom1}
if useP0 {
record.P0ArithmeticTwapAccumulator = accum
} else {
record.P1ArithmeticTwapAccumulator = accum
}
record.P0LastSpotPrice = sdk.ZeroDec()
record.P1LastSpotPrice = sdk.OneDec()
return record
}
type testCase struct {
startRecord types.TwapRecord
endRecord types.TwapRecord
quoteAsset string
expTwap sdk.Dec
}
testCaseFromDeltas := func(startAccum, accumDiff sdk.Dec, timeDelta time.Duration, expectedTwap sdk.Dec) testCase {
return testCase{
newOneSidedRecord(baseTime, startAccum, true),
newOneSidedRecord(baseTime.Add(timeDelta), startAccum.Add(accumDiff), true),
denom0,
expectedTwap,
}
}
testCaseFromDeltasAsset1 := func(startAccum, accumDiff sdk.Dec, timeDelta time.Duration, expectedTwap sdk.Dec) testCase {
return testCase{
newOneSidedRecord(baseTime, startAccum, false),
newOneSidedRecord(baseTime.Add(timeDelta), startAccum.Add(accumDiff), false),
denom1,
expectedTwap,
}
}
tenSecAccum := OneSec.MulInt64(10)
pointOneAccum := OneSec.QuoInt64(10)
tests := map[string]testCase{
"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: denom0, end spot price = 0": {
startRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true),
endRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true),
quoteAsset: denom0,
expTwap: sdk.ZeroDec(),
},
"same record: denom1, end spot price = 1": {
startRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true),
endRecord: newOneSidedRecord(baseTime, sdk.ZeroDec(), true),
quoteAsset: denom1,
expTwap: sdk.OneDec(),
},
"accumulator = 10*OneSec, t=5s. 0 base accum": testCaseFromDeltas(
sdk.ZeroDec(), tenSecAccum, 5*time.Second, sdk.NewDec(2)),
"accumulator = 10*OneSec, t=3s. 0 base accum": testCaseFromDeltas(
sdk.ZeroDec(), tenSecAccum, 3*time.Second, ThreePlusOneThird),
"accumulator = 10*OneSec, t=100s. 0 base accum": testCaseFromDeltas(
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(
sdk.NewDec(10), tenSecAccum, 5*time.Second, sdk.NewDec(2)),
"accumulator = 10*OneSec, t=3s. 10*second base accum": testCaseFromDeltas(
tenSecAccum, tenSecAccum, 3*time.Second, ThreePlusOneThird),
"accumulator = 10*OneSec, t=100s. .1*second base accum": testCaseFromDeltas(
pointOneAccum, tenSecAccum, 100*time.Second, sdk.NewDecWithPrec(1, 1)),
"accumulator = 10*OneSec, t=100s. 0 base accum (asset 1)": testCaseFromDeltasAsset1(sdk.ZeroDec(), OneSec.MulInt64(10), 100*time.Second, sdk.NewDecWithPrec(1, 1)),
// TODO: Overflow, rounding
}
for name, test := range tests {
t.Run(name, func(t *testing.T) {
actualTwap := twap.ComputeArithmeticTwap(test.startRecord, test.endRecord, test.quoteAsset)
require.Equal(t, test.expTwap, actualTwap)
})
}
}
package types
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/osmosis-labs/osmosis/v10/app/apptesting/osmoassert"
)
func TestGetAllUniqueDenomPairs(t *testing.T) {
tests := map[string]struct {
denoms []string
wantedPairGT []string
wantedPairLT []string
panics bool
}{
"basic": {[]string{"A", "B"}, []string{"B"}, []string{"A"}, false},
"basicRev": {[]string{"B", "A"}, []string{"B"}, []string{"A"}, false},
// AB > A
"prefixed": {[]string{"A", "AB"}, []string{"AB"}, []string{"A"}, false},
"basic-3": {[]string{"A", "B", "C"}, []string{"C", "C", "B"}, []string{"B", "A", "A"}, false},
"panics": {[]string{"A", "A"}, []string{}, []string{}, true},
}
for name, tt := range tests {
t.Run(name, func(t *testing.T) {
osmoassert.ConditionalPanic(t, tt.panics, func() {
pairGT, pairLT := GetAllUniqueDenomPairs(tt.denoms)
require.Equal(t, pairGT, tt.wantedPairGT)
require.Equal(t, pairLT, tt.wantedPairLT)
})
})
}
}
......@@ -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/v10/osmoutils"
"github.com/osmosis-labs/osmosis/v10/app/apptesting/osmoassert"
"github.com/osmosis-labs/osmosis/v10/x/mint/keeper"
"github.com/osmosis-labs/osmosis/v10/x/mint/types"
)
......@@ -105,7 +105,7 @@ func (suite *KeeperTestSuite) TestMintInitGenesis() {
originalVestingCoins := bankKeeper.GetBalance(ctx, developerAccount, tc.mintDenom)
// Test.
osmoutils.ConditionalPanic(suite.T(), tc.expectPanic, func() { mintKeeper.InitGenesis(ctx, tc.mintGenesis) })
osmoassert.ConditionalPanic(suite.T(), tc.expectPanic, func() { mintKeeper.InitGenesis(ctx, tc.mintGenesis) })
if tc.expectPanic {
return
}
......
package keeper_test
import (
"testing"
"github.com/stretchr/testify/suite"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
osmoapp "github.com/osmosis-labs/osmosis/v10/app"
"github.com/osmosis-labs/osmosis/v10/osmoutils"
"github.com/osmosis-labs/osmosis/v10/app/apptesting/osmoassert"
"github.com/osmosis-labs/osmosis/v10/x/mint/keeper"
"github.com/osmosis-labs/osmosis/v10/x/mint/types"
......@@ -33,6 +36,10 @@ var (
}
)
func TestHooksTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}
// TestAfterEpochEnd tests that the after epoch end hook correctly
// distributes the rewards depending on what epoch it is in.
func (suite *KeeperTestSuite) TestAfterEpochEnd() {
......@@ -63,6 +70,7 @@ func (suite *KeeperTestSuite) TestAfterEpochEnd() {
suite.assertAddressWeightsAddUpToOne(testWeightedAddresses)
defaultGenesisEpochProvisionsDec, err := sdk.NewDecFromStr(defaultGenesisEpochProvisions)
suite.Require().NoError(err)
defaultMainnetThirdenedProvisionsDec, err := sdk.NewDecFromStr(defaultMainnetThirdenedProvisions)
suite.Require().NoError(err)
......@@ -192,6 +200,21 @@ func (suite *KeeperTestSuite) TestAfterEpochEnd() {
weightedAddresses: testWeightedAddresses,
mintStartEpoch: defaultReductionPeriodInEpochs,
expectedDistribution: defaultGenesisEpochProvisionsDec,
expectedLastReductionEpochNum: defaultReductionPeriodInEpochs,
},
"start epoch == curEpoch + 1 && reduction epoch == curEpoch": {
hookArgEpochNum: defaultReductionPeriodInEpochs,
mintDenom: sdk.DefaultBondDenom,
genesisEpochProvisions: defaultGenesisEpochProvisionsDec,
epochIdentifier: defaultEpochIdentifier,
reductionPeriodInEpochs: defaultReductionPeriodInEpochs,
reductionFactor: defaultReductionFactor,
distributionProportions: defaultDistributionProportions,
weightedAddresses: testWeightedAddresses,
mintStartEpoch: defaultReductionPeriodInEpochs - 1,
expectedDistribution: defaultMainnetThirdenedProvisionsDec,
expectedLastReductionEpochNum: defaultReductionPeriodInEpochs,
},
......@@ -224,7 +247,7 @@ func (suite *KeeperTestSuite) TestAfterEpochEnd() {
weightedAddresses: testWeightedAddresses,
mintStartEpoch: defaultMintingRewardsDistributionStartEpoch,
expectedDistribution: defaultGenesisEpochProvisionsDec,
expectedDistribution: sdk.ZeroDec(),
},
"custom genesisEpochProvisions, at start epoch": {
hookArgEpochNum: defaultMintingRewardsDistributionStartEpoch,
......@@ -321,7 +344,7 @@ func (suite *KeeperTestSuite) TestAfterEpochEnd() {
weightedAddresses: testWeightedAddresses,
mintStartEpoch: defaultMintingRewardsDistributionStartEpoch,
expectedDistribution: defaultGenesisEpochProvisionsDec,
expectedDistribution: sdk.ZeroDec(),
expectedLastReductionEpochNum: defaultMintingRewardsDistributionStartEpoch,
expectedPanic: true,
},
......@@ -362,32 +385,40 @@ func (suite *KeeperTestSuite) TestAfterEpochEnd() {
if tc.expectedPanic {
// If panic is expected, burn developer module account balance so that it causes an error that leads to a
// panic in the hook.
distrKeeper.FundCommunityPool(ctx, sdk.NewCoins(developerAccountBalanceBeforeHook), accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName))
suite.Require().NoError(distrKeeper.FundCommunityPool(ctx, sdk.NewCoins(developerAccountBalanceBeforeHook), accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName)))
developerAccountBalanceBeforeHook.Amount = sdk.ZeroInt()
}
osmoutils.ConditionalPanic(suite.T(), tc.expectedPanic, func() {
// Old supply
oldSupply := app.BankKeeper.GetSupply(ctx, sdk.DefaultBondDenom).Amount
suite.Require().Equal(sdk.NewInt(keeper.DeveloperVestingAmount), oldSupply)
osmoassert.ConditionalPanic(suite.T(), tc.expectedPanic, func() {
// System under test.
mintKeeper.AfterEpochEnd(ctx, defaultEpochIdentifier, tc.hookArgEpochNum)
})
// If panics, the behavior is undefined.
if tc.expectedPanic {
return
}
// Validate developer account balance.
developerAccountBalanceAfterHook := bankKeeper.GetBalance(ctx, accountKeeper.GetModuleAddress(types.DeveloperVestingModuleAcctName), sdk.DefaultBondDenom)
osmoutils.DecApproxEq(suite.T(), developerAccountBalanceBeforeHook.Amount.Sub(expectedDevRewards.TruncateInt()).ToDec(), developerAccountBalanceAfterHook.Amount.ToDec(), maxArithmeticTolerance)
osmoassert.DecApproxEq(suite.T(), developerAccountBalanceBeforeHook.Amount.Sub(expectedDevRewards.TruncateInt()).ToDec(), developerAccountBalanceAfterHook.Amount.ToDec(), maxArithmeticTolerance)
// Validate supply.
expectedSupply = expectedSupply.Add(tc.expectedDistribution).Sub(expectedDevRewards)
osmoutils.DecApproxEq(suite.T(), expectedSupply, developerAccountBalanceAfterHook.Amount.ToDec(), maxArithmeticTolerance)
osmoassert.DecApproxEq(suite.T(), expectedSupply.Add(tc.expectedDistribution).Sub(expectedDevRewards), app.BankKeeper.GetSupply(ctx, sdk.DefaultBondDenom).Amount.ToDec(), maxArithmeticTolerance)
// Validate supply with offset.
expectedSupplyWithOffset = expectedSupply.Sub(developerAccountBalanceAfterHook.Amount.ToDec())
osmoutils.DecApproxEq(suite.T(), expectedSupplyWithOffset, app.BankKeeper.GetSupplyWithOffset(ctx, sdk.DefaultBondDenom).Amount.ToDec(), maxArithmeticTolerance)
osmoassert.DecApproxEq(suite.T(), expectedSupplyWithOffset.Add(tc.expectedDistribution), app.BankKeeper.GetSupplyWithOffset(ctx, sdk.DefaultBondDenom).Amount.ToDec(), maxArithmeticTolerance)
// Validate epoch provisions.
suite.Require().Equal(tc.expectedLastReductionEpochNum, mintKeeper.GetLastHalvenEpochNum(ctx))
if !tc.expectedDistribution.IsZero() {
// Validate distribution.
osmoutils.DecApproxEq(suite.T(), tc.expectedDistribution, mintKeeper.GetMinter(ctx).EpochProvisions, sdk.NewDecWithPrec(1, 18))
osmoassert.DecApproxEq(suite.T(), tc.expectedDistribution, mintKeeper.GetMinter(ctx).EpochProvisions, sdk.NewDecWithPrec(1, 6))
}
})
}
......
......@@ -14,7 +14,7 @@ import (
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/osmosis-labs/osmosis/v10/app/apptesting"
"github.com/osmosis-labs/osmosis/v10/osmoutils"
"github.com/osmosis-labs/osmosis/v10/app/apptesting/osmoassert"
"github.com/osmosis-labs/osmosis/v10/x/mint/keeper"
"github.com/osmosis-labs/osmosis/v10/x/mint/types"
poolincentivestypes "github.com/osmosis-labs/osmosis/v10/x/pool-incentives/types"
......@@ -429,7 +429,7 @@ func (suite *KeeperTestSuite) TestDistributeToModule() {
for name, tc := range tests {
suite.Run(name, func() {
suite.Setup()
osmoutils.ConditionalPanic(suite.T(), tc.expectPanic, func() {
osmoassert.ConditionalPanic(suite.T(), tc.expectPanic, func() {
mintKeeper := suite.App.MintKeeper
bankKeeper := suite.App.BankKeeper
accountKeeper := suite.App.AccountKeeper
......@@ -665,7 +665,7 @@ func (suite *KeeperTestSuite) TestDistributeDeveloperRewards() {
suite.Run(name, func() {
suite.Setup()
osmoutils.ConditionalPanic(suite.T(), tc.expectPanic, func() {
osmoassert.ConditionalPanic(suite.T(), tc.expectPanic, func() {
mintKeeper := suite.App.MintKeeper
bankKeeper := suite.App.BankKeeper
accountKeeper := suite.App.AccountKeeper
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment