github.com/Finschia/finschia-sdk@v0.49.1/x/feegrant/periodic_fee.go (about)

     1  package feegrant
     2  
     3  import (
     4  	"time"
     5  
     6  	sdk "github.com/Finschia/finschia-sdk/types"
     7  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
     8  )
     9  
    10  var _ FeeAllowanceI = (*PeriodicAllowance)(nil)
    11  
    12  // Accept can use fee payment requested as well as timestamp of the current block
    13  // to determine whether or not to process this. This is checked in
    14  // Keeper.UseGrantedFees and the return values should match how it is handled there.
    15  //
    16  // If it returns an error, the fee payment is rejected, otherwise it is accepted.
    17  // The FeeAllowance implementation is expected to update it's internal state
    18  // and will be saved again after an acceptance.
    19  //
    20  // If remove is true (regardless of the error), the FeeAllowance will be deleted from storage
    21  // (eg. when it is used up). (See call to RevokeAllowance in Keeper.UseGrantedFees)
    22  func (a *PeriodicAllowance) Accept(ctx sdk.Context, fee sdk.Coins, _ []sdk.Msg) (bool, error) {
    23  	blockTime := ctx.BlockTime()
    24  
    25  	if a.Basic.Expiration != nil && blockTime.After(*a.Basic.Expiration) {
    26  		return true, sdkerrors.Wrap(ErrFeeLimitExpired, "absolute limit")
    27  	}
    28  
    29  	a.tryResetPeriod(blockTime)
    30  
    31  	// deduct from both the current period and the max amount
    32  	var isNeg bool
    33  	a.PeriodCanSpend, isNeg = a.PeriodCanSpend.SafeSub(fee)
    34  	if isNeg {
    35  		return false, sdkerrors.Wrap(ErrFeeLimitExceeded, "period limit")
    36  	}
    37  
    38  	if a.Basic.SpendLimit != nil {
    39  		a.Basic.SpendLimit, isNeg = a.Basic.SpendLimit.SafeSub(fee)
    40  		if isNeg {
    41  			return false, sdkerrors.Wrap(ErrFeeLimitExceeded, "absolute limit")
    42  		}
    43  
    44  		return a.Basic.SpendLimit.IsZero(), nil
    45  	}
    46  
    47  	return false, nil
    48  }
    49  
    50  // tryResetPeriod will check if the PeriodReset has been hit. If not, it is a no-op.
    51  // If we hit the reset period, it will top up the PeriodCanSpend amount to
    52  // min(PeriodSpendLimit, Basic.SpendLimit) so it is never more than the maximum allowed.
    53  // It will also update the PeriodReset. If we are within one Period, it will update from the
    54  // last PeriodReset (eg. if you always do one tx per day, it will always reset the same time)
    55  // If we are more then one period out (eg. no activity in a week), reset is one Period from the execution of this method
    56  func (a *PeriodicAllowance) tryResetPeriod(blockTime time.Time) {
    57  	if blockTime.Before(a.PeriodReset) {
    58  		return
    59  	}
    60  
    61  	// set PeriodCanSpend to the lesser of Basic.SpendLimit and PeriodSpendLimit
    62  	if _, isNeg := a.Basic.SpendLimit.SafeSub(a.PeriodSpendLimit); isNeg && !a.Basic.SpendLimit.Empty() {
    63  		a.PeriodCanSpend = a.Basic.SpendLimit
    64  	} else {
    65  		a.PeriodCanSpend = a.PeriodSpendLimit
    66  	}
    67  
    68  	// 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)
    69  	// If we are more then one period out (eg. no activity in a week), reset is one period from this time
    70  	a.PeriodReset = a.PeriodReset.Add(a.Period)
    71  	if blockTime.After(a.PeriodReset) {
    72  		a.PeriodReset = blockTime.Add(a.Period)
    73  	}
    74  }
    75  
    76  // ValidateBasic implements FeeAllowance and enforces basic sanity checks
    77  func (a PeriodicAllowance) ValidateBasic() error {
    78  	if err := a.Basic.ValidateBasic(); err != nil {
    79  		return err
    80  	}
    81  
    82  	if !a.PeriodSpendLimit.IsValid() {
    83  		return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "spend amount is invalid: %s", a.PeriodSpendLimit)
    84  	}
    85  	if !a.PeriodSpendLimit.IsAllPositive() {
    86  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "spend limit must be positive")
    87  	}
    88  	if !a.PeriodCanSpend.IsValid() {
    89  		return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "can spend amount is invalid: %s", a.PeriodCanSpend)
    90  	}
    91  	// We allow 0 for CanSpend
    92  	if a.PeriodCanSpend.IsAnyNegative() {
    93  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "can spend must not be negative")
    94  	}
    95  
    96  	// ensure PeriodSpendLimit can be subtracted from total (same coin types)
    97  	if a.Basic.SpendLimit != nil && !a.PeriodSpendLimit.DenomsSubsetOf(a.Basic.SpendLimit) {
    98  		return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "period spend limit has different currency than basic spend limit")
    99  	}
   100  
   101  	// check times
   102  	if a.Period.Seconds() < 0 {
   103  		return sdkerrors.Wrap(ErrInvalidDuration, "negative clock step")
   104  	}
   105  
   106  	return nil
   107  }