diff --git a/protocol/app/upgrades.go b/protocol/app/upgrades.go index e85b7fbca1..51a6dc9e39 100644 --- a/protocol/app/upgrades.go +++ b/protocol/app/upgrades.go @@ -3,10 +3,11 @@ package app import ( "fmt" + v5_0_0 "github.com/dydxprotocol/v4-chain/protocol/app/upgrades/v5.0.0" + upgradetypes "cosmossdk.io/x/upgrade/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/dydxprotocol/v4-chain/protocol/app/upgrades" - v5_0_0 "github.com/dydxprotocol/v4-chain/protocol/app/upgrades/v5.0.0" ) var ( @@ -29,6 +30,7 @@ func (app *App) setupUpgradeHandlers() { v5_0_0.CreateUpgradeHandler( app.ModuleManager, app.configurator, + app.PerpetualsKeeper, ), ) } diff --git a/protocol/app/upgrades/v5.0.0/upgrade.go b/protocol/app/upgrades/v5.0.0/upgrade.go index 4fd015ceb3..56f78e72c5 100644 --- a/protocol/app/upgrades/v5.0.0/upgrade.go +++ b/protocol/app/upgrades/v5.0.0/upgrade.go @@ -4,19 +4,44 @@ import ( "context" "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + + perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + upgradetypes "cosmossdk.io/x/upgrade/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/dydxprotocol/v4-chain/protocol/lib" ) +// Set all existing perpetuals to cross market type +func perpetualsUpgrade( + ctx sdk.Context, + perpetualsKeeper perptypes.PerpetualsKeeper, +) { + // Set all perpetuals to cross market type + perpetuals := perpetualsKeeper.GetAllPerpetuals(ctx) + for _, p := range perpetuals { + _, err := perpetualsKeeper.SetPerpetualMarketType( + ctx, p.GetId(), + perptypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS) + if err != nil { + panic(fmt.Sprintf("failed to set perpetual market type for perpetual %d: %s", p.GetId(), err)) + } + } +} + func CreateUpgradeHandler( mm *module.Manager, configurator module.Configurator, + perpetualsKeeper perptypes.PerpetualsKeeper, ) upgradetypes.UpgradeHandler { return func(ctx context.Context, plan upgradetypes.Plan, vm module.VersionMap) (module.VersionMap, error) { sdkCtx := lib.UnwrapSDKContext(ctx, "app/upgrades") sdkCtx.Logger().Info(fmt.Sprintf("Running %s Upgrade...", UpgradeName)) + // Set all perpetuals to cross market type + perpetualsUpgrade(sdkCtx, perpetualsKeeper) + // TODO(TRA-93): Initialize `x/vault` module. return mm.RunMigrations(ctx, configurator, vm) diff --git a/protocol/mocks/PerpetualsKeeper.go b/protocol/mocks/PerpetualsKeeper.go index 46a4afac45..a82605b354 100644 --- a/protocol/mocks/PerpetualsKeeper.go +++ b/protocol/mocks/PerpetualsKeeper.go @@ -70,6 +70,22 @@ func (_m *PerpetualsKeeper) GetAddPremiumVotes(ctx types.Context) *perpetualstyp return r0 } +// GetAllPerpetuals provides a mock function with given fields: ctx +func (_m *PerpetualsKeeper) GetAllPerpetuals(ctx types.Context) []perpetualstypes.Perpetual { + ret := _m.Called(ctx) + + var r0 []perpetualstypes.Perpetual + if rf, ok := ret.Get(0).(func(types.Context) []perpetualstypes.Perpetual); ok { + r0 = rf(ctx) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]perpetualstypes.Perpetual) + } + } + + return r0 +} + // GetMarginRequirements provides a mock function with given fields: ctx, id, bigQuantums func (_m *PerpetualsKeeper) GetMarginRequirements(ctx types.Context, id uint32, bigQuantums *big.Int) (*big.Int, *big.Int, error) { ret := _m.Called(ctx, id, bigQuantums) @@ -283,6 +299,30 @@ func (_m *PerpetualsKeeper) SetParams(ctx types.Context, params perpetualstypes. return r0 } +// SetPerpetualMarketType provides a mock function with given fields: ctx, id, marketType +func (_m *PerpetualsKeeper) SetPerpetualMarketType(ctx types.Context, id uint32, marketType perpetualstypes.PerpetualMarketType) (perpetualstypes.Perpetual, error) { + ret := _m.Called(ctx, id, marketType) + + var r0 perpetualstypes.Perpetual + var r1 error + if rf, ok := ret.Get(0).(func(types.Context, uint32, perpetualstypes.PerpetualMarketType) (perpetualstypes.Perpetual, error)); ok { + return rf(ctx, id, marketType) + } + if rf, ok := ret.Get(0).(func(types.Context, uint32, perpetualstypes.PerpetualMarketType) perpetualstypes.Perpetual); ok { + r0 = rf(ctx, id, marketType) + } else { + r0 = ret.Get(0).(perpetualstypes.Perpetual) + } + + if rf, ok := ret.Get(1).(func(types.Context, uint32, perpetualstypes.PerpetualMarketType) error); ok { + r1 = rf(ctx, id, marketType) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + type mockConstructorTestingTNewPerpetualsKeeper interface { mock.TestingT Cleanup(func()) diff --git a/protocol/x/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index f80705a29c..0e834bcb40 100644 --- a/protocol/x/perpetuals/keeper/perpetual.go +++ b/protocol/x/perpetuals/keeper/perpetual.go @@ -111,7 +111,7 @@ func (k Keeper) CreatePerpetual( } // Store the new perpetual. - k.setPerpetual(ctx, perpetual) + k.SetPerpetual(ctx, perpetual) k.SetEmptyPremiumSamples(ctx) k.SetEmptyPremiumVotes(ctx) @@ -165,7 +165,7 @@ func (k Keeper) ModifyPerpetual( } // Store the modified perpetual. - k.setPerpetual(ctx, perpetual) + k.SetPerpetual(ctx, perpetual) // Emit indexer event. k.GetIndexerEventManager().AddTxnEvent( @@ -186,6 +186,40 @@ func (k Keeper) ModifyPerpetual( return perpetual, nil } +func (k Keeper) SetPerpetualMarketType( + ctx sdk.Context, + perpetualId uint32, + marketType types.PerpetualMarketType, +) (types.Perpetual, error) { + if marketType == types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_UNSPECIFIED { + return types.Perpetual{}, errorsmod.Wrap( + types.ErrInvalidMarketType, + fmt.Sprintf("invalid market type %v for perpetual %d", marketType, perpetualId), + ) + } + + // Get perpetual. + perpetual, err := k.GetPerpetual(ctx, perpetualId) + if err != nil { + return perpetual, err + } + + if perpetual.Params.MarketType != types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_UNSPECIFIED { + return types.Perpetual{}, errorsmod.Wrap( + types.ErrInvalidMarketType, + fmt.Sprintf("perpetual %d already has market type %v", perpetualId, perpetual.Params.MarketType), + ) + } + + // Modify perpetual. + perpetual.Params.MarketType = marketType + + // Store the modified perpetual. + k.SetPerpetual(ctx, perpetual) + + return perpetual, nil +} + // GetPerpetual returns a perpetual from its id. func (k Keeper) GetPerpetual( ctx sdk.Context, @@ -1181,7 +1215,7 @@ func (k Keeper) ModifyFundingIndex( bigFundingIndex.Add(bigFundingIndex, bigFundingIndexDelta) perpetual.FundingIndex = dtypes.NewIntFromBigInt(bigFundingIndex) - k.setPerpetual(ctx, perpetual) + k.SetPerpetual(ctx, perpetual) return nil } @@ -1207,7 +1241,7 @@ func (k Keeper) SetEmptyPremiumVotes( ) } -func (k Keeper) setPerpetual( +func (k Keeper) SetPerpetual( ctx sdk.Context, perpetual types.Perpetual, ) { diff --git a/protocol/x/perpetuals/keeper/perpetual_test.go b/protocol/x/perpetuals/keeper/perpetual_test.go index fbae544a07..5cef637e1e 100644 --- a/protocol/x/perpetuals/keeper/perpetual_test.go +++ b/protocol/x/perpetuals/keeper/perpetual_test.go @@ -320,6 +320,84 @@ func TestModifyPerpetual_Failure(t *testing.T) { } } +func TestSetPerpetualMarketType(t *testing.T) { + tests := map[string]struct { + currType types.PerpetualMarketType + newType types.PerpetualMarketType + errorExpected bool + expectedError error + }{ + "success": { + currType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_UNSPECIFIED, + newType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + errorExpected: false, + }, + "failure - setting to unspecified": { + currType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + newType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_UNSPECIFIED, + errorExpected: true, + expectedError: errorsmod.Wrap( + types.ErrInvalidMarketType, + fmt.Sprintf( + "invalid market type %v for perpetual %d", + types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_UNSPECIFIED, 0, + ), + ), + }, + "failure - market type already set": { + currType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED, + newType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + errorExpected: true, + expectedError: errorsmod.Wrap( + types.ErrInvalidMarketType, + fmt.Sprintf( + "perpetual %d already has market type %v", + 0, + types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED, + ), + ), + }, + } + + // Test setup. + for name, tc := range tests { + t.Run( + name, func(t *testing.T) { + pc := keepertest.PerpetualsKeepers(t) + // Create liquidity tiers and perpetuals, + perp := keepertest.CreateLiquidityTiersAndNPerpetuals( + t, + pc.Ctx, + pc.PerpetualsKeeper, + pc.PricesKeeper, + 1, + )[0] + perp.Params.MarketType = tc.currType + pc.PerpetualsKeeper.SetPerpetual(pc.Ctx, perp) + + _, err := pc.PerpetualsKeeper.SetPerpetualMarketType( + pc.Ctx, + perp.Params.Id, + tc.newType, + ) + + if tc.errorExpected { + require.EqualError(t, err, tc.expectedError.Error()) + } else { + require.NoError(t, err) + + rst, err := pc.PerpetualsKeeper.GetPerpetual( + pc.Ctx, + perp.Params.Id, + ) + require.NoError(t, err) + require.Equal(t, tc.newType, rst.Params.MarketType) + } + }, + ) + } +} + func TestGetPerpetual_Success(t *testing.T) { pc := keepertest.PerpetualsKeepers(t) // Create liquidity tiers and perpetuals, diff --git a/protocol/x/perpetuals/types/types.go b/protocol/x/perpetuals/types/types.go index 48634398a4..84cfaacd6e 100644 --- a/protocol/x/perpetuals/types/types.go +++ b/protocol/x/perpetuals/types/types.go @@ -103,4 +103,12 @@ type PerpetualsKeeper interface { ctx sdk.Context, params Params, ) error + SetPerpetualMarketType( + ctx sdk.Context, + id uint32, + marketType PerpetualMarketType, + ) (Perpetual, error) + GetAllPerpetuals( + ctx sdk.Context, + ) []Perpetual }