package keeper import ( "encoding/json" "io/ioutil" "strings" "testing" "github.com/golang/protobuf/proto" wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/cosmos/cosmos-sdk/codec" 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" bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/CosmWasm/wasmd/x/wasm/types" ) // ReflectInitMsg is {} // ReflectHandleMsg is used to encode handle messages type ReflectHandleMsg struct { Reflect *reflectPayload `json:"reflect_msg,omitempty"` ReflectSubMsg *reflectSubPayload `json:"reflect_sub_msg,omitempty"` Change *ownerPayload `json:"change_owner,omitempty"` } type ownerPayload struct { Owner sdk.Address `json:"owner"` } type reflectPayload struct { Msgs []wasmvmtypes.CosmosMsg `json:"msgs"` } type reflectSubPayload struct { Msgs []wasmvmtypes.SubMsg `json:"msgs"` } // ReflectQueryMsg is used to encode query messages type ReflectQueryMsg struct { Owner *struct{} `json:"owner,omitempty"` Capitalized *Text `json:"capitalized,omitempty"` Chain *ChainQuery `json:"chain,omitempty"` SubMsgResult *SubCall `json:"sub_msg_result,omitempty"` } type ChainQuery struct { Request *wasmvmtypes.QueryRequest `json:"request,omitempty"` } type Text struct { Text string `json:"text"` } type SubCall struct { ID uint64 `json:"id"` } type OwnerResponse struct { Owner string `json:"owner,omitempty"` } type ChainResponse struct { Data []byte `json:"data,omitempty"` } func buildReflectQuery(t *testing.T, query *ReflectQueryMsg) []byte { bz, err := json.Marshal(query) require.NoError(t, err) return bz } func mustParse(t *testing.T, data []byte, res interface{}) { err := json.Unmarshal(data, res) require.NoError(t, err) } const ReflectFeatures = "staking,mask,stargate" func TestReflectContractSend(t *testing.T) { cdc := MakeEncodingConfig(t).Marshaler ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc))) accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) creator := keepers.Faucet.NewFundedAccount(ctx, deposit...) _, _, bob := keyPubAddr() // upload reflect code reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) reflectID, err := keeper.Create(ctx, creator, reflectCode, nil) require.NoError(t, err) require.Equal(t, uint64(1), reflectID) // upload hackatom escrow code escrowCode, err := ioutil.ReadFile("./testdata/hackatom.wasm") require.NoError(t, err) escrowID, err := keeper.Create(ctx, creator, escrowCode, nil) require.NoError(t, err) require.Equal(t, uint64(2), escrowID) // creator instantiates a contract and gives it tokens reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) reflectAddr, _, err := keeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart) require.NoError(t, err) require.NotEmpty(t, reflectAddr) // now we set contract as verifier of an escrow initMsg := HackatomExampleInitMsg{ Verifier: reflectAddr, Beneficiary: bob, } initMsgBz, err := json.Marshal(initMsg) require.NoError(t, err) escrowStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 25000)) escrowAddr, _, err := keeper.Instantiate(ctx, escrowID, creator, nil, initMsgBz, "escrow contract 2", escrowStart) require.NoError(t, err) require.NotEmpty(t, escrowAddr) // let's make sure all balances make sense checkAccount(t, ctx, accKeeper, bankKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // 100k - 40k - 25k checkAccount(t, ctx, accKeeper, bankKeeper, reflectAddr, reflectStart) checkAccount(t, ctx, accKeeper, bankKeeper, escrowAddr, escrowStart) checkAccount(t, ctx, accKeeper, bankKeeper, bob, nil) // now for the trick.... we reflect a message through the reflect to call the escrow // we also send an additional 14k tokens there. // this should reduce the reflect balance by 14k (to 26k) // this 14k is added to the escrow, then the entire balance is sent to bob (total: 39k) approveMsg := []byte(`{"release":{}}`) msgs := []wasmvmtypes.CosmosMsg{{ Wasm: &wasmvmtypes.WasmMsg{ Execute: &wasmvmtypes.ExecuteMsg{ ContractAddr: escrowAddr.String(), Msg: approveMsg, Funds: []wasmvmtypes.Coin{{ Denom: "denom", Amount: "14000", }}, }, }, }} reflectSend := ReflectHandleMsg{ Reflect: &reflectPayload{ Msgs: msgs, }, } reflectSendBz, err := json.Marshal(reflectSend) require.NoError(t, err) _, err = keeper.Execute(ctx, reflectAddr, creator, reflectSendBz, nil) require.NoError(t, err) // did this work??? checkAccount(t, ctx, accKeeper, bankKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // same as before checkAccount(t, ctx, accKeeper, bankKeeper, reflectAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 26000))) // 40k - 14k (from send) checkAccount(t, ctx, accKeeper, bankKeeper, escrowAddr, sdk.Coins{}) // emptied reserved checkAccount(t, ctx, accKeeper, bankKeeper, bob, sdk.NewCoins(sdk.NewInt64Coin("denom", 39000))) // all escrow of 25k + 14k } func TestReflectCustomMsg(t *testing.T) { cdc := MakeEncodingConfig(t).Marshaler ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) accKeeper, keeper, bankKeeper := keepers.AccountKeeper, keepers.ContractKeeper, keepers.BankKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) creator := keepers.Faucet.NewFundedAccount(ctx, deposit...) bob := keepers.Faucet.NewFundedAccount(ctx, deposit...) _, _, fred := keyPubAddr() // upload code reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) codeID, err := keeper.Create(ctx, creator, reflectCode, nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) // creator instantiates a contract and gives it tokens contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) contractAddr, _, err := keeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) require.NoError(t, err) require.NotEmpty(t, contractAddr) // set owner to bob transfer := ReflectHandleMsg{ Change: &ownerPayload{ Owner: bob, }, } transferBz, err := json.Marshal(transfer) require.NoError(t, err) _, err = keeper.Execute(ctx, contractAddr, creator, transferBz, nil) require.NoError(t, err) // check some account values checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, contractStart) checkAccount(t, ctx, accKeeper, bankKeeper, bob, deposit) checkAccount(t, ctx, accKeeper, bankKeeper, fred, nil) // bob can send contract's tokens to fred (using SendMsg) msgs := []wasmvmtypes.CosmosMsg{{ Bank: &wasmvmtypes.BankMsg{ Send: &wasmvmtypes.SendMsg{ ToAddress: fred.String(), Amount: []wasmvmtypes.Coin{{ Denom: "denom", Amount: "15000", }}, }, }, }} reflectSend := ReflectHandleMsg{ Reflect: &reflectPayload{ Msgs: msgs, }, } reflectSendBz, err := json.Marshal(reflectSend) require.NoError(t, err) _, err = keeper.Execute(ctx, contractAddr, bob, reflectSendBz, nil) require.NoError(t, err) // fred got coins checkAccount(t, ctx, accKeeper, bankKeeper, fred, sdk.NewCoins(sdk.NewInt64Coin("denom", 15000))) // contract lost them checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 25000))) checkAccount(t, ctx, accKeeper, bankKeeper, bob, deposit) // construct an opaque message var sdkSendMsg sdk.Msg = &banktypes.MsgSend{ FromAddress: contractAddr.String(), ToAddress: fred.String(), Amount: sdk.NewCoins(sdk.NewInt64Coin("denom", 23000)), } opaque, err := toReflectRawMsg(cdc, sdkSendMsg) require.NoError(t, err) reflectOpaque := ReflectHandleMsg{ Reflect: &reflectPayload{ Msgs: []wasmvmtypes.CosmosMsg{opaque}, }, } reflectOpaqueBz, err := json.Marshal(reflectOpaque) require.NoError(t, err) _, err = keeper.Execute(ctx, contractAddr, bob, reflectOpaqueBz, nil) require.NoError(t, err) // fred got more coins checkAccount(t, ctx, accKeeper, bankKeeper, fred, sdk.NewCoins(sdk.NewInt64Coin("denom", 38000))) // contract lost them checkAccount(t, ctx, accKeeper, bankKeeper, contractAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 2000))) checkAccount(t, ctx, accKeeper, bankKeeper, bob, deposit) } func TestMaskReflectCustomQuery(t *testing.T) { cdc := MakeEncodingConfig(t).Marshaler ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) creator := keepers.Faucet.NewFundedAccount(ctx, deposit...) // upload code reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) codeID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) // creator instantiates a contract and gives it tokens contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect contract 1", contractStart) require.NoError(t, err) require.NotEmpty(t, contractAddr) // let's perform a normal query of state ownerQuery := ReflectQueryMsg{ Owner: &struct{}{}, } ownerQueryBz, err := json.Marshal(ownerQuery) require.NoError(t, err) ownerRes, err := keeper.QuerySmart(ctx, contractAddr, ownerQueryBz) require.NoError(t, err) var res OwnerResponse err = json.Unmarshal(ownerRes, &res) require.NoError(t, err) assert.Equal(t, res.Owner, creator.String()) // and now making use of the custom querier callbacks customQuery := ReflectQueryMsg{ Capitalized: &Text{ Text: "all Caps noW", }, } customQueryBz, err := json.Marshal(customQuery) require.NoError(t, err) custom, err := keeper.QuerySmart(ctx, contractAddr, customQueryBz) require.NoError(t, err) var resp capitalizedResponse err = json.Unmarshal(custom, &resp) require.NoError(t, err) assert.Equal(t, resp.Text, "ALL CAPS NOW") } func TestReflectStargateQuery(t *testing.T) { cdc := MakeEncodingConfig(t).Marshaler ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper funds := sdk.NewCoins(sdk.NewInt64Coin("denom", 320000)) contractStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) expectedBalance := funds.Sub(contractStart) creator := keepers.Faucet.NewFundedAccount(ctx, funds...) // upload code reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) codeID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) require.NoError(t, err) require.Equal(t, uint64(1), codeID) // creator instantiates a contract and gives it tokens contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "reflect 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(ReflectQueryMsg{ 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(ReflectQueryMsg{ 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 reflectState struct { Owner string `json:"owner"` } func TestMaskReflectWasmQueries(t *testing.T) { cdc := MakeEncodingConfig(t).Marshaler ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) creator := keepers.Faucet.NewFundedAccount(ctx, deposit...) // upload reflect code reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) reflectID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) require.NoError(t, err) require.Equal(t, uint64(1), reflectID) // creator instantiates a contract and gives it tokens reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) reflectAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart) require.NoError(t, err) require.NotEmpty(t, reflectAddr) // for control, let's make some queries directly on the reflect ownerQuery := buildReflectQuery(t, &ReflectQueryMsg{Owner: &struct{}{}}) res, err := keeper.QuerySmart(ctx, reflectAddr, ownerQuery) require.NoError(t, err) var ownerRes OwnerResponse mustParse(t, res, &ownerRes) require.Equal(t, ownerRes.Owner, creator.String()) // and a raw query: cosmwasm_storage::Singleton uses 2 byte big-endian length-prefixed to store data configKey := append([]byte{0, 6}, []byte("config")...) raw := keeper.QueryRaw(ctx, reflectAddr, configKey) var stateRes reflectState mustParse(t, raw, &stateRes) require.Equal(t, stateRes.Owner, creator.String()) // now, let's reflect a smart query into the x/wasm handlers and see if we get the same result reflectOwnerQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ Smart: &wasmvmtypes.SmartQuery{ ContractAddr: reflectAddr.String(), Msg: ownerQuery, }, }}}} reflectOwnerBin := buildReflectQuery(t, &reflectOwnerQuery) res, err = keeper.QuerySmart(ctx, reflectAddr, reflectOwnerBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response var reflectRes ChainResponse mustParse(t, res, &reflectRes) var reflectOwnerRes OwnerResponse mustParse(t, reflectRes.Data, &reflectOwnerRes) require.Equal(t, reflectOwnerRes.Owner, creator.String()) // and with queryRaw reflectStateQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ Raw: &wasmvmtypes.RawQuery{ ContractAddr: reflectAddr.String(), Key: configKey, }, }}}} reflectStateBin := buildReflectQuery(t, &reflectStateQuery) res, err = keeper.QuerySmart(ctx, reflectAddr, reflectStateBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response var reflectRawRes ChainResponse mustParse(t, res, &reflectRawRes) // now, with the raw data, we can parse it into state var reflectStateRes reflectState mustParse(t, reflectRawRes.Data, &reflectStateRes) require.Equal(t, reflectStateRes.Owner, creator.String()) } func TestWasmRawQueryWithNil(t *testing.T) { cdc := MakeEncodingConfig(t).Marshaler ctx, keepers := CreateTestInput(t, false, ReflectFeatures, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) creator := keepers.Faucet.NewFundedAccount(ctx, deposit...) // upload reflect code reflectCode, err := ioutil.ReadFile("./testdata/reflect.wasm") require.NoError(t, err) reflectID, err := keepers.ContractKeeper.Create(ctx, creator, reflectCode, nil) require.NoError(t, err) require.Equal(t, uint64(1), reflectID) // creator instantiates a contract and gives it tokens reflectStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000)) reflectAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, reflectID, creator, nil, []byte("{}"), "reflect contract 2", reflectStart) require.NoError(t, err) require.NotEmpty(t, reflectAddr) // control: query directly missingKey := []byte{0, 1, 2, 3, 4} raw := keeper.QueryRaw(ctx, reflectAddr, missingKey) require.Nil(t, raw) // and with queryRaw reflectQuery := ReflectQueryMsg{Chain: &ChainQuery{Request: &wasmvmtypes.QueryRequest{Wasm: &wasmvmtypes.WasmQuery{ Raw: &wasmvmtypes.RawQuery{ ContractAddr: reflectAddr.String(), Key: missingKey, }, }}}} reflectStateBin := buildReflectQuery(t, &reflectQuery) res, err := keeper.QuerySmart(ctx, reflectAddr, reflectStateBin) require.NoError(t, err) // first we pull out the data from chain response, before parsing the original response var reflectRawRes ChainResponse mustParse(t, res, &reflectRawRes) // and make sure there is no data require.Empty(t, reflectRawRes.Data) // we get an empty byte slice not nil (if anyone care in go-land) require.Equal(t, []byte{}, reflectRawRes.Data) } func checkAccount(t *testing.T, ctx sdk.Context, accKeeper authkeeper.AccountKeeper, bankKeeper bankkeeper.Keeper, addr sdk.AccAddress, expected sdk.Coins) { acct := accKeeper.GetAccount(ctx, addr) if expected == nil { assert.Nil(t, acct) } else { assert.NotNil(t, acct) if expected.Empty() { // there is confusion between nil and empty slice... let's just treat them the same assert.True(t, bankKeeper.GetAllBalances(ctx, acct.GetAddress()).Empty()) } else { assert.Equal(t, bankKeeper.GetAllBalances(ctx, acct.GetAddress()), expected) } } } /**** Code to support custom messages *****/ type reflectCustomMsg struct { Debug string `json:"debug,omitempty"` Raw []byte `json:"raw,omitempty"` } // toReflectRawMsg encodes an sdk msg using any type with json encoding. // Then wraps it as an opaque message func toReflectRawMsg(cdc codec.Codec, msg sdk.Msg) (wasmvmtypes.CosmosMsg, error) { any, err := codectypes.NewAnyWithValue(msg) if err != nil { return wasmvmtypes.CosmosMsg{}, err } rawBz, err := cdc.MarshalJSON(any) if err != nil { return wasmvmtypes.CosmosMsg{}, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) } customMsg, err := json.Marshal(reflectCustomMsg{ Raw: rawBz, }) res := wasmvmtypes.CosmosMsg{ Custom: customMsg, } return res, nil } // reflectEncoders needs to be registered in test setup to handle custom message callbacks func reflectEncoders(cdc codec.Codec) *MessageEncoders { return &MessageEncoders{ Custom: fromReflectRawMsg(cdc), } } // fromReflectRawMsg decodes msg.Data to an sdk.Msg using proto Any and json encoding. // this needs to be registered on the Encoders func fromReflectRawMsg(cdc codec.Codec) CustomEncoder { return func(_sender sdk.AccAddress, msg json.RawMessage) ([]sdk.Msg, error) { var custom reflectCustomMsg err := json.Unmarshal(msg, &custom) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } if custom.Raw != nil { var any codectypes.Any if err := cdc.UnmarshalJSON(custom.Raw, &any); err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } var msg sdk.Msg if err := cdc.UnpackAny(&any, &msg); err != nil { return nil, err } return []sdk.Msg{msg}, nil } if custom.Debug != "" { return nil, sdkerrors.Wrapf(types.ErrInvalidMsg, "Custom Debug: %s", custom.Debug) } return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown Custom message variant") } } type reflectCustomQuery struct { Ping *struct{} `json:"ping,omitempty"` Capitalized *Text `json:"capitalized,omitempty"` } // this is from the go code back to the contract (capitalized or ping) type customQueryResponse struct { Msg string `json:"msg"` } // these are the return values from contract -> go depending on type of query type ownerResponse struct { Owner string `json:"owner"` } type capitalizedResponse struct { Text string `json:"text"` } type chainResponse struct { Data []byte `json:"data"` } // reflectPlugins needs to be registered in test setup to handle custom query callbacks func reflectPlugins() *QueryPlugins { return &QueryPlugins{ Custom: performCustomQuery, } } func performCustomQuery(_ sdk.Context, request json.RawMessage) ([]byte, error) { var custom reflectCustomQuery err := json.Unmarshal(request, &custom) if err != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) } if custom.Capitalized != nil { msg := strings.ToUpper(custom.Capitalized.Text) return json.Marshal(customQueryResponse{Msg: msg}) } if custom.Ping != nil { return json.Marshal(customQueryResponse{Msg: "pong"}) } return nil, sdkerrors.Wrap(types.ErrInvalidMsg, "Unknown Custom query variant") }