diff --git a/app/app.go b/app/app.go index 651c86cd05f..569522587d3 100644 --- a/app/app.go +++ b/app/app.go @@ -346,14 +346,12 @@ func NewWasmApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b ) app.evidenceKeeper = *evidenceKeeper - // just re-use the full router - do we want to limit this more? - var wasmRouter = bApp.Router() wasmDir := filepath.Join(homePath, "wasm") - wasmConfig, err := wasm.ReadWasmConfig(appOpts) if err != nil { panic("error while reading wasm config: " + err.Error()) } + // The last arguments can contain custom message handlers, and custom query handlers, // if we want to allow any custom callbacks supportedFeatures := "staking,stargate" @@ -368,7 +366,8 @@ func NewWasmApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest b app.ibcKeeper.ChannelKeeper, &app.ibcKeeper.PortKeeper, scopedWasmKeeper, - wasmRouter, + app.Router(), + app.GRPCQueryRouter(), wasmDir, wasmConfig, supportedFeatures, diff --git a/x/wasm/internal/keeper/genesis_test.go b/x/wasm/internal/keeper/genesis_test.go index df37e4c7131..f2f389a7eea 100644 --- a/x/wasm/internal/keeper/genesis_test.go +++ b/x/wasm/internal/keeper/genesis_test.go @@ -616,7 +616,7 @@ func setupKeeper(t *testing.T) (*Keeper, sdk.Context, []sdk.StoreKey) { wasmConfig := wasmTypes.DefaultWasmConfig() pk := paramskeeper.NewKeeper(encodingConfig.Marshaler, encodingConfig.Amino, keyParams, tkeyParams) - srcKeeper := NewKeeper(encodingConfig.Marshaler, keyWasm, pk.Subspace(wasmTypes.DefaultParamspace), authkeeper.AccountKeeper{}, nil, stakingkeeper.Keeper{}, distributionkeeper.Keeper{}, nil, nil, nil, nil, tempDir, wasmConfig, SupportedFeatures, nil, nil) + srcKeeper := NewKeeper(encodingConfig.Marshaler, keyWasm, pk.Subspace(wasmTypes.DefaultParamspace), authkeeper.AccountKeeper{}, nil, stakingkeeper.Keeper{}, distributionkeeper.Keeper{}, nil, nil, nil, nil, nil, tempDir, wasmConfig, SupportedFeatures, nil, nil) return &srcKeeper, ctx, []sdk.StoreKey{keyWasm, keyParams} } diff --git a/x/wasm/internal/keeper/handler_plugin.go b/x/wasm/internal/keeper/handler_plugin.go index b5f237d6470..ba880bbe735 100644 --- a/x/wasm/internal/keeper/handler_plugin.go +++ b/x/wasm/internal/keeper/handler_plugin.go @@ -2,8 +2,10 @@ package keeper import ( "encoding/json" + "fmt" "github.com/CosmWasm/wasmd/x/wasm/internal/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" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" @@ -20,8 +22,8 @@ type DefaultMessageHandler struct { encoders MessageEncoders } -func NewDefaultMessageHandler(router sdk.Router, channelKeeper types.ChannelKeeper, capabilityKeeper types.CapabilityKeeper, customEncoders *MessageEncoders) DefaultMessageHandler { - encoders := DefaultEncoders(channelKeeper, capabilityKeeper).Merge(customEncoders) +func NewDefaultMessageHandler(router sdk.Router, channelKeeper types.ChannelKeeper, capabilityKeeper types.CapabilityKeeper, unpacker codectypes.AnyUnpacker, customEncoders *MessageEncoders) DefaultMessageHandler { + encoders := DefaultEncoders(channelKeeper, capabilityKeeper, unpacker).Merge(customEncoders) return DefaultMessageHandler{ router: router, encoders: encoders, @@ -31,24 +33,27 @@ func NewDefaultMessageHandler(router sdk.Router, channelKeeper types.ChannelKeep type BankEncoder func(sender sdk.AccAddress, msg *wasmvmtypes.BankMsg) ([]sdk.Msg, error) type CustomEncoder func(sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) type StakingEncoder func(sender sdk.AccAddress, msg *wasmvmtypes.StakingMsg) ([]sdk.Msg, error) +type StargateEncoder func(sender sdk.AccAddress, msg *wasmvmtypes.StargateMsg) ([]sdk.Msg, error) type WasmEncoder func(sender sdk.AccAddress, msg *wasmvmtypes.WasmMsg) ([]sdk.Msg, error) type IBCEncoder func(ctx sdk.Context, sender sdk.AccAddress, contractIBCPortID string, msg *wasmvmtypes.IBCMsg) ([]sdk.Msg, error) type MessageEncoders struct { - Bank BankEncoder - Custom CustomEncoder - Staking StakingEncoder - Wasm WasmEncoder - IBC IBCEncoder + Bank BankEncoder + Custom CustomEncoder + IBC IBCEncoder + Staking StakingEncoder + Stargate StargateEncoder + Wasm WasmEncoder } -func DefaultEncoders(channelKeeper types.ChannelKeeper, capabilityKeeper types.CapabilityKeeper) MessageEncoders { +func DefaultEncoders(channelKeeper types.ChannelKeeper, capabilityKeeper types.CapabilityKeeper, unpacker codectypes.AnyUnpacker) MessageEncoders { return MessageEncoders{ - Bank: EncodeBankMsg, - Custom: NoCustomMsg, - Staking: EncodeStakingMsg, - Wasm: EncodeWasmMsg, - IBC: EncodeIBCMsg(channelKeeper, capabilityKeeper), + Bank: EncodeBankMsg, + Custom: NoCustomMsg, + IBC: EncodeIBCMsg(channelKeeper, capabilityKeeper), + Staking: EncodeStakingMsg, + Stargate: EncodeStargateMsg(unpacker), + Wasm: EncodeWasmMsg, } } @@ -62,15 +67,18 @@ func (e MessageEncoders) Merge(o *MessageEncoders) MessageEncoders { if o.Custom != nil { e.Custom = o.Custom } + if o.IBC != nil { + e.IBC = o.IBC + } if o.Staking != nil { e.Staking = o.Staking } + if o.Stargate != nil { + e.Stargate = o.Stargate + } if o.Wasm != nil { e.Wasm = o.Wasm } - if o.IBC != nil { - e.IBC = o.IBC - } return e } @@ -80,12 +88,14 @@ func (e MessageEncoders) Encode(ctx sdk.Context, contractAddr sdk.AccAddress, co return e.Bank(contractAddr, msg.Bank) case msg.Custom != nil: return e.Custom(contractAddr, msg.Custom) + case msg.IBC != nil: + return e.IBC(ctx, contractAddr, contractIBCPortID, msg.IBC) case msg.Staking != nil: return e.Staking(contractAddr, msg.Staking) + case msg.Stargate != nil: + return e.Stargate(contractAddr, msg.Stargate) case msg.Wasm != nil: return e.Wasm(contractAddr, msg.Wasm) - case msg.IBC != nil: - return e.IBC(ctx, contractAddr, contractIBCPortID, msg.IBC) } return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown variant of Wasm") } @@ -170,6 +180,23 @@ func EncodeStakingMsg(sender sdk.AccAddress, msg *wasmvmtypes.StakingMsg) ([]sdk } } +func EncodeStargateMsg(unpacker codectypes.AnyUnpacker) StargateEncoder { + return func(sender sdk.AccAddress, msg *wasmvmtypes.StargateMsg) ([]sdk.Msg, error) { + any := codectypes.Any{ + TypeUrl: msg.TypeURL, + Value: msg.Value, + } + var sdkMsg sdk.Msg + if err := unpacker.UnpackAny(&any, &sdkMsg); err != nil { + return nil, sdkerrors.Wrap(types.ErrInvalidMsg, fmt.Sprintf("Cannot unpack proto message with type URL: %s", msg.TypeURL)) + } + if err := codectypes.UnpackInterfaces(sdkMsg, unpacker); err != nil { + return nil, sdkerrors.Wrap(types.ErrInvalidMsg, fmt.Sprintf("UnpackInterfaces inside msg: %s", err)) + } + return []sdk.Msg{sdkMsg}, nil + } +} + func EncodeWasmMsg(sender sdk.AccAddress, msg *wasmvmtypes.WasmMsg) ([]sdk.Msg, error) { switch { case msg.Execute != nil: diff --git a/x/wasm/internal/keeper/handler_plugin_test.go b/x/wasm/internal/keeper/handler_plugin_test.go index 62f3429a285..3348d8bcb0e 100644 --- a/x/wasm/internal/keeper/handler_plugin_test.go +++ b/x/wasm/internal/keeper/handler_plugin_test.go @@ -8,6 +8,7 @@ import ( clienttypes "github.com/cosmos/cosmos-sdk/x/ibc/core/02-client/types" channeltypes "github.com/cosmos/cosmos-sdk/x/ibc/core/04-channel/types" ibcexported "github.com/cosmos/cosmos-sdk/x/ibc/core/exported" + "github.com/golang/protobuf/proto" "testing" "github.com/CosmWasm/wasmd/x/wasm/internal/types" @@ -32,6 +33,17 @@ func TestEncoding(t *testing.T) { jsonMsg := json.RawMessage(`{"foo": 123}`) + bankMsg := &banktypes.MsgSend{ + FromAddress: addr2.String(), + ToAddress: addr1.String(), + Amount: sdk.Coins{ + sdk.NewInt64Coin("uatom", 12345), + sdk.NewInt64Coin("utgd", 54321), + }, + } + bankMsgBin, err := proto.Marshal(bankMsg) + require.NoError(t, err) + cases := map[string]struct { sender sdk.AccAddress srcMsg wasmvmtypes.CosmosMsg @@ -276,6 +288,27 @@ func TestEncoding(t *testing.T) { }, }, }, + // TODO: alpe? can you add an example with sub-interfaces (where the UnpackInterfaces call would be needed) + "stargate encoded bank msg": { + sender: addr2, + srcMsg: wasmvmtypes.CosmosMsg{ + Stargate: &wasmvmtypes.StargateMsg{ + TypeURL: "/cosmos.bank.v1beta1.MsgSend", + Value: bankMsgBin, + }, + }, + output: []sdk.Msg{bankMsg}, + }, + "stargate encoded invalid typeUrl": { + sender: addr2, + srcMsg: wasmvmtypes.CosmosMsg{ + Stargate: &wasmvmtypes.StargateMsg{ + TypeURL: "/cosmos.bank.v2.MsgSend", + Value: bankMsgBin, + }, + }, + isError: true, + }, "IBC transfer with block timeout": { sender: addr1, srcIBCPort: "myIBCPort", @@ -355,7 +388,8 @@ func TestEncoding(t *testing.T) { }, }, } - encoder := DefaultEncoders(nil, nil) + encodingConfig := MakeEncodingConfig(t) + encoder := DefaultEncoders(nil, nil, encodingConfig.Marshaler) for name, tc := range cases { tc := tc t.Run(name, func(t *testing.T) { diff --git a/x/wasm/internal/keeper/keeper.go b/x/wasm/internal/keeper/keeper.go index 89e4dcafaa8..2721b262a19 100644 --- a/x/wasm/internal/keeper/keeper.go +++ b/x/wasm/internal/keeper/keeper.go @@ -88,6 +88,7 @@ func NewKeeper( portKeeper types.PortKeeper, capabilityKeeper types.CapabilityKeeper, router sdk.Router, + queryRouter GRPCQueryRouter, homeDir string, wasmConfig types.WasmConfig, supportedFeatures string, @@ -105,7 +106,6 @@ func NewKeeper( paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) } - messageEncoders := DefaultEncoders(channelKeeper, capabilityKeeper).Merge(customEncoders) keeper := Keeper{ storeKey: storeKey, cdc: cdc, @@ -115,12 +115,12 @@ func NewKeeper( ChannelKeeper: channelKeeper, portKeeper: portKeeper, capabilityKeeper: capabilityKeeper, - messenger: NewDefaultMessageHandler(router, channelKeeper, capabilityKeeper, &messageEncoders), + messenger: NewDefaultMessageHandler(router, channelKeeper, capabilityKeeper, cdc, customEncoders), queryGasLimit: wasmConfig.SmartQueryGasLimit, authZPolicy: DefaultAuthorizationPolicy{}, paramSpace: paramSpace, } - keeper.queryPlugins = DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, &keeper).Merge(customPlugins) + keeper.queryPlugins = DefaultQueryPlugins(bankKeeper, stakingKeeper, distKeeper, queryRouter, &keeper).Merge(customPlugins) for _, o := range opts { o.apply(&keeper) } diff --git a/x/wasm/internal/keeper/options_test.go b/x/wasm/internal/keeper/options_test.go index 91bc5a9b0c2..d1847779819 100644 --- a/x/wasm/internal/keeper/options_test.go +++ b/x/wasm/internal/keeper/options_test.go @@ -43,6 +43,7 @@ func TestConstructorOptions(t *testing.T) { nil, nil, nil, + nil, "tempDir", types.DefaultWasmConfig(), SupportedFeatures, diff --git a/x/wasm/internal/keeper/query_plugins.go b/x/wasm/internal/keeper/query_plugins.go index a62b1745a50..91d03ba0b52 100644 --- a/x/wasm/internal/keeper/query_plugins.go +++ b/x/wasm/internal/keeper/query_plugins.go @@ -2,6 +2,7 @@ package keeper import ( "encoding/json" + "fmt" wasmvmtypes "github.com/CosmWasm/wasmvm/types" sdk "github.com/cosmos/cosmos-sdk/types" @@ -11,6 +12,7 @@ import ( distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + abci "github.com/tendermint/tendermint/abci/types" ) type QueryHandler struct { @@ -18,6 +20,18 @@ type QueryHandler struct { Plugins QueryPlugins } +// -- interfaces from baseapp - so we can use the GPRQueryRouter -- + +// GRPCQueryHandler defines a function type which handles ABCI Query requests +// using gRPC +type GRPCQueryHandler = func(ctx sdk.Context, req abci.RequestQuery) (abci.ResponseQuery, error) + +type GRPCQueryRouter interface { + Route(path string) GRPCQueryHandler +} + +// -- end baseapp interfaces -- + var _ wasmvmtypes.Querier = QueryHandler{} func (q QueryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ([]byte, error) { @@ -40,6 +54,9 @@ func (q QueryHandler) Query(request wasmvmtypes.QueryRequest, gasLimit uint64) ( if request.Staking != nil { return q.Plugins.Staking(subctx, request.Staking) } + if request.Stargate != nil { + return q.Plugins.Stargate(subctx, request.Stargate) + } if request.Wasm != nil { return q.Plugins.Wasm(subctx, request.Wasm) } @@ -53,18 +70,20 @@ func (q QueryHandler) GasConsumed() uint64 { type CustomQuerier func(ctx sdk.Context, request json.RawMessage) ([]byte, error) type QueryPlugins struct { - Bank func(ctx sdk.Context, request *wasmvmtypes.BankQuery) ([]byte, error) - Custom CustomQuerier - Staking func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) - Wasm func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error) + Bank func(ctx sdk.Context, request *wasmvmtypes.BankQuery) ([]byte, error) + Custom CustomQuerier + Staking func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) + Stargate func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) + Wasm func(ctx sdk.Context, request *wasmvmtypes.WasmQuery) ([]byte, error) } -func DefaultQueryPlugins(bank bankkeeper.ViewKeeper, staking stakingkeeper.Keeper, distKeeper distributionkeeper.Keeper, wasm *Keeper) QueryPlugins { +func DefaultQueryPlugins(bank bankkeeper.ViewKeeper, staking stakingkeeper.Keeper, distKeeper distributionkeeper.Keeper, queryRouter GRPCQueryRouter, wasm *Keeper) QueryPlugins { return QueryPlugins{ - Bank: BankQuerier(bank), - Custom: NoCustomQuerier, - Staking: StakingQuerier(staking, distKeeper), - Wasm: WasmQuerier(wasm), + Bank: BankQuerier(bank), + Custom: NoCustomQuerier, + Staking: StakingQuerier(staking, distKeeper), + Stargate: StargateQuerier(queryRouter), + Wasm: WasmQuerier(wasm), } } @@ -82,6 +101,9 @@ func (e QueryPlugins) Merge(o *QueryPlugins) QueryPlugins { if o.Staking != nil { e.Staking = o.Staking } + if o.Stargate != nil { + e.Stargate = o.Stargate + } if o.Wasm != nil { e.Wasm = o.Wasm } @@ -124,6 +146,24 @@ func NoCustomQuerier(sdk.Context, json.RawMessage) ([]byte, error) { return nil, wasmvmtypes.UnsupportedRequest{Kind: "custom"} } +func StargateQuerier(queryRouter GRPCQueryRouter) func(ctx sdk.Context, request *wasmvmtypes.StargateQuery) ([]byte, error) { + return func(ctx sdk.Context, msg *wasmvmtypes.StargateQuery) ([]byte, error) { + route := queryRouter.Route(msg.Path) + if route == nil { + return nil, wasmvmtypes.UnsupportedRequest{Kind: fmt.Sprintf("No route to query '%s'", msg.Path)} + } + req := abci.RequestQuery{ + Data: msg.Data, + Path: msg.Path, + } + res, err := route(ctx, req) + if err != nil { + return nil, err + } + return res.Value, nil + } +} + func StakingQuerier(keeper stakingkeeper.Keeper, distKeeper distributionkeeper.Keeper) func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) { return func(ctx sdk.Context, request *wasmvmtypes.StakingQuery) ([]byte, error) { if request.BondedDenom != nil { diff --git a/x/wasm/internal/keeper/reflect_test.go b/x/wasm/internal/keeper/reflect_test.go index da86e0380c1..c763535879a 100644 --- a/x/wasm/internal/keeper/reflect_test.go +++ b/x/wasm/internal/keeper/reflect_test.go @@ -2,6 +2,7 @@ package keeper import ( "encoding/json" + "github.com/golang/protobuf/proto" "io/ioutil" "strings" "testing" @@ -296,6 +297,79 @@ func TestMaskReflectCustomQuery(t *testing.T) { assert.Equal(t, resp.Text, "ALL CAPS NOW") } +func TestReflectStargateQuery(t *testing.T) { + cdc := MakeTestCodec(t) + ctx, keepers := CreateTestInput(t, false, MaskFeatures, maskEncoders(cdc), maskPlugins()) + accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.WasmKeeper, keepers.BankKeeper + + funds := sdk.NewCoins(sdk.NewInt64Coin("denom", 320000)) + contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) + expectedBalance := funds.Sub(contractStart) + creator := createFakeFundedAccount(t, ctx, accKeeper, bankKeeper, funds) + + // upload code + maskCode, err := ioutil.ReadFile("./testdata/reflect.wasm") + require.NoError(t, err) + codeID, err := keeper.Create(ctx, creator, maskCode, "", "", nil) + require.NoError(t, err) + require.Equal(t, uint64(1), codeID) + + // creator instantiates a contract and gives it tokens + contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "mask contract 1", contractStart) + require.NoError(t, err) + require.NotEmpty(t, contractAddr) + + // first, normal query for the bank balance (to make sure our query is proper) + bankQuery := wasmvmtypes.QueryRequest{ + Bank: &wasmvmtypes.BankQuery{ + AllBalances: &wasmvmtypes.AllBalancesQuery{ + Address: creator.String(), + }, + }, + } + simpleQueryBz, err := json.Marshal(MaskQueryMsg{ + Chain: &ChainQuery{Request: &bankQuery}, + }) + require.NoError(t, err) + simpleRes, err := keeper.QuerySmart(ctx, contractAddr, simpleQueryBz) + require.NoError(t, err) + var simpleChain ChainResponse + mustParse(t, simpleRes, &simpleChain) + var simpleBalance wasmvmtypes.AllBalancesResponse + mustParse(t, simpleChain.Data, &simpleBalance) + require.Equal(t, len(expectedBalance), len(simpleBalance.Amount)) + assert.Equal(t, simpleBalance.Amount[0].Amount, expectedBalance[0].Amount.String()) + assert.Equal(t, simpleBalance.Amount[0].Denom, expectedBalance[0].Denom) + + // now, try to build a protobuf query + protoQuery := banktypes.QueryAllBalancesRequest{ + Address: creator.String(), + } + protoQueryBin, err := proto.Marshal(&protoQuery) + protoRequest := wasmvmtypes.QueryRequest{ + Stargate: &wasmvmtypes.StargateQuery{ + Path: "/cosmos.bank.v1beta1.Query/AllBalances", + Data: protoQueryBin, + }, + } + protoQueryBz, err := json.Marshal(MaskQueryMsg{ + Chain: &ChainQuery{Request: &protoRequest}, + }) + require.NoError(t, err) + + // make a query on the chain + protoRes, err := keeper.QuerySmart(ctx, contractAddr, protoQueryBz) + require.NoError(t, err) + var protoChain ChainResponse + mustParse(t, protoRes, &protoChain) + + // unmarshal raw protobuf response + var protoResult banktypes.QueryAllBalancesResponse + err = proto.Unmarshal(protoChain.Data, &protoResult) + require.NoError(t, err) + assert.Equal(t, expectedBalance, protoResult.Balances) +} + type maskState struct { Owner []byte `json:"owner"` } diff --git a/x/wasm/internal/keeper/test_common.go b/x/wasm/internal/keeper/test_common.go index c93aa306d48..712e32f5323 100644 --- a/x/wasm/internal/keeper/test_common.go +++ b/x/wasm/internal/keeper/test_common.go @@ -255,6 +255,11 @@ func CreateTestInput(t *testing.T, isCheckTx bool, supportedFeatures string, enc dh := distribution.NewHandler(distKeeper) router.AddRoute(sdk.NewRoute(distributiontypes.RouterKey, dh)) + querier := baseapp.NewGRPCQueryRouter() + banktypes.RegisterQueryServer(querier, bankKeeper) + stakingtypes.RegisterQueryServer(querier, stakingkeeper.Querier{Keeper: stakingKeeper}) + distributiontypes.RegisterQueryServer(querier, distKeeper) + // Load default wasm config wasmConfig := types.DefaultWasmConfig() @@ -270,6 +275,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, supportedFeatures string, enc &ibcKeeper.PortKeeper, scopedWasmKeeper, router, + querier, tempDir, wasmConfig, supportedFeatures,