diff --git a/x/dex/keeper/migrations.go b/x/dex/keeper/migrations.go index f08b14797..ef9a8c2bb 100644 --- a/x/dex/keeper/migrations.go +++ b/x/dex/keeper/migrations.go @@ -4,6 +4,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" v3 "github.com/neutron-org/neutron/v4/x/dex/migrations/v3" + v4 "github.com/neutron-org/neutron/v4/x/dex/migrations/v4" ) // Migrator is a struct for handling in-place store migrations. @@ -20,3 +21,8 @@ func NewMigrator(keeper Keeper) Migrator { func (m Migrator) Migrate2to3(ctx sdk.Context) error { return v3.MigrateStore(ctx, m.keeper.cdc, m.keeper.storeKey) } + +// Migrate2to3 migrates from version 3 to 4. +func (m Migrator) Migrate3to4(ctx sdk.Context) error { + return v4.MigrateStore(ctx, m.keeper.cdc, m.keeper.storeKey) +} diff --git a/x/dex/migrations/v3/store.go b/x/dex/migrations/v3/store.go index 5180eeae7..bb26392f1 100644 --- a/x/dex/migrations/v3/store.go +++ b/x/dex/migrations/v3/store.go @@ -3,6 +3,7 @@ package v3 import ( "errors" + errorsmod "cosmossdk.io/errors" "cosmossdk.io/store/prefix" storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/codec" @@ -15,11 +16,14 @@ import ( // MigrateStore performs in-place store migrations. // The migration adds new dex params -- GoodTilPurgeAllowance & MaxJITsPerBlock// for handling JIT orders. func MigrateStore(ctx sdk.Context, cdc codec.BinaryCodec, storeKey storetypes.StoreKey) error { - err := migrateParams(ctx, cdc, storeKey) - if err != nil { + if err := migrateParams(ctx, cdc, storeKey); err != nil { + return err + } + + if err := migrateLimitOrderExpirations(ctx, cdc, storeKey); err != nil { return err } - return migrateLimitOrderExpirations(ctx, cdc, storeKey) + return nil } func migrateParams(ctx sdk.Context, cdc codec.BinaryCodec, storeKey storetypes.StoreKey) error { @@ -72,7 +76,7 @@ func migrateLimitOrderExpirations(ctx sdk.Context, cdc codec.BinaryCodec, storeK err := iterator.Close() if err != nil { - return err + return errorsmod.Wrap(err, "iterator failed to close during migration") } for i, key := range expirationKeys { diff --git a/x/dex/migrations/v4/store.go b/x/dex/migrations/v4/store.go new file mode 100644 index 000000000..61dd9a922 --- /dev/null +++ b/x/dex/migrations/v4/store.go @@ -0,0 +1,112 @@ +package v4 + +import ( + errorsmod "cosmossdk.io/errors" + "cosmossdk.io/store/prefix" + storetypes "cosmossdk.io/store/types" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + + "github.com/neutron-org/neutron/v4/x/dex/types" +) + +// MigrateStore performs in-place store migrations. +// Due to change in precision of PrecDec between v3 and v4 we need to recompute all PrecDecs in the kvstore +func MigrateStore(ctx sdk.Context, cdc codec.BinaryCodec, storeKey storetypes.StoreKey) error { + if err := migrateTickLiquidityPrices(ctx, cdc, storeKey); err != nil { + return err + } + + if err := migrateInactiveTranchePrices(ctx, cdc, storeKey); err != nil { + return err + } + + return nil +} + +type migrationUpdate struct { + key []byte + val []byte +} + +func migrateTickLiquidityPrices(ctx sdk.Context, cdc codec.BinaryCodec, storeKey storetypes.StoreKey) error { + // Due to change in precision of PrecDec between v2 and v3 we need to recompute all PrecDecs in the kvstore + ctx.Logger().Info("Migrating TickLiquidity Prices...") + + // Iterate through all tickLiquidity + store := prefix.NewStore(ctx.KVStore(storeKey), types.KeyPrefix(types.TickLiquidityKeyPrefix)) + iterator := storetypes.KVStorePrefixIterator(store, []byte{}) + ticksToUpdate := make([]migrationUpdate, 0) + + for ; iterator.Valid(); iterator.Next() { + var tickLiq types.TickLiquidity + var updatedTickLiq types.TickLiquidity + cdc.MustUnmarshal(iterator.Value(), &tickLiq) + // Recalculate all prices + switch liquidity := tickLiq.Liquidity.(type) { + case *types.TickLiquidity_LimitOrderTranche: + liquidity.LimitOrderTranche.PriceTakerToMaker = types.MustCalcPrice(liquidity.LimitOrderTranche.Key.TickIndexTakerToMaker) + updatedTickLiq = types.TickLiquidity{Liquidity: liquidity} + case *types.TickLiquidity_PoolReserves: + poolReservesKey := liquidity.PoolReserves.Key + liquidity.PoolReserves.PriceTakerToMaker = types.MustCalcPrice(poolReservesKey.TickIndexTakerToMaker) + liquidity.PoolReserves.PriceOppositeTakerToMaker = poolReservesKey.Counterpart().MustPriceTakerToMaker() + updatedTickLiq = types.TickLiquidity{Liquidity: liquidity} + + default: + panic("Tick does not contain valid liqudityType") + } + + bz := cdc.MustMarshal(&updatedTickLiq) + ticksToUpdate = append(ticksToUpdate, migrationUpdate{key: iterator.Key(), val: bz}) + + } + + err := iterator.Close() + if err != nil { + return errorsmod.Wrap(err, "iterator failed to close during migration") + } + + // Store the updated TickLiquidity + for _, v := range ticksToUpdate { + store.Set(v.key, v.val) + } + + ctx.Logger().Info("Finished migrating TickLiquidity Prices...") + + return nil +} + +func migrateInactiveTranchePrices(ctx sdk.Context, cdc codec.BinaryCodec, storeKey storetypes.StoreKey) error { + // Due to change in precision of PrecDec between v2 and v3 we need to recompute all PrecDecs in the kvstore + ctx.Logger().Info("Migrating InactiveLimitOrderTranche Prices...") + + // Iterate through all InactiveTranches + store := prefix.NewStore(ctx.KVStore(storeKey), types.KeyPrefix(types.InactiveLimitOrderTrancheKeyPrefix)) + iterator := storetypes.KVStorePrefixIterator(store, []byte{}) + ticksToUpdate := make([]migrationUpdate, 0) + + for ; iterator.Valid(); iterator.Next() { + var tranche types.LimitOrderTranche + cdc.MustUnmarshal(iterator.Value(), &tranche) + // Recalculate price + tranche.PriceTakerToMaker = types.MustCalcPrice(tranche.Key.TickIndexTakerToMaker) + + bz := cdc.MustMarshal(&tranche) + ticksToUpdate = append(ticksToUpdate, migrationUpdate{key: iterator.Key(), val: bz}) + } + + err := iterator.Close() + if err != nil { + return errorsmod.Wrap(err, "iterator failed to close during migration") + } + + // Store the updated InactiveTranches + for _, v := range ticksToUpdate { + store.Set(v.key, v.val) + } + + ctx.Logger().Info("Finished migrating InactiveLimitOrderTranche Prices...") + + return nil +} diff --git a/x/dex/migrations/v4/store_test.go b/x/dex/migrations/v4/store_test.go new file mode 100644 index 000000000..10aa1bded --- /dev/null +++ b/x/dex/migrations/v4/store_test.go @@ -0,0 +1,73 @@ +package v4_test + +import ( + "testing" + + "github.com/stretchr/testify/suite" + + "github.com/neutron-org/neutron/v4/testutil" + "github.com/neutron-org/neutron/v4/utils/math" + v4 "github.com/neutron-org/neutron/v4/x/dex/migrations/v4" + "github.com/neutron-org/neutron/v4/x/dex/types" +) + +type V4DexMigrationTestSuite struct { + testutil.IBCConnectionTestSuite +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(V4DexMigrationTestSuite)) +} + +func (suite *V4DexMigrationTestSuite) TestPriceUpdates() { + var ( + app = suite.GetNeutronZoneApp(suite.ChainA) + storeKey = app.GetKey(types.StoreKey) + ctx = suite.ChainA.GetContext() + cdc = app.AppCodec() + ) + + // Write tranche with incorrect price + trancheKey := &types.LimitOrderTrancheKey{ + TradePairId: types.MustNewTradePairID("TokenA", "TokenB"), + TickIndexTakerToMaker: -50, + TrancheKey: "123", + } + tranche := &types.LimitOrderTranche{ + Key: trancheKey, + PriceTakerToMaker: math.ZeroPrecDec(), + } + app.DexKeeper.SetLimitOrderTranche(ctx, tranche) + + // also create inactive tranche + app.DexKeeper.SetInactiveLimitOrderTranche(ctx, tranche) + + // Write poolReserves with incorrect prices + poolKey := &types.PoolReservesKey{ + TradePairId: types.MustNewTradePairID("TokenA", "TokenB"), + TickIndexTakerToMaker: 60000, + Fee: 1, + } + poolReserves := &types.PoolReserves{ + Key: poolKey, + PriceTakerToMaker: math.ZeroPrecDec(), + PriceOppositeTakerToMaker: math.ZeroPrecDec(), + } + app.DexKeeper.SetPoolReserves(ctx, poolReserves) + + // Run migration + suite.NoError(v4.MigrateStore(ctx, cdc, storeKey)) + + // Check LimitOrderTranche has correct price + newTranche := app.DexKeeper.GetLimitOrderTranche(ctx, trancheKey) + suite.True(newTranche.PriceTakerToMaker.Equal(math.MustNewPrecDecFromStr("1.005012269623051203500693815"))) + + // check InactiveLimitOrderTranche has correct price + inactiveTranche, _ := app.DexKeeper.GetInactiveLimitOrderTranche(ctx, trancheKey) + suite.True(inactiveTranche.PriceTakerToMaker.Equal(math.MustNewPrecDecFromStr("1.005012269623051203500693815"))) + + // Check PoolReserves has the correct prices + newPool, _ := app.DexKeeper.GetPoolReserves(ctx, poolKey) + suite.True(newPool.PriceTakerToMaker.Equal(math.MustNewPrecDecFromStr("0.002479495864288162666675923"))) + suite.True(newPool.PriceOppositeTakerToMaker.Equal(math.MustNewPrecDecFromStr("403.227141612124702272520931931"))) +} diff --git a/x/dex/module.go b/x/dex/module.go index 3e1e31136..f2fa6cb52 100644 --- a/x/dex/module.go +++ b/x/dex/module.go @@ -158,6 +158,10 @@ func (am AppModule) RegisterServices(cfg module.Configurator) { if err := cfg.RegisterMigration(types.ModuleName, 2, m.Migrate2to3); err != nil { panic(fmt.Sprintf("failed to migrate x/dex from version 2 to 3: %v", err)) } + + if err := cfg.RegisterMigration(types.ModuleName, 3, m.Migrate3to4); err != nil { + panic(fmt.Sprintf("failed to migrate x/dex from version 3 to 4: %v", err)) + } } // RegisterInvariants registers the capability module's invariants.