diff --git a/app/upgrades/v18/upgrades_test.go b/app/upgrades/v18/upgrades_test.go index ad393b02314..3fcba8ce562 100644 --- a/app/upgrades/v18/upgrades_test.go +++ b/app/upgrades/v18/upgrades_test.go @@ -2,6 +2,7 @@ package v18_test import ( "fmt" + poolmanagertypes "github.com/osmosis-labs/osmosis/v17/x/poolmanager/types" "sort" "testing" "time" @@ -44,34 +45,64 @@ func assertEqual(suite *UpgradeTestSuite, pre, post interface{}) { suite.Require().Equal(pre, post) } -func (suite *UpgradeTestSuite) TestUpgrade() { +func (s *UpgradeTestSuite) TestUpgrade() { // set up pools first to match v17 state(including linked cl pools) - suite.setupPoolsToMainnetState() + s.setupPoolsToMainnetState() // corrupt state to match mainnet state - suite.setupCorruptedState() + s.setupCorruptedState() // with the corrupted state, distribution used to panic in the `AfterEpochEnd` hook, // specifically from the one from incentives keeper. // This method ensures that with the corrupted state, we have the same state where // distribution would fail. - suite.ensurePreUpgradeDistributionPanics() + s.ensurePreUpgradeDistributionPanics() + + migrationInfo, err := s.App.GAMMKeeper.GetAllMigrationInfo(s.Ctx) + s.Require().NoError(err) + + link := migrationInfo.BalancerToConcentratedPoolLinks[0] + s.Require().Equal(uint64(3), link.BalancerPoolId) + + clPoolId := link.ClPoolId + + pool, err := s.App.ConcentratedLiquidityKeeper.GetConcentratedPoolById(s.Ctx, clPoolId) + s.Require().NoError(err) + + // LP Fails before the upgrade + lpTokens := sdk.NewCoins(sdk.NewCoin(pool.GetToken0(), sdk.NewInt(1_000_000)), sdk.NewCoin(pool.GetToken1(), sdk.NewInt(1_000_000))) + s.FundAcc(s.TestAccs[0], lpTokens) + // require a panic + s.Require().Panics(func() { + _, err = s.App.ConcentratedLiquidityKeeper.CreateFullRangePosition(s.Ctx, clPoolId, s.TestAccs[0], lpTokens) + }) // upgrade software - suite.imitateUpgrade() - suite.App.BeginBlocker(suite.Ctx, abci.RequestBeginBlock{}) - suite.Ctx = suite.Ctx.WithBlockTime(suite.Ctx.BlockTime().Add(time.Hour * 24)) + s.imitateUpgrade() + s.App.BeginBlocker(s.Ctx, abci.RequestBeginBlock{}) + s.Ctx = s.Ctx.WithBlockTime(s.Ctx.BlockTime().Add(time.Hour * 24)) // after the accum values have been resetted correctly after upgrade, we expect the accumulator store to be initialized with the correct value, // which in our test case would be 10000(the amount that was locked) - valueAfterClear := suite.App.LockupKeeper.GetPeriodLocksAccumulation(suite.Ctx, lockuptypes.QueryCondition{ + valueAfterClear := s.App.LockupKeeper.GetPeriodLocksAccumulation(s.Ctx, lockuptypes.QueryCondition{ LockQueryType: lockuptypes.ByDuration, Denom: "gamm/pool/3", Duration: time.Hour * 24 * 14, }) valueAfterClear.Equal(sdk.NewInt(shareStaysLocked)) - suite.ensurePostUpgradeDistributionWorks() + s.ensurePostUpgradeDistributionWorks() + + // Check that can LP and swap into pool 3 with no usses + // LP + _, err = s.App.ConcentratedLiquidityKeeper.CreateFullRangePosition(s.Ctx, clPoolId, s.TestAccs[0], lpTokens) + s.Require().NoError(err) + + // Swap + toSwap := sdk.NewCoin(pool.GetToken0(), sdk.NewInt(100)) + _, err = s.App.ConcentratedLiquidityKeeper.SwapExactAmountIn(s.Ctx, s.TestAccs[0], pool.(poolmanagertypes.PoolI), toSwap, pool.GetToken1(), sdk.NewInt(1), sdk.ZeroDec()) + s.Require().NoError(err) + } func (suite *UpgradeTestSuite) imitateUpgrade() { diff --git a/x/concentrated-liquidity/incentives.go b/x/concentrated-liquidity/incentives.go index c5868b9d868..9557adf75fb 100644 --- a/x/concentrated-liquidity/incentives.go +++ b/x/concentrated-liquidity/incentives.go @@ -171,32 +171,24 @@ func (k Keeper) prepareBalancerPoolAsFullRange(ctx sdk.Context, clPoolId uint64, // relaxed in the future. // Note that we check denom compatibility later, and pool weights technically do not matter as they // are analogous to changing the spot price, which is handled by our lower bounding. - if len(balancerPoolLiquidity) != 2 { + // Note that due to low share ratio, the balancer token liquidity may be truncated to zero. + // Balancer liquidity may also upgrade in-full to CL. + if len(balancerPoolLiquidity) > 2 { return 0, sdk.ZeroDec(), types.ErrInvalidBalancerPoolLiquidityError{ClPoolId: clPoolId, BalancerPoolId: canonicalBalancerPoolId, BalancerPoolLiquidity: balancerPoolLiquidity} } - // We ensure that the asset ordering is correct when passing Balancer assets into the CL pool. - var asset0Amount, asset1Amount sdk.Int - if balancerPoolLiquidity[0].Denom == clPool.GetToken0() { - asset0Amount = balancerPoolLiquidity[0].Amount - asset1Amount = balancerPoolLiquidity[1].Amount + denom0 := clPool.GetToken0() + denom1 := clPool.GetToken1() - // Ensure second denom matches (bal1 -> CL1) - if balancerPoolLiquidity[1].Denom != clPool.GetToken1() { - return 0, sdk.ZeroDec(), types.ErrInvalidBalancerPoolLiquidityError{ClPoolId: clPoolId, BalancerPoolId: canonicalBalancerPoolId, BalancerPoolLiquidity: balancerPoolLiquidity} - } - } else if balancerPoolLiquidity[0].Denom == clPool.GetToken1() { - asset0Amount = balancerPoolLiquidity[1].Amount - asset1Amount = balancerPoolLiquidity[0].Amount - - // Ensure second denom matches (bal1 -> CL0) - if balancerPoolLiquidity[1].Denom != clPool.GetToken0() { - return 0, sdk.ZeroDec(), types.ErrInvalidBalancerPoolLiquidityError{ClPoolId: clPoolId, BalancerPoolId: canonicalBalancerPoolId, BalancerPoolLiquidity: balancerPoolLiquidity} - } - } else { + // This check's purpose is to confirm that denoms are the same. + clCoins := totalBalancerPoolLiquidity.FilterDenoms([]string{denom0, denom1}) + if len(clCoins) != 2 { return 0, sdk.ZeroDec(), types.ErrInvalidBalancerPoolLiquidityError{ClPoolId: clPoolId, BalancerPoolId: canonicalBalancerPoolId, BalancerPoolLiquidity: balancerPoolLiquidity} } + asset0Amount := balancerPoolLiquidity.AmountOf(denom0) + asset1Amount := balancerPoolLiquidity.AmountOf(denom1) + // Calculate the amount of liquidity the Balancer amounts qualify in the CL pool. Note that since we use the CL spot price, this is // safe against prices drifting apart between the two pools (we take the lower bound on the qualifying liquidity in this case). // The `sqrtPriceLowerTick` and `sqrtPriceUpperTick` fields are set to the appropriate values for a full range position.