331 lines
9.8 KiB
Go
331 lines
9.8 KiB
Go
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
|
|
"cosmossdk.io/collections"
|
|
"cosmossdk.io/core/address"
|
|
"cosmossdk.io/core/appmodule"
|
|
corecontext "cosmossdk.io/core/context"
|
|
"cosmossdk.io/core/event"
|
|
errorsmod "cosmossdk.io/errors"
|
|
"cosmossdk.io/x/feegrant"
|
|
|
|
"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/ante"
|
|
)
|
|
|
|
// 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 {
|
|
appmodule.Environment
|
|
|
|
cdc codec.BinaryCodec
|
|
addrCdc address.Codec
|
|
Schema collections.Schema
|
|
// FeeAllowance key: grantee+granter | value: Grant
|
|
FeeAllowance collections.Map[collections.Pair[sdk.AccAddress, sdk.AccAddress], feegrant.Grant]
|
|
// FeeAllowanceQueue key: expiration time+grantee+granter | value: bool
|
|
FeeAllowanceQueue collections.Map[collections.Triple[time.Time, sdk.AccAddress, sdk.AccAddress], bool]
|
|
}
|
|
|
|
var _ ante.FeegrantKeeper = &Keeper{}
|
|
|
|
// NewKeeper creates a feegrant Keeper
|
|
func NewKeeper(env appmodule.Environment, cdc codec.BinaryCodec, addrCdc address.Codec) Keeper {
|
|
sb := collections.NewSchemaBuilder(env.KVStoreService)
|
|
|
|
return Keeper{
|
|
Environment: env,
|
|
cdc: cdc,
|
|
addrCdc: addrCdc,
|
|
FeeAllowance: collections.NewMap(
|
|
sb,
|
|
feegrant.FeeAllowanceKeyPrefix,
|
|
"allowances",
|
|
collections.PairKeyCodec(sdk.LengthPrefixedAddressKey(sdk.AccAddressKey), sdk.LengthPrefixedAddressKey(sdk.AccAddressKey)), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility
|
|
codec.CollValue[feegrant.Grant](cdc),
|
|
),
|
|
FeeAllowanceQueue: collections.NewMap(
|
|
sb,
|
|
feegrant.FeeAllowanceQueueKeyPrefix,
|
|
"allowances_queue",
|
|
collections.TripleKeyCodec(sdk.TimeKey, sdk.LengthPrefixedAddressKey(sdk.AccAddressKey), sdk.LengthPrefixedAddressKey(sdk.AccAddressKey)), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility
|
|
collections.BoolValue,
|
|
),
|
|
}
|
|
}
|
|
|
|
// GrantAllowance creates a new grant
|
|
func (k Keeper) GrantAllowance(ctx context.Context, granter, grantee sdk.AccAddress, feeAllowance feegrant.FeeAllowanceI) error {
|
|
// Checking for duplicate entry
|
|
if f, _ := k.GetAllowance(ctx, granter, grantee); f != nil {
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "fee allowance already exists")
|
|
}
|
|
|
|
exp, err := feeAllowance.ExpiresAt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// expiration shouldn't be in the past.
|
|
|
|
now := k.HeaderService.HeaderInfo(ctx).Time
|
|
if exp != nil && exp.Before(now) {
|
|
return errorsmod.Wrap(sdkerrors.ErrInvalidRequest, "expiration is before current block time")
|
|
}
|
|
|
|
// if expiry is not nil, add the new key to pruning queue.
|
|
if exp != nil {
|
|
err = k.FeeAllowanceQueue.Set(ctx, collections.Join3(*exp, grantee, granter), true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
granterStr, err := k.addrCdc.BytesToString(granter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
granteeStr, err := k.addrCdc.BytesToString(grantee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// if block time is not zero, update the period reset
|
|
// if it is zero, it could be genesis initialization, so we don't need to update the period reset
|
|
if !now.IsZero() {
|
|
err = feeAllowance.UpdatePeriodReset(now)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
grant, err := feegrant.NewGrant(granterStr, granteeStr, feeAllowance)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := k.FeeAllowance.Set(ctx, collections.Join(grantee, granter), grant); err != nil {
|
|
return err
|
|
}
|
|
|
|
return k.EventService.EventManager(ctx).EmitKV(
|
|
feegrant.EventTypeSetFeeGrant,
|
|
event.NewAttribute(feegrant.AttributeKeyGranter, grant.Granter),
|
|
event.NewAttribute(feegrant.AttributeKeyGrantee, grant.Grantee),
|
|
)
|
|
}
|
|
|
|
// UpdateAllowance updates the existing grant.
|
|
func (k Keeper) UpdateAllowance(ctx context.Context, granter, grantee sdk.AccAddress, feeAllowance feegrant.FeeAllowanceI) error {
|
|
_, err := k.GetAllowance(ctx, granter, grantee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
granterStr, err := k.addrCdc.BytesToString(granter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
granteeStr, err := k.addrCdc.BytesToString(grantee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
grant, err := feegrant.NewGrant(granterStr, granteeStr, feeAllowance)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := k.FeeAllowance.Set(ctx, collections.Join(grantee, granter), grant); err != nil {
|
|
return err
|
|
}
|
|
|
|
return k.EventService.EventManager(ctx).EmitKV(
|
|
feegrant.EventTypeUpdateFeeGrant,
|
|
event.NewAttribute(feegrant.AttributeKeyGranter, grant.Granter),
|
|
event.NewAttribute(feegrant.AttributeKeyGrantee, grant.Grantee),
|
|
)
|
|
}
|
|
|
|
// revokeAllowance removes an existing grant
|
|
func (k Keeper) revokeAllowance(ctx context.Context, granter, grantee sdk.AccAddress) error {
|
|
grant, err := k.GetAllowance(ctx, granter, grantee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err := k.FeeAllowance.Remove(ctx, collections.Join(grantee, granter)); err != nil {
|
|
return err
|
|
}
|
|
|
|
exp, err := grant.ExpiresAt()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if exp != nil {
|
|
if err := k.FeeAllowanceQueue.Remove(ctx, collections.Join3(*exp, grantee, granter)); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
granterStr, err := k.addrCdc.BytesToString(granter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
granteeStr, err := k.addrCdc.BytesToString(grantee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return k.EventService.EventManager(ctx).EmitKV(
|
|
feegrant.EventTypeRevokeFeeGrant,
|
|
event.NewAttribute(feegrant.AttributeKeyGranter, granterStr),
|
|
event.NewAttribute(feegrant.AttributeKeyGrantee, granteeStr),
|
|
)
|
|
}
|
|
|
|
// GetAllowance returns the allowance between the granter and grantee.
|
|
// If there is none, it returns nil, collections.ErrNotFound.
|
|
// Returns an error on parsing issues
|
|
func (k Keeper) GetAllowance(ctx context.Context, granter, grantee sdk.AccAddress) (feegrant.FeeAllowanceI, error) {
|
|
grant, err := k.FeeAllowance.Get(ctx, collections.Join(grantee, granter))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return grant.GetGrant()
|
|
}
|
|
|
|
// 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 context.Context, cb func(grant feegrant.Grant) bool) error {
|
|
return k.FeeAllowance.Walk(ctx, nil, func(key collections.Pair[sdk.AccAddress, sdk.AccAddress], grant feegrant.Grant) (stop bool, err error) {
|
|
return cb(grant), nil
|
|
})
|
|
}
|
|
|
|
// UseGrantedFees will try to pay the given fee from the granter's account as requested by the grantee
|
|
func (k Keeper) UseGrantedFees(ctx context.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error {
|
|
grant, err := k.GetAllowance(ctx, granter, grantee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
granterStr, err := k.addrCdc.BytesToString(granter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
granteeStr, err := k.addrCdc.BytesToString(grantee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
remove, err := grant.Accept(context.WithValue(ctx, corecontext.EnvironmentContextKey, k.Environment), fee, msgs)
|
|
if remove && err == nil {
|
|
// Ignoring the `revokeFeeAllowance` error, because the user has enough grants to perform this transaction.
|
|
_ = k.revokeAllowance(ctx, granter, grantee)
|
|
|
|
return k.emitUseGrantEvent(ctx, granterStr, granteeStr)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := k.emitUseGrantEvent(ctx, granterStr, granteeStr); err != nil {
|
|
return err
|
|
}
|
|
|
|
// if fee allowance is accepted, store the updated state of the allowance
|
|
return k.UpdateAllowance(ctx, granter, grantee, grant)
|
|
}
|
|
|
|
func (k *Keeper) emitUseGrantEvent(ctx context.Context, granter, grantee string) error {
|
|
return k.EventService.EventManager(ctx).EmitKV(
|
|
feegrant.EventTypeUseFeeGrant,
|
|
event.NewAttribute(feegrant.AttributeKeyGranter, granter),
|
|
event.NewAttribute(feegrant.AttributeKeyGrantee, grantee),
|
|
)
|
|
}
|
|
|
|
// InitGenesis will initialize the keeper from a *previously validated* GenesisState
|
|
func (k Keeper) InitGenesis(ctx context.Context, data *feegrant.GenesisState) error {
|
|
for _, f := range data.Allowances {
|
|
granter, err := k.addrCdc.StringToBytes(f.Granter)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
grantee, err := k.addrCdc.StringToBytes(f.Grantee)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
grant, err := f.GetGrant()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = k.GrantAllowance(ctx, granter, grantee, grant)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// ExportGenesis will dump the contents of the keeper into a serializable GenesisState.
|
|
func (k Keeper) ExportGenesis(ctx context.Context) (*feegrant.GenesisState, error) {
|
|
var grants []feegrant.Grant
|
|
|
|
err := k.IterateAllFeeAllowances(ctx, func(grant feegrant.Grant) bool {
|
|
grants = append(grants, grant)
|
|
return false
|
|
})
|
|
|
|
return &feegrant.GenesisState{
|
|
Allowances: grants,
|
|
}, err
|
|
}
|
|
|
|
// RemoveExpiredAllowances iterates grantsByExpiryQueue and deletes the expired grants.
|
|
func (k Keeper) RemoveExpiredAllowances(ctx context.Context, limit int) error {
|
|
exp := k.HeaderService.HeaderInfo(ctx).Time
|
|
rng := collections.NewPrefixUntilTripleRange[time.Time, sdk.AccAddress, sdk.AccAddress](exp)
|
|
count := 0
|
|
|
|
keysToRemove := []collections.Triple[time.Time, sdk.AccAddress, sdk.AccAddress]{}
|
|
err := k.FeeAllowanceQueue.Walk(ctx, rng, func(key collections.Triple[time.Time, sdk.AccAddress, sdk.AccAddress], value bool) (stop bool, err error) {
|
|
grantee, granter := key.K2(), key.K3()
|
|
|
|
if err := k.FeeAllowance.Remove(ctx, collections.Join(grantee, granter)); err != nil {
|
|
return true, err
|
|
}
|
|
|
|
keysToRemove = append(keysToRemove, key)
|
|
|
|
// limit the amount of iterations to avoid taking too much time
|
|
count++
|
|
if count == limit {
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, key := range keysToRemove {
|
|
if err := k.FeeAllowanceQueue.Remove(ctx, key); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|