From 017b2562fed8505fc2452f9439df3d11c19e31ba Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Thu, 7 May 2026 18:01:57 +0300 Subject: [PATCH 01/14] improve tranche key creation helpers, add up to 20 trailing zeroes to tranche key --- .../integration_placelimitorder_test.go | 62 +++++++++++++++++++ x/dex/keeper/limit_order_tranche.go | 12 ++-- x/dex/keeper/limit_order_tranche_test.go | 5 +- x/dex/keeper/limit_order_tranche_user_test.go | 3 +- x/dex/types/limit_order_tranche_key.go | 7 +++ 5 files changed, 78 insertions(+), 11 deletions(-) diff --git a/x/dex/keeper/integration_placelimitorder_test.go b/x/dex/keeper/integration_placelimitorder_test.go index 70d779e72..5bbe51b7c 100644 --- a/x/dex/keeper/integration_placelimitorder_test.go +++ b/x/dex/keeper/integration_placelimitorder_test.go @@ -721,3 +721,65 @@ func (s *DexTestSuite) TestPlaceLimitOrderMixedTypes() { s.NotEqual(trancheKey2, trancheKey3, "GTC and JIT in same tranche") s.Equal(trancheKey4, trancheKey3, "GTCs not combined") } + +func (s *DexTestSuite) TestTrancheKeysLexicographicOrdering() { + tomorrow := time.Now().AddDate(0, 0, 1) + + // GIVEN + // bob has 10 units of TokenA for 10 GTT orders; alice has 1 + s.fundBobBalances(10, 0) + s.fundAliceBalances(1, 0) + // carol (taker) has 3 units of TokenB to sweep exactly 3 maker tranches + s.fundCarolBalances(0, 3) + + bobEarlyKey0 := s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...0 + bobEarlyKey1 := s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...1 + aliceKey := s.aliceLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...2 + s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...3 + s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...4 + s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...5 + s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...6 + s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...7 + s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...8 + s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...9 + bobLateKey := s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...10 + + s.Assert().Equal(types.NewTrancheKey(0), bobEarlyKey0) + s.Assert().Equal(types.NewTrancheKey(1), bobEarlyKey1) + s.Assert().Equal(types.NewTrancheKey(2), aliceKey) + s.Assert().Equal(types.NewTrancheKey(10), bobLateKey) + + // all 11 orders are visible in the orderbook + s.assertLimitLiquidityAtTick("TokenA", 0, 11) + + // WHEN carol submits FILL_OR_KILL for 3 TokenB + // the swap iterator walks the KV store in key order: + // tk-...0 → tk-...1 → tk-...2 → … + // so the three oldest tranches are consumed + s.carolLimitSells("TokenB", -1, 3, types.LimitOrderType_FILL_OR_KILL) + + // THEN only 8 TokenA worth of maker liquidity remains + s.assertLimitLiquidityAtTick("TokenA", 0, 8) + + // bob's two orders (tk-...0 and tk-...1) were filled + s.bobWithdrawsLimitSell(bobEarlyKey0) + s.bobWithdrawsLimitSell(bobEarlyKey1) + s.assertBobBalances(0, 2) + + // alice's order (tk-...2) was the 3rd filled + s.aliceWithdrawsLimitSell(aliceKey) + s.assertAliceBalances(0, 1) + + // bob's late order (tk-...10) was NOT filled: it still sits in the active store with full + // maker reserves because it sorts after alice's key + bobLateTranche := s.App.DexKeeper.GetLimitOrderTranche(s.Ctx, &types.LimitOrderTrancheKey{ + TradePairId: defaultTradePairID1To0, + TickIndexTakerToMaker: 0, + TrancheKey: bobLateKey, + }) + s.Require().NotNil(bobLateTranche, "late-placed tranche must still exist in active store") + s.Assert().True( + bobLateTranche.ReservesMakerDenom.Equal(sdkmath.NewInt(1).Mul(denomMultiple)), + "late-placed order (tk-...10) must be unfilled while earlier orders remain", + ) +} diff --git a/x/dex/keeper/limit_order_tranche.go b/x/dex/keeper/limit_order_tranche.go index a64fc1b89..74e7442e5 100644 --- a/x/dex/keeper/limit_order_tranche.go +++ b/x/dex/keeper/limit_order_tranche.go @@ -2,7 +2,6 @@ package keeper import ( "encoding/binary" - "fmt" "time" "cosmossdk.io/math" @@ -208,10 +207,11 @@ func (k Keeper) GetAllLimitOrderTrancheAtIndex( return trancheList } -func (k Keeper) NewTrancheKey(ctx sdk.Context) string { +// NextTrancheKey increments the tranche count and returns the next tranche key. +func (k Keeper) NextTrancheKey(ctx sdk.Context) string { trancheCount := k.GetTrancheCount(ctx) k.IncrementTrancheCount(ctx) - return fmt.Sprintf("tk-%d", trancheCount) + return types.NewTrancheKey(trancheCount) } func (k Keeper) GetOrInitPlaceTranche(ctx sdk.Context, @@ -232,7 +232,7 @@ func (k Keeper) GetOrInitPlaceTranche(ctx sdk.Context, limitOrderTrancheKey := &types.LimitOrderTrancheKey{ TradePairId: tradePairID, TickIndexTakerToMaker: tickIndexTakerToMaker, - TrancheKey: k.NewTrancheKey(ctx), + TrancheKey: k.NextTrancheKey(ctx), } placeTranche, err = NewLimitOrderTranche(limitOrderTrancheKey, &JITGoodTilTime) ctx.EventManager().EmitEvents(types.GetEventsIncTotalOrders(tradePairID)) @@ -240,7 +240,7 @@ func (k Keeper) GetOrInitPlaceTranche(ctx sdk.Context, limitOrderTrancheKey := &types.LimitOrderTrancheKey{ TradePairId: tradePairID, TickIndexTakerToMaker: tickIndexTakerToMaker, - TrancheKey: k.NewTrancheKey(ctx), + TrancheKey: k.NextTrancheKey(ctx), } placeTranche, err = NewLimitOrderTranche(limitOrderTrancheKey, goodTil) ctx.EventManager().EmitEvents(types.GetEventsIncExpiringOrders(tradePairID)) @@ -250,7 +250,7 @@ func (k Keeper) GetOrInitPlaceTranche(ctx sdk.Context, limitOrderTrancheKey := &types.LimitOrderTrancheKey{ TradePairId: tradePairID, TickIndexTakerToMaker: tickIndexTakerToMaker, - TrancheKey: k.NewTrancheKey(ctx), + TrancheKey: k.NextTrancheKey(ctx), } placeTranche, err = NewLimitOrderTranche(limitOrderTrancheKey, nil) ctx.EventManager().EmitEvents(types.GetEventsIncTotalOrders(tradePairID)) diff --git a/x/dex/keeper/limit_order_tranche_test.go b/x/dex/keeper/limit_order_tranche_test.go index 6455717ea..b0fcfc034 100644 --- a/x/dex/keeper/limit_order_tranche_test.go +++ b/x/dex/keeper/limit_order_tranche_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "fmt" "testing" "cosmossdk.io/math" @@ -24,7 +23,7 @@ func createNLimitOrderTranches( items[i] = types.MustNewLimitOrderTranche( "TokenA", "TokenB", - keeper.NewTrancheKey(ctx), + keeper.NextTrancheKey(ctx), int64(i), math.ZeroInt(), math.ZeroInt(), @@ -47,7 +46,7 @@ func TestGetLimitOrderTranche(t *testing.T) { nullify.Fill(item), nullify.Fill(rst), ) - require.Equal(t, fmt.Sprintf("tk-%d", n), item.Key.TrancheKey) + require.Equal(t, types.NewTrancheKey(uint64(n)), item.Key.TrancheKey) } } diff --git a/x/dex/keeper/limit_order_tranche_user_test.go b/x/dex/keeper/limit_order_tranche_user_test.go index 8fb93ec0a..765115642 100644 --- a/x/dex/keeper/limit_order_tranche_user_test.go +++ b/x/dex/keeper/limit_order_tranche_user_test.go @@ -1,7 +1,6 @@ package keeper_test import ( - "fmt" "strconv" "testing" @@ -20,7 +19,7 @@ func createNLimitOrderTrancheUser(keeper *keeper.Keeper, ctx sdk.Context, n int) items := make([]*types.LimitOrderTrancheUser, n) for i := range items { val := &types.LimitOrderTrancheUser{ - TrancheKey: fmt.Sprintf("tk-%d", i), + TrancheKey: types.NewTrancheKey(uint64(i)), Address: strconv.Itoa(i), TradePairId: &types.TradePairID{MakerDenom: "TokenA", TakerDenom: "TokenB"}, TickIndexTakerToMaker: int64(i), diff --git a/x/dex/types/limit_order_tranche_key.go b/x/dex/types/limit_order_tranche_key.go index 6c38694c9..3f3448c70 100644 --- a/x/dex/types/limit_order_tranche_key.go +++ b/x/dex/types/limit_order_tranche_key.go @@ -1,6 +1,8 @@ package types import ( + fmt "fmt" + math_utils "github.com/neutron-org/neutron/v10/utils/math" ) @@ -42,3 +44,8 @@ func (p LimitOrderTrancheKey) MustPrice() (priceTakerToMaker math_utils.PrecDec) } return price } + +// NewTrancheKey returns a new tranche key based on the tranche index. +func NewTrancheKey(trancheIdx uint64) string { + return fmt.Sprintf("tk-%020d", trancheIdx) +} From 6562e91d318120dfc79088d649eea926da18588f Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Thu, 14 May 2026 14:00:14 +0300 Subject: [PATCH 02/14] add inactive LO tranche iterator getter to dex keeper --- x/dex/keeper/inactive_limit_order_tranche.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/x/dex/keeper/inactive_limit_order_tranche.go b/x/dex/keeper/inactive_limit_order_tranche.go index 97110ecdc..0d9bbc410 100644 --- a/x/dex/keeper/inactive_limit_order_tranche.go +++ b/x/dex/keeper/inactive_limit_order_tranche.go @@ -58,6 +58,12 @@ func (k Keeper) GetAllInactiveLimitOrderTranche(ctx sdk.Context) (list []*types. return list } +// GetInactiveLimitOrderTrancheIterator returns a store iterator over all inactive limit order tranches. +func (k Keeper) GetInactiveLimitOrderTrancheIterator(ctx sdk.Context) storetypes.Iterator { + store := prefix.NewStore(ctx.KVStore(k.storeKey), types.KeyPrefix(types.InactiveLimitOrderTrancheKeyPrefix)) + return storetypes.KVStorePrefixIterator(store, []byte{}) +} + // UpdateInactiveTranche handles the logic for all updates to InactiveLimitOrderTranches // It will delete an InactiveTranche if there is no remaining MakerReserves or TakerReserves func (k Keeper) UpdateInactiveTranche(sdkCtx sdk.Context, tranche *types.LimitOrderTranche) { From aa20deb966976b8663e1905c3585450b23f904cd Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Thu, 14 May 2026 14:01:23 +0300 Subject: [PATCH 03/14] draft version of tk-N -> tk-000...N upgrade handler --- app/upgrades/nextupgrade/constants.go | 21 ++ app/upgrades/nextupgrade/upgrades.go | 174 ++++++++++ app/upgrades/nextupgrade/upgrades_test.go | 387 ++++++++++++++++++++++ 3 files changed, 582 insertions(+) create mode 100644 app/upgrades/nextupgrade/constants.go create mode 100644 app/upgrades/nextupgrade/upgrades.go create mode 100644 app/upgrades/nextupgrade/upgrades_test.go diff --git a/app/upgrades/nextupgrade/constants.go b/app/upgrades/nextupgrade/constants.go new file mode 100644 index 000000000..3f567b8ce --- /dev/null +++ b/app/upgrades/nextupgrade/constants.go @@ -0,0 +1,21 @@ +package nextupgrade + +import ( + storetypes "cosmossdk.io/store/types" + + "github.com/neutron-org/neutron/v10/app/upgrades" +) + +const ( + // UpgradeName defines the on-chain upgrade name. + UpgradeName = "nextupgrade" +) + +var Upgrade = upgrades.Upgrade{ + UpgradeName: UpgradeName, + CreateUpgradeHandler: CreateUpgradeHandler, + StoreUpgrades: storetypes.StoreUpgrades{ + Added: []string{}, + Deleted: []string{}, + }, +} diff --git a/app/upgrades/nextupgrade/upgrades.go b/app/upgrades/nextupgrade/upgrades.go new file mode 100644 index 000000000..147fc4288 --- /dev/null +++ b/app/upgrades/nextupgrade/upgrades.go @@ -0,0 +1,174 @@ +package nextupgrade + +import ( + "context" + "fmt" + "strconv" + "strings" + + upgradetypes "cosmossdk.io/x/upgrade/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + "github.com/neutron-org/neutron/v10/app/upgrades" + dexkeeper "github.com/neutron-org/neutron/v10/x/dex/keeper" + dextypes "github.com/neutron-org/neutron/v10/x/dex/types" +) + +func CreateUpgradeHandler( + mm *module.Manager, + configurator module.Configurator, + keepers *upgrades.UpgradeKeepers, + _ upgrades.StoreKeys, + cdc codec.Codec, +) upgradetypes.UpgradeHandler { + return func(c context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { + ctx := sdk.UnwrapSDKContext(c) + + ctx.Logger().Info("Starting module migrations...") + + vm, err := mm.RunMigrations(ctx, configurator, vm) + if err != nil { + return vm, err + } + + ctx.Logger().Info("Reconstructing tranche keys...") + if err := ReconstructTrancheKeys(ctx, cdc, *keepers.DexKeeper); err != nil { + return vm, err + } + ctx.Logger().Info("Done") + + ctx.Logger().Info(fmt.Sprintf("Migration {%s} applied", UpgradeName)) + return vm, nil + } +} + +func ReconstructTrancheKeys(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) error { + if err := reconstructLoTranches(ctx, cdc, k); err != nil { + return fmt.Errorf("failed to reconstruct LO tranches: %w", err) + } + + if err := reconstructInactiveLoTranches(ctx, cdc, k); err != nil { + return fmt.Errorf("failed to reconstruct inactive LO tranches: %w", err) + } + + if err := reconstructLoTrancheUserLists(ctx, k); err != nil { + return fmt.Errorf("failed to reconstruct LO tranche user lists: %w", err) + } + + return nil +} + +func reconstructLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) error { + tickLiquidities := k.GetAllTickLiquidity(ctx) // there are only 600-ish entries, so getting all is fine + + loTrancheKeysToRemove := make([]dextypes.LimitOrderTrancheKey, 0) + loTranchesToUpdate := make([]dextypes.LimitOrderTranche, 0) + for _, tickLiquidity := range tickLiquidities { + if loTranche := tickLiquidity.GetLimitOrderTranche(); loTranche != nil { + if !strings.HasPrefix(loTranche.Key.TrancheKey, "tk-") { + continue + } + + loTrancheKeysToRemove = append(loTrancheKeysToRemove, *loTranche.Key) + + trancheIdxStr := strings.TrimPrefix(loTranche.Key.TrancheKey, "tk-") + trancheIdx, err := strconv.ParseUint(trancheIdxStr, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse tranche idx %s: %w", trancheIdxStr, err) + } + loTranche.Key.TrancheKey = dextypes.NewTrancheKey(trancheIdx) + loTranchesToUpdate = append(loTranchesToUpdate, *loTranche) + } + } + + for _, loTrancheKey := range loTrancheKeysToRemove { + k.RemoveLimitOrderTranche(ctx, &loTrancheKey) + } + for _, loTranche := range loTranchesToUpdate { + k.SetLimitOrderTranche(ctx, &loTranche) + } + + return nil +} + +func reconstructInactiveLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) error { + iter := k.GetInactiveLimitOrderTrancheIterator(ctx) + + inactiveKeysToRemove := make([]dextypes.LimitOrderTrancheKey, 0) + inactiveTranchesToUpdate := make([]dextypes.LimitOrderTranche, 0) + + for ; iter.Valid(); iter.Next() { + var tranche dextypes.LimitOrderTranche + cdc.MustUnmarshal(iter.Value(), &tranche) + + if !strings.HasPrefix(tranche.Key.TrancheKey, "tk-") { + continue + } + + inactiveKeysToRemove = append(inactiveKeysToRemove, *tranche.Key) + + trancheIdxStr := strings.TrimPrefix(tranche.Key.TrancheKey, "tk-") + trancheIdx, err := strconv.ParseUint(trancheIdxStr, 10, 64) + if err != nil { + iter.Close() //nolint:errcheck + return fmt.Errorf("failed to parse tranche idx %s: %w", trancheIdxStr, err) + } + tranche.Key.TrancheKey = dextypes.NewTrancheKey(trancheIdx) + inactiveTranchesToUpdate = append(inactiveTranchesToUpdate, tranche) + } + iter.Close() //nolint:errcheck + + for _, key := range inactiveKeysToRemove { + k.RemoveInactiveLimitOrderTranche(ctx, &key) + } + for i := range inactiveTranchesToUpdate { + k.SetInactiveLimitOrderTranche(ctx, &inactiveTranchesToUpdate[i]) + } + + return nil +} + +func reconstructLoTrancheUserLists(ctx sdk.Context, k dexkeeper.Keeper) error { + allUsers := k.GetAllLimitOrderTrancheUser(ctx) // there are only 300-ish entries, so getting all is fine + + // Each LimitOrderTrancheUser has its TrancheKey embedded in both the KV store key + // (address + trancheKey) and the serialised value. It is required to remove the old entry and + // write a new one under the updated key. + type userRemoveKey struct { + address string + trancheKey string + } + + keysToRemove := make([]userRemoveKey, 0) + usersToUpdate := make([]*dextypes.LimitOrderTrancheUser, 0) + + for _, user := range allUsers { + if !strings.HasPrefix(user.TrancheKey, "tk-") { + continue + } + + // Snapshot address + old key before mutation. + keysToRemove = append(keysToRemove, userRemoveKey{ + address: user.Address, + trancheKey: user.TrancheKey, + }) + + trancheIdxStr := strings.TrimPrefix(user.TrancheKey, "tk-") + trancheIdx, err := strconv.ParseUint(trancheIdxStr, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse tranche idx %s: %w", trancheIdxStr, err) + } + user.TrancheKey = dextypes.NewTrancheKey(trancheIdx) + usersToUpdate = append(usersToUpdate, user) + } + + for _, key := range keysToRemove { + k.RemoveLimitOrderTrancheUserByKey(ctx, key.trancheKey, key.address) + } + for _, user := range usersToUpdate { + k.SetLimitOrderTrancheUser(ctx, user) + } + + return nil +} diff --git a/app/upgrades/nextupgrade/upgrades_test.go b/app/upgrades/nextupgrade/upgrades_test.go new file mode 100644 index 000000000..51cf0a3d2 --- /dev/null +++ b/app/upgrades/nextupgrade/upgrades_test.go @@ -0,0 +1,387 @@ +package nextupgrade_test + +import ( + "testing" + + sdkmath "cosmossdk.io/math" + upgradetypes "cosmossdk.io/x/upgrade/types" + "github.com/stretchr/testify/require" + "github.com/stretchr/testify/suite" + + "github.com/neutron-org/neutron/v10/app/upgrades/nextupgrade" + "github.com/neutron-org/neutron/v10/testutil" + dextypes "github.com/neutron-org/neutron/v10/x/dex/types" +) + +type UpgradeTestSuite struct { + testutil.IBCConnectionTestSuite +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(UpgradeTestSuite)) +} + +func (suite *UpgradeTestSuite) SetupTest() { + suite.IBCConnectionTestSuite.SetupTest() +} + +func (suite *UpgradeTestSuite) TestUpgrade() { + app := suite.GetNeutronZoneApp(suite.ChainA) + ctx := suite.ChainA.GetContext().WithChainID("neutron-1") + t := suite.T() + + upgrade := upgradetypes.Plan{ + Name: nextupgrade.UpgradeName, + Info: "some text here", + Height: 100, + } + + require.NoError(t, app.UpgradeKeeper.ApplyUpgrade(ctx, upgrade)) +} + +// TestReconstructTrancheKeys verifies the tranche key migration from the plain-decimal +// "tk-N" format to the zero-padded "tk-%020d" format. It uses realistic mainnet entries: +// +// - tk-19993998 / tk-19940606: old decimal keys that must be rewritten. +// - 57mgzl47if5: original base-36 sortable key (no "tk-" prefix) that must be left untouched. +// - pool_reserves: a tick-liquidity entry that is not a limit order; must be untouched. +func (suite *UpgradeTestSuite) TestReconstructTrancheKeys() { + app := suite.GetNeutronZoneApp(suite.ChainA) + ctx := suite.ChainA.GetContext().WithChainID("neutron-1") + t := suite.T() + + // ── pre-upgrade state ──────────────────────────────────────────────────── + + // Two active limit-order tranches with old plain-decimal keys. + pairID1 := dextypes.MustNewTradePairID( + "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", + "factory/neutron1frc0p5czd9uaaymdkug2njz7dc7j65jxukp9apmt9260a8egujkspms2t2/udntrn", + ) + app.DexKeeper.SetLimitOrderTranche(ctx, &dextypes.LimitOrderTranche{ + Key: &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: "tk-19993998", + TickIndexTakerToMaker: -43028, + }, + }) + app.DexKeeper.SetLimitOrderTranche(ctx, &dextypes.LimitOrderTranche{ + Key: &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: "tk-19940606", + TickIndexTakerToMaker: -42321, + }, + }) + + // One tranche with the original base-36 sortable key (no "tk-" prefix). + // The migration must skip it and leave it unchanged. + pairID2 := dextypes.MustNewTradePairID( + "factory/neutron1dqd0wsqldr89m4d9trk2arv35twz7a5erjj6td/nick", + "factory/neutron1dqd0wsqldr89m4d9trk2arv35twz7a5erjj6td/jcp", + ) + app.DexKeeper.SetLimitOrderTranche(ctx, &dextypes.LimitOrderTranche{ + Key: &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID2, + TrancheKey: "57mgzl47if5", + TickIndexTakerToMaker: 46055, + }, + }) + + // One pool-reserves entry. It is stored under the same TickLiquidity prefix but is + // not a limit order; the migration must not touch it. + pairID3 := dextypes.MustNewTradePairID( + "ibc/E2A000FD3EDD91C9429B473995CE2C7C555BCC8CFC1D0A3D02F514392B7A80E8", + "factory/neutron17sp75wng9vl2hu3sf4ky86d7smmk3wle9gkts2gmedn9x4ut3xcqa5xp34/maxbtc", + ) + app.DexKeeper.SetPoolReserves(ctx, &dextypes.PoolReserves{ + Key: &dextypes.PoolReservesKey{ + TradePairId: pairID3, + TickIndexTakerToMaker: 187, + Fee: 102, + }, + }) + + require.Len(t, app.DexKeeper.GetAllTickLiquidity(ctx), 4, "pre-upgrade: 4 tick liquidity entries") + + // ── run migration ──────────────────────────────────────────────────────── + + require.NoError(t, nextupgrade.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) + + // ── post-upgrade assertions ────────────────────────────────────────────── + + // Total entry count must be unchanged. + require.Len(t, app.DexKeeper.GetAllTickLiquidity(ctx), 4, "post-upgrade: entry count must not change") + + // --- tk-19993998 → tk-00000000000019993998 --- + + migratedKey1 := dextypes.NewTrancheKey(19993998) // "tk-00000000000019993998" + + migratedTranche1 := app.DexKeeper.GetLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: migratedKey1, + TickIndexTakerToMaker: -43028, + }) + require.NotNil(t, migratedTranche1, "migrated tranche tk-19993998 must exist under new key") + require.Equal(t, migratedKey1, migratedTranche1.Key.TrancheKey) + + require.Nil(t, + app.DexKeeper.GetLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: "tk-19993998", + TickIndexTakerToMaker: -43028, + }), + "old key tk-19993998 must no longer exist", + ) + + // --- tk-19940606 → tk-00000000000019940606 --- + + migratedKey2 := dextypes.NewTrancheKey(19940606) // "tk-00000000000019940606" + + migratedTranche2 := app.DexKeeper.GetLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: migratedKey2, + TickIndexTakerToMaker: -42321, + }) + require.NotNil(t, migratedTranche2, "migrated tranche tk-19940606 must exist under new key") + require.Equal(t, migratedKey2, migratedTranche2.Key.TrancheKey) + + require.Nil(t, + app.DexKeeper.GetLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: "tk-19940606", + TickIndexTakerToMaker: -42321, + }), + "old key tk-19940606 must no longer exist", + ) + + // --- 57mgzl47if5 (base-36 key, no "tk-" prefix) must be unchanged --- + + untouchedTranche := app.DexKeeper.GetLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID2, + TrancheKey: "57mgzl47if5", + TickIndexTakerToMaker: 46055, + }) + require.NotNil(t, untouchedTranche, "base-36 tranche must still exist") + require.Equal(t, "57mgzl47if5", untouchedTranche.Key.TrancheKey, "base-36 key must not be rewritten") + + // --- pool_reserves must be untouched --- + + poolReserves, found := app.DexKeeper.GetPoolReserves(ctx, &dextypes.PoolReservesKey{ + TradePairId: pairID3, + TickIndexTakerToMaker: 187, + Fee: 102, + }) + require.True(t, found, "pool reserves must still be present") + require.NotNil(t, poolReserves) +} + +// TestReconstructInactiveLoTranches verifies that inactive limit order tranches stored +// under the old plain-decimal "tk-N" key format are rewritten to the zero-padded +// "tk-%020d" format, while entries with the original base-36 sortable key are left alone. +func (suite *UpgradeTestSuite) TestReconstructInactiveLoTranches() { + app := suite.GetNeutronZoneApp(suite.ChainA) + ctx := suite.ChainA.GetContext().WithChainID("neutron-1") + t := suite.T() + + // ── pre-upgrade state ──────────────────────────────────────────────────── + + // One inactive tranche with the original base-36 sortable key (no "tk-" prefix). + pairID1 := dextypes.MustNewTradePairID( + "factory/neutron10h9stc5v6ntgeygf5xf945njqq5h32r54rf7kf/nick", + "factory/neutron1dqd0wsqldr89m4d9trk2arv35twz7a5erjj6td/jcp", + ) + app.DexKeeper.SetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTranche{ + Key: &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: "57m0a14awvr", + TickIndexTakerToMaker: 0, + }, + }) + + // Two inactive tranches with old plain-decimal keys. + pairID2 := dextypes.MustNewTradePairID( + "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", + "factory/neutron17sp75wng9vl2hu3sf4ky86d7smmk3wle9gkts2gmedn9x4ut3xcqa5xp34/maxbtc", + ) + app.DexKeeper.SetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTranche{ + Key: &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID2, + TrancheKey: "tk-18498162", + TickIndexTakerToMaker: 67831, + }, + }) + app.DexKeeper.SetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTranche{ + Key: &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID2, + TrancheKey: "tk-18291522", + TickIndexTakerToMaker: 67961, + }, + }) + + require.Len(t, app.DexKeeper.GetAllInactiveLimitOrderTranche(ctx), 3, "pre-upgrade: 3 inactive tranches") + + // ── run migration ──────────────────────────────────────────────────────── + + require.NoError(t, nextupgrade.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) + + // ── post-upgrade assertions ────────────────────────────────────────────── + + require.Len(t, app.DexKeeper.GetAllInactiveLimitOrderTranche(ctx), 3, "post-upgrade: entry count must not change") + + // --- tk-18498162 → tk-00000000000018498162 --- + + migratedKey1 := dextypes.NewTrancheKey(18498162) + + migratedTranche1, found := app.DexKeeper.GetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID2, + TrancheKey: migratedKey1, + TickIndexTakerToMaker: 67831, + }) + require.True(t, found, "migrated tranche tk-18498162 must exist under new key") + require.Equal(t, migratedKey1, migratedTranche1.Key.TrancheKey) + + _, found = app.DexKeeper.GetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID2, + TrancheKey: "tk-18498162", + TickIndexTakerToMaker: 67831, + }) + require.False(t, found, "old key tk-18498162 must no longer exist") + + // --- tk-18291522 → tk-00000000000018291522 --- + + migratedKey2 := dextypes.NewTrancheKey(18291522) + + migratedTranche2, found := app.DexKeeper.GetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID2, + TrancheKey: migratedKey2, + TickIndexTakerToMaker: 67961, + }) + require.True(t, found, "migrated tranche tk-18291522 must exist under new key") + require.Equal(t, migratedKey2, migratedTranche2.Key.TrancheKey) + + _, found = app.DexKeeper.GetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID2, + TrancheKey: "tk-18291522", + TickIndexTakerToMaker: 67961, + }) + require.False(t, found, "old key tk-18291522 must no longer exist") + + // --- 57m0a14awvr (base-36 key) must be unchanged --- + + untouched, found := app.DexKeeper.GetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: "57m0a14awvr", + TickIndexTakerToMaker: 0, + }) + require.True(t, found, "base-36 tranche must still exist") + require.Equal(t, "57m0a14awvr", untouched.Key.TrancheKey, "base-36 key must not be rewritten") +} + +// TestReconstructLoTrancheUserLists verifies that LimitOrderTrancheUser entries stored under +// the old plain-decimal "tk-N" key are rewritten to the zero-padded "tk-%020d" format. +// The TrancheKey appears in both the KV store composite key (address + trancheKey) and in +// the serialised protobuf value, so both must be updated correctly. +// Entries with the original base-36 sortable key (no "tk-" prefix) must remain unchanged. +func (suite *UpgradeTestSuite) TestReconstructLoTrancheUserLists() { + app := suite.GetNeutronZoneApp(suite.ChainA) + ctx := suite.ChainA.GetContext().WithChainID("neutron-1") + t := suite.T() + + // ── pre-upgrade state ──────────────────────────────────────────────────── + + pairID1 := dextypes.MustNewTradePairID( + "ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7", + "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", + ) + // base-36 key — must NOT be migrated + app.DexKeeper.SetLimitOrderTrancheUser(ctx, &dextypes.LimitOrderTrancheUser{ + TradePairId: pairID1, + TickIndexTakerToMaker: -16365, + TrancheKey: "5atwxq41kck", + Address: "neutron12c20g3kvrvmqj3w5ep6vept6f77lunxyrrq44w", + SharesOwned: sdkmath.NewInt(5100000), + SharesWithdrawn: sdkmath.ZeroInt(), + OrderType: dextypes.LimitOrderType_GOOD_TIL_CANCELLED, + }) + + pairID2 := dextypes.MustNewTradePairID( + "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", + "factory/neutron1r5qx58l3xx2y8gzjtkqjndjgx69mktmapl45vns0pa73z0zpn7fqgltnll/TAB", + ) + // old plain-decimal key — must be migrated + app.DexKeeper.SetLimitOrderTrancheUser(ctx, &dextypes.LimitOrderTrancheUser{ + TradePairId: pairID2, + TickIndexTakerToMaker: -12041, + TrancheKey: "tk-3819855", + Address: "neutron12kmcmwljx7yplase4cjxqhry58fwvp5ljqu25a", + SharesOwned: sdkmath.NewInt(367803926), + SharesWithdrawn: sdkmath.ZeroInt(), + OrderType: dextypes.LimitOrderType_GOOD_TIL_CANCELLED, + }) + app.DexKeeper.SetLimitOrderTrancheUser(ctx, &dextypes.LimitOrderTrancheUser{ + TradePairId: pairID2, + TickIndexTakerToMaker: -6931, + TrancheKey: "tk-4079303", + Address: "neutron12nrq3myjsfltjh5x8w8xcvxr8wpkef0vrpvxu4", + SharesOwned: sdkmath.NewInt(36000000), + SharesWithdrawn: sdkmath.ZeroInt(), + OrderType: dextypes.LimitOrderType_GOOD_TIL_CANCELLED, + }) + + require.Len(t, app.DexKeeper.GetAllLimitOrderTrancheUser(ctx), 3, "pre-upgrade: 3 tranche user entries") + + // ── run migration ──────────────────────────────────────────────────────── + + require.NoError(t, nextupgrade.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) + + // ── post-upgrade assertions ────────────────────────────────────────────── + + require.Len(t, app.DexKeeper.GetAllLimitOrderTrancheUser(ctx), 3, "post-upgrade: entry count must not change") + + // --- tk-3819855 → tk-00000000000003819855 --- + + migratedKey1 := dextypes.NewTrancheKey(3819855) + + migratedUser1, found := app.DexKeeper.GetLimitOrderTrancheUser( + ctx, + "neutron12kmcmwljx7yplase4cjxqhry58fwvp5ljqu25a", + migratedKey1, + ) + require.True(t, found, "migrated user tk-3819855 must exist under new key") + require.Equal(t, migratedKey1, migratedUser1.TrancheKey, "TrancheKey in value must also be updated") + + _, found = app.DexKeeper.GetLimitOrderTrancheUser( + ctx, + "neutron12kmcmwljx7yplase4cjxqhry58fwvp5ljqu25a", + "tk-3819855", + ) + require.False(t, found, "old key tk-3819855 must no longer exist") + + // --- tk-4079303 → tk-00000000000004079303 --- + + migratedKey2 := dextypes.NewTrancheKey(4079303) + + migratedUser2, found := app.DexKeeper.GetLimitOrderTrancheUser( + ctx, + "neutron12nrq3myjsfltjh5x8w8xcvxr8wpkef0vrpvxu4", + migratedKey2, + ) + require.True(t, found, "migrated user tk-4079303 must exist under new key") + require.Equal(t, migratedKey2, migratedUser2.TrancheKey, "TrancheKey in value must also be updated") + + _, found = app.DexKeeper.GetLimitOrderTrancheUser( + ctx, + "neutron12nrq3myjsfltjh5x8w8xcvxr8wpkef0vrpvxu4", + "tk-4079303", + ) + require.False(t, found, "old key tk-4079303 must no longer exist") + + // --- 5atwxq41kck (base-36 key) must be unchanged --- + + untouched, found := app.DexKeeper.GetLimitOrderTrancheUser( + ctx, + "neutron12c20g3kvrvmqj3w5ep6vept6f77lunxyrrq44w", + "5atwxq41kck", + ) + require.True(t, found, "base-36 tranche user must still exist") + require.Equal(t, "5atwxq41kck", untouched.TrancheKey, "base-36 key must not be rewritten") +} From 4c166c990450ab8c095a56f46d93960cbadf65f5 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Thu, 14 May 2026 14:11:48 +0300 Subject: [PATCH 04/14] add nextupgrade to the list of upgrades --- app/app.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/app.go b/app/app.go index 895a04c5f..c7e7c39d0 100644 --- a/app/app.go +++ b/app/app.go @@ -10,6 +10,7 @@ import ( "path/filepath" "time" + "github.com/neutron-org/neutron/v10/app/upgrades/nextupgrade" v10_0_0 "github.com/neutron-org/neutron/v10/app/upgrades/v10.0.0" v10_1_0 "github.com/neutron-org/neutron/v10/app/upgrades/v10.1.0" v10_2_0 "github.com/neutron-org/neutron/v10/app/upgrades/v10.2.0" @@ -256,6 +257,7 @@ var ( v10_1_0.Upgrade, v10_2_0.Upgrade, v10_3_0.Upgrade, + nextupgrade.Upgrade, } // DefaultNodeHome default home directories for the application daemon From f56294fc2b6cfdbc099358d1e2f34d62fc0701e8 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Fri, 15 May 2026 15:06:53 +0300 Subject: [PATCH 05/14] add tk migration logging and sanity checks --- app/upgrades/nextupgrade/upgrades.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/app/upgrades/nextupgrade/upgrades.go b/app/upgrades/nextupgrade/upgrades.go index 147fc4288..ff7e6e74e 100644 --- a/app/upgrades/nextupgrade/upgrades.go +++ b/app/upgrades/nextupgrade/upgrades.go @@ -82,12 +82,17 @@ func reconstructLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) } } + if len(loTrancheKeysToRemove) != len(loTranchesToUpdate) { + return fmt.Errorf("mismatch in LO tranches to remove and update counts: %d != %d", len(loTrancheKeysToRemove), len(loTranchesToUpdate)) + } + for _, loTrancheKey := range loTrancheKeysToRemove { k.RemoveLimitOrderTranche(ctx, &loTrancheKey) } for _, loTranche := range loTranchesToUpdate { k.SetLimitOrderTranche(ctx, &loTranche) } + ctx.Logger().Info("LO tranche keys reconstructed", "count", len(loTranchesToUpdate)) return nil } @@ -97,7 +102,6 @@ func reconstructInactiveLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper inactiveKeysToRemove := make([]dextypes.LimitOrderTrancheKey, 0) inactiveTranchesToUpdate := make([]dextypes.LimitOrderTranche, 0) - for ; iter.Valid(); iter.Next() { var tranche dextypes.LimitOrderTranche cdc.MustUnmarshal(iter.Value(), &tranche) @@ -119,12 +123,17 @@ func reconstructInactiveLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper } iter.Close() //nolint:errcheck + if len(inactiveKeysToRemove) != len(inactiveTranchesToUpdate) { + return fmt.Errorf("mismatch in inactive LO tranches to remove and update counts: %d != %d", len(inactiveKeysToRemove), len(inactiveTranchesToUpdate)) + } + for _, key := range inactiveKeysToRemove { k.RemoveInactiveLimitOrderTranche(ctx, &key) } for i := range inactiveTranchesToUpdate { k.SetInactiveLimitOrderTranche(ctx, &inactiveTranchesToUpdate[i]) } + ctx.Logger().Info("inactive LO tranche keys reconstructed", "count", len(inactiveTranchesToUpdate)) return nil } @@ -142,13 +151,11 @@ func reconstructLoTrancheUserLists(ctx sdk.Context, k dexkeeper.Keeper) error { keysToRemove := make([]userRemoveKey, 0) usersToUpdate := make([]*dextypes.LimitOrderTrancheUser, 0) - for _, user := range allUsers { if !strings.HasPrefix(user.TrancheKey, "tk-") { continue } - // Snapshot address + old key before mutation. keysToRemove = append(keysToRemove, userRemoveKey{ address: user.Address, trancheKey: user.TrancheKey, @@ -163,12 +170,17 @@ func reconstructLoTrancheUserLists(ctx sdk.Context, k dexkeeper.Keeper) error { usersToUpdate = append(usersToUpdate, user) } + if len(keysToRemove) != len(usersToUpdate) { + return fmt.Errorf("mismatch in LO tranche user keys to remove and update counts: %d != %d", len(keysToRemove), len(usersToUpdate)) + } + for _, key := range keysToRemove { k.RemoveLimitOrderTrancheUserByKey(ctx, key.trancheKey, key.address) } for _, user := range usersToUpdate { k.SetLimitOrderTrancheUser(ctx, user) } + ctx.Logger().Info("LO tranche user keys reconstructed", "count", len(usersToUpdate)) return nil } From bd85f8161e6e79f816dedcf90bf8472a24f801e4 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Sat, 16 May 2026 18:32:50 +0300 Subject: [PATCH 06/14] simpliofy reconstructLoTrancheUserLists --- app/upgrades/nextupgrade/upgrades.go | 35 ++++++++--------------- app/upgrades/nextupgrade/upgrades_test.go | 6 ++-- 2 files changed, 14 insertions(+), 27 deletions(-) diff --git a/app/upgrades/nextupgrade/upgrades.go b/app/upgrades/nextupgrade/upgrades.go index ff7e6e74e..775c3b5ca 100644 --- a/app/upgrades/nextupgrade/upgrades.go +++ b/app/upgrades/nextupgrade/upgrades.go @@ -98,7 +98,7 @@ func reconstructLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) } func reconstructInactiveLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) error { - iter := k.GetInactiveLimitOrderTrancheIterator(ctx) + iter := k.GetInactiveLimitOrderTrancheIterator(ctx) // there are more than 400k entries -> iterating inactiveKeysToRemove := make([]dextypes.LimitOrderTrancheKey, 0) inactiveTranchesToUpdate := make([]dextypes.LimitOrderTranche, 0) @@ -130,8 +130,8 @@ func reconstructInactiveLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper for _, key := range inactiveKeysToRemove { k.RemoveInactiveLimitOrderTranche(ctx, &key) } - for i := range inactiveTranchesToUpdate { - k.SetInactiveLimitOrderTranche(ctx, &inactiveTranchesToUpdate[i]) + for _, tranche := range inactiveTranchesToUpdate { + k.SetInactiveLimitOrderTranche(ctx, &tranche) } ctx.Logger().Info("inactive LO tranche keys reconstructed", "count", len(inactiveTranchesToUpdate)) @@ -141,25 +141,14 @@ func reconstructInactiveLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper func reconstructLoTrancheUserLists(ctx sdk.Context, k dexkeeper.Keeper) error { allUsers := k.GetAllLimitOrderTrancheUser(ctx) // there are only 300-ish entries, so getting all is fine - // Each LimitOrderTrancheUser has its TrancheKey embedded in both the KV store key - // (address + trancheKey) and the serialised value. It is required to remove the old entry and - // write a new one under the updated key. - type userRemoveKey struct { - address string - trancheKey string - } - - keysToRemove := make([]userRemoveKey, 0) - usersToUpdate := make([]*dextypes.LimitOrderTrancheUser, 0) + usersToRemove := make([]dextypes.LimitOrderTrancheUser, 0) + usersToUpdate := make([]dextypes.LimitOrderTrancheUser, 0) for _, user := range allUsers { if !strings.HasPrefix(user.TrancheKey, "tk-") { continue } - keysToRemove = append(keysToRemove, userRemoveKey{ - address: user.Address, - trancheKey: user.TrancheKey, - }) + usersToRemove = append(usersToRemove, *user) trancheIdxStr := strings.TrimPrefix(user.TrancheKey, "tk-") trancheIdx, err := strconv.ParseUint(trancheIdxStr, 10, 64) @@ -167,18 +156,18 @@ func reconstructLoTrancheUserLists(ctx sdk.Context, k dexkeeper.Keeper) error { return fmt.Errorf("failed to parse tranche idx %s: %w", trancheIdxStr, err) } user.TrancheKey = dextypes.NewTrancheKey(trancheIdx) - usersToUpdate = append(usersToUpdate, user) + usersToUpdate = append(usersToUpdate, *user) } - if len(keysToRemove) != len(usersToUpdate) { - return fmt.Errorf("mismatch in LO tranche user keys to remove and update counts: %d != %d", len(keysToRemove), len(usersToUpdate)) + if len(usersToRemove) != len(usersToUpdate) { + return fmt.Errorf("mismatch in LO tranche user keys to remove and update counts: %d != %d", len(usersToRemove), len(usersToUpdate)) } - for _, key := range keysToRemove { - k.RemoveLimitOrderTrancheUserByKey(ctx, key.trancheKey, key.address) + for _, user := range usersToRemove { + k.RemoveLimitOrderTrancheUser(ctx, &user) } for _, user := range usersToUpdate { - k.SetLimitOrderTrancheUser(ctx, user) + k.SetLimitOrderTrancheUser(ctx, &user) } ctx.Logger().Info("LO tranche user keys reconstructed", "count", len(usersToUpdate)) diff --git a/app/upgrades/nextupgrade/upgrades_test.go b/app/upgrades/nextupgrade/upgrades_test.go index 51cf0a3d2..bb2b7b731 100644 --- a/app/upgrades/nextupgrade/upgrades_test.go +++ b/app/upgrades/nextupgrade/upgrades_test.go @@ -278,8 +278,6 @@ func (suite *UpgradeTestSuite) TestReconstructInactiveLoTranches() { // TestReconstructLoTrancheUserLists verifies that LimitOrderTrancheUser entries stored under // the old plain-decimal "tk-N" key are rewritten to the zero-padded "tk-%020d" format. -// The TrancheKey appears in both the KV store composite key (address + trancheKey) and in -// the serialised protobuf value, so both must be updated correctly. // Entries with the original base-36 sortable key (no "tk-" prefix) must remain unchanged. func (suite *UpgradeTestSuite) TestReconstructLoTrancheUserLists() { app := suite.GetNeutronZoneApp(suite.ChainA) @@ -347,7 +345,7 @@ func (suite *UpgradeTestSuite) TestReconstructLoTrancheUserLists() { migratedKey1, ) require.True(t, found, "migrated user tk-3819855 must exist under new key") - require.Equal(t, migratedKey1, migratedUser1.TrancheKey, "TrancheKey in value must also be updated") + require.Equal(t, migratedKey1, migratedUser1.TrancheKey) _, found = app.DexKeeper.GetLimitOrderTrancheUser( ctx, @@ -366,7 +364,7 @@ func (suite *UpgradeTestSuite) TestReconstructLoTrancheUserLists() { migratedKey2, ) require.True(t, found, "migrated user tk-4079303 must exist under new key") - require.Equal(t, migratedKey2, migratedUser2.TrancheKey, "TrancheKey in value must also be updated") + require.Equal(t, migratedKey2, migratedUser2.TrancheKey) _, found = app.DexKeeper.GetLimitOrderTrancheUser( ctx, From 539a370be71d857b0abff25efa7700e6eb3ddc7a Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Mon, 18 May 2026 14:44:44 +0300 Subject: [PATCH 07/14] add storage migration for LO expirations --- app/upgrades/nextupgrade/upgrades.go | 45 +++++++++ app/upgrades/nextupgrade/upgrades_test.go | 117 +++++++++++++++++++++- 2 files changed, 160 insertions(+), 2 deletions(-) diff --git a/app/upgrades/nextupgrade/upgrades.go b/app/upgrades/nextupgrade/upgrades.go index 775c3b5ca..1393e0c03 100644 --- a/app/upgrades/nextupgrade/upgrades.go +++ b/app/upgrades/nextupgrade/upgrades.go @@ -44,6 +44,10 @@ func CreateUpgradeHandler( } func ReconstructTrancheKeys(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) error { + if err := reconstructLoExpirations(ctx, k); err != nil { + return fmt.Errorf("failed to reconstruct LO expirations: %w", err) + } + if err := reconstructLoTranches(ctx, cdc, k); err != nil { return fmt.Errorf("failed to reconstruct LO tranches: %w", err) } @@ -59,6 +63,47 @@ func ReconstructTrancheKeys(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper return nil } +func reconstructLoExpirations(ctx sdk.Context, k dexkeeper.Keeper) error { + allExpirations := k.GetAllLimitOrderExpiration(ctx) // total count varies but is expected to be small or even 0 + + expirationsToRemove := make([]dextypes.LimitOrderExpiration, 0) + expirationsToUpdate := make([]dextypes.LimitOrderExpiration, 0) + for _, expiration := range allExpirations { + tranche, found := k.GetLimitOrderTrancheByKey(ctx, expiration.TrancheRef) + if !found { + return fmt.Errorf("limit order tranche not found for expiration.TrancheRef %s", expiration.TrancheRef) + } + + if !strings.HasPrefix(tranche.Key.TrancheKey, "tk-") { + continue + } + + expirationsToRemove = append(expirationsToRemove, *expiration) + + trancheIdxStr := strings.TrimPrefix(tranche.Key.TrancheKey, "tk-") + trancheIdx, err := strconv.ParseUint(trancheIdxStr, 10, 64) + if err != nil { + return fmt.Errorf("failed to parse tranche idx %s: %w", trancheIdxStr, err) + } + tranche.Key.TrancheKey = dextypes.NewTrancheKey(trancheIdx) + expirationsToUpdate = append(expirationsToUpdate, *dexkeeper.NewLimitOrderExpiration(tranche)) + } + + if len(expirationsToRemove) != len(expirationsToUpdate) { + return fmt.Errorf("mismatch in LO expirations to remove and update counts: %d != %d", len(expirationsToRemove), len(expirationsToUpdate)) + } + + for _, expiration := range expirationsToRemove { + k.RemoveLimitOrderExpiration(ctx, expiration.ExpirationTime, expiration.TrancheRef) + } + for _, expiration := range expirationsToUpdate { + k.SetLimitOrderExpiration(ctx, &expiration) + } + ctx.Logger().Info("LO expiration keys reconstructed", "count", len(expirationsToUpdate)) + + return nil +} + func reconstructLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) error { tickLiquidities := k.GetAllTickLiquidity(ctx) // there are only 600-ish entries, so getting all is fine diff --git a/app/upgrades/nextupgrade/upgrades_test.go b/app/upgrades/nextupgrade/upgrades_test.go index bb2b7b731..db9edc4b2 100644 --- a/app/upgrades/nextupgrade/upgrades_test.go +++ b/app/upgrades/nextupgrade/upgrades_test.go @@ -2,6 +2,7 @@ package nextupgrade_test import ( "testing" + "time" sdkmath "cosmossdk.io/math" upgradetypes "cosmossdk.io/x/upgrade/types" @@ -39,13 +40,125 @@ func (suite *UpgradeTestSuite) TestUpgrade() { require.NoError(t, app.UpgradeKeeper.ApplyUpgrade(ctx, upgrade)) } -// TestReconstructTrancheKeys verifies the tranche key migration from the plain-decimal +// TestReconstructLoExpirations verifies the LimitOrderExpiration migration. +// +// A LimitOrderExpiration stores a TrancheRef = tranche.Key.KeyMarshal(), which embeds the +// TrancheKey string as part of its bytes. When a tranche key is rewritten from the old +// plain-decimal "tk-N" format to the zero-padded "tk-%020d" format, the corresponding +// expiration entry must be removed from its old store key and re-inserted under the new one. +// +// Entries pointing to a base-36 tranche key (no "tk-" prefix) must be left untouched. +func (suite *UpgradeTestSuite) TestReconstructLoExpirations() { + app := suite.GetNeutronZoneApp(suite.ChainA) + ctx := suite.ChainA.GetContext().WithChainID("neutron-1") + t := suite.T() + + expTime1 := time.Now().UTC().Add(time.Second * 111) + expTime2 := time.Now().UTC().Add(time.Second * 222) + expTime3 := time.Now().UTC().Add(time.Second * 333) + + // ── pre-upgrade state ──────────────────────────────────────────────────── + + // Two active limit-order tranches with old plain-decimal keys and different ExpirationTime. + pairID1 := dextypes.MustNewTradePairID( + "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", + "factory/neutron1frc0p5czd9uaaymdkug2njz7dc7j65jxukp9apmt9260a8egujkspms2t2/udntrn", + ) + oldKey1 := &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: "tk-19993998", + TickIndexTakerToMaker: -43028, + } + app.DexKeeper.SetLimitOrderTranche(ctx, &dextypes.LimitOrderTranche{ + Key: oldKey1, + ExpirationTime: &expTime1, + }) + app.DexKeeper.SetLimitOrderExpiration(ctx, &dextypes.LimitOrderExpiration{ + ExpirationTime: expTime1, + TrancheRef: oldKey1.KeyMarshal(), + }) + + oldKey2 := &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: "tk-19940606", + TickIndexTakerToMaker: -42321, + } + app.DexKeeper.SetLimitOrderTranche(ctx, &dextypes.LimitOrderTranche{ + Key: oldKey2, + ExpirationTime: &expTime2, + }) + app.DexKeeper.SetLimitOrderExpiration(ctx, &dextypes.LimitOrderExpiration{ + ExpirationTime: expTime2, + TrancheRef: oldKey2.KeyMarshal(), + }) + + // One tranche with the original base-36 sortable key (no "tk-" prefix). + // Its expiration must not be touched. + pairID2 := dextypes.MustNewTradePairID( + "factory/neutron1dqd0wsqldr89m4d9trk2arv35twz7a5erjj6td/nick", + "factory/neutron1dqd0wsqldr89m4d9trk2arv35twz7a5erjj6td/jcp", + ) + base36Key := &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID2, + TrancheKey: "57mgzl47if5", + TickIndexTakerToMaker: 46055, + } + app.DexKeeper.SetLimitOrderTranche(ctx, &dextypes.LimitOrderTranche{ + Key: base36Key, + ExpirationTime: &expTime3, + }) + app.DexKeeper.SetLimitOrderExpiration(ctx, &dextypes.LimitOrderExpiration{ + ExpirationTime: expTime3, + TrancheRef: base36Key.KeyMarshal(), + }) + + require.Len(t, app.DexKeeper.GetAllLimitOrderExpiration(ctx), 3, "pre-upgrade: 3 expirations") + + // ── run migration ──────────────────────────────────────────────────────── + + require.NoError(t, nextupgrade.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) + + // ── post-upgrade assertions ────────────────────────────────────────────── + + require.Len(t, app.DexKeeper.GetAllLimitOrderExpiration(ctx), 3, "post-upgrade: expiration count must not change") + + // --- tk-19993998 expiration → new TrancheRef under tk-00000000000019993998 --- + + newKey1 := &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: dextypes.NewTrancheKey(19993998), + TickIndexTakerToMaker: -43028, + } + _, found := app.DexKeeper.GetLimitOrderExpiration(ctx, expTime1, newKey1.KeyMarshal()) + require.True(t, found, "new expiration (tk-00000000000019993998) must exist") + _, found = app.DexKeeper.GetLimitOrderExpiration(ctx, expTime1, oldKey1.KeyMarshal()) + require.False(t, found, "old expiration (tk-19993998) must be removed") + + // --- tk-19940606 expiration → new TrancheRef under tk-00000000000019940606 --- + + newKey2 := &dextypes.LimitOrderTrancheKey{ + TradePairId: pairID1, + TrancheKey: dextypes.NewTrancheKey(19940606), + TickIndexTakerToMaker: -42321, + } + _, found = app.DexKeeper.GetLimitOrderExpiration(ctx, expTime2, newKey2.KeyMarshal()) + require.True(t, found, "new expiration (tk-00000000000019940606) must exist") + _, found = app.DexKeeper.GetLimitOrderExpiration(ctx, expTime2, oldKey2.KeyMarshal()) + require.False(t, found, "old expiration (tk-19940606) must be removed") + + // --- base-36 expiration must be unchanged --- + + _, found = app.DexKeeper.GetLimitOrderExpiration(ctx, expTime3, base36Key.KeyMarshal()) + require.True(t, found, "base-36 expiration must still exist") +} + +// TestReconstructLoTrancheKeys verifies the tranche key migration from the plain-decimal // "tk-N" format to the zero-padded "tk-%020d" format. It uses realistic mainnet entries: // // - tk-19993998 / tk-19940606: old decimal keys that must be rewritten. // - 57mgzl47if5: original base-36 sortable key (no "tk-" prefix) that must be left untouched. // - pool_reserves: a tick-liquidity entry that is not a limit order; must be untouched. -func (suite *UpgradeTestSuite) TestReconstructTrancheKeys() { +func (suite *UpgradeTestSuite) TestReconstructLoTrancheKeys() { app := suite.GetNeutronZoneApp(suite.ChainA) ctx := suite.ChainA.GetContext().WithChainID("neutron-1") t := suite.T() From b08bebe8237e117c9493a2637115fb4501421c13 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Tue, 19 May 2026 18:16:40 +0300 Subject: [PATCH 08/14] fix linter --- app/upgrades/nextupgrade/upgrades.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/upgrades/nextupgrade/upgrades.go b/app/upgrades/nextupgrade/upgrades.go index dbeb8a05f..194b02909 100644 --- a/app/upgrades/nextupgrade/upgrades.go +++ b/app/upgrades/nextupgrade/upgrades.go @@ -48,7 +48,7 @@ func ReconstructTrancheKeys(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper return fmt.Errorf("failed to reconstruct LO expirations: %w", err) } - if err := reconstructLoTranches(ctx, cdc, k); err != nil { + if err := reconstructLoTranches(ctx, k); err != nil { return fmt.Errorf("failed to reconstruct LO tranches: %w", err) } @@ -104,7 +104,7 @@ func reconstructLoExpirations(ctx sdk.Context, k dexkeeper.Keeper) error { return nil } -func reconstructLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) error { +func reconstructLoTranches(ctx sdk.Context, k dexkeeper.Keeper) error { tickLiquidities := k.GetAllTickLiquidity(ctx) // there are only 600-ish entries, so getting all is fine loTrancheKeysToRemove := make([]dextypes.LimitOrderTrancheKey, 0) @@ -160,13 +160,13 @@ func reconstructInactiveLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper trancheIdxStr := strings.TrimPrefix(tranche.Key.TrancheKey, "tk-") trancheIdx, err := strconv.ParseUint(trancheIdxStr, 10, 64) if err != nil { - iter.Close() //nolint:errcheck + iter.Close() //nolint:errcheck,gosec return fmt.Errorf("failed to parse tranche idx %s: %w", trancheIdxStr, err) } tranche.Key.TrancheKey = dextypes.NewTrancheKey(trancheIdx) inactiveTranchesToUpdate = append(inactiveTranchesToUpdate, tranche) } - iter.Close() //nolint:errcheck + iter.Close() //nolint:errcheck,gosec if len(inactiveKeysToRemove) != len(inactiveTranchesToUpdate) { return fmt.Errorf("mismatch in inactive LO tranches to remove and update counts: %d != %d", len(inactiveKeysToRemove), len(inactiveTranchesToUpdate)) From 060338add12adc962fc8c58adcbc6eb5e7fdd528 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Wed, 27 May 2026 12:30:37 +0300 Subject: [PATCH 09/14] move NewLimitOrderExpiration from dex keeper to types --- x/dex/genesis.go | 2 +- x/dex/keeper/limit_order_expiration.go | 13 ------------- x/dex/keeper/place_limit_order.go | 2 +- x/dex/types/limit_order_expiration.go | 14 ++++++++++++++ 4 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 x/dex/types/limit_order_expiration.go diff --git a/x/dex/genesis.go b/x/dex/genesis.go index 5e526f44a..96088ddad 100644 --- a/x/dex/genesis.go +++ b/x/dex/genesis.go @@ -21,7 +21,7 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) k.SetLimitOrderTranche(ctx, tranche) if tranche.HasExpiration() { // re-create expiration record - loExpiration := keeper.NewLimitOrderExpiration(tranche) + loExpiration := types.NewLimitOrderExpiration(tranche) k.SetLimitOrderExpiration(ctx, loExpiration) } } diff --git a/x/dex/keeper/limit_order_expiration.go b/x/dex/keeper/limit_order_expiration.go index bdc84a918..c871388a6 100644 --- a/x/dex/keeper/limit_order_expiration.go +++ b/x/dex/keeper/limit_order_expiration.go @@ -10,19 +10,6 @@ import ( "github.com/neutron-org/neutron/v11/x/dex/types" ) -// Creates a new LimitOrderExpiration struct based on a LimitOrderTranche -func NewLimitOrderExpiration(tranche *types.LimitOrderTranche) *types.LimitOrderExpiration { - trancheExpiry := tranche.ExpirationTime - if trancheExpiry == nil { - panic("Cannot create LimitOrderExpiration from tranche with nil ExpirationTime") - } - - return &types.LimitOrderExpiration{ - TrancheRef: tranche.Key.KeyMarshal(), - ExpirationTime: *tranche.ExpirationTime, - } -} - // SetLimitOrderExpiration set a specific goodTilRecord in the store from its index func (k Keeper) SetLimitOrderExpiration( ctx sdk.Context, diff --git a/x/dex/keeper/place_limit_order.go b/x/dex/keeper/place_limit_order.go index dbab7a9a5..ef5320531 100644 --- a/x/dex/keeper/place_limit_order.go +++ b/x/dex/keeper/place_limit_order.go @@ -178,7 +178,7 @@ func (k Keeper) ExecutePlaceLimitOrder( trancheUser.SharesOwned = trancheUser.SharesOwned.Add(amountToPlace) if orderType.HasExpiration() { - goodTilRecord := NewLimitOrderExpiration(placeTranche) + goodTilRecord := types.NewLimitOrderExpiration(placeTranche) k.SetLimitOrderExpiration(ctx, goodTilRecord) ctx.GasMeter().ConsumeGas(types.ExpiringLimitOrderGas, "Expiring LimitOrder Fee") } diff --git a/x/dex/types/limit_order_expiration.go b/x/dex/types/limit_order_expiration.go new file mode 100644 index 000000000..b62672e80 --- /dev/null +++ b/x/dex/types/limit_order_expiration.go @@ -0,0 +1,14 @@ +package types + +// Creates a new LimitOrderExpiration struct based on a LimitOrderTranche +func NewLimitOrderExpiration(tranche *LimitOrderTranche) *LimitOrderExpiration { + trancheExpiry := tranche.ExpirationTime + if trancheExpiry == nil { + panic("Cannot create LimitOrderExpiration from tranche with nil ExpirationTime") + } + + return &LimitOrderExpiration{ + TrancheRef: tranche.Key.KeyMarshal(), + ExpirationTime: *tranche.ExpirationTime, + } +} From f79291615332f3bcee952498e890529374c5172c Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Wed, 27 May 2026 12:31:37 +0300 Subject: [PATCH 10/14] move dex tranche keys migration from app upgrade handler to dex storage migration handler --- app/app.go | 2 - app/upgrades/nextupgrade/constants.go | 21 ------ x/dex/keeper/migrations.go | 6 ++ .../dex/migrations/v9/store.go | 69 +++++++++---------- .../dex/migrations/v9/store_test.go | 43 ++++-------- x/dex/module.go | 3 + x/dex/types/constants.go | 2 +- 7 files changed, 55 insertions(+), 91 deletions(-) delete mode 100644 app/upgrades/nextupgrade/constants.go rename app/upgrades/nextupgrade/upgrades.go => x/dex/migrations/v9/store.go (73%) rename app/upgrades/nextupgrade/upgrades_test.go => x/dex/migrations/v9/store_test.go (93%) diff --git a/app/app.go b/app/app.go index bbd0b9821..db462b141 100644 --- a/app/app.go +++ b/app/app.go @@ -14,7 +14,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" - "github.com/neutron-org/neutron/v11/app/upgrades/nextupgrade" v10_0_0 "github.com/neutron-org/neutron/v11/app/upgrades/v10.0.0" v10_1_0 "github.com/neutron-org/neutron/v11/app/upgrades/v10.1.0" v10_2_0 "github.com/neutron-org/neutron/v11/app/upgrades/v10.2.0" @@ -247,7 +246,6 @@ var ( v10_2_0.Upgrade, v10_3_0.Upgrade, v11.Upgrade, - nextupgrade.Upgrade, } // DefaultNodeHome default home directories for the application daemon diff --git a/app/upgrades/nextupgrade/constants.go b/app/upgrades/nextupgrade/constants.go deleted file mode 100644 index 001aba106..000000000 --- a/app/upgrades/nextupgrade/constants.go +++ /dev/null @@ -1,21 +0,0 @@ -package nextupgrade - -import ( - storetypes "cosmossdk.io/store/types" - - "github.com/neutron-org/neutron/v11/app/upgrades" -) - -const ( - // UpgradeName defines the on-chain upgrade name. - UpgradeName = "nextupgrade" -) - -var Upgrade = upgrades.Upgrade{ - UpgradeName: UpgradeName, - CreateUpgradeHandler: CreateUpgradeHandler, - StoreUpgrades: storetypes.StoreUpgrades{ - Added: []string{}, - Deleted: []string{}, - }, -} diff --git a/x/dex/keeper/migrations.go b/x/dex/keeper/migrations.go index 1b7a65c9c..68feb597b 100644 --- a/x/dex/keeper/migrations.go +++ b/x/dex/keeper/migrations.go @@ -9,6 +9,7 @@ import ( v6 "github.com/neutron-org/neutron/v11/x/dex/migrations/v6" v7 "github.com/neutron-org/neutron/v11/x/dex/migrations/v7" v8 "github.com/neutron-org/neutron/v11/x/dex/migrations/v8" + v9 "github.com/neutron-org/neutron/v11/x/dex/migrations/v9" ) // Migrator is a struct for handling in-place store migrations. @@ -50,3 +51,8 @@ func (m Migrator) Migrate6to7(ctx sdk.Context) error { func (m Migrator) Migrate7to8(ctx sdk.Context) error { return v8.MigrateStore(ctx, m.keeper.cdc, m.keeper.storeKey) } + +// Migrate8to9 migrates from version 8 to 9. +func (m Migrator) Migrate8to9(ctx sdk.Context) error { + return v9.MigrateStore(ctx, m.keeper.cdc, &m.keeper) +} diff --git a/app/upgrades/nextupgrade/upgrades.go b/x/dex/migrations/v9/store.go similarity index 73% rename from app/upgrades/nextupgrade/upgrades.go rename to x/dex/migrations/v9/store.go index 194b02909..5044af82f 100644 --- a/app/upgrades/nextupgrade/upgrades.go +++ b/x/dex/migrations/v9/store.go @@ -1,49 +1,46 @@ -package nextupgrade +package v9 import ( - "context" "fmt" "strconv" "strings" + "time" - upgradetypes "cosmossdk.io/x/upgrade/types" + storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/neutron-org/neutron/v11/app/upgrades" - dexkeeper "github.com/neutron-org/neutron/v11/x/dex/keeper" dextypes "github.com/neutron-org/neutron/v11/x/dex/types" ) -func CreateUpgradeHandler( - mm *module.Manager, - configurator module.Configurator, - keepers *upgrades.UpgradeKeepers, - _ upgrades.StoreKeys, - cdc codec.Codec, -) upgradetypes.UpgradeHandler { - return func(c context.Context, _ upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { - ctx := sdk.UnwrapSDKContext(c) - - ctx.Logger().Info("Starting module migrations...") - - vm, err := mm.RunMigrations(ctx, configurator, vm) - if err != nil { - return vm, err - } - - ctx.Logger().Info("Reconstructing tranche keys...") - if err := ReconstructTrancheKeys(ctx, cdc, *keepers.DexKeeper); err != nil { - return vm, err - } - ctx.Logger().Info("Done") +// dexKeeper defines an interface with dex keeper methods required for the migration. It is defined +// to avoid import loop (x/dex/migrations <-> x/dex/keeper). +type dexKeeper interface { + GetAllLimitOrderExpiration(ctx sdk.Context) (list []*dextypes.LimitOrderExpiration) + GetLimitOrderTrancheByKey(ctx sdk.Context, key []byte) (tranche *dextypes.LimitOrderTranche, found bool) + RemoveLimitOrderExpiration(ctx sdk.Context, goodTilDate time.Time, trancheRef []byte) + SetLimitOrderExpiration(ctx sdk.Context, goodTilRecord *dextypes.LimitOrderExpiration) + GetAllTickLiquidity(ctx sdk.Context) (list []*dextypes.TickLiquidity) + RemoveLimitOrderTranche(ctx sdk.Context, trancheKey *dextypes.LimitOrderTrancheKey) + SetLimitOrderTranche(ctx sdk.Context, tranche *dextypes.LimitOrderTranche) + GetInactiveLimitOrderTrancheIterator(ctx sdk.Context) storetypes.Iterator + RemoveInactiveLimitOrderTranche(ctx sdk.Context, limitOrderTrancheKey *dextypes.LimitOrderTrancheKey) + SetInactiveLimitOrderTranche(ctx sdk.Context, limitOrderTranche *dextypes.LimitOrderTranche) + GetAllLimitOrderTrancheUser(ctx sdk.Context) (list []*dextypes.LimitOrderTrancheUser) + RemoveLimitOrderTrancheUser(ctx sdk.Context, trancheUser *dextypes.LimitOrderTrancheUser) + SetLimitOrderTrancheUser(ctx sdk.Context, limitOrderTrancheUser *dextypes.LimitOrderTrancheUser) +} - ctx.Logger().Info(fmt.Sprintf("Migration {%s} applied", UpgradeName)) - return vm, nil +// MigrateStore performs in-place store migrations. It reconstructs the tranche keys for limit order +// expirations, tranches, inactive tranches, and tranche user lists. +func MigrateStore(ctx sdk.Context, cdc codec.BinaryCodec, dexKeeper dexKeeper) error { + if err := ReconstructTrancheKeys(ctx, cdc, dexKeeper); err != nil { + return err } + + return nil } -func ReconstructTrancheKeys(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) error { +func ReconstructTrancheKeys(ctx sdk.Context, cdc codec.BinaryCodec, k dexKeeper) error { if err := reconstructLoExpirations(ctx, k); err != nil { return fmt.Errorf("failed to reconstruct LO expirations: %w", err) } @@ -63,7 +60,7 @@ func ReconstructTrancheKeys(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper return nil } -func reconstructLoExpirations(ctx sdk.Context, k dexkeeper.Keeper) error { +func reconstructLoExpirations(ctx sdk.Context, k dexKeeper) error { allExpirations := k.GetAllLimitOrderExpiration(ctx) // total count varies but is expected to be small or even 0 expirationsToRemove := make([]dextypes.LimitOrderExpiration, 0) @@ -86,7 +83,7 @@ func reconstructLoExpirations(ctx sdk.Context, k dexkeeper.Keeper) error { return fmt.Errorf("failed to parse tranche idx %s: %w", trancheIdxStr, err) } tranche.Key.TrancheKey = dextypes.NewTrancheKey(trancheIdx) - expirationsToUpdate = append(expirationsToUpdate, *dexkeeper.NewLimitOrderExpiration(tranche)) + expirationsToUpdate = append(expirationsToUpdate, *dextypes.NewLimitOrderExpiration(tranche)) } if len(expirationsToRemove) != len(expirationsToUpdate) { @@ -104,7 +101,7 @@ func reconstructLoExpirations(ctx sdk.Context, k dexkeeper.Keeper) error { return nil } -func reconstructLoTranches(ctx sdk.Context, k dexkeeper.Keeper) error { +func reconstructLoTranches(ctx sdk.Context, k dexKeeper) error { tickLiquidities := k.GetAllTickLiquidity(ctx) // there are only 600-ish entries, so getting all is fine loTrancheKeysToRemove := make([]dextypes.LimitOrderTrancheKey, 0) @@ -142,7 +139,7 @@ func reconstructLoTranches(ctx sdk.Context, k dexkeeper.Keeper) error { return nil } -func reconstructInactiveLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper.Keeper) error { +func reconstructInactiveLoTranches(ctx sdk.Context, cdc codec.BinaryCodec, k dexKeeper) error { iter := k.GetInactiveLimitOrderTrancheIterator(ctx) // there are more than 400k entries -> iterating inactiveKeysToRemove := make([]dextypes.LimitOrderTrancheKey, 0) @@ -183,7 +180,7 @@ func reconstructInactiveLoTranches(ctx sdk.Context, cdc codec.Codec, k dexkeeper return nil } -func reconstructLoTrancheUserLists(ctx sdk.Context, k dexkeeper.Keeper) error { +func reconstructLoTrancheUserLists(ctx sdk.Context, k dexKeeper) error { allUsers := k.GetAllLimitOrderTrancheUser(ctx) // there are only 300-ish entries, so getting all is fine usersToRemove := make([]dextypes.LimitOrderTrancheUser, 0) diff --git a/app/upgrades/nextupgrade/upgrades_test.go b/x/dex/migrations/v9/store_test.go similarity index 93% rename from app/upgrades/nextupgrade/upgrades_test.go rename to x/dex/migrations/v9/store_test.go index 9b72e02ca..6ce2d6d53 100644 --- a/app/upgrades/nextupgrade/upgrades_test.go +++ b/x/dex/migrations/v9/store_test.go @@ -1,43 +1,24 @@ -package nextupgrade_test +package v9_test import ( "testing" "time" sdkmath "cosmossdk.io/math" - upgradetypes "cosmossdk.io/x/upgrade/types" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" - "github.com/neutron-org/neutron/v11/app/upgrades/nextupgrade" "github.com/neutron-org/neutron/v11/testutil" + v9 "github.com/neutron-org/neutron/v11/x/dex/migrations/v9" dextypes "github.com/neutron-org/neutron/v11/x/dex/types" ) -type UpgradeTestSuite struct { +type V9DexMigrationTestSuite struct { testutil.IBCConnectionTestSuite } func TestKeeperTestSuite(t *testing.T) { - suite.Run(t, new(UpgradeTestSuite)) -} - -func (suite *UpgradeTestSuite) SetupTest() { - suite.IBCConnectionTestSuite.SetupTest() -} - -func (suite *UpgradeTestSuite) TestUpgrade() { - app := suite.GetNeutronZoneApp(suite.ChainA) - ctx := suite.ChainA.GetContext().WithChainID("neutron-1") - t := suite.T() - - upgrade := upgradetypes.Plan{ - Name: nextupgrade.UpgradeName, - Info: "some text here", - Height: 100, - } - - require.NoError(t, app.UpgradeKeeper.ApplyUpgrade(ctx, upgrade)) + suite.Run(t, new(V9DexMigrationTestSuite)) } // TestReconstructLoExpirations verifies the LimitOrderExpiration migration. @@ -48,7 +29,7 @@ func (suite *UpgradeTestSuite) TestUpgrade() { // expiration entry must be removed from its old store key and re-inserted under the new one. // // Entries pointing to a base-36 tranche key (no "tk-" prefix) must be left untouched. -func (suite *UpgradeTestSuite) TestReconstructLoExpirations() { +func (suite *V9DexMigrationTestSuite) TestReconstructLoExpirations() { app := suite.GetNeutronZoneApp(suite.ChainA) ctx := suite.ChainA.GetContext().WithChainID("neutron-1") t := suite.T() @@ -116,7 +97,7 @@ func (suite *UpgradeTestSuite) TestReconstructLoExpirations() { // ── run migration ──────────────────────────────────────────────────────── - require.NoError(t, nextupgrade.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) + require.NoError(t, v9.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) // ── post-upgrade assertions ────────────────────────────────────────────── @@ -158,7 +139,7 @@ func (suite *UpgradeTestSuite) TestReconstructLoExpirations() { // - tk-19993998 / tk-19940606: old decimal keys that must be rewritten. // - 57mgzl47if5: original base-36 sortable key (no "tk-" prefix) that must be left untouched. // - pool_reserves: a tick-liquidity entry that is not a limit order; must be untouched. -func (suite *UpgradeTestSuite) TestReconstructLoTrancheKeys() { +func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { app := suite.GetNeutronZoneApp(suite.ChainA) ctx := suite.ChainA.GetContext().WithChainID("neutron-1") t := suite.T() @@ -217,7 +198,7 @@ func (suite *UpgradeTestSuite) TestReconstructLoTrancheKeys() { // ── run migration ──────────────────────────────────────────────────────── - require.NoError(t, nextupgrade.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) + require.NoError(t, v9.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) // ── post-upgrade assertions ────────────────────────────────────────────── @@ -290,7 +271,7 @@ func (suite *UpgradeTestSuite) TestReconstructLoTrancheKeys() { // TestReconstructInactiveLoTranches verifies that inactive limit order tranches stored // under the old plain-decimal "tk-N" key format are rewritten to the zero-padded // "tk-%020d" format, while entries with the original base-36 sortable key are left alone. -func (suite *UpgradeTestSuite) TestReconstructInactiveLoTranches() { +func (suite *V9DexMigrationTestSuite) TestReconstructInactiveLoTranches() { app := suite.GetNeutronZoneApp(suite.ChainA) ctx := suite.ChainA.GetContext().WithChainID("neutron-1") t := suite.T() @@ -334,7 +315,7 @@ func (suite *UpgradeTestSuite) TestReconstructInactiveLoTranches() { // ── run migration ──────────────────────────────────────────────────────── - require.NoError(t, nextupgrade.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) + require.NoError(t, v9.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) // ── post-upgrade assertions ────────────────────────────────────────────── @@ -392,7 +373,7 @@ func (suite *UpgradeTestSuite) TestReconstructInactiveLoTranches() { // TestReconstructLoTrancheUserLists verifies that LimitOrderTrancheUser entries stored under // the old plain-decimal "tk-N" key are rewritten to the zero-padded "tk-%020d" format. // Entries with the original base-36 sortable key (no "tk-" prefix) must remain unchanged. -func (suite *UpgradeTestSuite) TestReconstructLoTrancheUserLists() { +func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { app := suite.GetNeutronZoneApp(suite.ChainA) ctx := suite.ChainA.GetContext().WithChainID("neutron-1") t := suite.T() @@ -442,7 +423,7 @@ func (suite *UpgradeTestSuite) TestReconstructLoTrancheUserLists() { // ── run migration ──────────────────────────────────────────────────────── - require.NoError(t, nextupgrade.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) + require.NoError(t, v9.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) // ── post-upgrade assertions ────────────────────────────────────────────── diff --git a/x/dex/module.go b/x/dex/module.go index d1407785d..f44335846 100644 --- a/x/dex/module.go +++ b/x/dex/module.go @@ -174,6 +174,9 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { if err := cfg.RegisterMigration(types.ModuleName, 7, m.Migrate7to8); err != nil { panic(fmt.Sprintf("failed to migrate x/dex from version 7 to 8: %v", err)) } + if err := cfg.RegisterMigration(types.ModuleName, 8, m.Migrate8to9); err != nil { + panic(fmt.Sprintf("failed to migrate x/dex from version 8 to 9: %v", err)) + } } // RegisterInvariants registers the capability module's invariants. diff --git a/x/dex/types/constants.go b/x/dex/types/constants.go index 04c02dfce..2a5845dd8 100644 --- a/x/dex/types/constants.go +++ b/x/dex/types/constants.go @@ -1,6 +1,6 @@ package types -const ConsensusVersion = 8 +const ConsensusVersion = 9 const ( MaxRoutesPerRequest = 16 From 86f04ef51d6c0892826ee8ef782d3dea50150445 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Wed, 27 May 2026 14:55:42 +0300 Subject: [PATCH 11/14] tranche keys as Uint64ToSortableString instead of tk-%020d --- x/dex/migrations/v9/store_test.go | 82 ++++++++++++++------------ x/dex/types/limit_order_tranche_key.go | 5 +- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/x/dex/migrations/v9/store_test.go b/x/dex/migrations/v9/store_test.go index 6ce2d6d53..0418f7722 100644 --- a/x/dex/migrations/v9/store_test.go +++ b/x/dex/migrations/v9/store_test.go @@ -11,6 +11,7 @@ import ( "github.com/neutron-org/neutron/v11/testutil" v9 "github.com/neutron-org/neutron/v11/x/dex/migrations/v9" dextypes "github.com/neutron-org/neutron/v11/x/dex/types" + "github.com/neutron-org/neutron/v11/x/dex/utils" ) type V9DexMigrationTestSuite struct { @@ -25,10 +26,11 @@ func TestKeeperTestSuite(t *testing.T) { // // A LimitOrderExpiration stores a TrancheRef = tranche.Key.KeyMarshal(), which embeds the // TrancheKey string as part of its bytes. When a tranche key is rewritten from the old -// plain-decimal "tk-N" format to the zero-padded "tk-%020d" format, the corresponding +// plain-decimal "tk-N" format to the base-36 sortable string format, the corresponding // expiration entry must be removed from its old store key and re-inserted under the new one. // -// Entries pointing to a base-36 tranche key (no "tk-" prefix) must be left untouched. +// Entries pointing to obsolete base-36 tranche keys built out of height and gas and no "tk-" prefix +// must be left untouched. func (suite *V9DexMigrationTestSuite) TestReconstructLoExpirations() { app := suite.GetNeutronZoneApp(suite.ChainA) ctx := suite.ChainA.GetContext().WithChainID("neutron-1") @@ -73,7 +75,7 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoExpirations() { TrancheRef: oldKey2.KeyMarshal(), }) - // One tranche with the original base-36 sortable key (no "tk-" prefix). + // One tranche with an obsolete base-36 key built out of height and gas and no "tk-" prefix. // Its expiration must not be touched. pairID2 := dextypes.MustNewTradePairID( "factory/neutron1dqd0wsqldr89m4d9trk2arv35twz7a5erjj6td/nick", @@ -103,27 +105,27 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoExpirations() { require.Len(t, app.DexKeeper.GetAllLimitOrderExpiration(ctx), 3, "post-upgrade: expiration count must not change") - // --- tk-19993998 expiration → new TrancheRef under tk-00000000000019993998 --- + // --- tk-19993998 expiration → new TrancheRef under Uint64ToSortableString(19993998) --- newKey1 := &dextypes.LimitOrderTrancheKey{ TradePairId: pairID1, - TrancheKey: dextypes.NewTrancheKey(19993998), + TrancheKey: utils.Uint64ToSortableString(19993998), TickIndexTakerToMaker: -43028, } _, found := app.DexKeeper.GetLimitOrderExpiration(ctx, expTime1, newKey1.KeyMarshal()) - require.True(t, found, "new expiration (tk-00000000000019993998) must exist") + require.True(t, found, "new expiration (Uint64ToSortableString(19993998)) must exist") _, found = app.DexKeeper.GetLimitOrderExpiration(ctx, expTime1, oldKey1.KeyMarshal()) require.False(t, found, "old expiration (tk-19993998) must be removed") - // --- tk-19940606 expiration → new TrancheRef under tk-00000000000019940606 --- + // --- tk-19940606 expiration → new TrancheRef under Uint64ToSortableString(19940606) --- newKey2 := &dextypes.LimitOrderTrancheKey{ TradePairId: pairID1, - TrancheKey: dextypes.NewTrancheKey(19940606), + TrancheKey: utils.Uint64ToSortableString(19940606), TickIndexTakerToMaker: -42321, } _, found = app.DexKeeper.GetLimitOrderExpiration(ctx, expTime2, newKey2.KeyMarshal()) - require.True(t, found, "new expiration (tk-00000000000019940606) must exist") + require.True(t, found, "new expiration (Uint64ToSortableString(19940606)) must exist") _, found = app.DexKeeper.GetLimitOrderExpiration(ctx, expTime2, oldKey2.KeyMarshal()) require.False(t, found, "old expiration (tk-19940606) must be removed") @@ -134,10 +136,11 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoExpirations() { } // TestReconstructLoTrancheKeys verifies the tranche key migration from the plain-decimal -// "tk-N" format to the zero-padded "tk-%020d" format. It uses realistic mainnet entries: +// "tk-N" format to the base-36 sortable string format. It uses realistic mainnet entries: // // - tk-19993998 / tk-19940606: old decimal keys that must be rewritten. -// - 57mgzl47if5: original base-36 sortable key (no "tk-" prefix) that must be left untouched. +// - 57mgzl47if5: obsolete base-36 key built out of height and gas and no "tk-" prefix that +// must be left untouched. // - pool_reserves: a tick-liquidity entry that is not a limit order; must be untouched. func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { app := suite.GetNeutronZoneApp(suite.ChainA) @@ -166,7 +169,7 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { }, }) - // One tranche with the original base-36 sortable key (no "tk-" prefix). + // One tranche with an obsolete base-36 key built out of height and gas and no "tk-" prefix. // The migration must skip it and leave it unchanged. pairID2 := dextypes.MustNewTradePairID( "factory/neutron1dqd0wsqldr89m4d9trk2arv35twz7a5erjj6td/nick", @@ -205,9 +208,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { // Total entry count must be unchanged. require.Len(t, app.DexKeeper.GetAllTickLiquidity(ctx), 4, "post-upgrade: entry count must not change") - // --- tk-19993998 → tk-00000000000019993998 --- + // --- tk-19993998 → Uint64ToSortableString(19993998) --- - migratedKey1 := dextypes.NewTrancheKey(19993998) // "tk-00000000000019993998" + migratedKey1 := utils.Uint64ToSortableString(19993998) migratedTranche1 := app.DexKeeper.GetLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ TradePairId: pairID1, @@ -226,9 +229,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { "old key tk-19993998 must no longer exist", ) - // --- tk-19940606 → tk-00000000000019940606 --- + // --- tk-19940606 → Uint64ToSortableString(19940606) --- - migratedKey2 := dextypes.NewTrancheKey(19940606) // "tk-00000000000019940606" + migratedKey2 := utils.Uint64ToSortableString(19940606) migratedTranche2 := app.DexKeeper.GetLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ TradePairId: pairID1, @@ -247,15 +250,15 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { "old key tk-19940606 must no longer exist", ) - // --- 57mgzl47if5 (base-36 key, no "tk-" prefix) must be unchanged --- + // --- 57mgzl47if5 (obsolete base-36 key, no "tk-" prefix) must be unchanged --- untouchedTranche := app.DexKeeper.GetLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ TradePairId: pairID2, TrancheKey: "57mgzl47if5", TickIndexTakerToMaker: 46055, }) - require.NotNil(t, untouchedTranche, "base-36 tranche must still exist") - require.Equal(t, "57mgzl47if5", untouchedTranche.Key.TrancheKey, "base-36 key must not be rewritten") + require.NotNil(t, untouchedTranche, "obsolete base-36 tranche must still exist") + require.Equal(t, "57mgzl47if5", untouchedTranche.Key.TrancheKey, "obsolete base-36 key must not be rewritten") // --- pool_reserves must be untouched --- @@ -269,8 +272,8 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { } // TestReconstructInactiveLoTranches verifies that inactive limit order tranches stored -// under the old plain-decimal "tk-N" key format are rewritten to the zero-padded -// "tk-%020d" format, while entries with the original base-36 sortable key are left alone. +// under the old plain-decimal "tk-N" key are rewritten to the base-36 sortable string format, +// while entries with the obsolete base-36 sortable key are left alone. func (suite *V9DexMigrationTestSuite) TestReconstructInactiveLoTranches() { app := suite.GetNeutronZoneApp(suite.ChainA) ctx := suite.ChainA.GetContext().WithChainID("neutron-1") @@ -278,7 +281,7 @@ func (suite *V9DexMigrationTestSuite) TestReconstructInactiveLoTranches() { // ── pre-upgrade state ──────────────────────────────────────────────────── - // One inactive tranche with the original base-36 sortable key (no "tk-" prefix). + // One inactive tranche with an obsolete base-36 key built out of height and gas and no "tk-" prefix. pairID1 := dextypes.MustNewTradePairID( "factory/neutron10h9stc5v6ntgeygf5xf945njqq5h32r54rf7kf/nick", "factory/neutron1dqd0wsqldr89m4d9trk2arv35twz7a5erjj6td/jcp", @@ -321,9 +324,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructInactiveLoTranches() { require.Len(t, app.DexKeeper.GetAllInactiveLimitOrderTranche(ctx), 3, "post-upgrade: entry count must not change") - // --- tk-18498162 → tk-00000000000018498162 --- + // --- tk-18498162 → Uint64ToSortableString(18498162) --- - migratedKey1 := dextypes.NewTrancheKey(18498162) + migratedKey1 := utils.Uint64ToSortableString(18498162) migratedTranche1, found := app.DexKeeper.GetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ TradePairId: pairID2, @@ -340,9 +343,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructInactiveLoTranches() { }) require.False(t, found, "old key tk-18498162 must no longer exist") - // --- tk-18291522 → tk-00000000000018291522 --- + // --- tk-18291522 → Uint64ToSortableString(18291522) --- - migratedKey2 := dextypes.NewTrancheKey(18291522) + migratedKey2 := utils.Uint64ToSortableString(18291522) migratedTranche2, found := app.DexKeeper.GetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ TradePairId: pairID2, @@ -359,20 +362,21 @@ func (suite *V9DexMigrationTestSuite) TestReconstructInactiveLoTranches() { }) require.False(t, found, "old key tk-18291522 must no longer exist") - // --- 57m0a14awvr (base-36 key) must be unchanged --- + // --- 57m0a14awvr (obsolete base-36 key, no "tk-" prefix) must be unchanged --- untouched, found := app.DexKeeper.GetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ TradePairId: pairID1, TrancheKey: "57m0a14awvr", TickIndexTakerToMaker: 0, }) - require.True(t, found, "base-36 tranche must still exist") - require.Equal(t, "57m0a14awvr", untouched.Key.TrancheKey, "base-36 key must not be rewritten") + require.True(t, found, "obsolete base-36 tranche must still exist") + require.Equal(t, "57m0a14awvr", untouched.Key.TrancheKey, "obsolete base-36 key must not be rewritten") } // TestReconstructLoTrancheUserLists verifies that LimitOrderTrancheUser entries stored under -// the old plain-decimal "tk-N" key are rewritten to the zero-padded "tk-%020d" format. -// Entries with the original base-36 sortable key (no "tk-" prefix) must remain unchanged. +// the old plain-decimal "tk-N" key are rewritten to the base-36 sortable string format. +// Entries with the obsolete base-36 sortable key built out of height and gas and no "tk-" prefix +// must remain unchanged. func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { app := suite.GetNeutronZoneApp(suite.ChainA) ctx := suite.ChainA.GetContext().WithChainID("neutron-1") @@ -384,7 +388,7 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { "ibc/773B4D0A3CD667B2275D5A4A7A2F0909C0BA0F4059C0B9181E680DDF4965DCC7", "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", ) - // base-36 key — must NOT be migrated + // obsolete base-36 key — must NOT be migrated app.DexKeeper.SetLimitOrderTrancheUser(ctx, &dextypes.LimitOrderTrancheUser{ TradePairId: pairID1, TickIndexTakerToMaker: -16365, @@ -429,9 +433,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { require.Len(t, app.DexKeeper.GetAllLimitOrderTrancheUser(ctx), 3, "post-upgrade: entry count must not change") - // --- tk-3819855 → tk-00000000000003819855 --- + // --- tk-3819855 → Uint64ToSortableString(3819855) --- - migratedKey1 := dextypes.NewTrancheKey(3819855) + migratedKey1 := utils.Uint64ToSortableString(3819855) migratedUser1, found := app.DexKeeper.GetLimitOrderTrancheUser( ctx, @@ -448,9 +452,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { ) require.False(t, found, "old key tk-3819855 must no longer exist") - // --- tk-4079303 → tk-00000000000004079303 --- + // --- tk-4079303 → Uint64ToSortableString(4079303) --- - migratedKey2 := dextypes.NewTrancheKey(4079303) + migratedKey2 := utils.Uint64ToSortableString(4079303) migratedUser2, found := app.DexKeeper.GetLimitOrderTrancheUser( ctx, @@ -467,13 +471,13 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { ) require.False(t, found, "old key tk-4079303 must no longer exist") - // --- 5atwxq41kck (base-36 key) must be unchanged --- + // --- 5atwxq41kck (obsolete base-36 key) must be unchanged --- untouched, found := app.DexKeeper.GetLimitOrderTrancheUser( ctx, "neutron12c20g3kvrvmqj3w5ep6vept6f77lunxyrrq44w", "5atwxq41kck", ) - require.True(t, found, "base-36 tranche user must still exist") - require.Equal(t, "5atwxq41kck", untouched.TrancheKey, "base-36 key must not be rewritten") + require.True(t, found, "obsolete base-36 tranche user must still exist") + require.Equal(t, "5atwxq41kck", untouched.TrancheKey, "obsolete base-36 key must not be rewritten") } diff --git a/x/dex/types/limit_order_tranche_key.go b/x/dex/types/limit_order_tranche_key.go index c435ef472..be3cbab93 100644 --- a/x/dex/types/limit_order_tranche_key.go +++ b/x/dex/types/limit_order_tranche_key.go @@ -1,9 +1,8 @@ package types import ( - fmt "fmt" - math_utils "github.com/neutron-org/neutron/v11/utils/math" + "github.com/neutron-org/neutron/v11/x/dex/utils" ) var _ TickLiquidityKey = (*LimitOrderTrancheKey)(nil) @@ -47,5 +46,5 @@ func (p LimitOrderTrancheKey) MustPrice() (priceTakerToMaker math_utils.PrecDec) // NewTrancheKey returns a new tranche key based on the tranche index. func NewTrancheKey(trancheIdx uint64) string { - return fmt.Sprintf("tk-%020d", trancheIdx) + return utils.Uint64ToSortableString(trancheIdx) } From 4b6367dfbc113e4118b8414c2efc7e11cf1eaeeb Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Thu, 28 May 2026 20:18:22 +0300 Subject: [PATCH 12/14] add tk- prefix to new tranche key format for correct ordering --- x/dex/migrations/v9/store_test.go | 61 +++++++++++++------------- x/dex/types/limit_order_tranche_key.go | 4 +- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/x/dex/migrations/v9/store_test.go b/x/dex/migrations/v9/store_test.go index 0418f7722..133c9e437 100644 --- a/x/dex/migrations/v9/store_test.go +++ b/x/dex/migrations/v9/store_test.go @@ -11,7 +11,6 @@ import ( "github.com/neutron-org/neutron/v11/testutil" v9 "github.com/neutron-org/neutron/v11/x/dex/migrations/v9" dextypes "github.com/neutron-org/neutron/v11/x/dex/types" - "github.com/neutron-org/neutron/v11/x/dex/utils" ) type V9DexMigrationTestSuite struct { @@ -26,8 +25,8 @@ func TestKeeperTestSuite(t *testing.T) { // // A LimitOrderExpiration stores a TrancheRef = tranche.Key.KeyMarshal(), which embeds the // TrancheKey string as part of its bytes. When a tranche key is rewritten from the old -// plain-decimal "tk-N" format to the base-36 sortable string format, the corresponding -// expiration entry must be removed from its old store key and re-inserted under the new one. +// plain-decimal "tk-N" format to the "tk-Uint64ToSortableString(N)" string format, the corresponding expiration +// entry must be removed from its old store key and re-inserted under the new one. // // Entries pointing to obsolete base-36 tranche keys built out of height and gas and no "tk-" prefix // must be left untouched. @@ -42,7 +41,7 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoExpirations() { // ── pre-upgrade state ──────────────────────────────────────────────────── - // Two active limit-order tranches with old plain-decimal keys and different ExpirationTime. + // Two active limit-order tranches with old "tk-N" keys and different ExpirationTime. pairID1 := dextypes.MustNewTradePairID( "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", "factory/neutron1frc0p5czd9uaaymdkug2njz7dc7j65jxukp9apmt9260a8egujkspms2t2/udntrn", @@ -105,27 +104,27 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoExpirations() { require.Len(t, app.DexKeeper.GetAllLimitOrderExpiration(ctx), 3, "post-upgrade: expiration count must not change") - // --- tk-19993998 expiration → new TrancheRef under Uint64ToSortableString(19993998) --- + // --- tk-19993998 expiration → new TrancheRef under "tk-Uint64ToSortableString(19993998)" string format --- newKey1 := &dextypes.LimitOrderTrancheKey{ TradePairId: pairID1, - TrancheKey: utils.Uint64ToSortableString(19993998), + TrancheKey: dextypes.NewTrancheKey(19993998), TickIndexTakerToMaker: -43028, } _, found := app.DexKeeper.GetLimitOrderExpiration(ctx, expTime1, newKey1.KeyMarshal()) - require.True(t, found, "new expiration (Uint64ToSortableString(19993998)) must exist") + require.True(t, found, "new expiration (tk-Uint64ToSortableString(19993998)) must exist") _, found = app.DexKeeper.GetLimitOrderExpiration(ctx, expTime1, oldKey1.KeyMarshal()) require.False(t, found, "old expiration (tk-19993998) must be removed") - // --- tk-19940606 expiration → new TrancheRef under Uint64ToSortableString(19940606) --- + // --- tk-19940606 expiration → new TrancheRef under "tk-Uint64ToSortableString(19940606)" string format --- newKey2 := &dextypes.LimitOrderTrancheKey{ TradePairId: pairID1, - TrancheKey: utils.Uint64ToSortableString(19940606), + TrancheKey: dextypes.NewTrancheKey(19940606), TickIndexTakerToMaker: -42321, } _, found = app.DexKeeper.GetLimitOrderExpiration(ctx, expTime2, newKey2.KeyMarshal()) - require.True(t, found, "new expiration (Uint64ToSortableString(19940606)) must exist") + require.True(t, found, "new expiration (tk-Uint64ToSortableString(19940606)) must exist") _, found = app.DexKeeper.GetLimitOrderExpiration(ctx, expTime2, oldKey2.KeyMarshal()) require.False(t, found, "old expiration (tk-19940606) must be removed") @@ -136,9 +135,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoExpirations() { } // TestReconstructLoTrancheKeys verifies the tranche key migration from the plain-decimal -// "tk-N" format to the base-36 sortable string format. It uses realistic mainnet entries: +// "tk-N" format to the "tk-Uint64ToSortableString(N)" string format. It uses realistic mainnet entries: // -// - tk-19993998 / tk-19940606: old decimal keys that must be rewritten. +// - tk-19993998 / tk-19940606: old "tk-N" keys that must be rewritten. // - 57mgzl47if5: obsolete base-36 key built out of height and gas and no "tk-" prefix that // must be left untouched. // - pool_reserves: a tick-liquidity entry that is not a limit order; must be untouched. @@ -149,7 +148,7 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { // ── pre-upgrade state ──────────────────────────────────────────────────── - // Two active limit-order tranches with old plain-decimal keys. + // Two active limit-order tranches with old "tk-N" keys. pairID1 := dextypes.MustNewTradePairID( "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", "factory/neutron1frc0p5czd9uaaymdkug2njz7dc7j65jxukp9apmt9260a8egujkspms2t2/udntrn", @@ -208,9 +207,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { // Total entry count must be unchanged. require.Len(t, app.DexKeeper.GetAllTickLiquidity(ctx), 4, "post-upgrade: entry count must not change") - // --- tk-19993998 → Uint64ToSortableString(19993998) --- + // --- tk-19993998 → "tk-Uint64ToSortableString(19993998)" string format --- - migratedKey1 := utils.Uint64ToSortableString(19993998) + migratedKey1 := dextypes.NewTrancheKey(19993998) migratedTranche1 := app.DexKeeper.GetLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ TradePairId: pairID1, @@ -229,9 +228,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { "old key tk-19993998 must no longer exist", ) - // --- tk-19940606 → Uint64ToSortableString(19940606) --- + // --- tk-19940606 → "tk-Uint64ToSortableString(19940606)" string format --- - migratedKey2 := utils.Uint64ToSortableString(19940606) + migratedKey2 := dextypes.NewTrancheKey(19940606) migratedTranche2 := app.DexKeeper.GetLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ TradePairId: pairID1, @@ -271,9 +270,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheKeys() { require.NotNil(t, poolReserves) } -// TestReconstructInactiveLoTranches verifies that inactive limit order tranches stored -// under the old plain-decimal "tk-N" key are rewritten to the base-36 sortable string format, -// while entries with the obsolete base-36 sortable key are left alone. +// TestReconstructInactiveLoTranches verifies that inactive limit order tranches stored under +// the old "tk-N" key are rewritten to the "tk-Uint64ToSortableString(N)" string format, while entries with +// the obsolete base-36 key are left alone. func (suite *V9DexMigrationTestSuite) TestReconstructInactiveLoTranches() { app := suite.GetNeutronZoneApp(suite.ChainA) ctx := suite.ChainA.GetContext().WithChainID("neutron-1") @@ -324,9 +323,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructInactiveLoTranches() { require.Len(t, app.DexKeeper.GetAllInactiveLimitOrderTranche(ctx), 3, "post-upgrade: entry count must not change") - // --- tk-18498162 → Uint64ToSortableString(18498162) --- + // --- tk-18498162 → "tk-Uint64ToSortableString(18498162)" string format --- - migratedKey1 := utils.Uint64ToSortableString(18498162) + migratedKey1 := dextypes.NewTrancheKey(18498162) migratedTranche1, found := app.DexKeeper.GetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ TradePairId: pairID2, @@ -343,9 +342,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructInactiveLoTranches() { }) require.False(t, found, "old key tk-18498162 must no longer exist") - // --- tk-18291522 → Uint64ToSortableString(18291522) --- + // --- tk-18291522 → "tk-Uint64ToSortableString(18291522)" string format --- - migratedKey2 := utils.Uint64ToSortableString(18291522) + migratedKey2 := dextypes.NewTrancheKey(18291522) migratedTranche2, found := app.DexKeeper.GetInactiveLimitOrderTranche(ctx, &dextypes.LimitOrderTrancheKey{ TradePairId: pairID2, @@ -374,8 +373,8 @@ func (suite *V9DexMigrationTestSuite) TestReconstructInactiveLoTranches() { } // TestReconstructLoTrancheUserLists verifies that LimitOrderTrancheUser entries stored under -// the old plain-decimal "tk-N" key are rewritten to the base-36 sortable string format. -// Entries with the obsolete base-36 sortable key built out of height and gas and no "tk-" prefix +// the old "tk-N" key are rewritten to the "tk-Uint64ToSortableString(N)" string format. +// Entries with the obsolete base-36 key built out of height and gas and no "tk-" prefix // must remain unchanged. func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { app := suite.GetNeutronZoneApp(suite.ChainA) @@ -403,7 +402,7 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", "factory/neutron1r5qx58l3xx2y8gzjtkqjndjgx69mktmapl45vns0pa73z0zpn7fqgltnll/TAB", ) - // old plain-decimal key — must be migrated + // old "tk-N" key — must be migrated app.DexKeeper.SetLimitOrderTrancheUser(ctx, &dextypes.LimitOrderTrancheUser{ TradePairId: pairID2, TickIndexTakerToMaker: -12041, @@ -433,9 +432,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { require.Len(t, app.DexKeeper.GetAllLimitOrderTrancheUser(ctx), 3, "post-upgrade: entry count must not change") - // --- tk-3819855 → Uint64ToSortableString(3819855) --- + // --- tk-3819855 → "tk-Uint64ToSortableString(3819855)" string format --- - migratedKey1 := utils.Uint64ToSortableString(3819855) + migratedKey1 := dextypes.NewTrancheKey(3819855) migratedUser1, found := app.DexKeeper.GetLimitOrderTrancheUser( ctx, @@ -452,9 +451,9 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { ) require.False(t, found, "old key tk-3819855 must no longer exist") - // --- tk-4079303 → Uint64ToSortableString(4079303) --- + // --- tk-4079303 → "tk-Uint64ToSortableString(4079303)" string format --- - migratedKey2 := utils.Uint64ToSortableString(4079303) + migratedKey2 := dextypes.NewTrancheKey(4079303) migratedUser2, found := app.DexKeeper.GetLimitOrderTrancheUser( ctx, diff --git a/x/dex/types/limit_order_tranche_key.go b/x/dex/types/limit_order_tranche_key.go index be3cbab93..a7b97e933 100644 --- a/x/dex/types/limit_order_tranche_key.go +++ b/x/dex/types/limit_order_tranche_key.go @@ -1,6 +1,8 @@ package types import ( + fmt "fmt" + math_utils "github.com/neutron-org/neutron/v11/utils/math" "github.com/neutron-org/neutron/v11/x/dex/utils" ) @@ -46,5 +48,5 @@ func (p LimitOrderTrancheKey) MustPrice() (priceTakerToMaker math_utils.PrecDec) // NewTrancheKey returns a new tranche key based on the tranche index. func NewTrancheKey(trancheIdx uint64) string { - return utils.Uint64ToSortableString(trancheIdx) + return fmt.Sprintf("tk-%s", utils.Uint64ToSortableString(trancheIdx)) } From 66c45109d37640c145f018f238bad801bab095b8 Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Thu, 28 May 2026 20:18:40 +0300 Subject: [PATCH 13/14] add better logging to dex storage migration to v9 --- x/dex/migrations/v9/store.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/x/dex/migrations/v9/store.go b/x/dex/migrations/v9/store.go index 5044af82f..36e6a170a 100644 --- a/x/dex/migrations/v9/store.go +++ b/x/dex/migrations/v9/store.go @@ -33,29 +33,41 @@ type dexKeeper interface { // MigrateStore performs in-place store migrations. It reconstructs the tranche keys for limit order // expirations, tranches, inactive tranches, and tranche user lists. func MigrateStore(ctx sdk.Context, cdc codec.BinaryCodec, dexKeeper dexKeeper) error { + ctx.Logger().Info("Starting dex store migration...") + + ctx.Logger().Info("Reconstructing tranche keys...") if err := ReconstructTrancheKeys(ctx, cdc, dexKeeper); err != nil { return err } + ctx.Logger().Info("Dex store migration completed") return nil } func ReconstructTrancheKeys(ctx sdk.Context, cdc codec.BinaryCodec, k dexKeeper) error { + ctx.Logger().Info("Reconstructing LO expirations...") if err := reconstructLoExpirations(ctx, k); err != nil { return fmt.Errorf("failed to reconstruct LO expirations: %w", err) } + ctx.Logger().Info("Done") + ctx.Logger().Info("Reconstructing LO tranches...") if err := reconstructLoTranches(ctx, k); err != nil { return fmt.Errorf("failed to reconstruct LO tranches: %w", err) } + ctx.Logger().Info("Done") + ctx.Logger().Info("Reconstructing inactive LO tranches...") if err := reconstructInactiveLoTranches(ctx, cdc, k); err != nil { return fmt.Errorf("failed to reconstruct inactive LO tranches: %w", err) } + ctx.Logger().Info("Done") + ctx.Logger().Info("Reconstructing LO tranche user lists...") if err := reconstructLoTrancheUserLists(ctx, k); err != nil { return fmt.Errorf("failed to reconstruct LO tranche user lists: %w", err) } + ctx.Logger().Info("Done") return nil } From 9c837101b3366cc6d12eeb877254088d98d1fb4b Mon Sep 17 00:00:00 2001 From: sotnikov-s Date: Mon, 1 Jun 2026 20:21:48 +0300 Subject: [PATCH 14/14] move dex tk reconstruction test to v9 store migration package and refactor it --- .../integration_placelimitorder_test.go | 62 ----------- x/dex/migrations/v9/store_test.go | 104 ++++++++++++++++++ 2 files changed, 104 insertions(+), 62 deletions(-) diff --git a/x/dex/keeper/integration_placelimitorder_test.go b/x/dex/keeper/integration_placelimitorder_test.go index 33ff23574..20066b695 100644 --- a/x/dex/keeper/integration_placelimitorder_test.go +++ b/x/dex/keeper/integration_placelimitorder_test.go @@ -721,65 +721,3 @@ func (s *DexTestSuite) TestPlaceLimitOrderMixedTypes() { s.NotEqual(trancheKey2, trancheKey3, "GTC and JIT in same tranche") s.Equal(trancheKey4, trancheKey3, "GTCs not combined") } - -func (s *DexTestSuite) TestTrancheKeysLexicographicOrdering() { - tomorrow := time.Now().AddDate(0, 0, 1) - - // GIVEN - // bob has 10 units of TokenA for 10 GTT orders; alice has 1 - s.fundBobBalances(10, 0) - s.fundAliceBalances(1, 0) - // carol (taker) has 3 units of TokenB to sweep exactly 3 maker tranches - s.fundCarolBalances(0, 3) - - bobEarlyKey0 := s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...0 - bobEarlyKey1 := s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...1 - aliceKey := s.aliceLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...2 - s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...3 - s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...4 - s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...5 - s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...6 - s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...7 - s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...8 - s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...9 - bobLateKey := s.bobLimitSellsGoodTil("TokenA", 0, 1, tomorrow) // tk-...10 - - s.Assert().Equal(types.NewTrancheKey(0), bobEarlyKey0) - s.Assert().Equal(types.NewTrancheKey(1), bobEarlyKey1) - s.Assert().Equal(types.NewTrancheKey(2), aliceKey) - s.Assert().Equal(types.NewTrancheKey(10), bobLateKey) - - // all 11 orders are visible in the orderbook - s.assertLimitLiquidityAtTick("TokenA", 0, 11) - - // WHEN carol submits FILL_OR_KILL for 3 TokenB - // the swap iterator walks the KV store in key order: - // tk-...0 → tk-...1 → tk-...2 → … - // so the three oldest tranches are consumed - s.carolLimitSells("TokenB", -1, 3, types.LimitOrderType_FILL_OR_KILL) - - // THEN only 8 TokenA worth of maker liquidity remains - s.assertLimitLiquidityAtTick("TokenA", 0, 8) - - // bob's two orders (tk-...0 and tk-...1) were filled - s.bobWithdrawsLimitSell(bobEarlyKey0) - s.bobWithdrawsLimitSell(bobEarlyKey1) - s.assertBobBalances(0, 2) - - // alice's order (tk-...2) was the 3rd filled - s.aliceWithdrawsLimitSell(aliceKey) - s.assertAliceBalances(0, 1) - - // bob's late order (tk-...10) was NOT filled: it still sits in the active store with full - // maker reserves because it sorts after alice's key - bobLateTranche := s.App.DexKeeper.GetLimitOrderTranche(s.Ctx, &types.LimitOrderTrancheKey{ - TradePairId: defaultTradePairID1To0, - TickIndexTakerToMaker: 0, - TrancheKey: bobLateKey, - }) - s.Require().NotNil(bobLateTranche, "late-placed tranche must still exist in active store") - s.Assert().True( - bobLateTranche.ReservesMakerDenom.Equal(sdkmath.NewInt(1).Mul(denomMultiple)), - "late-placed order (tk-...10) must be unfilled while earlier orders remain", - ) -} diff --git a/x/dex/migrations/v9/store_test.go b/x/dex/migrations/v9/store_test.go index 133c9e437..bf887452f 100644 --- a/x/dex/migrations/v9/store_test.go +++ b/x/dex/migrations/v9/store_test.go @@ -5,10 +5,12 @@ import ( "time" sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/stretchr/testify/require" "github.com/stretchr/testify/suite" "github.com/neutron-org/neutron/v11/testutil" + "github.com/neutron-org/neutron/v11/x/dex/keeper" v9 "github.com/neutron-org/neutron/v11/x/dex/migrations/v9" dextypes "github.com/neutron-org/neutron/v11/x/dex/types" ) @@ -480,3 +482,105 @@ func (suite *V9DexMigrationTestSuite) TestReconstructLoTrancheUserLists() { require.True(t, found, "obsolete base-36 tranche user must still exist") require.Equal(t, "5atwxq41kck", untouched.TrancheKey, "obsolete base-36 key must not be rewritten") } + +// TestProperOrderingAfterReconstruction demonstrates the plain-decimal "tk-N" lexicographic sorting +// bug and verifies that ReconstructTrancheKeys fixes ordering via tk-Uint64ToSortableString(N). +func (suite *V9DexMigrationTestSuite) TestProperOrderingAfterReconstruction() { + app := suite.GetNeutronZoneApp(suite.ChainA) + ctx := suite.ChainA.GetContext().WithChainID("neutron-1") + t := suite.T() + + pairID := dextypes.MustNewTradePairID( + "ibc/B559A80D62249C8AA07A380E2A2BEA6E5CA9A6F079C912C3A9E9B494105E4F81", + "factory/neutron1frc0p5czd9uaaymdkug2njz7dc7j65jxukp9apmt9260a8egujkspms2t2/udntrn", + ) + const tickIndex int64 = -43028 + + // insert both kind of keys (tk-N and obsolete base-36 build of height and gas) in mixed order + insertOrder := []string{ + "tk-11", + "57mgzl47if5", + "tk-2", + "5f2w8k1m9q3", + "tk-10", + "57m0a14awvr", + "tk-9", + "5atwxq41kck", + "tk-1", + "57n5z9l5d18", + } + for _, trancheKey := range insertOrder { + app.DexKeeper.SetLimitOrderTranche(ctx, dextypes.MustNewLimitOrderTranche( + pairID.MakerDenom, + pairID.TakerDenom, + trancheKey, + tickIndex, + sdkmath.NewInt(1), + sdkmath.ZeroInt(), + sdkmath.NewInt(1), + sdkmath.ZeroInt(), + )) + } + require.Len(t, app.DexKeeper.GetAllTickLiquidity(ctx), len(insertOrder)) + + // ── pre-migration: sorting bug for plain decimal "tk-N" keys ───────────────── + + preMigrationExpected := []string{ + "57m0a14awvr", + "57mgzl47if5", + "57n5z9l5d18", + "5atwxq41kck", + "5f2w8k1m9q3", + "tk-1", + "tk-10", // the bug is that this + "tk-11", // and this + "tk-2", // come before this + "tk-9", // and this + } + preMigrationIterated := collectTrancheKeysViaIterator(&app.DexKeeper, ctx, pairID) + require.Equal(t, preMigrationExpected, preMigrationIterated, + "pre-migration iterator should follow raw lexicographic key order") + + // ── run migration ───────────────────────────────────────────────────────── + + require.NoError(t, v9.ReconstructTrancheKeys(ctx, app.AppCodec(), app.DexKeeper)) + + postMigrationExpected := []string{ + "57m0a14awvr", + "57mgzl47if5", + "57n5z9l5d18", + "5atwxq41kck", + "5f2w8k1m9q3", + dextypes.NewTrancheKey(1), + dextypes.NewTrancheKey(2), + dextypes.NewTrancheKey(9), + dextypes.NewTrancheKey(10), + dextypes.NewTrancheKey(11), + } + postMigrationIterated := collectTrancheKeysViaIterator(&app.DexKeeper, ctx, pairID) + require.Equal(t, postMigrationExpected, postMigrationIterated, + "post-migration iterator must follow corrected lexicographic key order") +} + +func collectTrancheKeysViaIterator( + k *keeper.Keeper, + ctx sdk.Context, + tradePairID *dextypes.TradePairID, +) []string { + liqIter := k.NewLiquidityIterator(ctx, tradePairID) + defer liqIter.Close() + + var keys []string + for { + liq := liqIter.Next() + if liq == nil { + break + } + tranche, ok := liq.(*dextypes.LimitOrderTranche) + if !ok { + continue + } + keys = append(keys, tranche.Key.TrancheKey) + } + return keys +}