Skip to content

Commit

Permalink
Merge pull request #582 from neutron-org/fix/price_migration
Browse files Browse the repository at this point in the history
FIX: Add a migration to recalculate price to use higher prec_dec precision
  • Loading branch information
pr0n00gler authored Jun 21, 2024
2 parents a73edfc + c9d55c5 commit 57371da
Show file tree
Hide file tree
Showing 5 changed files with 203 additions and 4 deletions.
6 changes: 6 additions & 0 deletions x/dex/keeper/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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)
}
12 changes: 8 additions & 4 deletions x/dex/migrations/v3/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
112 changes: 112 additions & 0 deletions x/dex/migrations/v4/store.go
Original file line number Diff line number Diff line change
@@ -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
}
73 changes: 73 additions & 0 deletions x/dex/migrations/v4/store_test.go
Original file line number Diff line number Diff line change
@@ -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")))
}
4 changes: 4 additions & 0 deletions x/dex/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 57371da

Please sign in to comment.