diff --git a/CHANGELOG.md b/CHANGELOG.md index e4b959f4dff..5cdf4fa3433 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## Misc Improvements * [#4131](https://github.com/osmosis-labs/osmosis/pull/4141) Add GatherValuesFromStorePrefixWithKeyParser function to osmoutils. * [#4388](https://github.com/osmosis-labs/osmosis/pull/4388) Increase the max allowed contract size for non-proposal contracts to 3MB + * [#4384](https://github.com/osmosis-labs/osmosis/pull/4384) migrate stXXX/XXX constant product pools 833, 817, 810 to stable swap ### API breaks diff --git a/app/upgrades/v15/constants.go b/app/upgrades/v15/constants.go index 4c180a09714..4380dfb74db 100644 --- a/app/upgrades/v15/constants.go +++ b/app/upgrades/v15/constants.go @@ -15,6 +15,11 @@ import ( // UpgradeName defines the on-chain upgrade name for the Osmosis v15 upgrade. const UpgradeName = "v15" +// pool ids to migrate +const stOSMO_OSMOPoolId = 833 +const stJUNO_JUNOPoolId = 817 +const stSTARS_STARSPoolId = 810 + var Upgrade = upgrades.Upgrade{ UpgradeName: UpgradeName, CreateUpgradeHandler: CreateUpgradeHandler, diff --git a/app/upgrades/v15/export_test.go b/app/upgrades/v15/export_test.go index a5ae35d11b0..ddd4f8063cb 100644 --- a/app/upgrades/v15/export_test.go +++ b/app/upgrades/v15/export_test.go @@ -4,12 +4,14 @@ import ( wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" sdk "github.com/cosmos/cosmos-sdk/types" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - ibcratelimit "github.com/osmosis-labs/osmosis/v14/x/ibc-rate-limit" icqkeeper "github.com/strangelove-ventures/async-icq/v4/keeper" + ibcratelimit "github.com/osmosis-labs/osmosis/v14/x/ibc-rate-limit" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" gammkeeper "github.com/osmosis-labs/osmosis/v14/x/gamm/keeper" + "github.com/osmosis-labs/osmosis/v14/x/poolmanager" poolmanagerkeeper "github.com/osmosis-labs/osmosis/v14/x/poolmanager" ) @@ -25,6 +27,10 @@ func SetICQParams(ctx sdk.Context, icqKeeper *icqkeeper.Keeper) { setICQParams(ctx, icqKeeper) } +func MigrateBalancerPoolToSolidlyStable(ctx sdk.Context, gammKeeper *gammkeeper.Keeper, poolmanagerKeeper *poolmanager.Keeper, bankKeeper bankkeeper.Keeper, poolId uint64) { + migrateBalancerPoolToSolidlyStable(ctx, gammKeeper, poolmanagerKeeper, bankKeeper, poolId) +} + func SetRateLimits(ctx sdk.Context, accountKeeper *authkeeper.AccountKeeper, rateLimitingICS4Wrapper *ibcratelimit.ICS4Wrapper, wasmKeeper *wasmkeeper.Keeper) { setRateLimits(ctx, accountKeeper, rateLimitingICS4Wrapper, wasmKeeper) } diff --git a/app/upgrades/v15/upgrade_test.go b/app/upgrades/v15/upgrade_test.go index a4a1012e764..836140ef8f0 100644 --- a/app/upgrades/v15/upgrade_test.go +++ b/app/upgrades/v15/upgrade_test.go @@ -12,20 +12,30 @@ import ( govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" + "github.com/osmosis-labs/osmosis/osmoutils/osmoassert" ibcratelimittypes "github.com/osmosis-labs/osmosis/v14/x/ibc-rate-limit/types" - gamm "github.com/osmosis-labs/osmosis/v14/x/gamm/keeper" - "github.com/stretchr/testify/suite" "github.com/osmosis-labs/osmosis/v14/app/apptesting" v15 "github.com/osmosis-labs/osmosis/v14/app/upgrades/v15" + gamm "github.com/osmosis-labs/osmosis/v14/x/gamm/keeper" + balancer "github.com/osmosis-labs/osmosis/v14/x/gamm/pool-models/balancer" + balancertypes "github.com/osmosis-labs/osmosis/v14/x/gamm/pool-models/balancer" + poolmanagertypes "github.com/osmosis-labs/osmosis/v14/x/poolmanager/types" ) type UpgradeTestSuite struct { apptesting.KeeperTestHelper } +var DefaultAcctFunds sdk.Coins = sdk.NewCoins( + sdk.NewCoin("uosmo", sdk.NewInt(10000000000)), + sdk.NewCoin("foo", sdk.NewInt(10000000)), + sdk.NewCoin("bar", sdk.NewInt(10000000)), + sdk.NewCoin("baz", sdk.NewInt(10000000)), +) + func (suite *UpgradeTestSuite) SetupTest() { suite.Setup() } @@ -77,6 +87,84 @@ func (suite *UpgradeTestSuite) TestMigrateNextPoolIdAndCreatePool() { suite.Require().Equal(gammPoolCreationFee, poolmanagerPoolCreationFee) } +func (suite *UpgradeTestSuite) TestMigrateBalancerToStablePools() { + suite.SetupTest() // reset + + ctx := suite.Ctx + gammKeeper := suite.App.GAMMKeeper + poolmanagerKeeper := suite.App.PoolManagerKeeper + // bankKeeper := suite.App.BankKeeper + testAccount := suite.TestAccs[0] + + // Mint some assets to the accounts. + suite.FundAcc(testAccount, DefaultAcctFunds) + + // Create the balancer pool + swapFee := sdk.MustNewDecFromStr("0.003") + exitFee := sdk.MustNewDecFromStr("0.025") + poolID, err := suite.App.PoolManagerKeeper.CreatePool( + suite.Ctx, + balancer.NewMsgCreateBalancerPool(suite.TestAccs[0], + balancer.PoolParams{ + SwapFee: swapFee, + ExitFee: exitFee, + }, + []balancertypes.PoolAsset{ + { + Weight: sdk.NewInt(100), + Token: sdk.NewCoin("foo", sdk.NewInt(5000000)), + }, + { + Weight: sdk.NewInt(200), + Token: sdk.NewCoin("bar", sdk.NewInt(5000000)), + }, + }, + ""), + ) + suite.Require().NoError(err) + + // join the pool + shareOutAmount := sdk.NewInt(1_000_000_000_000_000) + tokenInMaxs := sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(5000000)), sdk.NewCoin("bar", sdk.NewInt(5000000))) + tokenIn, sharesOut, err := suite.App.GAMMKeeper.JoinPoolNoSwap(suite.Ctx, testAccount, poolID, shareOutAmount, tokenInMaxs) + suite.Require().NoError(err) + + // shares before migration + balancerPool, err := gammKeeper.GetPool(suite.Ctx, poolID) + suite.Require().NoError(err) + balancerShares := balancerPool.GetTotalShares() + balancerLiquidity := balancerPool.GetTotalPoolLiquidity(ctx).String() + // check balancer pool liquidity using the bank module + balancerBalances := suite.App.BankKeeper.GetAllBalances(ctx, balancerPool.GetAddress()) + + // test migrating the balancer pool to a stable pool + v15.MigrateBalancerPoolToSolidlyStable(ctx, gammKeeper, poolmanagerKeeper, suite.App.BankKeeper, poolID) + + // check that the pool is now a stable pool + stablepool, err := gammKeeper.GetPool(ctx, poolID) + suite.Require().NoError(err) + suite.Require().Equal(stablepool.GetType(), poolmanagertypes.Stableswap) + // check that the number of stableswap LP shares is the same as the number of balancer LP shares + suite.Require().Equal(balancerShares.String(), stablepool.GetTotalShares().String()) + // check that the pool liquidity is the same + suite.Require().Equal(balancerLiquidity, stablepool.GetTotalPoolLiquidity(ctx).String()) + // check pool liquidity using the bank module + stableBalances := suite.App.BankKeeper.GetAllBalances(ctx, stablepool.GetAddress()) + suite.Require().Equal(balancerBalances, stableBalances) + + // exit the pool + exitCoins, err := suite.App.GAMMKeeper.ExitPool(suite.Ctx, testAccount, poolID, sharesOut, sdk.NewCoins()) + suite.Require().NoError(err) + + suite.validateCons(exitCoins, tokenIn) + + // join again + tokenInStable, _, err := suite.App.GAMMKeeper.JoinPoolNoSwap(suite.Ctx, testAccount, poolID, shareOutAmount, tokenInMaxs) + suite.Require().NoError(err) + + suite.validateCons(tokenInStable, tokenIn) +} + func (suite *UpgradeTestSuite) TestRegisterOsmoIonMetadata() { suite.SetupTest() // reset @@ -159,3 +247,12 @@ func (suite *UpgradeTestSuite) TestSetRateLimits() { suite.Require().Greaterf(len(state), 0, "state should not be empty") } + +func (suite *UpgradeTestSuite) validateCons(coinsA, coinsB sdk.Coins) { + suite.Require().Equal(len(coinsA), len(coinsB)) + for _, coinA := range coinsA { + coinBAmount := coinsB.AmountOf(coinA.Denom) + // minor tolerance due to fees and rounding + osmoassert.DecApproxEq(suite.T(), coinBAmount.ToDec(), coinA.Amount.ToDec(), sdk.NewDec(2)) + } +} diff --git a/app/upgrades/v15/upgrades.go b/app/upgrades/v15/upgrades.go index 3cb1419eb51..c9ca461b2f6 100644 --- a/app/upgrades/v15/upgrades.go +++ b/app/upgrades/v15/upgrades.go @@ -25,6 +25,8 @@ import ( appParams "github.com/osmosis-labs/osmosis/v14/app/params" "github.com/osmosis-labs/osmosis/v14/app/upgrades" gammkeeper "github.com/osmosis-labs/osmosis/v14/x/gamm/keeper" + "github.com/osmosis-labs/osmosis/v14/x/gamm/pool-models/stableswap" + gammtypes "github.com/osmosis-labs/osmosis/v14/x/gamm/types" "github.com/osmosis-labs/osmosis/v14/x/poolmanager" ) @@ -59,6 +61,10 @@ func CreateUpgradeHandler( // They are added in this upgrade. registerOsmoIonMetadata(ctx, keepers.BankKeeper) + // Stride stXXX/XXX pools are being migrated from the standard balancer curve to the + // solidly stable curve. + migrateBalancerPoolsToSolidlyStable(ctx, keepers.GAMMKeeper, keepers.PoolManagerKeeper, keepers.BankKeeper) + setRateLimits(ctx, keepers.AccountKeeper, keepers.RateLimitingICS4Wrapper, keepers.WasmKeeper) return mm.RunMigrations(ctx, configurator, fromVM) @@ -73,6 +79,53 @@ func setICQParams(ctx sdk.Context, icqKeeper *icqkeeper.Keeper) { icqKeeper.SetParams(ctx, icqparams) } +func migrateBalancerPoolsToSolidlyStable(ctx sdk.Context, gammKeeper *gammkeeper.Keeper, poolmanagerKeeper *poolmanager.Keeper, bankKeeper bankkeeper.Keeper) { + // migrate stOSMO_OSMOPoolId, stJUNO_JUNOPoolId, stSTARS_STARSPoolId + pools := []uint64{stOSMO_OSMOPoolId, stJUNO_JUNOPoolId, stSTARS_STARSPoolId} + for _, poolId := range pools { + migrateBalancerPoolToSolidlyStable(ctx, gammKeeper, poolmanagerKeeper, bankKeeper, poolId) + } +} + +func migrateBalancerPoolToSolidlyStable(ctx sdk.Context, gammKeeper *gammkeeper.Keeper, poolmanagerKeeper *poolmanager.Keeper, bankKeeper bankkeeper.Keeper, poolId uint64) { + // fetch the pool with the given poolId + balancerPool, err := gammKeeper.GetPool(ctx, poolId) + if err != nil { + panic(err) + } + + // initialize the stableswap pool + stableswapPool, err := stableswap.NewStableswapPool( + poolId, + stableswap.PoolParams{SwapFee: balancerPool.GetSwapFee(ctx), ExitFee: balancerPool.GetExitFee(ctx)}, + balancerPool.GetTotalPoolLiquidity(ctx), + []uint64{1, 1}, + "osmo1k8c2m5cn322akk5wy8lpt87dd2f4yh9afcd7af", // Stride Foundation 2/3 multisig + "", + ) + if err != nil { + panic(err) + } + + // ensure the number of stableswap LP shares is the same as the number of balancer LP shares + totalShares := sdk.NewCoin( + gammtypes.GetPoolShareDenom(poolId), + balancerPool.GetTotalShares(), + ) + stableswapPool.TotalShares = totalShares + + balancesBefore := bankKeeper.GetAllBalances(ctx, balancerPool.GetAddress()) + // overwrite the balancer pool with the new stableswap pool + err = gammKeeper.OverwritePoolV15MigrationUnsafe(ctx, &stableswapPool) + if err != nil { + panic(err) + } + balancesAfter := bankKeeper.GetAllBalances(ctx, stableswapPool.GetAddress()) + if !balancesBefore.IsEqual(balancesAfter) { + panic("balances before and after migration are not equal") + } +} + func setRateLimits(ctx sdk.Context, accountKeeper *authkeeper.AccountKeeper, rateLimitingICS4Wrapper *ibcratelimit.ICS4Wrapper, wasmKeeper *wasmkeeper.Keeper) { govModule := accountKeeper.GetModuleAddress(govtypes.ModuleName) contractKeeper := wasmkeeper.NewGovPermissionKeeper(wasmKeeper) diff --git a/tests/e2e/configurer/chain/commands.go b/tests/e2e/configurer/chain/commands.go index 62c3d1d3080..187a453441c 100644 --- a/tests/e2e/configurer/chain/commands.go +++ b/tests/e2e/configurer/chain/commands.go @@ -196,6 +196,22 @@ func (n *NodeConfig) SwapExactAmountIn(tokenInCoin, tokenOutMinAmountInt string, n.LogActionF("successfully swapped") } +func (n *NodeConfig) JoinPoolExactAmountIn(tokenIn string, poolId uint64, shareOutMinAmount string, from string) { + n.LogActionF("join-swap-extern-amount-in (%s) (%s) from (%s), pool id (%d)", tokenIn, shareOutMinAmount, from, poolId) + cmd := []string{"osmosisd", "tx", "gamm", "join-swap-extern-amount-in", tokenIn, shareOutMinAmount, fmt.Sprintf("--pool-id=%d", poolId), fmt.Sprintf("--from=%s", from)} + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully joined pool") +} + +func (n *NodeConfig) ExitPool(from, minAmountsOut string, poolId uint64, shareAmountIn string) { + n.LogActionF("exiting gamm pool") + cmd := []string{"osmosisd", "tx", "gamm", "exit-pool", fmt.Sprintf("--min-amounts-out=%s", minAmountsOut), fmt.Sprintf("--share-amount-in=%s", shareAmountIn), fmt.Sprintf("--pool-id=%d", poolId), fmt.Sprintf("--from=%s", from)} + _, _, err := n.containerManager.ExecTxCmd(n.t, n.chainId, n.Name, cmd) + require.NoError(n.t, err) + n.LogActionF("successfully exited pool %d, minAmountsOut %s, shareAmountIn %s", poolId, minAmountsOut, shareAmountIn) +} + func (n *NodeConfig) SubmitUpgradeProposal(upgradeVersion string, upgradeHeight int64, initialDeposit sdk.Coin) { n.LogActionF("submitting upgrade proposal %s for height %d", upgradeVersion, upgradeHeight) cmd := []string{"osmosisd", "tx", "gov", "submit-proposal", "software-upgrade", upgradeVersion, fmt.Sprintf("--title=\"%s upgrade\"", upgradeVersion), "--description=\"upgrade proposal submission\"", fmt.Sprintf("--upgrade-height=%d", upgradeHeight), "--upgrade-info=\"\"", "--from=val", fmt.Sprintf("--deposit=%s", initialDeposit)} diff --git a/tests/e2e/configurer/chain/queries.go b/tests/e2e/configurer/chain/queries.go index 1483019f17c..434097ec92f 100644 --- a/tests/e2e/configurer/chain/queries.go +++ b/tests/e2e/configurer/chain/queries.go @@ -255,6 +255,18 @@ func (n *NodeConfig) QueryNumPools() uint64 { return numPools.NumPools } +func (n *NodeConfig) QueryPoolType(poolId string) string { + path := fmt.Sprintf("/osmosis/gamm/v1beta1/pool_type/%s", poolId) + bz, err := n.QueryGRPCGateway(path) + require.NoError(n.t, err) + + var poolTypeResponse gammtypes.QueryPoolTypeResponse + err = util.Cdc.UnmarshalJSON(bz, &poolTypeResponse) + require.NoError(n.t, err) + + return poolTypeResponse.PoolType +} + func (n *NodeConfig) QueryConcentratedPositions(address string) []cltypes.FullPositionByOwnerResult { path := fmt.Sprintf("/osmosis/concentratedliquidity/v1beta1/positions/%s", address) diff --git a/tests/e2e/configurer/config/constants.go b/tests/e2e/configurer/config/constants.go index cefee9aa4a3..b37e1c2be7e 100644 --- a/tests/e2e/configurer/config/constants.go +++ b/tests/e2e/configurer/config/constants.go @@ -35,4 +35,6 @@ var ( // creation in case more pools are added to genesis // in the future PreUpgradePoolId uint64 = 2 + + StrideMigrateWallet = "stride-migration" ) diff --git a/tests/e2e/configurer/upgrade.go b/tests/e2e/configurer/upgrade.go index 428bdf23162..d6f223c39c3 100644 --- a/tests/e2e/configurer/upgrade.go +++ b/tests/e2e/configurer/upgrade.go @@ -151,6 +151,26 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { // test lock and add to existing lock for both regular and superfluid lockups (only chainA) chainA.LockAndAddToExistingLock(sdk.NewInt(1000000000000000000), poolShareDenom, lockupWalletAddrA, lockupWalletSuperfluidAddrA) + // LP to pools 833, 817, 810 + // initialize lp wallets + amountOfEachTokenToLP := initialization.DefaultStrideDenomBalance / 1_000_000 + shareOutMin := "1" + + config.StrideMigrateWallet = chainANode.CreateWalletAndFund(config.StrideMigrateWallet, []string{ + fmt.Sprintf("%d%s", amountOfEachTokenToLP, initialization.StOsmoDenom), + fmt.Sprintf("%d%s", amountOfEachTokenToLP, initialization.StJunoDenom), + fmt.Sprintf("%d%s", amountOfEachTokenToLP, initialization.StStarsDenom), + }) + + tokenInStOsmo := fmt.Sprintf("%d%s", amountOfEachTokenToLP, initialization.StOsmoDenom) + chainANode.JoinPoolExactAmountIn(tokenInStOsmo, initialization.StOSMO_OSMOPoolId, shareOutMin, config.StrideMigrateWallet) + + tokenInStJuno := fmt.Sprintf("%d%s", amountOfEachTokenToLP, initialization.StJunoDenom) + chainANode.JoinPoolExactAmountIn(tokenInStJuno, initialization.StJUNO_JUNOPoolId, shareOutMin, config.StrideMigrateWallet) + + tokenInStStars := fmt.Sprintf("%d%s", amountOfEachTokenToLP, initialization.StStarsDenom) + chainANode.JoinPoolExactAmountIn(tokenInStStars, initialization.StSTARS_STARSPoolId, shareOutMin, config.StrideMigrateWallet) + return nil } diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index a8a25d259ed..9ff1034e302 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -10,9 +10,10 @@ import ( "time" transfertypes "github.com/cosmos/ibc-go/v4/modules/apps/transfer/types" - "github.com/iancoleman/orderedmap" + poolmanagertypes "github.com/osmosis-labs/osmosis/v14/x/poolmanager/types" + "github.com/osmosis-labs/osmosis/v14/tests/e2e/configurer/chain" "github.com/osmosis-labs/osmosis/v14/tests/e2e/util" @@ -1095,11 +1096,15 @@ func (s *IntegrationTestSuite) TestGeometricTWAP() { // This test is to be re-enabled for upgrade once the upgrade handler logic is added and // the balancer pool genesis is backported to v14. func (s *IntegrationTestSuite) TestStridePoolMigration() { + if s.skipUpgrade { + s.T().Log("Skipping migration test when upgrade is disable. This test depends on running v15 upgrade handler.") + } + const ( // Configurations for tests/e2e/scripts/pool1A.json // This pool gets initialized pre-upgrade. - minAmountOut = "1" - migrationWallet = "stride-migration" + minAmountOut = "1" + shareAmountIn = "1" ) chainA := s.configurer.GetChainConfig(0) @@ -1107,13 +1112,24 @@ func (s *IntegrationTestSuite) TestStridePoolMigration() { s.Require().NoError(err) fundTokens := []string{fmt.Sprintf("1000000%s", initialization.StOsmoDenom), fmt.Sprintf("1000000%s", initialization.StJunoDenom), fmt.Sprintf("1000000%s", initialization.StStarsDenom)} + for _, token := range fundTokens { + node.BankSend(token, initialization.ValidatorWalletName, config.StrideMigrateWallet) + } + otherDenoms := []string{initialization.OsmoDenom, initialization.JunoDenom, initialization.StarsDenom} - swapWalletAddr := node.CreateWalletAndFund(migrationWallet, fundTokens) migrationPools := []uint64{initialization.StOSMO_OSMOPoolId, initialization.StJUNO_JUNOPoolId, initialization.StSTARS_STARSPoolId} for i, poolId := range migrationPools { - // Swap to make sure that - node.SwapExactAmountIn(fundTokens[i], minAmountOut, fmt.Sprintf("%d", poolId), otherDenoms[i], swapWalletAddr) + // Query and assert to make sure that pool type is stableswap + poolType := node.QueryPoolType(fmt.Sprintf("%d", poolId)) + stableswapType := poolmanagertypes.Stableswap.String() + s.Require().Equal(poolType, stableswapType, "Pool type should be stableswap after upgrade") + + // Swap to make sure that migrations did not break anything critical. + node.SwapExactAmountIn(fundTokens[i], minAmountOut, fmt.Sprintf("%d", poolId), otherDenoms[i], config.StrideMigrateWallet) + + // Exit one share + node.ExitPool(config.StrideMigrateWallet, "", poolId, shareAmountIn) } } diff --git a/x/gamm/keeper/pool.go b/x/gamm/keeper/pool.go index 08791b45770..341d33973de 100644 --- a/x/gamm/keeper/pool.go +++ b/x/gamm/keeper/pool.go @@ -105,6 +105,12 @@ func (k Keeper) setPool(ctx sdk.Context, pool poolmanagertypes.PoolI) error { return nil } +// OverwritePoolV15MigrationUnsafe is a temporary method for calling from the v15 upgrade handler +// for balancer to stableswap pool migration. Do not use for other purposes. +func (k Keeper) OverwritePoolV15MigrationUnsafe(ctx sdk.Context, pool poolmanagertypes.PoolI) error { + return k.setPool(ctx, pool) +} + func (k Keeper) DeletePool(ctx sdk.Context, poolId uint64) error { store := ctx.KVStore(k.storeKey) poolKey := types.GetKeyPrefixPools(poolId)