From 7016a94ebdaf49d84cc448693fba0916b9d2dbfa Mon Sep 17 00:00:00 2001 From: Abdullah Eryuzlu <24809834+aeryz@users.noreply.github.com> Date: Fri, 1 Dec 2023 09:25:05 +0000 Subject: [PATCH] feat: add custom queries to wasm module (#5261) * feat: add custom queries to wasm module Signed-off-by: aeryz * linter stuff as per usual. * review comments Signed-off-by: aeryz * lint Signed-off-by: aeryz * Use wasmvm.Querier. * Lint this bad boy * Small test nits. * add default querier and update documentation --------- Signed-off-by: aeryz Co-authored-by: DimitrisJim Co-authored-by: Carlos Rodriguez --- .../04-wasm/03-integration.md | 13 ++- .../03-light-clients/04-wasm/05-governance.md | 1 + .../08-wasm/internal/ibcwasm/querier.go | 17 +++ .../08-wasm/internal/ibcwasm/wasm.go | 19 +++ .../light-clients/08-wasm/keeper/keeper.go | 5 +- .../08-wasm/keeper/keeper_test.go | 5 + .../08-wasm/testing/simapp/app.go | 4 +- .../08-wasm/types/querier_test.go | 109 ++++++++++++++++++ modules/light-clients/08-wasm/types/vm.go | 8 +- 9 files changed, 173 insertions(+), 8 deletions(-) create mode 100644 modules/light-clients/08-wasm/internal/ibcwasm/querier.go create mode 100644 modules/light-clients/08-wasm/types/querier_test.go diff --git a/docs/docs/03-light-clients/04-wasm/03-integration.md b/docs/docs/03-light-clients/04-wasm/03-integration.md index 7e2d63eb474..6eee6ac28b7 100644 --- a/docs/docs/03-light-clients/04-wasm/03-integration.md +++ b/docs/docs/03-light-clients/04-wasm/03-integration.md @@ -63,6 +63,10 @@ func NewSimApp( // This is the recommended approach when the chain // also uses `x/wasm`, and then the Wasm VM instance // can be shared. + // This sample code uses also an implementation of the + // wasmvm.Querier interface (querier). If nil is passed + // instead, then a default querier will be used that + // returns an error for all query types. // See the section below for more information. app.WasmClientKeeper = wasmkeeper.NewKeeperWithVM( appCodec, @@ -70,6 +74,7 @@ func NewSimApp( app.IBCKeeper.ClientKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmVM, + querier, ) app.ModuleManager = module.NewManager( // SDK app modules @@ -125,7 +130,7 @@ func NewSimApp( ## Keeper instantiation -When it comes to instantiating `08-wasm`'s keeper there are two recommended ways of doing it. Choosing one or the other will depend on whether the chain already integrates [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) or not. +When it comes to instantiating `08-wasm`'s keeper there are two recommended ways of doing it. Choosing one or the other will depend on whether the chain already integrates [`x/wasm`](https://github.com/CosmWasm/wasmd/tree/main/x/wasm) or not. Both available constructor functions accept a querier parameter that should implement the [`Querier` interface of `wasmvm`](https://github.com/CosmWasm/wasmvm/blob/v1.5.0/types/queries.go#L37). If `nil` is provided, then a default querier implementation is used that returns error for any query type. ### If `x/wasm` is present @@ -194,12 +199,17 @@ app.WasmKeeper = wasmkeeper.NewKeeper( wasmOpts..., ) +// This sample code uses also an implementation of the +// wasmvm.Querier interface (querier). If nil is passed +// instead, then a default querier will be used that +// returns an error for all query types. app.WasmClientKeeper = wasmkeeper.NewKeeperWithVM( appCodec, runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmer, // pass the Wasm VM instance to `08-wasm` keeper constructor + querier, ) ... ``` @@ -240,6 +250,7 @@ app.WasmClientKeeper = wasmkeeper.NewKeeperWithConfig( app.IBCKeeper.ClientKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmConfig, + querier ) ``` diff --git a/docs/docs/03-light-clients/04-wasm/05-governance.md b/docs/docs/03-light-clients/04-wasm/05-governance.md index 4434eeaa798..a5917b35b83 100644 --- a/docs/docs/03-light-clients/04-wasm/05-governance.md +++ b/docs/docs/03-light-clients/04-wasm/05-governance.md @@ -33,6 +33,7 @@ app.WasmClientKeeper = wasmkeeper.NewKeeperWithVM( app.IBCKeeper.ClientKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), // authority wasmVM, + querier, ) ``` diff --git a/modules/light-clients/08-wasm/internal/ibcwasm/querier.go b/modules/light-clients/08-wasm/internal/ibcwasm/querier.go new file mode 100644 index 00000000000..a955d4f7c10 --- /dev/null +++ b/modules/light-clients/08-wasm/internal/ibcwasm/querier.go @@ -0,0 +1,17 @@ +package ibcwasm + +import ( + "errors" + + wasmvmtypes "github.com/CosmWasm/wasmvm/types" +) + +type defaultQuerier struct{} + +func (*defaultQuerier) GasConsumed() uint64 { + return 0 +} + +func (*defaultQuerier) Query(_ wasmvmtypes.QueryRequest, _ uint64) ([]byte, error) { + return nil, errors.New("queries in contract are not allowed") +} diff --git a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go index 54ce26fd80c..6dbfc0c1c5f 100644 --- a/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go +++ b/modules/light-clients/08-wasm/internal/ibcwasm/wasm.go @@ -3,6 +3,8 @@ package ibcwasm import ( "errors" + wasmvm "github.com/CosmWasm/wasmvm" + "cosmossdk.io/collections" storetypes "cosmossdk.io/core/store" ) @@ -10,6 +12,8 @@ import ( var ( vm WasmEngine + querier wasmvm.Querier + // state management Schema collections.Schema Checksums collections.KeySet[[]byte] @@ -32,6 +36,21 @@ func GetVM() WasmEngine { return vm } +// SetQuerier sets the custom wasm query handle for the 08-wasm module. +// If wasmQuerier is nil a default querier is used that return always an error for any query. +func SetQuerier(wasmQuerier wasmvm.Querier) { + if wasmQuerier == nil { + querier = &defaultQuerier{} + } else { + querier = wasmQuerier + } +} + +// GetQuerier returns the custom wasm query handler for the 08-wasm module. +func GetQuerier() wasmvm.Querier { + return querier +} + // SetupWasmStoreService sets up the 08-wasm module's collections. func SetupWasmStoreService(storeService storetypes.KVStoreService) { sb := collections.NewSchemaBuilder(storeService) diff --git a/modules/light-clients/08-wasm/keeper/keeper.go b/modules/light-clients/08-wasm/keeper/keeper.go index 72672f6dfd7..59e9dc2ce37 100644 --- a/modules/light-clients/08-wasm/keeper/keeper.go +++ b/modules/light-clients/08-wasm/keeper/keeper.go @@ -41,6 +41,7 @@ func NewKeeperWithVM( clientKeeper types.ClientKeeper, authority string, vm ibcwasm.WasmEngine, + querier wasmvm.Querier, ) Keeper { if clientKeeper == nil { panic(errors.New("client keeper must be not nil")) @@ -59,6 +60,7 @@ func NewKeeperWithVM( } ibcwasm.SetVM(vm) + ibcwasm.SetQuerier(querier) ibcwasm.SetupWasmStoreService(storeService) return Keeper{ @@ -77,13 +79,14 @@ func NewKeeperWithConfig( clientKeeper types.ClientKeeper, authority string, wasmConfig types.WasmConfig, + querier wasmvm.Querier, ) Keeper { vm, err := wasmvm.NewVM(wasmConfig.DataDir, wasmConfig.SupportedCapabilities, types.ContractMemoryLimit, wasmConfig.ContractDebugMode, types.MemoryCacheSize) if err != nil { panic(fmt.Errorf("failed to instantiate new Wasm VM instance: %v", err)) } - return NewKeeperWithVM(cdc, storeService, clientKeeper, authority, vm) + return NewKeeperWithVM(cdc, storeService, clientKeeper, authority, vm, querier) } // GetAuthority returns the 08-wasm module's authority. diff --git a/modules/light-clients/08-wasm/keeper/keeper_test.go b/modules/light-clients/08-wasm/keeper/keeper_test.go index e50b813992e..d2337806b2b 100644 --- a/modules/light-clients/08-wasm/keeper/keeper_test.go +++ b/modules/light-clients/08-wasm/keeper/keeper_test.go @@ -156,6 +156,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), ibcwasm.GetVM(), + nil, ) }, true, @@ -170,6 +171,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, "", // authority ibcwasm.GetVM(), + nil, ) }, false, @@ -184,6 +186,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { nil, // client keeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), ibcwasm.GetVM(), + nil, ) }, false, @@ -198,6 +201,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), nil, + nil, ) }, false, @@ -212,6 +216,7 @@ func (suite *KeeperTestSuite) TestNewKeeper() { GetSimApp(suite.chainA).IBCKeeper.ClientKeeper, GetSimApp(suite.chainA).WasmClientKeeper.GetAuthority(), ibcwasm.GetVM(), + nil, ) }, false, diff --git a/modules/light-clients/08-wasm/testing/simapp/app.go b/modules/light-clients/08-wasm/testing/simapp/app.go index f343cea2ef8..ec9081e9594 100644 --- a/modules/light-clients/08-wasm/testing/simapp/app.go +++ b/modules/light-clients/08-wasm/testing/simapp/app.go @@ -471,12 +471,12 @@ func NewSimApp( // NOTE: mockVM is used for testing purposes only! app.WasmClientKeeper = wasmkeeper.NewKeeperWithVM( appCodec, runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), mockVM, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), mockVM, nil, ) } else { app.WasmClientKeeper = wasmkeeper.NewKeeperWithConfig( appCodec, runtime.NewKVStoreService(keys[wasmtypes.StoreKey]), app.IBCKeeper.ClientKeeper, - authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmConfig, + authtypes.NewModuleAddress(govtypes.ModuleName).String(), wasmConfig, nil, ) } diff --git a/modules/light-clients/08-wasm/types/querier_test.go b/modules/light-clients/08-wasm/types/querier_test.go new file mode 100644 index 00000000000..f3a6ce7745a --- /dev/null +++ b/modules/light-clients/08-wasm/types/querier_test.go @@ -0,0 +1,109 @@ +package types_test + +import ( + "encoding/json" + "math" + + wasmvm "github.com/CosmWasm/wasmvm" + wasmvmtypes "github.com/CosmWasm/wasmvm/types" + + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/internal/ibcwasm" + wasmtesting "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/testing" + "github.com/cosmos/ibc-go/modules/light-clients/08-wasm/types" + "github.com/cosmos/ibc-go/v8/modules/core/exported" +) + +type CustomQuery struct { + Echo *QueryEcho `json:"echo,omitempty"` +} + +type QueryEcho struct { + Data string `json:"data"` +} + +type CustomQueryHandler struct{} + +func (*CustomQueryHandler) GasConsumed() uint64 { + return 0 +} + +func (*CustomQueryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ([]byte, error) { + var customQuery CustomQuery + err := json.Unmarshal([]byte(request.Custom), &customQuery) + if err != nil { + return nil, wasmtesting.ErrMockContract + } + + if customQuery.Echo != nil { + data, err := json.Marshal(customQuery.Echo.Data) + return data, err + } + + return nil, wasmtesting.ErrMockContract +} + +func (suite *TypesTestSuite) TestCustomQuery() { + testCases := []struct { + name string + malleate func() + }{ + { + "success: custom query", + func() { + ibcwasm.SetQuerier(&CustomQueryHandler{}) + + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + echo := CustomQuery{ + Echo: &QueryEcho{ + Data: "hello world", + }, + } + echoJSON, err := json.Marshal(echo) + suite.Require().NoError(err) + + resp, err := querier.Query(wasmvmtypes.QueryRequest{ + Custom: json.RawMessage(echoJSON), + }, math.MaxUint64) + suite.Require().NoError(err) + + var respData string + err = json.Unmarshal(resp, &respData) + suite.Require().NoError(err) + suite.Require().Equal("hello world", respData) + + resp, err = json.Marshal(types.StatusResult{Status: exported.Active.String()}) + suite.Require().NoError(err) + + return resp, wasmtesting.DefaultGasUsed, nil + }) + }, + }, + { + "default query", + func() { + suite.mockVM.RegisterQueryCallback(types.StatusMsg{}, func(checksum wasmvm.Checksum, env wasmvmtypes.Env, queryMsg []byte, store wasmvm.KVStore, goapi wasmvm.GoAPI, querier wasmvm.Querier, gasMeter wasmvm.GasMeter, gasLimit uint64, deserCost wasmvmtypes.UFraction) ([]byte, uint64, error) { + _, err := querier.Query(wasmvmtypes.QueryRequest{}, math.MaxUint64) + suite.Require().Error(err) + + return nil, wasmtesting.DefaultGasUsed, err + }) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + suite.SetupWasmWithMockVM() + + endpoint := wasmtesting.NewWasmEndpoint(suite.chainA) + err := endpoint.CreateClient() + suite.Require().NoError(err) + + tc.malleate() + + clientStore := suite.chainA.App.GetIBCKeeper().ClientKeeper.ClientStore(suite.chainA.GetContext(), endpoint.ClientID) + clientState := endpoint.GetClientState() + clientState.Status(suite.chainA.GetContext(), clientStore, suite.chainA.App.AppCodec()) + }) + } +} diff --git a/modules/light-clients/08-wasm/types/vm.go b/modules/light-clients/08-wasm/types/vm.go index bc24b78f7cb..ab44e2466b9 100644 --- a/modules/light-clients/08-wasm/types/vm.go +++ b/modules/light-clients/08-wasm/types/vm.go @@ -49,7 +49,7 @@ func instantiateContract(ctx sdk.Context, clientStore storetypes.KVStore, checks } ctx.GasMeter().ConsumeGas(VMGasRegister.NewContractInstanceCosts(true, len(msg)), "Loading CosmWasm module: instantiate") - response, gasUsed, err := ibcwasm.GetVM().Instantiate(checksum, env, msgInfo, msg, newStoreAdapter(clientStore), wasmvmAPI, nil, multipliedGasMeter, gasLimit, costJSONDeserialization) + response, gasUsed, err := ibcwasm.GetVM().Instantiate(checksum, env, msgInfo, msg, newStoreAdapter(clientStore), wasmvmAPI, ibcwasm.GetQuerier(), multipliedGasMeter, gasLimit, costJSONDeserialization) VMGasRegister.consumeRuntimeGas(ctx, gasUsed) return response, err } @@ -67,7 +67,7 @@ func callContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Chec env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.InstantiateContractCosts(true, len(msg)), "Loading CosmWasm module: sudo") - resp, gasUsed, err := ibcwasm.GetVM().Sudo(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, nil, multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := ibcwasm.GetVM().Sudo(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, ibcwasm.GetQuerier(), multipliedGasMeter, gasLimit, costJSONDeserialization) VMGasRegister.consumeRuntimeGas(ctx, gasUsed) return resp, err } @@ -81,7 +81,7 @@ func migrateContract(ctx sdk.Context, clientID string, clientStore storetypes.KV env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.InstantiateContractCosts(true, len(msg)), "Loading CosmWasm module: migrate") - resp, gasUsed, err := ibcwasm.GetVM().Migrate(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, nil, multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := ibcwasm.GetVM().Migrate(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, ibcwasm.GetQuerier(), multipliedGasMeter, gasLimit, costJSONDeserialization) VMGasRegister.consumeRuntimeGas(ctx, gasUsed) return resp, err } @@ -99,7 +99,7 @@ func queryContract(ctx sdk.Context, clientStore storetypes.KVStore, checksum Che env := getEnv(ctx, clientID) ctx.GasMeter().ConsumeGas(VMGasRegister.InstantiateContractCosts(true, len(msg)), "Loading CosmWasm module: query") - resp, gasUsed, err := ibcwasm.GetVM().Query(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, nil, multipliedGasMeter, gasLimit, costJSONDeserialization) + resp, gasUsed, err := ibcwasm.GetVM().Query(checksum, env, msg, newStoreAdapter(clientStore), wasmvmAPI, ibcwasm.GetQuerier(), multipliedGasMeter, gasLimit, costJSONDeserialization) VMGasRegister.consumeRuntimeGas(ctx, gasUsed) return resp, err }