Skip to content

Commit

Permalink
feat: add feegrant query to see allowances from a given granter (#10947)
Browse files Browse the repository at this point in the history
  • Loading branch information
cmwaters authored and julienrbrt committed May 6, 2022
1 parent 162fb49 commit 4b7fb5c
Show file tree
Hide file tree
Showing 10 changed files with 1,424 additions and 1,037 deletions.
2,058 changes: 1,027 additions & 1,031 deletions CHANGELOG.md

Large diffs are not rendered by default.

23 changes: 23 additions & 0 deletions proto/cosmos/feegrant/v1beta1/query.proto
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ service Query {
rpc Allowances(QueryAllowancesRequest) returns (QueryAllowancesResponse) {
option (google.api.http).get = "/cosmos/feegrant/v1beta1/allowances/{grantee}";
}

// AllowancesByGranter returns all the grants given by an address
// Since v0.46
rpc AllowancesByGranter(QueryAllowancesByGranterRequest) returns (QueryAllowancesByGranterResponse) {
option (google.api.http).get = "/cosmos/feegrant/v1beta1/issued/{granter}";
}
}

// QueryAllowanceRequest is the request type for the Query/Allowance RPC method.
Expand Down Expand Up @@ -53,3 +59,20 @@ message QueryAllowancesResponse {
// pagination defines an pagination for the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

// QueryAllowancesByGranterRequest is the request type for the Query/AllowancesByGranter RPC method.
message QueryAllowancesByGranterRequest {
string granter = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"];

// pagination defines an pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}

// QueryAllowancesByGranterResponse is the response type for the Query/AllowancesByGranter RPC method.
message QueryAllowancesByGranterResponse {
// allowances that have been issued by the granter.
repeated cosmos.feegrant.v1beta1.Grant allowances = 1;

// pagination defines an pagination for the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}
56 changes: 53 additions & 3 deletions x/feegrant/client/cli/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ func GetQueryCmd() *cobra.Command {

feegrantQueryCmd.AddCommand(
GetCmdQueryFeeGrant(),
GetCmdQueryFeeGrants(),
GetCmdQueryFeeGrantsByGrantee(),
GetCmdQueryFeeGrantsByGranter(),
)

return feegrantQueryCmd
Expand Down Expand Up @@ -80,8 +81,8 @@ $ %s query feegrant grant [granter] [grantee]
return cmd
}

// GetCmdQueryFeeGrants returns cmd to query for all grants for a grantee.
func GetCmdQueryFeeGrants() *cobra.Command {
// GetCmdQueryFeeGrantsByGrantee returns cmd to query for all grants for a grantee.
func GetCmdQueryFeeGrantsByGrantee() *cobra.Command {
cmd := &cobra.Command{
Use: "grants [grantee]",
Args: cobra.ExactArgs(1),
Expand Down Expand Up @@ -128,3 +129,52 @@ $ %s query feegrant grants [grantee]

return cmd
}

// GetCmdQueryFeeGrantsByGranter returns cmd to query for all grants by a granter.
func GetCmdQueryFeeGrantsByGranter() *cobra.Command {
cmd := &cobra.Command{
Use: "grants [granter]",
Args: cobra.ExactArgs(1),
Short: "Query all grants by a granter",
Long: strings.TrimSpace(
fmt.Sprintf(`Queries all the grants issued for a granter address.
Example:
$ %s query feegrant grants [granter]
`, version.AppName),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := feegrant.NewQueryClient(clientCtx)

granterAddr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}

pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}

res, err := queryClient.AllowancesByGranter(
cmd.Context(),
&feegrant.QueryAllowancesByGranterRequest{
Granter: granterAddr.String(),
Pagination: pageReq,
},
)

if err != nil {
return err
}

return clientCtx.PrintProto(res)
},
}

flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "grants")

return cmd
}
1 change: 1 addition & 0 deletions x/feegrant/client/testutil/cli_test.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
//go:build norace
// +build norace

package testutil
Expand Down
62 changes: 59 additions & 3 deletions x/feegrant/client/testutil/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ func (s *IntegrationTestSuite) TestCmdGetFeeGrant() {
}
}

func (s *IntegrationTestSuite) TestCmdGetFeeGrants() {
func (s *IntegrationTestSuite) TestCmdGetFeeGrantsByGrantee() {
val := s.network.Validators[0]
grantee := s.addedGrantee
clientCtx := val.ClientCtx
Expand All @@ -216,7 +216,7 @@ func (s *IntegrationTestSuite) TestCmdGetFeeGrants() {
true, nil, 0,
},
{
"non existed grantee",
"non existent grantee",
[]string{
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
Expand All @@ -237,7 +237,63 @@ func (s *IntegrationTestSuite) TestCmdGetFeeGrants() {
tc := tc

s.Run(tc.name, func() {
cmd := cli.GetCmdQueryFeeGrants()
cmd := cli.GetCmdQueryFeeGrantsByGrantee()
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)

if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err)
s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), tc.resp), out.String())
s.Require().Len(tc.resp.Allowances, tc.expectLength)
}
})
}
}

