diff --git a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/genesis.ts b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/genesis.ts index 6eb10a8756..ec58a89c8c 100644 --- a/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/genesis.ts +++ b/indexer/packages/v4-protos/src/codegen/dydxprotocol/listing/genesis.ts @@ -1,3 +1,4 @@ +import { ListingVaultDepositParams, ListingVaultDepositParamsSDKType } from "./params"; import * as _m0 from "protobufjs/minimal"; import { DeepPartial } from "../../helpers"; /** GenesisState defines `x/listing`'s genesis state. */ @@ -8,6 +9,9 @@ export interface GenesisState { * listed */ hardCapForMarkets: number; + /** listing_vault_deposit_params is the params for PML megavault deposits */ + + listingVaultDepositParams?: ListingVaultDepositParams; } /** GenesisState defines `x/listing`'s genesis state. */ @@ -17,11 +21,15 @@ export interface GenesisStateSDKType { * listed */ hard_cap_for_markets: number; + /** listing_vault_deposit_params is the params for PML megavault deposits */ + + listing_vault_deposit_params?: ListingVaultDepositParamsSDKType; } function createBaseGenesisState(): GenesisState { return { - hardCapForMarkets: 0 + hardCapForMarkets: 0, + listingVaultDepositParams: undefined }; } @@ -31,6 +39,10 @@ export const GenesisState = { writer.uint32(8).uint32(message.hardCapForMarkets); } + if (message.listingVaultDepositParams !== undefined) { + ListingVaultDepositParams.encode(message.listingVaultDepositParams, writer.uint32(18).fork()).ldelim(); + } + return writer; }, @@ -47,6 +59,10 @@ export const GenesisState = { message.hardCapForMarkets = reader.uint32(); break; + case 2: + message.listingVaultDepositParams = ListingVaultDepositParams.decode(reader, reader.uint32()); + break; + default: reader.skipType(tag & 7); break; @@ -59,6 +75,7 @@ export const GenesisState = { fromPartial(object: DeepPartial): GenesisState { const message = createBaseGenesisState(); message.hardCapForMarkets = object.hardCapForMarkets ?? 0; + message.listingVaultDepositParams = object.listingVaultDepositParams !== undefined && object.listingVaultDepositParams !== null ? ListingVaultDepositParams.fromPartial(object.listingVaultDepositParams) : undefined; return message; } diff --git a/proto/dydxprotocol/listing/genesis.proto b/proto/dydxprotocol/listing/genesis.proto index 98d3d29a07..2aefaaac79 100644 --- a/proto/dydxprotocol/listing/genesis.proto +++ b/proto/dydxprotocol/listing/genesis.proto @@ -1,6 +1,9 @@ syntax = "proto3"; package dydxprotocol.listing; +import "gogoproto/gogo.proto"; +import "dydxprotocol/listing/params.proto"; + option go_package = "github.com/dydxprotocol/v4-chain/protocol/x/listing/types"; // GenesisState defines `x/listing`'s genesis state. @@ -8,4 +11,8 @@ message GenesisState { // hard_cap_for_markets is the hard cap for the number of markets that can be // listed uint32 hard_cap_for_markets = 1; + + // listing_vault_deposit_params is the params for PML megavault deposits + ListingVaultDepositParams listing_vault_deposit_params = 2 + [ (gogoproto.nullable) = false ]; } diff --git a/protocol/app/app.go b/protocol/app/app.go index d45a739b8e..0528e2b17c 100644 --- a/protocol/app/app.go +++ b/protocol/app/app.go @@ -1206,6 +1206,7 @@ func New( app.ClobKeeper, &app.MarketMapKeeper, app.PerpetualsKeeper, + app.VaultKeeper, ) listingModule := listingmodule.NewAppModule( appCodec, @@ -1214,6 +1215,7 @@ func New( app.ClobKeeper, &app.MarketMapKeeper, app.PerpetualsKeeper, + app.VaultKeeper, ) // Initialize authenticators diff --git a/protocol/app/testdata/default_genesis_state.json b/protocol/app/testdata/default_genesis_state.json index e8fd038a30..48de48a5ba 100644 --- a/protocol/app/testdata/default_genesis_state.json +++ b/protocol/app/testdata/default_genesis_state.json @@ -353,7 +353,12 @@ } }, "listing": { - "hard_cap_for_markets": 0 + "hard_cap_for_markets": 500, + "listing_vault_deposit_params": { + "new_vault_deposit_amount": "10000000000", + "main_vault_deposit_amount": "0", + "num_blocks_to_lock_shares": 2592000 + } }, "params": null, "perpetuals": { diff --git a/protocol/scripts/genesis/sample_pregenesis.json b/protocol/scripts/genesis/sample_pregenesis.json index b3d0afbb32..6594f9c167 100644 --- a/protocol/scripts/genesis/sample_pregenesis.json +++ b/protocol/scripts/genesis/sample_pregenesis.json @@ -869,7 +869,12 @@ } }, "listing": { - "hard_cap_for_markets": 0 + "hard_cap_for_markets": 500, + "listing_vault_deposit_params": { + "main_vault_deposit_amount": "0", + "new_vault_deposit_amount": "10000000000", + "num_blocks_to_lock_shares": 2592000 + } }, "marketmap": { "last_updated": "0", diff --git a/protocol/testing/genesis.sh b/protocol/testing/genesis.sh index 28d1f8c6c7..69dab93c04 100755 --- a/protocol/testing/genesis.sh +++ b/protocol/testing/genesis.sh @@ -2258,6 +2258,14 @@ function edit_genesis() { # ICA Controller Params update_ica_controller_params + # Listing + # Set hard cap for markets + dasel put -t int -f "$GENESIS" ".app_state.listing.hard_cap_for_markets" -v '500' + # Set default listing vault deposit params + dasel put -t string -f "$GENESIS" ".app_state.listing.listing_vault_deposit_params.new_vault_deposit_amount" -v "10000000000" # 10_000 USDC + dasel put -t string -f "$GENESIS" ".app_state.listing.listing_vault_deposit_params.main_vault_deposit_amount" -v "0" # 0 USDC + dasel put -t int -f "$GENESIS" ".app_state.listing.listing_vault_deposit_params.num_blocks_to_lock_shares" -v '2592000' # 30 days + # Vaults # Set default quoting params. dasel put -t int -f "$GENESIS" ".app_state.vault.default_quoting_params.spread_min_ppm" -v '3000' diff --git a/protocol/testutil/constants/genesis.go b/protocol/testutil/constants/genesis.go index a33db9f187..f56364819b 100644 --- a/protocol/testutil/constants/genesis.go +++ b/protocol/testutil/constants/genesis.go @@ -873,7 +873,12 @@ const GenesisState = `{ } }, "listing": { - "hard_cap_for_markets": 0 + "hard_cap_for_markets": 500, + "listing_vault_deposit_params": { + "new_vault_deposit_amount": "10000000000", + "main_vault_deposit_amount": "0", + "num_blocks_to_lock_shares": 2592000 + } }, "perpetuals": { "liquidity_tiers": [ diff --git a/protocol/testutil/keeper/listing.go b/protocol/testutil/keeper/listing.go index 39e5b80cac..824eb50ef5 100644 --- a/protocol/testutil/keeper/listing.go +++ b/protocol/testutil/keeper/listing.go @@ -14,6 +14,7 @@ import ( 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" + vaultkeeper "github.com/dydxprotocol/v4-chain/protocol/x/vault/keeper" marketmapkeeper "github.com/skip-mev/slinky/x/marketmap/keeper" "github.com/stretchr/testify/mock" @@ -165,6 +166,7 @@ func ListingKeepers( perpetualsKeeper, clobKeeper, marketMapKeeper, + vaultKeeper, ) return []GenesisInitializer{keeper} @@ -182,6 +184,7 @@ func createListingKeeper( perpetualsKeeper *perpetualskeeper.Keeper, clobKeeper *clobkeeper.Keeper, marketMapKeeper *marketmapkeeper.Keeper, + vaultkeeper *vaultkeeper.Keeper, ) ( *keeper.Keeper, storetypes.StoreKey, @@ -201,6 +204,7 @@ func createListingKeeper( clobKeeper, marketMapKeeper, perpetualsKeeper, + vaultkeeper, ) return k, storeKey, mockTimeProvider diff --git a/protocol/x/clob/keeper/clob_pair.go b/protocol/x/clob/keeper/clob_pair.go index c03ebdfb6f..3b49d7c246 100644 --- a/protocol/x/clob/keeper/clob_pair.go +++ b/protocol/x/clob/keeper/clob_pair.go @@ -66,7 +66,10 @@ func (k Keeper) CreatePerpetualClobPair( return clobPair, err } - err := k.CreateClobPair(ctx, clobPair) + // Write the `ClobPair` to state. + k.SetClobPair(ctx, clobPair) + + err := k.CreateClobPairStructures(ctx, clobPair) if err != nil { return clobPair, err } @@ -162,22 +165,11 @@ func (k Keeper) createOrderbook(ctx sdk.Context, clobPair types.ClobPair) { k.MemClob.CreateOrderbook(clobPair) } -// CreateClobPair creates a new `ClobPair` in the store and creates the corresponding orderbook in the memclob. +// CreateClobPair performs all non stateful operations to create a CLOB pair. +// These include creating the corresponding orderbook in the memclob, the mapping between +// the CLOB pair and the perpetual and the indexer event. // This function returns an error if a value for the ClobPair's id already exists in state. -func (k Keeper) CreateClobPair(ctx sdk.Context, clobPair types.ClobPair) error { - // Validate the given clob pair id is not already in use. - if _, exists := k.GetClobPair(ctx, clobPair.GetClobPairId()); exists { - panic( - fmt.Sprintf( - "ClobPair with id %+v already exists in state", - clobPair.GetClobPairId(), - ), - ) - } - - // Write the `ClobPair` to state. - k.setClobPair(ctx, clobPair) - +func (k Keeper) CreateClobPairStructures(ctx sdk.Context, clobPair types.ClobPair) error { // Create the corresponding orderbook in the memclob. k.createOrderbook(ctx, clobPair) @@ -217,8 +209,8 @@ func (k Keeper) CreateClobPair(ctx sdk.Context, clobPair types.ClobPair) error { return nil } -// setClobPair sets a specific `ClobPair` in the store from its index. -func (k Keeper) setClobPair(ctx sdk.Context, clobPair types.ClobPair) { +// SetClobPair sets a specific `ClobPair` in the store from its index. +func (k Keeper) SetClobPair(ctx sdk.Context, clobPair types.ClobPair) { store := k.getClobPairStore(ctx) b := k.cdc.MustMarshal(&clobPair) store.Set(clobPairKey(clobPair.GetClobPairId()), b) @@ -567,7 +559,7 @@ func (k Keeper) UpdateClobPair( return err } - k.setClobPair(ctx, clobPair) + k.SetClobPair(ctx, clobPair) // Send UpdateClobPair to indexer. k.GetIndexerEventManager().AddTxnEvent( diff --git a/protocol/x/listing/genesis.go b/protocol/x/listing/genesis.go index fcd391c6ab..38c9d2c686 100644 --- a/protocol/x/listing/genesis.go +++ b/protocol/x/listing/genesis.go @@ -13,6 +13,10 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState) if err := k.SetMarketsHardCap(ctx, genState.HardCapForMarkets); err != nil { panic(err) } + + if err := k.SetListingVaultDepositParams(ctx, genState.ListingVaultDepositParams); err != nil { + panic(err) + } } // ExportGenesis returns the module's exported genesis. diff --git a/protocol/x/listing/keeper/keeper.go b/protocol/x/listing/keeper/keeper.go index daf4c0c3a4..881fa63fe5 100644 --- a/protocol/x/listing/keeper/keeper.go +++ b/protocol/x/listing/keeper/keeper.go @@ -20,6 +20,7 @@ type ( ClobKeeper types.ClobKeeper MarketMapKeeper types.MarketMapKeeper PerpetualsKeeper types.PerpetualsKeeper + VaultKeeper types.VaultKeeper } ) @@ -31,6 +32,7 @@ func NewKeeper( clobKeeper types.ClobKeeper, marketMapKeeper types.MarketMapKeeper, perpetualsKeeper types.PerpetualsKeeper, + vaultKeeper types.VaultKeeper, ) *Keeper { return &Keeper{ cdc: cdc, @@ -40,6 +42,7 @@ func NewKeeper( ClobKeeper: clobKeeper, MarketMapKeeper: marketMapKeeper, PerpetualsKeeper: perpetualsKeeper, + VaultKeeper: vaultKeeper, } } diff --git a/protocol/x/listing/keeper/listing.go b/protocol/x/listing/keeper/listing.go index 53ca3e8ca0..3141933630 100644 --- a/protocol/x/listing/keeper/listing.go +++ b/protocol/x/listing/keeper/listing.go @@ -2,6 +2,11 @@ package keeper import ( "math" + "math/big" + + vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" "github.com/dydxprotocol/v4-chain/protocol/lib" @@ -101,10 +106,12 @@ func (k Keeper) CreateClobPair( return 0, err } + k.ClobKeeper.SetClobPair(ctx, clobPair) + // Only create the clob pair if we are in deliver tx mode. This is to prevent populating // in memory data structures in the CLOB during simulation mode. if lib.IsDeliverTxMode(ctx) { - err := k.ClobKeeper.CreateClobPair(ctx, clobPair) + err := k.ClobKeeper.CreateClobPairStructures(ctx, clobPair) if err != nil { return 0, err } @@ -187,3 +194,67 @@ func (k Keeper) GetListingVaultDepositParams( k.cdc.MustUnmarshal(b, &vaultDepositParams) return vaultDepositParams } + +// Function to deposit to the megavault for a new PML market +// This function deposits money to the megavault, transfers the new vault +// deposit amount to the new market vault and locks the shares for the deposit +func (k Keeper) DepositToMegavaultforPML( + ctx sdk.Context, + fromSubaccount satypes.SubaccountId, + clobPairId uint32, +) error { + // Get the listing vault deposit params + vaultDepositParams := k.GetListingVaultDepositParams(ctx) + + // Deposit to the megavault + totalDepositAmount := new(big.Int).Add( + vaultDepositParams.NewVaultDepositAmount.BigInt(), + vaultDepositParams.MainVaultDepositAmount.BigInt(), + ) + mintedShares, err := k.VaultKeeper.DepositToMegavault( + ctx, + fromSubaccount, + totalDepositAmount, + ) + if err != nil { + return err + } + + vaultId := vaulttypes.VaultId{ + Type: vaulttypes.VaultType_VAULT_TYPE_CLOB, + Number: clobPairId, + } + + // Transfer the new vault deposit amount to the new market vault + err = k.VaultKeeper.AllocateToVault( + ctx, + vaultId, + vaultDepositParams.NewVaultDepositAmount.BigInt(), + ) + if err != nil { + return err + } + + // Lock the shares for the new vault deposit amount + err = k.VaultKeeper.LockShares( + ctx, + fromSubaccount.Owner, + vaulttypes.BigIntToNumShares(mintedShares), + uint32(ctx.BlockHeight())+vaultDepositParams.NumBlocksToLockShares, + ) + if err != nil { + return err + } + + // Activate vault to quoting status + err = k.VaultKeeper.SetVaultStatus( + ctx, + vaultId, + vaulttypes.VaultStatus_VAULT_STATUS_QUOTING, + ) + if err != nil { + return err + } + + return nil +} diff --git a/protocol/x/listing/keeper/listing_test.go b/protocol/x/listing/keeper/listing_test.go index 3e37a9ec09..4678a1bc7e 100644 --- a/protocol/x/listing/keeper/listing_test.go +++ b/protocol/x/listing/keeper/listing_test.go @@ -2,8 +2,20 @@ package keeper_test import ( "errors" + "math/big" "testing" + testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + testutil "github.com/dydxprotocol/v4-chain/protocol/testutil/util" + + vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + + asstypes "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" + + satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + "github.com/stretchr/testify/mock" "github.com/dydxprotocol/v4-chain/protocol/lib" @@ -16,6 +28,7 @@ import ( pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" + comettypes "github.com/cometbft/cometbft/types" "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" "github.com/dydxprotocol/v4-chain/protocol/mocks" @@ -265,27 +278,138 @@ func TestCreateClobPair(t *testing.T) { clobPairId, err := keeper.CreateClobPair(ctx, perpetualId) require.NoError(t, err) + clobPair, found := clobKeeper.GetClobPair(ctx, clobtypes.ClobPairId(clobPairId)) + require.True(t, found) + require.Equal(t, clobtypes.ClobPair_STATUS_ACTIVE, clobPair.Status) + require.Equal( + t, + clobtypes.SubticksPerTick(types.SubticksPerTick_LongTail), + clobPair.GetClobPairSubticksPerTick(), + ) + require.Equal( + t, + types.DefaultStepBaseQuantums, + clobPair.GetClobPairMinOrderBaseQuantums().ToUint64(), + ) + require.Equal(t, perpetualId, clobPair.MustGetPerpetualId()) + // Check if the clob pair was created only if we are in deliverTx mode + _, found = clobKeeper.PerpetualIdToClobPairId[perpetualId] if tc.isDeliverTx { - clobPair, found := clobKeeper.GetClobPair(ctx, clobtypes.ClobPairId(clobPairId)) require.True(t, found) - require.Equal(t, clobtypes.ClobPair_STATUS_ACTIVE, clobPair.Status) - require.Equal( - t, - clobtypes.SubticksPerTick(types.SubticksPerTick_LongTail), - clobPair.GetClobPairSubticksPerTick(), - ) - require.Equal( - t, - types.DefaultStepBaseQuantums, - clobPair.GetClobPairMinOrderBaseQuantums().ToUint64(), - ) - require.Equal(t, perpetualId, clobPair.MustGetPerpetualId()) } else { - _, found := clobKeeper.GetClobPair(ctx, clobtypes.ClobPairId(clobPairId)) require.False(t, found) } }, ) } } + +func TestDepositToMegavaultforPML(t *testing.T) { + tests := map[string]struct { + address string + balance *big.Int + asset asstypes.Asset + clobPairId uint32 + + expectedErr string + }{ + "success": { + address: constants.AliceAccAddress.String(), + balance: big.NewInt(10_000_000_000), // 10k USDC + asset: *constants.Usdc, + clobPairId: 1, + + expectedErr: "", + }, + "failure - insufficient balance": { + address: constants.AliceAccAddress.String(), + balance: big.NewInt(0), + asset: *constants.Usdc, + clobPairId: 1, + + expectedErr: "NewlyUndercollateralized", + }, + "failure - invalid clob pair id": { + address: constants.AliceAccAddress.String(), + balance: big.NewInt(10_000_000_000), // 10k USDC + asset: *constants.Usdc, + clobPairId: 100, // non existent clob pair id + + expectedErr: "ClobPair not found", + }, + } + + for name, tc := range tests { + t.Run( + name, func(t *testing.T) { + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn( + func() (genesis comettypes.GenesisDoc) { + genesis = testapp.DefaultGenesis() + // Initialize vault with its existing equity. + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *satypes.GenesisState) { + genesisState.Subaccounts = []satypes.Subaccount{ + { + Id: &vaulttypes.MegavaultMainSubaccount, + AssetPositions: []*satypes.AssetPosition{ + testutil.CreateSingleAssetPosition( + 0, + big.NewInt(1_000_000), + ), + }, + }, + { + Id: &satypes.SubaccountId{ + Owner: tc.address, + Number: 0, + }, + AssetPositions: []*satypes.AssetPosition{ + testutil.CreateSingleAssetPosition( + tc.asset.Id, + tc.balance, + ), + }, + }, + } + }, + ) + return genesis + }, + ).Build() + ctx := tApp.InitChain() + + // Set existing total shares. + err := tApp.App.VaultKeeper.SetTotalShares( + ctx, + vaulttypes.BigIntToNumShares(big.NewInt(1_000_000)), + ) + require.NoError(t, err) + + err = tApp.App.ListingKeeper.DepositToMegavaultforPML( + ctx, + satypes.SubaccountId{ + Owner: tc.address, + Number: 0, + }, + tc.clobPairId, + ) + if tc.expectedErr != "" { + require.ErrorContains(t, err, tc.expectedErr) + } else { + require.NoError(t, err) + vaultParams, exists := tApp.App.VaultKeeper.GetVaultParams( + ctx, + vaulttypes.VaultId{ + Type: vaulttypes.VaultType_VAULT_TYPE_CLOB, + Number: tc.clobPairId, + }, + ) + require.True(t, exists) + require.Equal(t, vaulttypes.VaultStatus_VAULT_STATUS_QUOTING, vaultParams.Status) + } + }, + ) + } +} diff --git a/protocol/x/listing/keeper/msg_create_market_permissionless.go b/protocol/x/listing/keeper/msg_create_market_permissionless.go index 194ff01b08..a54cff5145 100644 --- a/protocol/x/listing/keeper/msg_create_market_permissionless.go +++ b/protocol/x/listing/keeper/msg_create_market_permissionless.go @@ -31,13 +31,17 @@ func (k msgServer) CreateMarketPermissionless( return nil, err } - _, err = k.Keeper.CreateClobPair(ctx, perpetualId) + clobPairId, err := k.Keeper.CreateClobPair(ctx, perpetualId) if err != nil { k.Logger(ctx).Error("failed to create clob pair for PML market", "error", err) return nil, err } - // TODO: vault deposit for PML + err = k.Keeper.DepositToMegavaultforPML(ctx, *msg.SubaccountId, clobPairId) + if err != nil { + k.Logger(ctx).Error("failed to deposit to megavault for PML market", "error", err) + return nil, err + } return &types.MsgCreateMarketPermissionlessResponse{}, nil } diff --git a/protocol/x/listing/keeper/msg_create_market_permissionless_test.go b/protocol/x/listing/keeper/msg_create_market_permissionless_test.go index a4c531739a..a1f351e0b2 100644 --- a/protocol/x/listing/keeper/msg_create_market_permissionless_test.go +++ b/protocol/x/listing/keeper/msg_create_market_permissionless_test.go @@ -1,12 +1,17 @@ package keeper_test import ( + "math/big" + + comettypes "github.com/cometbft/cometbft/types" testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + testutil "github.com/dydxprotocol/v4-chain/protocol/testutil/util" "github.com/dydxprotocol/v4-chain/protocol/x/listing/keeper" "github.com/dydxprotocol/v4-chain/protocol/x/listing/types" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" satypes "github.com/dydxprotocol/v4-chain/protocol/x/subaccounts/types" + vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" oracletypes "github.com/skip-mev/slinky/pkg/types" marketmaptypes "github.com/skip-mev/slinky/x/marketmap/types" "github.com/skip-mev/slinky/x/marketmap/types/tickermetadata" @@ -19,27 +24,35 @@ func TestMsgCreateMarketPermissionless(t *testing.T) { tests := map[string]struct { ticker string hardCap uint32 + balance *big.Int expectedErr error }{ "success": { ticker: "TEST2-USD", hardCap: 300, + balance: big.NewInt(10_000_000_000), expectedErr: nil, }, "failure - hard cap reached": { - ticker: "TEST2-USD", - hardCap: 0, + ticker: "TEST2-USD", + hardCap: 0, + balance: big.NewInt(10_000_000_000), + expectedErr: types.ErrMarketsHardCapReached, }, "failure - ticker not found": { - ticker: "INVALID-USD", - hardCap: 300, + ticker: "INVALID-USD", + hardCap: 300, + balance: big.NewInt(10_000_000_000), + expectedErr: types.ErrMarketNotFound, }, "failure - market already listed": { - ticker: "BTC-USD", - hardCap: 300, + ticker: "BTC-USD", + hardCap: 300, + balance: big.NewInt(10_000_000_000), + expectedErr: pricestypes.ErrMarketParamPairAlreadyExists, }, } @@ -47,7 +60,38 @@ func TestMsgCreateMarketPermissionless(t *testing.T) { for name, tc := range tests { t.Run( name, func(t *testing.T) { - tApp := testapp.NewTestAppBuilder(t).Build() + tApp := testapp.NewTestAppBuilder(t).WithGenesisDocFn( + func() (genesis comettypes.GenesisDoc) { + genesis = testapp.DefaultGenesis() + // Initialize vault with its existing equity. + testapp.UpdateGenesisDocWithAppStateForModule( + &genesis, + func(genesisState *satypes.GenesisState) { + genesisState.Subaccounts = []satypes.Subaccount{ + { + Id: &vaulttypes.MegavaultMainSubaccount, + AssetPositions: []*satypes.AssetPosition{ + testutil.CreateSingleAssetPosition( + 0, + big.NewInt(1_000_000), + ), + }, + }, + { + Id: &constants.Alice_Num0, + AssetPositions: []*satypes.AssetPosition{ + testutil.CreateSingleAssetPosition( + 0, + tc.balance, + ), + }, + }, + } + }, + ) + return genesis + }, + ).Build() ctx := tApp.InitChain() k := tApp.App.ListingKeeper ms := keeper.NewMsgServerImpl(k) diff --git a/protocol/x/listing/module.go b/protocol/x/listing/module.go index 07f2fdef9a..819e921c13 100644 --- a/protocol/x/listing/module.go +++ b/protocol/x/listing/module.go @@ -103,6 +103,7 @@ type AppModule struct { clobKeeper types.ClobKeeper marketMapKeeper types.MarketMapKeeper perpetualsKeeper types.PerpetualsKeeper + vaultKeeper types.VaultKeeper } func NewAppModule( @@ -112,6 +113,7 @@ func NewAppModule( clobKeeper types.ClobKeeper, marketMapKeeper types.MarketMapKeeper, perpetualsKeeper types.PerpetualsKeeper, + vaultKeeper types.VaultKeeper, ) AppModule { return AppModule{ AppModuleBasic: NewAppModuleBasic(cdc), @@ -120,6 +122,7 @@ func NewAppModule( clobKeeper: clobKeeper, marketMapKeeper: marketMapKeeper, perpetualsKeeper: perpetualsKeeper, + vaultKeeper: vaultKeeper, } } diff --git a/protocol/x/listing/types/expected_keepers.go b/protocol/x/listing/types/expected_keepers.go index 5201a05b5d..d6b72ac3f9 100644 --- a/protocol/x/listing/types/expected_keepers.go +++ b/protocol/x/listing/types/expected_keepers.go @@ -1,6 +1,10 @@ package types import ( + "math/big" + + vaulttypes "github.com/dydxprotocol/v4-chain/protocol/x/vault/types" + sdk "github.com/cosmos/cosmos-sdk/types" clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types" perpetualtypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" @@ -30,7 +34,8 @@ type ClobKeeper interface { ) (clobtypes.ClobPair, error) AcquireNextClobPairID(ctx sdk.Context) uint32 ValidateClobPairCreation(ctx sdk.Context, clobPair *clobtypes.ClobPair) error - CreateClobPair(ctx sdk.Context, clobPair clobtypes.ClobPair) error + CreateClobPairStructures(ctx sdk.Context, clobPair clobtypes.ClobPair) error + SetClobPair(ctx sdk.Context, clobPair clobtypes.ClobPair) } type MarketMapKeeper interface { @@ -59,3 +64,27 @@ type PerpetualsKeeper interface { AcquireNextPerpetualID(ctx sdk.Context) uint32 GetAllPerpetuals(ctx sdk.Context) (list []perpetualtypes.Perpetual) } + +type VaultKeeper interface { + DepositToMegavault( + ctx sdk.Context, + fromSubaccount satypes.SubaccountId, + quoteQuantums *big.Int, + ) (mintedShares *big.Int, err error) + AllocateToVault( + ctx sdk.Context, + vaultId vaulttypes.VaultId, + quantums *big.Int, + ) error + LockShares( + ctx sdk.Context, + ownerAddress string, + sharesToLock vaulttypes.NumShares, + tilBlock uint32, + ) error + SetVaultStatus( + ctx sdk.Context, + vaultId vaulttypes.VaultId, + status vaulttypes.VaultStatus, + ) error +} diff --git a/protocol/x/listing/types/genesis.go b/protocol/x/listing/types/genesis.go index 0bbef0cd95..ae7b369acf 100644 --- a/protocol/x/listing/types/genesis.go +++ b/protocol/x/listing/types/genesis.go @@ -3,7 +3,8 @@ package types // DefaultGenesis returns the default stats genesis state. func DefaultGenesis() *GenesisState { return &GenesisState{ - HardCapForMarkets: 0, + HardCapForMarkets: 500, + ListingVaultDepositParams: DefaultParams(), } } diff --git a/protocol/x/listing/types/genesis.pb.go b/protocol/x/listing/types/genesis.pb.go index f42e4ac001..f8aaeda512 100644 --- a/protocol/x/listing/types/genesis.pb.go +++ b/protocol/x/listing/types/genesis.pb.go @@ -5,6 +5,7 @@ package types import ( fmt "fmt" + _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" io "io" math "math" @@ -27,6 +28,8 @@ type GenesisState struct { // hard_cap_for_markets is the hard cap for the number of markets that can be // listed HardCapForMarkets uint32 `protobuf:"varint,1,opt,name=hard_cap_for_markets,json=hardCapForMarkets,proto3" json:"hard_cap_for_markets,omitempty"` + // listing_vault_deposit_params is the params for PML megavault deposits + ListingVaultDepositParams ListingVaultDepositParams `protobuf:"bytes,2,opt,name=listing_vault_deposit_params,json=listingVaultDepositParams,proto3" json:"listing_vault_deposit_params"` } func (m *GenesisState) Reset() { *m = GenesisState{} } @@ -69,6 +72,13 @@ func (m *GenesisState) GetHardCapForMarkets() uint32 { return 0 } +func (m *GenesisState) GetListingVaultDepositParams() ListingVaultDepositParams { + if m != nil { + return m.ListingVaultDepositParams + } + return ListingVaultDepositParams{} +} + func init() { proto.RegisterType((*GenesisState)(nil), "dydxprotocol.listing.GenesisState") } @@ -78,19 +88,24 @@ func init() { } var fileDescriptor_d378c53d0f5a34b3 = []byte{ - // 186 bytes of a gzipped FileDescriptorProto + // 269 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4a, 0xa9, 0x4c, 0xa9, 0x28, 0x28, 0xca, 0x2f, 0xc9, 0x4f, 0xce, 0xcf, 0xd1, 0xcf, 0xc9, 0x2c, 0x2e, 0xc9, 0xcc, 0x4b, 0xd7, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x03, 0x4b, 0x08, 0x89, 0x20, 0xab, 0xd1, - 0x83, 0xaa, 0x51, 0xb2, 0xe7, 0xe2, 0x71, 0x87, 0x28, 0x0b, 0x2e, 0x49, 0x2c, 0x49, 0x15, 0xd2, - 0xe7, 0x12, 0xc9, 0x48, 0x2c, 0x4a, 0x89, 0x4f, 0x4e, 0x2c, 0x88, 0x4f, 0xcb, 0x2f, 0x8a, 0xcf, - 0x4d, 0x2c, 0xca, 0x4e, 0x2d, 0x29, 0x96, 0x60, 0x54, 0x60, 0xd4, 0xe0, 0x0d, 0x12, 0x04, 0xc9, - 0x39, 0x27, 0x16, 0xb8, 0xe5, 0x17, 0xf9, 0x42, 0x24, 0x9c, 0x82, 0x4f, 0x3c, 0x92, 0x63, 0xbc, - 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09, 0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, - 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0xca, 0x32, 0x3d, 0xb3, 0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, - 0x57, 0x1f, 0xc5, 0x7d, 0x65, 0x26, 0xba, 0xc9, 0x19, 0x89, 0x99, 0x79, 0xfa, 0x70, 0x91, 0x0a, - 0xb8, 0x9b, 0x4b, 0x2a, 0x0b, 0x52, 0x8b, 0x93, 0xd8, 0xc0, 0x32, 0xc6, 0x80, 0x00, 0x00, 0x00, - 0xff, 0xff, 0x2c, 0x8a, 0x33, 0x2d, 0xd8, 0x00, 0x00, 0x00, + 0x83, 0xaa, 0x91, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0x8b, 0xea, 0x83, 0x58, 0x10, 0xb5, 0x52, + 0x8a, 0x58, 0xcd, 0x2b, 0x48, 0x2c, 0x4a, 0xcc, 0x85, 0x1a, 0xa7, 0xb4, 0x9d, 0x91, 0x8b, 0xc7, + 0x1d, 0x62, 0x41, 0x70, 0x49, 0x62, 0x49, 0xaa, 0x90, 0x3e, 0x97, 0x48, 0x46, 0x62, 0x51, 0x4a, + 0x7c, 0x72, 0x62, 0x41, 0x7c, 0x5a, 0x7e, 0x51, 0x7c, 0x6e, 0x62, 0x51, 0x76, 0x6a, 0x49, 0xb1, + 0x04, 0xa3, 0x02, 0xa3, 0x06, 0x6f, 0x90, 0x20, 0x48, 0xce, 0x39, 0xb1, 0xc0, 0x2d, 0xbf, 0xc8, + 0x17, 0x22, 0x21, 0x54, 0xc6, 0x25, 0x03, 0x35, 0x39, 0xbe, 0x2c, 0xb1, 0x34, 0xa7, 0x24, 0x3e, + 0x25, 0xb5, 0x20, 0xbf, 0x38, 0xb3, 0x24, 0x1e, 0x62, 0x8f, 0x04, 0x93, 0x02, 0xa3, 0x06, 0xb7, + 0x91, 0xbe, 0x1e, 0x36, 0x77, 0xeb, 0xf9, 0x40, 0xe8, 0x30, 0x90, 0x46, 0x17, 0x88, 0xbe, 0x00, + 0xb0, 0x36, 0x27, 0x96, 0x13, 0xf7, 0xe4, 0x19, 0x82, 0x24, 0x73, 0x70, 0x2a, 0x08, 0x3e, 0xf1, + 0x48, 0x8e, 0xf1, 0xc2, 0x23, 0x39, 0xc6, 0x07, 0x8f, 0xe4, 0x18, 0x27, 0x3c, 0x96, 0x63, 0xb8, + 0xf0, 0x58, 0x8e, 0xe1, 0xc6, 0x63, 0x39, 0x86, 0x28, 0xcb, 0xf4, 0xcc, 0x92, 0x8c, 0xd2, 0x24, + 0xbd, 0xe4, 0xfc, 0x5c, 0x7d, 0x94, 0x10, 0x28, 0x33, 0xd1, 0x4d, 0xce, 0x48, 0xcc, 0xcc, 0xd3, + 0x87, 0x8b, 0x54, 0xc0, 0x43, 0xa5, 0xa4, 0xb2, 0x20, 0xb5, 0x38, 0x89, 0x0d, 0x2c, 0x63, 0x0c, + 0x08, 0x00, 0x00, 0xff, 0xff, 0xfd, 0xee, 0x81, 0x57, 0x8a, 0x01, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -113,6 +128,16 @@ func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + { + size, err := m.ListingVaultDepositParams.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 if m.HardCapForMarkets != 0 { i = encodeVarintGenesis(dAtA, i, uint64(m.HardCapForMarkets)) i-- @@ -141,6 +166,8 @@ func (m *GenesisState) Size() (n int) { if m.HardCapForMarkets != 0 { n += 1 + sovGenesis(uint64(m.HardCapForMarkets)) } + l = m.ListingVaultDepositParams.Size() + n += 1 + l + sovGenesis(uint64(l)) return n } @@ -198,6 +225,39 @@ func (m *GenesisState) Unmarshal(dAtA []byte) error { break } } + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListingVaultDepositParams", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ListingVaultDepositParams.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/protocol/x/listing/types/params.go b/protocol/x/listing/types/params.go index 2cf5960179..073654d4d7 100644 --- a/protocol/x/listing/types/params.go +++ b/protocol/x/listing/types/params.go @@ -5,7 +5,7 @@ import "github.com/dydxprotocol/v4-chain/protocol/dtypes" // DefaultParams defines the default parameters for listing vault deposits. func DefaultParams() ListingVaultDepositParams { return ListingVaultDepositParams{ - NewVaultDepositAmount: dtypes.NewIntFromUint64(10_000), + NewVaultDepositAmount: dtypes.NewIntFromUint64(10_000_000_000), // 10_000 USDC MainVaultDepositAmount: dtypes.NewIntFromUint64(0), NumBlocksToLockShares: 30 * 24 * 3600, // 30 days } diff --git a/protocol/x/vault/keeper/params.go b/protocol/x/vault/keeper/params.go index a054e9d057..3903281396 100644 --- a/protocol/x/vault/keeper/params.go +++ b/protocol/x/vault/keeper/params.go @@ -117,6 +117,20 @@ func (k Keeper) SetVaultParams( return nil } +// SetVaultStatus sets `VaultParams.Status` in state for a given vault. +func (k Keeper) SetVaultStatus( + ctx sdk.Context, + vaultId types.VaultId, + status types.VaultStatus, +) error { + vaultParams, exists := k.GetVaultParams(ctx, vaultId) + if !exists { + return types.ErrVaultParamsNotFound + } + vaultParams.Status = status + return k.SetVaultParams(ctx, vaultId, vaultParams) +} + // getVaultParamsIterator returns an iterator over all VaultParams. func (k Keeper) getVaultParamsIterator(ctx sdk.Context) storetypes.Iterator { store := prefix.NewStore(ctx.KVStore(k.storeKey), []byte(types.VaultParamsKeyPrefix))