Grant is stored in the KVStore to record a grant with full context. Every grant will contain granter, grantee and what kind of allowance is granted. granter is an account address who is giving permission to grantee (the beneficiary account address) to pay for some or all of grantee's transaction fees. allowance defines what kind of fee allowance (BasicAllowance or PeriodicAllowance, see below) is granted to grantee. allowance accepts an interface which implements FeeAllowanceI, encoded as Any type. There can be only one existing fee grant allowed for a grantee and granter, self grants are not allowed.
Copy
// allowance can be any of basic, periodic, allowed fee allowance.
google.protobuf.Any allowance = 3 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"];
FeeAllowanceI looks like:
Copy
// 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 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 RevokeAllowance in Keeper.UseGrantedFees)Accept(ctx sdk.Context, fee sdk.Coins, msgs []sdk.Msg)(remove bool, err error)// ValidateBasic should evaluate this FeeAllowance for internal consistency.// Don't allow negative amounts, or negative periods for example.ValidateBasic()error// ExpiresAt returns the expiry time of the allowance.ExpiresAt()(*time.Time,error)}
BasicAllowance is permission for grantee to use fee from a granter's account. If any of the spend_limit or expiration reaches its limit, the grant will be removed from the state.
Copy
// BasicAllowance implements Allowance with a one-time grant of tokens
// that optionally expires. The grantee can use up to SpendLimit to cover fees.
message BasicAllowance {
option (cosmos_proto.implements_interface) = "FeeAllowanceI";
// spend_limit specifies the maximum amount of tokens that can be spent
// by this allowance and will be updated as tokens are spent. If it is
// empty, there is no spend limit and any amount of coins can be spent.
repeated cosmos.base.v1beta1.Coin spend_limit = 1
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// expiration specifies an optional time when this allowance expires
google.protobuf.Timestamp expiration = 2 [(gogoproto.stdtime) = true];
spend_limit is the limit of coins that are allowed to be used from the granter account. If it is empty, it assumes there's no spend limit, grantee can use any number of available coins from granter account address before the expiration.
expiration specifies an optional time when this allowance expires. If the value is left empty, there is no expiry for the grant.
When a grant is created with empty values for spend_limit and expiration, it is still a valid grant. It won't restrict the grantee to use any number of coins from granter and it won't have any expiration. The only way to restrict the grantee is by revoking the grant.
PeriodicAllowance is a repeating fee allowance for the mentioned period, we can mention when the grant can expire as well as when a period can reset. We can also define the maximum number of coins that can be used in a mentioned period of time.
Copy
// PeriodicAllowance extends Allowance to allow for both a maximum cap,
// as well as a limit per time period.
message PeriodicAllowance {
option (cosmos_proto.implements_interface) = "FeeAllowanceI";
// basic specifies a struct of `BasicAllowance`
BasicAllowance basic = 1 [(gogoproto.nullable) = false];
// period specifies the time duration in which period_spend_limit coins can
// be spent before that allowance is reset
google.protobuf.Duration period = 2 [(gogoproto.stdduration) = true, (gogoproto.nullable) = false];
// period_spend_limit specifies the maximum number of coins that can be spent
// in the period
repeated cosmos.base.v1beta1.Coin period_spend_limit = 3
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// period_can_spend is the number of coins left to be spent before the period_reset time
repeated cosmos.base.v1beta1.Coin period_can_spend = 4
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// period_reset is the time at which this period resets and a new one begins,
// it is calculated from the start time of the first transaction after the
// last period ended
google.protobuf.Timestamp period_reset = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
}
basic is the instance of BasicAllowance which is optional for periodic fee allowance. If empty, the grant will have no expiration and no spend_limit.
period is the specific period of time, after each period passes, period_spend_limit will be reset.
period_spend_limit specifies the maximum number of coins that can be spent in the period.
period_can_spend is the number of coins left to be spent before the period_reset time.
period_reset keeps track of when a next period reset should happen.
AllowedMsgAllowance is a fee allowance, it can be any of BasicFeeAllowance, PeriodicAllowance but restricted only to the allowed messages mentioned by the granter.
Copy
// AllowedMsgAllowance creates allowance only for specified message types.
message AllowedMsgAllowance {
option (gogoproto.goproto_getters) = false;
option (cosmos_proto.implements_interface) = "FeeAllowanceI";
// allowance can be any of basic and periodic fee allowance.
google.protobuf.Any allowance = 1 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"];
// allowed_messages are the messages for which the grantee has the access.
repeated string allowed_messages = 2;
}
allowance is either BasicAllowance or PeriodicAllowance.
allowed_messages is array of messages allowed to execute the given allowance.
feegrant module introduces a FeeGranter flag for CLI for the sake of executing transactions with fee granter. When this flag is set, clientCtx will append the granter account address for transactions generated through CLI.
Copy
func(w *wrapper)SetFeePayer(feePayer sdk.AccAddress){if w.tx.AuthInfo.Fee ==nil{
w.tx.AuthInfo.Fee =&tx.Fee{}}
w.tx.AuthInfo.Fee.Payer = feePayer.String()// set authInfoBz to nil because the cached authInfoBz no longer matches tx.AuthInfo
w.authInfoBz =nil}
Copy
// Fee 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.
message Fee {
// amount is the amount of coins to be paid as a fee
repeated cosmos.base.v1beta1.Coin amount = 1
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// gas_limit is the maximum gas that can be used in transaction processing
// before an out of gas error occurs
uint64 gas_limit = 2;
// if unset, the first signer is responsible for paying the fees. If set, the specified account must pay the fees.
// the payer must be a tx signer (and thus have signed this field in AuthInfo).
// setting this field does *not* change the ordering of required signers for the transaction.
string payer = 3 [(cosmos_proto.scalar) = "cosmos.AddressString"];
// if set, the fee payer (either the first signer or the value of the payer field) requests that a fee grant be used
// to pay fees instead of the fee payer's own balance. If an appropriate fee grant does not exist or the chain does
// not support fee grants, this will fail
string granter = 4 [(cosmos_proto.scalar) = "cosmos.AddressString"];
}
In order to prevent DoS attacks, using a filtered x/feegrant incurs gas. The SDK must assure that the grantee's transactions all conform to the filter set by the granter. The SDK does this by iterating over the allowed messages in the filter and charging 10 gas per filtered message. The SDK will then iterate over the messages being sent by the grantee to ensure the messages adhere to the filter, also charging 10 gas per message. The SDK will stop iterating and fail the transaction if it finds a message that does not conform to the filter.
WARNING: The gas is charged against the granted allowance. Ensure your messages conform to the filter, if any, before sending transactions using your allowance.
A queue in the state maintained with the prefix of expiration of the grants and checks them on EndBlock with the current block time for every block to prune.