diff --git a/CHANGELOG.md b/CHANGELOG.md index cc208b8020e..d08a4bbcfc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * [#5923] (https://github.com/osmosis-labs/osmosis/pull/5923) CL: Lower gas for initializing ticks * [#5927] (https://github.com/osmosis-labs/osmosis/pull/5927) Add gas metering to x/tokenfactory trackBeforeSend hook * [#5890](https://github.com/osmosis-labs/osmosis/pull/5890) feat: CreateCLPool & LinkCFMMtoCL pool into one gov-prop +* [#5959](https://github.com/osmosis-labs/osmosis/pull/5959) allow testing with different chain-id's in E2E testing * [#5964](https://github.com/osmosis-labs/osmosis/pull/5964) fix e2e test concurrency bugs * [#5948] (https://github.com/osmosis-labs/osmosis/pull/5948) Parameterizing Pool Type Information in Protorev * [#6001](https://github.com/osmosis-labs/osmosis/pull/6001) feat: improve set-env CLI cmd diff --git a/app/upgrades/v17/constants.go b/app/upgrades/v17/constants.go index b1a8a292c01..d9c099a32ae 100644 --- a/app/upgrades/v17/constants.go +++ b/app/upgrades/v17/constants.go @@ -1,9 +1,16 @@ package v17 import ( + "errors" "fmt" + errorsmod "cosmossdk.io/errors" + "github.com/osmosis-labs/osmosis/v17/app/upgrades" + cltypes "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types" + gammtypes "github.com/osmosis-labs/osmosis/v17/x/gamm/types" + poolManagerTypes "github.com/osmosis-labs/osmosis/v17/x/poolmanager/types" + "github.com/osmosis-labs/osmosis/v17/x/superfluid/types" store "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -68,13 +75,13 @@ var AssetPairs = []AssetPair{ } // AssetPairs contract: all AssetPairs being initialized in this upgrade handler all have the same quote asset (OSMO). -func InitializeAssetPairs(ctx sdk.Context, keepers *keepers.AppKeepers) []AssetPair { +func InitializeAssetPairs(ctx sdk.Context, keepers *keepers.AppKeepers) ([]AssetPair, error) { gammKeeper := keepers.GAMMKeeper superfluidKeeper := keepers.SuperfluidKeeper for i, assetPair := range AssetPairs { pool, err := gammKeeper.GetCFMMPool(ctx, assetPair.LinkedClassicPool) if err != nil { - panic(err) + return nil, err } // Set the base asset as the non-osmo asset in the pool @@ -100,7 +107,7 @@ func InitializeAssetPairs(ctx sdk.Context, keepers *keepers.AppKeepers) []AssetP } AssetPairs[i].Superfluid = true } - return AssetPairs + return AssetPairs, nil } // The values below this comment are used strictly for testing. @@ -295,3 +302,112 @@ var AssetPairsForTestsOnly = []AssetPair{ Superfluid: false, }, } + +// InitializeAssetPairsTestnet initializes the asset pairs for the testnet, which is every osmo paired gamm pool with exactly 2 tokens. +func InitializeAssetPairsTestnet(ctx sdk.Context, keepers *keepers.AppKeepers) ([]AssetPair, error) { + superfluidKeeper := keepers.SuperfluidKeeper + testnetAssetPairs := []AssetPair{} + + // Retrieve all GAMM pools on the testnet. + pools, err := keepers.GAMMKeeper.GetPools(ctx) + if err != nil { + return nil, err + } + + for _, pool := range pools { + if pool.GetType() != poolManagerTypes.Balancer { + continue + } + + gammPoolId := pool.GetId() + + // Skip pools that are already linked. + clPoolId, err := keepers.GAMMKeeper.GetLinkedConcentratedPoolID(ctx, gammPoolId) + if err == nil && clPoolId != 0 { + ctx.Logger().Info(fmt.Sprintf("gammPoolId %d is already linked to CL pool %d, skipping", gammPoolId, clPoolId)) + continue + } + + cfmmPool, err := keepers.GAMMKeeper.GetCFMMPool(ctx, gammPoolId) + if err != nil { + return nil, err + } + + totalPoolLiquidity := cfmmPool.GetTotalPoolLiquidity(ctx) + + // Skip pools that are not paired with exactly 2 tokens. + if len(totalPoolLiquidity) != 2 { + continue + } + + // Skip pools that aren't paired with OSMO. OSMO will be the quote asset. + quoteAsset, baseAsset := "", "" + for _, coin := range totalPoolLiquidity { + if coin.Denom == QuoteAsset { + quoteAsset = coin.Denom + } else { + baseAsset = coin.Denom + } + } + if quoteAsset == "" || baseAsset == "" { + continue + } + + spreadFactor := cfmmPool.GetSpreadFactor(ctx) + err = validateSpotPriceFallsInBounds(ctx, cfmmPool, keepers, baseAsset, spreadFactor) + if errors.Is(err, gammtypes.ErrInvalidMathApprox) { + // Result is zero, which means 0.1 osmo was too much for the swap to handle. + // This is likely because the pool liquidity is too small, so since this just testnet, we skip it. + continue + } else if err != nil { + return nil, err + } + + // Set the spread factor to the same spread factor the GAMM pool was. + // If its spread factor is not authorized, set it to the first authorized non-zero spread factor. + authorizedSpreadFactors := keepers.ConcentratedLiquidityKeeper.GetParams(ctx).AuthorizedSpreadFactors + spreadFactorAuthorized := false + for _, authorizedSpreadFactor := range authorizedSpreadFactors { + if authorizedSpreadFactor.Equal(spreadFactor) { + spreadFactorAuthorized = true + break + } + } + if !spreadFactorAuthorized { + spreadFactor = authorizedSpreadFactors[1] + } + + isSuperfluid := false + poolShareDenom := fmt.Sprintf("gamm/pool/%d", gammPoolId) + _, err = superfluidKeeper.GetSuperfluidAsset(ctx, poolShareDenom) + if err != nil && !errors.Is(err, errorsmod.Wrapf(types.ErrNonSuperfluidAsset, "denom: %s", poolShareDenom)) { + return nil, err + } else if err == nil { + isSuperfluid = true + } + + internalAssetPair := AssetPair{ + BaseAsset: baseAsset, + SpreadFactor: spreadFactor, + LinkedClassicPool: gammPoolId, + Superfluid: isSuperfluid, + } + testnetAssetPairs = append(testnetAssetPairs, internalAssetPair) + } + return testnetAssetPairs, nil +} + +// validateSpotPriceFallsInBounds ensures that after swapping in the OSMO for the baseAsset, the resulting spot price is within the +// min and max spot price bounds of the concentrated liquidity module. +func validateSpotPriceFallsInBounds(ctx sdk.Context, cfmmPool gammtypes.CFMMPoolI, keepers *keepers.AppKeepers, baseAsset string, spreadFactor sdk.Dec) error { + // Check if swapping 0.1 OSMO results in a spot price less than the min or greater than the max + respectiveBaseAsset, err := keepers.GAMMKeeper.CalcOutAmtGivenIn(ctx, cfmmPool, sdk.NewCoin(QuoteAsset, sdk.NewInt(100000)), baseAsset, spreadFactor) + if err != nil { + return err + } + expectedSpotPriceFromSwap := sdk.NewDec(100000).Quo(respectiveBaseAsset.Amount.ToDec()) + if expectedSpotPriceFromSwap.LT(cltypes.MinSpotPrice) || expectedSpotPriceFromSwap.GT(cltypes.MaxSpotPrice) { + return err + } + return nil +} diff --git a/app/upgrades/v17/upgrades.go b/app/upgrades/v17/upgrades.go index cf3da1d437d..4ba71db2828 100644 --- a/app/upgrades/v17/upgrades.go +++ b/app/upgrades/v17/upgrades.go @@ -1,9 +1,13 @@ package v17 import ( + "errors" "fmt" "time" + ibchookstypes "github.com/osmosis-labs/osmosis/x/ibc-hooks/types" + + errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" upgradetypes "github.com/cosmos/cosmos-sdk/x/upgrade/types" @@ -11,6 +15,7 @@ import ( distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types" cltypes "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types" + gammtypes "github.com/osmosis-labs/osmosis/v17/x/gamm/types" gammmigration "github.com/osmosis-labs/osmosis/v17/x/gamm/types/migration" superfluidtypes "github.com/osmosis-labs/osmosis/v17/x/superfluid/types" @@ -21,6 +26,14 @@ import ( poolmanagertypes "github.com/osmosis-labs/osmosis/v17/x/poolmanager/types" ) +const ( + mainnetChainID = "osmosis-1" + e2eChainA = "osmo-test-a" + e2eChainB = "osmo-test-b" +) + +var notEnoughLiquidityForSwapErr = errorsmod.Wrapf(gammtypes.ErrInvalidMathApprox, "token amount must be positive") + func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, @@ -28,6 +41,8 @@ func CreateUpgradeHandler( keepers *keepers.AppKeepers, ) upgradetypes.UpgradeHandler { return func(ctx sdk.Context, plan upgradetypes.Plan, fromVM module.VersionMap) (module.VersionMap, error) { + var assetPairs []AssetPair + // Run migrations before applying any other state changes. // NOTE: DO NOT PUT ANY STATE CHANGES BEFORE RunMigrations(). migrations, err := mm.RunMigrations(ctx, configurator, fromVM) @@ -42,78 +57,54 @@ func CreateUpgradeHandler( } // migrate twap records for CL Pools - err = FlipTwapSpotPriceRecords(ctx, pools, keepers) + err = flipTwapSpotPriceRecords(ctx, pools, keepers) + if err != nil { + return nil, err + } + + // Set the asset pair list depending on the chain ID. + if ctx.ChainID() == mainnetChainID || ctx.ChainID() == e2eChainA || ctx.ChainID() == e2eChainB { + // Upgrades specific balancer pools to concentrated liquidity pools and links them to their CL equivalent. + ctx.Logger().Info(fmt.Sprintf("Chain ID is %s, running mainnet upgrade handler", ctx.ChainID())) + assetPairs, err = InitializeAssetPairs(ctx, keepers) + } else { + // Upgrades all existing balancer pools to concentrated liquidity pools and links them to their CL equivalent. + ctx.Logger().Info(fmt.Sprintf("Chain ID is %s, running testnet upgrade handler", ctx.ChainID())) + assetPairs, err = InitializeAssetPairsTestnet(ctx, keepers) + } if err != nil { return nil, err } // Get community pool address. communityPoolAddress := keepers.AccountKeeper.GetModuleAddress(distrtypes.ModuleName) - - // fullRangeCoinsUsed tracks the coins we use in the below for loop from the community pool to create the full range position for each new pool. - fullRangeCoinsUsed := sdk.NewCoins() - poolLinks := []gammmigration.BalancerToConcentratedPoolLink{} - - assetPairs := InitializeAssetPairs(ctx, keepers) + fullRangeCoinsUsed := sdk.Coins{} for _, assetPair := range assetPairs { - // Create a concentrated liquidity pool for asset pair. - clPool, err := keepers.GAMMKeeper.CreateConcentratedPoolFromCFMM(ctx, assetPair.LinkedClassicPool, assetPair.BaseAsset, assetPair.SpreadFactor, TickSpacing) - if err != nil { - return nil, err - } - clPoolId := clPool.GetId() - clPoolDenom := cltypes.GetConcentratedLockupDenomFromPoolId(clPoolId) - - // Add the pool link to the list of pool links (we set them all at once later) - poolLinks = append(poolLinks, gammmigration.BalancerToConcentratedPoolLink{ - BalancerPoolId: assetPair.LinkedClassicPool, - ClPoolId: clPoolId, - }) - - // Determine the amount of baseAsset that can be bought with 1 OSMO. - oneOsmo := sdk.NewCoin(QuoteAsset, sdk.NewInt(1000000)) - linkedClassicPool, err := keepers.PoolManagerKeeper.GetPool(ctx, assetPair.LinkedClassicPool) - if err != nil { - return nil, err - } - respectiveBaseAsset, err := keepers.GAMMKeeper.CalcOutAmtGivenIn(ctx, linkedClassicPool, oneOsmo, assetPair.BaseAsset, sdk.ZeroDec()) - if err != nil { - return nil, err - } - - // Create a full range position via the community pool with the funds we calculated above. - fullRangeCoins := sdk.NewCoins(respectiveBaseAsset, oneOsmo) - positionData, err := keepers.ConcentratedLiquidityKeeper.CreateFullRangePosition(ctx, clPoolId, communityPoolAddress, fullRangeCoins) - if err != nil { + clPoolDenom, clPoolId, poolLink, coinsUsed, err := createCLPoolWithCommunityPoolPosition(ctx, keepers, assetPair.LinkedClassicPool, assetPair.BaseAsset, assetPair.SpreadFactor, communityPoolAddress) + if errors.Is(err, notEnoughLiquidityForSwapErr) { + continue + } else if err != nil { return nil, err } - // Track the coins used to create the full range position (we manually update the fee pool later all at once). - fullRangeCoinsUsed = fullRangeCoinsUsed.Add(sdk.NewCoins(sdk.NewCoin(QuoteAsset, positionData.Amount1), sdk.NewCoin(assetPair.BaseAsset, positionData.Amount0))...) + // Track pool link created and coins used for the community pool. + poolLinks = append(poolLinks, poolLink) + fullRangeCoinsUsed = fullRangeCoinsUsed.Add(coinsUsed...) - // If pair was previously superfluid enabled, add the cl pool's full range denom as an authorized superfluid asset. if assetPair.Superfluid { - superfluidAsset := superfluidtypes.SuperfluidAsset{ - Denom: clPoolDenom, - AssetType: superfluidtypes.SuperfluidAssetTypeConcentratedShare, - } - err = keepers.SuperfluidKeeper.AddNewSuperfluidAsset(ctx, superfluidAsset) + ctx.Logger().Info(fmt.Sprintf("gammPoolId %d is superfluid enabled, enabling %s as a superfluid asset", assetPair.LinkedClassicPool, clPoolDenom)) + err := authorizeSuperfluid(ctx, keepers, clPoolDenom) if err != nil { return nil, err } } - clPoolTwapRecords, err := keepers.TwapKeeper.GetAllMostRecentRecordsForPool(ctx, clPoolId) + err = manuallySetTWAPRecords(ctx, keepers, clPoolId) if err != nil { return nil, err } - - for _, twapRecord := range clPoolTwapRecords { - twapRecord.LastErrorTime = time.Time{} - keepers.TwapKeeper.StoreNewRecord(ctx, twapRecord) - } } // Set the migration links in x/gamm. @@ -136,6 +127,9 @@ func CreateUpgradeHandler( feePool.CommunityPool = newPool keepers.DistrKeeper.SetFeePool(ctx, feePool) + // Set ibc-hooks params + keepers.IBCHooksKeeper.SetParams(ctx, ibchookstypes.DefaultParams()) + // Reset the pool weights upon upgrade. This will add support for CW pools on ProtoRev. keepers.ProtoRevKeeper.SetInfoByPoolType(ctx, types.DefaultPoolTypeInfo) @@ -143,8 +137,95 @@ func CreateUpgradeHandler( } } -// FlipTwapSpotPriceRecords flips the denoms and spot price of twap record of a given pool. -func FlipTwapSpotPriceRecords(ctx sdk.Context, pools []poolmanagertypes.PoolI, keepers *keepers.AppKeepers) error { +// createCLPoolWithCommunityPoolPosition creates a CL pool for a given balancer pool and adds a full range position with the community pool. +func createCLPoolWithCommunityPoolPosition(ctx sdk.Context, keepers *keepers.AppKeepers, gammPoolId uint64, baseAsset string, spreadFactor sdk.Dec, communityPoolAddress sdk.AccAddress) (clPoolDenom string, clPoolId uint64, poolLink gammmigration.BalancerToConcentratedPoolLink, coinsUsed sdk.Coins, err error) { + // Check if classic pool has enough liquidity to support a 0.1 OSMO swap before creating a CL pool. + // If not, skip the pool. + osmoIn := sdk.NewCoin(QuoteAsset, sdk.NewInt(100000)) + linkedClassicPool, err := keepers.PoolManagerKeeper.GetPool(ctx, gammPoolId) + if err != nil { + return "", 0, gammmigration.BalancerToConcentratedPoolLink{}, nil, err + } + _, err = keepers.GAMMKeeper.CalcOutAmtGivenIn(ctx, linkedClassicPool, osmoIn, baseAsset, spreadFactor) + if err != nil { + return "", 0, gammmigration.BalancerToConcentratedPoolLink{}, nil, err + } + + // Create a concentrated liquidity pool for asset pair. + ctx.Logger().Info(fmt.Sprintf("Creating CL pool from poolID (%d), baseAsset (%s), spreadFactor (%s), tickSpacing (%d)", gammPoolId, baseAsset, spreadFactor, TickSpacing)) + clPool, err := keepers.GAMMKeeper.CreateConcentratedPoolFromCFMM(ctx, gammPoolId, baseAsset, spreadFactor, TickSpacing) + if err != nil { + return "", 0, gammmigration.BalancerToConcentratedPoolLink{}, nil, err + } + clPoolId = clPool.GetId() + clPoolDenom = cltypes.GetConcentratedLockupDenomFromPoolId(clPoolId) + + // Create pool link object. + poolLink = gammmigration.BalancerToConcentratedPoolLink{ + BalancerPoolId: gammPoolId, + ClPoolId: clPoolId, + } + + // Get community pool balance before swap and position creation + commPoolBalanceBaseAssetPre := keepers.BankKeeper.GetBalance(ctx, communityPoolAddress, baseAsset) + commPoolBalanceQuoteAssetPre := keepers.BankKeeper.GetBalance(ctx, communityPoolAddress, QuoteAsset) + commPoolBalancePre := sdk.NewCoins(commPoolBalanceBaseAssetPre, commPoolBalanceQuoteAssetPre) + + // Swap 0.1 OSMO for baseAsset from the community pool. + respectiveBaseAssetInt, err := keepers.GAMMKeeper.SwapExactAmountIn(ctx, communityPoolAddress, linkedClassicPool, osmoIn, baseAsset, sdk.ZeroInt(), linkedClassicPool.GetSpreadFactor(ctx)) + if err != nil { + return "", 0, gammmigration.BalancerToConcentratedPoolLink{}, nil, err + } + ctx.Logger().Info(fmt.Sprintf("Swapped %s for %s%s from the community pool", osmoIn.String(), respectiveBaseAssetInt.String(), baseAsset)) + + respectiveBaseAsset := sdk.NewCoin(baseAsset, respectiveBaseAssetInt) + + // Create a full range position via the community pool with the funds we calculated above. + fullRangeCoins := sdk.NewCoins(respectiveBaseAsset, osmoIn) + _, err = keepers.ConcentratedLiquidityKeeper.CreateFullRangePosition(ctx, clPoolId, communityPoolAddress, fullRangeCoins) + if err != nil { + return "", 0, gammmigration.BalancerToConcentratedPoolLink{}, nil, err + } + + // Get community pool balance after swap and position creation + commPoolBalanceBaseAssetPost := keepers.BankKeeper.GetBalance(ctx, communityPoolAddress, baseAsset) + commPoolBalanceQuoteAssetPost := keepers.BankKeeper.GetBalance(ctx, communityPoolAddress, QuoteAsset) + commPoolBalancePost := sdk.NewCoins(commPoolBalanceBaseAssetPost, commPoolBalanceQuoteAssetPost) + + // While we can be fairly certain the diff between these two is 0.2 OSMO, if for whatever reason + // some baseAsset dust remains in the community pool and we don't account for it, when updating the + // fee pool balance later, we will be off by that amount and will cause a panic. + coinsUsed = commPoolBalancePre.Sub(commPoolBalancePost) + + return clPoolDenom, clPoolId, poolLink, coinsUsed, nil +} + +// authorizeSuperfluid authorizes superfluid for the provided CL pool. +func authorizeSuperfluid(ctx sdk.Context, keepers *keepers.AppKeepers, clPoolDenom string) (err error) { + superfluidAsset := superfluidtypes.SuperfluidAsset{ + Denom: clPoolDenom, + AssetType: superfluidtypes.SuperfluidAssetTypeConcentratedShare, + } + return keepers.SuperfluidKeeper.AddNewSuperfluidAsset(ctx, superfluidAsset) +} + +// manuallySetTWAPRecords manually sets the TWAP records for a CL pool. This prevents a panic when the CL pool is first used. +func manuallySetTWAPRecords(ctx sdk.Context, keepers *keepers.AppKeepers, clPoolId uint64) error { + ctx.Logger().Info(fmt.Sprintf("manually setting twap record for newly created CL poolID %d", clPoolId)) + clPoolTwapRecords, err := keepers.TwapKeeper.GetAllMostRecentRecordsForPool(ctx, clPoolId) + if err != nil { + return err + } + + for _, twapRecord := range clPoolTwapRecords { + twapRecord.LastErrorTime = time.Time{} + keepers.TwapKeeper.StoreNewRecord(ctx, twapRecord) + } + return nil +} + +// flipTwapSpotPriceRecords flips the denoms and spot price of twap record of a given pool. +func flipTwapSpotPriceRecords(ctx sdk.Context, pools []poolmanagertypes.PoolI, keepers *keepers.AppKeepers) error { for _, pool := range pools { poolId := pool.GetId() twapRecordHistoricalPoolIndexed, err := keepers.TwapKeeper.GetAllHistoricalPoolIndexedTWAPsForPoolId(ctx, poolId) @@ -177,6 +258,5 @@ func FlipTwapSpotPriceRecords(ctx sdk.Context, pools []poolmanagertypes.PoolI, k keepers.TwapKeeper.DeleteMostRecentRecord(ctx, oldRecord) } } - return nil } diff --git a/app/upgrades/v17/upgrades_test.go b/app/upgrades/v17/upgrades_test.go index a6ad8a33467..1ceeb08fdb4 100644 --- a/app/upgrades/v17/upgrades_test.go +++ b/app/upgrades/v17/upgrades_test.go @@ -19,7 +19,9 @@ import ( "github.com/osmosis-labs/osmosis/v17/app/keepers" v17 "github.com/osmosis-labs/osmosis/v17/app/upgrades/v17" cltypes "github.com/osmosis-labs/osmosis/v17/x/concentrated-liquidity/types" + poolManagerTypes "github.com/osmosis-labs/osmosis/v17/x/poolmanager/types" poolmanagertypes "github.com/osmosis-labs/osmosis/v17/x/poolmanager/types" + superfluidtypes "github.com/osmosis-labs/osmosis/v17/x/superfluid/types" "github.com/osmosis-labs/osmosis/v17/x/twap/types" ) @@ -102,7 +104,7 @@ func (suite *UpgradeTestSuite) TestUpgrade() { upgrade func(sdk.Context, *keepers.AppKeepers, sdk.Coins, uint64) }{ { - "Test that the upgrade succeeds", + "Test that the upgrade succeeds: mainnet", func(ctx sdk.Context, keepers *keepers.AppKeepers) (sdk.Coins, uint64) { upgradeSetup() @@ -130,24 +132,23 @@ func (suite *UpgradeTestSuite) TestUpgrade() { // Now create the pool with the correct pool ID. poolCoins := sdk.NewCoins(sdk.NewCoin(assetPair.BaseAsset, sdk.NewInt(10000000000)), sdk.NewCoin(v17.QuoteAsset, sdk.NewInt(10000000000))) - poolId := suite.PrepareBalancerPoolWithCoins(poolCoins...) + suite.PrepareBalancerPoolWithCoins(poolCoins...) - // Send two of the base asset to the community pool. - twoBaseAsset := sdk.NewCoins(sdk.NewCoin(assetPair.BaseAsset, sdk.NewInt(2000000))) - suite.FundAcc(suite.TestAccs[0], twoBaseAsset) + // 0.1 OSMO used to get the respective base asset amount, 0.1 OSMO used to create the position + osmoIn := sdk.NewCoin(v17.QuoteAsset, sdk.NewInt(100000).MulRaw(2)) - err := suite.App.DistrKeeper.FundCommunityPool(suite.Ctx, twoBaseAsset, suite.TestAccs[0]) - suite.Require().NoError(err) - - // Determine approx how much baseAsset will be used from community pool when 1 OSMO used. - oneOsmo := sdk.NewCoin(v17.QuoteAsset, sdk.NewInt(1000000)) - pool, err := suite.App.PoolManagerKeeper.GetPool(suite.Ctx, poolId) - suite.Require().NoError(err) - respectiveBaseAsset, err := suite.App.GAMMKeeper.CalcOutAmtGivenIn(suite.Ctx, pool, oneOsmo, assetPair.BaseAsset, sdk.ZeroDec()) - suite.Require().NoError(err) + // Add the amount of osmo that will be used to the expectedCoinsUsedInUpgradeHandler. + expectedCoinsUsedInUpgradeHandler = expectedCoinsUsedInUpgradeHandler.Add(osmoIn) - // Add the amount of baseAsset that will be used to the expectedCoinsUsedInUpgradeHandler. - expectedCoinsUsedInUpgradeHandler = expectedCoinsUsedInUpgradeHandler.Add(respectiveBaseAsset) + // Enable the GAMM pool for superfluid if the record says so. + if assetPair.Superfluid { + poolShareDenom := fmt.Sprintf("gamm/pool/%d", assetPair.LinkedClassicPool) + superfluidAsset := superfluidtypes.SuperfluidAsset{ + Denom: poolShareDenom, + AssetType: superfluidtypes.SuperfluidAssetTypeLPShare, + } + suite.App.SuperfluidKeeper.SetSuperfluidAsset(suite.Ctx, superfluidAsset) + } // Update the lastPoolID to the current pool ID. lastPoolID = poolID @@ -216,6 +217,7 @@ func (suite *UpgradeTestSuite) TestUpgrade() { communityPoolAddress := suite.App.AccountKeeper.GetModuleAddress(distrtypes.ModuleName) communityPoolBalancePre := suite.App.BankKeeper.GetAllBalances(suite.Ctx, communityPoolAddress) + numPoolPreUpgrade := suite.App.PoolManagerKeeper.GetNextPoolId(suite.Ctx) - 1 clPool1TwapRecordPreUpgrade, err := keepers.TwapKeeper.GetAllMostRecentRecordsForPool(ctx, lastPoolIdMinusTwo) suite.Require().NoError(err) @@ -285,15 +287,10 @@ func (suite *UpgradeTestSuite) TestUpgrade() { communityPoolBalancePost := suite.App.BankKeeper.GetAllBalances(suite.Ctx, communityPoolAddress) feePoolCommunityPoolPost := suite.App.DistrKeeper.GetFeePool(suite.Ctx).CommunityPool - assetPairs := v17.InitializeAssetPairs(ctx, keepers) + assetPairs, err := v17.InitializeAssetPairs(ctx, keepers) + suite.Require().NoError(err) for i, assetPair := range assetPairs { - // Validate that the community pool balance has been reduced by the amount of baseAsset that was used to create the pool. - suite.Require().Equal(communityPoolBalancePre.AmountOf(assetPair.BaseAsset).Sub(expectedCoinsUsedInUpgradeHandler.AmountOf(assetPair.BaseAsset)).String(), communityPoolBalancePost.AmountOf(assetPair.BaseAsset).String()) - - // Validate that the fee pool community pool balance has been decreased by the amount of baseAsset that was used to create the pool. - suite.Require().Equal(communityPoolBalancePost.AmountOf(assetPair.BaseAsset).String(), feePoolCommunityPoolPost.AmountOf(assetPair.BaseAsset).TruncateInt().String()) - // Get balancer pool's spot price. balancerSpotPrice, err := suite.App.GAMMKeeper.CalculateSpotPrice(suite.Ctx, assetPair.LinkedClassicPool, v17.QuoteAsset, assetPair.BaseAsset) suite.Require().NoError(err) @@ -330,9 +327,16 @@ func (suite *UpgradeTestSuite) TestUpgrade() { lastPoolID++ } - // Check osmo balance (was used in every pool creation) - suite.Require().Equal(0, multiplicativeTolerance.Compare(communityPoolBalancePre.AmountOf(v17.QuoteAsset), communityPoolBalancePost.AmountOf(v17.QuoteAsset).Sub(expectedCoinsUsedInUpgradeHandler.AmountOf(v17.QuoteAsset)))) - suite.Require().Equal(communityPoolBalancePost.AmountOf(v17.QuoteAsset).String(), feePoolCommunityPoolPost.AmountOf(v17.QuoteAsset).TruncateInt().String()) + // Validate that the community pool balance has been reduced by the amount of osmo that was used to create the pool. + suite.Require().Equal(communityPoolBalancePre.Sub(expectedCoinsUsedInUpgradeHandler).String(), communityPoolBalancePost.String()) + + // Validate that the fee pool community pool balance has been decreased by the amount of osmo that was used to create the pool. + suite.Require().Equal(sdk.NewDecCoinsFromCoins(communityPoolBalancePost...).String(), feePoolCommunityPoolPost.String()) + + numPoolPostUpgrade := suite.App.PoolManagerKeeper.GetNextPoolId(suite.Ctx) - 1 + + // Number of pools created should be equal to the number of records in the asset pairs. + suite.Require().Equal(len(assetPairs), int(numPoolPostUpgrade-numPoolPreUpgrade)) // Validate that all links were created. migrationInfo, err := suite.App.GAMMKeeper.GetAllMigrationInfo(suite.Ctx) @@ -341,6 +345,182 @@ func (suite *UpgradeTestSuite) TestUpgrade() { }, }, + { + "Test that the upgrade succeeds: testnet", + func(ctx sdk.Context, keepers *keepers.AppKeepers) (sdk.Coins, uint64) { + upgradeSetup() + suite.Ctx = suite.Ctx.WithChainID("osmo-test-5") + + var lastPoolID uint64 // To keep track of the last assigned pool ID + + sort.Sort(ByLinkedClassicPool(v17.AssetPairsForTestsOnly)) + sort.Sort(ByLinkedClassicPool(v17.AssetPairs)) + + expectedCoinsUsedInUpgradeHandler := sdk.NewCoins() + + // Create earlier pools or dummy pools if needed + for _, assetPair := range v17.AssetPairsForTestsOnly { + poolID := assetPair.LinkedClassicPool + + // For testnet, we create a CL pool for ANY balancer pool. + // The only thing we use the assetPair list here for to select some pools to enable superfluid for. + for lastPoolID+1 < poolID { + poolCoins := sdk.NewCoins(sdk.NewCoin(assetPair.BaseAsset, sdk.NewInt(10000000000)), sdk.NewCoin(v17.QuoteAsset, sdk.NewInt(10000000000))) + suite.PrepareBalancerPoolWithCoins(poolCoins...) + + // 0.1 OSMO used to get the respective base asset amount, 0.1 OSMO used to create the position + osmoIn := sdk.NewCoin(v17.QuoteAsset, sdk.NewInt(100000).MulRaw(2)) + + // Add the amount of osmo that will be used to the expectedCoinsUsedInUpgradeHandler. + expectedCoinsUsedInUpgradeHandler = expectedCoinsUsedInUpgradeHandler.Add(osmoIn) + + lastPoolID++ + } + + // Enable the GAMM pool for superfluid if the asset pair is marked as superfluid. + if assetPair.Superfluid { + poolShareDenom := fmt.Sprintf("gamm/pool/%d", assetPair.LinkedClassicPool) + superfluidAsset := superfluidtypes.SuperfluidAsset{ + Denom: poolShareDenom, + AssetType: superfluidtypes.SuperfluidAssetTypeLPShare, + } + suite.App.SuperfluidKeeper.SetSuperfluidAsset(suite.Ctx, superfluidAsset) + } + } + + // We now create various pools that are not balancer pools. + // This is to test if the testnet upgrade handler properly handles pools that are not of type balancer (i.e. should ignore them and move on). + + // Stableswap pool + suite.CreatePoolFromType(poolmanagertypes.Stableswap) + // Cosmwasm pool + suite.CreatePoolFromType(poolmanagertypes.CosmWasm) + // CL pool + suite.CreatePoolFromType(poolmanagertypes.Concentrated) + + lastPoolID += 3 + + return expectedCoinsUsedInUpgradeHandler, lastPoolID + + }, + func(ctx sdk.Context, keepers *keepers.AppKeepers, expectedCoinsUsedInUpgradeHandler sdk.Coins, lastPoolID uint64) { + stakingParams := suite.App.StakingKeeper.GetParams(suite.Ctx) + stakingParams.BondDenom = "uosmo" + suite.App.StakingKeeper.SetParams(suite.Ctx, stakingParams) + + // Retrieve the community pool balance before the upgrade + communityPoolAddress := suite.App.AccountKeeper.GetModuleAddress(distrtypes.ModuleName) + communityPoolBalancePre := suite.App.BankKeeper.GetAllBalances(suite.Ctx, communityPoolAddress) + + numPoolPreUpgrade := suite.App.PoolManagerKeeper.GetNextPoolId(suite.Ctx) - 1 + + gammPoolsPreUpgrade, err := suite.App.GAMMKeeper.GetPools(suite.Ctx) + suite.Require().NoError(err) + + // Run upgrade handler. + dummyUpgrade(suite) + suite.Require().NotPanics(func() { + suite.App.BeginBlocker(suite.Ctx, abci.RequestBeginBlock{}) + }) + + // Retrieve the community pool balance (and the feePool balance) after the upgrade + communityPoolBalancePost := suite.App.BankKeeper.GetAllBalances(suite.Ctx, communityPoolAddress) + feePoolCommunityPoolPost := suite.App.DistrKeeper.GetFeePool(suite.Ctx).CommunityPool + + indexOffset := int(0) + assetListIndex := int(0) + + // For testnet, we run through all gamm pools (not just the asset list) + for i, pool := range gammPoolsPreUpgrade { + // Skip pools that are not balancer pools + if pool.GetType() != poolManagerTypes.Balancer { + indexOffset++ + continue + } + + gammPoolId := pool.GetId() + cfmmPool, err := keepers.GAMMKeeper.GetCFMMPool(ctx, gammPoolId) + suite.Require().NoError(err) + + poolCoins := cfmmPool.GetTotalPoolLiquidity(ctx) + + // Retrieve quoteAsset and baseAsset from the poolCoins + quoteAsset, baseAsset := "", "" + for _, coin := range poolCoins { + if coin.Denom == v17.QuoteAsset { + quoteAsset = coin.Denom + } else { + baseAsset = coin.Denom + } + } + if quoteAsset == "" || baseAsset == "" { + indexOffset++ + continue + } + + // Get balancer pool's spot price. + balancerSpotPrice, err := suite.App.GAMMKeeper.CalculateSpotPrice(suite.Ctx, gammPoolId, v17.QuoteAsset, baseAsset) + suite.Require().NoError(err) + + // Validate CL pool was created. + concentratedPool, err := suite.App.PoolManagerKeeper.GetPool(suite.Ctx, lastPoolID+1) + suite.Require().NoError(err) + suite.Require().Equal(poolmanagertypes.Concentrated, concentratedPool.GetType()) + + // Validate that denom0 and denom1 were set correctly + concentratedTypePool, ok := concentratedPool.(cltypes.ConcentratedPoolExtension) + suite.Require().True(ok) + suite.Require().Equal(baseAsset, concentratedTypePool.GetToken0()) + suite.Require().Equal(v17.QuoteAsset, concentratedTypePool.GetToken1()) + + // Validate that the spot price of the CL pool is what we expect + suite.Require().Equal(0, multiplicativeTolerance.CompareBigDec(concentratedTypePool.GetCurrentSqrtPrice().PowerInteger(2), osmomath.BigDecFromSDKDec(balancerSpotPrice))) + + // Validate that the link is correct. + migrationInfo, err := suite.App.GAMMKeeper.GetAllMigrationInfo(suite.Ctx) + link := migrationInfo.BalancerToConcentratedPoolLinks[i-indexOffset] + suite.Require().Equal(gammPoolId, link.BalancerPoolId) + suite.Require().Equal(concentratedPool.GetId(), link.ClPoolId) + + // Validate the sfs status. + // If the poolId matches a poolId on that asset list that had superfluid enabled, this pool should also be superfluid enabled. + // Otherwise, it should not be superfluid enabled. + assetListPoolId := v17.AssetPairsForTestsOnly[assetListIndex].LinkedClassicPool + clPoolDenom := cltypes.GetConcentratedLockupDenomFromPoolId(concentratedPool.GetId()) + _, err = suite.App.SuperfluidKeeper.GetSuperfluidAsset(suite.Ctx, clPoolDenom) + if assetListPoolId == gammPoolId { + suite.Require().NoError(err) + assetListIndex++ + for assetListIndex < len(v17.AssetPairsForTestsOnly)-1 && v17.AssetPairsForTestsOnly[assetListIndex].Superfluid == false { + assetListIndex++ + } + } else { + suite.Require().Error(err) + } + + lastPoolID++ + } + + // Validate that the community pool balance has been reduced by the amount of osmo that was used to create the pool. + suite.Require().Equal(communityPoolBalancePre.Sub(expectedCoinsUsedInUpgradeHandler).String(), communityPoolBalancePost.String()) + + // Validate that the fee pool community pool balance has been decreased by the amount of osmo that was used to create the pool. + suite.Require().Equal(sdk.NewDecCoinsFromCoins(communityPoolBalancePost...).String(), feePoolCommunityPoolPost.String()) + + numPoolPostUpgrade := suite.App.PoolManagerKeeper.GetNextPoolId(suite.Ctx) - 1 + numPoolsCreated := numPoolPostUpgrade - numPoolPreUpgrade + + // Number of pools created should be equal to the number of pools preUpgrade minus the number of pools that were not eligible for migration. + numPoolsEligibleForMigration := numPoolPreUpgrade - 3 + suite.Require().Equal(int(numPoolsEligibleForMigration), int(numPoolsCreated)) + + // Validate that all links were created. + migrationInfo, err := suite.App.GAMMKeeper.GetAllMigrationInfo(suite.Ctx) + suite.Require().Equal(int(numPoolsEligibleForMigration), len(migrationInfo.BalancerToConcentratedPoolLinks)) + suite.Require().NoError(err) + + }, + }, { "Fails because CFMM pool is not found", func(ctx sdk.Context, keepers *keepers.AppKeepers) (sdk.Coins, uint64) { diff --git a/tests/cl-genesis-positions/convert.go b/tests/cl-genesis-positions/convert.go index ecf0b8311e2..3e3cdb7e862 100644 --- a/tests/cl-genesis-positions/convert.go +++ b/tests/cl-genesis-positions/convert.go @@ -111,7 +111,7 @@ func ConvertSubgraphToOsmosisGenesis(positionCreatorAddresses []sdk.AccAddress, // Initialize first position to be 1:1 price // this is because the first position must have non-zero token0 and token1 to initialize the price // however, our data has first position with non-zero amount. - _, _, _, _, err = osmosis.App.ConcentratedLiquidityKeeper.CreateFullRangePosition(osmosis.Ctx, pool.GetId(), osmosis.TestAccs[0], sdk.NewCoins(sdk.NewCoin(msgCreatePool.Denom0, sdk.NewInt(100)), sdk.NewCoin(msgCreatePool.Denom1, sdk.NewInt(100)))) + _, err = osmosis.App.ConcentratedLiquidityKeeper.CreateFullRangePosition(osmosis.Ctx, pool.GetId(), osmosis.TestAccs[0], sdk.NewCoins(sdk.NewCoin(msgCreatePool.Denom0, sdk.NewInt(100)), sdk.NewCoin(msgCreatePool.Denom1, sdk.NewInt(100)))) if err != nil { panic(err) } diff --git a/tests/e2e/configurer/chain/commands.go b/tests/e2e/configurer/chain/commands.go index 1bc862fe8c5..f419dc6259e 100644 --- a/tests/e2e/configurer/chain/commands.go +++ b/tests/e2e/configurer/chain/commands.go @@ -662,7 +662,7 @@ func (n *NodeConfig) SendIBC(srcChain, dstChain *Config, recipient string, token return balancePost.Amount.Equal(balancePre.Amount.Add(token.Amount)) }, - 2*time.Minute, + 3*time.Minute, 10*time.Millisecond, "tx not received on destination chain", ) diff --git a/tests/e2e/configurer/upgrade.go b/tests/e2e/configurer/upgrade.go index 547fc750c38..f8d43db4ef2 100644 --- a/tests/e2e/configurer/upgrade.go +++ b/tests/e2e/configurer/upgrade.go @@ -143,15 +143,10 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { v17SuperfluidAssets := v17GetSuperfluidAssets() - wg.Add(4) + wg.Add(2) // Chain A - go func() { - defer wg.Done() - chainANode.FundCommunityPool(initialization.ValidatorWalletName, strAllUpgradeBaseDenoms()) - }() - go func() { defer wg.Done() chainANode.EnableSuperfluidAsset(chainA, v17SuperfluidAssets) @@ -159,11 +154,6 @@ func (uc *UpgradeConfigurer) CreatePreUpgradeState() error { // Chain B - go func() { - defer wg.Done() - chainBNode.FundCommunityPool(initialization.ValidatorWalletName, strAllUpgradeBaseDenoms()) - }() - go func() { defer wg.Done() chainBNode.EnableSuperfluidAsset(chainB, v17SuperfluidAssets) @@ -452,18 +442,6 @@ func (uc *UpgradeConfigurer) upgradeContainers(chainConfig *chain.Config, propHe // START: CAN REMOVE POST v17 UPGRADE -func strAllUpgradeBaseDenoms() string { - upgradeBaseDenoms := "" - n := len(v17.AssetPairsForTestsOnly) - for i, assetPair := range v17.AssetPairsForTestsOnly { - upgradeBaseDenoms += "2000000" + assetPair.BaseAsset - if i < n-1 { // Check if it's not the last iteration - upgradeBaseDenoms += "," - } - } - return upgradeBaseDenoms -} - func v17GetSuperfluidAssets() string { assets := "" for _, assetPair := range v17.AssetPairsForTestsOnly { diff --git a/tests/e2e/e2e_test.go b/tests/e2e/e2e_test.go index f628eaddf2b..13237521061 100644 --- a/tests/e2e/e2e_test.go +++ b/tests/e2e/e2e_test.go @@ -1830,9 +1830,11 @@ func (s *IntegrationTestSuite) ConcentratedLiquidity_CanonicalPools() { expectedSpotPrice, err := balancerPool.SpotPrice(sdk.Context{}, v17.QuoteAsset, assetPair.BaseAsset) s.Require().NoError(err) - // Allow 0.1% margin of error. + // Margin of error should be slightly larger than the gamm pool's spread factor, as the gamm pool is used to + // swap through when creating the initial position. The below implies a 0.1% margin of error. + tollerance := expectedSpreadFactor.Add(sdk.MustNewDecFromStr("0.0001")) multiplicativeTolerance := osmomath.ErrTolerance{ - MultiplicativeTolerance: sdk.MustNewDecFromStr("0.001"), + MultiplicativeTolerance: tollerance, } s.Require().Equal(0, multiplicativeTolerance.CompareBigDec(osmomath.BigDecFromSDKDec(expectedSpotPrice), concentratedPool.GetCurrentSqrtPrice().PowerInteger(2))) diff --git a/x/gamm/keeper/migrate_test.go b/x/gamm/keeper/migrate_test.go index b34fac028ce..e1e56829bc6 100644 --- a/x/gamm/keeper/migrate_test.go +++ b/x/gamm/keeper/migrate_test.go @@ -1035,7 +1035,7 @@ func (s *KeeperTestSuite) TestCreateConcentratedPoolFromCFMM() { poolLiquidity: sdk.NewCoins(desiredDenom0Coin, daiCoin), cfmmPoolIdToLinkWith: validPoolId, desiredDenom0: USDCIBCDenom, - expectError: types.NoDesiredDenomInPoolError{USDCIBCDenom}, + expectError: types.NoDesiredDenomInPoolError{DesiredDenom: USDCIBCDenom}, }, "error: pool with 3 assets, must have two": { poolLiquidity: sdk.NewCoins(desiredDenom0Coin, daiCoin, usdcCoin), @@ -1124,7 +1124,7 @@ func (s *KeeperTestSuite) TestCreateCanonicalConcentratedLiquidityPoolAndMigrati poolLiquidity: sdk.NewCoins(desiredDenom0Coin, daiCoin), cfmmPoolIdToLinkWith: validPoolId, desiredDenom0: USDCIBCDenom, - expectError: types.NoDesiredDenomInPoolError{USDCIBCDenom}, + expectError: types.NoDesiredDenomInPoolError{DesiredDenom: USDCIBCDenom}, }, "error: pool with 3 assets, must have two": { poolLiquidity: sdk.NewCoins(desiredDenom0Coin, daiCoin, usdcCoin), diff --git a/x/pool-incentives/keeper/keeper.go b/x/pool-incentives/keeper/keeper.go index a84c1f5fe38..ebfc443d1c6 100644 --- a/x/pool-incentives/keeper/keeper.go +++ b/x/pool-incentives/keeper/keeper.go @@ -185,13 +185,14 @@ func (k Keeper) GetPoolGaugeId(ctx sdk.Context, poolId uint64, lockableDuration key := types.GetPoolGaugeIdInternalStoreKey(poolId, lockableDuration) store := ctx.KVStore(k.storeKey) - bz := store.Get(key) - if len(bz) == 0 { + if !store.Has(key) { return 0, types.NoGaugeAssociatedWithPoolError{PoolId: poolId, Duration: lockableDuration} } - return sdk.BigEndianToUint64(bz), nil + bz := store.Get(key) + gaugeId := sdk.BigEndianToUint64(bz) + return gaugeId, nil } // GetNoLockGaugeIdsFromPool returns all the NoLock gauge ids associated with the pool id.