diff --git a/client/flags/flags.go b/client/flags/flags.go index fb4438da722b..5c267b89d3b3 100644 --- a/client/flags/flags.go +++ b/client/flags/flags.go @@ -65,6 +65,7 @@ const ( FlagRPCMaxBodyBytes = "max-body-bytes" FlagOutputDocument = "output-document" // inspired by wget -O FlagSkipConfirmation = "yes" + FlagFeeAccount = "fee-account" FlagProve = "prove" FlagKeyringBackend = "keyring-backend" FlagPage = "page" @@ -122,6 +123,7 @@ func PostCommands(cmds ...*cobra.Command) []*cobra.Command { c.Flags().Bool(FlagOffline, false, "Offline mode (does not allow any online functionality") c.Flags().BoolP(FlagSkipConfirmation, "y", false, "Skip tx broadcasting prompt confirmation") c.Flags().String(FlagKeyringBackend, DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)") + c.Flags().String(FlagFeeAccount, "", "Set a fee account to pay fess with if they have been authorized by this account") // --gas can accept integers and "simulate" c.Flags().Var(&GasFlagVar, "gas", fmt.Sprintf( diff --git a/simapp/ante.go b/simapp/ante.go new file mode 100644 index 000000000000..e53f8638d577 --- /dev/null +++ b/simapp/ante.go @@ -0,0 +1,35 @@ +package simapp + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + "github.com/cosmos/cosmos-sdk/x/feegrant" + feegrantante "github.com/cosmos/cosmos-sdk/x/feegrant/ante" +) + +// NewAnteHandler returns an AnteHandler that checks and increments sequence +// numbers, checks signatures & account numbers, and deducts fees from the first +// signer. +func NewAnteHandler( + ak auth.AccountKeeper, supplyKeeper feegrant.SupplyKeeper, feeGrantKeeper feegrant.Keeper, + sigGasConsumer auth.SignatureVerificationGasConsumer, +) sdk.AnteHandler { + + return sdk.ChainAnteDecorators( + authante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + authante.NewMempoolFeeDecorator(), + authante.NewValidateBasicDecorator(), + authante.NewValidateMemoDecorator(ak), + authante.NewConsumeGasForTxSizeDecorator(ak), + // DeductGrantedFeeDecorator will create an empty account if we sign with no + // tokens but valid validation. This must be before SetPubKey, ValidateSigCount, + // SigVerification, which error if account doesn't exist yet. + feegrantante.NewDeductGrantedFeeDecorator(ak, supplyKeeper, feeGrantKeeper), + authante.NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators + authante.NewValidateSigCountDecorator(ak), + authante.NewSigGasConsumeDecorator(ak, sigGasConsumer), + authante.NewSigVerificationDecorator(ak), + authante.NewIncrementSequenceDecorator(ak), // innermost AnteDecorator + ) +} diff --git a/simapp/app.go b/simapp/app.go index 7835fa284368..d4b2c700a355 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -18,12 +18,12 @@ import ( "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/version" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/auth/ante" "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/capability" "github.com/cosmos/cosmos-sdk/x/crisis" distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/evidence" + "github.com/cosmos/cosmos-sdk/x/feegrant" "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/ibc" @@ -67,6 +67,7 @@ var ( crisis.AppModuleBasic{}, slashing.AppModuleBasic{}, ibc.AppModuleBasic{}, + feegrant.AppModuleBasic{}, upgrade.AppModuleBasic{}, evidence.AppModuleBasic{}, transfer.AppModuleBasic{}, @@ -117,6 +118,7 @@ type SimApp struct { SlashingKeeper slashing.Keeper MintKeeper mint.Keeper DistrKeeper distr.Keeper + FeeGrantKeeper feegrant.Keeper GovKeeper gov.Keeper CrisisKeeper crisis.Keeper UpgradeKeeper upgrade.Keeper @@ -152,7 +154,7 @@ func NewSimApp( keys := sdk.NewKVStoreKeys( auth.StoreKey, bank.StoreKey, staking.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey, - gov.StoreKey, params.StoreKey, ibc.StoreKey, upgrade.StoreKey, + gov.StoreKey, params.StoreKey, ibc.StoreKey, upgrade.StoreKey, feegrant.StoreKey, evidence.StoreKey, transfer.StoreKey, capability.StoreKey, ) tkeys := sdk.NewTransientStoreKeys(params.TStoreKey) @@ -212,6 +214,7 @@ func NewSimApp( app.CrisisKeeper = crisis.NewKeeper( app.subspaces[crisis.ModuleName], invCheckPeriod, app.BankKeeper, auth.FeeCollectorName, ) + app.FeeGrantKeeper = feegrant.NewKeeper(appCodec, keys[feegrant.StoreKey]) app.UpgradeKeeper = upgrade.NewKeeper(skipUpgradeHeights, keys[upgrade.StoreKey], appCodec, homePath) // register the proposal types @@ -269,6 +272,7 @@ func NewSimApp( bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), capability.NewAppModule(appCodec, *app.CapabilityKeeper), crisis.NewAppModule(&app.CrisisKeeper), + feegrant.NewAppModule(app.FeeGrantKeeper), gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), @@ -299,7 +303,7 @@ func NewSimApp( app.mm.SetOrderInitGenesis( capability.ModuleName, auth.ModuleName, distr.ModuleName, staking.ModuleName, bank.ModuleName, slashing.ModuleName, gov.ModuleName, mint.ModuleName, crisis.ModuleName, - ibc.ModuleName, genutil.ModuleName, evidence.ModuleName, transfer.ModuleName, + ibc.ModuleName, genutil.ModuleName, evidence.ModuleName, transfer.ModuleName, feegrant.ModuleName, ) app.mm.RegisterInvariants(&app.CrisisKeeper) @@ -340,7 +344,7 @@ func NewSimApp( app.SetBeginBlocker(app.BeginBlocker) app.SetAnteHandler( ante.NewAnteHandler( - app.AccountKeeper, app.BankKeeper, *app.IBCKeeper, ante.DefaultSigVerificationGasConsumer, + app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, *app.IBCKeeper, ante.DefaultSigVerificationGasConsumer, ), ) app.SetEndBlocker(app.EndBlocker) diff --git a/x/feegrant/alias.go b/x/feegrant/alias.go new file mode 100644 index 000000000000..74ea930daf17 --- /dev/null +++ b/x/feegrant/alias.go @@ -0,0 +1,66 @@ +package feegrant + +import ( + "github.com/cosmos/cosmos-sdk/x/feegrant/ante" + "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" + "github.com/cosmos/cosmos-sdk/x/feegrant/types" +) + +// nolint + +const ( + DefaultCodespace = types.DefaultCodespace + EventTypeUseFeeGrant = types.EventTypeUseFeeGrant + EventTypeRevokeFeeGrant = types.EventTypeRevokeFeeGrant + EventTypeSetFeeGrant = types.EventTypeSetFeeGrant + AttributeKeyGranter = types.AttributeKeyGranter + AttributeKeyGrantee = types.AttributeKeyGrantee + ModuleName = types.ModuleName + StoreKey = types.StoreKey + RouterKey = types.RouterKey + QuerierRoute = types.QuerierRoute + QueryGetFeeAllowances = keeper.QueryGetFeeAllowances +) + +var ( + NewDeductGrantedFeeDecorator = ante.NewDeductGrantedFeeDecorator + RegisterCodec = types.RegisterCodec + ExpiresAtTime = types.ExpiresAtTime + ExpiresAtHeight = types.ExpiresAtHeight + ClockDuration = types.ClockDuration + BlockDuration = types.BlockDuration + FeeAllowanceKey = types.FeeAllowanceKey + FeeAllowancePrefixByGrantee = types.FeeAllowancePrefixByGrantee + NewMsgRevokeFeeAllowance = types.NewMsgRevokeFeeAllowance + NewFeeGrantTx = types.NewFeeGrantTx + CountSubKeys = types.CountSubKeys + NewGrantedFee = types.NewGrantedFee + StdSignBytes = types.StdSignBytes + NewKeeper = keeper.NewKeeper + NewQuerier = keeper.NewQuerier + + ModuleCdc = types.ModuleCdc + ErrFeeLimitExceeded = types.ErrFeeLimitExceeded + ErrFeeLimitExpired = types.ErrFeeLimitExpired + ErrInvalidDuration = types.ErrInvalidDuration + ErrNoAllowance = types.ErrNoAllowance + FeeAllowanceKeyPrefix = types.FeeAllowanceKeyPrefix +) + +type ( + GrantedFeeTx = ante.GrantedFeeTx + DeductGrantedFeeDecorator = ante.DeductGrantedFeeDecorator + BasicFeeAllowance = types.BasicFeeAllowance + ExpiresAt = types.ExpiresAt + Duration = types.Duration + FeeAllowance = types.FeeAllowance + FeeAllowanceGrant = types.FeeAllowanceGrant + MsgGrantFeeAllowance = types.MsgGrantFeeAllowance + MsgRevokeFeeAllowance = types.MsgRevokeFeeAllowance + PeriodicFeeAllowance = types.PeriodicFeeAllowance + FeeGrantTx = types.FeeGrantTx + GrantedFee = types.GrantedFee + DelegatedSignDoc = types.DelegatedSignDoc + SupplyKeeper = types.SupplyKeeper + Keeper = keeper.Keeper +) diff --git a/x/feegrant/ante/fee.go b/x/feegrant/ante/fee.go new file mode 100644 index 000000000000..af3cf51852a8 --- /dev/null +++ b/x/feegrant/ante/fee.go @@ -0,0 +1,98 @@ +package ante + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" + "github.com/cosmos/cosmos-sdk/x/feegrant/types" +) + +var ( + _ GrantedFeeTx = (*types.FeeGrantTx)(nil) // assert FeeGrantTx implements GrantedFeeTx +) + +// GrantedFeeTx defines the interface to be implemented by Tx to use the GrantedFeeDecorator +type GrantedFeeTx interface { + sdk.Tx + + GetGas() uint64 + GetFee() sdk.Coins + FeePayer() sdk.AccAddress + MainSigner() sdk.AccAddress +} + +// DeductGrantedFeeDecorator deducts fees from the first signer of the tx +// If the first signer does not have the funds to pay for the fees, return with InsufficientFunds error +// Call next AnteHandler if fees successfully deducted +// CONTRACT: Tx must implement GrantedFeeTx interface to use DeductGrantedFeeDecorator +type DeductGrantedFeeDecorator struct { + ak types.AccountKeeper + k keeper.Keeper + sk types.SupplyKeeper +} + +func NewDeductGrantedFeeDecorator(ak types.AccountKeeper, sk types.SupplyKeeper, k keeper.Keeper) DeductGrantedFeeDecorator { + return DeductGrantedFeeDecorator{ + ak: ak, + k: k, + sk: sk, + } +} + +// AnteHandle performs a decorated ante-handler responsible for deducting transaction +// fees. Fees will be deducted from the account designated by the FeePayer on a +// transaction by default. However, if the fee payer differs from the transaction +// signer, the handler will check if a fee grant has been authorized. If the +// transaction's signer does not exist, it will be created. +func (d DeductGrantedFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + feeTx, ok := tx.(GrantedFeeTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a GrantedFeeTx") + } + + // sanity check from DeductFeeDecorator + if addr := d.sk.GetModuleAddress(auth.FeeCollectorName); addr == nil { + panic(fmt.Sprintf("%s module account has not been set", auth.FeeCollectorName)) + } + + fee := feeTx.GetFee() + feePayer := feeTx.FeePayer() + txSigner := feeTx.MainSigner() + + // ensure the grant is allowed, if we request a different fee payer + if !txSigner.Equals(feePayer) { + err := d.k.UseGrantedFees(ctx, feePayer, txSigner, fee) + if err != nil { + return ctx, sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", txSigner, feePayer) + } + + // if there was a valid grant, ensure that the txSigner account exists (we create it if needed) + signerAcc := d.ak.GetAccount(ctx, txSigner) + if signerAcc == nil { + signerAcc = d.ak.NewAccountWithAddress(ctx, txSigner) + d.ak.SetAccount(ctx, signerAcc) + } + } + + // now, either way, we know that we are authorized to deduct the fees from the feePayer account + feePayerAcc := d.ak.GetAccount(ctx, feePayer) + if feePayerAcc == nil { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", feePayer) + } + + // move on if there is no fee to deduct + if fee.IsZero() { + return next(ctx, tx, simulate) + } + + // deduct fee if non-zero + err = auth.DeductFees(d.sk, ctx, feePayerAcc, fee) + if err != nil { + return ctx, err + } + + return next(ctx, tx, simulate) +} diff --git a/x/feegrant/ante/fee_test.go b/x/feegrant/ante/fee_test.go new file mode 100644 index 000000000000..de1773f0c4c3 --- /dev/null +++ b/x/feegrant/ante/fee_test.go @@ -0,0 +1,279 @@ +package ante_test + +import ( + "testing" + + "github.com/stretchr/testify/require" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + authante "github.com/cosmos/cosmos-sdk/x/auth/ante" + authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/feegrant/ante" + "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" + "github.com/cosmos/cosmos-sdk/x/feegrant/types" +) + +// newAnteHandler is just like auth.NewAnteHandler, except we use the DeductGrantedFeeDecorator +// in order to allow payment of fees via a grant. +// +// This is used for our full-stack tests +func newAnteHandler(ak authkeeper.AccountKeeper, supplyKeeper authtypes.SupplyKeeper, dk keeper.Keeper, sigGasConsumer authante.SignatureVerificationGasConsumer) sdk.AnteHandler { + return sdk.ChainAnteDecorators( + authante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first + authante.NewMempoolFeeDecorator(), + authante.NewValidateBasicDecorator(), + authante.NewValidateMemoDecorator(ak), + authante.NewConsumeGasForTxSizeDecorator(ak), + // DeductGrantedFeeDecorator will create an empty account if we sign with no tokens but valid validation + // This must be before SetPubKey, ValidateSigCount, SigVerification, which error if account doesn't exist yet + ante.NewDeductGrantedFeeDecorator(ak, supplyKeeper, dk), + authante.NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators + authante.NewValidateSigCountDecorator(ak), + authante.NewSigGasConsumeDecorator(ak, sigGasConsumer), + authante.NewSigVerificationDecorator(ak), + authante.NewIncrementSequenceDecorator(ak), // innermost AnteDecorator + ) +} + +func TestDeductFeesNoDelegation(t *testing.T) { + // setup + app, ctx := createTestApp(true) + + // this just tests our handler + dfd := ante.NewDeductGrantedFeeDecorator(app.AccountKeeper, app.SupplyKeeper, app.FeeGrantKeeper) + ourAnteHandler := sdk.ChainAnteDecorators(dfd) + + // this tests the whole stack + anteHandlerStack := newAnteHandler(app.AccountKeeper, app.SupplyKeeper, app.FeeGrantKeeper, SigGasNoConsumer) + + // keys and addresses + priv1, _, addr1 := authtypes.KeyTestPubAddr() + priv2, _, addr2 := authtypes.KeyTestPubAddr() + priv3, _, addr3 := authtypes.KeyTestPubAddr() + priv4, _, addr4 := authtypes.KeyTestPubAddr() + + // Set addr1 with insufficient funds + acc1 := app.AccountKeeper.NewAccountWithAddress(ctx, addr1) + app.AccountKeeper.SetAccount(ctx, acc1) + app.BankKeeper.SetBalances(ctx, addr1, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(10))}) + + // Set addr2 with more funds + acc2 := app.AccountKeeper.NewAccountWithAddress(ctx, addr2) + app.AccountKeeper.SetAccount(ctx, acc2) + app.BankKeeper.SetBalances(ctx, addr2, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(99999))}) + + // Set grant from addr2 to addr3 (plenty to pay) + app.FeeGrantKeeper.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: addr2, + Grantee: addr3, + Allowance: &types.FeeAllowance{Sum: &types.FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &types.BasicFeeAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 500)), + }, + }, + }, + }) + + // Set low grant from addr2 to addr4 (keeper will reject) + app.FeeGrantKeeper.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: addr2, + Grantee: addr4, + Allowance: &types.FeeAllowance{Sum: &types.FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &types.BasicFeeAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 20)), + }, + }, + }, + }) + + // Set grant from addr1 to addr4 (cannot cover this ) + app.FeeGrantKeeper.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: addr2, + Grantee: addr3, + Allowance: &types.FeeAllowance{Sum: &types.FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &types.BasicFeeAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 500)), + }, + }, + }, + }) + + cases := map[string]struct { + signerKey crypto.PrivKey + signer sdk.AccAddress + feeAccount sdk.AccAddress + handler sdk.AnteHandler + fee int64 + valid bool + }{ + "paying with low funds (only ours)": { + signerKey: priv1, + signer: addr1, + fee: 50, + handler: ourAnteHandler, + valid: false, + }, + "paying with good funds (only ours)": { + signerKey: priv2, + signer: addr2, + fee: 50, + handler: ourAnteHandler, + valid: true, + }, + "paying with no account (only ours)": { + signerKey: priv3, + signer: addr3, + fee: 1, + handler: ourAnteHandler, + valid: false, + }, + "no fee with real account (only ours)": { + signerKey: priv1, + signer: addr1, + fee: 0, + handler: ourAnteHandler, + valid: true, + }, + "no fee with no account (only ours)": { + signerKey: priv4, + signer: addr4, + fee: 0, + handler: ourAnteHandler, + valid: false, + }, + "valid fee grant without account (only ours)": { + signerKey: priv3, + signer: addr3, + feeAccount: addr2, + fee: 50, + handler: ourAnteHandler, + valid: true, + }, + "no fee grant (only ours)": { + signerKey: priv3, + signer: addr3, + feeAccount: addr1, + fee: 2, + handler: ourAnteHandler, + valid: false, + }, + "allowance smaller than requested fee (only ours)": { + signerKey: priv4, + signer: addr4, + feeAccount: addr2, + fee: 50, + handler: ourAnteHandler, + valid: false, + }, + "granter cannot cover allowed fee grant (only ours)": { + signerKey: priv4, + signer: addr4, + feeAccount: addr1, + fee: 50, + handler: ourAnteHandler, + valid: false, + }, + + "paying with low funds (whole stack)": { + signerKey: priv1, + signer: addr1, + fee: 50, + handler: anteHandlerStack, + valid: false, + }, + "paying with good funds (whole stack)": { + signerKey: priv2, + signer: addr2, + fee: 50, + handler: anteHandlerStack, + valid: true, + }, + "paying with no account (whole stack)": { + signerKey: priv3, + signer: addr3, + fee: 1, + handler: anteHandlerStack, + valid: false, + }, + "no fee with real account (whole stack)": { + signerKey: priv1, + signer: addr1, + fee: 0, + handler: anteHandlerStack, + valid: true, + }, + "no fee with no account (whole stack)": { + signerKey: priv4, + signer: addr4, + fee: 0, + handler: anteHandlerStack, + valid: false, + }, + "valid fee grant without account (whole stack)": { + signerKey: priv3, + signer: addr3, + feeAccount: addr2, + fee: 50, + handler: anteHandlerStack, + valid: true, + }, + "no fee grant (whole stack)": { + signerKey: priv3, + signer: addr3, + feeAccount: addr1, + fee: 2, + handler: anteHandlerStack, + valid: false, + }, + "allowance smaller than requested fee (whole stack)": { + signerKey: priv4, + signer: addr4, + feeAccount: addr2, + fee: 50, + handler: anteHandlerStack, + valid: false, + }, + "granter cannot cover allowed fee grant (whole stack)": { + signerKey: priv4, + signer: addr4, + feeAccount: addr1, + fee: 50, + handler: anteHandlerStack, + valid: false, + }, + } + + for name, stc := range cases { + tc := stc // to make scopelint happy + t.Run(name, func(t *testing.T) { + // msg and signatures + fee := types.NewGrantedFee(100000, sdk.NewCoins(sdk.NewInt64Coin("atom", tc.fee)), tc.feeAccount) + msgs := []sdk.Msg{sdk.NewTestMsg(tc.signer)} + privs, accNums, seqs := []crypto.PrivKey{tc.signerKey}, []uint64{0}, []uint64{0} + + tx := types.NewTestTx(ctx, msgs, privs, accNums, seqs, fee) + + _, err := tc.handler(ctx, tx, false) + if tc.valid { + require.NoError(t, err) + } else { + require.Error(t, err) + } + }) + } +} + +// returns context and app with params set on account keeper +func createTestApp(isCheckTx bool) (*simapp.SimApp, sdk.Context) { + app := simapp.Setup(isCheckTx) + ctx := app.BaseApp.NewContext(isCheckTx, abci.Header{}) + app.AccountKeeper.SetParams(ctx, authtypes.DefaultParams()) + + return app, ctx +} + +// don't cosume any gas +func SigGasNoConsumer(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params authtypes.Params) error { + return nil +} diff --git a/x/feegrant/doc.go b/x/feegrant/doc.go new file mode 100644 index 000000000000..4f190e3140c7 --- /dev/null +++ b/x/feegrant/doc.go @@ -0,0 +1,29 @@ +/* +Package feegrant provides functionality for authorizing the payment of transaction +fees from one account (key) to another account (key). + +Effectively, this allows for a user to pay fees using the balance of an account +different from their own. Example use cases would be allowing a key on a device to +pay for fees using a master wallet, or a third party service allowing users to +pay for transactions without ever really holding their own tokens. This package +provides ways for specifying fee allowances such that authorizing fee payment to +another account can be done with clear and safe restrictions. + +A user would authorize granting fee payment to another user using +MsgDelegateFeeAllowance and revoke that delegation using MsgRevokeFeeAllowance. +In both cases, Granter is the one who is authorizing fee payment and Grantee is +the one who is receiving the fee payment authorization. So grantee would correspond +to the one who is signing a transaction and the granter would be the address that +pays the fees. + +The fee allowance that a grantee receives is specified by an implementation of +the FeeAllowance interface. Two FeeAllowance implementations are provided in +this package: BasicFeeAllowance and PeriodicFeeAllowance. + +In order to integrate this into an application, we must use the DeductGrantedFeeDecorator +ante handler from this package instead of the default DeductFeeDecorator from x/auth. + +To allow handling txs from empty accounts (with fees paid from an existing account), +we have to re-order the decorators as well. +*/ +package feegrant diff --git a/x/feegrant/genesis.go b/x/feegrant/genesis.go new file mode 100644 index 000000000000..d32598a6e167 --- /dev/null +++ b/x/feegrant/genesis.go @@ -0,0 +1,45 @@ +package feegrant + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// GenesisState contains a set of fee allowances, persisted from the store +type GenesisState []FeeAllowanceGrant + +// ValidateBasic ensures all grants in the genesis state are valid +func (g GenesisState) ValidateBasic() error { + for _, f := range g { + err := f.GetFeeGrant().ValidateBasic() + if err != nil { + return err + } + } + return nil +} + +// InitGenesis will initialize the keeper from a *previously validated* GenesisState +func InitGenesis(ctx sdk.Context, k Keeper, gen GenesisState) { + for _, f := range gen { + k.GrantFeeAllowance(ctx, f) + } +} + +// ExportGenesis will dump the contents of the keeper into a serializable GenesisState +// +// All expiration heights will be thrown off if we dump state and start at a new +// chain at height 0. Thus, we allow the Allowances to "prepare themselves" +// for export, like if they have expiry at 5000 and current is 4000, they export with +// expiry of 1000. Every FeeAllowance has a method `PrepareForExport` that allows +// them to perform any changes needed prior to export. +func ExportGenesis(ctx sdk.Context, k Keeper) (GenesisState, error) { + time, height := ctx.BlockTime(), ctx.BlockHeight() + var grants []FeeAllowanceGrant + + err := k.IterateAllFeeAllowances(ctx, func(grant FeeAllowanceGrant) bool { + grants = append(grants, grant.PrepareForExport(time, height)) + return false + }) + + return grants, err +} diff --git a/x/feegrant/handler.go b/x/feegrant/handler.go new file mode 100644 index 000000000000..40d483a41790 --- /dev/null +++ b/x/feegrant/handler.go @@ -0,0 +1,35 @@ +package feegrant + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +func NewHandler(k Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + + switch msg := msg.(type) { + case MsgGrantFeeAllowance: + return handleGrantFee(ctx, k, msg) + + case MsgRevokeFeeAllowance: + return handleRevokeFee(ctx, k, msg) + + default: + return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unrecognized %s message type: %T", ModuleName, msg) + } + } +} + +func handleGrantFee(ctx sdk.Context, k Keeper, msg MsgGrantFeeAllowance) (*sdk.Result, error) { + feegrant := FeeAllowanceGrant(msg) + + k.GrantFeeAllowance(ctx, feegrant) + return &sdk.Result{Events: ctx.EventManager().Events()}, nil +} + +func handleRevokeFee(ctx sdk.Context, k Keeper, msg MsgRevokeFeeAllowance) (*sdk.Result, error) { + k.RevokeFeeAllowance(ctx, msg.Granter, msg.Grantee) + return &sdk.Result{Events: ctx.EventManager().Events()}, nil +} diff --git a/x/feegrant/keeper/keeper.go b/x/feegrant/keeper/keeper.go new file mode 100644 index 000000000000..d3fb815f06a5 --- /dev/null +++ b/x/feegrant/keeper/keeper.go @@ -0,0 +1,162 @@ +package keeper + +import ( + "fmt" + + "github.com/tendermint/tendermint/libs/log" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/feegrant/types" +) + +// Keeper manages state of all fee grants, as well as calculating approval. +// It must have a codec with all available allowances registered. +type Keeper struct { + cdc codec.Marshaler + storeKey sdk.StoreKey +} + +// NewKeeper creates a fee grant Keeper +func NewKeeper(cdc codec.Marshaler, storeKey sdk.StoreKey) Keeper { + return Keeper{cdc: cdc, storeKey: storeKey} +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} + +// GrantFeeAllowance creates a new grant +func (k Keeper) GrantFeeAllowance(ctx sdk.Context, grant types.FeeAllowanceGrant) { + store := ctx.KVStore(k.storeKey) + key := types.FeeAllowanceKey(grant.Granter, grant.Grantee) + bz := k.cdc.MustMarshalBinaryBare(&grant) + store.Set(key, bz) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeSetFeeGrant, + sdk.NewAttribute(types.AttributeKeyGranter, grant.Granter.String()), + sdk.NewAttribute(types.AttributeKeyGrantee, grant.Grantee.String()), + ), + ) +} + +// RevokeFeeAllowance removes an existing grant +func (k Keeper) RevokeFeeAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress) { + store := ctx.KVStore(k.storeKey) + key := types.FeeAllowanceKey(granter, grantee) + + store.Delete(key) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeRevokeFeeGrant, + sdk.NewAttribute(types.AttributeKeyGranter, granter.String()), + sdk.NewAttribute(types.AttributeKeyGrantee, grantee.String()), + ), + ) +} + +// 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.FeeAllowance { + grant, found := k.GetFeeGrant(ctx, granter, grantee) + if !found { + return nil + } + + return grant.Allowance +} + +// GetFeeGrant returns entire grant between both accounts +func (k Keeper) GetFeeGrant(ctx sdk.Context, granter sdk.AccAddress, grantee sdk.AccAddress) (types.FeeAllowanceGrant, bool) { + store := ctx.KVStore(k.storeKey) + key := types.FeeAllowanceKey(granter, grantee) + bz := store.Get(key) + if len(bz) == 0 { + return types.FeeAllowanceGrant{}, false + } + + var feegrant types.FeeAllowanceGrant + k.cdc.MustUnmarshalBinaryBare(bz, &feegrant) + + return feegrant, true +} + +// IterateAllGranteeFeeAllowances iterates over all the grants from anyone to the given grantee. +// Callback to get all data, returns true to stop, false to keep reading +func (k Keeper) IterateAllGranteeFeeAllowances(ctx sdk.Context, grantee sdk.AccAddress, cb func(types.FeeAllowanceGrant) bool) error { + store := ctx.KVStore(k.storeKey) + prefix := types.FeeAllowancePrefixByGrantee(grantee) + iter := sdk.KVStorePrefixIterator(store, prefix) + defer iter.Close() + + stop := false + for ; iter.Valid() && !stop; iter.Next() { + bz := iter.Value() + + var feeGrant types.FeeAllowanceGrant + k.cdc.MustUnmarshalBinaryBare(bz, &feeGrant) + + stop = cb(feeGrant) + } + + return nil +} + +// IterateAllFeeAllowances iterates over all the grants in the store. +// Callback to get all data, returns true to stop, false to keep reading +// Calling this without pagination is very expensive and only designed for export genesis +func (k Keeper) IterateAllFeeAllowances(ctx sdk.Context, cb func(types.FeeAllowanceGrant) bool) error { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, types.FeeAllowanceKeyPrefix) + defer iter.Close() + + stop := false + for ; iter.Valid() && !stop; iter.Next() { + bz := iter.Value() + var feeGrant types.FeeAllowanceGrant + k.cdc.MustUnmarshalBinaryBare(bz, &feeGrant) + + stop = cb(feeGrant) + } + + return nil +} + +// 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 { + return sdkerrors.Wrapf(types.ErrNoAllowance, "grant missing") + } + + remove, err := grant.GetFeeGrant().Accept(fee, ctx.BlockTime(), ctx.BlockHeight()) + if err == nil { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeUseFeeGrant, + sdk.NewAttribute(types.AttributeKeyGranter, granter.String()), + sdk.NewAttribute(types.AttributeKeyGrantee, grantee.String()), + ), + ) + } + + if remove { + k.RevokeFeeAllowance(ctx, granter, grantee) + // note this returns nil if err == nil + return sdkerrors.Wrap(err, "removed grant") + } + + if err != nil { + return sdkerrors.Wrap(err, "invalid grant") + } + + // if we accepted, store the updated state of the allowance + k.GrantFeeAllowance(ctx, grant) + return nil +} diff --git a/x/feegrant/keeper/keeper_test.go b/x/feegrant/keeper/keeper_test.go new file mode 100644 index 000000000000..e579922999b6 --- /dev/null +++ b/x/feegrant/keeper/keeper_test.go @@ -0,0 +1,307 @@ +package keeper_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/suite" + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/libs/log" + dbm "github.com/tendermint/tm-db" + + codec "github.com/cosmos/cosmos-sdk/codec" + codecstd "github.com/cosmos/cosmos-sdk/codec/std" + "github.com/cosmos/cosmos-sdk/store" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" + "github.com/cosmos/cosmos-sdk/x/feegrant/types" +) + +type KeeperTestSuite struct { + suite.Suite + + cdc *codec.Codec + ctx sdk.Context + dk keeper.Keeper + + addr sdk.AccAddress + addr2 sdk.AccAddress + addr3 sdk.AccAddress + addr4 sdk.AccAddress +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) SetupTest() { + db := dbm.NewMemDB() + + cdc := codec.New() + types.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + suite.cdc = cdc + appCodec := codecstd.NewAppCodec(cdc) + delCapKey := sdk.NewKVStoreKey("delKey") + + ms := store.NewCommitMultiStore(db) + ms.MountStoreWithDB(delCapKey, sdk.StoreTypeIAVL, db) + ms.LoadLatestVersion() + + suite.dk = keeper.NewKeeper(appCodec, delCapKey) + suite.ctx = sdk.NewContext(ms, abci.Header{ChainID: "test-chain-id", Time: time.Now().UTC(), Height: 1234}, false, log.NewNopLogger()) + + suite.addr = mustAddr("cosmos157ez5zlaq0scm9aycwphhqhmg3kws4qusmekll") + suite.addr2 = mustAddr("cosmos1rjxwm0rwyuldsg00qf5lt26wxzzppjzxs2efdw") + suite.addr3 = mustAddr("cosmos1qk93t4j0yyzgqgt6k5qf8deh8fq6smpn3ntu3x") + suite.addr4 = mustAddr("cosmos1p9qh4ldfd6n0qehujsal4k7g0e37kel90rc4ts") + +} + +func mustAddr(acc string) sdk.AccAddress { + addr, err := sdk.AccAddressFromBech32(acc) + if err != nil { + panic(err) + } + return addr +} + +func (suite *KeeperTestSuite) TestKeeperCrud() { + ctx := suite.ctx + k := suite.dk + + // some helpers + atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555)) + eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 123)) + basic := &types.FeeAllowance{Sum: &types.FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &types.BasicFeeAllowance{ + SpendLimit: atom, + Expiration: types.ExpiresAtHeight(334455), + }, + }, + } + basic2 := &types.FeeAllowance{Sum: &types.FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &types.BasicFeeAllowance{ + SpendLimit: eth, + Expiration: types.ExpiresAtHeight(172436), + }, + }, + } + + // let's set up some initial state here + k.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: suite.addr, + Grantee: suite.addr2, + Allowance: basic, + }) + k.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: suite.addr, + Grantee: suite.addr3, + Allowance: basic2, + }) + k.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: suite.addr2, + Grantee: suite.addr3, + Allowance: basic, + }) + k.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: suite.addr2, + Grantee: suite.addr4, + Allowance: basic, + }) + k.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: suite.addr4, + Grantee: suite.addr, + Allowance: basic2, + }) + + // remove some, overwrite other + k.RevokeFeeAllowance(ctx, suite.addr, suite.addr2) + k.RevokeFeeAllowance(ctx, suite.addr, suite.addr3) + k.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: suite.addr, + Grantee: suite.addr3, + Allowance: basic, + }) + k.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: suite.addr2, + Grantee: suite.addr3, + Allowance: basic2, + }) + + // end state: + // addr -> addr3 (basic) + // addr2 -> addr3 (basic2), addr4(basic) + // addr4 -> addr (basic2) + + // then lots of queries + cases := map[string]struct { + grantee sdk.AccAddress + granter sdk.AccAddress + allowance *types.FeeAllowance + }{ + "addr revoked": { + granter: suite.addr, + grantee: suite.addr2, + }, + "addr revoked and added": { + granter: suite.addr, + grantee: suite.addr3, + allowance: basic, + }, + "addr never there": { + granter: suite.addr, + grantee: suite.addr4, + }, + "addr modified": { + granter: suite.addr2, + grantee: suite.addr3, + allowance: basic2, + }, + } + + for name, tc := range cases { + tc := tc + suite.Run(name, func() { + allow := k.GetFeeAllowance(ctx, tc.granter, tc.grantee) + if tc.allowance == nil { + suite.Nil(allow) + return + } + suite.NotNil(allow) + suite.Equal(tc.allowance, allow) + }) + } + + allCases := map[string]struct { + grantee sdk.AccAddress + grants []types.FeeAllowanceGrant + }{ + "addr2 has none": { + grantee: suite.addr2, + }, + "addr has one": { + grantee: suite.addr, + grants: []types.FeeAllowanceGrant{{Granter: suite.addr4, Grantee: suite.addr, Allowance: basic2}}, + }, + "addr3 has two": { + grantee: suite.addr3, + grants: []types.FeeAllowanceGrant{ + {Granter: suite.addr, Grantee: suite.addr3, Allowance: basic}, + {Granter: suite.addr2, Grantee: suite.addr3, Allowance: basic2}, + }, + }, + } + + for name, tc := range allCases { + tc := tc + suite.Run(name, func() { + var grants []types.FeeAllowanceGrant + err := k.IterateAllGranteeFeeAllowances(ctx, tc.grantee, func(grant types.FeeAllowanceGrant) bool { + grants = append(grants, grant) + return false + }) + suite.NoError(err) + suite.Equal(tc.grants, grants) + }) + } +} + +func (suite *KeeperTestSuite) TestUseGrantedFee() { + ctx := suite.ctx + k := suite.dk + + // some helpers + atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555)) + eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 123)) + future := &types.FeeAllowance{Sum: &types.FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &types.BasicFeeAllowance{ + SpendLimit: atom, + Expiration: types.ExpiresAtHeight(5678), + }, + }, + } + + expired := &types.FeeAllowance{Sum: &types.FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &types.BasicFeeAllowance{ + SpendLimit: eth, + Expiration: types.ExpiresAtHeight(55), + }, + }, + } + + // for testing limits of the contract + hugeAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 9999)) + smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 1)) + futureAfterSmall := &types.FeeAllowance{Sum: &types.FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &types.BasicFeeAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 554)), + Expiration: types.ExpiresAtHeight(5678), + }, + }, + } + + // then lots of queries + cases := map[string]struct { + grantee sdk.AccAddress + granter sdk.AccAddress + fee sdk.Coins + allowed bool + final *types.FeeAllowance + }{ + "use entire pot": { + granter: suite.addr, + grantee: suite.addr2, + fee: atom, + allowed: true, + final: nil, + }, + "expired and removed": { + granter: suite.addr, + grantee: suite.addr3, + fee: eth, + allowed: false, + final: nil, + }, + "too high": { + granter: suite.addr, + grantee: suite.addr2, + fee: hugeAtom, + allowed: false, + final: future, + }, + "use a little": { + granter: suite.addr, + grantee: suite.addr2, + fee: smallAtom, + allowed: true, + final: futureAfterSmall, + }, + } + + for name, tc := range cases { + tc := tc + suite.Run(name, func() { + // let's set up some initial state here + // addr -> addr2 (future) + // addr -> addr3 (expired) + + k.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: suite.addr, + Grantee: suite.addr2, + Allowance: future, + }) + k.GrantFeeAllowance(ctx, types.FeeAllowanceGrant{ + Granter: suite.addr, + Grantee: suite.addr3, + Allowance: expired, + }) + + err := k.UseGrantedFees(ctx, tc.granter, tc.grantee, tc.fee) + if tc.allowed { + suite.NoError(err) + } else { + suite.Error(err) + } + + loaded := k.GetFeeAllowance(ctx, tc.granter, tc.grantee) + + suite.Equal(tc.final, loaded) + }) + } +} diff --git a/x/feegrant/keeper/querier.go b/x/feegrant/keeper/querier.go new file mode 100644 index 000000000000..41b0ab7970c4 --- /dev/null +++ b/x/feegrant/keeper/querier.go @@ -0,0 +1,62 @@ +package keeper + +import ( + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/feegrant/types" +) + +const ( + QueryGetFeeAllowances = "fees" +) + +// NewQuerier creates a new querier +func NewQuerier(keeper Keeper) sdk.Querier { + return func(ctx sdk.Context, path []string, req abci.RequestQuery) ([]byte, error) { + var ( + res []byte + err error + ) + + switch path[0] { + case QueryGetFeeAllowances: + res, err = queryGetFeeAllowances(ctx, path[1:], keeper) + + default: + err = sdkerrors.Wrapf(sdkerrors.ErrUnknownRequest, "unknown %s query endpoint: %s", types.ModuleName, path[0]) + } + + return res, err + } +} + +func queryGetFeeAllowances(ctx sdk.Context, args []string, keeper Keeper) ([]byte, error) { + grantee := args[0] + granteeAddr, err := sdk.AccAddressFromBech32(grantee) + if err != nil { + return nil, sdkerrors.Wrapf(err, "invalid address") + } + + var grants []types.FeeAllowanceGrant + err = keeper.IterateAllGranteeFeeAllowances(ctx, granteeAddr, func(grant types.FeeAllowanceGrant) bool { + grants = append(grants, grant) + return false + }) + if err != nil { + return nil, err + } + + if grants == nil { + return []byte("[]"), nil + } + + bz, err := codec.MarshalJSONIndent(keeper.cdc, grants) + if err != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrJSONMarshal, err.Error()) + } + + return bz, nil +} diff --git a/x/feegrant/keeper/querier_test.go b/x/feegrant/keeper/querier_test.go new file mode 100644 index 000000000000..3efd772b33f7 --- /dev/null +++ b/x/feegrant/keeper/querier_test.go @@ -0,0 +1,82 @@ +package keeper_test + +import ( + abci "github.com/tendermint/tendermint/abci/types" + + codec "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/feegrant/keeper" + "github.com/cosmos/cosmos-sdk/x/feegrant/types" +) + +func (suite *KeeperTestSuite) TestQuery() { + ctx := suite.ctx + k := suite.dk + + cdc := codec.New() + types.RegisterCodec(cdc) + + // some helpers + grant1 := types.FeeAllowanceGrant{ + Granter: suite.addr, + Grantee: suite.addr3, + Allowance: &types.FeeAllowance{Sum: &types.FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &types.BasicFeeAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 555)), + Expiration: types.ExpiresAtHeight(334455), + }, + }, + }, + } + grant2 := types.FeeAllowanceGrant{ + Granter: suite.addr2, + Grantee: suite.addr3, + Allowance: &types.FeeAllowance{Sum: &types.FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &types.BasicFeeAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("eth", 123)), + Expiration: types.ExpiresAtHeight(334455)}}}, + } + // let's set up some initial state here + k.GrantFeeAllowance(ctx, grant1) + k.GrantFeeAllowance(ctx, grant2) + + // now try some queries + cases := map[string]struct { + path []string + valid bool + res []types.FeeAllowanceGrant + }{ + "bad path": { + path: []string{"foo", "bar"}, + }, + "no data": { + // addr in bech32 + path: []string{"fees", "cosmos157ez5zlaq0scm9aycwphhqhmg3kws4qusmekll"}, + valid: true, + }, + "two grants": { + // addr3 in bech32 + path: []string{"fees", "cosmos1qk93t4j0yyzgqgt6k5qf8deh8fq6smpn3ntu3x"}, + valid: true, + res: []types.FeeAllowanceGrant{grant1, grant2}, + }, + } + + querier := keeper.NewQuerier(k) + for name, tc := range cases { + tc := tc + suite.Run(name, func() { + bz, err := querier(ctx, tc.path, abci.RequestQuery{}) + if !tc.valid { + suite.Error(err) + return + } + suite.NoError(err) + + var grants []types.FeeAllowanceGrant + serr := cdc.UnmarshalJSON(bz, &grants) + suite.NoError(serr) + + suite.Equal(tc.res, grants) + }) + } + +} diff --git a/x/feegrant/module.go b/x/feegrant/module.go new file mode 100644 index 000000000000..72699c3c5043 --- /dev/null +++ b/x/feegrant/module.go @@ -0,0 +1,156 @@ +package feegrant + +import ( + "encoding/json" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} +) + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic defines the basic application module used by the feegrant module. +type AppModuleBasic struct{} + +// Name returns the feegrant module's name. +func (AppModuleBasic) Name() string { + return ModuleName +} + +// RegisterCodec registers the feegrant module's types for the given codec. +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + RegisterCodec(cdc) +} + +// DefaultGenesis returns default genesis state as raw bytes for the feegrant +// module. +func (AppModuleBasic) DefaultGenesis(_ codec.JSONMarshaler) json.RawMessage { + return []byte("[]") +} + +// ValidateGenesis performs genesis state validation for the feegrant module. +func (a AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, bz json.RawMessage) error { + _, err := a.getValidatedGenesis(cdc, bz) + return err +} + +func (a AppModuleBasic) getValidatedGenesis(cdc codec.JSONMarshaler, bz json.RawMessage) (GenesisState, error) { + var data GenesisState + + err := cdc.UnmarshalJSON(bz, &data) + if err != nil { + return nil, err + } + + return data, data.ValidateBasic() +} + +// RegisterRESTRoutes registers the REST routes for the feegrant module. +func (AppModuleBasic) RegisterRESTRoutes(ctx context.CLIContext, rtr *mux.Router) { + // TODO + // rest.RegisterRoutes(ctx, rtr) +} + +// GetTxCmd returns the root tx command for the feegrant module. +func (AppModuleBasic) GetTxCmd(_ *codec.Codec) *cobra.Command { + // TODO + return nil +} + +// GetQueryCmd returns no root query command for the feegrant module. +func (AppModuleBasic) GetQueryCmd(cdc *codec.Codec) *cobra.Command { + // TODO + return nil +} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements an application module for the feegrant module. +type AppModule struct { + AppModuleBasic + keeper Keeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(keeper Keeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + keeper: keeper, + } +} + +// Name returns the feegrant module's name. +func (AppModule) Name() string { + return ModuleName +} + +// RegisterInvariants registers the feegrant module invariants. +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {} + +// Route returns the message routing key for the feegrant module. +func (AppModule) Route() string { + return RouterKey +} + +// NewHandler returns an sdk.Handler for the feegrant module. +func (am AppModule) NewHandler() sdk.Handler { + return NewHandler(am.keeper) +} + +// QuerierRoute returns the feegrant module's querier route name. +func (AppModule) QuerierRoute() string { + return QuerierRoute +} + +// NewQuerierHandler returns the feegrant module sdk.Querier. +func (am AppModule) NewQuerierHandler() sdk.Querier { + return NewQuerier(am.keeper) +} + +// InitGenesis performs genesis initialization for the feegrant module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, bz json.RawMessage) []abci.ValidatorUpdate { + genesisState, err := am.getValidatedGenesis(cdc, bz) + if err != nil { + panic(err) + } + + InitGenesis(ctx, am.keeper, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the feegrant +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json.RawMessage { + gs, err := ExportGenesis(ctx, am.keeper) + if err != nil { + panic(err) + } + + return cdc.MustMarshalJSON(gs) +} + +// BeginBlock returns the begin blocker for the feegrant module. +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock returns the end blocker for the feegrant module. It returns no validator +// updates. +func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/feegrant/types/basic_fee.go b/x/feegrant/types/basic_fee.go new file mode 100644 index 000000000000..52c079ca64ee --- /dev/null +++ b/x/feegrant/types/basic_fee.go @@ -0,0 +1,55 @@ +package types + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +var _ FeeAllowanceI = (*BasicFeeAllowance)(nil) + +// Accept can use fee payment requested as well as timestamp/height of the current block +// to determine whether or not to process this. This is checked in +// Keeper.UseGrantedFees and the return values should match how it is handled there. +// +// If it returns an error, the fee payment is rejected, otherwise it is accepted. +// The FeeAllowance implementation is expected to update it's internal state +// and will be saved again after an acceptance. +// +// 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) { + if a.Expiration.IsExpired(blockTime, blockHeight) { + return true, sdkerrors.Wrap(ErrFeeLimitExpired, "basic allowance") + } + + left, invalid := a.SpendLimit.SafeSub(fee) + if invalid { + return false, sdkerrors.Wrap(ErrFeeLimitExceeded, "basic allowance") + } + + a.SpendLimit = left + return left.IsZero(), nil +} + +// 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 *BasicFeeAllowance) PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceI { + return &BasicFeeAllowance{ + SpendLimit: a.SpendLimit, + Expiration: a.Expiration.PrepareForExport(dumpTime, dumpHeight), + } +} + +// ValidateBasic implements FeeAllowance and enforces basic sanity checks +func (a BasicFeeAllowance) ValidateBasic() error { + if !a.SpendLimit.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "send amount is invalid: %s", a.SpendLimit) + } + if !a.SpendLimit.IsAllPositive() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "spend limit must be positive") + } + return a.Expiration.ValidateBasic() +} diff --git a/x/feegrant/types/basic_fee_test.go b/x/feegrant/types/basic_fee_test.go new file mode 100644 index 000000000000..887fa5fbf3c5 --- /dev/null +++ b/x/feegrant/types/basic_fee_test.go @@ -0,0 +1,109 @@ +package types + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestBasicFeeValidAllow(t *testing.T) { + eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 10)) + atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555)) + smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 43)) + leftAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 512)) + + cases := map[string]struct { + allow *BasicFeeAllowance + // all other checks are ignored if valid=false + fee sdk.Coins + blockTime time.Time + blockHeight int64 + valid bool + accept bool + remove bool + remains sdk.Coins + }{ + "empty": { + allow: &BasicFeeAllowance{}, + valid: false, + }, + "small fee": { + allow: &BasicFeeAllowance{ + SpendLimit: atom, + }, + valid: true, + fee: smallAtom, + accept: true, + remove: false, + remains: leftAtom, + }, + "all fee": { + allow: &BasicFeeAllowance{ + SpendLimit: smallAtom, + }, + valid: true, + fee: smallAtom, + accept: true, + remove: true, + }, + "wrong fee": { + allow: &BasicFeeAllowance{ + SpendLimit: smallAtom, + }, + valid: true, + fee: eth, + accept: false, + }, + "non-expired": { + allow: &BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + valid: true, + fee: smallAtom, + blockHeight: 85, + accept: true, + remove: false, + remains: leftAtom, + }, + "expired": { + allow: &BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + valid: true, + fee: smallAtom, + blockHeight: 121, + accept: false, + remove: true, + }, + } + + for name, stc := range cases { + tc := stc // to make scopelint happy + t.Run(name, func(t *testing.T) { + err := tc.allow.ValidateBasic() + if !tc.valid { + require.Error(t, err) + return + } + require.NoError(t, err) + + // now try to deduct + remove, err := tc.allow.Accept(tc.fee, tc.blockTime, tc.blockHeight) + if !tc.accept { + require.Error(t, err) + return + } + require.NoError(t, err) + + require.Equal(t, tc.remove, remove) + if !remove { + assert.Equal(t, tc.allow.SpendLimit, tc.remains) + } + }) + } +} diff --git a/x/feegrant/types/codec.go b/x/feegrant/types/codec.go new file mode 100644 index 000000000000..a051889d6b2d --- /dev/null +++ b/x/feegrant/types/codec.go @@ -0,0 +1,27 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/codec" +) + +// RegisterCodec registers the account types and interface +func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterInterface((*isFeeAllowance_Sum)(nil), nil) + cdc.RegisterInterface((*FeeAllowanceI)(nil), nil) + cdc.RegisterConcrete(&FeeAllowance_BasicFeeAllowance{}, "cosmos-sdk/ProtoBasicFeeAllowance", nil) + cdc.RegisterConcrete(&BasicFeeAllowance{}, "cosmos-sdk/BasicFeeAllowance", nil) + cdc.RegisterConcrete(&PeriodicFeeAllowance{}, "cosmos-sdk/PeriodicFeeAllowance", nil) + cdc.RegisterConcrete(FeeGrantTx{}, "cosmos-sdk/FeeGrantTx", nil) +} + +var ( + amino = codec.New() + + ModuleCdc = codec.NewHybridCodec(amino) +) + +func init() { + RegisterCodec(amino) + codec.RegisterCrypto(amino) + amino.Seal() +} diff --git a/x/feegrant/types/errors.go b/x/feegrant/types/errors.go new file mode 100644 index 000000000000..9b824253ae9d --- /dev/null +++ b/x/feegrant/types/errors.go @@ -0,0 +1,21 @@ +package types + +import ( + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// Codes for governance errors +const ( + DefaultCodespace = ModuleName +) + +var ( + // ErrFeeLimitExceeded error if there are not enough allowance to cover the fees + ErrFeeLimitExceeded = sdkerrors.Register(DefaultCodespace, 2, "fee limit exceeded") + // ErrFeeLimitExpired error if the allowance has expired + ErrFeeLimitExpired = sdkerrors.Register(DefaultCodespace, 3, "fee limit expired") + // ErrInvalidDuration error if the Duration is invalid or doesn't match the expiration + ErrInvalidDuration = sdkerrors.Register(DefaultCodespace, 4, "invalid duration") + // ErrNoAllowance error if there is no allowance for that pair + ErrNoAllowance = sdkerrors.Register(DefaultCodespace, 5, "no allowance") +) diff --git a/x/feegrant/types/events.go b/x/feegrant/types/events.go new file mode 100644 index 000000000000..28caa2e6c9f8 --- /dev/null +++ b/x/feegrant/types/events.go @@ -0,0 +1,11 @@ +package types + +// evidence module events +const ( + EventTypeUseFeeGrant = "use_feegrant" + EventTypeRevokeFeeGrant = "revoke_feegrant" + EventTypeSetFeeGrant = "set_feegrant" + + AttributeKeyGranter = "granter" + AttributeKeyGrantee = "grantee" +) diff --git a/x/feegrant/types/expected_keepers.go b/x/feegrant/types/expected_keepers.go new file mode 100644 index 000000000000..e60e5ad08171 --- /dev/null +++ b/x/feegrant/types/expected_keepers.go @@ -0,0 +1,21 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + auth "github.com/cosmos/cosmos-sdk/x/auth/exported" + supply "github.com/cosmos/cosmos-sdk/x/supply/exported" +) + +// SupplyKeeper defines the expected supply Keeper (noalias) +type SupplyKeeper interface { + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error + GetModuleAccount(ctx sdk.Context, moduleName string) supply.ModuleAccountI + GetModuleAddress(moduleName string) sdk.AccAddress +} + +// SupplyKeeper defines the expected auth Account Keeper (noalias) +type AccountKeeper interface { + NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) auth.Account + GetAccount(ctx sdk.Context, addr sdk.AccAddress) auth.Account + SetAccount(ctx sdk.Context, acc auth.Account) +} diff --git a/x/feegrant/types/expiration.go b/x/feegrant/types/expiration.go new file mode 100644 index 000000000000..2bfdc3e412d0 --- /dev/null +++ b/x/feegrant/types/expiration.go @@ -0,0 +1,124 @@ +package types + +import ( + "time" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// ExpiresAtTime creates an expiration at the given time +func ExpiresAtTime(t time.Time) ExpiresAt { + return ExpiresAt{Time: t} +} + +// ExpiresAtHeight creates an expiration at the given height +func ExpiresAtHeight(h int64) ExpiresAt { + return ExpiresAt{Height: h} +} + +// ValidateBasic performs basic sanity checks. +// Note that empty expiration is allowed +func (e ExpiresAt) ValidateBasic() error { + if !e.Time.IsZero() && e.Height != 0 { + return sdkerrors.Wrap(ErrInvalidDuration, "both time and height are set") + } + if e.Height < 0 { + return sdkerrors.Wrap(ErrInvalidDuration, "negative height") + } + return nil +} + +// IsZero returns true for an uninitialized struct +func (e ExpiresAt) IsZero() bool { + return e.Time.IsZero() && e.Height == 0 +} + +// FastForward produces a new Expiration with the time or height set to the +// new value, depending on what was set on the original expiration +func (e ExpiresAt) FastForward(t time.Time, h int64) ExpiresAt { + if !e.Time.IsZero() { + return ExpiresAtTime(t) + } + return ExpiresAtHeight(h) +} + +// IsExpired returns if the time or height is *equal to* or greater +// than the defined expiration point. Note that it is expired upon +// an exact match. +// +// Note a "zero" ExpiresAt is never expired +func (e ExpiresAt) IsExpired(t time.Time, h int64) bool { + if !e.Time.IsZero() && !t.Before(e.Time) { + return true + } + return e.Height != 0 && h >= e.Height +} + +// IsCompatible returns true iff the two use the same units. +// If false, they cannot be added. +func (e ExpiresAt) IsCompatible(d Duration) bool { + if !e.Time.IsZero() { + return d.Clock > 0 + } + return d.Block > 0 +} + +// Step will increase the expiration point by one Duration +// It returns an error if the Duration is incompatible +func (e ExpiresAt) Step(d Duration) (ExpiresAt, error) { + if !e.IsCompatible(d) { + return ExpiresAt{}, sdkerrors.Wrap(ErrInvalidDuration, "expiration time and provided duration have different units") + } + if !e.Time.IsZero() { + e.Time = e.Time.Add(d.Clock) + } else { + e.Height += d.Block + } + return e, nil +} + +// MustStep is like Step, but panics on error +func (e ExpiresAt) MustStep(d Duration) ExpiresAt { + res, err := e.Step(d) + if err != nil { + panic(err) + } + return res +} + +// PrepareForExport will deduct the dumpHeight from the expiration, so when this is +// reloaded after a hard fork, the actual number of allowed blocks is constant +func (e ExpiresAt) PrepareForExport(dumpTime time.Time, dumpHeight int64) ExpiresAt { + if e.Height != 0 { + e.Height -= dumpHeight + } + return e +} + +// ClockDuration creates an Duration by clock time +func ClockDuration(d time.Duration) Duration { + return Duration{Clock: d} +} + +// BlockDuration creates an Duration by block height +func BlockDuration(h int64) Duration { + return Duration{Block: h} +} + +// ValidateBasic performs basic sanity checks +// Note that exactly one must be set and it must be positive +func (d Duration) ValidateBasic() error { + if d.Block == 0 && d.Clock == 0 { + return sdkerrors.Wrap(ErrInvalidDuration, "neither time and height are set") + } + if d.Block != 0 && d.Clock != 0 { + return sdkerrors.Wrap(ErrInvalidDuration, "both time and height are set") + } + if d.Block < 0 { + return sdkerrors.Wrap(ErrInvalidDuration, "negative block step") + } + if d.Clock < 0 { + return sdkerrors.Wrap(ErrInvalidDuration, "negative clock step") + } + return nil +} diff --git a/x/feegrant/types/expiration_test.go b/x/feegrant/types/expiration_test.go new file mode 100644 index 000000000000..6cab366eabdc --- /dev/null +++ b/x/feegrant/types/expiration_test.go @@ -0,0 +1,172 @@ +package types + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestExpiresAt(t *testing.T) { + now := time.Now() + + cases := map[string]struct { + example ExpiresAt + valid bool + zero bool + before ExpiresAt + after ExpiresAt + }{ + "basic": { + example: ExpiresAtHeight(100), + valid: true, + before: ExpiresAt{Height: 50, Time: now}, + after: ExpiresAt{Height: 122, Time: now}, + }, + "zero": { + example: ExpiresAt{}, + zero: true, + valid: true, + before: ExpiresAt{Height: 1}, + }, + "double": { + example: ExpiresAt{Height: 100, Time: now}, + valid: false, + }, + "match height": { + example: ExpiresAtHeight(1000), + valid: true, + before: ExpiresAt{Height: 999, Time: now}, + after: ExpiresAt{Height: 1000, Time: now}, + }, + "match time": { + example: ExpiresAtTime(now), + valid: true, + before: ExpiresAt{Height: 43, Time: now.Add(-1 * time.Second)}, + after: ExpiresAt{Height: 76, Time: now}, + }, + } + + for name, stc := range cases { + tc := stc // to make scopelint happy + t.Run(name, func(t *testing.T) { + err := tc.example.ValidateBasic() + assert.Equal(t, tc.zero, tc.example.IsZero()) + if !tc.valid { + require.Error(t, err) + return + } + require.NoError(t, err) + + if !tc.before.IsZero() { + assert.Equal(t, false, tc.example.IsExpired(tc.before.Time, tc.before.Height)) + } + if !tc.after.IsZero() { + assert.Equal(t, true, tc.example.IsExpired(tc.after.Time, tc.after.Height)) + } + }) + } +} + +func TestDurationValid(t *testing.T) { + now := time.Now() + + cases := map[string]struct { + period Duration + valid bool + compatible ExpiresAt + incompatible ExpiresAt + }{ + "basic height": { + period: BlockDuration(100), + valid: true, + compatible: ExpiresAtHeight(50), + incompatible: ExpiresAtTime(now), + }, + "basic time": { + period: ClockDuration(time.Hour), + valid: true, + compatible: ExpiresAtTime(now), + incompatible: ExpiresAtHeight(50), + }, + "zero": { + period: Duration{}, + valid: false, + }, + "double": { + period: Duration{Block: 100, Clock: time.Hour}, + valid: false, + }, + "negative clock": { + period: ClockDuration(-1 * time.Hour), + valid: false, + }, + "negative block": { + period: BlockDuration(-5), + valid: false, + }, + } + + for name, stc := range cases { + tc := stc // to make scopelint happy + t.Run(name, func(t *testing.T) { + err := tc.period.ValidateBasic() + if !tc.valid { + require.Error(t, err) + return + } + require.NoError(t, err) + + assert.Equal(t, true, tc.compatible.IsCompatible(tc.period)) + assert.Equal(t, false, tc.incompatible.IsCompatible(tc.period)) + }) + } +} + +func TestDurationStep(t *testing.T) { + now := time.Now() + + cases := map[string]struct { + expires ExpiresAt + period Duration + valid bool + result ExpiresAt + }{ + "add height": { + expires: ExpiresAtHeight(789), + period: BlockDuration(100), + valid: true, + result: ExpiresAtHeight(889), + }, + "add time": { + expires: ExpiresAtTime(now), + period: ClockDuration(time.Hour), + valid: true, + result: ExpiresAtTime(now.Add(time.Hour)), + }, + "mismatch": { + expires: ExpiresAtHeight(789), + period: ClockDuration(time.Hour), + valid: false, + }, + } + + for name, stc := range cases { + tc := stc // to make scopelint happy + t.Run(name, func(t *testing.T) { + err := tc.period.ValidateBasic() + require.NoError(t, err) + err = tc.expires.ValidateBasic() + require.NoError(t, err) + + next, err := tc.expires.Step(tc.period) + if !tc.valid { + require.Error(t, err) + return + } + require.NoError(t, err) + require.Equal(t, tc.result, next) + }) + } +} diff --git a/x/feegrant/types/fees.go b/x/feegrant/types/fees.go new file mode 100644 index 000000000000..cc526628f5f9 --- /dev/null +++ b/x/feegrant/types/fees.go @@ -0,0 +1,32 @@ +package types + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// FeeAllowance implementations are tied to a given fee delegator and delegatee, +// and are used to enforce fee grant limits. +type FeeAllowanceI interface { + // Accept can use fee payment requested as well as timestamp/height of the current block + // to determine whether or not to process this. This is checked in + // Keeper.UseGrantedFees and the return values should match how it is handled there. + // + // If it returns an error, the fee payment is rejected, otherwise it is accepted. + // The FeeAllowance implementation is expected to update it's internal state + // and will be saved again after an acceptance. + // + // 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) + + // 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 + // given the time and height of the actual dump (may safely return self if no changes needed) + PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceI + + // ValidateBasic should evaluate this FeeAllowance for internal consistency. + // Don't allow negative amounts, or negative periods for example. + ValidateBasic() error +} diff --git a/x/feegrant/types/grant.go b/x/feegrant/types/grant.go new file mode 100644 index 000000000000..a3c6b142ef49 --- /dev/null +++ b/x/feegrant/types/grant.go @@ -0,0 +1,37 @@ +package types + +import ( + "time" + + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +// ValidateBasic performs basic validation on +// FeeAllowanceGrant +func (a FeeAllowanceGrant) ValidateBasic() error { + if a.Granter.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing granter address") + } + if a.Grantee.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing grantee address") + } + if a.Grantee.Equals(a.Granter) { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "cannot self-grant fee authorization") + } + + return a.GetFeeGrant().ValidateBasic() +} + +func (a FeeAllowanceGrant) GetFeeGrant() FeeAllowanceI { + return a.Allowance.GetFeeAllowanceI() +} + +// PrepareForExport will m ake 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 { + err := a.GetFeeGrant().PrepareForExport(dumpTime, dumpHeight) + if err != nil { + return FeeAllowanceGrant{} + } + return a +} diff --git a/x/feegrant/types/grant_test.go b/x/feegrant/types/grant_test.go new file mode 100644 index 000000000000..a82b15ecd9f1 --- /dev/null +++ b/x/feegrant/types/grant_test.go @@ -0,0 +1,109 @@ +package types + +import ( + "testing" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestGrant(t *testing.T) { + addr, err := sdk.AccAddressFromBech32("cosmos1qk93t4j0yyzgqgt6k5qf8deh8fq6smpn3ntu3x") + require.NoError(t, err) + addr2, err := sdk.AccAddressFromBech32("cosmos1p9qh4ldfd6n0qehujsal4k7g0e37kel90rc4ts") + require.NoError(t, err) + atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555)) + + cdc := codec.New() + RegisterCodec(cdc) + + cases := map[string]struct { + grant FeeAllowanceGrant + valid bool + }{ + "good": { + grant: FeeAllowanceGrant{ + Grantee: addr, + Granter: addr2, + Allowance: &FeeAllowance{Sum: &FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + }, + }, + }, + valid: true, + }, + "no grantee": { + grant: FeeAllowanceGrant{ + Granter: addr2, + Allowance: &FeeAllowance{Sum: &FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + }, + }, + }, + }, + "no granter": { + grant: FeeAllowanceGrant{ + Grantee: addr2, + Allowance: &FeeAllowance{Sum: &FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + }, + }, + }, + }, + "self-grant": { + grant: FeeAllowanceGrant{ + Grantee: addr2, + Granter: addr2, + Allowance: &FeeAllowance{Sum: &FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + }, + }, + }, + }, + "bad allowance": { + grant: FeeAllowanceGrant{ + Grantee: addr, + Granter: addr2, + Allowance: &FeeAllowance{Sum: &FeeAllowance_BasicFeeAllowance{BasicFeeAllowance: &BasicFeeAllowance{ + Expiration: ExpiresAtHeight(0), + }, + }, + }, + }, + }, + } + + for name, tc := range cases { + tc := tc + t.Run(name, func(t *testing.T) { + err := tc.grant.ValidateBasic() + if !tc.valid { + require.Error(t, err) + return + } + require.NoError(t, err) + + // if it is valid, let's try to serialize, deserialize, and make sure it matches + bz, err := cdc.MarshalBinaryBare(tc.grant) + require.NoError(t, err) + var loaded FeeAllowanceGrant + err = cdc.UnmarshalBinaryBare(bz, &loaded) + require.NoError(t, err) + + err = tc.grant.ValidateBasic() + require.NoError(t, err) + assert.Equal(t, tc.grant, loaded) + }) + } + +} diff --git a/x/feegrant/types/key.go b/x/feegrant/types/key.go new file mode 100644 index 000000000000..675cb41097eb --- /dev/null +++ b/x/feegrant/types/key.go @@ -0,0 +1,37 @@ +package types + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" +) + +const ( + // ModuleName is the module name constant used in many places + ModuleName = "feegrant" + + // StoreKey is the store key string for supply + StoreKey = ModuleName + + // RouterKey is the message route for supply + RouterKey = ModuleName + + // QuerierRoute is the querier route for supply + QuerierRoute = ModuleName +) + +var ( + // FeeAllowanceKeyPrefix is the set of the kvstore for fee allowance data + FeeAllowanceKeyPrefix = []byte{0x00} +) + +// FeeAllowanceKey is the canonical key to store a grant from granter to grantee +// We store by grantee first to allow searching by everyone who granted to you +func FeeAllowanceKey(granter sdk.AccAddress, grantee sdk.AccAddress) []byte { + return append(FeeAllowanceKeyPrefix, []byte(fmt.Sprintf("%s/%s", grantee, granter))...) +} + +// FeeAllowancePrefixByGrantee returns a prefix to scan for all grants to this given address. +func FeeAllowancePrefixByGrantee(grantee sdk.AccAddress) []byte { + return append(FeeAllowanceKeyPrefix, []byte(fmt.Sprintf("%s/", grantee))...) +} diff --git a/x/feegrant/types/msgs.go b/x/feegrant/types/msgs.go new file mode 100644 index 000000000000..3f7f1690cd36 --- /dev/null +++ b/x/feegrant/types/msgs.go @@ -0,0 +1,76 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +func (msg MsgGrantFeeAllowance) NewMsgGrantFeeAllowance(feeAllowance *FeeAllowance, granter, grantee sdk.AccAddress) (MsgGrantFeeAllowance, error) { + return MsgGrantFeeAllowance{ + Granter: granter, + Grantee: grantee, + Allowance: feeAllowance, + }, nil +} + +func (msg MsgGrantFeeAllowance) GetFeeGrant() FeeAllowanceI { + return msg.Allowance.GetFeeAllowanceI() +} + +func (msg MsgGrantFeeAllowance) Route() string { + return RouterKey +} + +func (msg MsgGrantFeeAllowance) Type() string { + return "grant-fee-allowance" +} + +func (msg MsgGrantFeeAllowance) ValidateBasic() error { + if msg.Granter.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing granter address") + } + if msg.Grantee.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing grantee address") + } + + return nil +} + +func (msg MsgGrantFeeAllowance) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +func (msg MsgGrantFeeAllowance) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Granter} +} + +func NewMsgRevokeFeeAllowance(granter sdk.AccAddress, grantee sdk.AccAddress) MsgRevokeFeeAllowance { + return MsgRevokeFeeAllowance{Granter: granter, Grantee: grantee} +} + +func (msg MsgRevokeFeeAllowance) Route() string { + return RouterKey +} + +func (msg MsgRevokeFeeAllowance) Type() string { + return "revoke-fee-allowance" +} + +func (msg MsgRevokeFeeAllowance) ValidateBasic() error { + if msg.Granter.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing granter address") + } + if msg.Grantee.Empty() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing grantee address") + } + + return nil +} + +func (msg MsgRevokeFeeAllowance) GetSignBytes() []byte { + return sdk.MustSortJSON(ModuleCdc.MustMarshalJSON(msg)) +} + +func (msg MsgRevokeFeeAllowance) GetSigners() []sdk.AccAddress { + return []sdk.AccAddress{msg.Granter} +} diff --git a/x/feegrant/types/periodic_fee.go b/x/feegrant/types/periodic_fee.go new file mode 100644 index 000000000000..8108a70f4402 --- /dev/null +++ b/x/feegrant/types/periodic_fee.go @@ -0,0 +1,115 @@ +package types + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" +) + +var _ FeeAllowanceI = (*PeriodicFeeAllowance)(nil) + +// Accept can use fee payment requested as well as timestamp/height of the current block +// to determine whether or not to process this. This is checked in +// Keeper.UseGrantedFees and the return values should match how it is handled there. +// +// If it returns an error, the fee payment is rejected, otherwise it is accepted. +// The FeeAllowance implementation is expected to update it's internal state +// and will be saved again after an acceptance. +// +// 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) { + if a.Basic.Expiration.IsExpired(blockTime, blockHeight) { + return true, sdkerrors.Wrap(ErrFeeLimitExpired, "absolute limit") + } + + a.TryResetPeriod(blockTime, blockHeight) + + // deduct from both the current period and the max amount + var isNeg bool + a.PeriodCanSpend, isNeg = a.PeriodCanSpend.SafeSub(fee) + if isNeg { + return false, sdkerrors.Wrap(ErrFeeLimitExceeded, "period limit") + } + a.Basic.SpendLimit, isNeg = a.Basic.SpendLimit.SafeSub(fee) + if isNeg { + return false, sdkerrors.Wrap(ErrFeeLimitExceeded, "absolute limit") + } + + return a.Basic.SpendLimit.IsZero(), nil +} + +// TryResetPeriod will check if the PeriodReset has been hit. If not, it is a no-op. +// If we hit the reset period, it will top up the PeriodCanSpend amount to +// min(PeriodicSpendLimit, a.Basic.SpendLimit) so it is never more than the maximum allowed. +// It will also update the PeriodReset. If we are within one Period, it will update from the +// last PeriodReset (eg. if you always do one tx per day, it will always reset the same time) +// If we are more then one period out (eg. no activity in a week), reset is one Period from the execution of this method +func (a *PeriodicFeeAllowance) TryResetPeriod(blockTime time.Time, blockHeight int64) { + if !a.PeriodReset.IsZero() && !a.PeriodReset.IsExpired(blockTime, blockHeight) { + return + } + // set CanSpend to the lesser of PeriodSpendLimit and the TotalLimit + if _, isNeg := a.Basic.SpendLimit.SafeSub(a.PeriodSpendLimit); isNeg { + a.PeriodCanSpend = a.Basic.SpendLimit + } else { + a.PeriodCanSpend = a.PeriodSpendLimit + } + + // If we are within the period, step from expiration (eg. if you always do one tx per day, it will always reset the same time) + // If we are more then one period out (eg. no activity in a week), reset is one period from this time + a.PeriodReset = a.PeriodReset.MustStep(a.Period) + if a.PeriodReset.IsExpired(blockTime, blockHeight) { + a.PeriodReset = a.PeriodReset.FastForward(blockTime, blockHeight).MustStep(a.Period) + } +} + +// 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. +// (For PeriodReset and Basic.Expiration) +func (a *PeriodicFeeAllowance) PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceI { + return &PeriodicFeeAllowance{ + Basic: BasicFeeAllowance{ + SpendLimit: a.Basic.SpendLimit, + Expiration: a.Basic.Expiration.PrepareForExport(dumpTime, dumpHeight), + }, + PeriodSpendLimit: a.PeriodSpendLimit, + PeriodCanSpend: a.PeriodCanSpend, + Period: a.Period, + PeriodReset: a.PeriodReset.PrepareForExport(dumpTime, dumpHeight), + } +} + +// ValidateBasic implements FeeAllowance and enforces basic sanity checks +func (a PeriodicFeeAllowance) ValidateBasic() error { + if err := a.Basic.ValidateBasic(); err != nil { + return err + } + + if !a.PeriodSpendLimit.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "spend amount is invalid: %s", a.PeriodSpendLimit) + } + if !a.PeriodSpendLimit.IsAllPositive() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "spend limit must be positive") + } + if !a.PeriodCanSpend.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "can spend amount is invalid: %s", a.PeriodCanSpend) + } + // We allow 0 for CanSpend + if a.PeriodCanSpend.IsAnyNegative() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "can spend must not be negative") + } + + // ensure PeriodSpendLimit can be subtracted from total (same coin types) + if !a.PeriodSpendLimit.DenomsSubsetOf(a.Basic.SpendLimit) { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "period spend limit has different currency than basic spend limit") + } + + // check times + if err := a.Period.ValidateBasic(); err != nil { + return err + } + return a.PeriodReset.ValidateBasic() +} diff --git a/x/feegrant/types/periodic_fee_test.go b/x/feegrant/types/periodic_fee_test.go new file mode 100644 index 000000000000..40a8ca864184 --- /dev/null +++ b/x/feegrant/types/periodic_fee_test.go @@ -0,0 +1,199 @@ +package types + +import ( + "testing" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestPeriodicFeeValidAllow(t *testing.T) { + atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555)) + smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 43)) + leftAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 512)) + oneAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 1)) + eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 1)) + + cases := map[string]struct { + allow PeriodicFeeAllowance + // all other checks are ignored if valid=false + fee sdk.Coins + blockTime time.Time + blockHeight int64 + valid bool + accept bool + remove bool + remains sdk.Coins + remainsPeriod sdk.Coins + periodReset ExpiresAt + }{ + "empty": { + allow: PeriodicFeeAllowance{}, + valid: false, + }, + "only basic": { + allow: PeriodicFeeAllowance{ + Basic: BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + }, + valid: false, + }, + "empty basic": { + allow: PeriodicFeeAllowance{ + Period: BlockDuration(50), + PeriodSpendLimit: smallAtom, + }, + valid: false, + }, + "mismatched currencies": { + allow: PeriodicFeeAllowance{ + Basic: BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + Period: BlockDuration(10), + PeriodSpendLimit: eth, + }, + valid: false, + }, + "first time": { + allow: PeriodicFeeAllowance{ + Basic: BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + Period: BlockDuration(10), + PeriodSpendLimit: smallAtom, + }, + valid: true, + fee: smallAtom, + blockHeight: 75, + accept: true, + remove: false, + remainsPeriod: nil, + remains: leftAtom, + periodReset: ExpiresAtHeight(85), + }, + "same period": { + allow: PeriodicFeeAllowance{ + Basic: BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + Period: BlockDuration(10), + PeriodReset: ExpiresAtHeight(80), + PeriodSpendLimit: leftAtom, + PeriodCanSpend: smallAtom, + }, + valid: true, + fee: smallAtom, + blockHeight: 75, + accept: true, + remove: false, + remainsPeriod: nil, + remains: leftAtom, + periodReset: ExpiresAtHeight(80), + }, + "step one period": { + allow: PeriodicFeeAllowance{ + Basic: BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + Period: BlockDuration(10), + PeriodReset: ExpiresAtHeight(70), + PeriodSpendLimit: leftAtom, + }, + valid: true, + fee: leftAtom, + blockHeight: 75, + accept: true, + remove: false, + remainsPeriod: nil, + remains: smallAtom, + periodReset: ExpiresAtHeight(80), // one step from last reset, not now + }, + "step limited by global allowance": { + allow: PeriodicFeeAllowance{ + Basic: BasicFeeAllowance{ + SpendLimit: smallAtom, + Expiration: ExpiresAtHeight(100), + }, + Period: BlockDuration(10), + PeriodReset: ExpiresAtHeight(70), + PeriodSpendLimit: atom, + }, + valid: true, + fee: oneAtom, + blockHeight: 75, + accept: true, + remove: false, + remainsPeriod: smallAtom.Sub(oneAtom), + remains: smallAtom.Sub(oneAtom), + periodReset: ExpiresAtHeight(80), // one step from last reset, not now + }, + "expired": { + allow: PeriodicFeeAllowance{ + Basic: BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + Period: BlockDuration(10), + PeriodSpendLimit: smallAtom, + }, + valid: true, + fee: smallAtom, + blockHeight: 101, + accept: false, + remove: true, + }, + "over period limit": { + allow: PeriodicFeeAllowance{ + Basic: BasicFeeAllowance{ + SpendLimit: atom, + Expiration: ExpiresAtHeight(100), + }, + Period: BlockDuration(10), + PeriodReset: ExpiresAtHeight(80), + PeriodSpendLimit: leftAtom, + PeriodCanSpend: smallAtom, + }, + valid: true, + fee: leftAtom, + blockHeight: 70, + accept: false, + remove: true, + }, + } + + for name, stc := range cases { + tc := stc // to make scopelint happy + t.Run(name, func(t *testing.T) { + err := tc.allow.ValidateBasic() + if !tc.valid { + require.Error(t, err) + return + } + require.NoError(t, err) + + // now try to deduct + remove, err := tc.allow.Accept(tc.fee, tc.blockTime, tc.blockHeight) + if !tc.accept { + require.Error(t, err) + return + } + require.NoError(t, err) + + require.Equal(t, tc.remove, remove) + if !remove { + assert.Equal(t, tc.remains, tc.allow.Basic.SpendLimit) + assert.Equal(t, tc.remainsPeriod, tc.allow.PeriodCanSpend) + assert.Equal(t, tc.periodReset, tc.allow.PeriodReset) + } + }) + } +} diff --git a/x/feegrant/types/test_common.go b/x/feegrant/types/test_common.go new file mode 100644 index 000000000000..bb3686650c48 --- /dev/null +++ b/x/feegrant/types/test_common.go @@ -0,0 +1,27 @@ +// noalias + +package types + +import ( + "github.com/tendermint/tendermint/crypto" + + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +func NewTestTx(ctx sdk.Context, msgs []sdk.Msg, privs []crypto.PrivKey, accNums []uint64, seqs []uint64, fee GrantedFee) sdk.Tx { + sigs := make([]authtypes.StdSignature, len(privs)) + for i, priv := range privs { + signBytes := StdSignBytes(ctx.ChainID(), accNums[i], seqs[i], fee, msgs, "") + + sig, err := priv.Sign(signBytes) + if err != nil { + panic(err) + } + + sigs[i] = authtypes.StdSignature{PubKey: priv.PubKey(), Signature: sig} + } + + tx := NewFeeGrantTx(msgs, fee, sigs, "") + return tx +} diff --git a/x/feegrant/types/tx.go b/x/feegrant/types/tx.go new file mode 100644 index 000000000000..d2c5c94c62b4 --- /dev/null +++ b/x/feegrant/types/tx.go @@ -0,0 +1,250 @@ +package types + +import ( + "encoding/json" + + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/multisig" + + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" + "github.com/cosmos/cosmos-sdk/x/auth/exported" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" +) + +var ( + _ sdk.Tx = FeeGrantTx{} + + maxGasWanted = uint64((1 << 63) - 1) +) + +// FeeGrantTx wraps a Msg with Fee and Signatures, +// adding the ability to delegate the fee payment +// NOTE: the first signature responsible for paying fees, either directly, +// or must be authorized to spend from the provided Fee.FeeAccount +type FeeGrantTx struct { + Msgs []sdk.Msg `json:"msg" yaml:"msg"` + Fee GrantedFee `json:"fee" yaml:"fee"` + Signatures []authtypes.StdSignature `json:"signatures" yaml:"signatures"` + Memo string `json:"memo" yaml:"memo"` + FeeAccount sdk.AccAddress `json:"fee_account" yaml:"fee_account"` +} + +func NewFeeGrantTx(msgs []sdk.Msg, fee GrantedFee, sigs []authtypes.StdSignature, memo string) FeeGrantTx { + return FeeGrantTx{ + Msgs: msgs, + Fee: fee, + Signatures: sigs, + Memo: memo, + } +} + +// GetMsgs returns the all the transaction's messages. +func (tx FeeGrantTx) GetMsgs() []sdk.Msg { return tx.Msgs } + +// ValidateBasic does a simple and lightweight validation check that doesn't +// require access to any other information. +func (tx FeeGrantTx) ValidateBasic() error { + stdSigs := tx.GetSignatures() + + if tx.Fee.Gas > maxGasWanted { + return sdkerrors.Wrapf( + sdkerrors.ErrInvalidRequest, + "invalid gas supplied; %d > %d", tx.Fee.Gas, maxGasWanted, + ) + } + if tx.Fee.Amount.IsAnyNegative() { + return sdkerrors.Wrapf( + sdkerrors.ErrInsufficientFee, + "invalid fee provided: %s", tx.Fee.Amount, + ) + } + if len(stdSigs) == 0 { + return sdkerrors.ErrNoSignatures + } + if len(stdSigs) != len(tx.GetSigners()) { + return sdkerrors.Wrapf( + sdkerrors.ErrUnauthorized, + "wrong number of signers; expected %d, got %d", tx.GetSigners(), len(stdSigs), + ) + } + + return nil +} + +// CountSubKeys counts the total number of keys for a multi-sig public key. +func CountSubKeys(pub crypto.PubKey) int { + v, ok := pub.(multisig.PubKeyMultisigThreshold) + if !ok { + return 1 + } + + numKeys := 0 + for _, subkey := range v.PubKeys { + numKeys += CountSubKeys(subkey) + } + + return numKeys +} + +// GetSigners returns the addresses that must sign the transaction. +// Addresses are returned in a deterministic order. +// They are accumulated from the GetSigners method for each Msg +// in the order they appear in tx.GetMsgs(). +// Duplicate addresses will be omitted. +func (tx FeeGrantTx) GetSigners() []sdk.AccAddress { + seen := map[string]bool{} + var signers []sdk.AccAddress + for _, msg := range tx.GetMsgs() { + for _, addr := range msg.GetSigners() { + if !seen[addr.String()] { + signers = append(signers, addr) + seen[addr.String()] = true + } + } + } + return signers +} + +// GetMemo returns the memo +func (tx FeeGrantTx) GetMemo() string { return tx.Memo } + +// GetSignatures returns the signature of signers who signed the Msg. +// CONTRACT: Length returned is same as length of +// pubkeys returned from MsgKeySigners, and the order +// matches. +// CONTRACT: If the signature is missing (ie the Msg is +// invalid), then the corresponding signature is +// .Empty(). +func (tx FeeGrantTx) GetSignatures() [][]byte { + sigs := make([][]byte, len(tx.Signatures)) + for i, stdSig := range tx.Signatures { + sigs[i] = stdSig.Signature + } + return sigs +} + +// GetPubkeys returns the pubkeys of signers if the pubkey is included in the signature +// If pubkey is not included in the signature, then nil is in the slice instead +func (tx FeeGrantTx) GetPubKeys() []crypto.PubKey { + pks := make([]crypto.PubKey, len(tx.Signatures)) + for i, stdSig := range tx.Signatures { + pks[i] = stdSig.PubKey + } + return pks +} + +// GetSignBytes returns the signBytes of the tx for a given signer +func (tx FeeGrantTx) GetSignBytes(ctx sdk.Context, acc exported.Account) []byte { + genesis := ctx.BlockHeight() == 0 + chainID := ctx.ChainID() + var accNum uint64 + if !genesis { + accNum = acc.GetAccountNumber() + } + return StdSignBytes(chainID, accNum, acc.GetSequence(), tx.Fee, tx.Msgs, tx.Memo) +} + +// GetGas returns the Gas in GrantedFee +func (tx FeeGrantTx) GetGas() uint64 { return tx.Fee.Gas } + +// GetFee returns the FeeAmount in GrantedFee +func (tx FeeGrantTx) GetFee() sdk.Coins { return tx.Fee.Amount } + +// FeePayer returns the address that is responsible for paying fee +// This can be explicily set in GrantedFee, or defaults to MainSigner +func (tx FeeGrantTx) FeePayer() sdk.AccAddress { + if len(tx.Fee.FeeAccount) != 0 { + return tx.Fee.FeeAccount + } + return tx.MainSigner() +} + +// MainSigner returns the first signer of the tx, by default this +// account is responsible for fees, if not explicitly set. +func (tx FeeGrantTx) MainSigner() sdk.AccAddress { + if len(tx.GetSigners()) != 0 { + return tx.GetSigners()[0] + } + return sdk.AccAddress{} +} + +// GrantedFee includes the amount of coins paid in fees and the maximum +// gas to be used by the transaction. The ratio yields an effective "gasprice", +// which must be above some miminum to be accepted into the mempool. +type GrantedFee struct { + Amount sdk.Coins `json:"amount" yaml:"amount"` + Gas uint64 `json:"gas" yaml:"gas"` + FeeAccount sdk.AccAddress `json:"fee_account,omitempty" yaml:"fee_account"` +} + +// NewGrantedFee returns a new instance of GrantedFee +func NewGrantedFee(gas uint64, amount sdk.Coins, feeAccount sdk.AccAddress) GrantedFee { + return GrantedFee{ + Amount: amount, + Gas: gas, + FeeAccount: feeAccount, + } +} + +// Bytes for signing later +func (fee GrantedFee) Bytes() []byte { + // normalize. XXX + // this is a sign of something ugly + // (in the lcd_test, client side its null, + // server side its []) + if len(fee.Amount) == 0 { + fee.Amount = sdk.NewCoins() + } + cdc := codec.New() + bz, err := cdc.MarshalJSON(fee) + if err != nil { + panic(err) + } + return bz +} + +// GasPrices returns the gas prices for a GrantedFee. +// +// NOTE: The gas prices returned are not the true gas prices that were +// originally part of the submitted transaction because the fee is computed +// as fee = ceil(gasWanted * gasPrices). +func (fee GrantedFee) GasPrices() sdk.DecCoins { + return sdk.NewDecCoinsFromCoins(fee.Amount...).QuoDec(sdk.NewDec(int64(fee.Gas))) +} + +// DelegatedSignDoc is replay-prevention structure. +// It includes the result of msg.GetSignBytes(), +// as well as the ChainID (prevent cross chain replay) +// and the Sequence numbers for each signature (prevent +// inchain replay and enforce tx ordering per account). +type DelegatedSignDoc struct { + AccountNumber uint64 `json:"account_number" yaml:"account_number"` + ChainID string `json:"chain_id" yaml:"chain_id"` + Fee json.RawMessage `json:"fee" yaml:"fee"` + Memo string `json:"memo" yaml:"memo"` + Msgs []json.RawMessage `json:"msgs" yaml:"msgs"` + Sequence uint64 `json:"sequence" yaml:"sequence"` +} + +// StdSignBytes returns the bytes to sign for a transaction. +func StdSignBytes(chainID string, accnum uint64, sequence uint64, fee GrantedFee, msgs []sdk.Msg, memo string) []byte { + cdc := codec.New() + msgsBytes := make([]json.RawMessage, 0, len(msgs)) + for _, msg := range msgs { + msgsBytes = append(msgsBytes, json.RawMessage(msg.GetSignBytes())) + } + bz, err := cdc.MarshalJSON(DelegatedSignDoc{ + AccountNumber: accnum, + ChainID: chainID, + Fee: json.RawMessage(fee.Bytes()), + Memo: memo, + Msgs: msgsBytes, + Sequence: sequence, + }) + if err != nil { + panic(err) + } + return sdk.MustSortJSON(bz) +} diff --git a/x/feegrant/types/types.pb.go b/x/feegrant/types/types.pb.go new file mode 100644 index 000000000000..79c6e9672521 --- /dev/null +++ b/x/feegrant/types/types.pb.go @@ -0,0 +1,2531 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: x/feegrant/types/types.proto + +package types + +import ( + fmt "fmt" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" + types "github.com/cosmos/cosmos-sdk/types" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" + _ "github.com/golang/protobuf/ptypes/timestamp" + _ "github.com/regen-network/cosmos-proto" + io "io" + math "math" + math_bits "math/bits" + time "time" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// FeeAllowance defines the application-level fee allowance to be used in +// feegrant module +type FeeAllowance struct { + // sum defines a set of all acceptable concrete feeallowance implementations. + // + // Types that are valid to be assigned to Sum: + // *FeeAllowance_BasicFeeAllowance + // *FeeAllowance_PeriodicFeeAllowance + Sum isFeeAllowance_Sum `protobuf_oneof:"sum"` +} + +func (m *FeeAllowance) Reset() { *m = FeeAllowance{} } +func (m *FeeAllowance) String() string { return proto.CompactTextString(m) } +func (*FeeAllowance) ProtoMessage() {} +func (*FeeAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_86c534389d2c5768, []int{0} +} +func (m *FeeAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *FeeAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_FeeAllowance.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 *FeeAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_FeeAllowance.Merge(m, src) +} +func (m *FeeAllowance) XXX_Size() int { + return m.Size() +} +func (m *FeeAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_FeeAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_FeeAllowance proto.InternalMessageInfo + +type isFeeAllowance_Sum interface { + isFeeAllowance_Sum() + Equal(interface{}) bool + MarshalTo([]byte) (int, error) + Size() int +} + +type FeeAllowance_BasicFeeAllowance struct { + BasicFeeAllowance *BasicFeeAllowance `protobuf:"bytes,1,opt,name=basic_fee_allowance,json=basicFeeAllowance,proto3,oneof" json:"basic_fee_allowance,omitempty"` +} +type FeeAllowance_PeriodicFeeAllowance struct { + PeriodicFeeAllowance *PeriodicFeeAllowance `protobuf:"bytes,2,opt,name=periodic_fee_allowance,json=periodicFeeAllowance,proto3,oneof" json:"periodic_fee_allowance,omitempty"` +} + +func (*FeeAllowance_BasicFeeAllowance) isFeeAllowance_Sum() {} +func (*FeeAllowance_PeriodicFeeAllowance) isFeeAllowance_Sum() {} + +func (m *FeeAllowance) GetSum() isFeeAllowance_Sum { + if m != nil { + return m.Sum + } + return nil +} + +func (m *FeeAllowance) GetBasicFeeAllowance() *BasicFeeAllowance { + if x, ok := m.GetSum().(*FeeAllowance_BasicFeeAllowance); ok { + return x.BasicFeeAllowance + } + return nil +} + +func (m *FeeAllowance) GetPeriodicFeeAllowance() *PeriodicFeeAllowance { + if x, ok := m.GetSum().(*FeeAllowance_PeriodicFeeAllowance); ok { + return x.PeriodicFeeAllowance + } + return nil +} + +// XXX_OneofWrappers is for the internal use of the proto package. +func (*FeeAllowance) XXX_OneofWrappers() []interface{} { + return []interface{}{ + (*FeeAllowance_BasicFeeAllowance)(nil), + (*FeeAllowance_PeriodicFeeAllowance)(nil), + } +} + +// MsgGrantFeeAllowance adds permission for Grantee to spend up to Allowance +// of fees from the account of Granter. +type MsgGrantFeeAllowance struct { + Granter github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,1,opt,name=granter,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"granter,omitempty"` + Grantee github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,2,opt,name=grantee,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"grantee,omitempty"` + Allowance *FeeAllowance `protobuf:"bytes,3,opt,name=allowance,proto3" json:"allowance,omitempty"` +} + +func (m *MsgGrantFeeAllowance) Reset() { *m = MsgGrantFeeAllowance{} } +func (m *MsgGrantFeeAllowance) String() string { return proto.CompactTextString(m) } +func (*MsgGrantFeeAllowance) ProtoMessage() {} +func (*MsgGrantFeeAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_86c534389d2c5768, []int{1} +} +func (m *MsgGrantFeeAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgGrantFeeAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgGrantFeeAllowance.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 *MsgGrantFeeAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgGrantFeeAllowance.Merge(m, src) +} +func (m *MsgGrantFeeAllowance) XXX_Size() int { + return m.Size() +} +func (m *MsgGrantFeeAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_MsgGrantFeeAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgGrantFeeAllowance proto.InternalMessageInfo + +// MsgRevokeFeeAllowance removes any existing FeeAllowance from Granter to Grantee. +type MsgRevokeFeeAllowance struct { + Granter github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,1,opt,name=granter,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"granter,omitempty"` + Grantee github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,2,opt,name=grantee,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"grantee,omitempty"` +} + +func (m *MsgRevokeFeeAllowance) Reset() { *m = MsgRevokeFeeAllowance{} } +func (m *MsgRevokeFeeAllowance) String() string { return proto.CompactTextString(m) } +func (*MsgRevokeFeeAllowance) ProtoMessage() {} +func (*MsgRevokeFeeAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_86c534389d2c5768, []int{2} +} +func (m *MsgRevokeFeeAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRevokeFeeAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRevokeFeeAllowance.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 *MsgRevokeFeeAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRevokeFeeAllowance.Merge(m, src) +} +func (m *MsgRevokeFeeAllowance) XXX_Size() int { + return m.Size() +} +func (m *MsgRevokeFeeAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRevokeFeeAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRevokeFeeAllowance proto.InternalMessageInfo + +func (m *MsgRevokeFeeAllowance) GetGranter() github_com_cosmos_cosmos_sdk_types.AccAddress { + if m != nil { + return m.Granter + } + return nil +} + +func (m *MsgRevokeFeeAllowance) GetGrantee() github_com_cosmos_cosmos_sdk_types.AccAddress { + if m != nil { + return m.Grantee + } + return nil +} + +// BasicFeeAllowance implements FeeAllowance with a one-time grant of tokens +// that optionally expires. The delegatee can use up to SpendLimit to cover fees. +type BasicFeeAllowance struct { + SpendLimit github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=spend_limit,json=spendLimit,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"spend_limit" yaml:"spend_limit"` + Expiration ExpiresAt `protobuf:"bytes,2,opt,name=expiration,proto3" json:"expiration"` +} + +func (m *BasicFeeAllowance) Reset() { *m = BasicFeeAllowance{} } +func (m *BasicFeeAllowance) String() string { return proto.CompactTextString(m) } +func (*BasicFeeAllowance) ProtoMessage() {} +func (*BasicFeeAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_86c534389d2c5768, []int{3} +} +func (m *BasicFeeAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BasicFeeAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BasicFeeAllowance.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 *BasicFeeAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_BasicFeeAllowance.Merge(m, src) +} +func (m *BasicFeeAllowance) XXX_Size() int { + return m.Size() +} +func (m *BasicFeeAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_BasicFeeAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_BasicFeeAllowance proto.InternalMessageInfo + +// PeriodicFeeAllowance extends FeeAllowance to allow for both a maximum cap, +// as well as a limit per time period. +type PeriodicFeeAllowance struct { + Basic BasicFeeAllowance `protobuf:"bytes,1,opt,name=basic,proto3" json:"basic"` + Period Duration `protobuf:"bytes,2,opt,name=period,proto3" json:"period"` + PeriodSpendLimit github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,3,rep,name=period_spend_limit,json=periodSpendLimit,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"period_spend_limit" yaml:"period_spend_limit"` + PeriodCanSpend github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,4,rep,name=period_can_spend,json=periodCanSpend,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"period_can_spend" yaml:"period_can_spend"` + PeriodReset ExpiresAt `protobuf:"bytes,5,opt,name=period_reset,json=periodReset,proto3" json:"period_reset" yaml:"period_reset"` +} + +func (m *PeriodicFeeAllowance) Reset() { *m = PeriodicFeeAllowance{} } +func (m *PeriodicFeeAllowance) String() string { return proto.CompactTextString(m) } +func (*PeriodicFeeAllowance) ProtoMessage() {} +func (*PeriodicFeeAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_86c534389d2c5768, []int{4} +} +func (m *PeriodicFeeAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PeriodicFeeAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PeriodicFeeAllowance.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 *PeriodicFeeAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_PeriodicFeeAllowance.Merge(m, src) +} +func (m *PeriodicFeeAllowance) XXX_Size() int { + return m.Size() +} +func (m *PeriodicFeeAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_PeriodicFeeAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_PeriodicFeeAllowance proto.InternalMessageInfo + +// Duration is a repeating unit of either clock time or number of blocks. +// This is designed to be added to an ExpiresAt struct. +type Duration struct { + Clock time.Duration `protobuf:"bytes,1,opt,name=clock,proto3,stdduration" json:"clock"` + Block int64 `protobuf:"varint,2,opt,name=block,proto3" json:"block,omitempty"` +} + +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_86c534389d2c5768, []int{5} +} +func (m *Duration) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Duration) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Duration.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 *Duration) XXX_Merge(src proto.Message) { + xxx_messageInfo_Duration.Merge(m, src) +} +func (m *Duration) XXX_Size() int { + return m.Size() +} +func (m *Duration) XXX_DiscardUnknown() { + xxx_messageInfo_Duration.DiscardUnknown(m) +} + +var xxx_messageInfo_Duration proto.InternalMessageInfo + +func (m *Duration) GetClock() time.Duration { + if m != nil { + return m.Clock + } + return 0 +} + +func (m *Duration) GetBlock() int64 { + if m != nil { + return m.Block + } + return 0 +} + +// ExpiresAt is a point in time where something expires. +// It may be *either* block time or block height +type ExpiresAt struct { + Time time.Time `protobuf:"bytes,1,opt,name=time,proto3,stdtime" json:"time"` + Height int64 `protobuf:"varint,2,opt,name=height,proto3" json:"height,omitempty"` +} + +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_86c534389d2c5768, []int{6} +} +func (m *ExpiresAt) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ExpiresAt) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ExpiresAt.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 *ExpiresAt) XXX_Merge(src proto.Message) { + xxx_messageInfo_ExpiresAt.Merge(m, src) +} +func (m *ExpiresAt) XXX_Size() int { + return m.Size() +} +func (m *ExpiresAt) XXX_DiscardUnknown() { + xxx_messageInfo_ExpiresAt.DiscardUnknown(m) +} + +var xxx_messageInfo_ExpiresAt proto.InternalMessageInfo + +func (m *ExpiresAt) GetTime() time.Time { + if m != nil { + return m.Time + } + return time.Time{} +} + +func (m *ExpiresAt) GetHeight() int64 { + if m != nil { + return m.Height + } + return 0 +} + +// FeeAllowanceGrant is stored in the KVStore to record a grant with full context +type FeeAllowanceGrant struct { + Granter github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,1,opt,name=granter,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"granter,omitempty"` + Grantee github_com_cosmos_cosmos_sdk_types.AccAddress `protobuf:"bytes,2,opt,name=grantee,proto3,casttype=github.com/cosmos/cosmos-sdk/types.AccAddress" json:"grantee,omitempty"` + Allowance *FeeAllowance `protobuf:"bytes,3,opt,name=allowance,proto3" json:"allowance,omitempty"` +} + +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_86c534389d2c5768, []int{7} +} +func (m *FeeAllowanceGrant) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *FeeAllowanceGrant) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_FeeAllowanceGrant.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 *FeeAllowanceGrant) XXX_Merge(src proto.Message) { + xxx_messageInfo_FeeAllowanceGrant.Merge(m, src) +} +func (m *FeeAllowanceGrant) XXX_Size() int { + return m.Size() +} +func (m *FeeAllowanceGrant) XXX_DiscardUnknown() { + xxx_messageInfo_FeeAllowanceGrant.DiscardUnknown(m) +} + +var xxx_messageInfo_FeeAllowanceGrant proto.InternalMessageInfo + +func init() { + proto.RegisterType((*FeeAllowance)(nil), "cosmos_sdk.x.feegrant.v1.FeeAllowance") + proto.RegisterType((*MsgGrantFeeAllowance)(nil), "cosmos_sdk.x.feegrant.v1.MsgGrantFeeAllowance") + proto.RegisterType((*MsgRevokeFeeAllowance)(nil), "cosmos_sdk.x.feegrant.v1.MsgRevokeFeeAllowance") + proto.RegisterType((*BasicFeeAllowance)(nil), "cosmos_sdk.x.feegrant.v1.BasicFeeAllowance") + proto.RegisterType((*PeriodicFeeAllowance)(nil), "cosmos_sdk.x.feegrant.v1.PeriodicFeeAllowance") + proto.RegisterType((*Duration)(nil), "cosmos_sdk.x.feegrant.v1.Duration") + proto.RegisterType((*ExpiresAt)(nil), "cosmos_sdk.x.feegrant.v1.ExpiresAt") + proto.RegisterType((*FeeAllowanceGrant)(nil), "cosmos_sdk.x.feegrant.v1.FeeAllowanceGrant") +} + +func init() { proto.RegisterFile("x/feegrant/types/types.proto", fileDescriptor_86c534389d2c5768) } + +var fileDescriptor_86c534389d2c5768 = []byte{ + // 735 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe4, 0x56, 0xcd, 0x4f, 0x13, 0x4d, + 0x18, 0xef, 0xbc, 0x6d, 0x79, 0x61, 0x4a, 0xc8, 0xdb, 0xa1, 0x2f, 0xd6, 0x6a, 0xba, 0x64, 0x4d, + 0x08, 0x91, 0xb0, 0x0d, 0x78, 0x31, 0x3d, 0xd9, 0x05, 0x41, 0x82, 0x24, 0x66, 0xf5, 0x64, 0x62, + 0x36, 0xfb, 0x31, 0xdd, 0x6e, 0xda, 0xdd, 0xd9, 0xec, 0x6c, 0x11, 0x12, 0xff, 0x00, 0xe3, 0x89, + 0xa3, 0x47, 0xce, 0x9e, 0x34, 0xf1, 0x8f, 0x20, 0x9e, 0x38, 0x7a, 0x02, 0x03, 0x89, 0xf1, 0x6c, + 0xbc, 0x68, 0x3c, 0x98, 0x9d, 0x99, 0xa5, 0x0b, 0x65, 0x0d, 0xe8, 0xc9, 0x78, 0x69, 0x76, 0x9e, + 0x3c, 0xbf, 0x8f, 0xe7, 0x63, 0x77, 0x0a, 0xaf, 0x6f, 0x35, 0xda, 0x18, 0x3b, 0xa1, 0xe1, 0x47, + 0x8d, 0x68, 0x3b, 0xc0, 0x94, 0xff, 0x2a, 0x41, 0x48, 0x22, 0x82, 0xaa, 0x16, 0xa1, 0x1e, 0xa1, + 0x3a, 0xb5, 0xbb, 0xca, 0x96, 0x92, 0x24, 0x2a, 0x9b, 0x0b, 0xb5, 0xb9, 0xa8, 0xe3, 0x86, 0xb6, + 0x1e, 0x18, 0x61, 0xb4, 0xdd, 0x60, 0xc9, 0x0d, 0x9e, 0x3b, 0x9f, 0x3e, 0x70, 0x9a, 0xda, 0xcc, + 0x70, 0xb2, 0x43, 0x1c, 0x32, 0x78, 0x12, 0x79, 0xe5, 0x21, 0x07, 0x35, 0xc9, 0x21, 0xc4, 0xe9, + 0x61, 0x8e, 0x32, 0xfb, 0xed, 0x46, 0xe4, 0x7a, 0x98, 0x46, 0x86, 0x17, 0xf0, 0x04, 0xf9, 0x0b, + 0x80, 0xe3, 0x2b, 0x18, 0xb7, 0x7a, 0x3d, 0xf2, 0xd4, 0xf0, 0x2d, 0x8c, 0x9e, 0xc0, 0x49, 0xd3, + 0xa0, 0xae, 0xa5, 0xb7, 0x31, 0xd6, 0x8d, 0x24, 0x5c, 0x05, 0xd3, 0x60, 0xb6, 0xb4, 0x38, 0xa7, + 0x64, 0x55, 0xa4, 0xa8, 0x31, 0x28, 0xcd, 0x74, 0x2f, 0xa7, 0x95, 0xcd, 0xb3, 0x41, 0xd4, 0x86, + 0x53, 0x01, 0x0e, 0x5d, 0x62, 0x0f, 0x29, 0xfc, 0xc3, 0x14, 0x94, 0x6c, 0x85, 0x07, 0x02, 0x77, + 0x46, 0xa4, 0x12, 0x9c, 0x13, 0x6f, 0x4e, 0x7d, 0xda, 0x95, 0xc0, 0xbb, 0xb7, 0xf3, 0x13, 0x37, + 0xd3, 0xe1, 0x35, 0xb5, 0x08, 0xf3, 0xb4, 0xef, 0xc9, 0xdf, 0x01, 0xac, 0x6c, 0x50, 0x67, 0x35, + 0xe6, 0x3e, 0xe5, 0x6f, 0x1d, 0xfe, 0xcb, 0x04, 0x71, 0xc8, 0x4a, 0x1e, 0x57, 0x17, 0xbe, 0x1d, + 0x48, 0xf3, 0x8e, 0x1b, 0x75, 0xfa, 0xa6, 0x62, 0x11, 0x4f, 0x4c, 0x26, 0x99, 0x16, 0xb5, 0xbb, + 0xa2, 0xdf, 0x2d, 0xcb, 0x6a, 0xd9, 0x76, 0x88, 0x29, 0xd5, 0x12, 0x86, 0x01, 0x19, 0xaf, 0xee, + 0x77, 0xc8, 0x30, 0x5a, 0x86, 0x63, 0x83, 0x66, 0xe5, 0x59, 0xb3, 0x66, 0xb2, 0x9b, 0x95, 0x2e, + 0x4a, 0x1b, 0x00, 0x9b, 0x85, 0xe7, 0xbb, 0x52, 0x4e, 0x7e, 0x03, 0xe0, 0xff, 0x1b, 0xd4, 0xd1, + 0xf0, 0x26, 0xe9, 0xe2, 0x3f, 0xa3, 0x7e, 0xf9, 0x23, 0x80, 0xe5, 0xa1, 0x25, 0x43, 0xcf, 0x60, + 0x89, 0x06, 0xd8, 0xb7, 0xf5, 0x9e, 0xeb, 0xb9, 0x51, 0x15, 0x4c, 0xe7, 0x67, 0x4b, 0x8b, 0x93, + 0xe9, 0xbe, 0x6c, 0x2e, 0x28, 0x4b, 0xc4, 0xf5, 0xd5, 0x95, 0xbd, 0x03, 0x29, 0xf7, 0xf9, 0x40, + 0x42, 0xdb, 0x86, 0xd7, 0x6b, 0xca, 0x29, 0x94, 0xfc, 0xea, 0x50, 0x9a, 0xbd, 0x80, 0xab, 0x98, + 0x86, 0x6a, 0x90, 0x21, 0xef, 0xc7, 0x40, 0xb4, 0x06, 0x21, 0xde, 0x0a, 0xdc, 0xd0, 0x88, 0x5c, + 0xe2, 0x8b, 0x0d, 0xbe, 0x91, 0x3d, 0x94, 0xbb, 0x71, 0x2e, 0xa6, 0xad, 0x48, 0x2d, 0xc4, 0x66, + 0xb4, 0x14, 0xb8, 0x39, 0x1a, 0x0f, 0x26, 0x5e, 0x5a, 0xf9, 0x75, 0x01, 0x56, 0xce, 0xdb, 0x75, + 0xb4, 0x0a, 0x8b, 0xec, 0x85, 0xfa, 0x85, 0x97, 0x51, 0x08, 0x72, 0x3c, 0xba, 0x03, 0x47, 0xf8, + 0x4b, 0x23, 0x2c, 0xcb, 0xd9, 0x4c, 0xcb, 0x7d, 0xee, 0x4f, 0x10, 0x08, 0x1c, 0xda, 0x01, 0x10, + 0xf1, 0x47, 0x3d, 0xdd, 0xfe, 0x7c, 0x76, 0xfb, 0x37, 0x44, 0xfb, 0xaf, 0xf2, 0xf6, 0x0f, 0x83, + 0x2f, 0x37, 0x85, 0xff, 0x38, 0xc1, 0xc3, 0xc1, 0x2c, 0x5e, 0x00, 0x28, 0x82, 0xba, 0x65, 0xf8, + 0x9c, 0xb9, 0x5a, 0xc8, 0x36, 0xb4, 0x2e, 0x0c, 0x5d, 0x39, 0x65, 0xe8, 0x04, 0x7a, 0x39, 0x3b, + 0x13, 0x1c, 0xbe, 0x64, 0xf8, 0xcc, 0x11, 0xb2, 0xe0, 0xb8, 0x20, 0x0c, 0x31, 0xc5, 0x51, 0xb5, + 0x78, 0xf1, 0xd5, 0xb8, 0x26, 0x7c, 0x4d, 0x9e, 0xf2, 0xc5, 0x68, 0x64, 0xad, 0xc4, 0x8f, 0x5a, + 0x7c, 0x4a, 0xad, 0x8c, 0x09, 0x47, 0x93, 0x41, 0xa1, 0x26, 0x2c, 0x5a, 0x3d, 0x62, 0x75, 0xc5, + 0x96, 0xd4, 0x14, 0x7e, 0x05, 0x28, 0xc9, 0x15, 0xa0, 0x3c, 0x4a, 0xae, 0x00, 0x75, 0x34, 0x96, + 0x7a, 0x79, 0x28, 0x01, 0x8d, 0x43, 0x50, 0x05, 0x16, 0x4d, 0x86, 0x8d, 0xf7, 0x22, 0xaf, 0xf1, + 0x43, 0xb3, 0xc0, 0x34, 0x2c, 0x38, 0x76, 0x62, 0x12, 0xdd, 0x86, 0x85, 0xf8, 0x26, 0xb9, 0xa8, + 0xc6, 0x4e, 0xac, 0xc1, 0x10, 0x68, 0x0a, 0x8e, 0x74, 0xb0, 0xeb, 0x74, 0x22, 0xa1, 0x21, 0x4e, + 0x42, 0xe4, 0x2b, 0x80, 0xe5, 0xf4, 0xde, 0xb2, 0x0f, 0xf4, 0xdf, 0xf1, 0x51, 0x56, 0x57, 0xf7, + 0x8e, 0xea, 0x60, 0xff, 0xa8, 0x0e, 0x3e, 0x1c, 0xd5, 0xc1, 0xce, 0x71, 0x3d, 0xb7, 0x7f, 0x5c, + 0xcf, 0xbd, 0x3f, 0xae, 0xe7, 0x1e, 0xff, 0xdc, 0xdd, 0xd9, 0xbf, 0x20, 0xe6, 0x08, 0x1b, 0xc3, + 0xad, 0x1f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x60, 0xf1, 0xd1, 0x21, 0x9d, 0x08, 0x00, 0x00, +} + +func (this *FeeAllowance) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*FeeAllowance) + if !ok { + that2, ok := that.(FeeAllowance) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if that1.Sum == nil { + if this.Sum != nil { + return false + } + } else if this.Sum == nil { + return false + } else if !this.Sum.Equal(that1.Sum) { + return false + } + return true +} +func (this *FeeAllowance_BasicFeeAllowance) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*FeeAllowance_BasicFeeAllowance) + if !ok { + that2, ok := that.(FeeAllowance_BasicFeeAllowance) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if !this.BasicFeeAllowance.Equal(that1.BasicFeeAllowance) { + return false + } + return true +} +func (this *FeeAllowance_PeriodicFeeAllowance) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*FeeAllowance_PeriodicFeeAllowance) + if !ok { + that2, ok := that.(FeeAllowance_PeriodicFeeAllowance) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if !this.PeriodicFeeAllowance.Equal(that1.PeriodicFeeAllowance) { + return false + } + return true +} +func (this *BasicFeeAllowance) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*BasicFeeAllowance) + if !ok { + that2, ok := that.(BasicFeeAllowance) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if len(this.SpendLimit) != len(that1.SpendLimit) { + return false + } + for i := range this.SpendLimit { + if !this.SpendLimit[i].Equal(&that1.SpendLimit[i]) { + return false + } + } + if !this.Expiration.Equal(&that1.Expiration) { + return false + } + return true +} +func (this *PeriodicFeeAllowance) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*PeriodicFeeAllowance) + if !ok { + that2, ok := that.(PeriodicFeeAllowance) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if !this.Basic.Equal(&that1.Basic) { + return false + } + if !this.Period.Equal(&that1.Period) { + return false + } + if len(this.PeriodSpendLimit) != len(that1.PeriodSpendLimit) { + return false + } + for i := range this.PeriodSpendLimit { + if !this.PeriodSpendLimit[i].Equal(&that1.PeriodSpendLimit[i]) { + return false + } + } + if len(this.PeriodCanSpend) != len(that1.PeriodCanSpend) { + return false + } + for i := range this.PeriodCanSpend { + if !this.PeriodCanSpend[i].Equal(&that1.PeriodCanSpend[i]) { + return false + } + } + if !this.PeriodReset.Equal(&that1.PeriodReset) { + return false + } + return true +} +func (this *Duration) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*Duration) + if !ok { + that2, ok := that.(Duration) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if this.Clock != that1.Clock { + return false + } + if this.Block != that1.Block { + return false + } + return true +} +func (this *ExpiresAt) Equal(that interface{}) bool { + if that == nil { + return this == nil + } + + that1, ok := that.(*ExpiresAt) + if !ok { + that2, ok := that.(ExpiresAt) + if ok { + that1 = &that2 + } else { + return false + } + } + if that1 == nil { + return this == nil + } else if this == nil { + return false + } + if !this.Time.Equal(that1.Time) { + return false + } + if this.Height != that1.Height { + return false + } + return true +} +func (this *FeeAllowance) GetFeeAllowanceI() FeeAllowanceI { + if x := this.GetBasicFeeAllowance(); x != nil { + return x + } + if x := this.GetPeriodicFeeAllowance(); x != nil { + return x + } + return nil +} + +func (this *FeeAllowance) SetFeeAllowanceI(value FeeAllowanceI) error { + if value == nil { + this.Sum = nil + return nil + } + switch vt := value.(type) { + case *BasicFeeAllowance: + this.Sum = &FeeAllowance_BasicFeeAllowance{vt} + return nil + case *PeriodicFeeAllowance: + this.Sum = &FeeAllowance_PeriodicFeeAllowance{vt} + return nil + } + return fmt.Errorf("can't encode value of type %T as message FeeAllowance", value) +} + +func (m *FeeAllowance) 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 *FeeAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FeeAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Sum != nil { + { + size := m.Sum.Size() + i -= size + if _, err := m.Sum.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + } + } + return len(dAtA) - i, nil +} + +func (m *FeeAllowance_BasicFeeAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FeeAllowance_BasicFeeAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.BasicFeeAllowance != nil { + { + size, err := m.BasicFeeAllowance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} +func (m *FeeAllowance_PeriodicFeeAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FeeAllowance_PeriodicFeeAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + if m.PeriodicFeeAllowance != nil { + { + size, err := m.PeriodicFeeAllowance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + return len(dAtA) - i, nil +} +func (m *MsgGrantFeeAllowance) 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 *MsgGrantFeeAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgGrantFeeAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Allowance != nil { + { + size, err := m.Allowance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.Grantee) > 0 { + i -= len(m.Grantee) + copy(dAtA[i:], m.Grantee) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Grantee))) + i-- + dAtA[i] = 0x12 + } + if len(m.Granter) > 0 { + i -= len(m.Granter) + copy(dAtA[i:], m.Granter) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Granter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRevokeFeeAllowance) 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 *MsgRevokeFeeAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRevokeFeeAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Grantee) > 0 { + i -= len(m.Grantee) + copy(dAtA[i:], m.Grantee) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Grantee))) + i-- + dAtA[i] = 0x12 + } + if len(m.Granter) > 0 { + i -= len(m.Granter) + copy(dAtA[i:], m.Granter) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Granter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *BasicFeeAllowance) 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 *BasicFeeAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BasicFeeAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.Expiration.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + if len(m.SpendLimit) > 0 { + for iNdEx := len(m.SpendLimit) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.SpendLimit[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *PeriodicFeeAllowance) 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 *PeriodicFeeAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PeriodicFeeAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + { + size, err := m.PeriodReset.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a + if len(m.PeriodCanSpend) > 0 { + for iNdEx := len(m.PeriodCanSpend) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.PeriodCanSpend[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.PeriodSpendLimit) > 0 { + for iNdEx := len(m.PeriodSpendLimit) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.PeriodSpendLimit[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + { + size, err := m.Period.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + { + size, err := m.Basic.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(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) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *Duration) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Duration) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Block != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Block)) + i-- + dAtA[i] = 0x10 + } + n8, err8 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.Clock, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.Clock):]) + if err8 != nil { + return 0, err8 + } + i -= n8 + i = encodeVarintTypes(dAtA, i, uint64(n8)) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *ExpiresAt) 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 *ExpiresAt) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ExpiresAt) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Height != 0 { + i = encodeVarintTypes(dAtA, i, uint64(m.Height)) + i-- + dAtA[i] = 0x10 + } + n9, err9 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.Time):]) + if err9 != nil { + return 0, err9 + } + i -= n9 + i = encodeVarintTypes(dAtA, i, uint64(n9)) + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *FeeAllowanceGrant) 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 *FeeAllowanceGrant) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *FeeAllowanceGrant) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Allowance != nil { + { + size, err := m.Allowance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTypes(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.Grantee) > 0 { + i -= len(m.Grantee) + copy(dAtA[i:], m.Grantee) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Grantee))) + i-- + dAtA[i] = 0x12 + } + if len(m.Granter) > 0 { + i -= len(m.Granter) + copy(dAtA[i:], m.Granter) + i = encodeVarintTypes(dAtA, i, uint64(len(m.Granter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintTypes(dAtA []byte, offset int, v uint64) int { + offset -= sovTypes(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *FeeAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Sum != nil { + n += m.Sum.Size() + } + return n +} + +func (m *FeeAllowance_BasicFeeAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.BasicFeeAllowance != nil { + l = m.BasicFeeAllowance.Size() + n += 1 + l + sovTypes(uint64(l)) + } + return n +} +func (m *FeeAllowance_PeriodicFeeAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.PeriodicFeeAllowance != nil { + l = m.PeriodicFeeAllowance.Size() + n += 1 + l + sovTypes(uint64(l)) + } + return n +} +func (m *MsgGrantFeeAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Granter) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.Grantee) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.Allowance != nil { + l = m.Allowance.Size() + n += 1 + l + sovTypes(uint64(l)) + } + return n +} + +func (m *MsgRevokeFeeAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Granter) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.Grantee) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + return n +} + +func (m *BasicFeeAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.SpendLimit) > 0 { + for _, e := range m.SpendLimit { + l = e.Size() + n += 1 + l + sovTypes(uint64(l)) + } + } + l = m.Expiration.Size() + n += 1 + l + sovTypes(uint64(l)) + return n +} + +func (m *PeriodicFeeAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Basic.Size() + n += 1 + l + sovTypes(uint64(l)) + l = m.Period.Size() + n += 1 + l + sovTypes(uint64(l)) + if len(m.PeriodSpendLimit) > 0 { + for _, e := range m.PeriodSpendLimit { + l = e.Size() + n += 1 + l + sovTypes(uint64(l)) + } + } + if len(m.PeriodCanSpend) > 0 { + for _, e := range m.PeriodCanSpend { + l = e.Size() + n += 1 + l + sovTypes(uint64(l)) + } + } + l = m.PeriodReset.Size() + n += 1 + l + sovTypes(uint64(l)) + return n +} + +func (m *Duration) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.Clock) + n += 1 + l + sovTypes(uint64(l)) + if m.Block != 0 { + n += 1 + sovTypes(uint64(m.Block)) + } + return n +} + +func (m *ExpiresAt) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.Time) + n += 1 + l + sovTypes(uint64(l)) + if m.Height != 0 { + n += 1 + sovTypes(uint64(m.Height)) + } + return n +} + +func (m *FeeAllowanceGrant) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Granter) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + l = len(m.Grantee) + if l > 0 { + n += 1 + l + sovTypes(uint64(l)) + } + if m.Allowance != nil { + l = m.Allowance.Size() + n += 1 + l + sovTypes(uint64(l)) + } + return n +} + +func sovTypes(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTypes(x uint64) (n int) { + return sovTypes(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *FeeAllowance) 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 ErrIntOverflowTypes + } + 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: FeeAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FeeAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field BasicFeeAllowance", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &BasicFeeAllowance{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Sum = &FeeAllowance_BasicFeeAllowance{v} + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeriodicFeeAllowance", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + v := &PeriodicFeeAllowance{} + if err := v.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + m.Sum = &FeeAllowance_PeriodicFeeAllowance{v} + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgGrantFeeAllowance) 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 ErrIntOverflowTypes + } + 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: MsgGrantFeeAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgGrantFeeAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Granter", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Granter = append(m.Granter[:0], dAtA[iNdEx:postIndex]...) + if m.Granter == nil { + m.Granter = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Grantee", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Grantee = append(m.Grantee[:0], dAtA[iNdEx:postIndex]...) + if m.Grantee == nil { + m.Grantee = []byte{} + } + iNdEx = postIndex + case 3: + 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 ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Allowance == nil { + m.Allowance = &FeeAllowance{} + } + if err := m.Allowance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRevokeFeeAllowance) 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 ErrIntOverflowTypes + } + 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: MsgRevokeFeeAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRevokeFeeAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Granter", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Granter = append(m.Granter[:0], dAtA[iNdEx:postIndex]...) + if m.Granter == nil { + m.Granter = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Grantee", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Grantee = append(m.Grantee[:0], dAtA[iNdEx:postIndex]...) + if m.Grantee == nil { + m.Grantee = []byte{} + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *BasicFeeAllowance) 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 ErrIntOverflowTypes + } + 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: BasicFeeAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BasicFeeAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpendLimit", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.SpendLimit = append(m.SpendLimit, types.Coin{}) + if err := m.SpendLimit[len(m.SpendLimit)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Expiration", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Expiration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *PeriodicFeeAllowance) 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 ErrIntOverflowTypes + } + 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: PeriodicFeeAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PeriodicFeeAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Basic", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Basic.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Period", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.Period.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeriodSpendLimit", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PeriodSpendLimit = append(m.PeriodSpendLimit, types.Coin{}) + if err := m.PeriodSpendLimit[len(m.PeriodSpendLimit)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeriodCanSpend", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.PeriodCanSpend = append(m.PeriodCanSpend, types.Coin{}) + if err := m.PeriodCanSpend[len(m.PeriodCanSpend)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeriodReset", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.PeriodReset.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTypes + } + 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 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + 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: Duration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Duration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Clock", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.Clock, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Block", wireType) + } + m.Block = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Block |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ExpiresAt) 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 ErrIntOverflowTypes + } + 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: ExpiresAt: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ExpiresAt: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.Time, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType) + } + m.Height = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.Height |= int64(b&0x7F) << shift + if b < 0x80 { + break + } + } + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *FeeAllowanceGrant) 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 ErrIntOverflowTypes + } + 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: FeeAllowanceGrant: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: FeeAllowanceGrant: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Granter", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Granter = append(m.Granter[:0], dAtA[iNdEx:postIndex]...) + if m.Granter == nil { + m.Granter = []byte{} + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Grantee", wireType) + } + var byteLen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + byteLen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if byteLen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + byteLen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Grantee = append(m.Grantee[:0], dAtA[iNdEx:postIndex]...) + if m.Grantee == nil { + m.Grantee = []byte{} + } + iNdEx = postIndex + case 3: + 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 ErrIntOverflowTypes + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTypes + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTypes + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Allowance == nil { + m.Allowance = &FeeAllowance{} + } + if err := m.Allowance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTypes(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) < 0 { + return ErrInvalidLengthTypes + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTypes(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTypes + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTypes + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTypes + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTypes + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTypes + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTypes + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTypes = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTypes = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTypes = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/feegrant/types/types.proto b/x/feegrant/types/types.proto new file mode 100644 index 000000000000..69eab22c1e70 --- /dev/null +++ b/x/feegrant/types/types.proto @@ -0,0 +1,105 @@ +syntax = "proto3"; +package cosmos_sdk.x.feegrant.v1; + +option go_package = "github.com/cosmos/cosmos-sdk/x/feegrant/types"; + +import "third_party/proto/cosmos-proto/cosmos.proto"; +import "third_party/proto/gogoproto/gogo.proto"; +import "types/types.proto"; +import "google/protobuf/timestamp.proto"; + +// FeeAllowance defines the application-level fee allowance to be used in +// feegrant module +message FeeAllowance { + option (gogoproto.equal) = true; + option (cosmos_proto.interface_type) = "*FeeAllowanceI"; + + // sum defines a set of all acceptable concrete feeallowance implementations. + oneof sum{ + BasicFeeAllowance basic_fee_allowance= 1; + PeriodicFeeAllowance periodic_fee_allowance= 2; + } +} +// MsgGrantFeeAllowance adds permission for Grantee to spend up to Allowance +// of fees from the account of Granter. +message MsgGrantFeeAllowance{ + option (gogoproto.goproto_getters) = false; + + bytes granter = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + bytes grantee = 2 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + FeeAllowance allowance = 3; +} + +// MsgRevokeFeeAllowance removes any existing FeeAllowance from Granter to Grantee. +message MsgRevokeFeeAllowance{ + bytes granter = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + bytes grantee = 2 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; +} + +// BasicFeeAllowance implements FeeAllowance with a one-time grant of tokens +// that optionally expires. The delegatee can use up to SpendLimit to cover fees. +message BasicFeeAllowance{ + option (gogoproto.equal) = true; + option (gogoproto.goproto_getters) = false; + + repeated cosmos_sdk.v1.Coin spend_limit = 1 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.moretags) = "yaml:\"spend_limit\"" + ]; + ExpiresAt expiration = 2 [(gogoproto.nullable) = false]; +} + +// PeriodicFeeAllowance extends FeeAllowance to allow for both a maximum cap, +// as well as a limit per time period. +message PeriodicFeeAllowance{ + option (gogoproto.equal) = true; + option (gogoproto.goproto_getters) = false; + + BasicFeeAllowance basic = 1[(gogoproto.nullable) = false]; + Duration period = 2[(gogoproto.nullable) = false]; + repeated cosmos_sdk.v1.Coin period_spend_limit = 3 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.moretags) = "yaml:\"period_spend_limit\"" + ]; + repeated cosmos_sdk.v1.Coin period_can_spend = 4 [ + (gogoproto.nullable) = false, + (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins", + (gogoproto.moretags) = "yaml:\"period_can_spend\"" + ]; + + ExpiresAt period_reset = 5 [(gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"period_reset\""]; +} + +// Duration is a repeating unit of either clock time or number of blocks. +// This is designed to be added to an ExpiresAt struct. +message Duration{ + option (gogoproto.equal) = true; + google.protobuf.Timestamp clock = 1[(gogoproto.stdduration) = true, + (gogoproto.nullable) = false + ]; + int64 block = 2; +} + +// ExpiresAt is a point in time where something expires. +// It may be *either* block time or block height +message ExpiresAt{ + option (gogoproto.equal) = true; + google.protobuf.Timestamp time = 1 [(gogoproto.stdtime) = true, + (gogoproto.nullable) = false + ]; + int64 height = 2; +} + + + +// FeeAllowanceGrant is stored in the KVStore to record a grant with full context +message FeeAllowanceGrant{ + option (gogoproto.goproto_getters) = false; + + bytes granter = 1 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + bytes grantee = 2 [(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress"]; + FeeAllowance allowance = 3; +} \ No newline at end of file