From e8ff10f7874df488d5702dcdcb9b9f6538c04454 Mon Sep 17 00:00:00 2001 From: Xingchen Liao Date: Mon, 12 Sep 2022 13:25:28 -0700 Subject: [PATCH 1/8] Refactor wasmbinding to use message decorator (#245) * Refactor wasmbinding to use message decorator * Fix fmt Co-authored-by: Cyson --- app/app.go | 2 +- cmd/seid/cmd/root.go | 8 +- wasmbinding/encoder.go | 43 ----------- wasmbinding/message_plugin.go | 128 +++++++++++++++++++++++++++++++ wasmbinding/test/encoder_test.go | 51 ++++-------- wasmbinding/wasm.go | 7 ++ 6 files changed, 151 insertions(+), 88 deletions(-) delete mode 100644 wasmbinding/encoder.go create mode 100644 wasmbinding/message_plugin.go diff --git a/app/app.go b/app/app.go index 7f9937622c..8fdc2083a9 100644 --- a/app/app.go +++ b/app/app.go @@ -502,7 +502,7 @@ func New( // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks supportedFeatures := "iterator,staking,stargate,sei" - wasmOpts = append(wasmbinding.RegisterCustomPlugins(&app.OracleKeeper, &app.DexKeeper, &app.EpochKeeper), wasmOpts...) + wasmOpts = append(wasmbinding.RegisterCustomPlugins(&app.OracleKeeper, &app.DexKeeper, &app.EpochKeeper, &app.AccountKeeper, app.MsgServiceRouter()), wasmOpts...) app.WasmKeeper = wasm.NewKeeper( appCodec, keys[wasm.StoreKey], diff --git a/cmd/seid/cmd/root.go b/cmd/seid/cmd/root.go index 2a49e8c3b8..9434693d8d 100644 --- a/cmd/seid/cmd/root.go +++ b/cmd/seid/cmd/root.go @@ -7,7 +7,6 @@ import ( "path/filepath" "github.com/CosmWasm/wasmd/x/wasm" - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/client/config" @@ -31,7 +30,6 @@ import ( "github.com/sei-protocol/sei-chain/app" "github.com/sei-protocol/sei-chain/app/params" "github.com/sei-protocol/sei-chain/utils/tracing" - "github.com/sei-protocol/sei-chain/wasmbinding" "github.com/spf13/cast" "github.com/spf13/cobra" tmcli "github.com/tendermint/tendermint/libs/cli" @@ -240,10 +238,6 @@ func newApp( panic(err) } - wasmopts := []wasm.Option{wasmkeeper.WithMessageEncoders(&wasmkeeper.MessageEncoders{ - Custom: wasmbinding.CustomEncoder, - })} - return app.New( logger, db, @@ -255,7 +249,7 @@ func newApp( app.MakeEncodingConfig(), wasm.EnableAllProposals, appOpts, - wasmopts, + []wasm.Option{}, []aclkeeper.Option{}, baseapp.SetPruning(pruningOpts), baseapp.SetMinGasPrices(cast.ToString(appOpts.Get(server.FlagMinGasPrices))), diff --git a/wasmbinding/encoder.go b/wasmbinding/encoder.go deleted file mode 100644 index b26971ebdf..0000000000 --- a/wasmbinding/encoder.go +++ /dev/null @@ -1,43 +0,0 @@ -package wasmbinding - -import ( - "encoding/json" - - wasmvmtypes "github.com/CosmWasm/wasmvm/types" - sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" - tokenfactorywasm "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm" -) - -type SeiWasmMessage struct { - PlaceOrders json.RawMessage `json:"place_orders,omitempty"` - CancelOrders json.RawMessage `json:"cancel_orders,omitempty"` - CreateDenom json.RawMessage `json:"create_denom,omitempty"` - MintTokens json.RawMessage `json:"mint_tokens,omitempty"` - BurnTokens json.RawMessage `json:"burn_tokens,omitempty"` - ChangeAdmin json.RawMessage `json:"change_admin,omitempty"` -} - -func CustomEncoder(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { - var parsedMessage SeiWasmMessage - if err := json.Unmarshal(msg, &parsedMessage); err != nil { - return []sdk.Msg{}, sdkerrors.Wrap(err, "Error parsing Sei Wasm Message") - } - switch { - case parsedMessage.PlaceOrders != nil: - return dexwasm.EncodeDexPlaceOrders(parsedMessage.PlaceOrders, sender) - case parsedMessage.CancelOrders != nil: - return dexwasm.EncodeDexCancelOrders(parsedMessage.CancelOrders, sender) - case parsedMessage.CreateDenom != nil: - return tokenfactorywasm.EncodeTokenFactoryCreateDenom(parsedMessage.CreateDenom, sender) - case parsedMessage.MintTokens != nil: - return tokenfactorywasm.EncodeTokenFactoryMint(parsedMessage.MintTokens, sender) - case parsedMessage.BurnTokens != nil: - return tokenfactorywasm.EncodeTokenFactoryBurn(parsedMessage.BurnTokens, sender) - case parsedMessage.ChangeAdmin != nil: - return tokenfactorywasm.EncodeTokenFactoryChangeAdmin(parsedMessage.ChangeAdmin, sender) - default: - return []sdk.Msg{}, wasmvmtypes.UnsupportedRequest{Kind: "Unknown Sei Wasm Message"} - } -} diff --git a/wasmbinding/message_plugin.go b/wasmbinding/message_plugin.go new file mode 100644 index 0000000000..3c46b92d0f --- /dev/null +++ b/wasmbinding/message_plugin.go @@ -0,0 +1,128 @@ +package wasmbinding + +import ( + "encoding/json" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" + tokenfactorywasm "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm" +) + +// CustomMessageDecorator returns decorator for custom CosmWasm bindings messages +func CustomMessageDecorator( + router wasmkeeper.MessageRouter, + accountKeeper *authkeeper.AccountKeeper, +) func(wasmkeeper.Messenger) wasmkeeper.Messenger { + return func(old wasmkeeper.Messenger) wasmkeeper.Messenger { + return &CustomMessenger{ + router: router, + wrapped: old, + accountKeeper: accountKeeper, + } + } +} + +type CustomMessenger struct { + router wasmkeeper.MessageRouter + wrapped wasmkeeper.Messenger + accountKeeper *authkeeper.AccountKeeper +} + +type SeiWasmMessage struct { + PlaceOrders json.RawMessage `json:"place_orders,omitempty"` + CancelOrders json.RawMessage `json:"cancel_orders,omitempty"` + CreateDenom json.RawMessage `json:"create_denom,omitempty"` + MintTokens json.RawMessage `json:"mint_tokens,omitempty"` + BurnTokens json.RawMessage `json:"burn_tokens,omitempty"` + ChangeAdmin json.RawMessage `json:"change_admin,omitempty"` +} + +var _ wasmkeeper.Messenger = &CustomMessenger{} + +// DispatchMsg executes on the bindingMsgs +func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) { + if msg.Custom != nil { + return m.DispatchCustomMsg(ctx, contractAddr, contractIBCPortID, msg) + } + return m.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) +} + +// DispatchCustomMsg function is forked from wasmd. sdk.Msg will be validated and routed to the corresponding module msg server in this function. +func (m *CustomMessenger) DispatchCustomMsg( + ctx sdk.Context, + contractAddr sdk.AccAddress, + contractIBCPortID string, + msg wasmvmtypes.CosmosMsg, +) (events []sdk.Event, data [][]byte, err error) { + var parsedMessage SeiWasmMessage + if err := json.Unmarshal(msg.Custom, &parsedMessage); err != nil { + return nil, nil, sdkerrors.Wrap(err, "Error parsing Sei Wasm Message") + } + + var sdkMsgs []sdk.Msg + switch { + case parsedMessage.PlaceOrders != nil: + sdkMsgs, err = dexwasm.EncodeDexPlaceOrders(parsedMessage.PlaceOrders, contractAddr) + case parsedMessage.CancelOrders != nil: + sdkMsgs, err = dexwasm.EncodeDexCancelOrders(parsedMessage.CancelOrders, contractAddr) + case parsedMessage.CreateDenom != nil: + sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryCreateDenom(parsedMessage.CreateDenom, contractAddr) + case parsedMessage.MintTokens != nil: + sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryMint(parsedMessage.MintTokens, contractAddr) + case parsedMessage.BurnTokens != nil: + sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryBurn(parsedMessage.BurnTokens, contractAddr) + case parsedMessage.ChangeAdmin != nil: + sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryChangeAdmin(parsedMessage.ChangeAdmin, contractAddr) + default: + sdkMsgs, err = []sdk.Msg{}, wasmvmtypes.UnsupportedRequest{Kind: "Unknown Sei Wasm Message"} + } + if err != nil { + return nil, nil, err + } + + for _, sdkMsg := range sdkMsgs { + res, err := m.handleSdkMessage(ctx, contractAddr, sdkMsg) + if err != nil { + return nil, nil, err + } + // append data + data = append(data, res.Data) + // append events + sdkEvents := make([]sdk.Event, len(res.Events)) + for i := range res.Events { + sdkEvents[i] = sdk.Event(res.Events[i]) + } + events = append(events, sdkEvents...) + } + return events, data, nil +} + +// This function is forked from wasmd. sdk.Msg will be validated and routed to the corresponding module msg server in this function. +func (m *CustomMessenger) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) (*sdk.Result, error) { + if err := msg.ValidateBasic(); err != nil { + return nil, err + } + // make sure this account can send it + for _, acct := range msg.GetSigners() { + if !acct.Equals(contractAddr) { + return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission") + } + } + + // find the handler and execute it + if handler := m.router.Handler(msg); handler != nil { + // ADR 031 request type routing + msgResult, err := handler(ctx, msg) + return msgResult, err + } + // legacy sdk.Msg routing + // Assuming that the app developer has migrated all their Msgs to + // proto messages and has registered all `Msg services`, then this + // path should never be called, because all those Msgs should be + // registered within the `msgServiceRouter` already. + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) +} diff --git a/wasmbinding/test/encoder_test.go b/wasmbinding/test/encoder_test.go index 8c8d17b427..ad8a07580c 100644 --- a/wasmbinding/test/encoder_test.go +++ b/wasmbinding/test/encoder_test.go @@ -5,10 +5,11 @@ import ( "testing" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/sei-protocol/sei-chain/wasmbinding" "github.com/sei-protocol/sei-chain/wasmbinding/bindings" + dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" "github.com/sei-protocol/sei-chain/x/dex/types" dextypes "github.com/sei-protocol/sei-chain/x/dex/types" + tokenfactorywasm "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm" tokenfactorytypes "github.com/sei-protocol/sei-chain/x/tokenfactory/types" "github.com/stretchr/testify/require" ) @@ -36,13 +37,9 @@ func TestEncodePlaceOrder(t *testing.T) { Funds: []sdk.Coin{fund}, ContractAddr: TEST_TARGET_CONTRACT, } - serialized, _ := json.Marshal(msg) - msgData := wasmbinding.SeiWasmMessage{ - PlaceOrders: serialized, - } - serializedMsg, _ := json.Marshal(msgData) + serializedMsg, _ := json.Marshal(msg) - decodedMsgs, err := wasmbinding.CustomEncoder(contractAddr, serializedMsg) + decodedMsgs, err := dexwasm.EncodeDexPlaceOrders(serializedMsg, contractAddr) require.NoError(t, err) require.Equal(t, 1, len(decodedMsgs)) typedDecodedMsg, ok := decodedMsgs[0].(*dextypes.MsgPlaceOrders) @@ -65,13 +62,9 @@ func TestDecodeOrderCancellation(t *testing.T) { }, ContractAddr: TEST_TARGET_CONTRACT, } - serialized, _ := json.Marshal(msg) - msgData := wasmbinding.SeiWasmMessage{ - CancelOrders: serialized, - } - serializedMsg, _ := json.Marshal(msgData) + serializedMsg, _ := json.Marshal(msg) - decodedMsgs, err := wasmbinding.CustomEncoder(contractAddr, serializedMsg) + decodedMsgs, err := dexwasm.EncodeDexCancelOrders(serializedMsg, contractAddr) require.NoError(t, err) require.Equal(t, 1, len(decodedMsgs)) typedDecodedMsg, ok := decodedMsgs[0].(*dextypes.MsgCancelOrders) @@ -94,13 +87,9 @@ func TestEncodeCreateDenom(t *testing.T) { msg := bindings.CreateDenom{ Subdenom: "subdenom", } - serialized, _ := json.Marshal(msg) - msgData := wasmbinding.SeiWasmMessage{ - CreateDenom: serialized, - } - serializedMsg, _ := json.Marshal(msgData) + serializedMsg, _ := json.Marshal(msg) - decodedMsgs, err := wasmbinding.CustomEncoder(contractAddr, serializedMsg) + decodedMsgs, err := tokenfactorywasm.EncodeTokenFactoryCreateDenom(serializedMsg, contractAddr) require.NoError(t, err) require.Equal(t, 1, len(decodedMsgs)) typedDecodedMsg, ok := decodedMsgs[0].(*tokenfactorytypes.MsgCreateDenom) @@ -118,13 +107,9 @@ func TestEncodeMint(t *testing.T) { msg := bindings.MintTokens{ Amount: sdk.Coin{Amount: sdk.NewInt(100), Denom: "subdenom"}, } - serialized, _ := json.Marshal(msg) - msgData := wasmbinding.SeiWasmMessage{ - MintTokens: serialized, - } - serializedMsg, _ := json.Marshal(msgData) + serializedMsg, _ := json.Marshal(msg) - decodedMsgs, err := wasmbinding.CustomEncoder(contractAddr, serializedMsg) + decodedMsgs, err := tokenfactorywasm.EncodeTokenFactoryMint(serializedMsg, contractAddr) require.NoError(t, err) require.Equal(t, 1, len(decodedMsgs)) typedDecodedMsg, ok := decodedMsgs[0].(*tokenfactorytypes.MsgMint) @@ -142,13 +127,9 @@ func TestEncodeBurn(t *testing.T) { msg := bindings.BurnTokens{ Amount: sdk.Coin{Amount: sdk.NewInt(10), Denom: "subdenom"}, } - serialized, _ := json.Marshal(msg) - msgData := wasmbinding.SeiWasmMessage{ - BurnTokens: serialized, - } - serializedMsg, _ := json.Marshal(msgData) + serializedMsg, _ := json.Marshal(msg) - decodedMsgs, err := wasmbinding.CustomEncoder(contractAddr, serializedMsg) + decodedMsgs, err := tokenfactorywasm.EncodeTokenFactoryBurn(serializedMsg, contractAddr) require.NoError(t, err) require.Equal(t, 1, len(decodedMsgs)) typedDecodedMsg, ok := decodedMsgs[0].(*tokenfactorytypes.MsgBurn) @@ -167,13 +148,9 @@ func TestEncodeChangeAdmin(t *testing.T) { Denom: "factory/sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw/subdenom", NewAdminAddress: "sei1hjfwcza3e3uzeznf3qthhakdr9juetl7g6esl4", } - serialized, _ := json.Marshal(msg) - msgData := wasmbinding.SeiWasmMessage{ - ChangeAdmin: serialized, - } - serializedMsg, _ := json.Marshal(msgData) + serializedMsg, _ := json.Marshal(msg) - decodedMsgs, err := wasmbinding.CustomEncoder(contractAddr, serializedMsg) + decodedMsgs, err := tokenfactorywasm.EncodeTokenFactoryChangeAdmin(serializedMsg, contractAddr) require.NoError(t, err) require.Equal(t, 1, len(decodedMsgs)) typedDecodedMsg, ok := decodedMsgs[0].(*tokenfactorytypes.MsgChangeAdmin) diff --git a/wasmbinding/wasm.go b/wasmbinding/wasm.go index d6d6844578..3ddba5ac8e 100644 --- a/wasmbinding/wasm.go +++ b/wasmbinding/wasm.go @@ -3,6 +3,7 @@ package wasmbinding import ( "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" dexkeeper "github.com/sei-protocol/sei-chain/x/dex/keeper" epochwasm "github.com/sei-protocol/sei-chain/x/epoch/client/wasm" @@ -15,6 +16,8 @@ func RegisterCustomPlugins( oracle *oraclekeeper.Keeper, dex *dexkeeper.Keeper, epoch *epochkeeper.Keeper, + accountKeeper *authkeeper.AccountKeeper, + router wasmkeeper.MessageRouter, ) []wasmkeeper.Option { dexHandler := dexwasm.NewDexWasmQueryHandler(dex) oracleHandler := oraclewasm.NewOracleWasmQueryHandler(oracle) @@ -24,8 +27,12 @@ func RegisterCustomPlugins( queryPluginOpt := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ Custom: CustomQuerier(wasmQueryPlugin), }) + messengerDecoratorOpt := wasmkeeper.WithMessageHandlerDecorator( + CustomMessageDecorator(router, accountKeeper), + ) return []wasm.Option{ queryPluginOpt, + messengerDecoratorOpt, } } From c71a3f345a0e13e612f9197ccad55f90ad94bfa4 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Wed, 12 Oct 2022 22:02:45 -0700 Subject: [PATCH 2/8] [wasmbinding] Add message handler and dep validator --- aclmapping/dependency_generator.go | 8 +- aclmapping/wasm/mappings.go | 32 +-- app/app.go | 33 ++- go.mod | 2 +- go.sum | 2 - wasmbinding/encoder.go | 43 ++++ wasmbinding/message_plugin.go | 249 ++++++++++++++--------- wasmbinding/test/message_handler_test.go | 161 +++++++++++++++ wasmbinding/wasm.go | 15 +- 9 files changed, 401 insertions(+), 144 deletions(-) create mode 100644 wasmbinding/encoder.go create mode 100644 wasmbinding/test/message_handler_test.go diff --git a/aclmapping/dependency_generator.go b/aclmapping/dependency_generator.go index 2d875e4022..3dc77d374f 100644 --- a/aclmapping/dependency_generator.go +++ b/aclmapping/dependency_generator.go @@ -1,7 +1,6 @@ package aclmapping import ( - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" aclbankmapping "github.com/sei-protocol/sei-chain/aclmapping/bank" acldexmapping "github.com/sei-protocol/sei-chain/aclmapping/dex" @@ -9,11 +8,10 @@ import ( ) type CustomDependencyGenerator struct { - WasmKeeper wasmkeeper.Keeper } -func NewCustomDependencyGenerator(wasmKeeper wasmkeeper.Keeper) CustomDependencyGenerator { - return CustomDependencyGenerator{WasmKeeper: wasmKeeper} +func NewCustomDependencyGenerator() CustomDependencyGenerator { + return CustomDependencyGenerator{} } func (customDepGen CustomDependencyGenerator) GetCustomDependencyGenerators() aclkeeper.DependencyGeneratorMap { @@ -21,7 +19,7 @@ func (customDepGen CustomDependencyGenerator) GetCustomDependencyGenerators() ac dependencyGeneratorMap.Merge(acldexmapping.GetDexDependencyGenerators()) dependencyGeneratorMap.Merge(aclbankmapping.GetBankDepedencyGenerator()) - wasmDependencyGenerators := aclwasmmapping.NewWasmDependencyGenerator(customDepGen.WasmKeeper) + wasmDependencyGenerators := aclwasmmapping.NewWasmDependencyGenerator() dependencyGeneratorMap.Merge(wasmDependencyGenerators.GetWasmDependencyGenerators()) return dependencyGeneratorMap diff --git a/aclmapping/wasm/mappings.go b/aclmapping/wasm/mappings.go index 4dc3d1d1e2..879b97c415 100644 --- a/aclmapping/wasm/mappings.go +++ b/aclmapping/wasm/mappings.go @@ -1,10 +1,8 @@ package aclwasmmapping import ( - "encoding/json" "fmt" - wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" @@ -19,13 +17,10 @@ var ( ) type WasmDependencyGenerator struct { - WasmKeeper wasmkeeper.Keeper } -func NewWasmDependencyGenerator(wasmKeeper wasmkeeper.Keeper) WasmDependencyGenerator { - return WasmDependencyGenerator{ - WasmKeeper: wasmKeeper, - } +func NewWasmDependencyGenerator() WasmDependencyGenerator { + return WasmDependencyGenerator{} } func (wasmDepGen WasmDependencyGenerator) GetWasmDependencyGenerators() aclkeeper.DependencyGeneratorMap { @@ -47,28 +42,7 @@ func (wasmDepGen WasmDependencyGenerator) WasmExecuteContractGenerator(keeper ac if err != nil { return []sdkacltypes.AccessOperation{}, err } - contractInfo := wasmDepGen.WasmKeeper.GetContractInfo(ctx, contractAddr) - codeID := contractInfo.CodeID - - jsonObj := make(map[string]interface{}) - jsonErr := json.Unmarshal(executeContractMsg.Msg, &jsonObj) - var wasmFunction string - if jsonErr != nil { - // try unmarshalling to string for execute function with no params - jsonErr2 := json.Unmarshal(executeContractMsg.Msg, &wasmFunction) - if jsonErr2 != nil { - return []sdkacltypes.AccessOperation{}, ErrInvalidWasmFunction - } - } else { - if len(jsonObj) != 1 { - return []sdkacltypes.AccessOperation{}, ErrInvalidWasmFunction - } - for fieldName := range jsonObj { - // this should only run once based on the check above - wasmFunction = fieldName - } - } - wasmDependencyMapping, err := keeper.GetWasmFunctionDependencyMapping(ctx, codeID, wasmFunction) + wasmDependencyMapping, err := keeper.GetWasmDependencyMapping(ctx, contractAddr) if err != nil { return []sdkacltypes.AccessOperation{}, err } diff --git a/app/app.go b/app/app.go index 8fdc2083a9..763668d63c 100644 --- a/app/app.go +++ b/app/app.go @@ -499,10 +499,33 @@ func New( app.BankKeeper.(bankkeeper.BaseKeeper).WithMintCoinsRestriction(tokenfactorytypes.NewTokenFactoryDenomMintCoinsRestriction()), app.DistrKeeper, ) + customDependencyGenerators := aclmapping.NewCustomDependencyGenerator() + aclOpts = append(aclOpts, aclkeeper.WithDependencyGeneratorMappings(customDependencyGenerators.GetCustomDependencyGenerators())) + app.AccessControlKeeper = aclkeeper.NewKeeper( + appCodec, + app.keys[acltypes.StoreKey], + app.GetSubspace(acltypes.ModuleName), + aclOpts..., + ) // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks supportedFeatures := "iterator,staking,stargate,sei" - wasmOpts = append(wasmbinding.RegisterCustomPlugins(&app.OracleKeeper, &app.DexKeeper, &app.EpochKeeper, &app.AccountKeeper, app.MsgServiceRouter()), wasmOpts...) + wasmOpts = append( + wasmbinding.RegisterCustomPlugins( + &app.OracleKeeper, + &app.DexKeeper, + &app.EpochKeeper, + &app.AccountKeeper, + app.MsgServiceRouter(), + app.IBCKeeper.ChannelKeeper, + scopedWasmKeeper, + app.BankKeeper, + appCodec, + app.TransferKeeper, + app.AccessControlKeeper, + ), + wasmOpts..., + ) app.WasmKeeper = wasm.NewKeeper( appCodec, keys[wasm.StoreKey], @@ -526,14 +549,6 @@ func New( dexModule := dexmodule.NewAppModule(appCodec, app.DexKeeper, app.AccountKeeper, app.BankKeeper, app.WasmKeeper, app.tracingInfo) epochModule := epochmodule.NewAppModule(appCodec, app.EpochKeeper, app.AccountKeeper, app.BankKeeper) - customDependencyGenerators := aclmapping.NewCustomDependencyGenerator(app.WasmKeeper) - aclOpts = append(aclOpts, aclkeeper.WithDependencyGeneratorMappings(customDependencyGenerators.GetCustomDependencyGenerators())) - app.AccessControlKeeper = aclkeeper.NewKeeper( - appCodec, - app.keys[acltypes.StoreKey], - app.GetSubspace(acltypes.ModuleName), - aclOpts..., - ) // register the proposal types govRouter := govtypes.NewRouter() govRouter.AddRoute(govtypes.RouterKey, govtypes.ProposalHandler). diff --git a/go.mod b/go.mod index 2a4dbda7a0..27fadccd34 100644 --- a/go.mod +++ b/go.mod @@ -131,7 +131,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.114 + github.com/cosmos/cosmos-sdk => ../sei-cosmos //github.com/sei-protocol/sei-cosmos v0.1.114 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.1.59 diff --git a/go.sum b/go.sum index 855691039a..4135647ad2 100644 --- a/go.sum +++ b/go.sum @@ -1097,8 +1097,6 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= -github.com/sei-protocol/sei-cosmos v0.1.114 h1:Cy5MnBdvql5VJw5pC104DsivQxPg0xkvVOuq6VwDiRk= -github.com/sei-protocol/sei-cosmos v0.1.114/go.mod h1:8ccWQxpBkWbpvBos/T4QO9K9gQxFs0duTqKRnagKo+0= github.com/sei-protocol/sei-tendermint v0.1.59 h1:POGL60PumMQHF4EzAHzvkGfDnodQJLHpl65LuiwSO/Y= github.com/sei-protocol/sei-tendermint v0.1.59/go.mod h1:Olwbjyagrpoxj5DAUhHxMTWDVEfQ3FYdpypaJ3+6Hs8= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= diff --git a/wasmbinding/encoder.go b/wasmbinding/encoder.go new file mode 100644 index 0000000000..b26971ebdf --- /dev/null +++ b/wasmbinding/encoder.go @@ -0,0 +1,43 @@ +package wasmbinding + +import ( + "encoding/json" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" + tokenfactorywasm "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm" +) + +type SeiWasmMessage struct { + PlaceOrders json.RawMessage `json:"place_orders,omitempty"` + CancelOrders json.RawMessage `json:"cancel_orders,omitempty"` + CreateDenom json.RawMessage `json:"create_denom,omitempty"` + MintTokens json.RawMessage `json:"mint_tokens,omitempty"` + BurnTokens json.RawMessage `json:"burn_tokens,omitempty"` + ChangeAdmin json.RawMessage `json:"change_admin,omitempty"` +} + +func CustomEncoder(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { + var parsedMessage SeiWasmMessage + if err := json.Unmarshal(msg, &parsedMessage); err != nil { + return []sdk.Msg{}, sdkerrors.Wrap(err, "Error parsing Sei Wasm Message") + } + switch { + case parsedMessage.PlaceOrders != nil: + return dexwasm.EncodeDexPlaceOrders(parsedMessage.PlaceOrders, sender) + case parsedMessage.CancelOrders != nil: + return dexwasm.EncodeDexCancelOrders(parsedMessage.CancelOrders, sender) + case parsedMessage.CreateDenom != nil: + return tokenfactorywasm.EncodeTokenFactoryCreateDenom(parsedMessage.CreateDenom, sender) + case parsedMessage.MintTokens != nil: + return tokenfactorywasm.EncodeTokenFactoryMint(parsedMessage.MintTokens, sender) + case parsedMessage.BurnTokens != nil: + return tokenfactorywasm.EncodeTokenFactoryBurn(parsedMessage.BurnTokens, sender) + case parsedMessage.ChangeAdmin != nil: + return tokenfactorywasm.EncodeTokenFactoryChangeAdmin(parsedMessage.ChangeAdmin, sender) + default: + return []sdk.Msg{}, wasmvmtypes.UnsupportedRequest{Kind: "Unknown Sei Wasm Message"} + } +} diff --git a/wasmbinding/message_plugin.go b/wasmbinding/message_plugin.go index 3c46b92d0f..7af5307a2b 100644 --- a/wasmbinding/message_plugin.go +++ b/wasmbinding/message_plugin.go @@ -1,128 +1,187 @@ package wasmbinding import ( - "encoding/json" + "fmt" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" wasmvmtypes "github.com/CosmWasm/wasmvm/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" sdk "github.com/cosmos/cosmos-sdk/types" - sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" - authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" - dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" - tokenfactorywasm "github.com/sei-protocol/sei-chain/x/tokenfactory/client/wasm" + sdkacltypes "github.com/cosmos/cosmos-sdk/types/accesscontrol" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" ) -// CustomMessageDecorator returns decorator for custom CosmWasm bindings messages -func CustomMessageDecorator( +var ErrUnexpectedWasmDependency = fmt.Errorf("unexpected wasm dependency detected") + +// forked from wasm +func CustomMessageHandler( router wasmkeeper.MessageRouter, - accountKeeper *authkeeper.AccountKeeper, -) func(wasmkeeper.Messenger) wasmkeeper.Messenger { - return func(old wasmkeeper.Messenger) wasmkeeper.Messenger { - return &CustomMessenger{ - router: router, - wrapped: old, - accountKeeper: accountKeeper, - } - } + channelKeeper wasmtypes.ChannelKeeper, + capabilityKeeper wasmtypes.CapabilityKeeper, + bankKeeper wasmtypes.Burner, + unpacker codectypes.AnyUnpacker, + portSource wasmtypes.ICS20TransferPortSource, + aclKeeper aclkeeper.Keeper, +) wasmkeeper.Messenger { + encoders := wasmkeeper.DefaultEncoders(unpacker, portSource) + // TODO: + // Instead of default encoders, we also want to merge in the custom encoder. + // Then we also fork the DispatchMsg for SDKMessage handler, and betweeen encoding and running, + // we make a check with context and sdk message dependencies to verify that the contract has access to the proper access ops to run the logic. + // if it doesnt we first set the acl keeper wasm dependency mapping enabled to false, and then fail the message. + // determine if the "false" stays (it should) + // end TODO: + encoders = encoders.Merge( + &wasmkeeper.MessageEncoders{ + Custom: CustomEncoder, + }) + return wasmkeeper.NewMessageHandlerChain( + NewSDKMessageDependencyDecorator(wasmkeeper.NewSDKMessageHandler(router, encoders), aclKeeper, encoders), + wasmkeeper.NewIBCRawPacketHandler(channelKeeper, capabilityKeeper), + wasmkeeper.NewBurnCoinMessageHandler(bankKeeper), + ) } -type CustomMessenger struct { - router wasmkeeper.MessageRouter - wrapped wasmkeeper.Messenger - accountKeeper *authkeeper.AccountKeeper +// SDKMessageHandler can handles messages that can be encoded into sdk.Message types and routed. +type SDKMessageDependencyDecorator struct { + wrapped wasmkeeper.Messenger + aclKeeper aclkeeper.Keeper + encoders wasmkeeper.MessageEncoders } -type SeiWasmMessage struct { - PlaceOrders json.RawMessage `json:"place_orders,omitempty"` - CancelOrders json.RawMessage `json:"cancel_orders,omitempty"` - CreateDenom json.RawMessage `json:"create_denom,omitempty"` - MintTokens json.RawMessage `json:"mint_tokens,omitempty"` - BurnTokens json.RawMessage `json:"burn_tokens,omitempty"` - ChangeAdmin json.RawMessage `json:"change_admin,omitempty"` +func NewSDKMessageDependencyDecorator(handler wasmkeeper.Messenger, aclKeeper aclkeeper.Keeper, encoders wasmkeeper.MessageEncoders) SDKMessageDependencyDecorator { + return SDKMessageDependencyDecorator{ + wrapped: handler, + aclKeeper: aclKeeper, + encoders: encoders, + } } -var _ wasmkeeper.Messenger = &CustomMessenger{} - -// DispatchMsg executes on the bindingMsgs -func (m *CustomMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) ([]sdk.Event, [][]byte, error) { - if msg.Custom != nil { - return m.DispatchCustomMsg(ctx, contractAddr, contractIBCPortID, msg) +func BuildWasmDependencyLookupMap(accessOps []sdkacltypes.AccessOperation) map[acltypes.ResourceAccess]map[string]struct{} { + lookupMap := make(map[acltypes.ResourceAccess]map[string]struct{}) + for _, accessOp := range accessOps { + resourceAccess := acltypes.ResourceAccess{ + ResourceType: accessOp.ResourceType, + AccessType: accessOp.AccessType, + } + if _, ok := lookupMap[resourceAccess]; !ok { + // we haven't added any identifiers for this resource type, so lets initialize the nested map (set) + lookupMap[resourceAccess] = make(map[string]struct{}) + } + lookupMap[resourceAccess][accessOp.IdentifierTemplate] = struct{}{} } - return m.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) + return lookupMap } -// DispatchCustomMsg function is forked from wasmd. sdk.Msg will be validated and routed to the corresponding module msg server in this function. -func (m *CustomMessenger) DispatchCustomMsg( - ctx sdk.Context, - contractAddr sdk.AccAddress, - contractIBCPortID string, - msg wasmvmtypes.CosmosMsg, -) (events []sdk.Event, data [][]byte, err error) { - var parsedMessage SeiWasmMessage - if err := json.Unmarshal(msg.Custom, &parsedMessage); err != nil { - return nil, nil, sdkerrors.Wrap(err, "Error parsing Sei Wasm Message") +func GenerateAllowedResourceAccess(resource sdkacltypes.ResourceType, access sdkacltypes.AccessType) []acltypes.ResourceAccess { + // by default, write, and unknown are ok + accesses := []acltypes.ResourceAccess{ + { + ResourceType: resource, + AccessType: sdkacltypes.AccessType_WRITE, + }, + { + ResourceType: resource, + AccessType: sdkacltypes.AccessType_UNKNOWN, + }, + } + if access == sdkacltypes.AccessType_READ { + accesses = append(accesses, acltypes.ResourceAccess{ + ResourceType: resource, + AccessType: access, + }) } + return accesses +} - var sdkMsgs []sdk.Msg - switch { - case parsedMessage.PlaceOrders != nil: - sdkMsgs, err = dexwasm.EncodeDexPlaceOrders(parsedMessage.PlaceOrders, contractAddr) - case parsedMessage.CancelOrders != nil: - sdkMsgs, err = dexwasm.EncodeDexCancelOrders(parsedMessage.CancelOrders, contractAddr) - case parsedMessage.CreateDenom != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryCreateDenom(parsedMessage.CreateDenom, contractAddr) - case parsedMessage.MintTokens != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryMint(parsedMessage.MintTokens, contractAddr) - case parsedMessage.BurnTokens != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryBurn(parsedMessage.BurnTokens, contractAddr) - case parsedMessage.ChangeAdmin != nil: - sdkMsgs, err = tokenfactorywasm.EncodeTokenFactoryChangeAdmin(parsedMessage.ChangeAdmin, contractAddr) - default: - sdkMsgs, err = []sdk.Msg{}, wasmvmtypes.UnsupportedRequest{Kind: "Unknown Sei Wasm Message"} +func (decorator SDKMessageDependencyDecorator) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { + sdkMsgs, err := decorator.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg) + if err != nil { + decorator.DisableWasmDependencyForContract(ctx, contractAddr) + return nil, nil, err + } + // get the dependencies for the contract to validate against + wasmDependency, err := decorator.aclKeeper.GetWasmDependencyMapping(ctx, contractAddr) + // If no mapping exists, or mapping is disabled, this message would behave as blocking for all resources + if err == aclkeeper.ErrWasmDependencyMappingNotFound { + // no mapping, we can just continue + return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } if err != nil { + // some other error, return error + decorator.DisableWasmDependencyForContract(ctx, contractAddr) return nil, nil, err } - - for _, sdkMsg := range sdkMsgs { - res, err := m.handleSdkMessage(ctx, contractAddr, sdkMsg) - if err != nil { - return nil, nil, err - } - // append data - data = append(data, res.Data) - // append events - sdkEvents := make([]sdk.Event, len(res.Events)) - for i := range res.Events { - sdkEvents[i] = sdk.Event(res.Events[i]) + if !wasmDependency.Enabled { + // if not enabled, just move on + // TODO: confirm that this is ok, is there ever a case where we should still verify dependencies for a disabled dependency? IDTS + return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) + } + // convert wasm dependency to a map of resource access and identifier we can look up in + lookupMap := BuildWasmDependencyLookupMap(wasmDependency.AccessOps) + // wasm dependency enabled, we need to validate the message dependencies + for _, msg := range sdkMsgs { + accessOps := decorator.aclKeeper.GetMessageDependencies(ctx, msg) + // go through each access op, and check if there is a completion signal for it OR a parent + for _, accessOp := range accessOps { + // first check for our specific resource access AND identifier template + currResourceAccesses := GenerateAllowedResourceAccess(accessOp.ResourceType, accessOp.AccessType) + validResourceAccessFound := false + for _, currResourceAccess := range currResourceAccesses { + if identifierMap, ok := lookupMap[currResourceAccess]; ok { + if _, ok := identifierMap[accessOp.IdentifierTemplate]; ok { + // we found a proper listed dependency, we can go to the next access op + validResourceAccessFound = true + break + } + } + } + if validResourceAccessFound { + continue + } + // what about + parentResources := accessOp.ResourceType.GetParentResources() + // for each of the parent resources, we need at least one to be defined in the wasmDependencies + for _, parentResource := range parentResources { + // make parent resource access with same access type + parentResourceAccesses := GenerateAllowedResourceAccess(parentResource, accessOp.AccessType) + // for each of the parent resources, we check to see if its in the lookup map (identifier doesnt matter bc parent) + for _, parentResourceAccess := range parentResourceAccesses { + if _, parentResourcePresent := lookupMap[parentResourceAccess]; parentResourcePresent { + // we can continue to the next access op + validResourceAccessFound = true + break + } + } + if validResourceAccessFound { + break + } + } + if !validResourceAccessFound { + // didnt find a parent resource for the message requirements, disable wasm parallelizatiomn and disable the mapping + decorator.DisableWasmDependencyForContract(ctx, contractAddr) + return nil, nil, ErrUnexpectedWasmDependency + } } - events = append(events, sdkEvents...) } - return events, data, nil + // we've gone through all of the messages + // and verified their dependencies with the declared dependencies in the wasm contract dependencies, we can process it now + return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } -// This function is forked from wasmd. sdk.Msg will be validated and routed to the corresponding module msg server in this function. -func (m *CustomMessenger) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) (*sdk.Result, error) { - if err := msg.ValidateBasic(); err != nil { - return nil, err +func (decorator SDKMessageDependencyDecorator) DisableWasmDependencyForContract(ctx sdk.Context, contractAddr sdk.AccAddress) { + wasmDependency, err := decorator.aclKeeper.GetWasmDependencyMapping(ctx, contractAddr) + if err == aclkeeper.ErrWasmDependencyMappingNotFound { + return } - // make sure this account can send it - for _, acct := range msg.GetSigners() { - if !acct.Equals(contractAddr) { - return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "contract doesn't have permission") - } + if err != nil { + ctx.Logger().Error("Error disabling contract wasm dependency") } - - // find the handler and execute it - if handler := m.router.Handler(msg); handler != nil { - // ADR 031 request type routing - msgResult, err := handler(ctx, msg) - return msgResult, err + wasmDependency.Enabled = false + err = decorator.aclKeeper.SetWasmDependencyMapping(ctx, contractAddr, wasmDependency) + if err != nil { + ctx.Logger().Error("Error disabling contract wasm dependency") } - // legacy sdk.Msg routing - // Assuming that the app developer has migrated all their Msgs to - // proto messages and has registered all `Msg services`, then this - // path should never be called, because all those Msgs should be - // registered within the `msgServiceRouter` already. - return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "can't route message %+v", msg) } diff --git a/wasmbinding/test/message_handler_test.go b/wasmbinding/test/message_handler_test.go new file mode 100644 index 0000000000..dad70a932f --- /dev/null +++ b/wasmbinding/test/message_handler_test.go @@ -0,0 +1,161 @@ +package wasmbinding + +import ( + "encoding/json" + "testing" + + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" + banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" + "github.com/sei-protocol/sei-chain/app" + "github.com/sei-protocol/sei-chain/wasmbinding" + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/proto/tendermint/types" +) + +type MockMessenger struct{} + +func (m MockMessenger) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { + return []sdk.Event{{ + Type: "test", + Attributes: []abci.EventAttribute{}, + }}, nil, nil +} + +func TestMessageHandlerDependencyDecorator(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + defaultEncoders := wasmkeeper.DefaultEncoders(app.AppCodec(), app.TransferKeeper) + dependencyDecorator := wasmbinding.NewSDKMessageDependencyDecorator(MockMessenger{}, app.AccessControlKeeper, defaultEncoders) + testContext := app.NewContext(false, types.Header{}) + + // setup bank send message with aclkeeper + app.AccessControlKeeper.SetResourceDependencyMapping(testContext, accesscontrol.MessageDependencyMapping{ + MessageKey: string(acltypes.GenerateMessageKey(&banktypes.MsgSend{})), + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + DynamicEnabled: false, + }) + + // setup the wasm contract's dependency mapping + app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_ANY, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + + events, _, _ := dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: "sdfasdf", + Amount: []wasmvmtypes.Coin{ + { + Denom: "usei", + Amount: "12345", + }, + }, + }, + }, + }) + // we should have received the test event + require.Equal(t, []sdk.Event{ + { + Type: "test", + Attributes: []abci.EventAttribute{}, + }, + }, events) + + app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "otherIdentifier", + }, + acltypes.CommitAccessOp(), + }, + }) + + _, _, err = dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: "sdfasdf", + Amount: []wasmvmtypes.Coin{ + { + Denom: "usei", + Amount: "12345", + }, + }, + }, + }, + }) + // we expect an error now + require.Error(t, wasmbinding.ErrUnexpectedWasmDependency, err) + // we also expect the wasm dependency to be disabled + deps, err := app.AccessControlKeeper.GetWasmDependencyMapping(testContext, contractAddr) + require.NoError(t, err) + require.False(t, deps.Enabled) + + // since we've disabled the incorrect dependency mapping, running it again should no longer error + events, _, err = dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ + Bank: &wasmvmtypes.BankMsg{ + Send: &wasmvmtypes.SendMsg{ + ToAddress: "sdfasdf", + Amount: []wasmvmtypes.Coin{ + { + Denom: "usei", + Amount: "12345", + }, + }, + }, + }, + }) + require.NoError(t, err) + require.Equal(t, []sdk.Event{ + { + Type: "test", + Attributes: []abci.EventAttribute{}, + }, + }, events) + // reenable wasm mapping that's correct + app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + // lets try with a message that wont decode properly + _, _, err = dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ + Custom: json.RawMessage{}, + }) + require.Error(t, wasmtypes.ErrUnknownMsg, err) + // double check that wasm dependency mapping was disabled + deps, err = app.AccessControlKeeper.GetWasmDependencyMapping(testContext, contractAddr) + require.NoError(t, err) + require.False(t, deps.Enabled) + +} diff --git a/wasmbinding/wasm.go b/wasmbinding/wasm.go index 3ddba5ac8e..e4c024bbc4 100644 --- a/wasmbinding/wasm.go +++ b/wasmbinding/wasm.go @@ -3,6 +3,9 @@ package wasmbinding import ( "github.com/CosmWasm/wasmd/x/wasm" wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" dexwasm "github.com/sei-protocol/sei-chain/x/dex/client/wasm" dexkeeper "github.com/sei-protocol/sei-chain/x/dex/keeper" @@ -18,6 +21,12 @@ func RegisterCustomPlugins( epoch *epochkeeper.Keeper, accountKeeper *authkeeper.AccountKeeper, router wasmkeeper.MessageRouter, + channelKeeper wasmtypes.ChannelKeeper, + capabilityKeeper wasmtypes.CapabilityKeeper, + bankKeeper wasmtypes.Burner, + unpacker codectypes.AnyUnpacker, + portSource wasmtypes.ICS20TransferPortSource, + aclKeeper aclkeeper.Keeper, ) []wasmkeeper.Option { dexHandler := dexwasm.NewDexWasmQueryHandler(dex) oracleHandler := oraclewasm.NewOracleWasmQueryHandler(oracle) @@ -27,12 +36,12 @@ func RegisterCustomPlugins( queryPluginOpt := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ Custom: CustomQuerier(wasmQueryPlugin), }) - messengerDecoratorOpt := wasmkeeper.WithMessageHandlerDecorator( - CustomMessageDecorator(router, accountKeeper), + messengerHandlerOpt := wasmkeeper.WithMessageHandler( + CustomMessageHandler(router, channelKeeper, capabilityKeeper, bankKeeper, unpacker, portSource, aclKeeper), ) return []wasm.Option{ queryPluginOpt, - messengerDecoratorOpt, + messengerHandlerOpt, } } From fae4aff64e3f4c356f60d3f91309c76ffc2c89c2 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Wed, 12 Oct 2022 22:09:11 -0700 Subject: [PATCH 3/8] remove todo --- wasmbinding/message_plugin.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/wasmbinding/message_plugin.go b/wasmbinding/message_plugin.go index 7af5307a2b..d9b1574f07 100644 --- a/wasmbinding/message_plugin.go +++ b/wasmbinding/message_plugin.go @@ -26,13 +26,6 @@ func CustomMessageHandler( aclKeeper aclkeeper.Keeper, ) wasmkeeper.Messenger { encoders := wasmkeeper.DefaultEncoders(unpacker, portSource) - // TODO: - // Instead of default encoders, we also want to merge in the custom encoder. - // Then we also fork the DispatchMsg for SDKMessage handler, and betweeen encoding and running, - // we make a check with context and sdk message dependencies to verify that the contract has access to the proper access ops to run the logic. - // if it doesnt we first set the acl keeper wasm dependency mapping enabled to false, and then fail the message. - // determine if the "false" stays (it should) - // end TODO: encoders = encoders.Merge( &wasmkeeper.MessageEncoders{ Custom: CustomEncoder, From b85353f0f385129b6911de30d1a811725dc084e7 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Fri, 14 Oct 2022 12:54:46 -0700 Subject: [PATCH 4/8] [wasmbinding] Add query dep validation and remove dependency disabling --- wasmbinding/message_plugin.go | 83 +++++-------- wasmbinding/query_plugin.go | 142 +++++++++++++++++++++++ wasmbinding/test/message_handler_test.go | 30 ----- wasmbinding/wasm.go | 6 +- 4 files changed, 174 insertions(+), 87 deletions(-) diff --git a/wasmbinding/message_plugin.go b/wasmbinding/message_plugin.go index d9b1574f07..e3776be2a6 100644 --- a/wasmbinding/message_plugin.go +++ b/wasmbinding/message_plugin.go @@ -89,10 +89,37 @@ func GenerateAllowedResourceAccess(resource sdkacltypes.ResourceType, access sdk return accesses } +func AreDependenciesFulfilled(lookupMap map[acltypes.ResourceAccess]map[string]struct{}, accessOp sdkacltypes.AccessOperation) bool { + currResourceAccesses := GenerateAllowedResourceAccess(accessOp.ResourceType, accessOp.AccessType) + for _, currResourceAccess := range currResourceAccesses { + if identifierMap, ok := lookupMap[currResourceAccess]; ok { + if _, ok := identifierMap[accessOp.IdentifierTemplate]; ok { + // we found a proper listed dependency, we can go to the next access op + return true + } + } + } + + // what about parent resources + parentResources := accessOp.ResourceType.GetParentResources() + // for each of the parent resources, we need at least one to be defined in the wasmDependencies + for _, parentResource := range parentResources { + // make parent resource access with same access type + parentResourceAccesses := GenerateAllowedResourceAccess(parentResource, accessOp.AccessType) + // for each of the parent resources, we check to see if its in the lookup map (identifier doesnt matter bc parent) + for _, parentResourceAccess := range parentResourceAccesses { + if _, parentResourcePresent := lookupMap[parentResourceAccess]; parentResourcePresent { + // we can continue to the next access op + return true + } + } + } + return false +} + func (decorator SDKMessageDependencyDecorator) DispatchMsg(ctx sdk.Context, contractAddr sdk.AccAddress, contractIBCPortID string, msg wasmvmtypes.CosmosMsg) (events []sdk.Event, data [][]byte, err error) { sdkMsgs, err := decorator.encoders.Encode(ctx, contractAddr, contractIBCPortID, msg) if err != nil { - decorator.DisableWasmDependencyForContract(ctx, contractAddr) return nil, nil, err } // get the dependencies for the contract to validate against @@ -103,8 +130,6 @@ func (decorator SDKMessageDependencyDecorator) DispatchMsg(ctx sdk.Context, cont return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } if err != nil { - // some other error, return error - decorator.DisableWasmDependencyForContract(ctx, contractAddr) return nil, nil, err } if !wasmDependency.Enabled { @@ -120,41 +145,8 @@ func (decorator SDKMessageDependencyDecorator) DispatchMsg(ctx sdk.Context, cont // go through each access op, and check if there is a completion signal for it OR a parent for _, accessOp := range accessOps { // first check for our specific resource access AND identifier template - currResourceAccesses := GenerateAllowedResourceAccess(accessOp.ResourceType, accessOp.AccessType) - validResourceAccessFound := false - for _, currResourceAccess := range currResourceAccesses { - if identifierMap, ok := lookupMap[currResourceAccess]; ok { - if _, ok := identifierMap[accessOp.IdentifierTemplate]; ok { - // we found a proper listed dependency, we can go to the next access op - validResourceAccessFound = true - break - } - } - } - if validResourceAccessFound { - continue - } - // what about - parentResources := accessOp.ResourceType.GetParentResources() - // for each of the parent resources, we need at least one to be defined in the wasmDependencies - for _, parentResource := range parentResources { - // make parent resource access with same access type - parentResourceAccesses := GenerateAllowedResourceAccess(parentResource, accessOp.AccessType) - // for each of the parent resources, we check to see if its in the lookup map (identifier doesnt matter bc parent) - for _, parentResourceAccess := range parentResourceAccesses { - if _, parentResourcePresent := lookupMap[parentResourceAccess]; parentResourcePresent { - // we can continue to the next access op - validResourceAccessFound = true - break - } - } - if validResourceAccessFound { - break - } - } - if !validResourceAccessFound { - // didnt find a parent resource for the message requirements, disable wasm parallelizatiomn and disable the mapping - decorator.DisableWasmDependencyForContract(ctx, contractAddr) + depsFulfilled := AreDependenciesFulfilled(lookupMap, accessOp) + if !depsFulfilled { return nil, nil, ErrUnexpectedWasmDependency } } @@ -163,18 +155,3 @@ func (decorator SDKMessageDependencyDecorator) DispatchMsg(ctx sdk.Context, cont // and verified their dependencies with the declared dependencies in the wasm contract dependencies, we can process it now return decorator.wrapped.DispatchMsg(ctx, contractAddr, contractIBCPortID, msg) } - -func (decorator SDKMessageDependencyDecorator) DisableWasmDependencyForContract(ctx sdk.Context, contractAddr sdk.AccAddress) { - wasmDependency, err := decorator.aclKeeper.GetWasmDependencyMapping(ctx, contractAddr) - if err == aclkeeper.ErrWasmDependencyMappingNotFound { - return - } - if err != nil { - ctx.Logger().Error("Error disabling contract wasm dependency") - } - wasmDependency.Enabled = false - err = decorator.aclKeeper.SetWasmDependencyMapping(ctx, contractAddr, wasmDependency) - if err != nil { - ctx.Logger().Error("Error disabling contract wasm dependency") - } -} diff --git a/wasmbinding/query_plugin.go b/wasmbinding/query_plugin.go index a4e9a6bff9..dab85e8ce2 100644 --- a/wasmbinding/query_plugin.go +++ b/wasmbinding/query_plugin.go @@ -3,9 +3,12 @@ package wasmbinding import ( "encoding/json" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" wasmvmtypes "github.com/CosmWasm/wasmvm/types" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/accesscontrol" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + aclkeeper "github.com/cosmos/cosmos-sdk/x/accesscontrol/keeper" ) const ( @@ -39,3 +42,142 @@ func CustomQuerier(qp *QueryPlugin) func(ctx sdk.Context, request json.RawMessag } } } + +type CustomQueryHandler struct { + QueryPlugins wasmkeeper.QueryPlugins + aclKeeper aclkeeper.Keeper +} + +func (queryHandler CustomQueryHandler) HandleQuery(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { + wasmDependency, err := queryHandler.aclKeeper.GetWasmDependencyMapping(ctx, caller) + // If no mapping exists, or mapping is disabled, this message would behave as blocking for all resources + needToCheckDependencies := true + if err == aclkeeper.ErrWasmDependencyMappingNotFound { + // no mapping, we can just continue + needToCheckDependencies = false + } + if err != nil { + return nil, err + } + if !wasmDependency.Enabled { + needToCheckDependencies = false + } + lookupMap := BuildWasmDependencyLookupMap(wasmDependency.AccessOps) + if request.Bank != nil { + // check for BANK resource type + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_KV_BANK, + AccessType: accesscontrol.AccessType_READ, + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + return nil, ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Bank(ctx, request.Bank) + + } + if request.Custom != nil { + // TODO: specially break down the custom + var contractQuery SeiQueryWrapper + if err := json.Unmarshal(request.Custom, &contractQuery); err != nil { + return nil, sdkerrors.Wrap(err, "Error parsing request data") + } + resourceType := accesscontrol.ResourceType_ANY + switch contractQuery.Route { + case OracleRoute: + resourceType = accesscontrol.ResourceType_KV_ORACLE + case DexRoute: + resourceType = accesscontrol.ResourceType_KV_DEX + case EpochRoute: + resourceType = accesscontrol.ResourceType_KV_EPOCH + } + accessOp := accesscontrol.AccessOperation{ + ResourceType: resourceType, + AccessType: accesscontrol.AccessType_READ, + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + return nil, ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Custom(ctx, request.Custom) + } + if request.IBC != nil { + // check for ANY resource type + // TODO: do we need a special resource type for IBC? + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_ANY, + AccessType: accesscontrol.AccessType_READ, + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + return nil, ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.IBC(ctx, caller, request.IBC) + } + if request.Staking != nil { + // check for STAKING resource type + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_KV_STAKING, + AccessType: accesscontrol.AccessType_READ, + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + return nil, ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Staking(ctx, request.Staking) + } + if request.Stargate != nil { + // check for ANY resource type + // TODO: determine what Stargate dependency granularity looks like + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_ANY, + AccessType: accesscontrol.AccessType_READ, + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + return nil, ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Stargate(ctx, request.Stargate) + } + if request.Wasm != nil { + // check for WASM resource type + accessOp := accesscontrol.AccessOperation{ + ResourceType: accesscontrol.ResourceType_KV_WASM, + AccessType: accesscontrol.AccessType_READ, + } + if needToCheckDependencies { + if !AreDependenciesFulfilled(lookupMap, accessOp) { + return nil, ErrUnexpectedWasmDependency + } + } + return queryHandler.QueryPlugins.Wasm(ctx, request.Wasm) + } + return nil, wasmvmtypes.Unknown{} +} + +func NewCustomQueryHandler(queryPlugins wasmkeeper.QueryPlugins, aclKeeper aclkeeper.Keeper) wasmkeeper.WasmVMQueryHandler { + return CustomQueryHandler{ + QueryPlugins: queryPlugins, + aclKeeper: aclKeeper, + } +} + +func CustomQueryHandlerDecorator(aclKeeper aclkeeper.Keeper, customQueryPlugin QueryPlugin) func(wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { + // validate stuff, otherwise use default handler + return func(old wasmkeeper.WasmVMQueryHandler) wasmkeeper.WasmVMQueryHandler { + queryPlugins, ok := old.(wasmkeeper.QueryPlugins) + if !ok { + panic("Invalid query plugins") + } + + queryPlugins = queryPlugins.Merge(&wasmkeeper.QueryPlugins{ + Custom: CustomQuerier(&customQueryPlugin), + }) + return NewCustomQueryHandler(queryPlugins, aclKeeper) + } +} diff --git a/wasmbinding/test/message_handler_test.go b/wasmbinding/test/message_handler_test.go index dad70a932f..20dd685c6e 100644 --- a/wasmbinding/test/message_handler_test.go +++ b/wasmbinding/test/message_handler_test.go @@ -110,32 +110,7 @@ func TestMessageHandlerDependencyDecorator(t *testing.T) { }) // we expect an error now require.Error(t, wasmbinding.ErrUnexpectedWasmDependency, err) - // we also expect the wasm dependency to be disabled - deps, err := app.AccessControlKeeper.GetWasmDependencyMapping(testContext, contractAddr) - require.NoError(t, err) - require.False(t, deps.Enabled) - // since we've disabled the incorrect dependency mapping, running it again should no longer error - events, _, err = dependencyDecorator.DispatchMsg(testContext, contractAddr, "test", wasmvmtypes.CosmosMsg{ - Bank: &wasmvmtypes.BankMsg{ - Send: &wasmvmtypes.SendMsg{ - ToAddress: "sdfasdf", - Amount: []wasmvmtypes.Coin{ - { - Denom: "usei", - Amount: "12345", - }, - }, - }, - }, - }) - require.NoError(t, err) - require.Equal(t, []sdk.Event{ - { - Type: "test", - Attributes: []abci.EventAttribute{}, - }, - }, events) // reenable wasm mapping that's correct app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ Enabled: true, @@ -153,9 +128,4 @@ func TestMessageHandlerDependencyDecorator(t *testing.T) { Custom: json.RawMessage{}, }) require.Error(t, wasmtypes.ErrUnknownMsg, err) - // double check that wasm dependency mapping was disabled - deps, err = app.AccessControlKeeper.GetWasmDependencyMapping(testContext, contractAddr) - require.NoError(t, err) - require.False(t, deps.Enabled) - } diff --git a/wasmbinding/wasm.go b/wasmbinding/wasm.go index e4c024bbc4..2dee25d948 100644 --- a/wasmbinding/wasm.go +++ b/wasmbinding/wasm.go @@ -33,15 +33,13 @@ func RegisterCustomPlugins( epochHandler := epochwasm.NewEpochWasmQueryHandler(epoch) wasmQueryPlugin := NewQueryPlugin(oracleHandler, dexHandler, epochHandler) - queryPluginOpt := wasmkeeper.WithQueryPlugins(&wasmkeeper.QueryPlugins{ - Custom: CustomQuerier(wasmQueryPlugin), - }) + queryOpt := wasmkeeper.WithQueryHandlerDecorator(CustomQueryHandlerDecorator(aclKeeper, *wasmQueryPlugin)) messengerHandlerOpt := wasmkeeper.WithMessageHandler( CustomMessageHandler(router, channelKeeper, capabilityKeeper, bankKeeper, unpacker, portSource, aclKeeper), ) return []wasm.Option{ - queryPluginOpt, + queryOpt, messengerHandlerOpt, } } From 7ba439eb535589139cbcdee24800ee43dd65d684 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Fri, 14 Oct 2022 17:50:50 -0700 Subject: [PATCH 5/8] added query validation and tests --- wasmbinding/query_plugin.go | 30 ++- wasmbinding/test/query_test.go | 393 +++++++++++++++++++++++++++++++++ 2 files changed, 411 insertions(+), 12 deletions(-) diff --git a/wasmbinding/query_plugin.go b/wasmbinding/query_plugin.go index dab85e8ce2..3c8c6eefa4 100644 --- a/wasmbinding/query_plugin.go +++ b/wasmbinding/query_plugin.go @@ -66,8 +66,9 @@ func (queryHandler CustomQueryHandler) HandleQuery(ctx sdk.Context, caller sdk.A if request.Bank != nil { // check for BANK resource type accessOp := accesscontrol.AccessOperation{ - ResourceType: accesscontrol.ResourceType_KV_BANK, - AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_BANK, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", } if needToCheckDependencies { if !AreDependenciesFulfilled(lookupMap, accessOp) { @@ -93,8 +94,9 @@ func (queryHandler CustomQueryHandler) HandleQuery(ctx sdk.Context, caller sdk.A resourceType = accesscontrol.ResourceType_KV_EPOCH } accessOp := accesscontrol.AccessOperation{ - ResourceType: resourceType, - AccessType: accesscontrol.AccessType_READ, + ResourceType: resourceType, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", } if needToCheckDependencies { if !AreDependenciesFulfilled(lookupMap, accessOp) { @@ -107,8 +109,9 @@ func (queryHandler CustomQueryHandler) HandleQuery(ctx sdk.Context, caller sdk.A // check for ANY resource type // TODO: do we need a special resource type for IBC? accessOp := accesscontrol.AccessOperation{ - ResourceType: accesscontrol.ResourceType_ANY, - AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_ANY, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", } if needToCheckDependencies { if !AreDependenciesFulfilled(lookupMap, accessOp) { @@ -120,8 +123,9 @@ func (queryHandler CustomQueryHandler) HandleQuery(ctx sdk.Context, caller sdk.A if request.Staking != nil { // check for STAKING resource type accessOp := accesscontrol.AccessOperation{ - ResourceType: accesscontrol.ResourceType_KV_STAKING, - AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_STAKING, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", } if needToCheckDependencies { if !AreDependenciesFulfilled(lookupMap, accessOp) { @@ -134,8 +138,9 @@ func (queryHandler CustomQueryHandler) HandleQuery(ctx sdk.Context, caller sdk.A // check for ANY resource type // TODO: determine what Stargate dependency granularity looks like accessOp := accesscontrol.AccessOperation{ - ResourceType: accesscontrol.ResourceType_ANY, - AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_ANY, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", } if needToCheckDependencies { if !AreDependenciesFulfilled(lookupMap, accessOp) { @@ -147,8 +152,9 @@ func (queryHandler CustomQueryHandler) HandleQuery(ctx sdk.Context, caller sdk.A if request.Wasm != nil { // check for WASM resource type accessOp := accesscontrol.AccessOperation{ - ResourceType: accesscontrol.ResourceType_KV_WASM, - AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_WASM, + AccessType: accesscontrol.AccessType_READ, + IdentifierTemplate: "*", } if needToCheckDependencies { if !AreDependenciesFulfilled(lookupMap, accessOp) { diff --git a/wasmbinding/test/query_test.go b/wasmbinding/test/query_test.go index 7d240fe0f2..2d6891f030 100644 --- a/wasmbinding/test/query_test.go +++ b/wasmbinding/test/query_test.go @@ -6,8 +6,12 @@ import ( "testing" "time" + wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/accesscontrol" + acltypes "github.com/cosmos/cosmos-sdk/x/accesscontrol/types" "github.com/sei-protocol/sei-chain/app" "github.com/sei-protocol/sei-chain/wasmbinding" dexcache "github.com/sei-protocol/sei-chain/x/dex/cache" @@ -23,6 +27,7 @@ import ( oracletypes "github.com/sei-protocol/sei-chain/x/oracle/types" oracleutils "github.com/sei-protocol/sei-chain/x/oracle/utils" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/proto/tendermint/types" ) func SetupWasmbindingTest(t *testing.T) (*app.TestWrapper, func(ctx sdk.Context, request json.RawMessage) ([]byte, error)) { @@ -231,3 +236,391 @@ func TestWasmGetEpoch(t *testing.T) { require.Equal(t, time.Unix(12345, 0).UTC(), epoch.CurrentEpochStartTime) require.Equal(t, int64(40), epoch.CurrentEpochHeight) } + +func MockQueryPlugins() wasmkeeper.QueryPlugins { + return wasmkeeper.QueryPlugins{ + Bank: func(ctx sdk.Context, request *wasmvmtypes.BankQuery) ([]byte, error) { return []byte{}, nil }, + IBC: func(ctx sdk.Context, caller sdk.AccAddress, request *wasmvmtypes.IBCQuery) ([]byte, error) { + return []byte{}, nil + }, + Custom: func(ctx sdk.Context, request json.RawMessage) ([]byte, error) { + println("test") + return []byte{}, nil + }, + Stargate: func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { return []byte{}, nil }, + Staking: func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) { return []byte{}, nil }, + Wasm: func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error) { return []byte{}, nil }, + } +} + +func TestQueryHandlerDependencyDecoratorBank(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_BANK, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Bank: &wasmvmtypes.BankQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Bank: &wasmvmtypes.BankQuery{}, + }) + require.Error(t, wasmbinding.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorIBC(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_ANY, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + IBC: &wasmvmtypes.IBCQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + IBC: &wasmvmtypes.IBCQuery{}, + }) + require.Error(t, wasmbinding.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorStaking(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_STAKING, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Staking: &wasmvmtypes.StakingQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Staking: &wasmvmtypes.StakingQuery{}, + }) + require.Error(t, wasmbinding.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorStargate(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_ANY, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Stargate: &wasmvmtypes.StargateQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Stargate: &wasmvmtypes.StargateQuery{}, + }) + require.Error(t, wasmbinding.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorWasm(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_WASM, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Wasm: &wasmvmtypes.WasmQuery{}, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_WRITE, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Wasm: &wasmvmtypes.WasmQuery{}, + }) + require.Error(t, wasmbinding.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorDex(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_DEX, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.DexRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_ORACLE, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, wasmbinding.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorOracle(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_ORACLE, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.OracleRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_BANK, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, wasmbinding.ErrUnexpectedWasmDependency, err) +} + +func TestQueryHandlerDependencyDecoratorEpoch(t *testing.T) { + app := app.Setup(false) + contractAddr, err := sdk.AccAddressFromBech32("sei1y3pxq5dp900czh0mkudhjdqjq5m8cpmmps8yjw") + require.NoError(t, err) + queryDecorator := wasmbinding.NewCustomQueryHandler(MockQueryPlugins(), app.AccessControlKeeper) + testContext := app.NewContext(false, types.Header{}) + + // setup the wasm contract's dependency mapping + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_EPOCH, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + customQuery, err := json.Marshal(wasmbinding.SeiQueryWrapper{ + Route: wasmbinding.EpochRoute, + }) + require.NoError(t, err) + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.NoError(t, err) + + err = app.AccessControlKeeper.SetWasmDependencyMapping(testContext, contractAddr, accesscontrol.WasmDependencyMapping{ + Enabled: true, + AccessOps: []accesscontrol.AccessOperation{ + { + AccessType: accesscontrol.AccessType_READ, + ResourceType: accesscontrol.ResourceType_KV_BANK, + IdentifierTemplate: "*", + }, + acltypes.CommitAccessOp(), + }, + }) + require.NoError(t, err) + + _, err = queryDecorator.HandleQuery(testContext, contractAddr, wasmvmtypes.QueryRequest{ + Custom: customQuery, + }) + require.Error(t, wasmbinding.ErrUnexpectedWasmDependency, err) +} From d41a859fdddaac6c2b40e436756bdd702954fed3 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 17 Oct 2022 11:21:46 -0700 Subject: [PATCH 6/8] go mod tidy --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index 27fadccd34..b4daa07ce1 100644 --- a/go.mod +++ b/go.mod @@ -131,7 +131,7 @@ require ( ) replace ( - github.com/cosmos/cosmos-sdk => ../sei-cosmos //github.com/sei-protocol/sei-cosmos v0.1.114 + github.com/cosmos/cosmos-sdk => github.com/sei-protocol/sei-cosmos v0.1.153 github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/keybase/go-keychain => github.com/99designs/go-keychain v0.0.0-20191008050251-8e49817e8af4 github.com/tendermint/tendermint => github.com/sei-protocol/sei-tendermint v0.1.59 diff --git a/go.sum b/go.sum index 4135647ad2..48eb71b118 100644 --- a/go.sum +++ b/go.sum @@ -1097,6 +1097,8 @@ github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= github.com/securego/gosec/v2 v2.11.0/go.mod h1:SX8bptShuG8reGC0XS09+a4H2BoWSJi+fscA+Pulbpo= github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY= +github.com/sei-protocol/sei-cosmos v0.1.153 h1:6jDUjEicctFtcwZ+NjiNSGe1owFRlLGcam5bR+PFT64= +github.com/sei-protocol/sei-cosmos v0.1.153/go.mod h1:8ccWQxpBkWbpvBos/T4QO9K9gQxFs0duTqKRnagKo+0= github.com/sei-protocol/sei-tendermint v0.1.59 h1:POGL60PumMQHF4EzAHzvkGfDnodQJLHpl65LuiwSO/Y= github.com/sei-protocol/sei-tendermint v0.1.59/go.mod h1:Olwbjyagrpoxj5DAUhHxMTWDVEfQ3FYdpypaJ3+6Hs8= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= From 772f7c2a5a0d1c4c98621a17c308103ef8025861 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 17 Oct 2022 12:13:59 -0700 Subject: [PATCH 7/8] lint --- aclmapping/wasm/mappings.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aclmapping/wasm/mappings.go b/aclmapping/wasm/mappings.go index 879b97c415..17e8acaeca 100644 --- a/aclmapping/wasm/mappings.go +++ b/aclmapping/wasm/mappings.go @@ -16,8 +16,7 @@ var ( ErrWasmFunctionDependenciesDisabled = fmt.Errorf("wasm function dependency mapping disabled") ) -type WasmDependencyGenerator struct { -} +type WasmDependencyGenerator struct{} func NewWasmDependencyGenerator() WasmDependencyGenerator { return WasmDependencyGenerator{} From c0b563109242f3222634198fb28dafb42e0009b1 Mon Sep 17 00:00:00 2001 From: Uday Patil Date: Mon, 17 Oct 2022 12:16:22 -0700 Subject: [PATCH 8/8] lint --- aclmapping/dependency_generator.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/aclmapping/dependency_generator.go b/aclmapping/dependency_generator.go index 3dc77d374f..beec9783a8 100644 --- a/aclmapping/dependency_generator.go +++ b/aclmapping/dependency_generator.go @@ -7,8 +7,7 @@ import ( aclwasmmapping "github.com/sei-protocol/sei-chain/aclmapping/wasm" ) -type CustomDependencyGenerator struct { -} +type CustomDependencyGenerator struct{} func NewCustomDependencyGenerator() CustomDependencyGenerator { return CustomDependencyGenerator{}