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

     1  package feegrant_test
     2  
     3  import (
     4  	"testing"
     5  	"time"
     6  
     7  	"github.com/stretchr/testify/assert"
     8  	"github.com/stretchr/testify/require"
     9  	tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
    10  
    11  	"github.com/Finschia/finschia-sdk/simapp"
    12  	sdk "github.com/Finschia/finschia-sdk/types"
    13  	"github.com/Finschia/finschia-sdk/x/feegrant"
    14  )
    15  
    16  func TestPeriodicFeeValidAllow(t *testing.T) {
    17  	app := simapp.Setup(false)
    18  	ctx := app.BaseApp.NewContext(false, tmproto.Header{
    19  		Time: time.Now(),
    20  	})
    21  
    22  	atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555))
    23  	smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 43))
    24  	leftAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 512))
    25  	oneAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 1))
    26  	eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 1))
    27  
    28  	now := ctx.BlockTime()
    29  	oneHour := now.Add(1 * time.Hour)
    30  	twoHours := now.Add(2 * time.Hour)
    31  	tenMinutes := time.Duration(10) * time.Minute
    32  
    33  	cases := map[string]struct {
    34  		allow         feegrant.PeriodicAllowance
    35  		fee           sdk.Coins
    36  		blockTime     time.Time
    37  		valid         bool // all other checks are ignored if valid=false
    38  		accept        bool
    39  		remove        bool
    40  		remains       sdk.Coins
    41  		remainsPeriod sdk.Coins
    42  		periodReset   time.Time
    43  	}{
    44  		"empty": {
    45  			allow: feegrant.PeriodicAllowance{},
    46  			valid: false,
    47  		},
    48  		"only basic": {
    49  			allow: feegrant.PeriodicAllowance{
    50  				Basic: feegrant.BasicAllowance{
    51  					SpendLimit: atom,
    52  					Expiration: &oneHour,
    53  				},
    54  			},
    55  			valid: false,
    56  		},
    57  		"empty basic": {
    58  			allow: feegrant.PeriodicAllowance{
    59  				Period:           tenMinutes,
    60  				PeriodSpendLimit: smallAtom,
    61  				PeriodReset:      now.Add(30 * time.Minute),
    62  			},
    63  			blockTime:   now,
    64  			valid:       true,
    65  			accept:      true,
    66  			remove:      false,
    67  			periodReset: now.Add(30 * time.Minute),
    68  		},
    69  		"mismatched currencies": {
    70  			allow: feegrant.PeriodicAllowance{
    71  				Basic: feegrant.BasicAllowance{
    72  					SpendLimit: atom,
    73  					Expiration: &oneHour,
    74  				},
    75  				Period:           tenMinutes,
    76  				PeriodSpendLimit: eth,
    77  			},
    78  			valid: false,
    79  		},
    80  		"same period": {
    81  			allow: feegrant.PeriodicAllowance{
    82  				Basic: feegrant.BasicAllowance{
    83  					SpendLimit: atom,
    84  					Expiration: &twoHours,
    85  				},
    86  				Period:           tenMinutes,
    87  				PeriodReset:      now.Add(1 * time.Hour),
    88  				PeriodSpendLimit: leftAtom,
    89  				PeriodCanSpend:   smallAtom,
    90  			},
    91  			valid:         true,
    92  			fee:           smallAtom,
    93  			blockTime:     now,
    94  			accept:        true,
    95  			remove:        false,
    96  			remainsPeriod: nil,
    97  			remains:       leftAtom,
    98  			periodReset:   now.Add(1 * time.Hour),
    99  		},
   100  		"step one period": {
   101  			allow: feegrant.PeriodicAllowance{
   102  				Basic: feegrant.BasicAllowance{
   103  					SpendLimit: atom,
   104  					Expiration: &twoHours,
   105  				},
   106  				Period:           tenMinutes,
   107  				PeriodReset:      now,
   108  				PeriodSpendLimit: leftAtom,
   109  			},
   110  			valid:         true,
   111  			fee:           leftAtom,
   112  			blockTime:     now.Add(1 * time.Hour),
   113  			accept:        true,
   114  			remove:        false,
   115  			remainsPeriod: nil,
   116  			remains:       smallAtom,
   117  			periodReset:   oneHour.Add(tenMinutes), // one step from last reset, not now
   118  		},
   119  		"step limited by global allowance": {
   120  			allow: feegrant.PeriodicAllowance{
   121  				Basic: feegrant.BasicAllowance{
   122  					SpendLimit: smallAtom,
   123  					Expiration: &twoHours,
   124  				},
   125  				Period:           tenMinutes,
   126  				PeriodReset:      now,
   127  				PeriodSpendLimit: atom,
   128  			},
   129  			valid:         true,
   130  			fee:           oneAtom,
   131  			blockTime:     oneHour,
   132  			accept:        true,
   133  			remove:        false,
   134  			remainsPeriod: smallAtom.Sub(oneAtom),
   135  			remains:       smallAtom.Sub(oneAtom),
   136  			periodReset:   oneHour.Add(tenMinutes), // one step from last reset, not now
   137  		},
   138  		"period reset no spend limit": {
   139  			allow: feegrant.PeriodicAllowance{
   140  				Period:           tenMinutes,
   141  				PeriodReset:      now,
   142  				PeriodSpendLimit: atom,
   143  			},
   144  			valid:       true,
   145  			fee:         atom,
   146  			blockTime:   oneHour,
   147  			accept:      true,
   148  			remove:      false,
   149  			periodReset: oneHour.Add(tenMinutes), // one step from last reset, not now
   150  		},
   151  		"expired": {
   152  			allow: feegrant.PeriodicAllowance{
   153  				Basic: feegrant.BasicAllowance{
   154  					SpendLimit: atom,
   155  					Expiration: &now,
   156  				},
   157  				Period:           time.Hour,
   158  				PeriodSpendLimit: smallAtom,
   159  			},
   160  			valid:     true,
   161  			fee:       smallAtom,
   162  			blockTime: oneHour,
   163  			accept:    false,
   164  			remove:    true,
   165  		},
   166  		"over period limit": {
   167  			allow: feegrant.PeriodicAllowance{
   168  				Basic: feegrant.BasicAllowance{
   169  					SpendLimit: atom,
   170  					Expiration: &now,
   171  				},
   172  				Period:           time.Hour,
   173  				PeriodReset:      now.Add(1 * time.Hour),
   174  				PeriodSpendLimit: leftAtom,
   175  				PeriodCanSpend:   smallAtom,
   176  			},
   177  			valid:     true,
   178  			fee:       leftAtom,
   179  			blockTime: now,
   180  			accept:    false,
   181  			remove:    true,
   182  		},
   183  	}
   184  
   185  	for name, stc := range cases {
   186  		tc := stc // to make scopelint happy
   187  		t.Run(name, func(t *testing.T) {
   188  			err := tc.allow.ValidateBasic()
   189  			if !tc.valid {
   190  				require.Error(t, err)
   191  				return
   192  			}
   193  			require.NoError(t, err)
   194  
   195  			ctx := app.BaseApp.NewContext(false, tmproto.Header{}).WithBlockTime(tc.blockTime)
   196  			// now try to deduct
   197  			remove, err := tc.allow.Accept(ctx, tc.fee, []sdk.Msg{})
   198  			if !tc.accept {
   199  				require.Error(t, err)
   200  				return
   201  			}
   202  			require.NoError(t, err)
   203  
   204  			require.Equal(t, tc.remove, remove)
   205  			if !remove {
   206  				assert.Equal(t, tc.remains, tc.allow.Basic.SpendLimit)
   207  				assert.Equal(t, tc.remainsPeriod, tc.allow.PeriodCanSpend)
   208  				assert.Equal(t, tc.periodReset.String(), tc.allow.PeriodReset.String())
   209  			}
   210  		})
   211  	}
   212  }