Skip to content

Commit

Permalink
refactor: consolidate pool implementation (backport #1868) (#2111)
Browse files Browse the repository at this point in the history
* refactor: consolidate pool implementation (#1868)

(cherry picked from commit e2d4911)

# Conflicts:
#	x/gamm/pool-models/balancer/amm.go
#	x/gamm/pool-models/balancer/amm_test.go
#	x/gamm/pool-models/balancer/balancer_pool.go
#	x/gamm/pool-models/balancer/pool_suite_test.go
#	x/gamm/pool-models/balancer/suite_test.go

* Add import fixes

* Fix most merge conflicts (one remaining)

* Fix remaining merge conflict

Co-authored-by: Aleksandr Bezobchuk <[email protected]>
Co-authored-by: Dev Ojha <[email protected]>
  • Loading branch information
3 people authored Jul 18, 2022
1 parent daa57c2 commit 286b594
Show file tree
Hide file tree
Showing 11 changed files with 2,545 additions and 2,503 deletions.
556 changes: 70 additions & 486 deletions x/gamm/pool-models/balancer/amm.go

Large diffs are not rendered by default.

352 changes: 35 additions & 317 deletions x/gamm/pool-models/balancer/amm_test.go
Original file line number Diff line number Diff line change
@@ -1,334 +1,52 @@
package balancer_test

import (
"fmt"
"testing"

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/x/gamm/pool-models/balancer"
"github.com/osmosis-labs/osmosis/v10/x/gamm/types"
)

// This test sets up 2 asset pools, and then checks the spot price on them.
// It uses the pools spot price method, rather than the Gamm keepers spot price method.
func (suite *KeeperTestSuite) TestBalancerSpotPrice() {
baseDenom := "uosmo"
quoteDenom := "uion"

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
// NewPool panics in the error case.
tests := []struct {
name string
baseDenomPoolInput sdk.Coin
quoteDenomPoolInput sdk.Coin
expectError bool
expectedOutput sdk.Dec
SwapFee sdk.Dec
ExitFee sdk.Dec
shouldErr bool
}{
{
name: "equal value",
baseDenomPoolInput: sdk.NewInt64Coin(baseDenom, 100),
quoteDenomPoolInput: sdk.NewInt64Coin(quoteDenom, 100),
expectError: false,
expectedOutput: sdk.MustNewDecFromStr("1"),
},
{
name: "1:2 ratio",
baseDenomPoolInput: sdk.NewInt64Coin(baseDenom, 100),
quoteDenomPoolInput: sdk.NewInt64Coin(quoteDenom, 200),
expectError: false,
expectedOutput: sdk.MustNewDecFromStr("0.500000000000000000"),
},
{
name: "2:1 ratio",
baseDenomPoolInput: sdk.NewInt64Coin(baseDenom, 200),
quoteDenomPoolInput: sdk.NewInt64Coin(quoteDenom, 100),
expectError: false,
expectedOutput: sdk.MustNewDecFromStr("2.000000000000000000"),
},
{
name: "rounding after sigfig ratio",
baseDenomPoolInput: sdk.NewInt64Coin(baseDenom, 220),
quoteDenomPoolInput: sdk.NewInt64Coin(quoteDenom, 115),
expectError: false,
expectedOutput: sdk.MustNewDecFromStr("1.913043480000000000"), // ans is 1.913043478260869565, rounded is 1.91304348
},
{
name: "check number of sig figs",
baseDenomPoolInput: sdk.NewInt64Coin(baseDenom, 100),
quoteDenomPoolInput: sdk.NewInt64Coin(quoteDenom, 300),
expectError: false,
expectedOutput: sdk.MustNewDecFromStr("0.333333330000000000"),
},
{
name: "check number of sig figs high sizes",
baseDenomPoolInput: sdk.NewInt64Coin(baseDenom, 343569192534),
quoteDenomPoolInput: sdk.NewCoin(quoteDenom, sdk.MustNewDecFromStr("186633424395479094888742").TruncateInt()),
expectError: false,
expectedOutput: sdk.MustNewDecFromStr("0.000000000001840877"),
},
}

for _, tc := range tests {
suite.SetupTest()

poolId := suite.PrepareUni2PoolWithAssets(
tc.baseDenomPoolInput,
tc.quoteDenomPoolInput,
)

pool, err := suite.App.GAMMKeeper.GetPoolAndPoke(suite.Ctx, poolId)
suite.Require().NoError(err, "test: %s", tc.name)
balancerPool, isPool := pool.(*balancer.Pool)
suite.Require().True(isPool, "test: %s", tc.name)

spotPrice, err := balancerPool.SpotPrice(
suite.Ctx,
tc.baseDenomPoolInput.Denom,
tc.quoteDenomPoolInput.Denom)

if tc.expectError {
suite.Require().Error(err, "test: %s", tc.name)
} else {
suite.Require().NoError(err, "test: %s", tc.name)
suite.Require().True(spotPrice.Equal(tc.expectedOutput),
"test: %s\nSpot price wrong, got %s, expected %s\n", tc.name,
spotPrice, tc.expectedOutput)
// Should work
{defaultSwapFee, defaultExitFee, noErr},
// Can't set the swap fee as negative
{sdk.NewDecWithPrec(-1, 2), defaultExitFee, wantErr},
// Can't set the swap fee as 1
{sdk.NewDec(1), defaultExitFee, wantErr},
// Can't set the swap fee above 1
{sdk.NewDecWithPrec(15, 1), defaultExitFee, wantErr},
// Can't set the exit fee as negative
{defaultSwapFee, sdk.NewDecWithPrec(-1, 2), wantErr},
// Can't set the exit fee as 1
{defaultSwapFee, sdk.NewDec(1), wantErr},
// Can't set the exit fee above 1
{defaultSwapFee, sdk.NewDecWithPrec(15, 1), wantErr},
}

for i, params := range tests {
PoolParams := balancer.PoolParams{
SwapFee: params.SwapFee,
ExitFee: params.ExitFee,
}
}
}

// TestCalculateAmountOutAndIn_InverseRelationship tests that the same amount of token is guaranteed upon
// sequential operation of CalcInAmtGivenOut and CalcOutAmtGivenIn.
func TestCalculateAmountOutAndIn_InverseRelationship(t *testing.T) {
type testcase struct {
denomOut string
initialPoolOut int64
initialWeightOut int64
initialCalcOut int64

denomIn string
initialPoolIn int64
initialWeightIn int64
}

// For every test case in testcases, apply a swap fee in swapFeeCases.
testcases := []testcase{
{
denomOut: "uosmo",
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 := createTestContext(t)

poolAssetOut := balancer.PoolAsset{
Token: sdk.NewInt64Coin(tc.denomOut, tc.initialPoolOut),
Weight: sdk.NewInt(tc.initialWeightOut),
}

poolAssetIn := balancer.PoolAsset{
Token: sdk.NewInt64Coin(tc.denomIn, tc.initialPoolIn),
Weight: sdk.NewInt(tc.initialWeightIn),
}

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

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

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

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

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()

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

func TestCalcSingleAssetInAndOut_InverseRelationship(t *testing.T) {
type testcase struct {
initialPoolOut int64
initialPoolIn int64
initialWeightOut int64
tokenOut int64
initialWeightIn int64
}

// For every test case in testcases, apply a swap fee in swapFeeCases.
testcases := []testcase{
{
initialPoolOut: 1_000_000_000_000,
tokenOut: 100,
initialWeightOut: 100,
initialWeightIn: 100,
},
{
initialPoolOut: 1_000_000_000_000,
tokenOut: 100,
initialWeightOut: 50,
initialWeightIn: 100,
},
{
initialPoolOut: 1_000_000_000_000,
tokenOut: 50,
initialWeightOut: 100,
initialWeightIn: 100,
},
{
initialPoolOut: 1_000_000_000_000,
tokenOut: 100,
initialWeightOut: 100,
initialWeightIn: 50,
},
{
initialPoolOut: 1_000_000,
tokenOut: 100,
initialWeightOut: 100,
initialWeightIn: 100,
},
{
initialPoolOut: 2_351_333,
tokenOut: 7,
initialWeightOut: 148,
initialWeightIn: 57,
},
{
initialPoolOut: 1_000,
tokenOut: 25,
initialWeightOut: 100,
initialWeightIn: 100,
},
{
initialPoolOut: 1_000,
tokenOut: 26,
initialWeightOut: 100,
initialWeightIn: 100,
},
}

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

getTestCaseName := func(tc testcase, swapFeeCase string) string {
return fmt.Sprintf("initialPoolOut: %d, initialCalcOut: %d, initialWeightOut: %d, initialWeightIn: %d, swapFee: %s",
tc.initialPoolOut,
tc.tokenOut,
tc.initialWeightOut,
tc.initialWeightIn,
swapFeeCase,
)
}

for _, tc := range testcases {
for _, swapFee := range swapFeeCases {
t.Run(getTestCaseName(tc, swapFee), func(t *testing.T) {
swapFeeDec, err := sdk.NewDecFromStr(swapFee)
require.NoError(t, err)

initialPoolBalanceOut := sdk.NewInt(tc.initialPoolOut)

initialWeightOut := sdk.NewInt(tc.initialWeightOut)
initialWeightIn := sdk.NewInt(tc.initialWeightIn)

initialTotalShares := types.InitPoolSharesSupply.ToDec()
initialCalcTokenOut := sdk.NewInt(tc.tokenOut)

actualSharesOut := balancer.CalcPoolSharesOutGivenSingleAssetIn(
initialPoolBalanceOut.ToDec(),
initialWeightOut.ToDec().Quo(initialWeightOut.Add(initialWeightIn).ToDec()),
initialTotalShares,
initialCalcTokenOut.ToDec(),
swapFeeDec,
)

inverseCalcTokenOut := balancer.CalcSingleAssetInGivenPoolSharesOut(
initialPoolBalanceOut.Add(initialCalcTokenOut).ToDec(),
initialWeightOut.ToDec().Quo(initialWeightOut.Add(initialWeightIn).ToDec()),
initialTotalShares.Add(actualSharesOut),
actualSharesOut,
swapFeeDec,
)

tol := sdk.NewDec(1)
require.True(osmoutils.DecApproxEq(t, initialCalcTokenOut.ToDec(), inverseCalcTokenOut, tol))
})
err := PoolParams.Validate(dummyPoolAssets)
if params.shouldErr {
require.Error(t, err, "unexpected lack of error, tc %v", i)
// Check that these are also caught if passed to the underlying pool creation func
_, err = balancer.NewBalancerPool(1, PoolParams, dummyPoolAssets, defaultFutureGovernor, defaultCurBlockTime)
require.Error(t, err)
} else {
require.NoError(t, err, "unexpected error, tc %v", i)
}
}
}
Loading

0 comments on commit 286b594

Please sign in to comment.