From 7a3a156b4f304f3bfea7e28b815a34e54270907b Mon Sep 17 00:00:00 2001 From: atheeshp <59333759+atheeshp@users.noreply.github.com> Date: Fri, 9 Apr 2021 22:19:24 +0530 Subject: [PATCH] feegrant filtered msgs (#8604) * WIP: add filtered message * updated `Accept` interface method * fix tests * add cli tests for filtered fee allowance * fix tests * review changes * rename `filteredFeeAllowance` message * review changes * review changes * review changes * review changes * review changes * update errors * remove validation * fix conflicts * add `ctx` to `Accept` method * fix test * add gas consumption * review changes * review changes * revert * review changes * improve error handling * fix test * Merge branch 'master' of github.com:cosmos/cosmos-sdk into atheesh/feegrant-filtered-msgs * review changes * update gas * update type * review changes Co-authored-by: Alessio Treglia Co-authored-by: SaReN Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com> --- docs/core/proto-docs.md | 17 + proto/cosmos/feegrant/v1beta1/feegrant.proto | 12 + x/auth/ante/expected_keepers.go | 2 +- x/auth/ante/fee.go | 2 +- x/feegrant/client/cli/cli_test.go | 188 ++++++++++- x/feegrant/client/cli/tx.go | 22 +- x/feegrant/client/rest/grpc_query_test.go | 2 +- x/feegrant/genesis.go | 13 +- x/feegrant/keeper/grpc_query.go | 6 +- x/feegrant/keeper/keeper.go | 19 +- x/feegrant/keeper/keeper_test.go | 7 +- x/feegrant/keeper/msg_server.go | 10 +- x/feegrant/simulation/operations.go | 3 +- x/feegrant/types/basic_fee.go | 5 +- x/feegrant/types/basic_fee_test.go | 10 +- x/feegrant/types/codec.go | 1 + x/feegrant/types/errors.go | 4 + x/feegrant/types/feegrant.pb.go | 330 ++++++++++++++++--- x/feegrant/types/fees.go | 2 +- x/feegrant/types/filtered_fee.go | 123 +++++++ x/feegrant/types/genesis.go | 6 +- x/feegrant/types/grant.go | 20 +- x/feegrant/types/msgs.go | 13 +- x/feegrant/types/periodic_fee.go | 5 +- x/feegrant/types/periodic_fee_test.go | 8 +- 25 files changed, 729 insertions(+), 101 deletions(-) create mode 100644 x/feegrant/types/filtered_fee.go diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 219c8bf9d4..98817f6d06 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -307,6 +307,7 @@ - [Msg](#cosmos.evidence.v1beta1.Msg) - [cosmos/feegrant/v1beta1/feegrant.proto](#cosmos/feegrant/v1beta1/feegrant.proto) + - [AllowedMsgFeeAllowance](#cosmos.feegrant.v1beta1.AllowedMsgFeeAllowance) - [BasicFeeAllowance](#cosmos.feegrant.v1beta1.BasicFeeAllowance) - [Duration](#cosmos.feegrant.v1beta1.Duration) - [ExpiresAt](#cosmos.feegrant.v1beta1.ExpiresAt) @@ -4543,6 +4544,22 @@ Msg defines the evidence Msg service. + + +### AllowedMsgFeeAllowance +AllowedMsgFeeAllowance creates allowance only for specified message types. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `allowance` | [google.protobuf.Any](#google.protobuf.Any) | | allowance can be any of basic and filtered fee allowance. | +| `allowed_messages` | [string](#string) | repeated | allowed_messages are the messages for which the grantee has the access. | + + + + + + ### BasicFeeAllowance diff --git a/proto/cosmos/feegrant/v1beta1/feegrant.proto b/proto/cosmos/feegrant/v1beta1/feegrant.proto index 534de582d9..15382e56e3 100644 --- a/proto/cosmos/feegrant/v1beta1/feegrant.proto +++ b/proto/cosmos/feegrant/v1beta1/feegrant.proto @@ -52,6 +52,18 @@ message PeriodicFeeAllowance { ExpiresAt period_reset = 5 [(gogoproto.nullable) = false]; } +// AllowedMsgFeeAllowance creates allowance only for specified message types. +message AllowedMsgFeeAllowance { + option (gogoproto.goproto_getters) = false; + option (cosmos_proto.implements_interface) = "FeeAllowanceI"; + + // allowance can be any of basic and filtered fee allowance. + google.protobuf.Any allowance = 1 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"]; + + // allowed_messages are the messages for which the grantee has the access. + repeated string allowed_messages = 2; +} + // Duration is a span of a clock time or number of blocks. // This is designed to be added to an ExpiresAt struct. message Duration { diff --git a/x/auth/ante/expected_keepers.go b/x/auth/ante/expected_keepers.go index e9c2286f03..4dbbbd21c7 100644 --- a/x/auth/ante/expected_keepers.go +++ b/x/auth/ante/expected_keepers.go @@ -16,5 +16,5 @@ type AccountKeeper interface { // FeegrantKeeper defines the expected feegrant keeper. type FeegrantKeeper interface { - UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins) error + UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error } diff --git a/x/auth/ante/fee.go b/x/auth/ante/fee.go index 3cc6aee10c..e183887d0f 100644 --- a/x/auth/ante/fee.go +++ b/x/auth/ante/fee.go @@ -94,7 +94,7 @@ func (dfd DeductFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bo if dfd.feegrantKeeper == nil { return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee grants are not enabled") } else if !feeGranter.Equals(feePayer) { - err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee) + err := dfd.feegrantKeeper.UseGrantedFees(ctx, feeGranter, feePayer, fee, tx.GetMsgs()) if err != nil { return ctx, sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", feeGranter, feePayer) diff --git a/x/feegrant/client/cli/cli_test.go b/x/feegrant/client/cli/cli_test.go index 66827e6148..787a342d18 100644 --- a/x/feegrant/client/cli/cli_test.go +++ b/x/feegrant/client/cli/cli_test.go @@ -13,6 +13,7 @@ import ( "github.com/cosmos/cosmos-sdk/client/flags" "github.com/cosmos/cosmos-sdk/crypto/hd" "github.com/cosmos/cosmos-sdk/crypto/keyring" + "github.com/cosmos/cosmos-sdk/testutil" clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli" "github.com/cosmos/cosmos-sdk/testutil/network" sdk "github.com/cosmos/cosmos-sdk/types" @@ -40,7 +41,7 @@ func (s *IntegrationTestSuite) SetupSuite() { } cfg := network.DefaultConfig() - cfg.NumValidators = 2 + cfg.NumValidators = 3 s.cfg = cfg s.network = network.New(s.T(), cfg) @@ -137,7 +138,7 @@ func (s *IntegrationTestSuite) TestCmdGetFeeGrant() { grantee.String(), fmt.Sprintf("--%s=json", tmcli.OutputFlag), }, - "no fee allowance found", + "no allowance", true, nil, nil, }, { @@ -169,9 +170,13 @@ func (s *IntegrationTestSuite) TestCmdGetFeeGrant() { s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) s.Require().Equal(tc.respType.Grantee, tc.respType.Grantee) s.Require().Equal(tc.respType.Granter, tc.respType.Granter) + grant, err := tc.respType.GetFeeGrant() + s.Require().NoError(err) + grant1, err1 := tc.resp.GetFeeGrant() + s.Require().NoError(err1) s.Require().Equal( - tc.respType.GetFeeGrant().(*types.BasicFeeAllowance).SpendLimit, - tc.resp.GetFeeGrant().(*types.BasicFeeAllowance).SpendLimit, + grant.(*types.BasicFeeAllowance).SpendLimit, + grant1.(*types.BasicFeeAllowance).SpendLimit, ) } }) @@ -617,6 +622,181 @@ func (s *IntegrationTestSuite) TestTxWithFeeGrant() { s.Require().Equal(uint32(0), resp.Code) } +func (s *IntegrationTestSuite) TestFilteredFeeAllowance() { + val := s.network.Validators[0] + + granter := val.Address + info, _, err := val.ClientCtx.Keyring.NewMnemonic("grantee1", keyring.English, sdk.FullFundraiserPath, keyring.DefaultBIP39Passphrase, hd.Secp256k1) + s.Require().NoError(err) + grantee := sdk.AccAddress(info.GetPubKey().Address()) + + clientCtx := val.ClientCtx + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + spendLimit := sdk.NewCoin("stake", sdk.NewInt(1000)) + + allowMsgs := "/cosmos.gov.v1beta1.Msg/SubmitProposal" + + testCases := []struct { + name string + args []string + expectErr bool + respType proto.Message + expectedCode uint32 + }{ + { + "wrong granter", + append( + []string{ + "wrong granter", + "cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl", + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, &sdk.TxResponse{}, 0, + }, + { + "wrong grantee", + append( + []string{ + granter.String(), + "wrong grantee", + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, &sdk.TxResponse{}, 0, + }, + { + "valid filter fee grant", + append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, &sdk.TxResponse{}, 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.NewCmdFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } + + args := []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + } + + // get filtered fee allowance and check info + cmd := cli.GetCmdQueryFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + + resp := &types.FeeAllowanceGrant{} + + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), resp), out.String()) + s.Require().Equal(resp.Grantee, resp.Grantee) + s.Require().Equal(resp.Granter, resp.Granter) + + grant, err := resp.GetFeeGrant() + s.Require().NoError(err) + + filteredFeeGrant, err := grant.(*types.AllowedMsgFeeAllowance).GetAllowance() + s.Require().NoError(err) + + s.Require().Equal( + filteredFeeGrant.(*types.BasicFeeAllowance).SpendLimit.String(), + spendLimit.String(), + ) + + // exec filtered fee allowance + cases := []struct { + name string + malleate func() (testutil.BufferWriter, error) + expectErr bool + respType proto.Message + expectedCode uint32 + }{ + { + "valid tx", + func() (testutil.BufferWriter, error) { + return govtestutil.MsgSubmitProposal(val.ClientCtx, grantee.String(), + "Text Proposal", "No desc", govtypes.ProposalTypeText, + fmt.Sprintf("--%s=%s", flags.FlagFeeAccount, granter.String()), + ) + }, + false, + &sdk.TxResponse{}, + 0, + }, + { + "should fail with unauthorized msgs", + func() (testutil.BufferWriter, error) { + args := append( + []string{ + grantee.String(), + "cosmos14cm33pvnrv2497tyt8sp9yavhmw83nwej3m0e8", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFeeAccount, granter), + }, + commonFlags..., + ) + cmd := cli.NewCmdFeeGrant() + return clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + }, + false, &sdk.TxResponse{}, 7, + }, + } + + for _, tc := range cases { + tc := tc + + s.Run(tc.name, func() { + out, err := tc.malleate() + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + func TestIntegrationTestSuite(t *testing.T) { suite.Run(t, new(IntegrationTestSuite)) } diff --git a/x/feegrant/client/cli/tx.go b/x/feegrant/client/cli/tx.go index 49702e0c78..c355ca4b5a 100644 --- a/x/feegrant/client/cli/tx.go +++ b/x/feegrant/client/cli/tx.go @@ -22,6 +22,7 @@ const ( FlagPeriod = "period" FlagPeriodLimit = "period-limit" FlagSpendLimit = "spend-limit" + FlagAllowedMsgs = "allowed-messages" ) // GetTxCmd returns the transaction commands for this module @@ -55,8 +56,10 @@ func NewCmdFeeGrant() *cobra.Command { Examples: %s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --expiration 36000 or -%s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --period 3600 --period-limit 10stake --expiration 36000 - `, version.AppName, types.ModuleName, version.AppName, types.ModuleName, +%s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --period 3600 --period-limit 10stake --expiration 36000 or +%s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --expiration 36000 + --allowed-messages "/cosmos.gov.v1beta1.Msg/SubmitProposal,/cosmos.gov.v1beta1.Msg/Vote" + `, version.AppName, types.ModuleName, version.AppName, types.ModuleName, version.AppName, types.ModuleName, ), ), Args: cobra.ExactArgs(2), @@ -143,6 +146,18 @@ Examples: } } + allowedMsgs, err := cmd.Flags().GetStringSlice(FlagAllowedMsgs) + if err != nil { + return err + } + + if len(allowedMsgs) > 0 { + grant, err = types.NewAllowedMsgFeeAllowance(grant, allowedMsgs) + if err != nil { + return err + } + } + msg, err := types.NewMsgGrantFeeAllowance(grant, granter, grantee) if err != nil { return err @@ -160,10 +175,11 @@ Examples: } flags.AddTxFlagsToCmd(cmd) + cmd.Flags().StringSlice(FlagAllowedMsgs, []string{}, "Set of allowed messages for fee allowance") cmd.Flags().Int64(FlagExpiration, 0, "The second unit of time duration which the grant is active for the user") cmd.Flags().String(FlagSpendLimit, "", "Spend limit specifies the max limit can be used, if not mentioned there is no limit") cmd.Flags().Int64(FlagPeriod, 0, "period specifies the time duration in which period_spend_limit coins can be spent before that allowance is reset") - cmd.Flags().String(FlagPeriodLimit, "", "// period limit specifies the maximum number of coins that can be spent in the period") + cmd.Flags().String(FlagPeriodLimit, "", "period limit specifies the maximum number of coins that can be spent in the period") return cmd } diff --git a/x/feegrant/client/rest/grpc_query_test.go b/x/feegrant/client/rest/grpc_query_test.go index 5dac57a8fb..a8e83716af 100644 --- a/x/feegrant/client/rest/grpc_query_test.go +++ b/x/feegrant/client/rest/grpc_query_test.go @@ -92,7 +92,7 @@ func (s *IntegrationTestSuite) TestQueryFeeAllowance() { "fail: no grants", fmt.Sprintf("%s/cosmos/feegrant/v1beta1/fee_allowance/%s/%s", baseURL, val.Address.String(), s.grantee.String()), true, - "no fee allowance found", + "no allowance", func() {}, func(types.QueryFeeAllowanceResponse) {}, }, diff --git a/x/feegrant/genesis.go b/x/feegrant/genesis.go index f65cfdb578..5fd23608fc 100644 --- a/x/feegrant/genesis.go +++ b/x/feegrant/genesis.go @@ -12,7 +12,11 @@ type GenesisState []types.FeeAllowanceGrant // ValidateBasic ensures all grants in the genesis state are valid func (g GenesisState) ValidateBasic() error { for _, f := range g { - err := f.GetFeeGrant().ValidateBasic() + grant, err := f.GetFeeGrant() + if err != nil { + return err + } + err = grant.ValidateBasic() if err != nil { return err } @@ -32,7 +36,12 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, data *types.GenesisState) { panic(err) } - err = k.GrantFeeAllowance(ctx, granter, grantee, f.GetFeeGrant()) + grant, err := f.GetFeeGrant() + if err != nil { + panic(err) + } + + err = k.GrantFeeAllowance(ctx, granter, grantee, grant) if err != nil { panic(err) } diff --git a/x/feegrant/keeper/grpc_query.go b/x/feegrant/keeper/grpc_query.go index 0fce084f90..d8e6c49138 100644 --- a/x/feegrant/keeper/grpc_query.go +++ b/x/feegrant/keeper/grpc_query.go @@ -34,9 +34,9 @@ func (q Keeper) FeeAllowance(c context.Context, req *types.QueryFeeAllowanceRequ ctx := sdk.UnwrapSDKContext(c) - feeAllowance := q.GetFeeAllowance(ctx, granterAddr, granteeAddr) - if feeAllowance == nil { - return nil, status.Errorf(codes.NotFound, "no fee allowance found") + feeAllowance, err := q.GetFeeAllowance(ctx, granterAddr, granteeAddr) + if err != nil { + return nil, status.Errorf(codes.Internal, err.Error()) } msg, ok := feeAllowance.(proto.Message) diff --git a/x/feegrant/keeper/keeper.go b/x/feegrant/keeper/keeper.go index b8f30194ff..7abb87bd57 100644 --- a/x/feegrant/keeper/keeper.go +++ b/x/feegrant/keeper/keeper.go @@ -95,10 +95,10 @@ func (k Keeper) RevokeFeeAllowance(ctx sdk.Context, granter, grantee sdk.AccAddr // GetFeeAllowance returns the allowance between the granter and grantee. // If there is none, it returns nil, nil. // Returns an error on parsing issues -func (k Keeper) GetFeeAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress) types.FeeAllowanceI { +func (k Keeper) GetFeeAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress) (types.FeeAllowanceI, error) { grant, found := k.GetFeeGrant(ctx, granter, grantee) if !found { - return nil + return nil, sdkerrors.Wrapf(types.ErrNoAllowance, "grant missing") } return grant.GetFeeGrant() @@ -161,13 +161,18 @@ func (k Keeper) IterateAllFeeAllowances(ctx sdk.Context, cb func(types.FeeAllowa } // UseGrantedFees will try to pay the given fee from the granter's account as requested by the grantee -func (k Keeper) UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins) error { - grant, found := k.GetFeeGrant(ctx, granter, grantee) - if !found || grant.GetFeeGrant() == nil { +func (k Keeper) UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error { + f, found := k.GetFeeGrant(ctx, granter, grantee) + if !found { return sdkerrors.Wrapf(types.ErrNoAllowance, "grant missing") } - remove, err := grant.GetFeeGrant().Accept(fee, ctx.BlockTime(), ctx.BlockHeight()) + grant, err := f.GetFeeGrant() + if err != nil { + return err + } + + remove, err := grant.Accept(ctx, fee, msgs) if err == nil { ctx.EventManager().EmitEvent( sdk.NewEvent( @@ -189,5 +194,5 @@ func (k Keeper) UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, } // if we accepted, store the updated state of the allowance - return k.GrantFeeAllowance(ctx, granter, grantee, grant.GetFeeGrant()) + return k.GrantFeeAllowance(ctx, granter, grantee, grant) } diff --git a/x/feegrant/keeper/keeper_test.go b/x/feegrant/keeper/keeper_test.go index c14e141d31..b9a1f57019 100644 --- a/x/feegrant/keeper/keeper_test.go +++ b/x/feegrant/keeper/keeper_test.go @@ -109,7 +109,8 @@ func (suite *KeeperTestSuite) TestKeeperCrud() { for name, tc := range cases { tc := tc suite.Run(name, func() { - allow := k.GetFeeAllowance(ctx, tc.granter, tc.grantee) + allow, _ := k.GetFeeAllowance(ctx, tc.granter, tc.grantee) + if tc.allowance == nil { suite.Nil(allow) return @@ -242,14 +243,14 @@ func (suite *KeeperTestSuite) TestUseGrantedFee() { err = k.GrantFeeAllowance(ctx, suite.addrs[0], suite.addrs[3], expired) suite.Require().NoError(err) - err = k.UseGrantedFees(ctx, tc.granter, tc.grantee, tc.fee) + err = k.UseGrantedFees(ctx, tc.granter, tc.grantee, tc.fee, []sdk.Msg{}) if tc.allowed { suite.NoError(err) } else { suite.Error(err) } - loaded := k.GetFeeAllowance(ctx, tc.granter, tc.grantee) + loaded, _ := k.GetFeeAllowance(ctx, tc.granter, tc.grantee) suite.Equal(tc.final, loaded) }) diff --git a/x/feegrant/keeper/msg_server.go b/x/feegrant/keeper/msg_server.go index 3cd96e79ba..881e0f8b3f 100644 --- a/x/feegrant/keeper/msg_server.go +++ b/x/feegrant/keeper/msg_server.go @@ -37,12 +37,16 @@ func (k msgServer) GrantFeeAllowance(goCtx context.Context, msg *types.MsgGrantF } // Checking for duplicate entry - f := k.Keeper.GetFeeAllowance(ctx, granter, grantee) - if f != nil { + if f, _ := k.Keeper.GetFeeAllowance(ctx, granter, grantee); f != nil { return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee allowance already exists") } - err = k.Keeper.GrantFeeAllowance(ctx, granter, grantee, msg.GetFeeAllowanceI()) + allowance, err := msg.GetFeeAllowanceI() + if err != nil { + return nil, err + } + + err = k.Keeper.GrantFeeAllowance(ctx, granter, grantee, allowance) if err != nil { return nil, err } diff --git a/x/feegrant/simulation/operations.go b/x/feegrant/simulation/operations.go index 54821873b6..6e53738b2d 100644 --- a/x/feegrant/simulation/operations.go +++ b/x/feegrant/simulation/operations.go @@ -71,8 +71,7 @@ func SimulateMsgGrantFeeAllowance(ak types.AccountKeeper, bk types.BankKeeper, k return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgGrantFeeAllowance, "grantee and granter cannot be same"), nil, nil } - f := k.GetFeeAllowance(ctx, granter.Address, grantee.Address) - if f != nil { + if f, _ := k.GetFeeAllowance(ctx, granter.Address, grantee.Address); f != nil { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgGrantFeeAllowance, "fee allowance exists"), nil, nil } diff --git a/x/feegrant/types/basic_fee.go b/x/feegrant/types/basic_fee.go index 6d97068051..00054cb04b 100644 --- a/x/feegrant/types/basic_fee.go +++ b/x/feegrant/types/basic_fee.go @@ -19,7 +19,10 @@ var _ FeeAllowanceI = (*BasicFeeAllowance)(nil) // // If remove is true (regardless of the error), the FeeAllowance will be deleted from storage // (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees) -func (a *BasicFeeAllowance) Accept(fee sdk.Coins, blockTime time.Time, blockHeight int64) (bool, error) { +func (a *BasicFeeAllowance) Accept(ctx sdk.Context, fee sdk.Coins, _ []sdk.Msg) (bool, error) { + blockTime := ctx.BlockTime() + blockHeight := ctx.BlockHeight() + if a.Expiration.IsExpired(&blockTime, blockHeight) { return true, sdkerrors.Wrap(ErrFeeLimitExpired, "basic allowance") } diff --git a/x/feegrant/types/basic_fee_test.go b/x/feegrant/types/basic_fee_test.go index dd3f09590f..25a5eb755c 100644 --- a/x/feegrant/types/basic_fee_test.go +++ b/x/feegrant/types/basic_fee_test.go @@ -2,16 +2,19 @@ package types_test import ( "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/feegrant/types" ) func TestBasicFeeValidAllow(t *testing.T) { + app := simapp.Setup(false) + eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 10)) atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555)) smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 43)) @@ -22,7 +25,6 @@ func TestBasicFeeValidAllow(t *testing.T) { allow *types.BasicFeeAllowance // all other checks are ignored if valid=false fee sdk.Coins - blockTime time.Time blockHeight int64 valid bool accept bool @@ -124,8 +126,10 @@ func TestBasicFeeValidAllow(t *testing.T) { } require.NoError(t, err) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}).WithBlockHeight(tc.blockHeight) + // now try to deduct - remove, err := tc.allow.Accept(tc.fee, tc.blockTime, tc.blockHeight) + remove, err := tc.allow.Accept(ctx, tc.fee, []sdk.Msg{}) if !tc.accept { require.Error(t, err) return diff --git a/x/feegrant/types/codec.go b/x/feegrant/types/codec.go index 686187cd75..5557d96ac3 100644 --- a/x/feegrant/types/codec.go +++ b/x/feegrant/types/codec.go @@ -18,6 +18,7 @@ func RegisterInterfaces(registry types.InterfaceRegistry) { (*FeeAllowanceI)(nil), &BasicFeeAllowance{}, &PeriodicFeeAllowance{}, + &AllowedMsgFeeAllowance{}, ) msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) diff --git a/x/feegrant/types/errors.go b/x/feegrant/types/errors.go index 183f681ca5..d3234f8cd2 100644 --- a/x/feegrant/types/errors.go +++ b/x/feegrant/types/errors.go @@ -18,4 +18,8 @@ var ( ErrInvalidDuration = sdkerrors.Register(DefaultCodespace, 4, "invalid duration") // ErrNoAllowance error if there is no allowance for that pair ErrNoAllowance = sdkerrors.Register(DefaultCodespace, 5, "no allowance") + // ErrNoMessages error if there is no message + ErrNoMessages = sdkerrors.Register(DefaultCodespace, 6, "allowed messages are empty") + // ErrMessageNotAllowed error if message is not allowed + ErrMessageNotAllowed = sdkerrors.Register(DefaultCodespace, 7, "message not allowed") ) diff --git a/x/feegrant/types/feegrant.pb.go b/x/feegrant/types/feegrant.pb.go index 28f53e140d..f28e47d7ff 100644 --- a/x/feegrant/types/feegrant.pb.go +++ b/x/feegrant/types/feegrant.pb.go @@ -177,6 +177,47 @@ func (m *PeriodicFeeAllowance) GetPeriodReset() ExpiresAt { return ExpiresAt{} } +// AllowedMsgFeeAllowance creates allowance only for specified message types. +type AllowedMsgFeeAllowance struct { + // allowance can be any of basic and filtered fee allowance. + Allowance *types1.Any `protobuf:"bytes,1,opt,name=allowance,proto3" json:"allowance,omitempty"` + // allowed_messages are the messages for which the grantee has the access. + AllowedMessages []string `protobuf:"bytes,2,rep,name=allowed_messages,json=allowedMessages,proto3" json:"allowed_messages,omitempty"` +} + +func (m *AllowedMsgFeeAllowance) Reset() { *m = AllowedMsgFeeAllowance{} } +func (m *AllowedMsgFeeAllowance) String() string { return proto.CompactTextString(m) } +func (*AllowedMsgFeeAllowance) ProtoMessage() {} +func (*AllowedMsgFeeAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_7279582900c30aea, []int{2} +} +func (m *AllowedMsgFeeAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AllowedMsgFeeAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AllowedMsgFeeAllowance.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *AllowedMsgFeeAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_AllowedMsgFeeAllowance.Merge(m, src) +} +func (m *AllowedMsgFeeAllowance) XXX_Size() int { + return m.Size() +} +func (m *AllowedMsgFeeAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_AllowedMsgFeeAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_AllowedMsgFeeAllowance proto.InternalMessageInfo + // Duration is a span of a clock time or number of blocks. // This is designed to be added to an ExpiresAt struct. type Duration struct { @@ -192,7 +233,7 @@ func (m *Duration) Reset() { *m = Duration{} } func (m *Duration) String() string { return proto.CompactTextString(m) } func (*Duration) ProtoMessage() {} func (*Duration) Descriptor() ([]byte, []int) { - return fileDescriptor_7279582900c30aea, []int{2} + return fileDescriptor_7279582900c30aea, []int{3} } func (m *Duration) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -281,7 +322,7 @@ func (m *ExpiresAt) Reset() { *m = ExpiresAt{} } func (m *ExpiresAt) String() string { return proto.CompactTextString(m) } func (*ExpiresAt) ProtoMessage() {} func (*ExpiresAt) Descriptor() ([]byte, []int) { - return fileDescriptor_7279582900c30aea, []int{3} + return fileDescriptor_7279582900c30aea, []int{4} } func (m *ExpiresAt) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -366,7 +407,7 @@ func (m *FeeAllowanceGrant) Reset() { *m = FeeAllowanceGrant{} } func (m *FeeAllowanceGrant) String() string { return proto.CompactTextString(m) } func (*FeeAllowanceGrant) ProtoMessage() {} func (*FeeAllowanceGrant) Descriptor() ([]byte, []int) { - return fileDescriptor_7279582900c30aea, []int{4} + return fileDescriptor_7279582900c30aea, []int{5} } func (m *FeeAllowanceGrant) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -419,6 +460,7 @@ func (m *FeeAllowanceGrant) GetAllowance() *types1.Any { func init() { proto.RegisterType((*BasicFeeAllowance)(nil), "cosmos.feegrant.v1beta1.BasicFeeAllowance") proto.RegisterType((*PeriodicFeeAllowance)(nil), "cosmos.feegrant.v1beta1.PeriodicFeeAllowance") + proto.RegisterType((*AllowedMsgFeeAllowance)(nil), "cosmos.feegrant.v1beta1.AllowedMsgFeeAllowance") proto.RegisterType((*Duration)(nil), "cosmos.feegrant.v1beta1.Duration") proto.RegisterType((*ExpiresAt)(nil), "cosmos.feegrant.v1beta1.ExpiresAt") proto.RegisterType((*FeeAllowanceGrant)(nil), "cosmos.feegrant.v1beta1.FeeAllowanceGrant") @@ -429,45 +471,48 @@ func init() { } var fileDescriptor_7279582900c30aea = []byte{ - // 599 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x94, 0x4f, 0x6e, 0xd3, 0x40, - 0x14, 0xc6, 0x6d, 0x9c, 0x96, 0x76, 0x02, 0x88, 0x8c, 0x22, 0xe1, 0x64, 0xe1, 0x94, 0x2c, 0x50, - 0x84, 0x14, 0x9b, 0x16, 0x89, 0x05, 0x12, 0x42, 0x71, 0x69, 0x1b, 0x04, 0x0b, 0x64, 0x58, 0xb1, - 0x89, 0x6c, 0x67, 0xea, 0x0c, 0xb5, 0x3d, 0x96, 0x67, 0x02, 0xcd, 0x25, 0x50, 0x97, 0x9c, 0x81, - 0x35, 0x87, 0xa8, 0x58, 0x55, 0xac, 0x58, 0xb5, 0x28, 0x39, 0x01, 0x37, 0x40, 0xf3, 0xc7, 0x4e, - 0x94, 0x10, 0x24, 0xa4, 0xae, 0xe2, 0x99, 0x79, 0xef, 0xfb, 0xbd, 0xf7, 0xbd, 0x99, 0x80, 0x07, - 0x21, 0xa1, 0x09, 0xa1, 0xce, 0x31, 0x42, 0x51, 0xee, 0xa7, 0xcc, 0xf9, 0xb8, 0x1b, 0x20, 0xe6, - 0xef, 0x96, 0x1b, 0x76, 0x96, 0x13, 0x46, 0xe0, 0x3d, 0x19, 0x67, 0x97, 0xdb, 0x2a, 0xae, 0x59, - 0x8f, 0x48, 0x44, 0x44, 0x8c, 0xc3, 0xbf, 0x64, 0x78, 0xb3, 0x11, 0x11, 0x12, 0xc5, 0xc8, 0x11, - 0xab, 0x60, 0x7c, 0xec, 0xf8, 0xe9, 0xa4, 0x38, 0x92, 0x4a, 0x03, 0x99, 0xa3, 0x64, 0xe5, 0x91, - 0xa5, 0x8a, 0x09, 0x7c, 0x8a, 0xca, 0x42, 0x42, 0x82, 0x53, 0x75, 0xde, 0x5a, 0x56, 0x65, 0x38, - 0x41, 0x94, 0xf9, 0x49, 0x56, 0x08, 0x2c, 0x07, 0x0c, 0xc7, 0xb9, 0xcf, 0x30, 0x51, 0x02, 0xed, - 0x4b, 0x1d, 0xd4, 0x5c, 0x9f, 0xe2, 0xf0, 0x10, 0xa1, 0x5e, 0x1c, 0x93, 0x4f, 0x7e, 0x1a, 0x22, - 0x18, 0x83, 0x2a, 0xcd, 0x50, 0x3a, 0x1c, 0xc4, 0x38, 0xc1, 0xcc, 0xd4, 0x77, 0x8c, 0x4e, 0x75, - 0xaf, 0x61, 0xab, 0xd2, 0x78, 0x31, 0x45, 0xb7, 0xf6, 0x3e, 0xc1, 0xa9, 0xfb, 0xe8, 0xfc, 0xb2, - 0xa5, 0x7d, 0xbd, 0x6a, 0x75, 0x22, 0xcc, 0x46, 0xe3, 0xc0, 0x0e, 0x49, 0xa2, 0xfa, 0x50, 0x3f, - 0x5d, 0x3a, 0x3c, 0x71, 0xd8, 0x24, 0x43, 0x54, 0x24, 0x50, 0x0f, 0x08, 0xfd, 0xd7, 0x5c, 0x1e, - 0xf6, 0x01, 0x40, 0xa7, 0x19, 0x96, 0x75, 0x99, 0x37, 0x76, 0xf4, 0x4e, 0x75, 0xaf, 0x6d, 0xaf, - 0xb1, 0xd7, 0x3e, 0xe0, 0xa1, 0x88, 0xf6, 0x98, 0x5b, 0xe1, 0x54, 0x6f, 0x21, 0xf7, 0x69, 0xed, - 0xc7, 0xb7, 0xee, 0xed, 0xc5, 0x4e, 0x5e, 0xb6, 0x7f, 0x1b, 0xa0, 0xfe, 0x06, 0xe5, 0x98, 0x0c, - 0x97, 0x7a, 0x3c, 0x04, 0x1b, 0x01, 0x6f, 0xdc, 0xd4, 0x05, 0xf0, 0xe1, 0x5a, 0xe0, 0x8a, 0x3d, - 0x0a, 0x2c, 0xd3, 0xe1, 0x73, 0xb0, 0x99, 0x09, 0x7d, 0x55, 0xf9, 0xfd, 0xb5, 0x42, 0x2f, 0x94, - 0xf5, 0x2a, 0x5f, 0xa5, 0xc1, 0x09, 0x80, 0xf2, 0x6b, 0xb0, 0xe8, 0xb9, 0x71, 0xfd, 0x9e, 0xdf, - 0x95, 0x98, 0xb7, 0x73, 0xe7, 0xc7, 0x40, 0xed, 0x0d, 0x42, 0x3f, 0x95, 0x78, 0xb3, 0x72, 0xfd, - 0xe0, 0x3b, 0x12, 0xb2, 0xef, 0xa7, 0x82, 0x0d, 0x5f, 0x81, 0x5b, 0x0a, 0x9b, 0x23, 0x8a, 0x98, - 0xb9, 0xf1, 0x9f, 0x23, 0xaf, 0xca, 0x6c, 0x8f, 0x27, 0xff, 0x6d, 0xe6, 0x1f, 0xc0, 0x56, 0xe1, - 0x35, 0x7c, 0x06, 0xb6, 0x8a, 0x2b, 0xaf, 0x26, 0xdd, 0xb0, 0xe5, 0x9b, 0xb0, 0x8b, 0x37, 0xb1, - 0x30, 0x98, 0x2f, 0x57, 0x2d, 0xbd, 0xaf, 0x79, 0x65, 0x0a, 0x34, 0xc1, 0x66, 0x10, 0x93, 0xf0, - 0x84, 0x8a, 0xe9, 0x56, 0xfa, 0x9a, 0xa7, 0xd6, 0xee, 0x06, 0x30, 0xe8, 0x38, 0x69, 0x0f, 0xc1, - 0x76, 0x59, 0x1e, 0x7c, 0x02, 0x2a, 0xfc, 0x01, 0x2a, 0x50, 0x73, 0x05, 0xf4, 0xae, 0x78, 0x9d, - 0x6e, 0xe5, 0x4c, 0x92, 0x44, 0x3c, 0xa7, 0x8c, 0x10, 0x8e, 0x46, 0x4c, 0x50, 0x0c, 0x4e, 0x91, - 0xeb, 0x82, 0xf2, 0x59, 0x07, 0xb5, 0xc5, 0x1e, 0x8f, 0xb8, 0x3f, 0xd0, 0x04, 0x37, 0x85, 0x51, - 0x28, 0x17, 0xc4, 0x6d, 0xaf, 0x58, 0xce, 0x4f, 0x90, 0x50, 0x2c, 0x4f, 0x10, 0x3c, 0x00, 0xdb, - 0x7e, 0xa1, 0x62, 0x1a, 0xa2, 0xce, 0xfa, 0x4a, 0x9d, 0xbd, 0x74, 0xe2, 0xd6, 0xbe, 0x2f, 0xfb, - 0xea, 0xcd, 0x33, 0xdd, 0xa3, 0xf3, 0xa9, 0xa5, 0x5f, 0x4c, 0x2d, 0xfd, 0xd7, 0xd4, 0xd2, 0xcf, - 0x66, 0x96, 0x76, 0x31, 0xb3, 0xb4, 0x9f, 0x33, 0x4b, 0x7b, 0xdf, 0xfd, 0xe7, 0xb5, 0x38, 0x9d, - 0xff, 0xaf, 0x8a, 0x1b, 0x12, 0x6c, 0x0a, 0xe8, 0xe3, 0x3f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x74, - 0x27, 0xe6, 0x00, 0x77, 0x05, 0x00, 0x00, + // 650 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x94, 0x4d, 0x6f, 0xd3, 0x30, + 0x18, 0xc7, 0x93, 0xa5, 0x1b, 0xab, 0xcb, 0xcb, 0x6a, 0x0d, 0xc8, 0x76, 0x48, 0x47, 0x0f, 0xa8, + 0x20, 0x2d, 0x61, 0x43, 0xe2, 0x30, 0x09, 0xa1, 0x65, 0xec, 0x05, 0xc1, 0x24, 0x14, 0x38, 0x71, + 0xa9, 0x9c, 0xc4, 0x4b, 0xc3, 0x92, 0x38, 0x8a, 0x5d, 0x58, 0xbf, 0x01, 0x27, 0xb4, 0x23, 0x47, + 0xb8, 0x72, 0xe6, 0x43, 0x4c, 0x9c, 0x26, 0x4e, 0x9c, 0x36, 0xd4, 0x7e, 0x02, 0xbe, 0x01, 0x8a, + 0xed, 0xa4, 0xa5, 0xa5, 0x48, 0xa0, 0x9d, 0x92, 0xc7, 0x7e, 0x9e, 0xff, 0xef, 0x79, 0xb1, 0x0d, + 0x6e, 0x7b, 0x84, 0xc6, 0x84, 0x5a, 0x07, 0x18, 0x07, 0x19, 0x4a, 0x98, 0xf5, 0x66, 0xcd, 0xc5, + 0x0c, 0xad, 0x95, 0x0b, 0x66, 0x9a, 0x11, 0x46, 0xe0, 0x4d, 0xe1, 0x67, 0x96, 0xcb, 0xd2, 0x6f, + 0x79, 0x31, 0x20, 0x01, 0xe1, 0x3e, 0x56, 0xfe, 0x27, 0xdc, 0x97, 0x97, 0x02, 0x42, 0x82, 0x08, + 0x5b, 0xdc, 0x72, 0xbb, 0x07, 0x16, 0x4a, 0x7a, 0xc5, 0x96, 0x50, 0x6a, 0x8b, 0x18, 0x29, 0x2b, + 0xb6, 0x0c, 0x99, 0x8c, 0x8b, 0x28, 0x2e, 0x13, 0xf1, 0x48, 0x98, 0xc8, 0xfd, 0xc6, 0xb8, 0x2a, + 0x0b, 0x63, 0x4c, 0x19, 0x8a, 0xd3, 0x42, 0x60, 0xdc, 0xc1, 0xef, 0x66, 0x88, 0x85, 0x44, 0x0a, + 0x34, 0xcf, 0x54, 0x50, 0xb7, 0x11, 0x0d, 0xbd, 0x1d, 0x8c, 0x37, 0xa3, 0x88, 0xbc, 0x45, 0x89, + 0x87, 0x61, 0x04, 0x6a, 0x34, 0xc5, 0x89, 0xdf, 0x8e, 0xc2, 0x38, 0x64, 0xba, 0xba, 0xa2, 0xb5, + 0x6a, 0xeb, 0x4b, 0xa6, 0x4c, 0x2d, 0x4f, 0xa6, 0xa8, 0xd6, 0xdc, 0x22, 0x61, 0x62, 0xdf, 0x3b, + 0x39, 0x6b, 0x28, 0x9f, 0xcf, 0x1b, 0xad, 0x20, 0x64, 0x9d, 0xae, 0x6b, 0x7a, 0x24, 0x96, 0x75, + 0xc8, 0xcf, 0x2a, 0xf5, 0x0f, 0x2d, 0xd6, 0x4b, 0x31, 0xe5, 0x01, 0xd4, 0x01, 0x5c, 0xff, 0x59, + 0x2e, 0x0f, 0xf7, 0x00, 0xc0, 0x47, 0x69, 0x28, 0xf2, 0xd2, 0x67, 0x56, 0xd4, 0x56, 0x6d, 0xbd, + 0x69, 0x4e, 0x69, 0xaf, 0xb9, 0x9d, 0xbb, 0x62, 0xba, 0xc9, 0xec, 0x4a, 0x4e, 0x75, 0x46, 0x62, + 0x37, 0xea, 0xdf, 0xbe, 0xac, 0x5e, 0x19, 0xad, 0xe4, 0x49, 0xf3, 0xa7, 0x06, 0x16, 0x9f, 0xe3, + 0x2c, 0x24, 0xfe, 0x58, 0x8d, 0x3b, 0x60, 0xd6, 0xcd, 0x0b, 0xd7, 0x55, 0x0e, 0xbc, 0x3b, 0x15, + 0x38, 0xd1, 0x1e, 0x09, 0x16, 0xe1, 0xf0, 0x11, 0x98, 0x4b, 0xb9, 0xbe, 0xcc, 0xfc, 0xd6, 0x54, + 0xa1, 0xc7, 0xb2, 0xf5, 0x32, 0x5e, 0x86, 0xc1, 0x1e, 0x80, 0xe2, 0xaf, 0x3d, 0xda, 0x73, 0xed, + 0xe2, 0x7b, 0xbe, 0x20, 0x30, 0x2f, 0x86, 0x9d, 0xef, 0x02, 0xb9, 0xd6, 0xf6, 0x50, 0x22, 0xf0, + 0x7a, 0xe5, 0xe2, 0xc1, 0x57, 0x05, 0x64, 0x0b, 0x25, 0x9c, 0x0d, 0x9f, 0x82, 0xcb, 0x12, 0x9b, + 0x61, 0x8a, 0x99, 0x3e, 0xfb, 0x8f, 0x23, 0xaf, 0x89, 0x68, 0x27, 0x0f, 0xfe, 0xd3, 0xcc, 0x3f, + 0xa9, 0xe0, 0x06, 0x37, 0xb1, 0xbf, 0x4f, 0x83, 0xdf, 0xa6, 0xbe, 0x0d, 0xaa, 0xa8, 0x30, 0xe4, + 0xe4, 0x17, 0x4d, 0x71, 0x47, 0xcc, 0xe2, 0x8e, 0x98, 0x9b, 0x49, 0xcf, 0xae, 0x7f, 0x1d, 0x97, + 0x75, 0x86, 0x91, 0xf0, 0x0e, 0x58, 0x40, 0x02, 0xd0, 0x8e, 0x31, 0xa5, 0x28, 0xc0, 0x54, 0x9f, + 0x59, 0xd1, 0x5a, 0x55, 0xe7, 0x9a, 0x5c, 0xdf, 0x97, 0xcb, 0x1b, 0xd7, 0xdf, 0x7d, 0x6c, 0x28, + 0x93, 0x39, 0xbe, 0x06, 0xf3, 0xc5, 0x79, 0x80, 0x0f, 0xc1, 0x7c, 0x71, 0x2d, 0x65, 0x4e, 0x4b, + 0x13, 0x39, 0x0d, 0x0f, 0xcf, 0x87, 0xf3, 0x86, 0xba, 0xa7, 0x38, 0x65, 0x08, 0xd4, 0xc1, 0x9c, + 0x1b, 0x11, 0xef, 0x90, 0xf2, 0x13, 0x58, 0xd9, 0x53, 0x1c, 0x69, 0xdb, 0xb3, 0x40, 0xa3, 0xdd, + 0xb8, 0xe9, 0x83, 0x6a, 0xd9, 0x42, 0xf8, 0x00, 0x54, 0xf2, 0x47, 0x42, 0x82, 0x96, 0x27, 0x40, + 0x2f, 0x8b, 0x17, 0xc4, 0xae, 0x1c, 0x0b, 0x12, 0xf7, 0xcf, 0x29, 0x1d, 0x1c, 0x06, 0x1d, 0xc6, + 0x29, 0x5a, 0x4e, 0x11, 0x76, 0x41, 0x79, 0xaf, 0x82, 0xfa, 0x68, 0x8d, 0xbb, 0xf9, 0x0c, 0xa1, + 0x0e, 0x2e, 0xf1, 0x61, 0xe2, 0x8c, 0x13, 0xab, 0x4e, 0x61, 0x0e, 0x77, 0x30, 0x57, 0x2c, 0x77, + 0xc6, 0x86, 0xa4, 0xfd, 0xef, 0x90, 0xec, 0xdd, 0x93, 0xbe, 0xa1, 0x9e, 0xf6, 0x0d, 0xf5, 0x47, + 0xdf, 0x50, 0x8f, 0x07, 0x86, 0x72, 0x3a, 0x30, 0x94, 0xef, 0x03, 0x43, 0x79, 0xb5, 0xfa, 0xd7, + 0xa3, 0x7b, 0x34, 0x7c, 0xfb, 0xf9, 0x29, 0x76, 0xe7, 0x38, 0xf4, 0xfe, 0xaf, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xfc, 0x05, 0xa9, 0xc7, 0x1b, 0x06, 0x00, 0x00, } func (m *BasicFeeAllowance) Marshal() (dAtA []byte, err error) { @@ -598,6 +643,50 @@ func (m *PeriodicFeeAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *AllowedMsgFeeAllowance) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *AllowedMsgFeeAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AllowedMsgFeeAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AllowedMessages) > 0 { + for iNdEx := len(m.AllowedMessages) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AllowedMessages[iNdEx]) + copy(dAtA[i:], m.AllowedMessages[iNdEx]) + i = encodeVarintFeegrant(dAtA, i, uint64(len(m.AllowedMessages[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Allowance != nil { + { + size, err := m.Allowance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFeegrant(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *Duration) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -638,12 +727,12 @@ func (m *Duration_Duration) MarshalTo(dAtA []byte) (int, error) { func (m *Duration_Duration) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) if m.Duration != nil { - n5, err5 := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.Duration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Duration):]) - if err5 != nil { - return 0, err5 + n6, err6 := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.Duration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Duration):]) + if err6 != nil { + return 0, err6 } - i -= n5 - i = encodeVarintFeegrant(dAtA, i, uint64(n5)) + i -= n6 + i = encodeVarintFeegrant(dAtA, i, uint64(n6)) i-- dAtA[i] = 0xa } @@ -701,12 +790,12 @@ func (m *ExpiresAt_Time) MarshalTo(dAtA []byte) (int, error) { func (m *ExpiresAt_Time) MarshalToSizedBuffer(dAtA []byte) (int, error) { i := len(dAtA) if m.Time != nil { - n6, err6 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Time):]) - if err6 != nil { - return 0, err6 + n7, err7 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Time):]) + if err7 != nil { + return 0, err7 } - i -= n6 - i = encodeVarintFeegrant(dAtA, i, uint64(n6)) + i -= n7 + i = encodeVarintFeegrant(dAtA, i, uint64(n7)) i-- dAtA[i] = 0xa } @@ -828,6 +917,25 @@ func (m *PeriodicFeeAllowance) Size() (n int) { return n } +func (m *AllowedMsgFeeAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Allowance != nil { + l = m.Allowance.Size() + n += 1 + l + sovFeegrant(uint64(l)) + } + if len(m.AllowedMessages) > 0 { + for _, s := range m.AllowedMessages { + l = len(s) + n += 1 + l + sovFeegrant(uint64(l)) + } + } + return n +} + func (m *Duration) Size() (n int) { if m == nil { return 0 @@ -1255,6 +1363,124 @@ func (m *PeriodicFeeAllowance) Unmarshal(dAtA []byte) error { } return nil } +func (m *AllowedMsgFeeAllowance) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeegrant + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: AllowedMsgFeeAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AllowedMsgFeeAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Allowance", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeegrant + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthFeegrant + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthFeegrant + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Allowance == nil { + m.Allowance = &types1.Any{} + } + if err := m.Allowance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowedMessages", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeegrant + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFeegrant + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFeegrant + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowedMessages = append(m.AllowedMessages, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipFeegrant(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthFeegrant + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *Duration) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/feegrant/types/fees.go b/x/feegrant/types/fees.go index cc526628f5..d4b5348579 100644 --- a/x/feegrant/types/fees.go +++ b/x/feegrant/types/fees.go @@ -19,7 +19,7 @@ type FeeAllowanceI interface { // // If remove is true (regardless of the error), the FeeAllowance will be deleted from storage // (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees) - Accept(fee sdk.Coins, blockTime time.Time, blockHeight int64) (remove bool, err error) + Accept(ctx sdk.Context, fee sdk.Coins, msgs []sdk.Msg) (remove bool, err error) // If we export fee allowances the timing info will be quite off (eg. go from height 100000 to 0) // This callback allows the fee-allowance to change it's state and return a copy that is adjusted diff --git a/x/feegrant/types/filtered_fee.go b/x/feegrant/types/filtered_fee.go new file mode 100644 index 0000000000..99cf5f1731 --- /dev/null +++ b/x/feegrant/types/filtered_fee.go @@ -0,0 +1,123 @@ +package types + +import ( + "time" + + "github.com/cosmos/cosmos-sdk/codec/types" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + proto "github.com/gogo/protobuf/proto" +) + +// TODO: Revisit this once we have propoer gas fee framework. +// Tracking issues https://github.com/cosmos/cosmos-sdk/issues/9054, https://github.com/cosmos/cosmos-sdk/discussions/9072 +const ( + gasCostPerIteration = uint64(10) +) + +var _ FeeAllowanceI = (*AllowedMsgFeeAllowance)(nil) +var _ types.UnpackInterfacesMessage = (*AllowedMsgFeeAllowance)(nil) + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (a *AllowedMsgFeeAllowance) UnpackInterfaces(unpacker types.AnyUnpacker) error { + var allowance FeeAllowanceI + return unpacker.UnpackAny(a.Allowance, &allowance) +} + +// NewAllowedMsgFeeAllowance creates new filtered fee allowance. +func NewAllowedMsgFeeAllowance(allowance FeeAllowanceI, allowedMsgs []string) (*AllowedMsgFeeAllowance, error) { + msg, ok := allowance.(proto.Message) + if !ok { + return nil, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "cannot proto marshal %T", msg) + } + any, err := types.NewAnyWithValue(msg) + if err != nil { + return nil, err + } + + return &AllowedMsgFeeAllowance{ + Allowance: any, + AllowedMessages: allowedMsgs, + }, nil +} + +// GetAllowance returns allowed fee allowance. +func (a *AllowedMsgFeeAllowance) GetAllowance() (FeeAllowanceI, error) { + allowance, ok := a.Allowance.GetCachedValue().(FeeAllowanceI) + if !ok { + return nil, sdkerrors.Wrap(ErrNoAllowance, "failed to get allowance") + } + + return allowance, nil +} + +// Accept method checks for the filtered messages has valid expiry +func (a *AllowedMsgFeeAllowance) Accept(ctx sdk.Context, fee sdk.Coins, msgs []sdk.Msg) (bool, error) { + if !a.allMsgTypesAllowed(ctx, msgs) { + return false, sdkerrors.Wrap(ErrMessageNotAllowed, "message does not exist in allowed messages") + } + + allowance, err := a.GetAllowance() + if err != nil { + return false, err + } + + return allowance.Accept(ctx, fee, msgs) +} + +func (a *AllowedMsgFeeAllowance) allowedMsgsToMap(ctx sdk.Context) map[string]bool { + msgsMap := make(map[string]bool, len(a.AllowedMessages)) + for _, msg := range a.AllowedMessages { + ctx.GasMeter().ConsumeGas(gasCostPerIteration, "check msg") + msgsMap[msg] = true + } + + return msgsMap +} + +func (a *AllowedMsgFeeAllowance) allMsgTypesAllowed(ctx sdk.Context, msgs []sdk.Msg) bool { + msgsMap := a.allowedMsgsToMap(ctx) + + for _, msg := range msgs { + ctx.GasMeter().ConsumeGas(gasCostPerIteration, "check msg") + if !msgsMap[msg.Type()] { + return false + } + } + + return true +} + +// PrepareForExport will adjust the expiration based on export time. In particular, +// it will subtract the dumpHeight from any height-based expiration to ensure that +// the elapsed number of blocks this allowance is valid for is fixed. +func (a *AllowedMsgFeeAllowance) PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceI { + allowance, err := a.GetAllowance() + if err != nil { + panic("failed to get allowance") + } + + f, err := NewAllowedMsgFeeAllowance(allowance.PrepareForExport(dumpTime, dumpHeight), a.AllowedMessages) + if err != nil { + panic("failed to export filtered fee allowance") + } + + return f +} + +// ValidateBasic implements FeeAllowance and enforces basic sanity checks +func (a *AllowedMsgFeeAllowance) ValidateBasic() error { + if a.Allowance == nil { + return sdkerrors.Wrap(ErrNoAllowance, "allowance should not be empty") + } + if len(a.AllowedMessages) == 0 { + return sdkerrors.Wrap(ErrNoMessages, "allowed messages shouldn't be empty") + } + + allowance, err := a.GetAllowance() + if err != nil { + return err + } + + return allowance.ValidateBasic() +} diff --git a/x/feegrant/types/genesis.go b/x/feegrant/types/genesis.go index 57ade9b43d..0d72dc8b18 100644 --- a/x/feegrant/types/genesis.go +++ b/x/feegrant/types/genesis.go @@ -14,7 +14,11 @@ func NewGenesisState(entries []FeeAllowanceGrant) *GenesisState { // ValidateGenesis ensures all grants in the genesis state are valid func ValidateGenesis(data GenesisState) error { for _, f := range data.FeeAllowances { - err := f.GetFeeGrant().ValidateBasic() + grant, err := f.GetFeeGrant() + if err != nil { + return err + } + err = grant.ValidateBasic() if err != nil { return err } diff --git a/x/feegrant/types/grant.go b/x/feegrant/types/grant.go index 97a18de529..1c92080be3 100644 --- a/x/feegrant/types/grant.go +++ b/x/feegrant/types/grant.go @@ -47,17 +47,22 @@ func (a FeeAllowanceGrant) ValidateBasic() error { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "cannot self-grant fee authorization") } - return a.GetFeeGrant().ValidateBasic() + f, err := a.GetFeeGrant() + if err != nil { + return err + } + + return f.ValidateBasic() } // GetFeeGrant unpacks allowance -func (a FeeAllowanceGrant) GetFeeGrant() FeeAllowanceI { +func (a FeeAllowanceGrant) GetFeeGrant() (FeeAllowanceI, error) { allowance, ok := a.Allowance.GetCachedValue().(FeeAllowanceI) if !ok { - return nil + return nil, sdkerrors.Wrap(ErrNoAllowance, "failed to get allowance") } - return allowance + return allowance, nil } // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces @@ -69,7 +74,12 @@ func (a FeeAllowanceGrant) UnpackInterfaces(unpacker types.AnyUnpacker) error { // PrepareForExport will make all needed changes to the allowance to prepare to be // re-imported at height 0, and return a copy of this grant. func (a FeeAllowanceGrant) PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceGrant { - feegrant := a.GetFeeGrant().PrepareForExport(dumpTime, dumpHeight) + f, err := a.GetFeeGrant() + if err != nil { + return FeeAllowanceGrant{} + } + + feegrant := f.PrepareForExport(dumpTime, dumpHeight) if feegrant == nil { return FeeAllowanceGrant{} } diff --git a/x/feegrant/types/msgs.go b/x/feegrant/types/msgs.go index 04f09c8f3f..1f2be972d8 100644 --- a/x/feegrant/types/msgs.go +++ b/x/feegrant/types/msgs.go @@ -50,7 +50,12 @@ func (msg MsgGrantFeeAllowance) ValidateBasic() error { return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "cannot self-grant fee authorization") } - return msg.GetFeeAllowanceI().ValidateBasic() + allowance, err := msg.GetFeeAllowanceI() + if err != nil { + return err + } + + return allowance.ValidateBasic() } func (msg MsgGrantFeeAllowance) GetSigners() []sdk.AccAddress { @@ -62,13 +67,13 @@ func (msg MsgGrantFeeAllowance) GetSigners() []sdk.AccAddress { } // GetFeeAllowanceI returns unpacked FeeAllowance -func (msg MsgGrantFeeAllowance) GetFeeAllowanceI() FeeAllowanceI { +func (msg MsgGrantFeeAllowance) GetFeeAllowanceI() (FeeAllowanceI, error) { allowance, ok := msg.Allowance.GetCachedValue().(FeeAllowanceI) if !ok { - return nil + return nil, sdkerrors.Wrap(ErrNoAllowance, "failed to get allowance") } - return allowance + return allowance, nil } // UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces diff --git a/x/feegrant/types/periodic_fee.go b/x/feegrant/types/periodic_fee.go index 61c9ba2ffe..b20eeae5f0 100644 --- a/x/feegrant/types/periodic_fee.go +++ b/x/feegrant/types/periodic_fee.go @@ -19,7 +19,10 @@ var _ FeeAllowanceI = (*PeriodicFeeAllowance)(nil) // // If remove is true (regardless of the error), the FeeAllowance will be deleted from storage // (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees) -func (a *PeriodicFeeAllowance) Accept(fee sdk.Coins, blockTime time.Time, blockHeight int64) (bool, error) { +func (a *PeriodicFeeAllowance) Accept(ctx sdk.Context, fee sdk.Coins, _ []sdk.Msg) (bool, error) { + blockTime := ctx.BlockTime() + blockHeight := ctx.BlockHeight() + if a.Basic.Expiration.IsExpired(&blockTime, blockHeight) { return true, sdkerrors.Wrap(ErrFeeLimitExpired, "absolute limit") } diff --git a/x/feegrant/types/periodic_fee_test.go b/x/feegrant/types/periodic_fee_test.go index 229fd99cea..aa66828bab 100644 --- a/x/feegrant/types/periodic_fee_test.go +++ b/x/feegrant/types/periodic_fee_test.go @@ -2,16 +2,18 @@ package types_test import ( "testing" - "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + tmproto "github.com/tendermint/tendermint/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/feegrant/types" ) func TestPeriodicFeeValidAllow(t *testing.T) { + app := simapp.Setup(false) atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555)) smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 43)) leftAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 512)) @@ -22,7 +24,6 @@ func TestPeriodicFeeValidAllow(t *testing.T) { allow types.PeriodicFeeAllowance // all other checks are ignored if valid=false fee sdk.Coins - blockTime time.Time blockHeight int64 valid bool accept bool @@ -187,8 +188,9 @@ func TestPeriodicFeeValidAllow(t *testing.T) { } require.NoError(t, err) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}).WithBlockHeight(tc.blockHeight) // now try to deduct - remove, err := tc.allow.Accept(tc.fee, tc.blockTime, tc.blockHeight) + remove, err := tc.allow.Accept(ctx, tc.fee, []sdk.Msg{}) if !tc.accept { require.Error(t, err) return