package keeper_test import ( "encoding/hex" "encoding/json" "fmt" "testing" "time" wasmvmtypes "github.com/CosmWasm/wasmvm/types" "github.com/cosmos/cosmos-sdk/codec" codectypes "github.com/cosmos/cosmos-sdk/codec/types" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/store" 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" channeltypes "github.com/cosmos/ibc-go/v4/modules/core/04-channel/types" "github.com/gogo/protobuf/proto" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" tmproto "github.com/tendermint/tendermint/proto/tendermint/types" dbm "github.com/tendermint/tm-db" "github.com/cerc-io/laconicd/app" "github.com/cerc-io/laconicd/x/wasm/keeper" "github.com/cerc-io/laconicd/x/wasm/keeper/wasmtesting" "github.com/cerc-io/laconicd/x/wasm/types" ) func TestIBCQuerier(t *testing.T) { myExampleChannels := []channeltypes.IdentifiedChannel{ // this is returned { State: channeltypes.OPEN, Ordering: channeltypes.ORDERED, Counterparty: channeltypes.Counterparty{ PortId: "counterPartyPortID", ChannelId: "counterPartyChannelID", }, ConnectionHops: []string{"one"}, Version: "v1", PortId: "myPortID", ChannelId: "myChannelID", }, // this is filtered out { State: channeltypes.INIT, Ordering: channeltypes.UNORDERED, Counterparty: channeltypes.Counterparty{ PortId: "foobar", }, ConnectionHops: []string{"one"}, Version: "initversion", PortId: "initPortID", ChannelId: "initChannelID", }, // this is returned { State: channeltypes.OPEN, Ordering: channeltypes.UNORDERED, Counterparty: channeltypes.Counterparty{ PortId: "otherCounterPartyPortID", ChannelId: "otherCounterPartyChannelID", }, ConnectionHops: []string{"other", "second"}, Version: "otherVersion", PortId: "otherPortID", ChannelId: "otherChannelID", }, // this is filtered out { State: channeltypes.CLOSED, Ordering: channeltypes.ORDERED, Counterparty: channeltypes.Counterparty{ PortId: "super", ChannelId: "duper", }, ConnectionHops: []string{"no-more"}, Version: "closedVersion", PortId: "closedPortID", ChannelId: "closedChannelID", }, } specs := map[string]struct { srcQuery *wasmvmtypes.IBCQuery wasmKeeper *mockWasmQueryKeeper channelKeeper *wasmtesting.MockChannelKeeper expJsonResult string expErr *sdkerrors.Error }{ "query port id": { srcQuery: &wasmvmtypes.IBCQuery{ PortID: &wasmvmtypes.PortIDQuery{}, }, wasmKeeper: &mockWasmQueryKeeper{ GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { return &types.ContractInfo{IBCPortID: "myIBCPortID"} }, }, channelKeeper: &wasmtesting.MockChannelKeeper{}, expJsonResult: `{"port_id":"myIBCPortID"}`, }, "query list channels - all": { srcQuery: &wasmvmtypes.IBCQuery{ ListChannels: &wasmvmtypes.ListChannelsQuery{}, }, channelKeeper: &wasmtesting.MockChannelKeeper{ IterateChannelsFn: wasmtesting.MockChannelKeeperIterator(myExampleChannels), }, expJsonResult: `{ "channels": [ { "endpoint": { "port_id": "myPortID", "channel_id": "myChannelID" }, "counterparty_endpoint": { "port_id": "counterPartyPortID", "channel_id": "counterPartyChannelID" }, "order": "ORDER_ORDERED", "version": "v1", "connection_id": "one" }, { "endpoint": { "port_id": "otherPortID", "channel_id": "otherChannelID" }, "counterparty_endpoint": { "port_id": "otherCounterPartyPortID", "channel_id": "otherCounterPartyChannelID" }, "order": "ORDER_UNORDERED", "version": "otherVersion", "connection_id": "other" } ] }`, }, "query list channels - filtered": { srcQuery: &wasmvmtypes.IBCQuery{ ListChannels: &wasmvmtypes.ListChannelsQuery{ PortID: "otherPortID", }, }, channelKeeper: &wasmtesting.MockChannelKeeper{ IterateChannelsFn: wasmtesting.MockChannelKeeperIterator(myExampleChannels), }, expJsonResult: `{ "channels": [ { "endpoint": { "port_id": "otherPortID", "channel_id": "otherChannelID" }, "counterparty_endpoint": { "port_id": "otherCounterPartyPortID", "channel_id": "otherCounterPartyChannelID" }, "order": "ORDER_UNORDERED", "version": "otherVersion", "connection_id": "other" } ] }`, }, "query list channels - filtered empty": { srcQuery: &wasmvmtypes.IBCQuery{ ListChannels: &wasmvmtypes.ListChannelsQuery{ PortID: "none-existing", }, }, channelKeeper: &wasmtesting.MockChannelKeeper{ IterateChannelsFn: wasmtesting.MockChannelKeeperIterator(myExampleChannels), }, expJsonResult: `{"channels": []}`, }, "query channel": { srcQuery: &wasmvmtypes.IBCQuery{ Channel: &wasmvmtypes.ChannelQuery{ PortID: "myQueryPortID", ChannelID: "myQueryChannelID", }, }, channelKeeper: &wasmtesting.MockChannelKeeper{ GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { return channeltypes.Channel{ State: channeltypes.OPEN, Ordering: channeltypes.UNORDERED, Counterparty: channeltypes.Counterparty{ PortId: "counterPartyPortID", ChannelId: "otherCounterPartyChannelID", }, ConnectionHops: []string{"one"}, Version: "version", }, true }, }, expJsonResult: `{ "channel": { "endpoint": { "port_id": "myQueryPortID", "channel_id": "myQueryChannelID" }, "counterparty_endpoint": { "port_id": "counterPartyPortID", "channel_id": "otherCounterPartyChannelID" }, "order": "ORDER_UNORDERED", "version": "version", "connection_id": "one" } }`, }, "query channel - without port set": { srcQuery: &wasmvmtypes.IBCQuery{ Channel: &wasmvmtypes.ChannelQuery{ ChannelID: "myQueryChannelID", }, }, wasmKeeper: &mockWasmQueryKeeper{ GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { return &types.ContractInfo{IBCPortID: "myLoadedPortID"} }, }, channelKeeper: &wasmtesting.MockChannelKeeper{ GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { return channeltypes.Channel{ State: channeltypes.OPEN, Ordering: channeltypes.UNORDERED, Counterparty: channeltypes.Counterparty{ PortId: "counterPartyPortID", ChannelId: "otherCounterPartyChannelID", }, ConnectionHops: []string{"one"}, Version: "version", }, true }, }, expJsonResult: `{ "channel": { "endpoint": { "port_id": "myLoadedPortID", "channel_id": "myQueryChannelID" }, "counterparty_endpoint": { "port_id": "counterPartyPortID", "channel_id": "otherCounterPartyChannelID" }, "order": "ORDER_UNORDERED", "version": "version", "connection_id": "one" } }`, }, "query channel in init state": { srcQuery: &wasmvmtypes.IBCQuery{ Channel: &wasmvmtypes.ChannelQuery{ PortID: "myQueryPortID", ChannelID: "myQueryChannelID", }, }, channelKeeper: &wasmtesting.MockChannelKeeper{ GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { return channeltypes.Channel{ State: channeltypes.INIT, Ordering: channeltypes.UNORDERED, Counterparty: channeltypes.Counterparty{ PortId: "foobar", }, ConnectionHops: []string{"one"}, Version: "initversion", }, true }, }, expJsonResult: "{}", }, "query channel in closed state": { srcQuery: &wasmvmtypes.IBCQuery{ Channel: &wasmvmtypes.ChannelQuery{ PortID: "myQueryPortID", ChannelID: "myQueryChannelID", }, }, channelKeeper: &wasmtesting.MockChannelKeeper{ GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { return channeltypes.Channel{ State: channeltypes.CLOSED, Ordering: channeltypes.ORDERED, Counterparty: channeltypes.Counterparty{ PortId: "super", ChannelId: "duper", }, ConnectionHops: []string{"no-more"}, Version: "closedVersion", }, true }, }, expJsonResult: "{}", }, "query channel - empty result": { srcQuery: &wasmvmtypes.IBCQuery{ Channel: &wasmvmtypes.ChannelQuery{ PortID: "myQueryPortID", ChannelID: "myQueryChannelID", }, }, channelKeeper: &wasmtesting.MockChannelKeeper{ GetChannelFn: func(ctx sdk.Context, srcPort, srcChan string) (channel channeltypes.Channel, found bool) { return channeltypes.Channel{}, false }, }, expJsonResult: "{}", }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { h := keeper.IBCQuerier(spec.wasmKeeper, spec.channelKeeper) gotResult, gotErr := h(sdk.Context{}, keeper.RandomAccountAddress(t), spec.srcQuery) require.True(t, spec.expErr.Is(gotErr), "exp %v but got %#+v", spec.expErr, gotErr) if spec.expErr != nil { return } assert.JSONEq(t, spec.expJsonResult, string(gotResult), string(gotResult)) }) } } func TestBankQuerierBalance(t *testing.T) { mock := bankKeeperMock{GetBalanceFn: func(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin { return sdk.NewCoin(denom, sdk.NewInt(1)) }} ctx := sdk.Context{} q := keeper.BankQuerier(mock) gotBz, gotErr := q(ctx, &wasmvmtypes.BankQuery{ Balance: &wasmvmtypes.BalanceQuery{ Address: keeper.RandomBech32AccountAddress(t), Denom: "ALX", }, }) require.NoError(t, gotErr) var got wasmvmtypes.BalanceResponse require.NoError(t, json.Unmarshal(gotBz, &got)) exp := wasmvmtypes.BalanceResponse{ Amount: wasmvmtypes.Coin{ Denom: "ALX", Amount: "1", }, } assert.Equal(t, exp, got) } func TestContractInfoWasmQuerier(t *testing.T) { myValidContractAddr := keeper.RandomBech32AccountAddress(t) myCreatorAddr := keeper.RandomBech32AccountAddress(t) myAdminAddr := keeper.RandomBech32AccountAddress(t) var ctx sdk.Context specs := map[string]struct { req *wasmvmtypes.WasmQuery mock mockWasmQueryKeeper expRes wasmvmtypes.ContractInfoResponse expErr bool }{ "all good": { req: &wasmvmtypes.WasmQuery{ ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: myValidContractAddr}, }, mock: mockWasmQueryKeeper{ GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { val := types.ContractInfoFixture(func(i *types.ContractInfo) { i.Admin, i.Creator, i.IBCPortID = myAdminAddr, myCreatorAddr, "myIBCPort" }) return &val }, IsPinnedCodeFn: func(ctx sdk.Context, codeID uint64) bool { return true }, }, expRes: wasmvmtypes.ContractInfoResponse{ CodeID: 1, Creator: myCreatorAddr, Admin: myAdminAddr, Pinned: true, IBCPort: "myIBCPort", }, }, "invalid addr": { req: &wasmvmtypes.WasmQuery{ ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: "not a valid addr"}, }, expErr: true, }, "unknown addr": { req: &wasmvmtypes.WasmQuery{ ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: myValidContractAddr}, }, mock: mockWasmQueryKeeper{GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { return nil }}, expErr: true, }, "not pinned": { req: &wasmvmtypes.WasmQuery{ ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: myValidContractAddr}, }, mock: mockWasmQueryKeeper{ GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { val := types.ContractInfoFixture(func(i *types.ContractInfo) { i.Admin, i.Creator = myAdminAddr, myCreatorAddr }) return &val }, IsPinnedCodeFn: func(ctx sdk.Context, codeID uint64) bool { return false }, }, expRes: wasmvmtypes.ContractInfoResponse{ CodeID: 1, Creator: myCreatorAddr, Admin: myAdminAddr, Pinned: false, }, }, "without admin": { req: &wasmvmtypes.WasmQuery{ ContractInfo: &wasmvmtypes.ContractInfoQuery{ContractAddr: myValidContractAddr}, }, mock: mockWasmQueryKeeper{ GetContractInfoFn: func(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { val := types.ContractInfoFixture(func(i *types.ContractInfo) { i.Creator = myCreatorAddr }) return &val }, IsPinnedCodeFn: func(ctx sdk.Context, codeID uint64) bool { return true }, }, expRes: wasmvmtypes.ContractInfoResponse{ CodeID: 1, Creator: myCreatorAddr, Pinned: true, }, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { q := keeper.WasmQuerier(spec.mock) gotBz, gotErr := q(ctx, spec.req) if spec.expErr { require.Error(t, gotErr) return } require.NoError(t, gotErr) var gotRes wasmvmtypes.ContractInfoResponse require.NoError(t, json.Unmarshal(gotBz, &gotRes)) assert.Equal(t, spec.expRes, gotRes) }) } } func TestCodeInfoWasmQuerier(t *testing.T) { myCreatorAddr := keeper.RandomBech32AccountAddress(t) var ctx sdk.Context myRawChecksum := []byte("myHash78901234567890123456789012") specs := map[string]struct { req *wasmvmtypes.WasmQuery mock mockWasmQueryKeeper expRes wasmvmtypes.CodeInfoResponse expErr bool }{ "all good": { req: &wasmvmtypes.WasmQuery{ CodeInfo: &wasmvmtypes.CodeInfoQuery{CodeID: 1}, }, mock: mockWasmQueryKeeper{ GetCodeInfoFn: func(ctx sdk.Context, codeID uint64) *types.CodeInfo { return &types.CodeInfo{ CodeHash: myRawChecksum, Creator: myCreatorAddr, InstantiateConfig: types.AccessConfig{ Permission: types.AccessTypeNobody, Addresses: []string{myCreatorAddr}, }, } }, }, expRes: wasmvmtypes.CodeInfoResponse{ CodeID: 1, Creator: myCreatorAddr, Checksum: myRawChecksum, }, }, "empty code id": { req: &wasmvmtypes.WasmQuery{ CodeInfo: &wasmvmtypes.CodeInfoQuery{}, }, expErr: true, }, "unknown code id": { req: &wasmvmtypes.WasmQuery{ CodeInfo: &wasmvmtypes.CodeInfoQuery{CodeID: 1}, }, mock: mockWasmQueryKeeper{ GetCodeInfoFn: func(ctx sdk.Context, codeID uint64) *types.CodeInfo { return nil }, }, expErr: true, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { q := keeper.WasmQuerier(spec.mock) gotBz, gotErr := q(ctx, spec.req) if spec.expErr { require.Error(t, gotErr) return } require.NoError(t, gotErr) var gotRes wasmvmtypes.CodeInfoResponse require.NoError(t, json.Unmarshal(gotBz, &gotRes), string(gotBz)) assert.Equal(t, spec.expRes, gotRes) }) } } func TestQueryErrors(t *testing.T) { specs := map[string]struct { src error expErr error }{ "no error": {}, "no such contract": { src: types.ErrNoSuchContractFn("contract-addr"), expErr: wasmvmtypes.NoSuchContract{Addr: "contract-addr"}, }, "no such contract - wrapped": { src: sdkerrors.Wrap(types.ErrNoSuchContractFn("contract-addr"), "my additional data"), expErr: wasmvmtypes.NoSuchContract{Addr: "contract-addr"}, }, "no such code": { src: types.ErrNoSuchCodeFn(123), expErr: wasmvmtypes.NoSuchCode{CodeID: 123}, }, "no such code - wrapped": { src: sdkerrors.Wrap(types.ErrNoSuchCodeFn(123), "my additional data"), expErr: wasmvmtypes.NoSuchCode{CodeID: 123}, }, } for name, spec := range specs { t.Run(name, func(t *testing.T) { mock := keeper.WasmVMQueryHandlerFn(func(ctx sdk.Context, caller sdk.AccAddress, request wasmvmtypes.QueryRequest) ([]byte, error) { return nil, spec.src }) ctx := sdk.Context{}.WithGasMeter(sdk.NewInfiniteGasMeter()).WithMultiStore(store.NewCommitMultiStore(dbm.NewMemDB())) q := keeper.NewQueryHandler(ctx, mock, sdk.AccAddress{}, keeper.NewDefaultWasmGasRegister()) _, gotErr := q.Query(wasmvmtypes.QueryRequest{}, 1) assert.Equal(t, spec.expErr, gotErr) }) } } func TestAcceptListStargateQuerier(t *testing.T) { wasmApp := app.SetupWithEmptyStore(t) ctx := wasmApp.NewUncachedContext(false, tmproto.Header{ChainID: "foo", Height: 1, Time: time.Now()}) wasmApp.StakingKeeper.SetParams(ctx, stakingtypes.DefaultParams()) addrs := app.AddTestAddrs(wasmApp, ctx, 2, sdk.NewInt(1_000_000)) accepted := keeper.AcceptedStargateQueries{ "/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 sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo QueryRawFn func(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte QuerySmartFn func(ctx sdk.Context, contractAddr sdk.AccAddress, req types.RawContractMessage) ([]byte, error) IsPinnedCodeFn func(ctx sdk.Context, codeID uint64) bool GetCodeInfoFn func(ctx sdk.Context, codeID uint64) *types.CodeInfo } func (m mockWasmQueryKeeper) GetContractInfo(ctx sdk.Context, contractAddress sdk.AccAddress) *types.ContractInfo { if m.GetContractInfoFn == nil { panic("not expected to be called") } return m.GetContractInfoFn(ctx, contractAddress) } func (m mockWasmQueryKeeper) QueryRaw(ctx sdk.Context, contractAddress sdk.AccAddress, key []byte) []byte { if m.QueryRawFn == nil { panic("not expected to be called") } return m.QueryRawFn(ctx, contractAddress, key) } func (m mockWasmQueryKeeper) QuerySmart(ctx sdk.Context, contractAddr sdk.AccAddress, req []byte) ([]byte, error) { if m.QuerySmartFn == nil { panic("not expected to be called") } return m.QuerySmartFn(ctx, contractAddr, req) } func (m mockWasmQueryKeeper) IsPinnedCode(ctx sdk.Context, codeID uint64) bool { if m.IsPinnedCodeFn == nil { panic("not expected to be called") } return m.IsPinnedCodeFn(ctx, codeID) } func (m mockWasmQueryKeeper) GetCodeInfo(ctx sdk.Context, codeID uint64) *types.CodeInfo { if m.GetCodeInfoFn == nil { panic("not expected to be called") } return m.GetCodeInfoFn(ctx, codeID) } type bankKeeperMock struct { GetSupplyFn func(ctx sdk.Context, denom string) sdk.Coin GetBalanceFn func(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin GetAllBalancesFn func(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins } func (m bankKeeperMock) GetSupply(ctx sdk.Context, denom string) sdk.Coin { if m.GetSupplyFn == nil { panic("not expected to be called") } return m.GetSupplyFn(ctx, denom) } func (m bankKeeperMock) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin { if m.GetBalanceFn == nil { panic("not expected to be called") } return m.GetBalanceFn(ctx, addr, denom) } func (m bankKeeperMock) GetAllBalances(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins { if m.GetAllBalancesFn == nil { panic("not expected to be called") } return m.GetAllBalancesFn(ctx, addr) } func TestConvertProtoToJSONMarshal(t *testing.T) { testCases := []struct { name string queryPath string protoResponseStruct codec.ProtoMarshaler originalResponse string expectedProtoResponse codec.ProtoMarshaler expectedError bool }{ { name: "successful conversion from proto response to json marshalled response", queryPath: "/cosmos.bank.v1beta1.Query/AllBalances", originalResponse: "0a090a036261721202333012050a03666f6f", protoResponseStruct: &banktypes.QueryAllBalancesResponse{}, expectedProtoResponse: &banktypes.QueryAllBalancesResponse{ Balances: sdk.NewCoins(sdk.NewCoin("bar", sdk.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().Marshaler jsonMarshalledResponse, err := keeper.ConvertProtoToJSONMarshal(appCodec, tc.protoResponseStruct, originalVersionBz) if tc.expectedError { require.Error(t, err) return } require.NoError(t, err) // check response by json marshalling proto response into json response manually jsonMarshalExpectedResponse, err := appCodec.MarshalJSON(tc.expectedProtoResponse) require.NoError(t, err) require.JSONEq(t, string(jsonMarshalledResponse), string(jsonMarshalExpectedResponse)) }) } } // TestDeterministicJsonMarshal tests that we get deterministic JSON marshalled 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 codec.ProtoMarshaler expectedProto func() codec.ProtoMarshaler }{ /** * * 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() codec.ProtoMarshaler { 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().Marshaler 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 marshalled 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) }) } }