diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.rpc.msg.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.rpc.msg.ts index 170cf4f3bf..61a0bdf54b 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.rpc.msg.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.rpc.msg.ts @@ -1,6 +1,6 @@ import { Rpc } from "../../helpers"; import * as _m0 from "protobufjs/minimal"; -import { MsgSetMarketsHardCap, MsgSetMarketsHardCapResponse, MsgCreateMarketPermissionless, MsgCreateMarketPermissionlessResponse, MsgSetListingVaultDepositParams, MsgSetListingVaultDepositParamsResponse } from "./tx"; +import { MsgSetMarketsHardCap, MsgSetMarketsHardCapResponse, MsgCreateMarketPermissionless, MsgCreateMarketPermissionlessResponse, MsgSetListingVaultDepositParams, MsgSetListingVaultDepositParamsResponse, MsgUpgradeIsolatedPerpetualToCross, MsgUpgradeIsolatedPerpetualToCrossResponse } from "./tx"; /** Msg defines the Msg service. */ export interface Msg { @@ -12,6 +12,12 @@ export interface Msg { /** SetListingVaultDepositParams sets PML megavault deposit params */ setListingVaultDepositParams(request: MsgSetListingVaultDepositParams): Promise; + /** + * UpgradeIsolatedPerpetualToCross upgrades a perpetual from isolated to cross + * margin + */ + + upgradeIsolatedPerpetualToCross(request: MsgUpgradeIsolatedPerpetualToCross): Promise; } export class MsgClientImpl implements Msg { private readonly rpc: Rpc; @@ -21,6 +27,7 @@ export class MsgClientImpl implements Msg { this.setMarketsHardCap = this.setMarketsHardCap.bind(this); this.createMarketPermissionless = this.createMarketPermissionless.bind(this); this.setListingVaultDepositParams = this.setListingVaultDepositParams.bind(this); + this.upgradeIsolatedPerpetualToCross = this.upgradeIsolatedPerpetualToCross.bind(this); } setMarketsHardCap(request: MsgSetMarketsHardCap): Promise { @@ -41,4 +48,10 @@ export class MsgClientImpl implements Msg { return promise.then(data => MsgSetListingVaultDepositParamsResponse.decode(new _m0.Reader(data))); } + upgradeIsolatedPerpetualToCross(request: MsgUpgradeIsolatedPerpetualToCross): Promise { + const data = MsgUpgradeIsolatedPerpetualToCross.encode(request).finish(); + const promise = this.rpc.request("dydxprotocol.listing.Msg", "UpgradeIsolatedPerpetualToCross", data); + return promise.then(data => MsgUpgradeIsolatedPerpetualToCrossResponse.decode(new _m0.Reader(data))); + } + } \ No newline at end of file diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.ts index 7e8d14a123..111d29fc37 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/tx.ts @@ -100,6 +100,40 @@ export interface MsgSetListingVaultDepositParamsResponse {} */ export interface MsgSetListingVaultDepositParamsResponseSDKType {} +/** + * MsgUpgradeIsolatedPerpetualToCross is used to upgrade a market from + * isolated margin to cross margin. + */ + +export interface MsgUpgradeIsolatedPerpetualToCross { + authority: string; + /** ID of the perpetual to be upgraded to CROSS */ + + perpetualId: number; +} +/** + * MsgUpgradeIsolatedPerpetualToCross is used to upgrade a market from + * isolated margin to cross margin. + */ + +export interface MsgUpgradeIsolatedPerpetualToCrossSDKType { + authority: string; + /** ID of the perpetual to be upgraded to CROSS */ + + perpetual_id: number; +} +/** + * MsgUpgradeIsolatedPerpetualToCrossResponse defines the + * UpgradeIsolatedPerpetualToCross response type. + */ + +export interface MsgUpgradeIsolatedPerpetualToCrossResponse {} +/** + * MsgUpgradeIsolatedPerpetualToCrossResponse defines the + * UpgradeIsolatedPerpetualToCross response type. + */ + +export interface MsgUpgradeIsolatedPerpetualToCrossResponseSDKType {} function createBaseMsgSetMarketsHardCap(): MsgSetMarketsHardCap { return { @@ -366,4 +400,93 @@ export const MsgSetListingVaultDepositParamsResponse = { return message; } +}; + +function createBaseMsgUpgradeIsolatedPerpetualToCross(): MsgUpgradeIsolatedPerpetualToCross { + return { + authority: "", + perpetualId: 0 + }; +} + +export const MsgUpgradeIsolatedPerpetualToCross = { + encode(message: MsgUpgradeIsolatedPerpetualToCross, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + if (message.authority !== "") { + writer.uint32(10).string(message.authority); + } + + if (message.perpetualId !== 0) { + writer.uint32(16).uint32(message.perpetualId); + } + + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MsgUpgradeIsolatedPerpetualToCross { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgUpgradeIsolatedPerpetualToCross(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + case 1: + message.authority = reader.string(); + break; + + case 2: + message.perpetualId = reader.uint32(); + break; + + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(object: DeepPartial): MsgUpgradeIsolatedPerpetualToCross { + const message = createBaseMsgUpgradeIsolatedPerpetualToCross(); + message.authority = object.authority ?? ""; + message.perpetualId = object.perpetualId ?? 0; + return message; + } + +}; + +function createBaseMsgUpgradeIsolatedPerpetualToCrossResponse(): MsgUpgradeIsolatedPerpetualToCrossResponse { + return {}; +} + +export const MsgUpgradeIsolatedPerpetualToCrossResponse = { + encode(_: MsgUpgradeIsolatedPerpetualToCrossResponse, writer: _m0.Writer = _m0.Writer.create()): _m0.Writer { + return writer; + }, + + decode(input: _m0.Reader | Uint8Array, length?: number): MsgUpgradeIsolatedPerpetualToCrossResponse { + const reader = input instanceof _m0.Reader ? input : new _m0.Reader(input); + let end = length === undefined ? reader.len : reader.pos + length; + const message = createBaseMsgUpgradeIsolatedPerpetualToCrossResponse(); + + while (reader.pos < end) { + const tag = reader.uint32(); + + switch (tag >>> 3) { + default: + reader.skipType(tag & 7); + break; + } + } + + return message; + }, + + fromPartial(_: DeepPartial): MsgUpgradeIsolatedPerpetualToCrossResponse { + const message = createBaseMsgUpgradeIsolatedPerpetualToCrossResponse(); + return message; + } + }; \ No newline at end of file diff --git a/proto/dydxprotocol/listing/tx.proto b/proto/dydxprotocol/listing/tx.proto index 8f0808de9e..aa7d998452 100644 --- a/proto/dydxprotocol/listing/tx.proto +++ b/proto/dydxprotocol/listing/tx.proto @@ -22,6 +22,11 @@ service Msg { // SetListingVaultDepositParams sets PML megavault deposit params rpc SetListingVaultDepositParams(MsgSetListingVaultDepositParams) returns (MsgSetListingVaultDepositParamsResponse); + + // UpgradeIsolatedPerpetualToCross upgrades a perpetual from isolated to cross + // margin + rpc UpgradeIsolatedPerpetualToCross(MsgUpgradeIsolatedPerpetualToCross) + returns (MsgUpgradeIsolatedPerpetualToCrossResponse); } // MsgSetMarketsHardCap is used to set a hard cap on the number of markets @@ -69,4 +74,20 @@ message MsgSetListingVaultDepositParams { // MsgSetListingVaultDepositParamsResponse defines the // MsgSetListingVaultDepositParams response -message MsgSetListingVaultDepositParamsResponse {} \ No newline at end of file +message MsgSetListingVaultDepositParamsResponse {} + +// MsgUpgradeIsolatedPerpetualToCross is used to upgrade a market from +// isolated margin to cross margin. +message MsgUpgradeIsolatedPerpetualToCross { + // Authority is the address that controls the module. + option (cosmos.msg.v1.signer) = "authority"; + + string authority = 1 [ (cosmos_proto.scalar) = "cosmos.AddressString" ]; + + // ID of the perpetual to be upgraded to CROSS + uint32 perpetual_id = 2; +} + +// MsgUpgradeIsolatedPerpetualToCrossResponse defines the +// UpgradeIsolatedPerpetualToCross response type. +message MsgUpgradeIsolatedPerpetualToCrossResponse {} diff --git a/protocol/app/app.go b/protocol/app/app.go index 89a10ce846..042bac91ea 100644 --- a/protocol/app/app.go +++ b/protocol/app/app.go @@ -1242,6 +1242,7 @@ func New( app.ClobKeeper, &app.MarketMapKeeper, app.PerpetualsKeeper, + app.SubaccountsKeeper, app.VaultKeeper, ) listingModule := listingmodule.NewAppModule( diff --git a/protocol/app/msgs/all_msgs.go b/protocol/app/msgs/all_msgs.go index 2369ef1056..9a6e072df0 100644 --- a/protocol/app/msgs/all_msgs.go +++ b/protocol/app/msgs/all_msgs.go @@ -228,12 +228,14 @@ var ( "/dydxprotocol.govplus.MsgSlashValidatorResponse": {}, // listing - "/dydxprotocol.listing.MsgSetMarketsHardCap": {}, - "/dydxprotocol.listing.MsgSetMarketsHardCapResponse": {}, - "/dydxprotocol.listing.MsgCreateMarketPermissionless": {}, - "/dydxprotocol.listing.MsgCreateMarketPermissionlessResponse": {}, - "/dydxprotocol.listing.MsgSetListingVaultDepositParams": {}, - "/dydxprotocol.listing.MsgSetListingVaultDepositParamsResponse": {}, + "/dydxprotocol.listing.MsgSetMarketsHardCap": {}, + "/dydxprotocol.listing.MsgSetMarketsHardCapResponse": {}, + "/dydxprotocol.listing.MsgCreateMarketPermissionless": {}, + "/dydxprotocol.listing.MsgCreateMarketPermissionlessResponse": {}, + "/dydxprotocol.listing.MsgSetListingVaultDepositParams": {}, + "/dydxprotocol.listing.MsgSetListingVaultDepositParamsResponse": {}, + "/dydxprotocol.listing.MsgUpgradeIsolatedPerpetualToCross": {}, + "/dydxprotocol.listing.MsgUpgradeIsolatedPerpetualToCrossResponse": {}, // perpetuals "/dydxprotocol.perpetuals.MsgAddPremiumVotes": {}, diff --git a/protocol/app/msgs/internal_msgs.go b/protocol/app/msgs/internal_msgs.go index 9c0e58f9f6..8587ff5e4c 100644 --- a/protocol/app/msgs/internal_msgs.go +++ b/protocol/app/msgs/internal_msgs.go @@ -156,10 +156,12 @@ var ( "/dydxprotocol.govplus.MsgSlashValidatorResponse": nil, // listing - "/dydxprotocol.listing.MsgSetMarketsHardCap": &listing.MsgSetMarketsHardCap{}, - "/dydxprotocol.listing.MsgSetMarketsHardCapResponse": nil, - "/dydxprotocol.listing.MsgSetListingVaultDepositParams": &listing.MsgSetListingVaultDepositParams{}, - "/dydxprotocol.listing.MsgSetListingVaultDepositParamsResponse": nil, + "/dydxprotocol.listing.MsgSetMarketsHardCap": &listing.MsgSetMarketsHardCap{}, + "/dydxprotocol.listing.MsgSetMarketsHardCapResponse": nil, + "/dydxprotocol.listing.MsgSetListingVaultDepositParams": &listing.MsgSetListingVaultDepositParams{}, + "/dydxprotocol.listing.MsgSetListingVaultDepositParamsResponse": nil, + "/dydxprotocol.listing.MsgUpgradeIsolatedPerpetualToCross": &listing.MsgUpgradeIsolatedPerpetualToCross{}, + "/dydxprotocol.listing.MsgUpgradeIsolatedPerpetualToCrossResponse": nil, // perpetuals "/dydxprotocol.perpetuals.MsgCreatePerpetual": &perpetuals.MsgCreatePerpetual{}, diff --git a/protocol/app/msgs/internal_msgs_test.go b/protocol/app/msgs/internal_msgs_test.go index 70e3549fe5..00bcbdc912 100644 --- a/protocol/app/msgs/internal_msgs_test.go +++ b/protocol/app/msgs/internal_msgs_test.go @@ -116,6 +116,8 @@ func TestInternalMsgSamples_Gov_Key(t *testing.T) { "/dydxprotocol.listing.MsgSetListingVaultDepositParamsResponse", "/dydxprotocol.listing.MsgSetMarketsHardCap", "/dydxprotocol.listing.MsgSetMarketsHardCapResponse", + "/dydxprotocol.listing.MsgUpgradeIsolatedPerpetualToCross", + "/dydxprotocol.listing.MsgUpgradeIsolatedPerpetualToCrossResponse", // perpeutals "/dydxprotocol.perpetuals.MsgCreatePerpetual", diff --git a/protocol/lib/ante/internal_msg.go b/protocol/lib/ante/internal_msg.go index 4d578d4f10..3e1b44a917 100644 --- a/protocol/lib/ante/internal_msg.go +++ b/protocol/lib/ante/internal_msg.go @@ -103,6 +103,7 @@ func IsInternalMsg(msg sdk.Msg) bool { // listing *listing.MsgSetMarketsHardCap, *listing.MsgSetListingVaultDepositParams, + *listing.MsgUpgradeIsolatedPerpetualToCross, // perpetuals *perpetuals.MsgCreatePerpetual, diff --git a/protocol/mocks/ClobKeeper.go b/protocol/mocks/ClobKeeper.go index 2a7b5cfd9c..0bc8520214 100644 --- a/protocol/mocks/ClobKeeper.go +++ b/protocol/mocks/ClobKeeper.go @@ -332,26 +332,6 @@ func (_m *ClobKeeper) GetIndexerEventManager() indexer_manager.IndexerEventManag return r0 } -// GetInsuranceFundBalance provides a mock function with given fields: ctx, perpetualId -func (_m *ClobKeeper) GetInsuranceFundBalance(ctx types.Context, perpetualId uint32) *big.Int { - ret := _m.Called(ctx, perpetualId) - - if len(ret) == 0 { - panic("no return value specified for GetInsuranceFundBalance") - } - - var r0 *big.Int - if rf, ok := ret.Get(0).(func(types.Context, uint32) *big.Int); ok { - r0 = rf(ctx, perpetualId) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(*big.Int) - } - } - - return r0 -} - // GetLiquidationInsuranceFundDelta provides a mock function with given fields: ctx, subaccountId, perpetualId, isBuy, fillAmount, subticks func (_m *ClobKeeper) GetLiquidationInsuranceFundDelta(ctx types.Context, subaccountId subaccountstypes.SubaccountId, perpetualId uint32, isBuy bool, fillAmount uint64, subticks clobtypes.Subticks) (*big.Int, error) { ret := _m.Called(ctx, subaccountId, perpetualId, isBuy, fillAmount, subticks) diff --git a/protocol/testutil/keeper/listing.go b/protocol/testutil/keeper/listing.go index 98f8e7f69f..1448645130 100644 --- a/protocol/testutil/keeper/listing.go +++ b/protocol/testutil/keeper/listing.go @@ -11,9 +11,11 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/mocks" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + assetskeeper "github.com/dydxprotocol/v4-chain/protocol/x/assets/keeper" clobkeeper "github.com/dydxprotocol/v4-chain/protocol/x/clob/keeper" perpetualskeeper "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/keeper" priceskeeper "github.com/dydxprotocol/v4-chain/protocol/x/prices/keeper" + subaccountskeeper "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/keeper" vaultkeeper "github.com/dydxprotocol/v4-chain/protocol/x/vault/keeper" marketmapkeeper "github.com/skip-mev/connect/v2/x/marketmap/keeper" "github.com/stretchr/testify/mock" @@ -37,6 +39,9 @@ func ListingKeepers( perpetualsKeeper *perpetualskeeper.Keeper, clobKeeper *clobkeeper.Keeper, marketMapKeeper *marketmapkeeper.Keeper, + assetsKeeper *assetskeeper.Keeper, + bankKeeper_out *bankkeeper.BaseKeeper, + subaccountsKeeper *subaccountskeeper.Keeper, ) { ctx = initKeepers( t, func( @@ -63,13 +68,13 @@ func ListingKeepers( db, cdc, ) - bankKeeper, _ = createBankKeeper(stateStore, db, cdc, accountsKeeper) + bankKeeper_out, _ = createBankKeeper(stateStore, db, cdc, accountsKeeper) stakingKeeper, _ := createStakingKeeper( stateStore, db, cdc, accountsKeeper, - bankKeeper, + bankKeeper_out, ) statsKeeper, _ := createStatsKeeper( stateStore, @@ -113,7 +118,7 @@ func ListingKeepers( epochsKeeper, transientStoreKey, ) - assetsKeeper, _ := createAssetsKeeper( + assetsKeeper, _ = createAssetsKeeper( stateStore, db, cdc, @@ -126,19 +131,19 @@ func ListingKeepers( rewardsKeeper, _ := createRewardsKeeper( stateStore, assetsKeeper, - bankKeeper, + bankKeeper_out, feeTiersKeeper, pricesKeeper, indexerEventManager, db, cdc, ) - subaccountsKeeper, _ := createSubaccountsKeeper( + subaccountsKeeper, _ = createSubaccountsKeeper( stateStore, db, cdc, assetsKeeper, - bankKeeper, + bankKeeper_out, perpetualsKeeper, blockTimeKeeper, transientStoreKey, @@ -151,7 +156,7 @@ func ListingKeepers( memClob, assetsKeeper, blockTimeKeeper, - bankKeeper, + bankKeeper_out, feeTiersKeeper, perpetualsKeeper, pricesKeeper, @@ -173,6 +178,7 @@ func ListingKeepers( perpetualsKeeper, clobKeeper, marketMapKeeper, + subaccountsKeeper, vaultKeeper, ) @@ -180,7 +186,8 @@ func ListingKeepers( }, ) - return ctx, keeper, storeKey, mockTimeProvider, pricesKeeper, perpetualsKeeper, clobKeeper, marketMapKeeper + return ctx, keeper, storeKey, mockTimeProvider, pricesKeeper, perpetualsKeeper, + clobKeeper, marketMapKeeper, assetsKeeper, bankKeeper_out, subaccountsKeeper } func createListingKeeper( @@ -191,6 +198,7 @@ func createListingKeeper( perpetualsKeeper *perpetualskeeper.Keeper, clobKeeper *clobkeeper.Keeper, marketMapKeeper *marketmapkeeper.Keeper, + subaccountsKeeper *subaccountskeeper.Keeper, vaultkeeper *vaultkeeper.Keeper, ) ( *keeper.Keeper, @@ -211,6 +219,7 @@ func createListingKeeper( clobKeeper, marketMapKeeper, perpetualsKeeper, + subaccountsKeeper, vaultkeeper, ) diff --git a/protocol/x/clob/keeper/deleveraging.go b/protocol/x/clob/keeper/deleveraging.go index c088887747..a525deb521 100644 --- a/protocol/x/clob/keeper/deleveraging.go +++ b/protocol/x/clob/keeper/deleveraging.go @@ -18,7 +18,6 @@ import ( assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" perplib "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/lib" - perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) @@ -130,43 +129,6 @@ func (k Keeper) MaybeDeleverageSubaccount( return quantumsDeleveraged, err } -// GetInsuranceFundBalance returns the current balance of the specific insurance fund based on the -// perpetual (in quote quantums). -// This calls the Bank Keeper’s GetBalance() function for the Module Address of the insurance fund. -func (k Keeper) GetInsuranceFundBalance(ctx sdk.Context, perpetualId uint32) (balance *big.Int) { - usdcAsset, exists := k.assetsKeeper.GetAsset(ctx, assettypes.AssetUsdc.Id) - if !exists { - panic("GetInsuranceFundBalance: Usdc asset not found in state") - } - insuranceFundAddr, err := k.perpetualsKeeper.GetInsuranceFundModuleAddress(ctx, perpetualId) - if err != nil { - return nil - } - insuranceFundBalance := k.bankKeeper.GetBalance( - ctx, - insuranceFundAddr, - usdcAsset.Denom, - ) - - // Return as big.Int. - return insuranceFundBalance.Amount.BigInt() -} - -func (k Keeper) GetCrossInsuranceFundBalance(ctx sdk.Context) (balance *big.Int) { - usdcAsset, exists := k.assetsKeeper.GetAsset(ctx, assettypes.AssetUsdc.Id) - if !exists { - panic("GetCrossInsuranceFundBalance: Usdc asset not found in state") - } - insuranceFundBalance := k.bankKeeper.GetBalance( - ctx, - perptypes.InsuranceFundModuleAddress, - usdcAsset.Denom, - ) - - // Return as big.Int. - return insuranceFundBalance.Amount.BigInt() -} - // CanDeleverageSubaccount returns true if a subaccount can be deleveraged. // This function returns two booleans, shouldDeleverageAtBankruptcyPrice and shouldDeleverageAtOraclePrice. // - shouldDeleverageAtBankruptcyPrice is true if the subaccount has negative TNC. @@ -279,7 +241,7 @@ func (k Keeper) IsValidInsuranceFundDelta(ctx sdk.Context, insuranceFundDelta *b // The insurance fund delta is valid if the insurance fund balance is non-negative after adding // the delta. - currentInsuranceFundBalance := k.GetInsuranceFundBalance(ctx, perpetualId) + currentInsuranceFundBalance := k.subaccountsKeeper.GetInsuranceFundBalance(ctx, perpetualId) return new(big.Int).Add(currentInsuranceFundBalance, insuranceFundDelta).Sign() >= 0 } diff --git a/protocol/x/clob/keeper/deleveraging_test.go b/protocol/x/clob/keeper/deleveraging_test.go index d3e6cb1ff0..f36f730061 100644 --- a/protocol/x/clob/keeper/deleveraging_test.go +++ b/protocol/x/clob/keeper/deleveraging_test.go @@ -1,8 +1,6 @@ package keeper_test import ( - "errors" - "math" "math/big" "testing" "time" @@ -19,7 +17,6 @@ import ( keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" perptest "github.com/dydxprotocol/v4-chain/protocol/testutil/perpetuals" testutil "github.com/dydxprotocol/v4-chain/protocol/testutil/util" - assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types" "github.com/dydxprotocol/v4-chain/protocol/x/clob/memclob" "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" @@ -30,133 +27,6 @@ import ( "github.com/stretchr/testify/require" ) -func TestGetInsuranceFundBalance(t *testing.T) { - tests := map[string]struct { - // Setup - assets []assettypes.Asset - insuranceFundBalance *big.Int - perpetualId uint32 - perpetual *perptypes.Perpetual - - // Expectations. - expectedInsuranceFundBalance *big.Int - expectedError error - }{ - "can get zero balance": { - assets: []assettypes.Asset{ - *constants.Usdc, - }, - perpetualId: 0, - insuranceFundBalance: new(big.Int), - expectedInsuranceFundBalance: big.NewInt(0), - }, - "can get positive balance": { - assets: []assettypes.Asset{ - *constants.Usdc, - }, - perpetualId: 0, - insuranceFundBalance: big.NewInt(100), - expectedInsuranceFundBalance: big.NewInt(100), - }, - "can get greater than MaxUint64 balance": { - assets: []assettypes.Asset{ - *constants.Usdc, - }, - perpetualId: 0, - insuranceFundBalance: new(big.Int).Add( - new(big.Int).SetUint64(math.MaxUint64), - new(big.Int).SetUint64(math.MaxUint64), - ), - expectedInsuranceFundBalance: new(big.Int).Add( - new(big.Int).SetUint64(math.MaxUint64), - new(big.Int).SetUint64(math.MaxUint64), - ), - }, - "can get zero balance - isolated market": { - assets: []assettypes.Asset{ - *constants.Usdc, - }, - perpetualId: 3, // Isolated market. - insuranceFundBalance: new(big.Int), - expectedInsuranceFundBalance: big.NewInt(0), - }, - "can get positive balance - isolated market": { - assets: []assettypes.Asset{ - *constants.Usdc, - }, - perpetualId: 3, // Isolated market. - insuranceFundBalance: big.NewInt(100), - expectedInsuranceFundBalance: big.NewInt(100), - }, - "panics when asset not found in state": { - assets: []assettypes.Asset{}, - perpetualId: 0, - expectedError: errors.New("GetInsuranceFundBalance: Usdc asset not found in state"), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - // Setup keeper state. - memClob := memclob.NewMemClobPriceTimePriority(false) - bankMock := &mocks.BankKeeper{} - ks := keepertest.NewClobKeepersTestContext(t, memClob, bankMock, &mocks.IndexerEventManager{}) - - ctx := ks.Ctx.WithIsCheckTx(true) - // Create the default markets. - keepertest.CreateTestMarkets(t, ctx, ks.PricesKeeper) - - // Create liquidity tiers. - keepertest.CreateTestLiquidityTiers(t, ctx, ks.PerpetualsKeeper) - - keepertest.CreateTestPerpetuals(t, ctx, ks.PerpetualsKeeper) - - for _, a := range tc.assets { - _, err := ks.AssetsKeeper.CreateAsset( - ks.Ctx, - a.Id, - a.Symbol, - a.Denom, - a.DenomExponent, - a.HasMarket, - a.MarketId, - a.AtomicResolution, - ) - require.NoError(t, err) - } - - insuranceFundAddr, err := ks.PerpetualsKeeper.GetInsuranceFundModuleAddress(ks.Ctx, tc.perpetualId) - require.NoError(t, err) - if tc.insuranceFundBalance != nil { - bankMock.On( - "GetBalance", - mock.Anything, - insuranceFundAddr, - constants.Usdc.Denom, - ).Return( - sdk.NewCoin(constants.Usdc.Denom, sdkmath.NewIntFromBigInt(tc.insuranceFundBalance)), - ) - } - - if tc.expectedError != nil { - require.PanicsWithValue( - t, - tc.expectedError.Error(), - func() { - ks.ClobKeeper.GetInsuranceFundBalance(ks.Ctx, tc.perpetualId) - }, - ) - } else { - require.Equal( - t, - tc.expectedInsuranceFundBalance, - ks.ClobKeeper.GetInsuranceFundBalance(ks.Ctx, tc.perpetualId), - ) - } - }) - } -} - func TestIsValidInsuranceFundDelta(t *testing.T) { tests := map[string]struct { // Setup diff --git a/protocol/x/clob/keeper/keeper.go b/protocol/x/clob/keeper/keeper.go index 633a0725b9..e371e91039 100644 --- a/protocol/x/clob/keeper/keeper.go +++ b/protocol/x/clob/keeper/keeper.go @@ -3,6 +3,7 @@ package keeper import ( "errors" "fmt" + "math/big" "sync/atomic" "github.com/dydxprotocol/v4-chain/protocol/finalizeblock" @@ -162,6 +163,10 @@ func (k Keeper) GetFullNodeStreamingManager() streamingtypes.FullNodeStreamingMa return k.streamingManager } +func (k Keeper) GetCrossInsuranceFundBalance(ctx sdk.Context) *big.Int { + return k.subaccountsKeeper.GetCrossInsuranceFundBalance(ctx) +} + func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With( log.ModuleKey, "x/clob", diff --git a/protocol/x/clob/types/expected_keepers.go b/protocol/x/clob/types/expected_keepers.go index 846cc7d539..a1e3bafd3a 100644 --- a/protocol/x/clob/types/expected_keepers.go +++ b/protocol/x/clob/types/expected_keepers.go @@ -77,6 +77,17 @@ type SubaccountsKeeper interface { amount *big.Int, perpetualId uint32, ) error + GetInsuranceFundBalance( + ctx sdk.Context, + perpetualId uint32, + ) ( + balance *big.Int, + ) + GetCrossInsuranceFundBalance( + ctx sdk.Context, + ) ( + balance *big.Int, + ) GetCollateralPoolFromPerpetualId( ctx sdk.Context, perpetualId uint32, diff --git a/protocol/x/clob/types/liquidations_keeper.go b/protocol/x/clob/types/liquidations_keeper.go index b649a8e6c8..6bdf9f7a72 100644 --- a/protocol/x/clob/types/liquidations_keeper.go +++ b/protocol/x/clob/types/liquidations_keeper.go @@ -51,7 +51,6 @@ type LiquidationsKeeper interface { fillablePrice *big.Rat, err error, ) - GetInsuranceFundBalance(ctx sdk.Context, perpetualId uint32) (balance *big.Int) GetLiquidationInsuranceFundDelta( ctx sdk.Context, subaccountId satypes.SubaccountId, diff --git a/protocol/x/listing/keeper/keeper.go b/protocol/x/listing/keeper/keeper.go index 881fa63fe5..662fea98f4 100644 --- a/protocol/x/listing/keeper/keeper.go +++ b/protocol/x/listing/keeper/keeper.go @@ -13,14 +13,15 @@ import ( type ( Keeper struct { - cdc codec.BinaryCodec - storeKey storetypes.StoreKey - authorities map[string]struct{} - PricesKeeper types.PricesKeeper - ClobKeeper types.ClobKeeper - MarketMapKeeper types.MarketMapKeeper - PerpetualsKeeper types.PerpetualsKeeper - VaultKeeper types.VaultKeeper + cdc codec.BinaryCodec + storeKey storetypes.StoreKey + authorities map[string]struct{} + PricesKeeper types.PricesKeeper + ClobKeeper types.ClobKeeper + MarketMapKeeper types.MarketMapKeeper + PerpetualsKeeper types.PerpetualsKeeper + SubaccountsKeeper types.SubaccountsKeeper + VaultKeeper types.VaultKeeper } ) @@ -32,17 +33,19 @@ func NewKeeper( clobKeeper types.ClobKeeper, marketMapKeeper types.MarketMapKeeper, perpetualsKeeper types.PerpetualsKeeper, + subaccountsKeeper types.SubaccountsKeeper, vaultKeeper types.VaultKeeper, ) *Keeper { return &Keeper{ - cdc: cdc, - storeKey: storeKey, - authorities: lib.UniqueSliceToSet(authorities), - PricesKeeper: pricesKeeper, - ClobKeeper: clobKeeper, - MarketMapKeeper: marketMapKeeper, - PerpetualsKeeper: perpetualsKeeper, - VaultKeeper: vaultKeeper, + cdc: cdc, + storeKey: storeKey, + authorities: lib.UniqueSliceToSet(authorities), + PricesKeeper: pricesKeeper, + ClobKeeper: clobKeeper, + MarketMapKeeper: marketMapKeeper, + PerpetualsKeeper: perpetualsKeeper, + SubaccountsKeeper: subaccountsKeeper, + VaultKeeper: vaultKeeper, } } diff --git a/protocol/x/listing/keeper/listing.go b/protocol/x/listing/keeper/listing.go index 1a126dd527..e8329344a8 100644 --- a/protocol/x/listing/keeper/listing.go +++ b/protocol/x/listing/keeper/listing.go @@ -2,6 +2,7 @@ package keeper import ( "context" + "fmt" "math" "math/big" @@ -152,6 +153,43 @@ func (k Keeper) CreatePerpetual( return perpetual.GetId(), nil } +func (k Keeper) UpgradeIsolatedPerpetualToCross( + ctx sdk.Context, + perpetualId uint32, +) error { + // Validate perpetual exists and is in isolated mode + perpetual, err := k.PerpetualsKeeper.GetPerpetual(ctx, perpetualId) + if err != nil { + return err + } + if perpetual.Params.GetMarketType() != perpetualtypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED { + return fmt.Errorf("perpetual %d is not an isolated perpetual and cannot be upgraded to cross", perpetualId) + } + + err = k.SubaccountsKeeper.TransferIsolatedInsuranceFundToCross(ctx, perpetualId) + if err != nil { + return err + } + + err = k.SubaccountsKeeper.TransferIsolatedCollateralToCross(ctx, perpetualId) + if err != nil { + return err + } + + _, err = k.PerpetualsKeeper.SetPerpetualMarketType( + ctx, + perpetualId, + perpetualtypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + ) + if err != nil { + return err + } + + // TODO Propagate changes to indexer + + return nil +} + // Function to set listing vault deposit params in module store func (k Keeper) SetListingVaultDepositParams( ctx sdk.Context, diff --git a/protocol/x/listing/keeper/listing_test.go b/protocol/x/listing/keeper/listing_test.go index 18c9a8778f..5f75bfb5c0 100644 --- a/protocol/x/listing/keeper/listing_test.go +++ b/protocol/x/listing/keeper/listing_test.go @@ -62,7 +62,7 @@ func TestCreateMarket(t *testing.T) { t.Run( name, func(t *testing.T) { mockIndexerEventManager := &mocks.IndexerEventManager{} - ctx, keeper, _, _, pricesKeeper, _, _, marketMapKeeper := keepertest.ListingKeepers( + ctx, keeper, _, _, pricesKeeper, _, _, marketMapKeeper, _, _, _ := keepertest.ListingKeepers( t, &mocks.BankKeeper{}, mockIndexerEventManager, @@ -131,7 +131,7 @@ func TestCreatePerpetual(t *testing.T) { t.Run( name, func(t *testing.T) { mockIndexerEventManager := &mocks.IndexerEventManager{} - ctx, keeper, _, _, pricesKeeper, perpetualsKeeper, _, marketMapKeeper := keepertest.ListingKeepers( + ctx, keeper, _, _, pricesKeeper, perpetualsKeeper, _, marketMapKeeper, _, _, _ := keepertest.ListingKeepers( t, &mocks.BankKeeper{}, mockIndexerEventManager, @@ -217,7 +217,8 @@ func TestCreateClobPair(t *testing.T) { t.Run( name, func(t *testing.T) { mockIndexerEventManager := &mocks.IndexerEventManager{} - ctx, keeper, _, _, pricesKeeper, perpetualsKeeper, clobKeeper, marketMapKeeper := keepertest.ListingKeepers( + ctx, keeper, _, _, pricesKeeper, perpetualsKeeper, clobKeeper, marketMapKeeper, + _, _, _ := keepertest.ListingKeepers( t, &mocks.BankKeeper{}, mockIndexerEventManager, diff --git a/protocol/x/listing/keeper/msg_server_upgrade_isolated_perpetual_to_cross.go b/protocol/x/listing/keeper/msg_server_upgrade_isolated_perpetual_to_cross.go new file mode 100644 index 0000000000..a7599eb65f --- /dev/null +++ b/protocol/x/listing/keeper/msg_server_upgrade_isolated_perpetual_to_cross.go @@ -0,0 +1,35 @@ +package keeper + +import ( + "context" + + errorsmod "cosmossdk.io/errors" + govtypes "github.com/cosmos/cosmos-sdk/x/gov/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" +) + +func (k msgServer) UpgradeIsolatedPerpetualToCross( + goCtx context.Context, + msg *types.MsgUpgradeIsolatedPerpetualToCross, +) (*types.MsgUpgradeIsolatedPerpetualToCrossResponse, error) { + if !k.Keeper.HasAuthority(msg.Authority) { + return nil, errorsmod.Wrapf( + govtypes.ErrInvalidSigner, + "invalid authority %s", + msg.Authority, + ) + } + + ctx := lib.UnwrapSDKContext(goCtx, types.ModuleName) + + err := k.Keeper.UpgradeIsolatedPerpetualToCross( + ctx, + msg.PerpetualId, + ) + if err != nil { + return nil, err + } + + return &types.MsgUpgradeIsolatedPerpetualToCrossResponse{}, nil +} diff --git a/protocol/x/listing/keeper/msg_server_upgrade_isolated_perpetual_to_cross_test.go b/protocol/x/listing/keeper/msg_server_upgrade_isolated_perpetual_to_cross_test.go new file mode 100644 index 0000000000..1ee4846391 --- /dev/null +++ b/protocol/x/listing/keeper/msg_server_upgrade_isolated_perpetual_to_cross_test.go @@ -0,0 +1,230 @@ +package keeper_test + +import ( + "math/big" + "testing" + + sdkmath "cosmossdk.io/math" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/dydxprotocol/v4-chain/protocol/lib" + "github.com/dydxprotocol/v4-chain/protocol/mocks" + bank_testutil "github.com/dydxprotocol/v4-chain/protocol/testutil/bank" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + keepertest "github.com/dydxprotocol/v4-chain/protocol/testutil/keeper" + asstypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" + listingkeeper "github.com/dydxprotocol/v4-chain/protocol/x/listing/keeper" + types "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" + perpetualtypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + "github.com/stretchr/testify/require" +) + +var ( + validAuthority = lib.GovModuleAddress.String() +) + +func TestMsgUpgradeIsolatedPerpetualToCross(t *testing.T) { + tests := map[string]struct { + msg *types.MsgUpgradeIsolatedPerpetualToCross + isolatedInsuranceFundBalance *big.Int + isolatedCollateralPoolBalance *big.Int + crossInsuranceFundBalance *big.Int + crossCollateralPoolBalance *big.Int + + expectedErr string + }{ + "Success": { + msg: &types.MsgUpgradeIsolatedPerpetualToCross{ + Authority: validAuthority, + PerpetualId: 3, // isolated + }, + isolatedInsuranceFundBalance: big.NewInt(1), + isolatedCollateralPoolBalance: big.NewInt(1), + crossInsuranceFundBalance: big.NewInt(1), + crossCollateralPoolBalance: big.NewInt(1), + expectedErr: "", + }, + "Success - empty isolated insurance fund": { + msg: &types.MsgUpgradeIsolatedPerpetualToCross{ + Authority: validAuthority, + PerpetualId: 3, // isolated + }, + isolatedInsuranceFundBalance: big.NewInt(0), + isolatedCollateralPoolBalance: big.NewInt(1), + crossInsuranceFundBalance: big.NewInt(1), + crossCollateralPoolBalance: big.NewInt(1), + expectedErr: "", + }, + "Success - empty isolated collateral fund": { + msg: &types.MsgUpgradeIsolatedPerpetualToCross{ + Authority: validAuthority, + PerpetualId: 3, // isolated + }, + isolatedInsuranceFundBalance: big.NewInt(1), + isolatedCollateralPoolBalance: big.NewInt(0), + crossInsuranceFundBalance: big.NewInt(1), + crossCollateralPoolBalance: big.NewInt(1), + expectedErr: "", + }, + "Success - empty isolated insurance fund + empty isolated collateral fund": { + msg: &types.MsgUpgradeIsolatedPerpetualToCross{ + Authority: validAuthority, + PerpetualId: 3, // isolated + }, + isolatedInsuranceFundBalance: big.NewInt(0), + isolatedCollateralPoolBalance: big.NewInt(0), + crossInsuranceFundBalance: big.NewInt(1), + crossCollateralPoolBalance: big.NewInt(1), + expectedErr: "", + }, + "Failure: Empty authority": { + msg: &types.MsgUpgradeIsolatedPerpetualToCross{ + Authority: "", + PerpetualId: 3, // isolated + }, + isolatedInsuranceFundBalance: big.NewInt(1), + isolatedCollateralPoolBalance: big.NewInt(1), + crossInsuranceFundBalance: big.NewInt(1), + crossCollateralPoolBalance: big.NewInt(1), + expectedErr: "invalid authority", + }, + "Failure: Invalid authority": { + msg: &types.MsgUpgradeIsolatedPerpetualToCross{ + Authority: "invalid", + PerpetualId: 3, // isolated + }, + isolatedInsuranceFundBalance: big.NewInt(1), + isolatedCollateralPoolBalance: big.NewInt(1), + crossInsuranceFundBalance: big.NewInt(1), + crossCollateralPoolBalance: big.NewInt(1), + expectedErr: "invalid authority", + }, + "Failure: Invalid perpetual ID": { + msg: &types.MsgUpgradeIsolatedPerpetualToCross{ + Authority: validAuthority, + PerpetualId: 99999999, // invalid + }, + expectedErr: "Perpetual does not exist", + }, + "Failure - perpetual already has cross market type": { + msg: &types.MsgUpgradeIsolatedPerpetualToCross{ + Authority: validAuthority, + PerpetualId: 1, // cross + }, + isolatedInsuranceFundBalance: big.NewInt(1), + isolatedCollateralPoolBalance: big.NewInt(1), + crossInsuranceFundBalance: big.NewInt(1), + crossCollateralPoolBalance: big.NewInt(1), + expectedErr: "perpetual 1 is not an isolated perpetual and cannot be upgraded to cross", + }, + } + + for name, tc := range tests { + t.Run( + name, func(t *testing.T) { + mockIndexerEventManager := &mocks.IndexerEventManager{} + + ctx, keeper, _, _, pricesKeeper, perpetualsKeeper, _, _, assetsKeeper, + bankKeeper, subaccountsKeeper := keepertest.ListingKeepers( + t, + &mocks.BankKeeper{}, + mockIndexerEventManager, + ) + + // Create the default markets. + keepertest.CreateTestMarkets(t, ctx, pricesKeeper) + + // Create liquidity tiers. + keepertest.CreateTestLiquidityTiers(t, ctx, perpetualsKeeper) + + // Create USDC asset. + err := keepertest.CreateUsdcAsset(ctx, assetsKeeper) + require.NoError(t, err) + + // Create test perpetuals. + keepertest.CreateTestPerpetuals(t, ctx, perpetualsKeeper) + + var isolatedInsuranceFundAddr, crossInsuranceFundAddr, isolatedCollateralPoolAddr, + crossCollateralPoolAddr sdk.AccAddress + if tc.isolatedInsuranceFundBalance != nil { + // Get addresses for isolated/cross insurance funds and collateral pools. + isolatedInsuranceFundAddr, err = perpetualsKeeper.GetInsuranceFundModuleAddress(ctx, tc.msg.PerpetualId) + require.NoError(t, err) + + isolatedCollateralPoolAddr, err = subaccountsKeeper.GetCollateralPoolFromPerpetualId(ctx, tc.msg.PerpetualId) + require.NoError(t, err) + + crossInsuranceFundAddr = perpetualtypes.InsuranceFundModuleAddress + + crossCollateralPoolAddr = satypes.ModuleAddress + + // Fund the isolated insurance account, cross insurance account, + // isolated collateral pool, and cross collateral pool. + fundingData := [][]interface{}{ + {isolatedInsuranceFundAddr, tc.isolatedInsuranceFundBalance}, + {crossInsuranceFundAddr, tc.crossInsuranceFundBalance}, + {isolatedCollateralPoolAddr, tc.isolatedCollateralPoolBalance}, + {crossCollateralPoolAddr, tc.crossCollateralPoolBalance}, + } + + for _, data := range fundingData { + addr := data[0].(sdk.AccAddress) + amount := data[1].(*big.Int) + + if amount.Cmp(big.NewInt(0)) != 0 { + err = bank_testutil.FundAccount( + ctx, + addr, + sdk.Coins{ + sdk.NewCoin(constants.Usdc.Denom, sdkmath.NewIntFromBigInt(amount)), + }, + *bankKeeper, + ) + require.NoError(t, err) + } + } + } + + // Upgrade perpetual from isolated to cross. + ms := listingkeeper.NewMsgServerImpl(*keeper) + _, err = ms.UpgradeIsolatedPerpetualToCross(ctx, tc.msg) + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + return + } + require.NoError(t, err) + + // Check perpetual market type has been upgraded to cross. + perpetual, err := perpetualsKeeper.GetPerpetual(ctx, tc.msg.PerpetualId) + require.NoError(t, err) + require.Equal( + t, + perpetualtypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + perpetual.Params.MarketType, + ) + + // Check expected balance for isolated/cross insurance funds and collateral pools. + expectedBalances := [][]interface{}{ + {isolatedInsuranceFundAddr, big.NewInt(0)}, + {crossInsuranceFundAddr, big.NewInt(0).Add(tc.isolatedInsuranceFundBalance, tc.crossInsuranceFundBalance)}, + {isolatedCollateralPoolAddr, big.NewInt(0)}, + {crossCollateralPoolAddr, big.NewInt(0).Add(tc.isolatedCollateralPoolBalance, tc.crossCollateralPoolBalance)}, + } + + for _, data := range expectedBalances { + addr := data[0].(sdk.AccAddress) + amount := data[1].(*big.Int) + + require.Equal( + t, + sdk.NewCoin( + asstypes.AssetUsdc.Denom, + sdkmath.NewIntFromBigInt(amount), + ), + bankKeeper.GetBalance(ctx, addr, asstypes.AssetUsdc.Denom), + ) + } + }, + ) + } +} diff --git a/protocol/x/listing/types/expected_keepers.go b/protocol/x/listing/types/expected_keepers.go index e5a8b6693a..5fd30efa1b 100644 --- a/protocol/x/listing/types/expected_keepers.go +++ b/protocol/x/listing/types/expected_keepers.go @@ -64,7 +64,16 @@ type PerpetualsKeeper interface { marketType perpetualtypes.PerpetualMarketType, ) (perpetualtypes.Perpetual, error) AcquireNextPerpetualID(ctx sdk.Context) uint32 + GetPerpetual( + ctx sdk.Context, + id uint32, + ) (val perpetualtypes.Perpetual, err error) GetAllPerpetuals(ctx sdk.Context) (list []perpetualtypes.Perpetual) + SetPerpetualMarketType( + ctx sdk.Context, + id uint32, + marketType perpetualtypes.PerpetualMarketType, + ) (perpetualtypes.Perpetual, error) } type VaultKeeper interface { @@ -90,3 +99,14 @@ type VaultKeeper interface { status vaulttypes.VaultStatus, ) error } + +type SubaccountsKeeper interface { + TransferIsolatedInsuranceFundToCross( + ctx sdk.Context, + perpetualId uint32, + ) error + TransferIsolatedCollateralToCross( + ctx sdk.Context, + perpetualId uint32, + ) error +} diff --git a/protocol/x/listing/types/tx.pb.go b/protocol/x/listing/types/tx.pb.go index 4d8ce4644e..c1ba7f2abf 100644 --- a/protocol/x/listing/types/tx.pb.go +++ b/protocol/x/listing/types/tx.pb.go @@ -312,6 +312,103 @@ func (m *MsgSetListingVaultDepositParamsResponse) XXX_DiscardUnknown() { var xxx_messageInfo_MsgSetListingVaultDepositParamsResponse proto.InternalMessageInfo +// MsgUpgradeIsolatedPerpetualToCross is used to upgrade a market from +// isolated margin to cross margin. +type MsgUpgradeIsolatedPerpetualToCross struct { + Authority string `protobuf:"bytes,1,opt,name=authority,proto3" json:"authority,omitempty"` + // ID of the perpetual to be upgraded to CROSS + PerpetualId uint32 `protobuf:"varint,2,opt,name=perpetual_id,json=perpetualId,proto3" json:"perpetual_id,omitempty"` +} + +func (m *MsgUpgradeIsolatedPerpetualToCross) Reset() { *m = MsgUpgradeIsolatedPerpetualToCross{} } +func (m *MsgUpgradeIsolatedPerpetualToCross) String() string { return proto.CompactTextString(m) } +func (*MsgUpgradeIsolatedPerpetualToCross) ProtoMessage() {} +func (*MsgUpgradeIsolatedPerpetualToCross) Descriptor() ([]byte, []int) { + return fileDescriptor_144a579c1e2dcb94, []int{6} +} +func (m *MsgUpgradeIsolatedPerpetualToCross) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpgradeIsolatedPerpetualToCross) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpgradeIsolatedPerpetualToCross.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpgradeIsolatedPerpetualToCross) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpgradeIsolatedPerpetualToCross.Merge(m, src) +} +func (m *MsgUpgradeIsolatedPerpetualToCross) XXX_Size() int { + return m.Size() +} +func (m *MsgUpgradeIsolatedPerpetualToCross) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpgradeIsolatedPerpetualToCross.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpgradeIsolatedPerpetualToCross proto.InternalMessageInfo + +func (m *MsgUpgradeIsolatedPerpetualToCross) GetAuthority() string { + if m != nil { + return m.Authority + } + return "" +} + +func (m *MsgUpgradeIsolatedPerpetualToCross) GetPerpetualId() uint32 { + if m != nil { + return m.PerpetualId + } + return 0 +} + +// MsgUpgradeIsolatedPerpetualToCrossResponse defines the +// UpgradeIsolatedPerpetualToCross response type. +type MsgUpgradeIsolatedPerpetualToCrossResponse struct { +} + +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) Reset() { + *m = MsgUpgradeIsolatedPerpetualToCrossResponse{} +} +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) String() string { + return proto.CompactTextString(m) +} +func (*MsgUpgradeIsolatedPerpetualToCrossResponse) ProtoMessage() {} +func (*MsgUpgradeIsolatedPerpetualToCrossResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_144a579c1e2dcb94, []int{7} +} +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgUpgradeIsolatedPerpetualToCrossResponse.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgUpgradeIsolatedPerpetualToCrossResponse.Merge(m, src) +} +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgUpgradeIsolatedPerpetualToCrossResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgUpgradeIsolatedPerpetualToCrossResponse proto.InternalMessageInfo + func init() { proto.RegisterType((*MsgSetMarketsHardCap)(nil), "dydxprotocol.listing.MsgSetMarketsHardCap") proto.RegisterType((*MsgSetMarketsHardCapResponse)(nil), "dydxprotocol.listing.MsgSetMarketsHardCapResponse") @@ -319,46 +416,52 @@ func init() { proto.RegisterType((*MsgCreateMarketPermissionlessResponse)(nil), "dydxprotocol.listing.MsgCreateMarketPermissionlessResponse") proto.RegisterType((*MsgSetListingVaultDepositParams)(nil), "dydxprotocol.listing.MsgSetListingVaultDepositParams") proto.RegisterType((*MsgSetListingVaultDepositParamsResponse)(nil), "dydxprotocol.listing.MsgSetListingVaultDepositParamsResponse") + proto.RegisterType((*MsgUpgradeIsolatedPerpetualToCross)(nil), "dydxprotocol.listing.MsgUpgradeIsolatedPerpetualToCross") + proto.RegisterType((*MsgUpgradeIsolatedPerpetualToCrossResponse)(nil), "dydxprotocol.listing.MsgUpgradeIsolatedPerpetualToCrossResponse") } func init() { proto.RegisterFile("dydxprotocol/listing/tx.proto", fileDescriptor_144a579c1e2dcb94) } var fileDescriptor_144a579c1e2dcb94 = []byte{ - // 535 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x54, 0xcd, 0x6e, 0xd3, 0x40, - 0x10, 0xce, 0x52, 0x54, 0xa9, 0x0b, 0x45, 0xaa, 0x15, 0x41, 0xb0, 0x5a, 0xb7, 0x44, 0x82, 0xfe, - 0x48, 0xb5, 0x45, 0x0a, 0x48, 0x14, 0x71, 0x20, 0x45, 0x08, 0x04, 0x91, 0x2a, 0x47, 0xe2, 0xc0, - 0xc5, 0xda, 0xd8, 0x8b, 0xbd, 0x6a, 0xec, 0xb5, 0x76, 0xd6, 0x55, 0x72, 0xe5, 0x01, 0x80, 0x03, - 0x07, 0xce, 0x3c, 0x01, 0x07, 0x0e, 0x3c, 0x42, 0x8f, 0x15, 0x27, 0x4e, 0x08, 0x25, 0x07, 0x5e, - 0x03, 0xc5, 0xbb, 0xf9, 0x53, 0x9d, 0x80, 0x72, 0xf2, 0xce, 0xce, 0x7c, 0xdf, 0x7c, 0xe3, 0x6f, - 0xb4, 0x78, 0x23, 0xe8, 0x06, 0x9d, 0x54, 0x70, 0xc9, 0x7d, 0xde, 0x76, 0xda, 0x0c, 0x24, 0x4b, - 0x42, 0x47, 0x76, 0xec, 0xfc, 0xce, 0x28, 0x4f, 0xa6, 0x6d, 0x9d, 0x36, 0x6f, 0xfa, 0x1c, 0x62, - 0x0e, 0x5e, 0x9e, 0x70, 0x54, 0xa0, 0x00, 0xe6, 0x0d, 0x15, 0x39, 0x31, 0x84, 0xce, 0xe9, 0xdd, - 0xc1, 0x47, 0x27, 0xca, 0x21, 0x0f, 0xb9, 0x02, 0x0c, 0x4e, 0xfa, 0x76, 0x77, 0xaa, 0x3d, 0x64, - 0x2d, 0xe2, 0xfb, 0x3c, 0x4b, 0x24, 0x4c, 0x9c, 0x75, 0xe9, 0xad, 0x42, 0xa5, 0x29, 0x11, 0x24, - 0xd6, 0xcd, 0xab, 0x1f, 0x10, 0x2e, 0x37, 0x20, 0x6c, 0x52, 0xd9, 0x20, 0xe2, 0x84, 0x4a, 0x78, - 0x4e, 0x44, 0x70, 0x44, 0x52, 0xe3, 0x01, 0x5e, 0x21, 0x99, 0x8c, 0xb8, 0x60, 0xb2, 0x5b, 0x41, - 0x5b, 0x68, 0x67, 0xa5, 0x5e, 0xf9, 0xf1, 0x6d, 0xbf, 0xac, 0xa5, 0x3f, 0x09, 0x02, 0x41, 0x01, - 0x9a, 0x52, 0xb0, 0x24, 0x74, 0xc7, 0xa5, 0x86, 0x83, 0xcb, 0x11, 0x11, 0x81, 0xe7, 0x93, 0xd4, - 0x7b, 0xcb, 0x85, 0x17, 0x2b, 0xda, 0xca, 0xa5, 0x2d, 0xb4, 0xb3, 0xea, 0xae, 0x45, 0x8a, 0xfe, - 0x19, 0x17, 0xba, 0xdf, 0xe1, 0xb5, 0x77, 0x7f, 0xbe, 0xee, 0x8d, 0x09, 0xaa, 0x16, 0x5e, 0x2f, - 0x12, 0xe4, 0x52, 0x48, 0x79, 0x02, 0xb4, 0xfa, 0x19, 0xe1, 0x8d, 0x06, 0x84, 0x47, 0x82, 0x12, - 0x49, 0x55, 0xcd, 0x31, 0x15, 0x31, 0x03, 0x60, 0x3c, 0x69, 0x53, 0x00, 0xe3, 0x3a, 0x5e, 0x96, - 0xcc, 0x3f, 0xa1, 0x42, 0xe9, 0x76, 0x75, 0x64, 0xbc, 0xc4, 0xab, 0xe3, 0x5f, 0xe4, 0xb1, 0x20, - 0xd7, 0x74, 0xa5, 0x76, 0xc7, 0x9e, 0x72, 0x6c, 0xe2, 0x8f, 0xda, 0xcd, 0xd1, 0xf9, 0x45, 0xe0, - 0x5e, 0x85, 0x89, 0xe8, 0xd0, 0x18, 0xc8, 0x9e, 0xe6, 0xab, 0x6e, 0xe3, 0xdb, 0x73, 0x95, 0x8d, - 0x66, 0xf8, 0x8e, 0xf0, 0xa6, 0x1a, 0xf2, 0x95, 0x32, 0xe5, 0x35, 0xc9, 0xda, 0xf2, 0x29, 0x4d, - 0x39, 0x30, 0x79, 0x9c, 0xfb, 0xb3, 0xb0, 0x01, 0x0d, 0xbc, 0xac, 0x1c, 0xd6, 0xe3, 0x39, 0x76, - 0xd1, 0x42, 0xda, 0x33, 0x1b, 0xd7, 0x2f, 0x9f, 0xfd, 0xda, 0x2c, 0xb9, 0x9a, 0xe4, 0x82, 0x3d, - 0xbb, 0x78, 0xfb, 0x1f, 0xca, 0x87, 0x53, 0xd6, 0xbe, 0x2c, 0xe1, 0xa5, 0x06, 0x84, 0x06, 0xe0, - 0xb5, 0x8b, 0xfb, 0xb5, 0x57, 0x2c, 0xab, 0xc8, 0x7a, 0xb3, 0xf6, 0xff, 0xb5, 0xc3, 0xe6, 0xc6, - 0x7b, 0x84, 0xcd, 0x39, 0x3b, 0x72, 0x30, 0x93, 0x72, 0x36, 0xc8, 0x7c, 0xb4, 0x00, 0x68, 0x24, - 0xe8, 0x13, 0xc2, 0xeb, 0x73, 0x0d, 0xbf, 0x3f, 0x6f, 0xca, 0x99, 0x30, 0xf3, 0xf1, 0x42, 0xb0, - 0xa1, 0xac, 0x7a, 0xf3, 0xac, 0x67, 0xa1, 0xf3, 0x9e, 0x85, 0x7e, 0xf7, 0x2c, 0xf4, 0xb1, 0x6f, - 0x95, 0xce, 0xfb, 0x56, 0xe9, 0x67, 0xdf, 0x2a, 0xbd, 0x79, 0x18, 0x32, 0x19, 0x65, 0x2d, 0xdb, - 0xe7, 0xb1, 0x33, 0xf5, 0x90, 0x9c, 0xde, 0xdb, 0xf7, 0x23, 0xc2, 0x12, 0x67, 0x74, 0xd3, 0x19, - 0x3f, 0x83, 0xdd, 0x94, 0x42, 0x6b, 0x39, 0xcf, 0x1c, 0xfc, 0x0d, 0x00, 0x00, 0xff, 0xff, 0xfa, - 0xd7, 0x13, 0x8c, 0x2b, 0x05, 0x00, 0x00, + // 607 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x55, 0x3f, 0x6f, 0xd3, 0x40, + 0x14, 0xcf, 0x41, 0x55, 0xa9, 0xd7, 0x06, 0xa9, 0x56, 0x04, 0xc1, 0x6a, 0x9d, 0x36, 0x12, 0xb4, + 0x8d, 0xa8, 0x2d, 0x52, 0x40, 0x50, 0x84, 0x04, 0x09, 0x42, 0x44, 0x60, 0x29, 0x72, 0x80, 0x81, + 0x25, 0xba, 0xd8, 0x87, 0x63, 0xd5, 0xf6, 0x59, 0xf7, 0xce, 0x55, 0xb2, 0xf2, 0x01, 0x28, 0x03, + 0x03, 0x0b, 0xdf, 0x81, 0x81, 0x81, 0x99, 0xa9, 0x63, 0xc5, 0xc4, 0x84, 0x50, 0x32, 0xf0, 0x35, + 0x50, 0x62, 0xc7, 0x49, 0x94, 0x7f, 0x28, 0x93, 0xef, 0xfd, 0xf9, 0xbd, 0xf7, 0x7b, 0xf7, 0x7b, + 0xb6, 0xf1, 0xb6, 0xd5, 0xb6, 0x5a, 0x01, 0x67, 0x82, 0x99, 0xcc, 0xd5, 0x5c, 0x07, 0x84, 0xe3, + 0xdb, 0x9a, 0x68, 0xa9, 0x7d, 0x9f, 0x94, 0x19, 0x0d, 0xab, 0x71, 0x58, 0xbe, 0x6e, 0x32, 0xf0, + 0x18, 0xd4, 0xfb, 0x01, 0x2d, 0x32, 0x22, 0x80, 0x7c, 0x2d, 0xb2, 0x34, 0x0f, 0x6c, 0xed, 0xf4, + 0x76, 0xef, 0x11, 0x07, 0x32, 0x36, 0xb3, 0x59, 0x04, 0xe8, 0x9d, 0x62, 0xef, 0xc1, 0x58, 0x7b, + 0x08, 0x1b, 0xc4, 0x34, 0x59, 0xe8, 0x0b, 0x18, 0x39, 0xc7, 0xa9, 0xbb, 0x53, 0x99, 0x06, 0x84, + 0x13, 0x2f, 0x6e, 0x9e, 0x3f, 0x43, 0x38, 0xa3, 0x83, 0x5d, 0xa3, 0x42, 0x27, 0xfc, 0x84, 0x0a, + 0x78, 0x4e, 0xb8, 0x55, 0x26, 0x81, 0x74, 0x0f, 0xaf, 0x91, 0x50, 0x34, 0x19, 0x77, 0x44, 0x3b, + 0x8b, 0x76, 0xd0, 0xfe, 0x5a, 0x29, 0xfb, 0xf3, 0xdb, 0x61, 0x26, 0xa6, 0xfe, 0xc4, 0xb2, 0x38, + 0x05, 0xa8, 0x09, 0xee, 0xf8, 0xb6, 0x31, 0x4c, 0x95, 0x34, 0x9c, 0x69, 0x12, 0x6e, 0xd5, 0x4d, + 0x12, 0xd4, 0xdf, 0x31, 0x5e, 0xf7, 0xa2, 0xb2, 0xd9, 0x4b, 0x3b, 0x68, 0x3f, 0x6d, 0x6c, 0x36, + 0xa3, 0xf2, 0xcf, 0x18, 0x8f, 0xfb, 0x1d, 0x5f, 0x79, 0xff, 0xf7, 0x6b, 0x61, 0x58, 0x20, 0xaf, + 0xe0, 0xad, 0x69, 0x84, 0x0c, 0x0a, 0x01, 0xf3, 0x81, 0xe6, 0x3f, 0x23, 0xbc, 0xad, 0x83, 0x5d, + 0xe6, 0x94, 0x08, 0x1a, 0xe5, 0x54, 0x29, 0xf7, 0x1c, 0x00, 0x87, 0xf9, 0x2e, 0x05, 0x90, 0xae, + 0xe2, 0x55, 0xe1, 0x98, 0x27, 0x94, 0x47, 0xbc, 0x8d, 0xd8, 0x92, 0x5e, 0xe0, 0xf4, 0xf0, 0x8a, + 0xea, 0x8e, 0xd5, 0xe7, 0xb4, 0x5e, 0xbc, 0xa9, 0x8e, 0x29, 0x36, 0x72, 0xa3, 0x6a, 0x2d, 0x39, + 0x57, 0x2c, 0x63, 0x03, 0x46, 0xac, 0x63, 0xa9, 0x47, 0x7b, 0xbc, 0x5e, 0x7e, 0x0f, 0xdf, 0x98, + 0xcb, 0x2c, 0x99, 0xe1, 0x3b, 0xc2, 0xb9, 0x68, 0xc8, 0x97, 0x91, 0x28, 0x6f, 0x48, 0xe8, 0x8a, + 0xa7, 0x34, 0x60, 0xe0, 0x88, 0x6a, 0x5f, 0x9f, 0xa5, 0x05, 0xd0, 0xf1, 0x6a, 0xa4, 0x70, 0x3c, + 0x9e, 0xa6, 0x4e, 0x5b, 0x48, 0x75, 0x66, 0xe3, 0xd2, 0xca, 0xf9, 0xef, 0x5c, 0xca, 0x88, 0x8b, + 0x4c, 0xc8, 0x73, 0x80, 0xf7, 0x16, 0x30, 0x4f, 0xa6, 0x3c, 0x43, 0x38, 0xaf, 0x83, 0xfd, 0x3a, + 0xb0, 0x39, 0xb1, 0x68, 0x05, 0x98, 0x4b, 0x04, 0xb5, 0xaa, 0x94, 0x07, 0x54, 0x84, 0xc4, 0x7d, + 0xc5, 0xca, 0x9c, 0xc1, 0xf2, 0x83, 0xee, 0xe2, 0x8d, 0x60, 0x50, 0x6b, 0xa0, 0x66, 0xda, 0x58, + 0x4f, 0x7c, 0x15, 0x6b, 0x82, 0xfc, 0x2d, 0x5c, 0x58, 0x4c, 0x68, 0xc0, 0xbf, 0xf8, 0x63, 0x05, + 0x5f, 0xd6, 0xc1, 0x96, 0x00, 0x6f, 0x4e, 0xbe, 0x1f, 0x85, 0xe9, 0xd7, 0x3a, 0x6d, 0x75, 0xe5, + 0xe2, 0xff, 0xe7, 0x0e, 0x9a, 0x4b, 0x1f, 0x10, 0x96, 0xe7, 0xec, 0xf8, 0xd1, 0xcc, 0x92, 0xb3, + 0x41, 0xf2, 0xc3, 0x25, 0x40, 0x09, 0xa1, 0x4f, 0x08, 0x6f, 0xcd, 0x5d, 0xd8, 0xbb, 0xf3, 0xa6, + 0x9c, 0x09, 0x93, 0x1f, 0x2d, 0x05, 0x4b, 0x68, 0x7d, 0x41, 0x38, 0xb7, 0x68, 0xc3, 0xee, 0xcf, + 0x6c, 0xb1, 0x00, 0x29, 0x3f, 0x5e, 0x16, 0x39, 0xe0, 0x57, 0xaa, 0x9d, 0x77, 0x14, 0x74, 0xd1, + 0x51, 0xd0, 0x9f, 0x8e, 0x82, 0x3e, 0x76, 0x95, 0xd4, 0x45, 0x57, 0x49, 0xfd, 0xea, 0x2a, 0xa9, + 0xb7, 0x0f, 0x6c, 0x47, 0x34, 0xc3, 0x86, 0x6a, 0x32, 0x4f, 0x1b, 0xfb, 0x50, 0x9f, 0xde, 0x39, + 0x34, 0x9b, 0xc4, 0xf1, 0xb5, 0xc4, 0xd3, 0x1a, 0xfe, 0x66, 0xda, 0x01, 0x85, 0xc6, 0x6a, 0x3f, + 0x72, 0xf4, 0x2f, 0x00, 0x00, 0xff, 0xff, 0x92, 0xf4, 0x1f, 0x1d, 0x8b, 0x06, 0x00, 0x00, } // Reference imports to suppress errors if they are not otherwise used. @@ -379,6 +482,9 @@ type MsgClient interface { CreateMarketPermissionless(ctx context.Context, in *MsgCreateMarketPermissionless, opts ...grpc.CallOption) (*MsgCreateMarketPermissionlessResponse, error) // SetListingVaultDepositParams sets PML megavault deposit params SetListingVaultDepositParams(ctx context.Context, in *MsgSetListingVaultDepositParams, opts ...grpc.CallOption) (*MsgSetListingVaultDepositParamsResponse, error) + // UpgradeIsolatedPerpetualToCross upgrades a perpetual from isolated to cross + // margin + UpgradeIsolatedPerpetualToCross(ctx context.Context, in *MsgUpgradeIsolatedPerpetualToCross, opts ...grpc.CallOption) (*MsgUpgradeIsolatedPerpetualToCrossResponse, error) } type msgClient struct { @@ -416,6 +522,15 @@ func (c *msgClient) SetListingVaultDepositParams(ctx context.Context, in *MsgSet return out, nil } +func (c *msgClient) UpgradeIsolatedPerpetualToCross(ctx context.Context, in *MsgUpgradeIsolatedPerpetualToCross, opts ...grpc.CallOption) (*MsgUpgradeIsolatedPerpetualToCrossResponse, error) { + out := new(MsgUpgradeIsolatedPerpetualToCrossResponse) + err := c.cc.Invoke(ctx, "/dydxprotocol.listing.Msg/UpgradeIsolatedPerpetualToCross", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + // MsgServer is the server API for Msg service. type MsgServer interface { // SetMarketsHardCap sets a hard cap on the number of markets listed @@ -424,6 +539,9 @@ type MsgServer interface { CreateMarketPermissionless(context.Context, *MsgCreateMarketPermissionless) (*MsgCreateMarketPermissionlessResponse, error) // SetListingVaultDepositParams sets PML megavault deposit params SetListingVaultDepositParams(context.Context, *MsgSetListingVaultDepositParams) (*MsgSetListingVaultDepositParamsResponse, error) + // UpgradeIsolatedPerpetualToCross upgrades a perpetual from isolated to cross + // margin + UpgradeIsolatedPerpetualToCross(context.Context, *MsgUpgradeIsolatedPerpetualToCross) (*MsgUpgradeIsolatedPerpetualToCrossResponse, error) } // UnimplementedMsgServer can be embedded to have forward compatible implementations. @@ -439,6 +557,9 @@ func (*UnimplementedMsgServer) CreateMarketPermissionless(ctx context.Context, r func (*UnimplementedMsgServer) SetListingVaultDepositParams(ctx context.Context, req *MsgSetListingVaultDepositParams) (*MsgSetListingVaultDepositParamsResponse, error) { return nil, status.Errorf(codes.Unimplemented, "method SetListingVaultDepositParams not implemented") } +func (*UnimplementedMsgServer) UpgradeIsolatedPerpetualToCross(ctx context.Context, req *MsgUpgradeIsolatedPerpetualToCross) (*MsgUpgradeIsolatedPerpetualToCrossResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method UpgradeIsolatedPerpetualToCross not implemented") +} func RegisterMsgServer(s grpc1.Server, srv MsgServer) { s.RegisterService(&_Msg_serviceDesc, srv) @@ -498,6 +619,24 @@ func _Msg_SetListingVaultDepositParams_Handler(srv interface{}, ctx context.Cont return interceptor(ctx, in, info, handler) } +func _Msg_UpgradeIsolatedPerpetualToCross_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgUpgradeIsolatedPerpetualToCross) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).UpgradeIsolatedPerpetualToCross(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/dydxprotocol.listing.Msg/UpgradeIsolatedPerpetualToCross", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).UpgradeIsolatedPerpetualToCross(ctx, req.(*MsgUpgradeIsolatedPerpetualToCross)) + } + return interceptor(ctx, in, info, handler) +} + var _Msg_serviceDesc = grpc.ServiceDesc{ ServiceName: "dydxprotocol.listing.Msg", HandlerType: (*MsgServer)(nil), @@ -514,6 +653,10 @@ var _Msg_serviceDesc = grpc.ServiceDesc{ MethodName: "SetListingVaultDepositParams", Handler: _Msg_SetListingVaultDepositParams_Handler, }, + { + MethodName: "UpgradeIsolatedPerpetualToCross", + Handler: _Msg_UpgradeIsolatedPerpetualToCross_Handler, + }, }, Streams: []grpc.StreamDesc{}, Metadata: "dydxprotocol/listing/tx.proto", @@ -705,6 +848,64 @@ func (m *MsgSetListingVaultDepositParamsResponse) MarshalToSizedBuffer(dAtA []by return len(dAtA) - i, nil } +func (m *MsgUpgradeIsolatedPerpetualToCross) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpgradeIsolatedPerpetualToCross) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpgradeIsolatedPerpetualToCross) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.PerpetualId != 0 { + i = encodeVarintTx(dAtA, i, uint64(m.PerpetualId)) + i-- + dAtA[i] = 0x10 + } + if len(m.Authority) > 0 { + i -= len(m.Authority) + copy(dAtA[i:], m.Authority) + i = encodeVarintTx(dAtA, i, uint64(len(m.Authority))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + func encodeVarintTx(dAtA []byte, offset int, v uint64) int { offset -= sovTx(v) base := offset @@ -791,6 +992,31 @@ func (m *MsgSetListingVaultDepositParamsResponse) Size() (n int) { return n } +func (m *MsgUpgradeIsolatedPerpetualToCross) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Authority) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.PerpetualId != 0 { + n += 1 + sovTx(uint64(m.PerpetualId)) + } + return n +} + +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + func sovTx(x uint64) (n int) { return (math_bits.Len64(x|1) + 6) / 7 } @@ -1281,6 +1507,157 @@ func (m *MsgSetListingVaultDepositParamsResponse) Unmarshal(dAtA []byte) error { } return nil } +func (m *MsgUpgradeIsolatedPerpetualToCross) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpgradeIsolatedPerpetualToCross: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpgradeIsolatedPerpetualToCross: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Authority", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Authority = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field PerpetualId", wireType) + } + m.PerpetualId = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.PerpetualId |= uint32(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgUpgradeIsolatedPerpetualToCrossResponse) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: MsgUpgradeIsolatedPerpetualToCrossResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgUpgradeIsolatedPerpetualToCrossResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func skipTx(dAtA []byte) (n int, err error) { l := len(dAtA) iNdEx := 0 diff --git a/protocol/x/perpetuals/keeper/perpetual.go b/protocol/x/perpetuals/keeper/perpetual.go index 834f67e96a..b97c359426 100644 --- a/protocol/x/perpetuals/keeper/perpetual.go +++ b/protocol/x/perpetuals/keeper/perpetual.go @@ -197,10 +197,11 @@ func (k Keeper) SetPerpetualMarketType( return perpetual, err } - if perpetual.Params.MarketType != types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_UNSPECIFIED { + if perpetual.Params.MarketType == types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS { return types.Perpetual{}, errorsmod.Wrap( types.ErrInvalidMarketType, - fmt.Sprintf("perpetual %d already has market type %v", perpetualId, perpetual.Params.MarketType), + fmt.Sprintf("perpetual %d already has market type %v and cannot be changed", + perpetualId, types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS), ) } diff --git a/protocol/x/perpetuals/keeper/perpetual_test.go b/protocol/x/perpetuals/keeper/perpetual_test.go index 30cce2fa32..08e859de1d 100644 --- a/protocol/x/perpetuals/keeper/perpetual_test.go +++ b/protocol/x/perpetuals/keeper/perpetual_test.go @@ -323,11 +323,16 @@ func TestSetPerpetualMarketType(t *testing.T) { errorExpected bool expectedError error }{ - "success": { + "success - set unspecified to cross": { currType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_UNSPECIFIED, newType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, errorExpected: false, }, + "success - set isolated to cross": { + currType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED, + 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, @@ -340,16 +345,16 @@ func TestSetPerpetualMarketType(t *testing.T) { ), ), }, - "failure - market type already set": { - currType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED, - newType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + "failure - market type already set to cross": { + currType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, + newType: types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED, errorExpected: true, expectedError: errorsmod.Wrap( types.ErrInvalidMarketType, fmt.Sprintf( - "perpetual %d already has market type %v", + "perpetual %d already has market type %v and cannot be changed", 0, - types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_ISOLATED, + types.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS, ), ), }, diff --git a/protocol/x/subaccounts/keeper/subaccount.go b/protocol/x/subaccounts/keeper/subaccount.go index a047d9e2c9..f195e2dbf3 100644 --- a/protocol/x/subaccounts/keeper/subaccount.go +++ b/protocol/x/subaccounts/keeper/subaccount.go @@ -8,6 +8,7 @@ import ( "time" streamingtypes "github.com/dydxprotocol/v4-chain/protocol/streaming/types" + assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" @@ -825,3 +826,40 @@ func (k Keeper) GetAllRelevantPerpetuals( func (k Keeper) GetFullNodeStreamingManager() streamingtypes.FullNodeStreamingManager { return k.streamingManager } + +// GetInsuranceFundBalance returns the current balance of the specific insurance fund based on the +// perpetual (in quote quantums). +// This calls the Bank Keeper’s GetBalance() function for the Module Address of the insurance fund. +func (k Keeper) GetInsuranceFundBalance(ctx sdk.Context, perpetualId uint32) (balance *big.Int) { + usdcAsset, exists := k.assetsKeeper.GetAsset(ctx, assettypes.AssetUsdc.Id) + if !exists { + panic("GetInsuranceFundBalance: Usdc asset not found in state") + } + insuranceFundAddr, err := k.perpetualsKeeper.GetInsuranceFundModuleAddress(ctx, perpetualId) + if err != nil { + return nil + } + insuranceFundBalance := k.bankKeeper.GetBalance( + ctx, + insuranceFundAddr, + usdcAsset.Denom, + ) + + // Return as big.Int. + return insuranceFundBalance.Amount.BigInt() +} + +func (k Keeper) GetCrossInsuranceFundBalance(ctx sdk.Context) (balance *big.Int) { + usdcAsset, exists := k.assetsKeeper.GetAsset(ctx, assettypes.AssetUsdc.Id) + if !exists { + panic("GetCrossInsuranceFundBalance: Usdc asset not found in state") + } + insuranceFundBalance := k.bankKeeper.GetBalance( + ctx, + perptypes.InsuranceFundModuleAddress, + usdcAsset.Denom, + ) + + // Return as big.Int. + return insuranceFundBalance.Amount.BigInt() +} diff --git a/protocol/x/subaccounts/keeper/subaccount_test.go b/protocol/x/subaccounts/keeper/subaccount_test.go index 1eb7141fda..0c28eafd95 100644 --- a/protocol/x/subaccounts/keeper/subaccount_test.go +++ b/protocol/x/subaccounts/keeper/subaccount_test.go @@ -1,6 +1,7 @@ package keeper_test import ( + "errors" "math" "math/big" "strconv" @@ -6131,3 +6132,129 @@ func TestGetAllRelevantPerpetuals_Deterministic(t *testing.T) { }) } } + +func TestGetInsuranceFundBalance(t *testing.T) { + tests := map[string]struct { + // Setup + assets []asstypes.Asset + insuranceFundBalance *big.Int + perpetualId uint32 + perpetual *perptypes.Perpetual + + // Expectations. + expectedInsuranceFundBalance *big.Int + expectedError error + }{ + "can get zero balance": { + assets: []asstypes.Asset{ + *constants.Usdc, + }, + perpetualId: 0, + insuranceFundBalance: new(big.Int), + expectedInsuranceFundBalance: big.NewInt(0), + }, + "can get positive balance": { + assets: []asstypes.Asset{ + *constants.Usdc, + }, + perpetualId: 0, + insuranceFundBalance: big.NewInt(100), + expectedInsuranceFundBalance: big.NewInt(100), + }, + "can get greater than MaxUint64 balance": { + assets: []asstypes.Asset{ + *constants.Usdc, + }, + perpetualId: 0, + insuranceFundBalance: new(big.Int).Add( + new(big.Int).SetUint64(math.MaxUint64), + new(big.Int).SetUint64(math.MaxUint64), + ), + expectedInsuranceFundBalance: new(big.Int).Add( + new(big.Int).SetUint64(math.MaxUint64), + new(big.Int).SetUint64(math.MaxUint64), + ), + }, + "can get zero balance - isolated market": { + assets: []asstypes.Asset{ + *constants.Usdc, + }, + perpetualId: 3, // Isolated market. + insuranceFundBalance: new(big.Int), + expectedInsuranceFundBalance: big.NewInt(0), + }, + "can get positive balance - isolated market": { + assets: []asstypes.Asset{ + *constants.Usdc, + }, + perpetualId: 3, // Isolated market. + insuranceFundBalance: big.NewInt(100), + expectedInsuranceFundBalance: big.NewInt(100), + }, + "panics when asset not found in state": { + assets: []asstypes.Asset{}, + perpetualId: 0, + expectedError: errors.New("GetInsuranceFundBalance: Usdc asset not found in state"), + }, + } + + for name, tc := range tests { + t.Run(name, func(t *testing.T) { + // Setup keeper state. + ctx, keeper, pricesKeeper, perpetualsKeeper, _, bankKeeper, assetsKeeper, _, _, _, _ := + keepertest.SubaccountsKeepers(t, true) + + // Create the default markets. + keepertest.CreateTestMarkets(t, ctx, pricesKeeper) + + // Create liquidity tiers. + keepertest.CreateTestLiquidityTiers(t, ctx, perpetualsKeeper) + + keepertest.CreateTestPerpetuals(t, ctx, perpetualsKeeper) + + for _, a := range tc.assets { + _, err := assetsKeeper.CreateAsset( + ctx, + a.Id, + a.Symbol, + a.Denom, + a.DenomExponent, + a.HasMarket, + a.MarketId, + a.AtomicResolution, + ) + require.NoError(t, err) + } + + insuranceFundAddr, err := perpetualsKeeper.GetInsuranceFundModuleAddress(ctx, tc.perpetualId) + require.NoError(t, err) + if tc.insuranceFundBalance != nil && tc.insuranceFundBalance.Cmp(big.NewInt(0)) != 0 { + err := bank_testutil.FundAccount( + ctx, + insuranceFundAddr, + sdk.Coins{ + sdk.NewCoin(asstypes.AssetUsdc.Denom, sdkmath.NewIntFromBigInt(tc.insuranceFundBalance)), + }, + *bankKeeper, + ) + require.NoError(t, err) + } + + if tc.expectedError != nil { + require.PanicsWithValue( + t, + tc.expectedError.Error(), + func() { + keeper.GetInsuranceFundBalance(ctx, tc.perpetualId) + }, + ) + } else { + require.Equal( + t, + tc.expectedInsuranceFundBalance, + keeper.GetInsuranceFundBalance(ctx, tc.perpetualId), + ) + } + }) + } +} diff --git a/protocol/x/subaccounts/keeper/transfer.go b/protocol/x/subaccounts/keeper/transfer.go index 52ffedce14..5271d06454 100644 --- a/protocol/x/subaccounts/keeper/transfer.go +++ b/protocol/x/subaccounts/keeper/transfer.go @@ -1,6 +1,7 @@ package keeper import ( + "fmt" "math/big" errorsmod "cosmossdk.io/errors" @@ -11,6 +12,7 @@ import ( "github.com/dydxprotocol/v4-chain/protocol/lib/metrics" assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" + perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" revsharetypes "github.com/dydxprotocol/v4-chain/protocol/x/revshare/types" "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" ) @@ -503,3 +505,89 @@ func (k Keeper) TransferFundsFromSubaccountToSubaccount( types.Transfer, ) } + +// TransferIsolatedInsuranceFundToCross transfers funds from an isolated perpetual's +// insurance fund to the cross-perpetual insurance fund. +// Note: This uses the `x/bank` keeper and modifies `x/bank` state. +func (k Keeper) TransferIsolatedInsuranceFundToCross(ctx sdk.Context, perpetualId uint32) error { + // Validate perpetual exists + if _, err := k.perpetualsKeeper.GetPerpetual(ctx, perpetualId); err != nil { + return err + } + + isolatedInsuranceFundBalance := k.GetInsuranceFundBalance(ctx, perpetualId) + + // Skip if balance is zero + if isolatedInsuranceFundBalance.Sign() == 0 { + return nil + } + + _, exists := k.assetsKeeper.GetAsset(ctx, assettypes.AssetUsdc.Id) + if !exists { + return fmt.Errorf("USDC asset not found in state") + } + + _, coinToTransfer, err := k.assetsKeeper.ConvertAssetToCoin( + ctx, + assettypes.AssetUsdc.Id, + isolatedInsuranceFundBalance, + ) + if err != nil { + return err + } + + isolatedInsuranceFundAddr, err := k.perpetualsKeeper.GetInsuranceFundModuleAddress(ctx, perpetualId) + if err != nil { + return err + } + + crossInsuranceFundAddr := perptypes.InsuranceFundModuleAddress + + return k.bankKeeper.SendCoins( + ctx, + isolatedInsuranceFundAddr, + crossInsuranceFundAddr, + []sdk.Coin{coinToTransfer}, + ) +} + +// TransferIsolatedCollateralToCross transfers the collateral balance from an isolated perpetual's +// collateral pool to the cross-margin collateral pool. This is used during the upgrade process +// from isolated perpetuals to cross-margin. +// Note: This uses the `x/bank` keeper and modifies `x/bank` state. +func (k Keeper) TransferIsolatedCollateralToCross(ctx sdk.Context, perpetualId uint32) error { + // Validate perpetual exists + if _, err := k.perpetualsKeeper.GetPerpetual(ctx, perpetualId); err != nil { + return err + } + + isolatedCollateralPoolAddr, err := k.GetCollateralPoolFromPerpetualId(ctx, perpetualId) + if err != nil { + return err + } + + crossCollateralPoolAddr := types.ModuleAddress + + usdcAsset, exists := k.assetsKeeper.GetAsset(ctx, assettypes.AssetUsdc.Id) + if !exists { + panic("TransferIsolatedCollateralToCross: Usdc asset not found in state") + } + + isolatedCollateralPoolBalance := k.bankKeeper.GetBalance( + ctx, + isolatedCollateralPoolAddr, + usdcAsset.Denom, + ) + + // Skip if balance is zero + if isolatedCollateralPoolBalance.IsZero() { + return nil + } + + return k.bankKeeper.SendCoins( + ctx, + isolatedCollateralPoolAddr, + crossCollateralPoolAddr, + []sdk.Coin{isolatedCollateralPoolBalance}, + ) +} diff --git a/protocol/x/subaccounts/types/expected_keepers.go b/protocol/x/subaccounts/types/expected_keepers.go index a9e3f88f94..1a4a97ced3 100644 --- a/protocol/x/subaccounts/types/expected_keepers.go +++ b/protocol/x/subaccounts/types/expected_keepers.go @@ -6,6 +6,7 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" + assettypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" blocktimetypes "github.com/dydxprotocol/v4-chain/protocol/x/blocktime/types" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" @@ -28,6 +29,13 @@ type AssetsKeeper interface { coin sdk.Coin, err error, ) + GetAsset( + ctx sdk.Context, + id uint32, + ) ( + val assettypes.Asset, + exists bool, + ) } type PerpetualsKeeper interface { @@ -91,6 +99,7 @@ type BankKeeper interface { ) error SendCoinsFromModuleToModule(ctx context.Context, senderModule, recipientModule string, amt sdk.Coins) error SendCoins(ctx context.Context, fromAddr, toAddr sdk.AccAddress, amt sdk.Coins) error + GetBalance(ctx context.Context, addr sdk.AccAddress, denom string) sdk.Coin } type BlocktimeKeeper interface {