diff --git a/x/accounts/account_test.go b/x/accounts/account_test.go index 1377ba7a15..02af5e7b62 100644 --- a/x/accounts/account_test.go +++ b/x/accounts/account_test.go @@ -7,6 +7,8 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/wrapperspb" + bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" + basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" "cosmossdk.io/x/accounts/internal/implementation" ) @@ -15,8 +17,13 @@ var _ implementation.Account = (*TestAccount)(nil) type TestAccount struct{} func (t TestAccount) RegisterInitHandler(builder *implementation.InitBuilder) { - implementation.RegisterInitHandler(builder, func(_ context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { - return &emptypb.Empty{}, nil + implementation.RegisterInitHandler(builder, func(ctx context.Context, _ *emptypb.Empty) (*emptypb.Empty, error) { + // we also force a module call here to test things work as expected. + _, err := implementation.QueryModule[bankv1beta1.QueryBalanceResponse](ctx, &bankv1beta1.QueryBalanceRequest{ + Address: string(implementation.Whoami(ctx)), + Denom: "atom", + }) + return &emptypb.Empty{}, err }) } @@ -33,6 +40,28 @@ func (t TestAccount) RegisterExecuteHandlers(builder *implementation.ExecuteBuil return wrapperspb.UInt64(value), nil }) + + // this is for intermodule comms testing, we simulate a bank send + implementation.RegisterExecuteHandler(builder, func(ctx context.Context, req *wrapperspb.Int64Value) (*emptypb.Empty, error) { + resp, err := implementation.ExecModule[bankv1beta1.MsgSendResponse](ctx, &bankv1beta1.MsgSend{ + FromAddress: string(implementation.Whoami(ctx)), + ToAddress: "recipient", + Amount: []*basev1beta1.Coin{ + { + Denom: "test", + Amount: strconv.FormatInt(req.Value, 10), + }, + }, + }) + if err != nil { + return nil, err + } + if resp == nil { + panic("nil response") // should never happen + } + + return &emptypb.Empty{}, nil + }) } func (t TestAccount) RegisterQueryHandlers(builder *implementation.QueryBuilder) { @@ -43,4 +72,22 @@ func (t TestAccount) RegisterQueryHandlers(builder *implementation.QueryBuilder) implementation.RegisterQueryHandler(builder, func(_ context.Context, req *wrapperspb.UInt64Value) (*wrapperspb.StringValue, error) { return wrapperspb.String(strconv.FormatUint(req.Value, 10)), nil }) + + // test intermodule comms, we simulate someone is sending the account a request for the accounts balance + // of a given denom. + implementation.RegisterQueryHandler(builder, func(ctx context.Context, req *wrapperspb.StringValue) (*wrapperspb.Int64Value, error) { + resp, err := implementation.QueryModule[bankv1beta1.QueryBalanceResponse](ctx, &bankv1beta1.QueryBalanceRequest{ + Address: string(implementation.Whoami(ctx)), + Denom: req.Value, + }) + if err != nil { + return nil, err + } + + amt, err := strconv.ParseInt(resp.Balance.Amount, 10, 64) + if err != nil { + return nil, err + } + return wrapperspb.Int64(amt), nil + }) } diff --git a/x/accounts/go.mod b/x/accounts/go.mod index 0d7282e23e..01bd0e54bc 100644 --- a/x/accounts/go.mod +++ b/x/accounts/go.mod @@ -3,6 +3,7 @@ module cosmossdk.io/x/accounts go 1.21 require ( + cosmossdk.io/api v0.7.0 cosmossdk.io/collections v0.4.0 cosmossdk.io/core v0.11.0 github.com/cosmos/gogoproto v1.4.11 @@ -12,7 +13,6 @@ require ( ) require ( - cosmossdk.io/api v0.7.0 // indirect cosmossdk.io/depinject v1.0.0-alpha.4 // indirect github.com/DataDog/zstd v1.5.5 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -48,6 +48,8 @@ require ( golang.org/x/net v0.15.0 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/text v0.13.0 // indirect + google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/x/accounts/go.sum b/x/accounts/go.sum index be0b9cdf32..9fdce631e7 100644 --- a/x/accounts/go.sum +++ b/x/accounts/go.sum @@ -188,6 +188,10 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5 h1:L6iMMGrtzgHsWofoFcihmDEMYeDR9KN/ThbPWGrh++g= +google.golang.org/genproto v0.0.0-20230803162519-f966b187b2e5/go.mod h1:oH/ZOT02u4kWEp7oYBGYFFkCdKS/uYR9Z7+0/xuuFp8= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e h1:z3vDksarJxsAKM5dmEGv0GHwE2hKJ096wZra71Vs4sw= +google.golang.org/genproto/googleapis/api v0.0.0-20230726155614-23370e0ffb3e/go.mod h1:rsr7RhLuwsDKL7RmgDDCUc6yaGr1iqceVb5Wv6f6YvQ= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d h1:uvYuEyMHKNt+lT4K3bN6fGswmK8qSvcreM3BwjDh+y4= google.golang.org/genproto/googleapis/rpc v0.0.0-20230822172742-b8732ec3820d/go.mod h1:+Bk1OCOj40wS2hwAMA+aCW9ypzm63QTBBHp6lQ3p+9M= google.golang.org/grpc v1.58.0 h1:32JY8YpPMSR45K+c3o6b8VL73V+rR8k+DeMIr4vRH8o= diff --git a/x/accounts/internal/implementation/context.go b/x/accounts/internal/implementation/context.go index bccceb81d0..fe3bb8e26d 100644 --- a/x/accounts/internal/implementation/context.go +++ b/x/accounts/internal/implementation/context.go @@ -1,19 +1,29 @@ package implementation import ( + "bytes" "context" + "errors" + "fmt" + + "google.golang.org/protobuf/proto" "cosmossdk.io/core/store" "cosmossdk.io/x/accounts/internal/prefixstore" ) +var errUnauthorized = errors.New("unauthorized") + type contextKey struct{} type contextValue struct { - store store.KVStore // store is the prefixed store for the account. - sender []byte // sender is the address of the entity invoking the account action. - whoami []byte // whoami is the address of the account being invoked. - originalContext context.Context // originalContext that was used to build the account context. + store store.KVStore // store is the prefixed store for the account. + sender []byte // sender is the address of the entity invoking the account action. + whoami []byte // whoami is the address of the account being invoked. + originalContext context.Context // originalContext that was used to build the account context. + getExpectedSender func(msg proto.Message) ([]byte, error) + moduleExec func(ctx context.Context, msg proto.Message) (proto.Message, error) + moduleQuery func(ctx context.Context, msg proto.Message) (proto.Message, error) } // MakeAccountContext creates a new account execution context given: @@ -21,15 +31,68 @@ type contextValue struct { // accountAddr: the address of the account being invoked, which is used to give the // account a prefixed storage. // sender: the address of entity invoking the account action. -func MakeAccountContext(ctx context.Context, storeSvc store.KVStoreService, accountAddr, sender []byte) context.Context { +func MakeAccountContext( + ctx context.Context, + storeSvc store.KVStoreService, + accountAddr, + sender []byte, + getSenderFunc func(msg proto.Message) ([]byte, error), + moduleExec func(ctx context.Context, msg proto.Message) (proto.Message, error), + moduleQuery func(ctx context.Context, msg proto.Message) (proto.Message, error), +) context.Context { return context.WithValue(ctx, contextKey{}, contextValue{ - store: prefixstore.New(storeSvc.OpenKVStore(ctx), accountAddr), - sender: sender, - whoami: accountAddr, - originalContext: ctx, + store: prefixstore.New(storeSvc.OpenKVStore(ctx), accountAddr), + sender: sender, + whoami: accountAddr, + originalContext: ctx, + getExpectedSender: getSenderFunc, + moduleExec: moduleExec, + moduleQuery: moduleQuery, }) } +// ExecModule can be used to execute a message towards a module. +func ExecModule[Resp any, RespProto ProtoMsg[Resp], Req any, ReqProto ProtoMsg[Req]](ctx context.Context, msg ReqProto) (RespProto, error) { + // get sender + v := ctx.Value(contextKey{}).(contextValue) + // check sender + expectedSender, err := v.getExpectedSender(msg) + if err != nil { + return nil, err + } + if !bytes.Equal(expectedSender, v.whoami) { + return nil, errUnauthorized + } + + // execute module, unwrapping the original context. + resp, err := v.moduleExec(v.originalContext, msg) + if err != nil { + return nil, err + } + + concreteResp, ok := resp.(RespProto) + if !ok { + return nil, fmt.Errorf("unexpected response type %T", resp) + } + return concreteResp, nil +} + +// QueryModule can be used by an account to execute a module query. +func QueryModule[Resp any, RespProto ProtoMsg[Resp], Req any, ReqProto ProtoMsg[Req]](ctx context.Context, msg ReqProto) (RespProto, error) { + // we do not need to check the sender in a query because it is not a state transition. + // we also unwrap the original context. + v := ctx.Value(contextKey{}).(contextValue) + resp, err := v.moduleQuery(v.originalContext, msg) + if err != nil { + return nil, err + } + concreteResp, ok := resp.(RespProto) + if !ok { + return nil, fmt.Errorf("unexpected response type %T", resp) + } + return concreteResp, nil +} + // OpenKVStore returns the prefixed store for the account given the context. func OpenKVStore(ctx context.Context) store.KVStore { return ctx.Value(contextKey{}).(contextValue).store diff --git a/x/accounts/internal/implementation/context_test.go b/x/accounts/internal/implementation/context_test.go index 686c299350..a82df04dee 100644 --- a/x/accounts/internal/implementation/context_test.go +++ b/x/accounts/internal/implementation/context_test.go @@ -1,9 +1,11 @@ package implementation import ( + "context" "testing" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/wrapperspb" "cosmossdk.io/collections" @@ -11,12 +13,12 @@ import ( ) func TestMakeAccountContext(t *testing.T) { - storeService, ctx := colltest.MockStore() + storeService, originalContext := colltest.MockStore() accountAddr := []byte("accountAddr") sender := []byte("sender") sb := collections.NewSchemaBuilderFromAccessor(OpenKVStore) - accountCtx := MakeAccountContext(ctx, storeService, accountAddr, sender) + accountCtx := MakeAccountContext(originalContext, storeService, accountAddr, sender, nil, nil, nil) // ensure whoami require.Equal(t, accountAddr, Whoami(accountCtx)) @@ -34,10 +36,42 @@ func TestMakeAccountContext(t *testing.T) { // we want to ensure that the account wrote in the correct prefix. // this store is the global x/accounts module store. - store := storeService.OpenKVStore(ctx) + store := storeService.OpenKVStore(originalContext) // now we want the value to be store in the following accounts prefix (accountAddr + itemPrefix) value, err := store.Get(append(accountAddr, itemPrefix...)) require.NoError(t, err) require.Equal(t, []byte{0, 0, 0, 0, 0, 0, 3, 232}, value) + + // ensure getSenderAccount blocks impersonation + accountCtx = MakeAccountContext(originalContext, storeService, []byte("impersonator"), []byte("account-invoker"), func(_ proto.Message) ([]byte, error) { + return []byte("legit-exec-module"), nil + }, nil, nil) + + _, err = ExecModule[wrapperspb.StringValue](accountCtx, &wrapperspb.UInt64Value{Value: 1000}) + require.ErrorIs(t, err, errUnauthorized) + + // ensure calling ExecModule works + accountCtx = MakeAccountContext(originalContext, storeService, []byte("legit-exec-module"), []byte("invoker"), func(_ proto.Message) ([]byte, error) { + return []byte("legit-exec-module"), nil + }, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + // ensure we unwrapped the context when invoking a module call + require.Equal(t, originalContext, ctx) + return wrapperspb.String("module exec was called"), nil + }, nil) + + resp, err := ExecModule[wrapperspb.StringValue](accountCtx, &wrapperspb.UInt64Value{Value: 1000}) + require.NoError(t, err) + require.True(t, proto.Equal(wrapperspb.String("module exec was called"), resp)) + + // ensure calling QueryModule works, also by setting everything else communication related to nil + // we can guarantee that exec paths do not impact query paths. + accountCtx = MakeAccountContext(originalContext, storeService, nil, nil, nil, nil, func(ctx context.Context, msg proto.Message) (proto.Message, error) { + require.Equal(t, originalContext, ctx) + return wrapperspb.String("module query was called"), nil + }) + + resp, err = QueryModule[wrapperspb.StringValue](accountCtx, &wrapperspb.UInt64Value{Value: 1000}) + require.NoError(t, err) + require.True(t, proto.Equal(wrapperspb.String("module query was called"), resp)) } diff --git a/x/accounts/keeper.go b/x/accounts/keeper.go index 0da45b6f7d..040fcb0042 100644 --- a/x/accounts/keeper.go +++ b/x/accounts/keeper.go @@ -7,6 +7,8 @@ import ( "errors" "fmt" + "google.golang.org/protobuf/proto" + "cosmossdk.io/collections" "cosmossdk.io/core/address" "cosmossdk.io/core/store" @@ -22,14 +24,25 @@ var ( AccountNumberKey = collections.NewPrefix(1) ) -func NewKeeper(ss store.KVStoreService, addressCodec address.Codec, accounts map[string]implementation.Account) (Keeper, error) { +func NewKeeper( + ss store.KVStoreService, + addressCodec address.Codec, + getMsgSenderFunc func(msg proto.Message) ([]byte, error), + execModuleFunc func(ctx context.Context, msg proto.Message) (proto.Message, error), + queryModuleFunc func(ctx context.Context, msg proto.Message) (proto.Message, error), + accounts map[string]implementation.Account, +) (Keeper, error) { sb := collections.NewSchemaBuilder(ss) keeper := Keeper{ - storeService: ss, - addressCodec: addressCodec, - accounts: map[string]implementation.Implementation{}, - AccountNumber: collections.NewSequence(sb, AccountNumberKey, "account_number"), - AccountsByType: collections.NewMap(sb, AccountTypeKeyPrefix, "accounts_by_type", collections.BytesKey, collections.StringValue), + storeService: ss, + addressCodec: addressCodec, + getSenderFunc: getMsgSenderFunc, + execModuleFunc: execModuleFunc, + queryModuleFunc: queryModuleFunc, + accounts: map[string]implementation.Implementation{}, + Schema: collections.Schema{}, + AccountNumber: collections.NewSequence(sb, AccountNumberKey, "account_number"), + AccountsByType: collections.NewMap(sb, AccountTypeKeyPrefix, "accounts_by_type", collections.BytesKey, collections.StringValue), } // make accounts implementation @@ -49,12 +62,15 @@ func NewKeeper(ss store.KVStoreService, addressCodec address.Codec, accounts map } type Keeper struct { - storeService store.KVStoreService + // deps coming from the runtime + storeService store.KVStoreService + addressCodec address.Codec + getSenderFunc func(msg proto.Message) ([]byte, error) + execModuleFunc func(ctx context.Context, msg proto.Message) (proto.Message, error) + queryModuleFunc func(ctx context.Context, msg proto.Message) (proto.Message, error) accounts map[string]implementation.Implementation - addressCodec address.Codec - // Schema is the schema for the module. Schema collections.Schema // AccountNumber is the last global account number. @@ -82,7 +98,7 @@ func (k Keeper) Init( } // make the context and init the account - ctx = implementation.MakeAccountContext(ctx, k.storeService, accountAddr, creator) + ctx = k.makeAccountContext(ctx, accountAddr, creator, false) resp, err := impl.Init(ctx, initRequest) if err != nil { return nil, nil, err @@ -118,7 +134,7 @@ func (k Keeper) Execute( } // make the context and execute the account state transition. - ctx = implementation.MakeAccountContext(ctx, k.storeService, accountAddr, sender) + ctx = k.makeAccountContext(ctx, accountAddr, sender, false) return impl.Execute(ctx, execRequest) } @@ -143,8 +159,8 @@ func (k Keeper) Query( return nil, err } - // make the context and execute the account state transition. - ctx = implementation.MakeAccountContext(ctx, k.storeService, accountAddr, nil) + // make the context and execute the account query + ctx = k.makeAccountContext(ctx, accountAddr, nil, true) return impl.Query(ctx, queryRequest) } @@ -166,3 +182,35 @@ func (k Keeper) makeAddress(ctx context.Context) ([]byte, error) { addr := sha256.Sum256(append([]byte("x/accounts"), binary.BigEndian.AppendUint64(nil, num)...)) return addr[:], nil } + +// makeAccountContext makes a new context for the given account. +func (k Keeper) makeAccountContext(ctx context.Context, accountAddr, sender []byte, isQuery bool) context.Context { + // if it's not a query we create a context that allows to do anything. + if !isQuery { + return implementation.MakeAccountContext( + ctx, + k.storeService, + accountAddr, + sender, + k.getSenderFunc, + k.execModuleFunc, + k.queryModuleFunc, + ) + } + + // if it's a query we create a context that does not allow to execute modules + // and does not allow to get the sender. + return implementation.MakeAccountContext( + ctx, + k.storeService, + accountAddr, + nil, + func(_ proto.Message) ([]byte, error) { + return nil, fmt.Errorf("cannot get sender from query") + }, + func(ctx context.Context, _ proto.Message) (proto.Message, error) { + return nil, fmt.Errorf("cannot execute module from query") + }, + k.queryModuleFunc, + ) +} diff --git a/x/accounts/keeper_test.go b/x/accounts/keeper_test.go index a1c0fe6a0a..0bf9f2baa3 100644 --- a/x/accounts/keeper_test.go +++ b/x/accounts/keeper_test.go @@ -5,8 +5,12 @@ import ( "testing" "github.com/stretchr/testify/require" + "google.golang.org/protobuf/proto" "google.golang.org/protobuf/types/known/emptypb" + "google.golang.org/protobuf/types/known/wrapperspb" + bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" + basev1beta1 "cosmossdk.io/api/cosmos/base/v1beta1" "cosmossdk.io/collections" "cosmossdk.io/collections/colltest" "cosmossdk.io/core/address" @@ -23,15 +27,20 @@ func (a addressCodec) BytesToString(bz []byte) (string, error) { return string func newKeeper(t *testing.T, accounts map[string]implementation.Account) (Keeper, context.Context) { t.Helper() ss, ctx := colltest.MockStore() - m, err := NewKeeper(ss, addressCodec{}, accounts) + m, err := NewKeeper(ss, addressCodec{}, nil, nil, nil, accounts) require.NoError(t, err) return m, ctx } -func TestKeeper_Create(t *testing.T) { +func TestKeeper_Init(t *testing.T) { m, ctx := newKeeper(t, map[string]implementation.Account{ "test": TestAccount{}, }) + m.queryModuleFunc = func(ctx context.Context, msg proto.Message) (proto.Message, error) { + _, ok := msg.(*bankv1beta1.QueryBalanceRequest) + require.True(t, ok) + return &bankv1beta1.QueryBalanceResponse{}, nil + } t.Run("ok", func(t *testing.T) { sender := []byte("sender") @@ -62,6 +71,9 @@ func TestKeeper_Execute(t *testing.T) { m, ctx := newKeeper(t, map[string]implementation.Account{ "test": TestAccount{}, }) + m.queryModuleFunc = func(_ context.Context, _ proto.Message) (proto.Message, error) { + return &bankv1beta1.QueryBalanceResponse{}, nil + } // create account sender := []byte("sender") @@ -78,12 +90,33 @@ func TestKeeper_Execute(t *testing.T) { _, err := m.Execute(ctx, []byte("unknown"), sender, &emptypb.Empty{}) require.ErrorIs(t, err, collections.ErrNotFound) }) + + t.Run("exec module", func(t *testing.T) { + m.execModuleFunc = func(ctx context.Context, msg proto.Message) (proto.Message, error) { + concrete, ok := msg.(*bankv1beta1.MsgSend) + require.True(t, ok) + require.Equal(t, concrete.ToAddress, "recipient") + return &bankv1beta1.MsgSendResponse{}, nil + } + + m.getSenderFunc = func(msg proto.Message) ([]byte, error) { + require.Equal(t, msg.(*bankv1beta1.MsgSend).FromAddress, string(accAddr)) + return accAddr, nil + } + + resp, err := m.Execute(ctx, accAddr, sender, &wrapperspb.Int64Value{Value: 1000}) + require.NoError(t, err) + require.True(t, proto.Equal(&emptypb.Empty{}, resp.(proto.Message))) + }) } func TestKeeper_Query(t *testing.T) { m, ctx := newKeeper(t, map[string]implementation.Account{ "test": TestAccount{}, }) + m.queryModuleFunc = func(_ context.Context, _ proto.Message) (proto.Message, error) { + return &bankv1beta1.QueryBalanceResponse{}, nil + } // create account sender := []byte("sender") @@ -100,4 +133,23 @@ func TestKeeper_Query(t *testing.T) { _, err := m.Query(ctx, []byte("unknown"), &emptypb.Empty{}) require.ErrorIs(t, err, collections.ErrNotFound) }) + + t.Run("query module", func(t *testing.T) { + // we inject the module query function, which accepts only a specific type of message + // we force the response + m.queryModuleFunc = func(ctx context.Context, msg proto.Message) (proto.Message, error) { + concrete, ok := msg.(*bankv1beta1.QueryBalanceRequest) + require.True(t, ok) + require.Equal(t, string(accAddr), concrete.Address) + require.Equal(t, concrete.Denom, "atom") + return &bankv1beta1.QueryBalanceResponse{Balance: &basev1beta1.Coin{ + Denom: "atom", + Amount: "1000", + }}, nil + } + + resp, err := m.Query(ctx, accAddr, wrapperspb.String("atom")) + require.NoError(t, err) + require.True(t, proto.Equal(wrapperspb.Int64(1000), resp.(proto.Message))) + }) } diff --git a/x/accounts/msg_server_test.go b/x/accounts/msg_server_test.go index 579d8c19a7..7c00aa5b58 100644 --- a/x/accounts/msg_server_test.go +++ b/x/accounts/msg_server_test.go @@ -1,6 +1,7 @@ package accounts import ( + "context" "testing" "github.com/stretchr/testify/require" @@ -9,6 +10,7 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/wrapperspb" + bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" "cosmossdk.io/x/accounts/internal/implementation" v1 "cosmossdk.io/x/accounts/v1" ) @@ -17,6 +19,11 @@ func TestMsgServer(t *testing.T) { k, ctx := newKeeper(t, map[string]implementation.Account{ "test": TestAccount{}, }) + k.queryModuleFunc = func(ctx context.Context, msg proto.Message) (proto.Message, error) { + _, ok := msg.(*bankv1beta1.QueryBalanceRequest) + require.True(t, ok) + return &bankv1beta1.QueryBalanceResponse{}, nil + } s := NewMsgServer(k) diff --git a/x/accounts/query_server_test.go b/x/accounts/query_server_test.go index 3acaecd0dd..c4583b87d4 100644 --- a/x/accounts/query_server_test.go +++ b/x/accounts/query_server_test.go @@ -1,6 +1,7 @@ package accounts import ( + "context" "testing" "github.com/stretchr/testify/require" @@ -9,6 +10,7 @@ import ( "google.golang.org/protobuf/types/known/emptypb" "google.golang.org/protobuf/types/known/wrapperspb" + bankv1beta1 "cosmossdk.io/api/cosmos/bank/v1beta1" "cosmossdk.io/x/accounts/internal/implementation" v1 "cosmossdk.io/x/accounts/v1" ) @@ -17,6 +19,9 @@ func TestQueryServer(t *testing.T) { k, ctx := newKeeper(t, map[string]implementation.Account{ "test": TestAccount{}, }) + k.queryModuleFunc = func(ctx context.Context, msg proto.Message) (proto.Message, error) { + return &bankv1beta1.QueryBalanceResponse{}, nil + } ms := NewMsgServer(k) qs := NewQueryServer(k)