diff --git a/protocol/app/ante.go b/protocol/app/ante.go index e376b7b84b..074b9d5b58 100644 --- a/protocol/app/ante.go +++ b/protocol/app/ante.go @@ -103,6 +103,10 @@ func NewAnteHandler(options HandlerOptions) (sdk.AnteHandler, error) { return nil, errorsmod.Wrapf(sdkerrors.ErrLogic, "prices keeper is required for ante builder") } + if options.MarketMapKeeper == nil { + return nil, errorsmod.Wrapf(sdkerrors.ErrLogic, "market map keeper is required for ante builder") + } + h := &lockingAnteHandler{ authStoreKey: options.AuthStoreKey, setupContextDecorator: ante.NewSetUpContextDecorator(), diff --git a/protocol/app/ante/market_update.go b/protocol/app/ante/market_update.go index 2b8d86e4a4..9575f4da49 100644 --- a/protocol/app/ante/market_update.go +++ b/protocol/app/ante/market_update.go @@ -3,6 +3,7 @@ package ante import ( "errors" "fmt" + slinkytypes "github.com/skip-mev/slinky/pkg/types" errorsmod "cosmossdk.io/errors" sdk "github.com/cosmos/cosmos-sdk/types" @@ -14,13 +15,20 @@ import ( pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" ) -var ErrNoCrossMarketUpdates = errors.New("cannot call MsgUpdateMarkets or MsgUpsertMarkets " + - "on a market listed as cross margin") +var ErrRestrictedMarketUpdates = errors.New("cannot call MsgUpdateMarkets or MsgUpsertMarkets " + + "on a restricted market") type MarketMapKeeper interface { GetAllMarkets(ctx sdk.Context) (map[string]mmtypes.Market, error) } +var ( + cpUSDTUSD = slinkytypes.CurrencyPair{ + Base: "USDT", + Quote: "USD", + } +) + type ValidateMarketUpdateDecorator struct { perpKeeper perpetualstypes.PerpetualsKeeper priceKeeper pricestypes.PricesKeeper @@ -80,8 +88,8 @@ func (d ValidateMarketUpdateDecorator) AnteHandle( return ctx, fmt.Errorf("unrecognized message type: %T", msg) } - if contains := d.doMarketsContainCrossMarket(ctx, markets); contains { - return ctx, ErrNoCrossMarketUpdates + if contains, ticker := d.doMarketsContainRestrictedMarket(ctx, markets); contains { + return ctx, fmt.Errorf("%w: %s", ErrRestrictedMarketUpdates, ticker) } // check if the market updates are safe @@ -92,10 +100,16 @@ func (d ValidateMarketUpdateDecorator) AnteHandle( return next(ctx, tx, simulate) } -func (d ValidateMarketUpdateDecorator) doMarketsContainCrossMarket(ctx sdk.Context, markets []mmtypes.Market) bool { +// doMarketsContainRestrictedMarket checks if any of the given markets are restricted: +// 1. markets listed as CROSS perpetuals are restricted +// 2. the USDT/USD market is always restricted +func (d ValidateMarketUpdateDecorator) doMarketsContainRestrictedMarket( + ctx sdk.Context, + markets []mmtypes.Market, +) (bool, string) { // Grab all the perpetuals markets perps := d.perpKeeper.GetAllPerpetuals(ctx) - perpsMap := make(map[string]perpetualstypes.PerpetualMarketType) + restrictedMap := make(map[string]bool, len(perps)) // Attempt to fetch the corresponding Prices market and map it to a currency pair for _, perp := range perps { @@ -107,20 +121,24 @@ func (d ValidateMarketUpdateDecorator) doMarketsContainCrossMarket(ctx sdk.Conte if err != nil { continue } - perpsMap[cp.String()] = perp.Params.MarketType + restrictedMap[cp.String()] = perp.Params.MarketType == perpetualstypes. + PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS } + // add usdt/usd market to be restricted + restrictedMap[cpUSDTUSD.String()] = true + // Look in the mapped currency pairs to see if we have invalid updates for _, market := range markets { ticker := market.Ticker.CurrencyPair.String() - marketType, found := perpsMap[ticker] - if found && marketType == perpetualstypes.PerpetualMarketType_PERPETUAL_MARKET_TYPE_CROSS { - return true + restricted, found := restrictedMap[ticker] + if found && restricted { + return true, ticker } } - return false + return false, "" } // doMarketsUpdateEnabledValues checks if the given markets updates are safe, specifically: diff --git a/protocol/app/ante/market_update_test.go b/protocol/app/ante/market_update_test.go index f7ca81c160..9fc6c49e8e 100644 --- a/protocol/app/ante/market_update_test.go +++ b/protocol/app/ante/market_update_test.go @@ -1,32 +1,31 @@ package ante_test import ( - storetypes "cosmossdk.io/store/types" "math/rand" "testing" sdkmath "cosmossdk.io/math" - banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - "github.com/dydxprotocol/v4-chain/protocol/dtypes" - "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" - assets "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" - perpetualtypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" - prices_types "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" - "github.com/skip-mev/slinky/pkg/types" - mmtypes "github.com/skip-mev/slinky/x/marketmap/types" - + storetypes "cosmossdk.io/store/types" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/tx" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/tx/signing" xauthsigning "github.com/cosmos/cosmos-sdk/x/auth/signing" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/skip-mev/slinky/pkg/types" + mmtypes "github.com/skip-mev/slinky/x/marketmap/types" "github.com/stretchr/testify/require" "github.com/dydxprotocol/v4-chain/protocol/app/ante" + "github.com/dydxprotocol/v4-chain/protocol/dtypes" slinkylib "github.com/dydxprotocol/v4-chain/protocol/lib/slinky" testante "github.com/dydxprotocol/v4-chain/protocol/testutil/ante" testapp "github.com/dydxprotocol/v4-chain/protocol/testutil/app" + "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" + assets "github.com/dydxprotocol/v4-chain/protocol/x/assets/types" + perpetualtypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" + prices_types "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" ) func TestIsMarketUpdateTx(t *testing.T) { @@ -212,6 +211,25 @@ var ( }, } + testUSDTUSDMarket = mmtypes.Market{ + Ticker: mmtypes.Ticker{ + CurrencyPair: types.CurrencyPair{ + Base: "USDT", + Quote: "USD", + }, + Decimals: 1, + MinProviderCount: 1, + Enabled: true, + Metadata_JSON: "", + }, + ProviderConfigs: []mmtypes.ProviderConfig{ + { + Name: "test_provider", + OffChainTicker: "USDT/USD", + }, + }, + } + enabledTestMarketWithProviderConfig = mmtypes.Market{ Ticker: mmtypes.Ticker{ CurrencyPair: types.CurrencyPair{ @@ -726,6 +744,66 @@ func TestValidateMarketUpdateDecorator_AnteHandle(t *testing.T) { }, wantErr: false, }, + { + name: "always reject USDT/USD - simulate - upsert", + args: args{ + msgs: []sdk.Msg{ + &mmtypes.MsgUpsertMarkets{ + Authority: constants.BobAccAddress.String(), + Markets: []mmtypes.Market{ + testUSDTUSDMarket, + }, + }, + }, + simulate: true, + }, + wantErr: true, + }, + { + name: "always reject USDT/USD - upsert", + args: args{ + msgs: []sdk.Msg{ + &mmtypes.MsgUpsertMarkets{ + Authority: constants.BobAccAddress.String(), + Markets: []mmtypes.Market{ + testUSDTUSDMarket, + }, + }, + }, + simulate: false, + }, + wantErr: true, + }, + { + name: "always reject USDT/USD - simulate - update", + args: args{ + msgs: []sdk.Msg{ + &mmtypes.MsgUpdateMarkets{ + Authority: constants.BobAccAddress.String(), + UpdateMarkets: []mmtypes.Market{ + testUSDTUSDMarket, + }, + }, + }, + simulate: true, + }, + wantErr: true, + }, + { + name: "always reject USDT/USD - update", + args: args{ + msgs: []sdk.Msg{ + &mmtypes.MsgUpdateMarkets{ + Authority: constants.BobAccAddress.String(), + UpdateMarkets: []mmtypes.Market{ + testUSDTUSDMarket, + }, + }, + }, + simulate: false, + }, + wantErr: true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/protocol/app/ante_test.go b/protocol/app/ante_test.go index ef49aa02f9..4d4ebde573 100644 --- a/protocol/app/ante_test.go +++ b/protocol/app/ante_test.go @@ -29,6 +29,7 @@ func newHandlerOptions() app.HandlerOptions { AuthStoreKey: dydxApp.CommitMultiStore().(*rootmulti.Store).StoreKeysByName()[authtypes.StoreKey], PerpetualsKeeper: dydxApp.PerpetualsKeeper, PricesKeeper: dydxApp.PricesKeeper, + MarketMapKeeper: &dydxApp.MarketMapKeeper, } } @@ -60,6 +61,10 @@ func TestNewAnteHandler_Error(t *testing.T) { handlerMutation: func(options *app.HandlerOptions) { options.PricesKeeper = nil }, errorMsg: "prices keeper is required for ante builder", }, + "nil MarketMapKeeper": { + handlerMutation: func(options *app.HandlerOptions) { options.MarketMapKeeper = nil }, + errorMsg: "market map keeper is required for ante builder", + }, "nil handlerOptions.SignModeHandler": { handlerMutation: func(options *app.HandlerOptions) { options.SignModeHandler = nil }, errorMsg: "sign mode handler is required for ante builder",