func (s *IntegrationTestSuite) TestCmdGetFeeGrantsByGranter() {
val := s.network.Validators[0]
granter := s.addedGranter
clientCtx := val.ClientCtx

testCases := []struct {
name string
args []string
expectErr bool
resp *feegrant.QueryAllowancesByGranterResponse
expectLength int
}{
{
"wrong grantee",
[]string{
"wrong_grantee",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, nil, 0,
},
{
"non existent grantee",
[]string{
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, &feegrant.QueryAllowancesByGranterResponse{}, 0,
},
{
"valid req",
[]string{
granter.String(),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, &feegrant.QueryAllowancesByGranterResponse{}, 1,
},
}

for _, tc := range testCases {
tc := tc

s.Run(tc.name, func() {
cmd := cli.GetCmdQueryFeeGrantsByGranter()
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)

if tc.expectErr {
Expand Down
39 changes: 39 additions & 0 deletions x/feegrant/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,3 +93,42 @@ func (q Keeper) Allowances(c context.Context, req *feegrant.QueryAllowancesReque

return &feegrant.QueryAllowancesResponse{Allowances: grants, Pagination: pageRes}, nil
}

// AllowancesByGranter queries all the allowances granted by the given granter
func (q Keeper) AllowancesByGranter(c context.Context, req *feegrant.QueryAllowancesByGranterRequest) (*feegrant.QueryAllowancesByGranterResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}

granterAddr, err := sdk.AccAddressFromBech32(req.Granter)
if err != nil {
return nil, err
}

ctx := sdk.UnwrapSDKContext(c)

var grants []*feegrant.Grant

store := ctx.KVStore(q.storeKey)
pageRes, err := query.Paginate(store, req.Pagination, func(key []byte, value []byte) error {
var grant feegrant.Grant

granter, _ := feegrant.ParseAddressesFromFeeAllowanceKey(key)
if !granter.Equals(granterAddr) {
return nil
}

if err := q.cdc.Unmarshal(value, &grant); err != nil {
return err
}

grants = append(grants, &grant)
return nil
})

if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}

return &feegrant.QueryAllowancesByGranterResponse{Allowances: grants, Pagination: pageRes}, nil
}
66 changes: 66 additions & 0 deletions x/feegrant/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,72 @@ func (suite *KeeperTestSuite) TestFeeAllowances() {
}
}

func (suite *KeeperTestSuite) TestFeeAllowancesByGranter() {
testCases := []struct {
name string
req *feegrant.QueryAllowancesByGranterRequest
expectErr bool
preRun func()
postRun func(_ *feegrant.QueryAllowancesByGranterResponse)
}{
{
"nil request",
nil,
true,
func() {},
func(*feegrant.QueryAllowancesByGranterResponse) {},
},
{
"fail: invalid grantee",
&feegrant.QueryAllowancesByGranterRequest{
Granter: "invalid_grantee",
},
true,
func() {},
func(*feegrant.QueryAllowancesByGranterResponse) {},
},
{
"no grants",
&feegrant.QueryAllowancesByGranterRequest{
Granter: suite.addrs[0].String(),
},
false,
func() {},
func(resp *feegrant.QueryAllowancesByGranterResponse) {
suite.Require().Equal(len(resp.Allowances), 0)
},
},
{
"valid query: expect single grant",
&feegrant.QueryAllowancesByGranterRequest{
Granter: suite.addrs[0].String(),
},
false,
func() {
grantFeeAllowance(suite)
},
func(resp *feegrant.QueryAllowancesByGranterResponse) {
suite.Require().Equal(len(resp.Allowances), 1)
suite.Require().Equal(resp.Allowances[0].Granter, suite.addrs[0].String())
suite.Require().Equal(resp.Allowances[0].Grantee, suite.addrs[1].String())
},
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
tc.preRun()
resp, err := suite.keeper.AllowancesByGranter(suite.ctx, tc.req)
if tc.expectErr {
suite.Require().Error(err)
} else {
suite.Require().NoError(err)
tc.postRun(resp)
}
})
}
}

func grantFeeAllowance(suite *KeeperTestSuite) {
exp := suite.sdkCtx.BlockTime().AddDate(1, 0, 0)
err := suite.app.FeeGrantKeeper.GrantAllowance(suite.sdkCtx, suite.addrs[0], suite.addrs[1], &feegrant.BasicAllowance{
Expand Down
15 changes: 15 additions & 0 deletions x/feegrant/key.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package feegrant
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/types/kv"
)

const (
Expand Down Expand Up @@ -34,3 +35,17 @@ func FeeAllowanceKey(granter sdk.AccAddress, grantee sdk.AccAddress) []byte {
func FeeAllowancePrefixByGrantee(grantee sdk.AccAddress) []byte {
return append(FeeAllowanceKeyPrefix, address.MustLengthPrefix(grantee.Bytes())...)
}

func ParseAddressesFromFeeAllowanceKey(key []byte) (granter, grantee sdk.AccAddress) {
// key is of format:
// 0x00<granteeAddressLen (1 Byte)><granteeAddress_Bytes><granterAddressLen (1 Byte)><granterAddress_Bytes><msgType_Bytes>
kv.AssertKeyAtLeastLength(key, 2)
granteeAddrLen := key[1] // remove prefix key
kv.AssertKeyAtLeastLength(key, int(2+granteeAddrLen))
grantee = sdk.AccAddress(key[2 : 2+granteeAddrLen])
granterAddrLen := int(key[2+granteeAddrLen])
kv.AssertKeyAtLeastLength(key, 3+int(granteeAddrLen+byte(granterAddrLen)))
granter = sdk.AccAddress(key[3+granterAddrLen : 3+granteeAddrLen+byte(granterAddrLen)])

return granter, grantee
}
25 changes: 25 additions & 0 deletions x/feegrant/key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package feegrant_test

import (
"testing"

"github.com/stretchr/testify/require"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/feegrant"
)

func TestMarshalAndUnmarshalFeegrantKey(t *testing.T) {
grantee, err := sdk.AccAddressFromBech32("cosmos1qk93t4j0yyzgqgt6k5qf8deh8fq6smpn3ntu3x")
require.NoError(t, err)
granter, err := sdk.AccAddressFromBech32("cosmos1p9qh4ldfd6n0qehujsal4k7g0e37kel90rc4ts")
require.NoError(t, err)

key := feegrant.FeeAllowanceKey(granter, grantee)
require.Len(t, key, len(grantee.Bytes())+len(granter.Bytes())+3)
require.Equal(t, feegrant.FeeAllowancePrefixByGrantee(grantee), key[:len(grantee.Bytes())+2])

g1, g2 := feegrant.ParseAddressesFromFeeAllowanceKey(key)
require.Equal(t, granter, g1)
require.Equal(t, grantee, g2)
}
Loading

0 comments on commit 4b7fb5c

Please sign in to comment.