Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Generalize gamm Inverse relationship test , add Inverse relationship test for stableswap #1456

Merged
merged 12 commits into from
Jul 1, 2022
13 changes: 13 additions & 0 deletions app/apptesting/test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/store/rootmulti"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authsigning "github.com/cosmos/cosmos-sdk/x/auth/signing"
Expand All @@ -18,7 +19,10 @@ import (
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/libs/log"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
tmtypes "github.com/tendermint/tendermint/proto/tendermint/types"
dbm "github.com/tendermint/tm-db"

"github.com/osmosis-labs/osmosis/v7/app"
"github.com/osmosis-labs/osmosis/v7/x/gamm/pool-models/balancer"
Expand Down Expand Up @@ -47,6 +51,15 @@ func (s *KeeperTestHelper) Setup() {
s.TestAccs = CreateRandomAccounts(3)
}

func (s *KeeperTestHelper) CreateTestContext() sdk.Context {
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
db := dbm.NewMemDB()
logger := log.NewNopLogger()

ms := rootmulti.NewStore(db, logger)

return sdk.NewContext(ms, tmtypes.Header{}, false, logger)
}

func (s *KeeperTestHelper) FundAcc(acc sdk.AccAddress, amounts sdk.Coins) {
err := simapp.FundAccount(s.App.BankKeeper, s.Ctx, acc, amounts)
s.Require().NoError(err)
Expand Down
5 changes: 5 additions & 0 deletions x/gamm/pool-models/balancer/amm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,13 @@ import (
"github.com/stretchr/testify/require"

"github.com/osmosis-labs/osmosis/v7/x/gamm/pool-models/balancer"
"github.com/osmosis-labs/osmosis/v7/x/gamm/pool-models/internal/test_helpers"
)

type BalancerTestSuite struct {
mattverse marked this conversation as resolved.
Show resolved Hide resolved
test_helpers.CfmmCommonTestSuite
}

func TestBalancerPoolParams(t *testing.T) {
// Tests that creating a pool with the given pair of swapfee and exit fee
// errors or succeeds as intended. Furthermore, it checks that
Expand Down
21 changes: 3 additions & 18 deletions x/gamm/pool-models/balancer/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,7 +504,7 @@ func TestGetPoolAssetsByDenom(t *testing.T) {

// TestCalculateAmountOutAndIn_InverseRelationship tests that the same amount of token is guaranteed upon
// sequential operation of CalcInAmtGivenOut and CalcOutAmtGivenIn.
func TestCalculateAmountOutAndIn_InverseRelationship(t *testing.T) {
func (suite *BalancerTestSuite) TestBalancerCalculateAmountOutAndIn_InverseRelationship(t *testing.T) {
type testcase struct {
denomOut string
initialPoolOut int64
Expand Down Expand Up @@ -584,7 +584,7 @@ func TestCalculateAmountOutAndIn_InverseRelationship(t *testing.T) {
for _, tc := range testcases {
for _, swapFee := range swapFeeCases {
t.Run(getTestCaseName(tc, swapFee), func(t *testing.T) {
ctx := createTestContext(t)
ctx := suite.CreateTestContext()

poolAssetOut := balancer.PoolAsset{
Token: sdk.NewInt64Coin(tc.denomOut, tc.initialPoolOut),
Expand All @@ -605,24 +605,9 @@ func TestCalculateAmountOutAndIn_InverseRelationship(t *testing.T) {
pool := createTestPool(t, swapFeeDec, exitFeeDec, poolAssetOut, poolAssetIn)
require.NotNil(t, pool)

initialOut := sdk.NewInt64Coin(poolAssetOut.Token.Denom, tc.initialCalcOut)
initialOutCoins := sdk.NewCoins(initialOut)

sut := func() {
actualTokenIn, err := pool.CalcInAmtGivenOut(ctx, initialOutCoins, poolAssetIn.Token.Denom, swapFeeDec)
require.NoError(t, err)

inverseTokenOut, err := pool.CalcOutAmtGivenIn(ctx, sdk.NewCoins(actualTokenIn), poolAssetOut.Token.Denom, swapFeeDec)
require.NoError(t, err)

require.Equal(t, initialOut.Denom, inverseTokenOut.Denom)

expected := initialOut.Amount.ToDec()
actual := inverseTokenOut.Amount.ToDec()
suite.TestCalculateAmountOutAndIn_InverseRelationship(ctx, pool, poolAssetIn.Token.Denom, poolAssetOut.Token.Denom, tc.initialCalcOut, swapFeeDec)

// allow a rounding error of up to 1 for this relation
tol := sdk.NewDec(1)
require.True(osmoutils.DecApproxEq(t, expected, actual, tol))
}

balancerPool, ok := pool.(*balancer.Pool)
Expand Down
2 changes: 0 additions & 2 deletions x/gamm/pool-models/internal/cfmm_common/lp_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,6 @@ func TestCalcExitPool(t *testing.T) {
stableswap.PoolParams{ExitFee: sdk.ZeroDec()},
twoStablePoolAssets,
"",
time.Now(),
)
require.NoError(t, err)

Expand All @@ -64,7 +63,6 @@ func TestCalcExitPool(t *testing.T) {
stableswap.PoolParams{ExitFee: sdk.MustNewDecFromStr("0.0001")},
twoStablePoolAssets,
"",
time.Now(),
)
require.NoError(t, err)

Expand Down
63 changes: 63 additions & 0 deletions x/gamm/pool-models/internal/test_helpers/test_helpers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
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/v7/osmoutils"
"github.com/osmosis-labs/osmosis/v7/x/gamm/types"
)

// CfmmCommonTestSuite is the common test suite struct of Constant Function Market Maker,
// that pool-models can inherit from.
type CfmmCommonTestSuite struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we add a small godoc to what CFMM abbreviation is?

suite.Suite
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why aren't we just using apptesting here?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also delete the CreateTestContext if we used apptesting

Copy link
Member Author

@mattverse mattverse Jun 17, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's because we have import cycle problems when we do that since the apptesting package imports gamm module and the balancer module

}

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)
_, approxEqual, _, _, _ := osmoutils.DecApproxEq(suite.T(), expected, actual, tol)
suite.Require().True(approxEqual)
}

func TestCfmmCommonTestSuite(t *testing.T) {
t.Parallel()
suite.Run(t, new(CfmmCommonTestSuite))
}
109 changes: 108 additions & 1 deletion x/gamm/pool-models/stableswap/amm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,14 @@ import (

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"

"github.com/osmosis-labs/osmosis/v7/x/gamm/pool-models/internal/test_helpers"
)

type StableSwapTestSuite struct {
test_helpers.CfmmCommonTestSuite
}

// Replace with https://github.com/cosmos/cosmos-sdk/blob/master/types/decimal.go#L892-L895
// once our SDK branch is up to date with it
func decApproxEq(t *testing.T, exp sdk.Dec, actual sdk.Dec, errTolerance sdk.Dec) {
Expand Down Expand Up @@ -45,7 +51,7 @@ func TestCFMMInvariantTwoAssets(t *testing.T) {
// using two-asset cfmm
k0 := cfmmConstant(test.xReserve, test.yReserve)
xOut := solveCfmm(test.xReserve, test.yReserve, test.yIn)
fmt.Println("xOut", xOut)

k1 := cfmmConstant(test.xReserve.Sub(xOut), test.yReserve.Add(test.yIn))
decApproxEq(t, k0, k1, kErrTolerance)

Expand Down Expand Up @@ -100,3 +106,104 @@ func TestCFMMInvariantMultiAssets(t *testing.T) {
decApproxEq(t, k2, k3, kErrTolerance)
}
}

func (suite *StableSwapTestSuite) Test_StableSwap_CalculateAmountOutAndIn_InverseRelationship(t *testing.T) {
mattverse marked this conversation as resolved.
Show resolved Hide resolved
type testcase struct {
denomOut string
initialPoolOut int64
initialWeightOut int64
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
initialCalcOut int64

denomIn string
initialPoolIn int64
initialWeightIn int64
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
}

// For every test case in testcases, apply a swap fee in swapFeeCases.
testcases := []testcase{
{
denomOut: "uosmo",
p0mvn marked this conversation as resolved.
Show resolved Hide resolved
initialPoolOut: 1_000_000_000_000,
initialWeightOut: 100,
initialCalcOut: 100,

denomIn: "ion",
initialPoolIn: 1_000_000_000_000,
initialWeightIn: 100,
},
{
denomOut: "uosmo",
initialPoolOut: 1_000,
initialWeightOut: 100,
initialCalcOut: 100,

denomIn: "ion",
initialPoolIn: 1_000_000,
initialWeightIn: 100,
},
{
denomOut: "uosmo",
initialPoolOut: 1_000,
initialWeightOut: 100,
initialCalcOut: 100,

denomIn: "ion",
initialPoolIn: 1_000_000,
initialWeightIn: 100,
},
{
denomOut: "uosmo",
initialPoolOut: 1_000,
initialWeightOut: 200,
initialCalcOut: 100,

denomIn: "ion",
initialPoolIn: 1_000_000,
initialWeightIn: 50,
},
{
denomOut: "uosmo",
initialPoolOut: 1_000_000,
initialWeightOut: 200,
initialCalcOut: 100000,

denomIn: "ion",
initialPoolIn: 1_000_000_000,
initialWeightIn: 50,
},
}

swapFeeCases := []string{"0", "0.001", "0.1", "0.5", "0.99"}

getTestCaseName := func(tc testcase, swapFeeCase string) string {
return fmt.Sprintf("tokenOutInitial: %d, tokenInInitial: %d, initialOut: %d, swapFee: %s",
tc.initialPoolOut,
tc.initialPoolIn,
tc.initialCalcOut,
swapFeeCase,
)
}

for _, tc := range testcases {
for _, swapFee := range swapFeeCases {
t.Run(getTestCaseName(tc, swapFee), func(t *testing.T) {
ctx := suite.CreateTestContext()

poolLiquidityIn := sdk.NewInt64Coin(tc.denomOut, tc.initialPoolOut)
poolLiquidityOut := sdk.NewInt64Coin(tc.denomIn, tc.initialPoolIn)
poolLiquidity := sdk.NewCoins(poolLiquidityIn, poolLiquidityOut)

swapFeeDec, err := sdk.NewDecFromStr(swapFee)
require.NoError(t, err)

exitFeeDec, err := sdk.NewDecFromStr("0")
require.NoError(t, err)

pool := createTestPool(t, poolLiquidity, swapFeeDec, exitFeeDec)
require.NotNil(t, pool)

suite.TestCalculateAmountOutAndIn_InverseRelationship(ctx, pool, poolLiquidityIn.Denom, poolLiquidityOut.Denom, tc.initialCalcOut, swapFeeDec)
})
}
}
}
2 changes: 1 addition & 1 deletion x/gamm/pool-models/stableswap/msgs.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (msg MsgCreateStableswapPool) InitialLiquidity() sdk.Coins {
}

func (msg MsgCreateStableswapPool) CreatePool(ctx sdk.Context, poolId uint64) (types.PoolI, error) {
stableswapPool, err := NewStableswapPool(poolId, *msg.PoolParams, msg.InitialPoolLiquidity, msg.FuturePoolGovernor, ctx.BlockTime())
stableswapPool, err := NewStableswapPool(poolId, *msg.PoolParams, msg.InitialPoolLiquidity, msg.FuturePoolGovernor)
if err != nil {
return nil, err
}
Expand Down
4 changes: 1 addition & 3 deletions x/gamm/pool-models/stableswap/stableswap_pool.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package stableswap

import (
"time"

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

"github.com/osmosis-labs/osmosis/v7/x/gamm/types"
Expand All @@ -15,7 +13,7 @@ var _ types.PoolI = &Pool{}
// * len(initialLiquidity) = 2
// * FutureGovernor is valid
// * poolID doesn't already exist
func NewStableswapPool(poolId uint64, stableswapPoolParams PoolParams, initialLiquidity sdk.Coins, futureGovernor string, blockTime time.Time) (Pool, error) {
func NewStableswapPool(poolId uint64, stableswapPoolParams PoolParams, initialLiquidity sdk.Coins, futureGovernor string) (Pool, error) {
pool := Pool{
Address: types.NewPoolAddress(poolId).String(),
Id: poolId,
Expand Down
21 changes: 21 additions & 0 deletions x/gamm/pool-models/stableswap/util_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package stableswap

import (
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"

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

func createTestPool(t *testing.T, poolLiquidity sdk.Coins, swapFee, exitFee sdk.Dec) types.PoolI {
pool, err := NewStableswapPool(1, PoolParams{
SwapFee: swapFee,
ExitFee: exitFee,
}, poolLiquidity, "")

require.NoError(t, err)

return &pool
}