diff --git a/x/wasm/genesis_test.go b/tests/integration/genesis_test.go similarity index 99% rename from x/wasm/genesis_test.go rename to tests/integration/genesis_test.go index 73503c8c95..7495046ec8 100644 --- a/x/wasm/genesis_test.go +++ b/tests/integration/genesis_test.go @@ -1,4 +1,4 @@ -package wasm +package integration import ( "encoding/json" diff --git a/x/wasm/ibc_integration_test.go b/tests/integration/ibc_integration_test.go similarity index 99% rename from x/wasm/ibc_integration_test.go rename to tests/integration/ibc_integration_test.go index 7cb04bc174..9fd73ff50f 100644 --- a/x/wasm/ibc_integration_test.go +++ b/tests/integration/ibc_integration_test.go @@ -1,4 +1,4 @@ -package wasm_test +package integration import ( "encoding/base64" diff --git a/x/wasm/keeper/migrations_integration_test.go b/tests/integration/migrations_integration_test.go similarity index 99% rename from x/wasm/keeper/migrations_integration_test.go rename to tests/integration/migrations_integration_test.go index 0962a79977..8b99548611 100644 --- a/x/wasm/keeper/migrations_integration_test.go +++ b/tests/integration/migrations_integration_test.go @@ -1,4 +1,4 @@ -package keeper_test +package integration import ( "testing" diff --git a/x/wasm/module_test.go b/tests/integration/module_test.go similarity index 98% rename from x/wasm/module_test.go rename to tests/integration/module_test.go index 4fee3893e3..3aec850960 100644 --- a/x/wasm/module_test.go +++ b/tests/integration/module_test.go @@ -1,4 +1,4 @@ -package wasm +package integration import ( "bytes" @@ -24,6 +24,7 @@ import ( stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" "github.com/CosmWasm/wasmd/app/params" + "github.com/CosmWasm/wasmd/x/wasm" "github.com/CosmWasm/wasmd/x/wasm/exported" "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" @@ -44,7 +45,7 @@ func (ms mockSubspace) GetParamSet(ctx sdk.Context, ps exported.ParamSet) { } type testData struct { - module AppModule + module wasm.AppModule ctx sdk.Context acctKeeper authkeeper.AccountKeeper keeper keeper.Keeper @@ -73,7 +74,7 @@ func setupTest(t *testing.T) testData { queryRouter.SetInterfaceRegistry(encConf.InterfaceRegistry) serviceRouter.SetInterfaceRegistry(encConf.InterfaceRegistry) data := testData{ - module: NewAppModule(encConf.Codec, keepers.WasmKeeper, keepers.StakingKeeper, keepers.AccountKeeper, keepers.BankKeeper, nil, newMockSubspace(DefaultParams)), + module: wasm.NewAppModule(encConf.Codec, keepers.WasmKeeper, keepers.StakingKeeper, keepers.AccountKeeper, keepers.BankKeeper, nil, newMockSubspace(DefaultParams)), ctx: ctx, acctKeeper: keepers.AccountKeeper, keeper: *keepers.WasmKeeper, @@ -511,7 +512,7 @@ func TestReadWasmConfig(t *testing.T) { } for msg, spec := range specs { t.Run(msg, func(t *testing.T) { - got, err := ReadWasmConfig(spec.src) + got, err := wasm.ReadWasmConfig(spec.src) require.NoError(t, err) assert.Equal(t, spec.exp, got) }) diff --git a/x/wasm/keeper/msg_server_integration_test.go b/tests/integration/msg_server_integration_test.go similarity index 99% rename from x/wasm/keeper/msg_server_integration_test.go rename to tests/integration/msg_server_integration_test.go index fad2b112b6..1ffee93b57 100644 --- a/x/wasm/keeper/msg_server_integration_test.go +++ b/tests/integration/msg_server_integration_test.go @@ -1,4 +1,4 @@ -package keeper_test +package integration import ( _ "embed" @@ -20,10 +20,10 @@ import ( "github.com/CosmWasm/wasmd/x/wasm/types" ) -//go:embed testdata/reflect_1_5.wasm +//go:embed ../../../x/wasm/keeper/testdata/reflect_1_5.wasm var wasmContract []byte -//go:embed testdata/hackatom.wasm +//go:embed ../../../x/wasm/keeper/testdata/hackatom.wasm var hackatomContract []byte func TestStoreCode(t *testing.T) { diff --git a/x/wasm/keeper/proposal_integration_test.go b/tests/integration/proposal_integration_test.go similarity index 88% rename from x/wasm/keeper/proposal_integration_test.go rename to tests/integration/proposal_integration_test.go index c228424180..3417b6a8fe 100644 --- a/x/wasm/keeper/proposal_integration_test.go +++ b/tests/integration/proposal_integration_test.go @@ -1,4 +1,4 @@ -package keeper +package integration import ( "encoding/hex" @@ -18,27 +18,33 @@ import ( v1 "github.com/cosmos/cosmos-sdk/x/gov/types/v1" "github.com/cosmos/cosmos-sdk/x/gov/types/v1beta1" + "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" "github.com/CosmWasm/wasmd/x/wasm/types" ) +var ( + CyberpunkCapabilities = []string{"staking", "mask", "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3", "cosmwasm_1_4"} + ReflectCapabilities = []string{"staking", "mask", "stargate", "cosmwasm_1_1", "cosmwasm_1_2", "cosmwasm_1_3", "cosmwasm_1_4", "cosmwasm_2_0"} +) + func TestLoadStoredGovV1Beta1LegacyTypes(t *testing.T) { capabilities := make([]string, len(ReflectCapabilities)+1) copy(capabilities, ReflectCapabilities) capabilities = append(capabilities, "iterator") - pCtx, keepers := CreateTestInput(t, false, capabilities) + pCtx, keepers := keeper.CreateTestInput(t, false, capabilities) k := keepers.WasmKeeper keepers.GovKeeper.SetLegacyRouter(v1beta1.NewRouter(). - AddRoute(types.ModuleName, NewLegacyWasmProposalHandler(k, types.EnableAllProposals)), + AddRoute(types.ModuleName, keeper.NewLegacyWasmProposalHandler(k, types.EnableAllProposals)), ) - myAddress := RandomAccountAddress(t) + myAddress := keeper.RandomAccountAddress(t) keepers.Faucet.Fund(pCtx, myAddress, sdk.NewCoin(sdk.DefaultBondDenom, sdkmath.NewIntFromUint64(100_000_000))) keepers.Faucet.Fund(pCtx, myAddress, sdk.NewCoin("denom", sdkmath.NewIntFromUint64(100_000_000))) - reflectExample := InstantiateReflectExampleContract(t, pCtx, keepers) - burnerCodeID, _, err := k.create(pCtx, myAddress, testdata.BurnerContractWasm(), nil, DefaultAuthorizationPolicy{}) + reflectExample := keeper.InstantiateReflectExampleContract(t, pCtx, keepers) + burnerCodeID, _, err := k.create(pCtx, myAddress, testdata.BurnerContractWasm(), nil, keeper.DefaultAuthorizationPolicy{}) //TODO: what to do here require.NoError(t, err) - hackatomExample := InstantiateHackatomExampleContract(t, pCtx, keepers) + hackatomExample := keeper.InstantiateHackatomExampleContract(t, pCtx, keepers) type StealMsg struct { Recipient string `json:"recipient"` @@ -193,7 +199,7 @@ func TestLoadStoredGovV1Beta1LegacyTypes(t *testing.T) { } } -func mustSubmitAndExecuteLegacyProposal(t *testing.T, ctx sdk.Context, content v1beta1.Content, myActorAddress string, keepers TestKeepers) uint64 { +func mustSubmitAndExecuteLegacyProposal(t *testing.T, ctx sdk.Context, content v1beta1.Content, myActorAddress string, keepers keeper.TestKeepers) uint64 { t.Helper() govAuthority := keepers.AccountKeeper.GetModuleAddress(govtypes.ModuleName).String() msgServer := govkeeper.NewMsgServerImpl(keepers.GovKeeper) diff --git a/x/wasm/keeper/query_plugin_integration_test.go b/tests/integration/query_plugin_integration_test.go similarity index 73% rename from x/wasm/keeper/query_plugin_integration_test.go rename to tests/integration/query_plugin_integration_test.go index f9d126e20e..45dfa7c382 100644 --- a/x/wasm/keeper/query_plugin_integration_test.go +++ b/tests/integration/query_plugin_integration_test.go @@ -1,14 +1,19 @@ -package keeper +package integration import ( "bytes" + "encoding/hex" "encoding/json" "errors" "fmt" "strings" "testing" + "time" wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" + "github.com/cometbft/cometbft/crypto/ed25519" + cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/gogoproto/proto" channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" "github.com/stretchr/testify/assert" @@ -19,16 +24,22 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/types/query" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/CosmWasm/wasmd/app" + + "github.com/CosmWasm/wasmd/x/wasm/keeper" + wasmKeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/CosmWasm/wasmd/x/wasm/keeper/testdata" "github.com/CosmWasm/wasmd/x/wasm/types" ) func TestMaskReflectCustomQuery(t *testing.T) { - cdc := MakeEncodingConfig(t).Codec - ctx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) + cdc := wasmKeeper.MakeEncodingConfig(t).Codec + ctx, keepers := wasmKeeper.CreateTestInput(t, false, ReflectCapabilities, wasmKeeper.WithMessageEncoders(reflectEncoders(cdc)), wasmKeeper.WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) @@ -75,8 +86,8 @@ func TestMaskReflectCustomQuery(t *testing.T) { } func TestReflectStargateQuery(t *testing.T) { - cdc := MakeEncodingConfig(t).Codec - ctx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) + cdc := wasmKeeper.MakeEncodingConfig(t).Codec + ctx, keepers := wasmKeeper.CreateTestInput(t, false, ReflectCapabilities, wasmKeeper.WithMessageEncoders(reflectEncoders(cdc)), wasmKeeper.WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper funds := sdk.NewCoins(sdk.NewInt64Coin("denom", 320000)) @@ -118,7 +129,7 @@ func TestReflectStargateQuery(t *testing.T) { } func TestReflectGrpcQuery(t *testing.T) { - queryPlugins := (*reflectPlugins()).Merge(&QueryPlugins{ + queryPlugins := (*reflectPlugins()).Merge(&wasmKeeper.QueryPlugins{ Grpc: func(ctx sdk.Context, request *wasmvmtypes.GrpcQuery) (proto.Message, error) { if request.Path == "cosmos.bank.v1beta1.Query/AllBalances" { return &banktypes.QueryAllBalancesResponse{ @@ -128,11 +139,11 @@ func TestReflectGrpcQuery(t *testing.T) { return nil, errors.New("unsupported request") }, }) - cdc := MakeEncodingConfig(t).Codec - ctx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(&queryPlugins)) + cdc := wasmKeeper.MakeEncodingConfig(t).Codec + ctx, keepers := wasmKeeper.CreateTestInput(t, false, ReflectCapabilities, wasmKeeper.WithMessageEncoders(reflectEncoders(cdc)), wasmKeeper.WithQueryPlugins(&queryPlugins)) keeper := keepers.WasmKeeper - creator := RandomAccountAddress(t) + creator := wasmKeeper.RandomAccountAddress(t) // upload code codeID, _, err := keepers.ContractKeeper.Create(ctx, creator, testdata.ReflectContractWasm(), nil) @@ -170,13 +181,13 @@ func TestReflectGrpcQuery(t *testing.T) { } func TestReflectTotalSupplyQuery(t *testing.T) { - cdc := MakeEncodingConfig(t).Codec - ctx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) + cdc := wasmKeeper.MakeEncodingConfig(t).Codec + ctx, keepers := wasmKeeper.CreateTestInput(t, false, ReflectCapabilities, wasmKeeper.WithMessageEncoders(reflectEncoders(cdc)), wasmKeeper.WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper // upload code - codeID := StoreReflectContract(t, ctx, keepers).CodeID + codeID := wasmKeeper.StoreReflectContract(t, ctx, keepers).CodeID // creator instantiates a contract and gives it tokens - creator := RandomAccountAddress(t) + creator := wasmKeeper.RandomAccountAddress(t) contractAddr, _, err := keepers.ContractKeeper.Instantiate(ctx, codeID, creator, nil, []byte("{}"), "testing", nil) require.NoError(t, err) @@ -188,7 +199,7 @@ func TestReflectTotalSupplyQuery(t *testing.T) { }{ "known denom": { denom: "stake", - expAmount: ConvertSdkCoinToWasmCoin(currentStakeSupply), + expAmount: wasmKeeper.ConvertSdkCoinToWasmCoin(currentStakeSupply), }, "unknown denom": { denom: "unknown", @@ -221,8 +232,8 @@ func TestReflectTotalSupplyQuery(t *testing.T) { } func TestReflectInvalidStargateQuery(t *testing.T) { - cdc := MakeEncodingConfig(t).Codec - ctx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) + cdc := wasmKeeper.MakeEncodingConfig(t).Codec + ctx, keepers := wasmKeeper.CreateTestInput(t, false, ReflectCapabilities, wasmKeeper.WithMessageEncoders(reflectEncoders(cdc)), wasmKeeper.WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper funds := sdk.NewCoins(sdk.NewInt64Coin("denom", 320000)) @@ -302,8 +313,8 @@ type reflectState struct { } func TestMaskReflectWasmQueries(t *testing.T) { - cdc := MakeEncodingConfig(t).Codec - ctx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) + cdc := wasmKeeper.MakeEncodingConfig(t).Codec + ctx, keepers := wasmKeeper.CreateTestInput(t, false, ReflectCapabilities, wasmKeeper.WithMessageEncoders(reflectEncoders(cdc)), wasmKeeper.WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) @@ -372,8 +383,8 @@ func TestMaskReflectWasmQueries(t *testing.T) { } func TestWasmRawQueryWithNil(t *testing.T) { - cdc := MakeEncodingConfig(t).Codec - ctx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) + cdc := wasmKeeper.MakeEncodingConfig(t).Codec + ctx, keepers := wasmKeeper.CreateTestInput(t, false, ReflectCapabilities, wasmKeeper.WithMessageEncoders(reflectEncoders(cdc)), wasmKeeper.WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000)) @@ -416,7 +427,7 @@ func TestWasmRawQueryWithNil(t *testing.T) { } func TestQueryDenomsIntegration(t *testing.T) { - ctx, keepers := CreateTestInput(t, false, CyberpunkCapabilities) + ctx, keepers := wasmKeeper.CreateTestInput(t, false, CyberpunkCapabilities) ck, k := keepers.ContractKeeper, keepers.WasmKeeper creator := keepers.Faucet.NewFundedRandomAccount(ctx, sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))...) @@ -529,11 +540,11 @@ func TestQueryDenomsIntegration(t *testing.T) { } func TestDistributionQuery(t *testing.T) { - cdc := MakeEncodingConfig(t).Codec - pCtx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) + cdc := wasmKeeper.MakeEncodingConfig(t).Codec + pCtx, keepers := wasmKeeper.CreateTestInput(t, false, ReflectCapabilities, wasmKeeper.WithMessageEncoders(reflectEncoders(cdc)), wasmKeeper.WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper - example := InstantiateReflectExampleContract(t, pCtx, keepers) + example := wasmKeeper.InstantiateReflectExampleContract(t, pCtx, keepers) delegator := keepers.Faucet.NewFundedRandomAccount(pCtx, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000))...) otherAddr := keepers.Faucet.NewFundedRandomAccount(pCtx, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 100_000_000))...) @@ -698,11 +709,11 @@ func TestDistributionQuery(t *testing.T) { } func TestIBCListChannelsQuery(t *testing.T) { - cdc := MakeEncodingConfig(t).Codec - pCtx, keepers := CreateTestInput(t, false, ReflectCapabilities, WithMessageEncoders(reflectEncoders(cdc)), WithQueryPlugins(reflectPlugins())) + cdc := wasmKeeper.MakeEncodingConfig(t).Codec + pCtx, keepers := keeper.CreateTestInput(t, false, ReflectCapabilities, keeper.WithMessageEncoders(reflectEncoders(cdc)), keeper.WithQueryPlugins(reflectPlugins())) keeper := keepers.WasmKeeper - nonIbcExample := InstantiateReflectExampleContract(t, pCtx, keepers) - ibcExample := InstantiateReflectExampleContract(t, pCtx, keepers) + nonIbcExample := wasmKeeper.InstantiateReflectExampleContract(t, pCtx, keepers) + ibcExample := wasmKeeper.InstantiateReflectExampleContract(t, pCtx, keepers) // add an ibc port for testing myIBCPortID := "myValidPortID" cInfo := keeper.GetContractInfo(pCtx, ibcExample.Contract) @@ -907,8 +918,8 @@ type capitalizedResponse struct { } // reflectPlugins needs to be registered in test setup to handle custom query callbacks -func reflectPlugins() *QueryPlugins { - return &QueryPlugins{ +func reflectPlugins() *wasmKeeper.QueryPlugins { + return &wasmKeeper.QueryPlugins{ Custom: performCustomQuery, } } @@ -935,3 +946,220 @@ func buildReflectQuery(t *testing.T, query *testdata.ReflectQueryMsg) []byte { require.NoError(t, err) return bz } + +func TestAcceptListStargateQuerier(t *testing.T) { + wasmApp := app.SetupWithEmptyStore(t) + ctx := wasmApp.NewUncachedContext(false, cmtproto.Header{ChainID: "foo", Height: 1, Time: time.Now()}) + err := wasmApp.StakingKeeper.SetParams(ctx, stakingtypes.DefaultParams()) + require.NoError(t, err) + + addrs := app.AddTestAddrsIncremental(wasmApp, ctx, 2, sdkmath.NewInt(1_000_000)) + accepted := wasmKeeper.AcceptedQueries{ + "/cosmos.auth.v1beta1.Query/Account": &authtypes.QueryAccountResponse{}, + "/no/route/to/this": &authtypes.QueryAccountResponse{}, + } + + marshal := func(pb proto.Message) []byte { + b, err := proto.Marshal(pb) + require.NoError(t, err) + return b + } + + specs := map[string]struct { + req *wasmvmtypes.StargateQuery + expErr bool + expResp string + }{ + "in accept list - success result": { + req: &wasmvmtypes.StargateQuery{ + Path: "/cosmos.auth.v1beta1.Query/Account", + Data: marshal(&authtypes.QueryAccountRequest{Address: addrs[0].String()}), + }, + expResp: fmt.Sprintf(`{"account":{"@type":"/cosmos.auth.v1beta1.BaseAccount","address":%q,"pub_key":null,"account_number":"1","sequence":"0"}}`, addrs[0].String()), + }, + "in accept list - error result": { + req: &wasmvmtypes.StargateQuery{ + Path: "/cosmos.auth.v1beta1.Query/Account", + Data: marshal(&authtypes.QueryAccountRequest{Address: sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()).String()}), + }, + expErr: true, + }, + "not in accept list": { + req: &wasmvmtypes.StargateQuery{ + Path: "/cosmos.bank.v1beta1.Query/AllBalances", + Data: marshal(&banktypes.QueryAllBalancesRequest{Address: addrs[0].String()}), + }, + expErr: true, + }, + "unknown route": { + req: &wasmvmtypes.StargateQuery{ + Path: "/no/route/to/this", + Data: marshal(&banktypes.QueryAllBalancesRequest{Address: addrs[0].String()}), + }, + expErr: true, + }, + } + for name, spec := range specs { + t.Run(name, func(t *testing.T) { + q := wasmKeeper.AcceptListStargateQuerier(accepted, wasmApp.GRPCQueryRouter(), wasmApp.AppCodec()) + gotBz, gotErr := q(ctx, spec.req) + if spec.expErr { + require.Error(t, gotErr) + return + } + require.NoError(t, gotErr) + assert.JSONEq(t, spec.expResp, string(gotBz), string(gotBz)) + }) + } +} + +func TestResetProtoMarshalerAfterJsonMarshal(t *testing.T) { + appCodec := app.MakeEncodingConfig(t).Codec + + protoMarshaler := &banktypes.QueryAllBalancesResponse{} + expected := appCodec.MustMarshalJSON(&banktypes.QueryAllBalancesResponse{ + Balances: sdk.NewCoins(sdk.NewCoin("bar", sdkmath.NewInt(30))), + Pagination: &query.PageResponse{ + NextKey: []byte("foo"), + }, + }) + + bz, err := hex.DecodeString("0a090a036261721202333012050a03666f6f") + require.NoError(t, err) + + // first marshal + response, err := wasmKeeper.ConvertProtoToJSONMarshal(appCodec, protoMarshaler, bz) + require.NoError(t, err) + require.Equal(t, expected, response) + + // second marshal + response, err = wasmKeeper.ConvertProtoToJSONMarshal(appCodec, protoMarshaler, bz) + require.NoError(t, err) + require.Equal(t, expected, response) +} + +// TestDeterministicJsonMarshal tests that we get deterministic JSON marshaled response upon +// proto struct update in the state machine. +func TestDeterministicJsonMarshal(t *testing.T) { + testCases := []struct { + name string + originalResponse string + updatedResponse string + queryPath string + responseProtoStruct proto.Message + expectedProto func() proto.Message + }{ + /** + * + * Origin Response + * 0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f7331346c3268686a6e676c3939367772703935673867646a6871653038326375367a7732706c686b + * + * Updated Response + * 0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f7331646a783375676866736d6b6135386676673076616a6e6533766c72776b7a6a346e6377747271122d636f736d6f7331646a783375676866736d6b6135386676673076616a6e6533766c72776b7a6a346e6377747271 + // Origin proto + message QueryAccountResponse { + // account defines the account of the corresponding address. + google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; + } + // Updated proto + message QueryAccountResponse { + // account defines the account of the corresponding address. + google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; + // address is the address to query for. + string address = 2; + } + */ + { + "Query Account", + "0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679", + "0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679122d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679", + "/cosmos.auth.v1beta1.Query/Account", + &authtypes.QueryAccountResponse{}, + func() proto.Message { + account := authtypes.BaseAccount{ + Address: "cosmos1f8uxultn8sqzhznrsz3q77xwaquhgrsg6jyvfy", + } + accountResponse, err := codectypes.NewAnyWithValue(&account) + require.NoError(t, err) + return &authtypes.QueryAccountResponse{ + Account: accountResponse, + } + }, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.name), func(t *testing.T) { + appCodec := app.MakeEncodingConfig(t).Codec + + originVersionBz, err := hex.DecodeString(tc.originalResponse) + require.NoError(t, err) + jsonMarshalledOriginalBz, err := wasmKeeper.ConvertProtoToJSONMarshal(appCodec, tc.responseProtoStruct, originVersionBz) + require.NoError(t, err) + + newVersionBz, err := hex.DecodeString(tc.updatedResponse) + require.NoError(t, err) + jsonMarshalledUpdatedBz, err := wasmKeeper.ConvertProtoToJSONMarshal(appCodec, tc.responseProtoStruct, newVersionBz) + require.NoError(t, err) + + // json marshaled bytes should be the same since we use the same proto struct for unmarshalling + require.Equal(t, jsonMarshalledOriginalBz, jsonMarshalledUpdatedBz) + + // raw build also make same result + jsonMarshalExpectedResponse, err := appCodec.MarshalJSON(tc.expectedProto()) + require.NoError(t, err) + require.Equal(t, jsonMarshalledUpdatedBz, jsonMarshalExpectedResponse) + }) + } +} + +func TestConvertProtoToJSONMarshal(t *testing.T) { + testCases := []struct { + name string + queryPath string + protoResponseStruct proto.Message + originalResponse string + expectedProtoResponse proto.Message + expectedError bool + }{ + { + name: "successful conversion from proto response to json marshaled response", + queryPath: "/cosmos.bank.v1beta1.Query/AllBalances", + originalResponse: "0a090a036261721202333012050a03666f6f", + protoResponseStruct: &banktypes.QueryAllBalancesResponse{}, + expectedProtoResponse: &banktypes.QueryAllBalancesResponse{ + Balances: sdk.NewCoins(sdk.NewCoin("bar", sdkmath.NewInt(30))), + Pagination: &query.PageResponse{ + NextKey: []byte("foo"), + }, + }, + }, + { + name: "invalid proto response struct", + queryPath: "/cosmos.bank.v1beta1.Query/AllBalances", + originalResponse: "0a090a036261721202333012050a03666f6f", + protoResponseStruct: &authtypes.QueryAccountResponse{}, + expectedError: true, + }, + } + + for _, tc := range testCases { + t.Run(fmt.Sprintf("Case %s", tc.name), func(t *testing.T) { + originalVersionBz, err := hex.DecodeString(tc.originalResponse) + require.NoError(t, err) + appCodec := app.MakeEncodingConfig(t).Codec + + jsonMarshalledResponse, err := wasmKeeper.ConvertProtoToJSONMarshal(appCodec, tc.protoResponseStruct, originalVersionBz) + if tc.expectedError { + require.Error(t, err) + return + } + require.NoError(t, err) + + // check response by json marshaling proto response into json response manually + jsonMarshalExpectedResponse, err := appCodec.MarshalJSON(tc.expectedProtoResponse) + require.NoError(t, err) + require.JSONEq(t, string(jsonMarshalledResponse), string(jsonMarshalExpectedResponse)) + }) + } +} diff --git a/x/wasm/relay_pingpong_test.go b/tests/integration/relay_pingpong_test.go similarity index 99% rename from x/wasm/relay_pingpong_test.go rename to tests/integration/relay_pingpong_test.go index d06a460714..8da6f08a63 100644 --- a/x/wasm/relay_pingpong_test.go +++ b/tests/integration/relay_pingpong_test.go @@ -1,4 +1,4 @@ -package wasm_test +package integration import ( "encoding/json" diff --git a/x/wasm/relay_test.go b/tests/integration/relay_test.go similarity index 99% rename from x/wasm/relay_test.go rename to tests/integration/relay_test.go index bfb906db6b..a0d291370e 100644 --- a/x/wasm/relay_test.go +++ b/tests/integration/relay_test.go @@ -1,4 +1,4 @@ -package wasm_test +package integration import ( "encoding/json" diff --git a/x/wasm/keeper/snapshotter_integration_test.go b/tests/integration/snapshotter_integration_test.go similarity index 99% rename from x/wasm/keeper/snapshotter_integration_test.go rename to tests/integration/snapshotter_integration_test.go index ed9af49f99..81ae99a106 100644 --- a/x/wasm/keeper/snapshotter_integration_test.go +++ b/tests/integration/snapshotter_integration_test.go @@ -1,4 +1,4 @@ -package keeper_test +package integration import ( "os" diff --git a/tests/integration/test_common.go b/tests/integration/test_common.go new file mode 100644 index 0000000000..38a3fd1e06 --- /dev/null +++ b/tests/integration/test_common.go @@ -0,0 +1,169 @@ +package integration + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/CosmWasm/wasmd/x/wasm/types" + + errorsmod "cosmossdk.io/errors" + sdkmath "cosmossdk.io/math" + wasmKeeper "github.com/CosmWasm/wasmd/x/wasm/keeper" + "github.com/cosmos/cosmos-sdk/codec" + codectypes "github.com/cosmos/cosmos-sdk/codec/types" + secp256k1 "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + distributionkeeper "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +const firstCodeID = 1 + +// ensure store code returns the expected response +func assertStoreCodeResponse(t *testing.T, data []byte, expected uint64) { + var pStoreResp types.MsgStoreCodeResponse + require.NoError(t, pStoreResp.Unmarshal(data)) + require.Equal(t, pStoreResp.CodeID, expected) +} + +// ensure execution returns the expected data +func assertExecuteResponse(t *testing.T, data, expected []byte) { + var pExecResp types.MsgExecuteContractResponse + require.NoError(t, pExecResp.Unmarshal(data)) + require.Equal(t, pExecResp.Data, expected) +} + +// ensures this returns a valid bech32 address and returns it +func parseInitResponse(t *testing.T, data []byte) string { + var pInstResp types.MsgInstantiateContractResponse + require.NoError(t, pInstResp.Unmarshal(data)) + require.NotEmpty(t, pInstResp.Address) + addr := pInstResp.Address + // ensure this is a valid sdk address + _, err := sdk.AccAddressFromBech32(addr) + require.NoError(t, err) + return addr +} + +func must[t any](s t, err error) t { + if err != nil { + panic(err) + } + return s +} + +func mustUnmarshal(t *testing.T, data []byte, res interface{}) { + t.Helper() + err := json.Unmarshal(data, res) + require.NoError(t, err) +} + +func mustMarshal(t *testing.T, r interface{}) []byte { + t.Helper() + bz, err := json.Marshal(r) + require.NoError(t, err) + return bz +} + +// this will commit the current set, update the block height and set historic info +// basically, letting two blocks pass +func nextBlock(ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper) sdk.Context { + if _, err := stakingKeeper.EndBlocker(ctx); err != nil { + panic(err) + } + ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) + _ = stakingKeeper.BeginBlocker(ctx) + return ctx +} + +func setValidatorRewards(ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper, distKeeper distributionkeeper.Keeper, valAddr sdk.ValAddress, reward string) { + // allocate some rewards + vali, err := stakingKeeper.Validator(ctx, valAddr) + if err != nil { + panic(err) + } + amount, err := sdkmath.LegacyNewDecFromStr(reward) + if err != nil { + panic(err) + } + payout := sdk.DecCoins{{Denom: "stake", Amount: amount}} + err = distKeeper.AllocateTokensToValidator(ctx, vali, payout) + if err != nil { + panic(err) + } +} + +// adds a few validators and returns a list of validators that are registered +func addValidator(t *testing.T, ctx sdk.Context, stakingKeeper *stakingkeeper.Keeper, faucet *wasmKeeper.TestFaucet, value sdk.Coin) sdk.ValAddress { + owner := faucet.NewFundedRandomAccount(ctx, value) + + privKey := secp256k1.GenPrivKey() + pubKey := privKey.PubKey() + valAddr := sdk.ValAddress(owner) + + pkAny, err := codectypes.NewAnyWithValue(pubKey) + require.NoError(t, err) + msg := &stakingtypes.MsgCreateValidator{ + Description: stakingtypes.Description{ + Moniker: "Validator power", + }, + Commission: stakingtypes.CommissionRates{ + Rate: sdkmath.LegacyMustNewDecFromStr("0.1"), + MaxRate: sdkmath.LegacyMustNewDecFromStr("0.2"), + MaxChangeRate: sdkmath.LegacyMustNewDecFromStr("0.01"), + }, + MinSelfDelegation: sdkmath.OneInt(), + DelegatorAddress: owner.String(), + ValidatorAddress: valAddr.String(), + Pubkey: pkAny, + Value: value, + } + _, err = stakingkeeper.NewMsgServerImpl(stakingKeeper).CreateValidator(ctx, msg) + require.NoError(t, err) + return valAddr +} + +// reflectEncoders needs to be registered in test setup to handle custom message callbacks +func reflectEncoders(cdc codec.Codec) *wasmKeeper.MessageEncoders { + return &wasmKeeper.MessageEncoders{ + Custom: fromReflectRawMsg(cdc), + } +} + +/**** Code to support custom messages *****/ + +type reflectCustomMsg struct { + Debug string `json:"debug,omitempty"` + Raw []byte `json:"raw,omitempty"` +} + +// 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) wasmKeeper.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, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + if custom.Raw != nil { + var codecAny codectypes.Any + if err := cdc.UnmarshalJSON(custom.Raw, &codecAny); err != nil { + return nil, errorsmod.Wrap(sdkerrors.ErrJSONUnmarshal, err.Error()) + } + var msg sdk.Msg + if err := cdc.UnpackAny(&codecAny, &msg); err != nil { + return nil, err + } + return []sdk.Msg{msg}, nil + } + if custom.Debug != "" { + return nil, errorsmod.Wrapf(types.ErrInvalidMsg, "Custom Debug: %s", custom.Debug) + } + return nil, errorsmod.Wrap(types.ErrInvalidMsg, "Unknown Custom message variant") + } +} diff --git a/x/wasm/keeper/query_plugins_test.go b/x/wasm/keeper/query_plugins_test.go index a36a8139f0..76dd64aa64 100644 --- a/x/wasm/keeper/query_plugins_test.go +++ b/x/wasm/keeper/query_plugins_test.go @@ -2,17 +2,13 @@ package keeper_test import ( "context" - "encoding/hex" "encoding/json" - "fmt" "math" "testing" - "time" wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" cmtproto "github.com/cometbft/cometbft/proto/tendermint/types" dbm "github.com/cosmos/cosmos-db" - "github.com/cosmos/gogoproto/proto" channeltypes "github.com/cosmos/ibc-go/v8/modules/core/04-channel/types" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -24,15 +20,10 @@ import ( storemetrics "cosmossdk.io/store/metrics" storetypes "cosmossdk.io/store/types" - codectypes "github.com/cosmos/cosmos-sdk/codec/types" - "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/query" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" - stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" - "github.com/CosmWasm/wasmd/app" "github.com/CosmWasm/wasmd/x/wasm/keeper" "github.com/CosmWasm/wasmd/x/wasm/keeper/wasmtesting" "github.com/CosmWasm/wasmd/x/wasm/types" @@ -569,72 +560,6 @@ func TestQueryErrors(t *testing.T) { } } -func TestAcceptListStargateQuerier(t *testing.T) { - wasmApp := app.SetupWithEmptyStore(t) - ctx := wasmApp.NewUncachedContext(false, cmtproto.Header{ChainID: "foo", Height: 1, Time: time.Now()}) - err := wasmApp.StakingKeeper.SetParams(ctx, stakingtypes.DefaultParams()) - require.NoError(t, err) - - addrs := app.AddTestAddrsIncremental(wasmApp, ctx, 2, sdkmath.NewInt(1_000_000)) - accepted := keeper.AcceptedQueries{ - "/cosmos.auth.v1beta1.Query/Account": &authtypes.QueryAccountResponse{}, - "/no/route/to/this": &authtypes.QueryAccountResponse{}, - } - - marshal := func(pb proto.Message) []byte { - b, err := proto.Marshal(pb) - require.NoError(t, err) - return b - } - - specs := map[string]struct { - req *wasmvmtypes.StargateQuery - expErr bool - expResp string - }{ - "in accept list - success result": { - req: &wasmvmtypes.StargateQuery{ - Path: "/cosmos.auth.v1beta1.Query/Account", - Data: marshal(&authtypes.QueryAccountRequest{Address: addrs[0].String()}), - }, - expResp: fmt.Sprintf(`{"account":{"@type":"/cosmos.auth.v1beta1.BaseAccount","address":%q,"pub_key":null,"account_number":"1","sequence":"0"}}`, addrs[0].String()), - }, - "in accept list - error result": { - req: &wasmvmtypes.StargateQuery{ - Path: "/cosmos.auth.v1beta1.Query/Account", - Data: marshal(&authtypes.QueryAccountRequest{Address: sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()).String()}), - }, - expErr: true, - }, - "not in accept list": { - req: &wasmvmtypes.StargateQuery{ - Path: "/cosmos.bank.v1beta1.Query/AllBalances", - Data: marshal(&banktypes.QueryAllBalancesRequest{Address: addrs[0].String()}), - }, - expErr: true, - }, - "unknown route": { - req: &wasmvmtypes.StargateQuery{ - Path: "/no/route/to/this", - Data: marshal(&banktypes.QueryAllBalancesRequest{Address: addrs[0].String()}), - }, - expErr: true, - }, - } - for name, spec := range specs { - t.Run(name, func(t *testing.T) { - q := keeper.AcceptListStargateQuerier(accepted, wasmApp.GRPCQueryRouter(), wasmApp.AppCodec()) - gotBz, gotErr := q(ctx, spec.req) - if spec.expErr { - require.Error(t, gotErr) - return - } - require.NoError(t, gotErr) - assert.JSONEq(t, spec.expResp, string(gotBz), string(gotBz)) - }) - } -} - type mockWasmQueryKeeper struct { GetContractInfoFn func(ctx context.Context, contractAddress sdk.AccAddress) *types.ContractInfo QueryRawFn func(ctx context.Context, contractAddress sdk.AccAddress, key []byte) []byte @@ -721,57 +646,6 @@ func (m bankKeeperMock) DenomsMetadata(ctx context.Context, req *banktypes.Query return m.GetDenomsMetadataFn(ctx, req) } -func TestConvertProtoToJSONMarshal(t *testing.T) { - testCases := []struct { - name string - queryPath string - protoResponseStruct proto.Message - originalResponse string - expectedProtoResponse proto.Message - expectedError bool - }{ - { - name: "successful conversion from proto response to json marshaled response", - queryPath: "/cosmos.bank.v1beta1.Query/AllBalances", - originalResponse: "0a090a036261721202333012050a03666f6f", - protoResponseStruct: &banktypes.QueryAllBalancesResponse{}, - expectedProtoResponse: &banktypes.QueryAllBalancesResponse{ - Balances: sdk.NewCoins(sdk.NewCoin("bar", sdkmath.NewInt(30))), - Pagination: &query.PageResponse{ - NextKey: []byte("foo"), - }, - }, - }, - { - name: "invalid proto response struct", - queryPath: "/cosmos.bank.v1beta1.Query/AllBalances", - originalResponse: "0a090a036261721202333012050a03666f6f", - protoResponseStruct: &authtypes.QueryAccountResponse{}, - expectedError: true, - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("Case %s", tc.name), func(t *testing.T) { - originalVersionBz, err := hex.DecodeString(tc.originalResponse) - require.NoError(t, err) - appCodec := app.MakeEncodingConfig(t).Codec - - jsonMarshalledResponse, err := keeper.ConvertProtoToJSONMarshal(appCodec, tc.protoResponseStruct, originalVersionBz) - if tc.expectedError { - require.Error(t, err) - return - } - require.NoError(t, err) - - // check response by json marshaling proto response into json response manually - jsonMarshalExpectedResponse, err := appCodec.MarshalJSON(tc.expectedProtoResponse) - require.NoError(t, err) - require.JSONEq(t, string(jsonMarshalledResponse), string(jsonMarshalExpectedResponse)) - }) - } -} - func TestConvertSDKDecCoinToWasmDecCoin(t *testing.T) { specs := map[string]struct { src sdk.DecCoins @@ -808,103 +682,3 @@ func TestConvertSDKDecCoinToWasmDecCoin(t *testing.T) { }) } } - -func TestResetProtoMarshalerAfterJsonMarshal(t *testing.T) { - appCodec := app.MakeEncodingConfig(t).Codec - - protoMarshaler := &banktypes.QueryAllBalancesResponse{} - expected := appCodec.MustMarshalJSON(&banktypes.QueryAllBalancesResponse{ - Balances: sdk.NewCoins(sdk.NewCoin("bar", sdkmath.NewInt(30))), - Pagination: &query.PageResponse{ - NextKey: []byte("foo"), - }, - }) - - bz, err := hex.DecodeString("0a090a036261721202333012050a03666f6f") - require.NoError(t, err) - - // first marshal - response, err := keeper.ConvertProtoToJSONMarshal(appCodec, protoMarshaler, bz) - require.NoError(t, err) - require.Equal(t, expected, response) - - // second marshal - response, err = keeper.ConvertProtoToJSONMarshal(appCodec, protoMarshaler, bz) - require.NoError(t, err) - require.Equal(t, expected, response) -} - -// TestDeterministicJsonMarshal tests that we get deterministic JSON marshaled response upon -// proto struct update in the state machine. -func TestDeterministicJsonMarshal(t *testing.T) { - testCases := []struct { - name string - originalResponse string - updatedResponse string - queryPath string - responseProtoStruct proto.Message - expectedProto func() proto.Message - }{ - /** - * - * Origin Response - * 0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f7331346c3268686a6e676c3939367772703935673867646a6871653038326375367a7732706c686b - * - * Updated Response - * 0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f7331646a783375676866736d6b6135386676673076616a6e6533766c72776b7a6a346e6377747271122d636f736d6f7331646a783375676866736d6b6135386676673076616a6e6533766c72776b7a6a346e6377747271 - // Origin proto - message QueryAccountResponse { - // account defines the account of the corresponding address. - google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; - } - // Updated proto - message QueryAccountResponse { - // account defines the account of the corresponding address. - google.protobuf.Any account = 1 [(cosmos_proto.accepts_interface) = "AccountI"]; - // address is the address to query for. - string address = 2; - } - */ - { - "Query Account", - "0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679", - "0a530a202f636f736d6f732e617574682e763162657461312e426173654163636f756e74122f0a2d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679122d636f736d6f733166387578756c746e3873717a687a6e72737a3371373778776171756867727367366a79766679", - "/cosmos.auth.v1beta1.Query/Account", - &authtypes.QueryAccountResponse{}, - func() proto.Message { - account := authtypes.BaseAccount{ - Address: "cosmos1f8uxultn8sqzhznrsz3q77xwaquhgrsg6jyvfy", - } - accountResponse, err := codectypes.NewAnyWithValue(&account) - require.NoError(t, err) - return &authtypes.QueryAccountResponse{ - Account: accountResponse, - } - }, - }, - } - - for _, tc := range testCases { - t.Run(fmt.Sprintf("Case %s", tc.name), func(t *testing.T) { - appCodec := app.MakeEncodingConfig(t).Codec - - originVersionBz, err := hex.DecodeString(tc.originalResponse) - require.NoError(t, err) - jsonMarshalledOriginalBz, err := keeper.ConvertProtoToJSONMarshal(appCodec, tc.responseProtoStruct, originVersionBz) - require.NoError(t, err) - - newVersionBz, err := hex.DecodeString(tc.updatedResponse) - require.NoError(t, err) - jsonMarshalledUpdatedBz, err := keeper.ConvertProtoToJSONMarshal(appCodec, tc.responseProtoStruct, newVersionBz) - require.NoError(t, err) - - // json marshaled bytes should be the same since we use the same proto struct for unmarshalling - require.Equal(t, jsonMarshalledOriginalBz, jsonMarshalledUpdatedBz) - - // raw build also make same result - jsonMarshalExpectedResponse, err := appCodec.MarshalJSON(tc.expectedProto()) - require.NoError(t, err) - require.Equal(t, jsonMarshalledUpdatedBz, jsonMarshalExpectedResponse) - }) - } -} diff --git a/x/wasm/keeper/reflect_test.go b/x/wasm/keeper/reflect_test.go index d6a14ae361..1de1f0e6f3 100644 --- a/x/wasm/keeper/reflect_test.go +++ b/x/wasm/keeper/reflect_test.go @@ -3,6 +3,7 @@ package keeper import ( "encoding/json" "os" + "strings" "testing" wasmvmtypes "github.com/CosmWasm/wasmvm/v2/types" @@ -302,3 +303,54 @@ func fromReflectRawMsg(cdc codec.Codec) CustomEncoder { return nil, errorsmod.Wrap(types.ErrInvalidMsg, "Unknown Custom message variant") } } + +func unmarshalReflect[T any](t *testing.T, d []byte) T { + var v T + mustUnmarshal(t, d, &v) + return v +} + +type reflectCustomQuery struct { + Ping *struct{} `json:"ping,omitempty"` + Capitalized *testdata.Text `json:"capitalized,omitempty"` +} + +// this is from the go code back to the contract (capitalized or ping) +type customQueryResponse struct { + Msg string `json:"msg"` +} + +// this is from the contract to the go code (capitalized or ping) +type capitalizedResponse struct { + Text string `json:"text"` +} + +// 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, errorsmod.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, errorsmod.Wrap(types.ErrInvalidMsg, "Unknown Custom query variant") +} + +func buildReflectQuery(t *testing.T, query *testdata.ReflectQueryMsg) []byte { + t.Helper() + bz, err := json.Marshal(query) + require.NoError(t, err) + return bz +}