-
Notifications
You must be signed in to change notification settings - Fork 609
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
1 parent
daa57c2
commit 286b594
Showing
11 changed files
with
2,545 additions
and
2,503 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |
Oops, something went wrong.