x/feegrant remove height base expiration (#9206)
* remove height from proto files * remove PrepareForExport * fix basic fee * fix periodic fee * fix errors * fix error * fix errors * add tests * review changes * fix errors * fix tests * fix lint error * Update x/feegrant/types/basic_fee.go Co-authored-by: technicallyty <48813565+technicallyty@users.noreply.github.com> * fix errors * fix keeper tests * Update x/feegrant/keeper/keeper_test.go Co-authored-by: Marie Gauthier <marie.gauthier63@gmail.com> * review changes * review changes * fix tests * run make proto-gen * fix errors * Update x/feegrant/keeper/keeper_test.go Co-authored-by: atheeshp <59333759+atheeshp@users.noreply.github.com> * Update x/feegrant/keeper/keeper_test.go * update ADR * add test * review changes * review changes Co-authored-by: technicallyty <48813565+technicallyty@users.noreply.github.com> Co-authored-by: Marie Gauthier <marie.gauthier63@gmail.com> Co-authored-by: atheeshp <59333759+atheeshp@users.noreply.github.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
68d461052b
commit
1e1c812de2
@ -3,6 +3,7 @@
|
||||
## Changelog
|
||||
|
||||
- 2020/08/18: Initial Draft
|
||||
- 2021/05/05: Removed height based expiration support and simplified naming.
|
||||
|
||||
## Status
|
||||
|
||||
@ -38,87 +39,76 @@ Fee allowances are defined by the extensible `FeeAllowanceI` interface:
|
||||
|
||||
```go
|
||||
type FeeAllowanceI {
|
||||
// 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)
|
||||
// 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 RevokeFeeAllowance 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
|
||||
}
|
||||
```
|
||||
|
||||
Two basic fee allowance types, `BasicFeeAllowance` and `PeriodicFeeAllowance` are defined to support known use cases:
|
||||
Two basic fee allowance types, `BasicAllowance` and `PeriodicAllowance` are defined to support known use cases:
|
||||
|
||||
```proto
|
||||
// BasicFeeAllowance implements FeeAllowance with a one-time grant of tokens
|
||||
// BasicAllowance implements FeeAllowanceI with a one-time grant of tokens
|
||||
// that optionally expires. The delegatee can use up to SpendLimit to cover fees.
|
||||
message BasicFeeAllowance {
|
||||
// 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_sdk.v1.Coin spend_limit = 1;
|
||||
message BasicAllowance {
|
||||
// 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_sdk.v1.Coin spend_limit = 1;
|
||||
|
||||
// expires_at specifies an optional time when this allowance expires
|
||||
ExpiresAt expiration = 2;
|
||||
// expiration specifies an optional time when this allowance expires
|
||||
google.protobuf.Timestamp expiration = 2;
|
||||
}
|
||||
|
||||
// PeriodicFeeAllowance extends FeeAllowance to allow for both a maximum cap,
|
||||
// PeriodicAllowance extends FeeAllowanceI to allow for both a maximum cap,
|
||||
// as well as a limit per time period.
|
||||
message PeriodicFeeAllowance {
|
||||
BasicFeeAllowance basic = 1;
|
||||
message PeriodicAllowance {
|
||||
BasicAllowance basic = 1;
|
||||
|
||||
// period specifies the time duration in which period_spend_limit coins can
|
||||
// be spent before that allowance is reset
|
||||
Duration period = 2;
|
||||
|
||||
// period_spend_limit specifies the maximum number of coins that can be spent
|
||||
// in the period
|
||||
repeated cosmos_sdk.v1.Coin period_spend_limit = 3;
|
||||
// period specifies the time duration in which period_spend_limit coins can
|
||||
// be spent before that allowance is reset
|
||||
google.protobuf.Duration period = 2;
|
||||
|
||||
// period_can_spend is the number of coins left to be spent before the period_reset time
|
||||
repeated cosmos_sdk.v1.Coin period_can_spend = 4;
|
||||
|
||||
// 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
|
||||
ExpiresAt period_reset = 5;
|
||||
}
|
||||
// period_spend_limit specifies the maximum number of coins that can be spent
|
||||
// in the period
|
||||
repeated cosmos_sdk.v1.Coin period_spend_limit = 3;
|
||||
|
||||
// ExpiresAt is a point in time where something expires.
|
||||
// It may be *either* block time or block height
|
||||
message ExpiresAt {
|
||||
oneof sum {
|
||||
google.protobuf.Timestamp time = 1;
|
||||
uint64 height = 2;
|
||||
}
|
||||
}
|
||||
// period_can_spend is the number of coins left to be spent before the period_reset time
|
||||
repeated cosmos_sdk.v1.Coin period_can_spend = 4;
|
||||
|
||||
// Duration is a repeating unit of either clock time or number of blocks.
|
||||
message Duration {
|
||||
oneof sum {
|
||||
google.protobuf.Duration duration = 1;
|
||||
uint64 blocks = 2;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Allowances can be granted and revoked using `MsgGrantFeeAllowance` and `MsgRevokeFeeAllowance`:
|
||||
Allowances can be granted and revoked using `MsgGrantAllowance` and `MsgRevokeAllowance`:
|
||||
|
||||
```proto
|
||||
message MsgGrantFeeAllowance {
|
||||
// MsgGrantAllowance adds permission for Grantee to spend up to Allowance
|
||||
// of fees from the account of Granter.
|
||||
message MsgGrantAllowance {
|
||||
string granter = 1;
|
||||
string grantee = 2;
|
||||
google.protobuf.Any allowance = 3;
|
||||
}
|
||||
|
||||
// MsgRevokeFeeAllowance removes any existing FeeAllowance from Granter to Grantee.
|
||||
message MsgRevokeFeeAllowance {
|
||||
// MsgRevokeAllowance removes any existing FeeAllowance from Granter to Grantee.
|
||||
message MsgRevokeAllowance {
|
||||
string granter = 1;
|
||||
string grantee = 2;
|
||||
}
|
||||
|
||||
@ -309,8 +309,6 @@
|
||||
- [cosmos/feegrant/v1beta1/feegrant.proto](#cosmos/feegrant/v1beta1/feegrant.proto)
|
||||
- [AllowedMsgAllowance](#cosmos.feegrant.v1beta1.AllowedMsgAllowance)
|
||||
- [BasicAllowance](#cosmos.feegrant.v1beta1.BasicAllowance)
|
||||
- [Duration](#cosmos.feegrant.v1beta1.Duration)
|
||||
- [ExpiresAt](#cosmos.feegrant.v1beta1.ExpiresAt)
|
||||
- [Grant](#cosmos.feegrant.v1beta1.Grant)
|
||||
- [PeriodicAllowance](#cosmos.feegrant.v1beta1.PeriodicAllowance)
|
||||
|
||||
@ -4571,41 +4569,7 @@ that optionally expires. The grantee can use up to SpendLimit to cover fees.
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| `spend_limit` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | 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. |
|
||||
| `expiration` | [ExpiresAt](#cosmos.feegrant.v1beta1.ExpiresAt) | | expiration specifies an optional time when this allowance expires |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="cosmos.feegrant.v1beta1.Duration"></a>
|
||||
|
||||
### Duration
|
||||
Duration is a span of a clock time or number of blocks.
|
||||
This is designed to be added to an ExpiresAt struct.
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| `duration` | [google.protobuf.Duration](#google.protobuf.Duration) | | |
|
||||
| `blocks` | [uint64](#uint64) | | |
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
<a name="cosmos.feegrant.v1beta1.ExpiresAt"></a>
|
||||
|
||||
### ExpiresAt
|
||||
ExpiresAt is a point in time where something expires.
|
||||
It may be *either* block time or block height
|
||||
|
||||
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| `time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
|
||||
| `height` | [int64](#int64) | | |
|
||||
| `expiration` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | expiration specifies an optional time when this allowance expires |
|
||||
|
||||
|
||||
|
||||
@ -4639,10 +4603,10 @@ as well as a limit per time period.
|
||||
| Field | Type | Label | Description |
|
||||
| ----- | ---- | ----- | ----------- |
|
||||
| `basic` | [BasicAllowance](#cosmos.feegrant.v1beta1.BasicAllowance) | | basic specifies a struct of `BasicAllowance` |
|
||||
| `period` | [Duration](#cosmos.feegrant.v1beta1.Duration) | | period specifies the time duration in which period_spend_limit coins can be spent before that allowance is reset |
|
||||
| `period` | [google.protobuf.Duration](#google.protobuf.Duration) | | period specifies the time duration in which period_spend_limit coins can be spent before that allowance is reset |
|
||||
| `period_spend_limit` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | period_spend_limit specifies the maximum number of coins that can be spent in the period |
|
||||
| `period_can_spend` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | period_can_spend is the number of coins left to be spent before the period_reset time |
|
||||
| `period_reset` | [ExpiresAt](#cosmos.feegrant.v1beta1.ExpiresAt) | | 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 |
|
||||
| `period_reset` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | 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 |
|
||||
|
||||
|
||||
|
||||
|
||||
@ -22,7 +22,7 @@ message BasicAllowance {
|
||||
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
|
||||
|
||||
// expiration specifies an optional time when this allowance expires
|
||||
ExpiresAt expiration = 2 [(gogoproto.nullable) = false];
|
||||
google.protobuf.Timestamp expiration = 2 [(gogoproto.stdtime) = true];
|
||||
}
|
||||
|
||||
// PeriodicAllowance extends Allowance to allow for both a maximum cap,
|
||||
@ -35,7 +35,7 @@ message PeriodicAllowance {
|
||||
|
||||
// period specifies the time duration in which period_spend_limit coins can
|
||||
// be spent before that allowance is reset
|
||||
Duration period = 2 [(gogoproto.nullable) = false];
|
||||
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
|
||||
@ -49,7 +49,7 @@ message PeriodicAllowance {
|
||||
// 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
|
||||
ExpiresAt period_reset = 5 [(gogoproto.nullable) = false];
|
||||
google.protobuf.Timestamp period_reset = 5 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false];
|
||||
}
|
||||
|
||||
// AllowedMsgAllowance creates allowance only for specified message types.
|
||||
@ -64,26 +64,6 @@ message AllowedMsgAllowance {
|
||||
repeated string allowed_messages = 2;
|
||||
}
|
||||
|
||||
// Duration is a span of a clock time or number of blocks.
|
||||
// This is designed to be added to an ExpiresAt struct.
|
||||
message Duration {
|
||||
// sum is the oneof that represents either duration or block
|
||||
oneof sum {
|
||||
google.protobuf.Duration duration = 1 [(gogoproto.stdduration) = true];
|
||||
uint64 blocks = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// ExpiresAt is a point in time where something expires.
|
||||
// It may be *either* block time or block height
|
||||
message ExpiresAt {
|
||||
// sum is the oneof that represents either time or height
|
||||
oneof sum {
|
||||
google.protobuf.Timestamp time = 1 [(gogoproto.stdtime) = true];
|
||||
int64 height = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Grant is stored in the KVStore to record a grant with full context
|
||||
message Grant {
|
||||
// granter is the address of the user granting an allowance of their funds.
|
||||
|
||||
@ -107,7 +107,7 @@ Examples:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
basic.Expiration = types.ExpiresAtTime(expiresAtTime)
|
||||
basic.Expiration = &expiresAtTime
|
||||
}
|
||||
|
||||
var grant types.FeeAllowanceI
|
||||
@ -131,15 +131,15 @@ Examples:
|
||||
}
|
||||
|
||||
if periodClock > 0 && periodLimit != nil {
|
||||
periodReset := time.Now().Add(time.Duration(periodClock) * time.Second)
|
||||
periodReset := getPeriodReset(periodClock)
|
||||
if exp != "" && periodReset.Sub(expiresAtTime) > 0 {
|
||||
return fmt.Errorf("period(%d) cannot reset after expiration(%v)", periodClock, exp)
|
||||
}
|
||||
|
||||
periodic := types.PeriodicAllowance{
|
||||
Basic: basic,
|
||||
Period: types.ClockDuration(time.Duration(periodClock) * time.Second),
|
||||
PeriodReset: types.ExpiresAtTime(periodReset),
|
||||
Period: getPeriod(periodClock),
|
||||
PeriodReset: getPeriodReset(periodClock),
|
||||
PeriodSpendLimit: periodLimit,
|
||||
PeriodCanSpend: periodLimit,
|
||||
}
|
||||
@ -230,3 +230,11 @@ Example:
|
||||
flags.AddTxFlagsToCmd(cmd)
|
||||
return cmd
|
||||
}
|
||||
|
||||
func getPeriodReset(duration int64) time.Time {
|
||||
return time.Now().Add(getPeriod(duration))
|
||||
}
|
||||
|
||||
func getPeriod(duration int64) time.Duration {
|
||||
return time.Duration(duration) * time.Second
|
||||
}
|
||||
|
||||
@ -31,19 +31,12 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, data *types.GenesisState) err
|
||||
return nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
// ExportGenesis will dump the contents of the keeper into a serializable GenesisState.
|
||||
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) (*types.GenesisState, error) {
|
||||
time, height := ctx.BlockTime(), ctx.BlockHeight()
|
||||
var grants []types.Grant
|
||||
|
||||
err := k.IterateAllFeeAllowances(ctx, func(grant types.Grant) bool {
|
||||
grants = append(grants, grant.PrepareForExport(time, height))
|
||||
grants = append(grants, grant)
|
||||
return false
|
||||
})
|
||||
|
||||
|
||||
@ -39,9 +39,10 @@ var (
|
||||
func (suite *GenesisTestSuite) TestImportExportGenesis() {
|
||||
coins := sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1_000)))
|
||||
now := suite.ctx.BlockHeader().Time
|
||||
oneYear := now.AddDate(1, 0, 0)
|
||||
msgSrvr := keeper.NewMsgServerImpl(suite.keeper)
|
||||
|
||||
allowance := &types.BasicAllowance{SpendLimit: coins, Expiration: types.ExpiresAtTime(now.AddDate(1, 0, 0))}
|
||||
allowance := &types.BasicAllowance{SpendLimit: coins, Expiration: &oneYear}
|
||||
err := suite.keeper.GrantAllowance(suite.ctx, granterAddr, granteeAddr, allowance)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
|
||||
@ -149,9 +149,10 @@ func (suite *KeeperTestSuite) TestFeeAllowances() {
|
||||
}
|
||||
|
||||
func grantFeeAllowance(suite *KeeperTestSuite) {
|
||||
exp := suite.sdkCtx.BlockTime().AddDate(1, 0, 0)
|
||||
err := suite.app.FeeGrantKeeper.GrantAllowance(suite.sdkCtx, suite.addrs[0], suite.addrs[1], &types.BasicAllowance{
|
||||
SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 555)),
|
||||
Expiration: types.ExpiresAtHeight(334455),
|
||||
Expiration: &exp,
|
||||
})
|
||||
suite.Require().NoError(err)
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package keeper_test
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/suite"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
@ -46,14 +45,15 @@ func (suite *KeeperTestSuite) SetupTest() {
|
||||
func (suite *KeeperTestSuite) TestKeeperCrud() {
|
||||
// some helpers
|
||||
eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 123))
|
||||
exp := suite.sdkCtx.BlockTime().AddDate(1, 0, 0)
|
||||
basic := &types.BasicAllowance{
|
||||
SpendLimit: suite.atom,
|
||||
Expiration: types.ExpiresAtHeight(334455),
|
||||
Expiration: &exp,
|
||||
}
|
||||
|
||||
basic2 := &types.BasicAllowance{
|
||||
SpendLimit: eth,
|
||||
Expiration: types.ExpiresAtHeight(172436),
|
||||
Expiration: &exp,
|
||||
}
|
||||
|
||||
// let's set up some initial state here
|
||||
@ -149,24 +149,20 @@ func (suite *KeeperTestSuite) TestKeeperCrud() {
|
||||
|
||||
func (suite *KeeperTestSuite) TestUseGrantedFee() {
|
||||
eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 123))
|
||||
blockTime := suite.sdkCtx.BlockTime()
|
||||
oneYear := blockTime.AddDate(1, 0, 0)
|
||||
|
||||
future := &types.BasicAllowance{
|
||||
SpendLimit: suite.atom,
|
||||
Expiration: types.ExpiresAtHeight(5678),
|
||||
}
|
||||
|
||||
expired := &types.BasicAllowance{
|
||||
SpendLimit: eth,
|
||||
Expiration: types.ExpiresAtHeight(55),
|
||||
Expiration: &oneYear,
|
||||
}
|
||||
|
||||
// for testing limits of the contract
|
||||
hugeAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 9999))
|
||||
_ = hugeAtom
|
||||
smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 1))
|
||||
_ = smallAtom
|
||||
futureAfterSmall := &types.BasicAllowance{
|
||||
SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 554)),
|
||||
Expiration: types.ExpiresAtHeight(5678),
|
||||
Expiration: &oneYear,
|
||||
}
|
||||
|
||||
// then lots of queries
|
||||
@ -184,13 +180,6 @@ func (suite *KeeperTestSuite) TestUseGrantedFee() {
|
||||
allowed: true,
|
||||
final: nil,
|
||||
},
|
||||
"expired and removed": {
|
||||
granter: suite.addrs[0],
|
||||
grantee: suite.addrs[2],
|
||||
fee: eth,
|
||||
allowed: false,
|
||||
final: nil,
|
||||
},
|
||||
"too high": {
|
||||
granter: suite.addrs[0],
|
||||
grantee: suite.addrs[1],
|
||||
@ -210,16 +199,9 @@ func (suite *KeeperTestSuite) TestUseGrantedFee() {
|
||||
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)
|
||||
|
||||
err := suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[0], suite.addrs[1], future)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
err = suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[0], suite.addrs[3], expired)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
err = suite.keeper.UseGrantedFees(suite.sdkCtx, tc.granter, tc.grantee, tc.fee, []sdk.Msg{})
|
||||
if tc.allowed {
|
||||
suite.NoError(err)
|
||||
@ -228,22 +210,43 @@ func (suite *KeeperTestSuite) TestUseGrantedFee() {
|
||||
}
|
||||
|
||||
loaded, _ := suite.keeper.GetAllowance(suite.sdkCtx, tc.granter, tc.grantee)
|
||||
|
||||
suite.Equal(tc.final, loaded)
|
||||
})
|
||||
}
|
||||
|
||||
expired := &types.BasicAllowance{
|
||||
SpendLimit: eth,
|
||||
Expiration: &blockTime,
|
||||
}
|
||||
// creating expired feegrant
|
||||
ctx := suite.sdkCtx.WithBlockTime(oneYear)
|
||||
err := suite.keeper.GrantAllowance(ctx, suite.addrs[0], suite.addrs[2], expired)
|
||||
suite.Require().NoError(err)
|
||||
|
||||
// expect error: feegrant expired
|
||||
err = suite.keeper.UseGrantedFees(ctx, suite.addrs[0], suite.addrs[2], eth, []sdk.Msg{})
|
||||
suite.Error(err)
|
||||
suite.Contains(err.Error(), "fee allowance expired")
|
||||
|
||||
// verify: feegrant is revoked
|
||||
_, err = suite.keeper.GetAllowance(ctx, suite.addrs[0], suite.addrs[2])
|
||||
suite.Error(err)
|
||||
suite.Contains(err.Error(), "fee-grant not found")
|
||||
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestIterateGrants() {
|
||||
eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 123))
|
||||
exp := suite.sdkCtx.BlockTime().AddDate(1, 0, 0)
|
||||
|
||||
allowance := &types.BasicAllowance{
|
||||
SpendLimit: suite.atom,
|
||||
Expiration: types.ExpiresAtHeight(5678),
|
||||
Expiration: &exp,
|
||||
}
|
||||
|
||||
allowance1 := &types.BasicAllowance{
|
||||
SpendLimit: eth,
|
||||
Expiration: types.ExpiresAtTime(suite.sdkCtx.BlockTime().Add(24 * time.Hour)),
|
||||
Expiration: &exp,
|
||||
}
|
||||
|
||||
suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[0], suite.addrs[1], allowance)
|
||||
|
||||
@ -7,6 +7,8 @@ import (
|
||||
)
|
||||
|
||||
func (suite *KeeperTestSuite) TestGrantFeeAllowance() {
|
||||
oneYear := suite.sdkCtx.BlockTime().AddDate(1, 0, 0)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
req func() *types.MsgGrantAllowance
|
||||
@ -46,7 +48,7 @@ func (suite *KeeperTestSuite) TestGrantFeeAllowance() {
|
||||
func() *types.MsgGrantAllowance {
|
||||
any, err := codectypes.NewAnyWithValue(&types.BasicAllowance{
|
||||
SpendLimit: suite.atom,
|
||||
Expiration: types.ExpiresAtTime(suite.sdkCtx.BlockTime().AddDate(1, 0, 0)),
|
||||
Expiration: &oneYear,
|
||||
})
|
||||
suite.Require().NoError(err)
|
||||
return &types.MsgGrantAllowance{
|
||||
@ -63,7 +65,7 @@ func (suite *KeeperTestSuite) TestGrantFeeAllowance() {
|
||||
func() *types.MsgGrantAllowance {
|
||||
any, err := codectypes.NewAnyWithValue(&types.BasicAllowance{
|
||||
SpendLimit: suite.atom,
|
||||
Expiration: types.ExpiresAtTime(suite.sdkCtx.BlockTime().AddDate(1, 0, 0)),
|
||||
Expiration: &oneYear,
|
||||
})
|
||||
suite.Require().NoError(err)
|
||||
return &types.MsgGrantAllowance{
|
||||
@ -81,7 +83,7 @@ func (suite *KeeperTestSuite) TestGrantFeeAllowance() {
|
||||
any, err := codectypes.NewAnyWithValue(&types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: suite.atom,
|
||||
Expiration: types.ExpiresAtTime(suite.sdkCtx.BlockTime().AddDate(1, 0, 0)),
|
||||
Expiration: &oneYear,
|
||||
},
|
||||
})
|
||||
suite.Require().NoError(err)
|
||||
@ -100,7 +102,7 @@ func (suite *KeeperTestSuite) TestGrantFeeAllowance() {
|
||||
any, err := codectypes.NewAnyWithValue(&types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: suite.atom,
|
||||
Expiration: types.ExpiresAtTime(suite.sdkCtx.BlockTime().AddDate(1, 0, 0)),
|
||||
Expiration: &oneYear,
|
||||
},
|
||||
})
|
||||
suite.Require().NoError(err)
|
||||
@ -126,6 +128,7 @@ func (suite *KeeperTestSuite) TestGrantFeeAllowance() {
|
||||
}
|
||||
|
||||
func (suite *KeeperTestSuite) TestRevokeFeeAllowance() {
|
||||
oneYear := suite.sdkCtx.BlockTime().AddDate(1, 0, 0)
|
||||
|
||||
testCases := []struct {
|
||||
name string
|
||||
@ -179,7 +182,7 @@ func (suite *KeeperTestSuite) TestRevokeFeeAllowance() {
|
||||
any, err := codectypes.NewAnyWithValue(&types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: suite.atom,
|
||||
Expiration: types.ExpiresAtTime(suite.sdkCtx.BlockTime().AddDate(1, 0, 0)),
|
||||
Expiration: &oneYear,
|
||||
},
|
||||
})
|
||||
suite.Require().NoError(err)
|
||||
|
||||
@ -43,7 +43,7 @@ func generateRandomAllowances(granter, grantee sdk.AccAddress, r *rand.Rand) typ
|
||||
periodicAllowance, err := types.NewGrant(granter, grantee, &types.PeriodicAllowance{
|
||||
Basic: basic,
|
||||
PeriodSpendLimit: periodSpendLimit,
|
||||
Period: types.ClockDuration(time.Hour),
|
||||
Period: time.Hour,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
||||
@ -3,7 +3,6 @@ package simulation
|
||||
import (
|
||||
"context"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/baseapp"
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
@ -91,9 +90,10 @@ func SimulateMsgGrantFeeAllowance(ak types.AccountKeeper, bk types.BankKeeper, k
|
||||
return simtypes.NoOpMsg(types.ModuleName, TypeMsgGrantFeeAllowance, "unable to grant empty coins as SpendLimit"), nil, nil
|
||||
}
|
||||
|
||||
oneYear := ctx.BlockTime().AddDate(1, 0, 0)
|
||||
msg, err := types.NewMsgGrantAllowance(&types.BasicAllowance{
|
||||
SpendLimit: spendableCoins,
|
||||
Expiration: types.ExpiresAtTime(ctx.BlockTime().Add(30 * time.Hour)),
|
||||
Expiration: &oneYear,
|
||||
}, granter.Address, grantee.Address)
|
||||
|
||||
if err != nil {
|
||||
|
||||
@ -31,7 +31,9 @@ func (suite *SimTestSuite) SetupTest() {
|
||||
checkTx := false
|
||||
app := simapp.Setup(checkTx)
|
||||
suite.app = app
|
||||
suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{})
|
||||
suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{
|
||||
Time: time.Now(),
|
||||
})
|
||||
suite.protoCdc = codec.NewProtoCodec(suite.app.InterfaceRegistry())
|
||||
|
||||
}
|
||||
@ -139,13 +141,14 @@ func (suite *SimTestSuite) TestSimulateMsgRevokeFeeAllowance() {
|
||||
|
||||
granter, grantee := accounts[0], accounts[1]
|
||||
|
||||
oneYear := ctx.BlockTime().AddDate(1, 0, 0)
|
||||
err := app.FeeGrantKeeper.GrantAllowance(
|
||||
ctx,
|
||||
granter.Address,
|
||||
grantee.Address,
|
||||
&types.BasicAllowance{
|
||||
SpendLimit: feeCoins,
|
||||
Expiration: types.ExpiresAtTime(ctx.BlockTime().Add(30 * time.Hour)),
|
||||
Expiration: &oneYear,
|
||||
},
|
||||
)
|
||||
require.NoError(err)
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
)
|
||||
|
||||
var _ FeeAllowanceI = (*BasicAllowance)(nil)
|
||||
|
||||
// Accept can use fee payment requested as well as timestamp/height of the current block
|
||||
// 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.
|
||||
//
|
||||
@ -20,10 +18,7 @@ var _ FeeAllowanceI = (*BasicAllowance)(nil)
|
||||
// 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 *BasicAllowance) Accept(ctx sdk.Context, fee sdk.Coins, _ []sdk.Msg) (bool, error) {
|
||||
blockTime := ctx.BlockTime()
|
||||
blockHeight := ctx.BlockHeight()
|
||||
|
||||
if a.Expiration.IsExpired(&blockTime, blockHeight) {
|
||||
if a.Expiration != nil && a.Expiration.Before(ctx.BlockTime()) {
|
||||
return true, sdkerrors.Wrap(ErrFeeLimitExpired, "basic allowance")
|
||||
}
|
||||
|
||||
@ -40,16 +35,6 @@ func (a *BasicAllowance) Accept(ctx sdk.Context, fee sdk.Coins, _ []sdk.Msg) (bo
|
||||
return false, 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 *BasicAllowance) PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceI {
|
||||
return &BasicAllowance{
|
||||
SpendLimit: a.SpendLimit,
|
||||
Expiration: a.Expiration.PrepareForExport(dumpTime, dumpHeight),
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateBasic implements FeeAllowance and enforces basic sanity checks
|
||||
func (a BasicAllowance) ValidateBasic() error {
|
||||
if a.SpendLimit != nil {
|
||||
@ -60,5 +45,10 @@ func (a BasicAllowance) ValidateBasic() error {
|
||||
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "spend limit must be positive")
|
||||
}
|
||||
}
|
||||
return a.Expiration.ValidateBasic()
|
||||
|
||||
if a.Expiration != nil && a.Expiration.Unix() < 0 {
|
||||
return sdkerrors.Wrap(ErrInvalidDuration, "expiration time cannot be negative")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -16,24 +16,37 @@ import (
|
||||
func TestBasicFeeValidAllow(t *testing.T) {
|
||||
app := simapp.Setup(false)
|
||||
|
||||
ctx := app.BaseApp.NewContext(false, tmproto.Header{})
|
||||
badTime := ctx.BlockTime().AddDate(0, 0, -1)
|
||||
allowace := &types.BasicAllowance{
|
||||
Expiration: &badTime,
|
||||
}
|
||||
require.Error(t, allowace.ValidateBasic())
|
||||
|
||||
ctx = app.BaseApp.NewContext(false, tmproto.Header{
|
||||
Time: time.Now(),
|
||||
})
|
||||
eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 10))
|
||||
atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555))
|
||||
smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 43))
|
||||
bigAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 1000))
|
||||
leftAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 512))
|
||||
now := ctx.BlockTime()
|
||||
oneHour := now.Add(1 * time.Hour)
|
||||
|
||||
cases := map[string]struct {
|
||||
allowance *types.BasicAllowance
|
||||
// all other checks are ignored if valid=false
|
||||
fee sdk.Coins
|
||||
blockHeight int64
|
||||
accept bool
|
||||
remove bool
|
||||
remains sdk.Coins
|
||||
fee sdk.Coins
|
||||
blockTime time.Time
|
||||
valid bool
|
||||
accept bool
|
||||
remove bool
|
||||
remains sdk.Coins
|
||||
}{
|
||||
"empty": {
|
||||
allowance: &types.BasicAllowance{},
|
||||
accept: true,
|
||||
allowance: &types.BasicAllowance{},
|
||||
accept: true,
|
||||
},
|
||||
"small fee without expire": {
|
||||
allowance: &types.BasicAllowance{
|
||||
@ -62,48 +75,53 @@ func TestBasicFeeValidAllow(t *testing.T) {
|
||||
"non-expired": {
|
||||
allowance: &types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
Expiration: &oneHour,
|
||||
},
|
||||
fee: smallAtom,
|
||||
blockHeight: 85,
|
||||
accept: true,
|
||||
remove: false,
|
||||
remains: leftAtom,
|
||||
valid: true,
|
||||
fee: smallAtom,
|
||||
blockTime: now,
|
||||
accept: true,
|
||||
remove: false,
|
||||
remains: leftAtom,
|
||||
},
|
||||
"expired": {
|
||||
allowance: &types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
Expiration: &now,
|
||||
},
|
||||
fee: smallAtom,
|
||||
blockHeight: 121,
|
||||
accept: false,
|
||||
remove: true,
|
||||
valid: true,
|
||||
fee: smallAtom,
|
||||
blockTime: oneHour,
|
||||
accept: false,
|
||||
remove: true,
|
||||
},
|
||||
"fee more than allowed": {
|
||||
allowance: &types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
Expiration: &oneHour,
|
||||
},
|
||||
fee: bigAtom,
|
||||
blockHeight: 85,
|
||||
accept: false,
|
||||
valid: true,
|
||||
fee: bigAtom,
|
||||
blockTime: now,
|
||||
accept: false,
|
||||
},
|
||||
"with out spend limit": {
|
||||
allowance: &types.BasicAllowance{
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
Expiration: &oneHour,
|
||||
},
|
||||
fee: bigAtom,
|
||||
blockHeight: 85,
|
||||
accept: true,
|
||||
valid: true,
|
||||
fee: bigAtom,
|
||||
blockTime: now,
|
||||
accept: true,
|
||||
},
|
||||
"expired no spend limit": {
|
||||
allowance: &types.BasicAllowance{
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
Expiration: &now,
|
||||
},
|
||||
fee: bigAtom,
|
||||
blockHeight: 120,
|
||||
accept: false,
|
||||
valid: true,
|
||||
fee: bigAtom,
|
||||
blockTime: oneHour,
|
||||
accept: false,
|
||||
},
|
||||
}
|
||||
|
||||
@ -113,7 +131,7 @@ func TestBasicFeeValidAllow(t *testing.T) {
|
||||
err := tc.allowance.ValidateBasic()
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := app.BaseApp.NewContext(false, tmproto.Header{}).WithBlockHeight(tc.blockHeight)
|
||||
ctx := app.BaseApp.NewContext(false, tmproto.Header{}).WithBlockTime(tc.blockTime)
|
||||
|
||||
// now try to deduct
|
||||
removed, err := tc.allowance.Accept(ctx, tc.fee, []sdk.Msg{})
|
||||
@ -130,138 +148,3 @@ func TestBasicFeeValidAllow(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBasicFeeAllowTime(t *testing.T) {
|
||||
app := simapp.Setup(false)
|
||||
|
||||
eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 10))
|
||||
atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555))
|
||||
smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 43))
|
||||
bigAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 1000))
|
||||
leftAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 512))
|
||||
|
||||
now := time.Now()
|
||||
oneHour := now.Add(1 * time.Hour)
|
||||
|
||||
cases := map[string]struct {
|
||||
allow *types.BasicAllowance
|
||||
// all other checks are ignored if valid=false
|
||||
fee sdk.Coins
|
||||
blockTime time.Time
|
||||
valid bool
|
||||
accept bool
|
||||
remove bool
|
||||
remains sdk.Coins
|
||||
}{
|
||||
"empty": {
|
||||
allow: &types.BasicAllowance{},
|
||||
valid: true,
|
||||
accept: true,
|
||||
},
|
||||
"small fee without expire": {
|
||||
allow: &types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
},
|
||||
valid: true,
|
||||
fee: smallAtom,
|
||||
accept: true,
|
||||
remove: false,
|
||||
remains: leftAtom,
|
||||
},
|
||||
"all fee without expire": {
|
||||
allow: &types.BasicAllowance{
|
||||
SpendLimit: smallAtom,
|
||||
},
|
||||
valid: true,
|
||||
fee: smallAtom,
|
||||
accept: true,
|
||||
remove: true,
|
||||
},
|
||||
"wrong fee": {
|
||||
allow: &types.BasicAllowance{
|
||||
SpendLimit: smallAtom,
|
||||
},
|
||||
valid: true,
|
||||
fee: eth,
|
||||
accept: false,
|
||||
},
|
||||
"non-expired": {
|
||||
allow: &types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtTime(oneHour),
|
||||
},
|
||||
valid: true,
|
||||
fee: smallAtom,
|
||||
blockTime: now,
|
||||
accept: true,
|
||||
remove: false,
|
||||
remains: leftAtom,
|
||||
},
|
||||
"expired": {
|
||||
allow: &types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtTime(now),
|
||||
},
|
||||
valid: true,
|
||||
fee: smallAtom,
|
||||
blockTime: oneHour,
|
||||
accept: false,
|
||||
remove: true,
|
||||
},
|
||||
"fee more than allowed": {
|
||||
allow: &types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtTime(oneHour),
|
||||
},
|
||||
valid: true,
|
||||
fee: bigAtom,
|
||||
blockTime: now,
|
||||
accept: false,
|
||||
},
|
||||
"without spend limit": {
|
||||
allow: &types.BasicAllowance{
|
||||
Expiration: types.ExpiresAtTime(oneHour),
|
||||
},
|
||||
valid: true,
|
||||
fee: bigAtom,
|
||||
blockTime: now,
|
||||
accept: true,
|
||||
},
|
||||
"expired no spend limit": {
|
||||
allow: &types.BasicAllowance{
|
||||
Expiration: types.ExpiresAtTime(now),
|
||||
},
|
||||
valid: true,
|
||||
fee: bigAtom,
|
||||
blockTime: oneHour,
|
||||
accept: false,
|
||||
},
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
ctx := app.BaseApp.NewContext(false, tmproto.Header{}).WithBlockTime(tc.blockTime)
|
||||
|
||||
// now try to deduct
|
||||
remove, err := tc.allow.Accept(ctx, tc.fee, []sdk.Msg{})
|
||||
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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,7 +3,6 @@ package types
|
||||
import (
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
auth "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||
// supply "github.com/cosmos/cosmos-sdk/x/supply/exported"
|
||||
)
|
||||
|
||||
// AccountKeeper defines the expected auth Account Keeper (noalias)
|
||||
|
||||
@ -1,138 +0,0 @@
|
||||
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{
|
||||
Sum: &ExpiresAt_Time{
|
||||
Time: &t,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ExpiresAtHeight creates an expiration at the given height
|
||||
func ExpiresAtHeight(h int64) ExpiresAt {
|
||||
return ExpiresAt{
|
||||
&ExpiresAt_Height{
|
||||
Height: h,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateBasic performs basic sanity checks.
|
||||
// Note that empty expiration is allowed
|
||||
func (e ExpiresAt) ValidateBasic() error {
|
||||
if e.HasDefinedTime() && e.GetHeight() != 0 {
|
||||
return sdkerrors.Wrap(ErrInvalidDuration, "both time and height are set")
|
||||
}
|
||||
if e.GetHeight() < 0 {
|
||||
return sdkerrors.Wrap(ErrInvalidDuration, "negative height")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Undefined returns true for an uninitialized struct
|
||||
func (e ExpiresAt) Undefined() bool {
|
||||
return (e.GetTime() == nil || e.GetTime().Unix() <= 0) && e.GetHeight() == 0
|
||||
}
|
||||
|
||||
// HasDefinedTime returns true if `ExpiresAt` has valid time
|
||||
func (e ExpiresAt) HasDefinedTime() bool {
|
||||
t := e.GetTime()
|
||||
return t != nil && t.Unix() > 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.HasDefinedTime() {
|
||||
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.HasDefinedTime() && t.After(*e.GetTime()) {
|
||||
return true
|
||||
}
|
||||
|
||||
return e.GetHeight() != 0 && h >= e.GetHeight()
|
||||
}
|
||||
|
||||
// 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.HasDefinedTime() {
|
||||
return d.GetDuration() != nil && d.GetDuration().Seconds() > float64(0)
|
||||
}
|
||||
return d.GetBlocks() > 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.HasDefinedTime() {
|
||||
return ExpiresAtTime(e.GetTime().Add(*d.GetDuration())), nil
|
||||
}
|
||||
return ExpiresAtHeight(e.GetHeight() + int64(d.GetBlocks())), 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.GetHeight() != 0 {
|
||||
return ExpiresAtHeight(e.GetHeight() - dumpHeight)
|
||||
}
|
||||
return ExpiresAt{}
|
||||
}
|
||||
|
||||
// ClockDuration creates an Duration by clock time
|
||||
func ClockDuration(d time.Duration) Duration {
|
||||
return Duration{Sum: &Duration_Duration{
|
||||
Duration: &d,
|
||||
}}
|
||||
}
|
||||
|
||||
// BlockDuration creates an Duration by block height
|
||||
func BlockDuration(h uint64) Duration {
|
||||
return Duration{Sum: &Duration_Blocks{
|
||||
Blocks: 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.GetBlocks() == 0 && d.GetDuration() == nil {
|
||||
return sdkerrors.Wrap(ErrInvalidDuration, "neither time and height are set")
|
||||
}
|
||||
if d.GetBlocks() != 0 && d.GetDuration() != nil && d.GetDuration().Seconds() != float64(0) {
|
||||
return sdkerrors.Wrap(ErrInvalidDuration, "both time and height are set")
|
||||
}
|
||||
if d.GetDuration() != nil && d.GetDuration().Seconds() < 0 {
|
||||
return sdkerrors.Wrap(ErrInvalidDuration, "negative clock step")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -1,153 +0,0 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
|
||||
)
|
||||
|
||||
func TestExpiresAt(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
cases := map[string]struct {
|
||||
expires types.ExpiresAt
|
||||
zero bool
|
||||
before types.ExpiresAt
|
||||
after types.ExpiresAt
|
||||
}{
|
||||
"basic": {
|
||||
expires: types.ExpiresAtHeight(100),
|
||||
before: types.ExpiresAtHeight(50),
|
||||
after: types.ExpiresAtHeight(122),
|
||||
},
|
||||
"zero": {
|
||||
expires: types.ExpiresAt{},
|
||||
zero: true,
|
||||
before: types.ExpiresAtHeight(1),
|
||||
},
|
||||
"match height": {
|
||||
expires: types.ExpiresAtHeight(1000),
|
||||
before: types.ExpiresAtHeight(999),
|
||||
after: types.ExpiresAtHeight(1000),
|
||||
},
|
||||
"match time": {
|
||||
expires: types.ExpiresAtTime(now),
|
||||
before: types.ExpiresAtTime(now.Add(-1 * time.Second)),
|
||||
after: types.ExpiresAtTime(now.Add(1 * time.Second)),
|
||||
},
|
||||
}
|
||||
|
||||
for name, stc := range cases {
|
||||
tc := stc // to make scopelint happy
|
||||
t.Run(name, func(t *testing.T) {
|
||||
err := tc.expires.ValidateBasic()
|
||||
assert.Equal(t, tc.zero, tc.expires.Undefined())
|
||||
require.NoError(t, err)
|
||||
|
||||
if !tc.before.Undefined() {
|
||||
assert.Equal(t, false, tc.expires.IsExpired(tc.before.GetTime(), tc.before.GetHeight()))
|
||||
}
|
||||
if !tc.after.Undefined() {
|
||||
assert.Equal(t, true, tc.expires.IsExpired(tc.after.GetTime(), tc.after.GetHeight()))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDurationValid(t *testing.T) {
|
||||
now := time.Now()
|
||||
|
||||
cases := map[string]struct {
|
||||
period types.Duration
|
||||
valid bool
|
||||
compatible types.ExpiresAt
|
||||
incompatible types.ExpiresAt
|
||||
}{
|
||||
"basic height": {
|
||||
period: types.BlockDuration(100),
|
||||
valid: true,
|
||||
compatible: types.ExpiresAtHeight(50),
|
||||
incompatible: types.ExpiresAtTime(now),
|
||||
},
|
||||
"basic time": {
|
||||
period: types.ClockDuration(time.Hour),
|
||||
valid: true,
|
||||
compatible: types.ExpiresAtTime(now),
|
||||
incompatible: types.ExpiresAtHeight(50),
|
||||
},
|
||||
"zero": {
|
||||
period: types.Duration{},
|
||||
valid: false,
|
||||
},
|
||||
"negative clock": {
|
||||
period: types.ClockDuration(-1 * 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()
|
||||
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 types.ExpiresAt
|
||||
period types.Duration
|
||||
valid bool
|
||||
result types.ExpiresAt
|
||||
}{
|
||||
"add height": {
|
||||
expires: types.ExpiresAtHeight(789),
|
||||
period: types.BlockDuration(100),
|
||||
valid: true,
|
||||
result: types.ExpiresAtHeight(889),
|
||||
},
|
||||
"add time": {
|
||||
expires: types.ExpiresAtTime(now),
|
||||
period: types.ClockDuration(time.Hour),
|
||||
valid: true,
|
||||
result: types.ExpiresAtTime(now.Add(time.Hour)),
|
||||
},
|
||||
"mismatch": {
|
||||
expires: types.ExpiresAtHeight(789),
|
||||
period: types.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)
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -40,7 +40,7 @@ type BasicAllowance struct {
|
||||
// empty, there is no spend limit and any amount of coins can be spent.
|
||||
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"`
|
||||
// expiration specifies an optional time when this allowance expires
|
||||
Expiration ExpiresAt `protobuf:"bytes,2,opt,name=expiration,proto3" json:"expiration"`
|
||||
Expiration *time.Time `protobuf:"bytes,2,opt,name=expiration,proto3,stdtime" json:"expiration,omitempty"`
|
||||
}
|
||||
|
||||
func (m *BasicAllowance) Reset() { *m = BasicAllowance{} }
|
||||
@ -83,11 +83,11 @@ func (m *BasicAllowance) GetSpendLimit() github_com_cosmos_cosmos_sdk_types.Coin
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *BasicAllowance) GetExpiration() ExpiresAt {
|
||||
func (m *BasicAllowance) GetExpiration() *time.Time {
|
||||
if m != nil {
|
||||
return m.Expiration
|
||||
}
|
||||
return ExpiresAt{}
|
||||
return nil
|
||||
}
|
||||
|
||||
// PeriodicAllowance extends Allowance to allow for both a maximum cap,
|
||||
@ -97,7 +97,7 @@ type PeriodicAllowance struct {
|
||||
Basic BasicAllowance `protobuf:"bytes,1,opt,name=basic,proto3" json:"basic"`
|
||||
// period specifies the time duration in which period_spend_limit coins can
|
||||
// be spent before that allowance is reset
|
||||
Period Duration `protobuf:"bytes,2,opt,name=period,proto3" json:"period"`
|
||||
Period time.Duration `protobuf:"bytes,2,opt,name=period,proto3,stdduration" json:"period"`
|
||||
// period_spend_limit specifies the maximum number of coins that can be spent
|
||||
// in the 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"`
|
||||
@ -106,7 +106,7 @@ type PeriodicAllowance struct {
|
||||
// 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
|
||||
PeriodReset ExpiresAt `protobuf:"bytes,5,opt,name=period_reset,json=periodReset,proto3" json:"period_reset"`
|
||||
PeriodReset time.Time `protobuf:"bytes,5,opt,name=period_reset,json=periodReset,proto3,stdtime" json:"period_reset"`
|
||||
}
|
||||
|
||||
func (m *PeriodicAllowance) Reset() { *m = PeriodicAllowance{} }
|
||||
@ -149,11 +149,11 @@ func (m *PeriodicAllowance) GetBasic() BasicAllowance {
|
||||
return BasicAllowance{}
|
||||
}
|
||||
|
||||
func (m *PeriodicAllowance) GetPeriod() Duration {
|
||||
func (m *PeriodicAllowance) GetPeriod() time.Duration {
|
||||
if m != nil {
|
||||
return m.Period
|
||||
}
|
||||
return Duration{}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (m *PeriodicAllowance) GetPeriodSpendLimit() github_com_cosmos_cosmos_sdk_types.Coins {
|
||||
@ -170,11 +170,11 @@ func (m *PeriodicAllowance) GetPeriodCanSpend() github_com_cosmos_cosmos_sdk_typ
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *PeriodicAllowance) GetPeriodReset() ExpiresAt {
|
||||
func (m *PeriodicAllowance) GetPeriodReset() time.Time {
|
||||
if m != nil {
|
||||
return m.PeriodReset
|
||||
}
|
||||
return ExpiresAt{}
|
||||
return time.Time{}
|
||||
}
|
||||
|
||||
// AllowedMsgAllowance creates allowance only for specified message types.
|
||||
@ -218,184 +218,6 @@ func (m *AllowedMsgAllowance) XXX_DiscardUnknown() {
|
||||
|
||||
var xxx_messageInfo_AllowedMsgAllowance proto.InternalMessageInfo
|
||||
|
||||
// Duration is a span of a clock time or number of blocks.
|
||||
// This is designed to be added to an ExpiresAt struct.
|
||||
type Duration struct {
|
||||
// sum is the oneof that represents either duration or block
|
||||
//
|
||||
// Types that are valid to be assigned to Sum:
|
||||
// *Duration_Duration
|
||||
// *Duration_Blocks
|
||||
Sum isDuration_Sum `protobuf_oneof:"sum"`
|
||||
}
|
||||
|
||||
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_7279582900c30aea, []int{3}
|
||||
}
|
||||
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
|
||||
|
||||
type isDuration_Sum interface {
|
||||
isDuration_Sum()
|
||||
MarshalTo([]byte) (int, error)
|
||||
Size() int
|
||||
}
|
||||
|
||||
type Duration_Duration struct {
|
||||
Duration *time.Duration `protobuf:"bytes,1,opt,name=duration,proto3,oneof,stdduration" json:"duration,omitempty"`
|
||||
}
|
||||
type Duration_Blocks struct {
|
||||
Blocks uint64 `protobuf:"varint,2,opt,name=blocks,proto3,oneof" json:"blocks,omitempty"`
|
||||
}
|
||||
|
||||
func (*Duration_Duration) isDuration_Sum() {}
|
||||
func (*Duration_Blocks) isDuration_Sum() {}
|
||||
|
||||
func (m *Duration) GetSum() isDuration_Sum {
|
||||
if m != nil {
|
||||
return m.Sum
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Duration) GetDuration() *time.Duration {
|
||||
if x, ok := m.GetSum().(*Duration_Duration); ok {
|
||||
return x.Duration
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Duration) GetBlocks() uint64 {
|
||||
if x, ok := m.GetSum().(*Duration_Blocks); ok {
|
||||
return x.Blocks
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// XXX_OneofWrappers is for the internal use of the proto package.
|
||||
func (*Duration) XXX_OneofWrappers() []interface{} {
|
||||
return []interface{}{
|
||||
(*Duration_Duration)(nil),
|
||||
(*Duration_Blocks)(nil),
|
||||
}
|
||||
}
|
||||
|
||||
// ExpiresAt is a point in time where something expires.
|
||||
// It may be *either* block time or block height
|
||||
type ExpiresAt struct {
|
||||
// sum is the oneof that represents either time or height
|
||||
//
|
||||
// Types that are valid to be assigned to Sum:
|
||||
// *ExpiresAt_Time
|
||||
// *ExpiresAt_Height
|
||||
Sum isExpiresAt_Sum `protobuf_oneof:"sum"`
|
||||
}
|
||||
|
||||
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_7279582900c30aea, []int{4}
|
||||
}
|
||||
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
|
||||
|
||||
type isExpiresAt_Sum interface {
|
||||
isExpiresAt_Sum()
|
||||
MarshalTo([]byte) (int, error)
|
||||
Size() int
|
||||
}
|
||||
|
||||
type ExpiresAt_Time struct {
|
||||
Time *time.Time `protobuf:"bytes,1,opt,name=time,proto3,oneof,stdtime" json:"time,omitempty"`
|
||||
}
|
||||
type ExpiresAt_Height struct {
|
||||
Height int64 `protobuf:"varint,2,opt,name=height,proto3,oneof" json:"height,omitempty"`
|
||||
}
|
||||
|
||||
func (*ExpiresAt_Time) isExpiresAt_Sum() {}
|
||||
func (*ExpiresAt_Height) isExpiresAt_Sum() {}
|
||||
|
||||
func (m *ExpiresAt) GetSum() isExpiresAt_Sum {
|
||||
if m != nil {
|
||||
return m.Sum
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ExpiresAt) GetTime() *time.Time {
|
||||
if x, ok := m.GetSum().(*ExpiresAt_Time); ok {
|
||||
return x.Time
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *ExpiresAt) GetHeight() int64 {
|
||||
if x, ok := m.GetSum().(*ExpiresAt_Height); ok {
|
||||
return x.Height
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// XXX_OneofWrappers is for the internal use of the proto package.
|
||||
func (*ExpiresAt) XXX_OneofWrappers() []interface{} {
|
||||
return []interface{}{
|
||||
(*ExpiresAt_Time)(nil),
|
||||
(*ExpiresAt_Height)(nil),
|
||||
}
|
||||
}
|
||||
|
||||
// Grant is stored in the KVStore to record a grant with full context
|
||||
type Grant struct {
|
||||
// granter is the address of the user granting an allowance of their funds.
|
||||
@ -410,7 +232,7 @@ func (m *Grant) Reset() { *m = Grant{} }
|
||||
func (m *Grant) String() string { return proto.CompactTextString(m) }
|
||||
func (*Grant) ProtoMessage() {}
|
||||
func (*Grant) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_7279582900c30aea, []int{5}
|
||||
return fileDescriptor_7279582900c30aea, []int{3}
|
||||
}
|
||||
func (m *Grant) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -464,8 +286,6 @@ func init() {
|
||||
proto.RegisterType((*BasicAllowance)(nil), "cosmos.feegrant.v1beta1.BasicAllowance")
|
||||
proto.RegisterType((*PeriodicAllowance)(nil), "cosmos.feegrant.v1beta1.PeriodicAllowance")
|
||||
proto.RegisterType((*AllowedMsgAllowance)(nil), "cosmos.feegrant.v1beta1.AllowedMsgAllowance")
|
||||
proto.RegisterType((*Duration)(nil), "cosmos.feegrant.v1beta1.Duration")
|
||||
proto.RegisterType((*ExpiresAt)(nil), "cosmos.feegrant.v1beta1.ExpiresAt")
|
||||
proto.RegisterType((*Grant)(nil), "cosmos.feegrant.v1beta1.Grant")
|
||||
}
|
||||
|
||||
@ -474,48 +294,43 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_7279582900c30aea = []byte{
|
||||
// 645 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0xbf, 0x6f, 0xd3, 0x40,
|
||||
0x14, 0xb6, 0xeb, 0xa4, 0x34, 0x17, 0x28, 0xed, 0x51, 0x84, 0xdb, 0xc1, 0x29, 0x19, 0x20, 0x0c,
|
||||
0xb5, 0x69, 0x91, 0x18, 0x2a, 0x21, 0x54, 0x97, 0xd2, 0x22, 0xa8, 0x84, 0x0c, 0x13, 0x4b, 0x74,
|
||||
0xb6, 0xaf, 0xae, 0xa9, 0xed, 0xb3, 0x7c, 0x17, 0x68, 0x56, 0x26, 0xc6, 0x8e, 0x4c, 0x88, 0x99,
|
||||
0x99, 0x3f, 0xa2, 0x62, 0xaa, 0x98, 0x90, 0x90, 0x28, 0x6a, 0x46, 0xfe, 0x09, 0xe4, 0xbb, 0xb3,
|
||||
0x1d, 0x12, 0x82, 0x04, 0xea, 0x64, 0xbf, 0x7b, 0xef, 0xfb, 0xbe, 0xf7, 0xeb, 0x0e, 0xdc, 0xf0,
|
||||
0x08, 0x8d, 0x09, 0xb5, 0xf6, 0x30, 0x0e, 0x32, 0x94, 0x30, 0xeb, 0xd5, 0xaa, 0x8b, 0x19, 0x5a,
|
||||
0x2d, 0x0f, 0xcc, 0x34, 0x23, 0x8c, 0xc0, 0x6b, 0x22, 0xce, 0x2c, 0x8f, 0x65, 0xdc, 0xd2, 0x42,
|
||||
0x40, 0x02, 0xc2, 0x63, 0xac, 0xfc, 0x4f, 0x84, 0x2f, 0x2d, 0x06, 0x84, 0x04, 0x11, 0xb6, 0xb8,
|
||||
0xe5, 0xf6, 0xf6, 0x2c, 0x94, 0xf4, 0x0b, 0x97, 0x60, 0xea, 0x0a, 0x8c, 0xa4, 0x15, 0x2e, 0x43,
|
||||
0x26, 0xe3, 0x22, 0x8a, 0xcb, 0x44, 0x3c, 0x12, 0x26, 0xd2, 0xdf, 0x1a, 0x65, 0x65, 0x61, 0x8c,
|
||||
0x29, 0x43, 0x71, 0x5a, 0x10, 0x8c, 0x06, 0xf8, 0xbd, 0x0c, 0xb1, 0x90, 0x48, 0x82, 0xf6, 0x37,
|
||||
0x15, 0xcc, 0xda, 0x88, 0x86, 0xde, 0x46, 0x14, 0x91, 0xd7, 0x28, 0xf1, 0x30, 0x8c, 0x40, 0x93,
|
||||
0xa6, 0x38, 0xf1, 0xbb, 0x51, 0x18, 0x87, 0x4c, 0x57, 0x97, 0xb5, 0x4e, 0x73, 0x6d, 0xd1, 0x94,
|
||||
0x79, 0xe5, 0x99, 0x14, 0xa5, 0x9a, 0x9b, 0x24, 0x4c, 0xec, 0xdb, 0xc7, 0xdf, 0x5b, 0xca, 0xc7,
|
||||
0xd3, 0x56, 0x27, 0x08, 0xd9, 0x7e, 0xcf, 0x35, 0x3d, 0x12, 0xcb, 0x22, 0xe4, 0x67, 0x85, 0xfa,
|
||||
0x07, 0x16, 0xeb, 0xa7, 0x98, 0x72, 0x00, 0x75, 0x00, 0xe7, 0x7f, 0x92, 0xd3, 0xc3, 0x1d, 0x00,
|
||||
0xf0, 0x61, 0x1a, 0x8a, 0xa4, 0xf4, 0xa9, 0x65, 0xb5, 0xd3, 0x5c, 0x6b, 0x9b, 0x13, 0x7a, 0x6b,
|
||||
0x6e, 0xe5, 0xa1, 0x98, 0x6e, 0x30, 0xbb, 0x96, 0xab, 0x3a, 0x43, 0xd8, 0xf5, 0xf9, 0x2f, 0x9f,
|
||||
0x56, 0x2e, 0x3d, 0xc4, 0xb8, 0xac, 0xe4, 0x51, 0xfb, 0xa7, 0x06, 0xe6, 0x9f, 0xe2, 0x2c, 0x24,
|
||||
0xfe, 0x70, 0x81, 0x9b, 0xa0, 0xee, 0xe6, 0x25, 0xeb, 0x2a, 0x57, 0xbb, 0x39, 0x51, 0xed, 0xf7,
|
||||
0xc6, 0x48, 0x49, 0x81, 0x85, 0xf7, 0xc1, 0x74, 0xca, 0x99, 0x65, 0xce, 0xd7, 0x27, 0xb2, 0x3c,
|
||||
0x90, 0x1d, 0x97, 0x78, 0x09, 0x83, 0x7d, 0x00, 0xc5, 0x5f, 0x77, 0xb8, 0xdb, 0xda, 0xf9, 0x77,
|
||||
0x7b, 0x4e, 0xc8, 0x3c, 0xab, 0x7a, 0xde, 0x03, 0xf2, 0xac, 0xeb, 0xa1, 0x44, 0xc8, 0xeb, 0xb5,
|
||||
0xf3, 0x17, 0x9e, 0x15, 0x22, 0x9b, 0x28, 0xe1, 0xda, 0xf0, 0x31, 0xb8, 0x28, 0x65, 0x33, 0x4c,
|
||||
0x31, 0xd3, 0xeb, 0xff, 0x38, 0xec, 0xa6, 0x40, 0x3b, 0x39, 0xf8, 0x4f, 0xd3, 0x7e, 0xaf, 0x82,
|
||||
0x2b, 0xdc, 0xc4, 0xfe, 0x2e, 0x0d, 0xaa, 0x79, 0x6f, 0x81, 0x06, 0x2a, 0x0c, 0x39, 0xf3, 0x05,
|
||||
0x53, 0xdc, 0x0b, 0xb3, 0xb8, 0x17, 0xe6, 0x46, 0xd2, 0xb7, 0xe7, 0x3f, 0x8f, 0x72, 0x3a, 0x15,
|
||||
0x12, 0xde, 0x02, 0x73, 0x48, 0xb0, 0x77, 0x63, 0x4c, 0x29, 0x0a, 0x30, 0xd5, 0xa7, 0x96, 0xb5,
|
||||
0x4e, 0xc3, 0xb9, 0x2c, 0xcf, 0x77, 0xe5, 0xf1, 0xfa, 0xd5, 0xb7, 0x1f, 0x5a, 0xca, 0x78, 0x82,
|
||||
0x2f, 0xc1, 0x4c, 0xb1, 0x0c, 0xf0, 0x1e, 0x98, 0x29, 0xae, 0xa2, 0xcc, 0x69, 0x71, 0x2c, 0xa7,
|
||||
0x6a, 0x73, 0xde, 0x9d, 0xb6, 0xd4, 0x1d, 0xc5, 0x29, 0x21, 0x50, 0x07, 0xd3, 0x6e, 0x44, 0xbc,
|
||||
0x03, 0xca, 0xd7, 0xaf, 0xb6, 0xa3, 0x38, 0xd2, 0xb6, 0xeb, 0x40, 0xa3, 0xbd, 0xb8, 0xed, 0x83,
|
||||
0x46, 0xd9, 0x3f, 0x78, 0x17, 0xd4, 0xf2, 0x87, 0x41, 0x0a, 0x2d, 0x8d, 0x09, 0x3d, 0x2f, 0x5e,
|
||||
0x0d, 0xbb, 0x76, 0x24, 0x94, 0x78, 0x7c, 0xae, 0xb2, 0x8f, 0xc3, 0x60, 0x9f, 0x71, 0x15, 0x2d,
|
||||
0x57, 0x11, 0x76, 0xa1, 0xf2, 0x46, 0x05, 0xf5, 0xed, 0x7c, 0x68, 0x50, 0x07, 0x17, 0xf8, 0xf4,
|
||||
0x70, 0xc6, 0x55, 0x1a, 0x4e, 0x61, 0x56, 0x1e, 0xcc, 0x59, 0x4a, 0xcf, 0xc8, 0x60, 0xb4, 0xff,
|
||||
0x1d, 0x8c, 0xbd, 0x7d, 0x7c, 0x66, 0xa8, 0x27, 0x67, 0x86, 0xfa, 0xe3, 0xcc, 0x50, 0x8f, 0x06,
|
||||
0x86, 0x72, 0x32, 0x30, 0x94, 0xaf, 0x03, 0x43, 0x79, 0xb1, 0xf2, 0xd7, 0x5d, 0x3d, 0xac, 0xde,
|
||||
0x78, 0xbe, 0xb6, 0xee, 0x34, 0x17, 0xbd, 0xf3, 0x2b, 0x00, 0x00, 0xff, 0xff, 0x5a, 0x4c, 0xb0,
|
||||
0x35, 0x03, 0x06, 0x00, 0x00,
|
||||
// 564 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3f, 0x6f, 0xd3, 0x40,
|
||||
0x14, 0x8f, 0x9b, 0xa4, 0x90, 0x0b, 0x94, 0xc6, 0x14, 0xe1, 0x64, 0x70, 0xa2, 0x0e, 0x10, 0x86,
|
||||
0x9c, 0x69, 0xd9, 0xca, 0x42, 0x1d, 0x20, 0x42, 0xa2, 0x12, 0x32, 0x4c, 0x2c, 0xd1, 0xd9, 0x79,
|
||||
0x35, 0x27, 0x62, 0x9f, 0xe5, 0xbb, 0x40, 0xb3, 0x32, 0x31, 0x76, 0x64, 0x42, 0xcc, 0xcc, 0x7c,
|
||||
0x88, 0x8a, 0xa9, 0x82, 0x85, 0x89, 0xa2, 0xe4, 0x8b, 0x20, 0xdf, 0x9d, 0x93, 0x90, 0xf0, 0x47,
|
||||
0x42, 0x9d, 0xe2, 0xbb, 0xf7, 0x7e, 0xff, 0xde, 0x3b, 0x05, 0xdd, 0x08, 0x18, 0x8f, 0x18, 0x77,
|
||||
0x0e, 0x01, 0xc2, 0x94, 0xc4, 0xc2, 0x79, 0xb5, 0xe3, 0x83, 0x20, 0x3b, 0xb3, 0x0b, 0x9c, 0xa4,
|
||||
0x4c, 0x30, 0xf3, 0xba, 0xea, 0xc3, 0xb3, 0x6b, 0xdd, 0xd7, 0xd8, 0x0a, 0x59, 0xc8, 0x64, 0x8f,
|
||||
0x93, 0x7d, 0xa9, 0xf6, 0x46, 0x3d, 0x64, 0x2c, 0x1c, 0x82, 0x23, 0x4f, 0xfe, 0xe8, 0xd0, 0x21,
|
||||
0xf1, 0x38, 0x2f, 0x29, 0xa6, 0xbe, 0xc2, 0x68, 0x5a, 0x55, 0xb2, 0xb5, 0x19, 0x9f, 0x70, 0x98,
|
||||
0x19, 0x09, 0x18, 0x8d, 0x75, 0xbd, 0xb9, 0xcc, 0x2a, 0x68, 0x04, 0x5c, 0x90, 0x28, 0xc9, 0x09,
|
||||
0x96, 0x1b, 0x06, 0xa3, 0x94, 0x08, 0xca, 0x34, 0xc1, 0xf6, 0x57, 0x03, 0x6d, 0xb8, 0x84, 0xd3,
|
||||
0x60, 0x7f, 0x38, 0x64, 0xaf, 0x49, 0x1c, 0x80, 0x39, 0x44, 0x55, 0x9e, 0x40, 0x3c, 0xe8, 0x0f,
|
||||
0x69, 0x44, 0x85, 0x65, 0xb4, 0x8a, 0xed, 0xea, 0x6e, 0x1d, 0x6b, 0x5f, 0x99, 0x93, 0x3c, 0x2a,
|
||||
0xee, 0x32, 0x1a, 0xbb, 0xb7, 0x4f, 0xbe, 0x37, 0x0b, 0x1f, 0xcf, 0x9a, 0xed, 0x90, 0x8a, 0x17,
|
||||
0x23, 0x1f, 0x07, 0x2c, 0xd2, 0x21, 0xf4, 0x4f, 0x87, 0x0f, 0x5e, 0x3a, 0x62, 0x9c, 0x00, 0x97,
|
||||
0x00, 0xee, 0x21, 0xc9, 0xff, 0x38, 0xa3, 0x37, 0xef, 0x21, 0x04, 0x47, 0x09, 0x55, 0xa6, 0xac,
|
||||
0xb5, 0x96, 0xd1, 0xae, 0xee, 0x36, 0xb0, 0x72, 0x8d, 0x73, 0xd7, 0xf8, 0x59, 0x1e, 0xcb, 0x2d,
|
||||
0x1d, 0x9f, 0x35, 0x0d, 0x6f, 0x01, 0xb3, 0x57, 0xfb, 0xf2, 0xa9, 0x73, 0xf9, 0x21, 0xc0, 0x2c,
|
||||
0xc1, 0xa3, 0xed, 0x69, 0x11, 0xd5, 0x9e, 0x40, 0x4a, 0xd9, 0x60, 0x31, 0x58, 0x17, 0x95, 0xfd,
|
||||
0x2c, 0xaa, 0x65, 0x48, 0x95, 0x9b, 0xf8, 0x0f, 0x1b, 0xc4, 0xbf, 0x0e, 0xc4, 0x2d, 0x65, 0x01,
|
||||
0x3d, 0x85, 0x35, 0xef, 0xa2, 0xf5, 0x44, 0x32, 0x6b, 0xaf, 0xf5, 0x15, 0xaf, 0xf7, 0xf5, 0x84,
|
||||
0xdd, 0x8b, 0x19, 0xee, 0x5d, 0x66, 0x57, 0x43, 0xcc, 0x31, 0x32, 0xd5, 0x57, 0x7f, 0x71, 0xc2,
|
||||
0xc5, 0xf3, 0x9f, 0xf0, 0xa6, 0x92, 0x79, 0x3a, 0x9f, 0xf3, 0x08, 0xe9, 0xbb, 0x7e, 0x40, 0x62,
|
||||
0x25, 0x6f, 0x95, 0xce, 0x5f, 0x78, 0x43, 0x89, 0x74, 0x49, 0x2c, 0xb5, 0xcd, 0x1e, 0xba, 0xa4,
|
||||
0x65, 0x53, 0xe0, 0x20, 0xac, 0xf2, 0x3f, 0x17, 0x2c, 0xa7, 0x26, 0x97, 0x5c, 0x55, 0x48, 0x2f,
|
||||
0x03, 0xfe, 0x6e, 0xcb, 0xef, 0x0d, 0x74, 0x55, 0x1e, 0x61, 0x70, 0xc0, 0xc3, 0xf9, 0x9e, 0x1f,
|
||||
0xa0, 0x0a, 0xc9, 0x0f, 0x7a, 0xd7, 0x5b, 0x2b, 0x82, 0xfb, 0xf1, 0xd8, 0xad, 0x7d, 0x5e, 0xe6,
|
||||
0xf4, 0xe6, 0x48, 0xf3, 0x16, 0xda, 0x24, 0x8a, 0xbd, 0x1f, 0x01, 0xe7, 0x24, 0x04, 0x6e, 0xad,
|
||||
0xb5, 0x8a, 0xed, 0x8a, 0x77, 0x45, 0xdf, 0x1f, 0xe8, 0xeb, 0xbd, 0x6b, 0x6f, 0x3f, 0x34, 0x0b,
|
||||
0xab, 0x06, 0xdf, 0x18, 0xa8, 0xdc, 0xcb, 0x5e, 0x96, 0x69, 0xa1, 0x0b, 0xf2, 0x89, 0x41, 0x2a,
|
||||
0x0d, 0x55, 0xbc, 0xfc, 0x38, 0xaf, 0x80, 0x7c, 0x50, 0xb3, 0xca, 0x52, 0x8c, 0xe2, 0xff, 0xc6,
|
||||
0x70, 0x7b, 0x27, 0x13, 0xdb, 0x38, 0x9d, 0xd8, 0xc6, 0x8f, 0x89, 0x6d, 0x1c, 0x4f, 0xed, 0xc2,
|
||||
0xe9, 0xd4, 0x2e, 0x7c, 0x9b, 0xda, 0x85, 0xe7, 0x9d, 0xbf, 0x6e, 0xf5, 0x68, 0xfe, 0x0f, 0x28,
|
||||
0x17, 0xec, 0xaf, 0x4b, 0xd1, 0x3b, 0x3f, 0x03, 0x00, 0x00, 0xff, 0xff, 0x34, 0xef, 0x7e, 0x35,
|
||||
0x21, 0x05, 0x00, 0x00,
|
||||
}
|
||||
|
||||
func (m *BasicAllowance) Marshal() (dAtA []byte, err error) {
|
||||
@ -538,16 +353,16 @@ func (m *BasicAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
{
|
||||
size, err := m.Expiration.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
if m.Expiration != nil {
|
||||
n1, err1 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Expiration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expiration):])
|
||||
if err1 != nil {
|
||||
return 0, err1
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintFeegrant(dAtA, i, uint64(size))
|
||||
i -= n1
|
||||
i = encodeVarintFeegrant(dAtA, i, uint64(n1))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
if len(m.SpendLimit) > 0 {
|
||||
for iNdEx := len(m.SpendLimit) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
@ -585,14 +400,12 @@ func (m *PeriodicAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
{
|
||||
size, err := m.PeriodReset.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintFeegrant(dAtA, i, uint64(size))
|
||||
n2, err2 := github_com_gogo_protobuf_types.StdTimeMarshalTo(m.PeriodReset, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(m.PeriodReset):])
|
||||
if err2 != nil {
|
||||
return 0, err2
|
||||
}
|
||||
i -= n2
|
||||
i = encodeVarintFeegrant(dAtA, i, uint64(n2))
|
||||
i--
|
||||
dAtA[i] = 0x2a
|
||||
if len(m.PeriodCanSpend) > 0 {
|
||||
@ -623,14 +436,12 @@ func (m *PeriodicAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
dAtA[i] = 0x1a
|
||||
}
|
||||
}
|
||||
{
|
||||
size, err := m.Period.MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintFeegrant(dAtA, i, uint64(size))
|
||||
n3, err3 := github_com_gogo_protobuf_types.StdDurationMarshalTo(m.Period, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(m.Period):])
|
||||
if err3 != nil {
|
||||
return 0, err3
|
||||
}
|
||||
i -= n3
|
||||
i = encodeVarintFeegrant(dAtA, i, uint64(n3))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
{
|
||||
@ -690,132 +501,6 @@ func (m *AllowedMsgAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
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.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 *Duration_Duration) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *Duration_Duration) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
if m.Duration != nil {
|
||||
n6, err6 := github_com_gogo_protobuf_types.StdDurationMarshalTo(*m.Duration, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Duration):])
|
||||
if err6 != nil {
|
||||
return 0, err6
|
||||
}
|
||||
i -= n6
|
||||
i = encodeVarintFeegrant(dAtA, i, uint64(n6))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
func (m *Duration_Blocks) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *Duration_Blocks) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
i = encodeVarintFeegrant(dAtA, i, uint64(m.Blocks))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
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.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 *ExpiresAt_Time) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *ExpiresAt_Time) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
if m.Time != nil {
|
||||
n7, err7 := github_com_gogo_protobuf_types.StdTimeMarshalTo(*m.Time, dAtA[i-github_com_gogo_protobuf_types.SizeOfStdTime(*m.Time):])
|
||||
if err7 != nil {
|
||||
return 0, err7
|
||||
}
|
||||
i -= n7
|
||||
i = encodeVarintFeegrant(dAtA, i, uint64(n7))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
}
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
func (m *ExpiresAt_Height) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *ExpiresAt_Height) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
i = encodeVarintFeegrant(dAtA, i, uint64(m.Height))
|
||||
i--
|
||||
dAtA[i] = 0x10
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
func (m *Grant) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
@ -888,8 +573,10 @@ func (m *BasicAllowance) Size() (n int) {
|
||||
n += 1 + l + sovFeegrant(uint64(l))
|
||||
}
|
||||
}
|
||||
l = m.Expiration.Size()
|
||||
n += 1 + l + sovFeegrant(uint64(l))
|
||||
if m.Expiration != nil {
|
||||
l = github_com_gogo_protobuf_types.SizeOfStdTime(*m.Expiration)
|
||||
n += 1 + l + sovFeegrant(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@ -901,7 +588,7 @@ func (m *PeriodicAllowance) Size() (n int) {
|
||||
_ = l
|
||||
l = m.Basic.Size()
|
||||
n += 1 + l + sovFeegrant(uint64(l))
|
||||
l = m.Period.Size()
|
||||
l = github_com_gogo_protobuf_types.SizeOfStdDuration(m.Period)
|
||||
n += 1 + l + sovFeegrant(uint64(l))
|
||||
if len(m.PeriodSpendLimit) > 0 {
|
||||
for _, e := range m.PeriodSpendLimit {
|
||||
@ -915,7 +602,7 @@ func (m *PeriodicAllowance) Size() (n int) {
|
||||
n += 1 + l + sovFeegrant(uint64(l))
|
||||
}
|
||||
}
|
||||
l = m.PeriodReset.Size()
|
||||
l = github_com_gogo_protobuf_types.SizeOfStdTime(m.PeriodReset)
|
||||
n += 1 + l + sovFeegrant(uint64(l))
|
||||
return n
|
||||
}
|
||||
@ -939,72 +626,6 @@ func (m *AllowedMsgAllowance) Size() (n int) {
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *Duration) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.Sum != nil {
|
||||
n += m.Sum.Size()
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *Duration_Duration) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.Duration != nil {
|
||||
l = github_com_gogo_protobuf_types.SizeOfStdDuration(*m.Duration)
|
||||
n += 1 + l + sovFeegrant(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
func (m *Duration_Blocks) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
n += 1 + sovFeegrant(uint64(m.Blocks))
|
||||
return n
|
||||
}
|
||||
func (m *ExpiresAt) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.Sum != nil {
|
||||
n += m.Sum.Size()
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *ExpiresAt_Time) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
if m.Time != nil {
|
||||
l = github_com_gogo_protobuf_types.SizeOfStdTime(*m.Time)
|
||||
n += 1 + l + sovFeegrant(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
func (m *ExpiresAt_Height) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
n += 1 + sovFeegrant(uint64(m.Height))
|
||||
return n
|
||||
}
|
||||
func (m *Grant) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
@ -1124,7 +745,10 @@ func (m *BasicAllowance) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.Expiration.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
if m.Expiration == nil {
|
||||
m.Expiration = new(time.Time)
|
||||
}
|
||||
if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(m.Expiration, dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
@ -1240,7 +864,7 @@ func (m *PeriodicAllowance) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.Period.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.Period, dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
@ -1341,7 +965,7 @@ func (m *PeriodicAllowance) Unmarshal(dAtA []byte) error {
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if err := m.PeriodReset.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.PeriodReset, dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
@ -1484,216 +1108,6 @@ func (m *AllowedMsgAllowance) Unmarshal(dAtA []byte) error {
|
||||
}
|
||||
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 ErrIntOverflowFeegrant
|
||||
}
|
||||
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 Duration", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowFeegrant
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthFeegrant
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthFeegrant
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
v := new(time.Duration)
|
||||
if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(v, dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
m.Sum = &Duration_Duration{v}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Blocks", wireType)
|
||||
}
|
||||
var v uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowFeegrant
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
v |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Sum = &Duration_Blocks{v}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipFeegrant(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthFeegrant
|
||||
}
|
||||
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 ErrIntOverflowFeegrant
|
||||
}
|
||||
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 ErrIntOverflowFeegrant
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthFeegrant
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthFeegrant
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
v := new(time.Time)
|
||||
if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(v, dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
m.Sum = &ExpiresAt_Time{v}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Height", wireType)
|
||||
}
|
||||
var v int64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowFeegrant
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
v |= int64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Sum = &ExpiresAt_Height{v}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipFeegrant(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthFeegrant
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *Grant) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
|
||||
@ -1,15 +1,13 @@
|
||||
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
|
||||
// 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.
|
||||
//
|
||||
@ -21,11 +19,6 @@ type FeeAllowanceI interface {
|
||||
// (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees)
|
||||
Accept(ctx sdk.Context, fee sdk.Coins, msgs []sdk.Msg) (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
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||
@ -88,23 +86,6 @@ func (a *AllowedMsgAllowance) allMsgTypesAllowed(ctx sdk.Context, msgs []sdk.Msg
|
||||
return true
|
||||
}
|
||||
|
||||
// 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 *AllowedMsgAllowance) PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceI {
|
||||
allowance, err := a.GetAllowance()
|
||||
if err != nil {
|
||||
panic("failed to get allowance")
|
||||
}
|
||||
|
||||
f, err := NewAllowedMsgAllowance(allowance.PrepareForExport(dumpTime, dumpHeight), a.AllowedMessages)
|
||||
if err != nil {
|
||||
panic("failed to export filtered fee allowance")
|
||||
}
|
||||
|
||||
return f
|
||||
}
|
||||
|
||||
// ValidateBasic implements FeeAllowance and enforces basic sanity checks
|
||||
func (a *AllowedMsgAllowance) ValidateBasic() error {
|
||||
if a.Allowance == nil {
|
||||
|
||||
@ -1,8 +1,6 @@
|
||||
package types
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec/types"
|
||||
@ -70,34 +68,3 @@ func (a Grant) UnpackInterfaces(unpacker types.AnyUnpacker) error {
|
||||
var allowance FeeAllowanceI
|
||||
return unpacker.UnpackAny(a.Allowance, &allowance)
|
||||
}
|
||||
|
||||
// PrepareForExport will make all needed changes to the allowance to prepare to be
|
||||
// re-imported at height 0, and return a copy of this grant.
|
||||
func (a Grant) PrepareForExport(dumpTime time.Time, dumpHeight int64) Grant {
|
||||
f, err := a.GetGrant()
|
||||
if err != nil {
|
||||
return Grant{}
|
||||
}
|
||||
|
||||
feegrant := f.PrepareForExport(dumpTime, dumpHeight)
|
||||
if feegrant == nil {
|
||||
return Grant{}
|
||||
}
|
||||
|
||||
granter, err := sdk.AccAddressFromBech32(a.Granter)
|
||||
if err != nil {
|
||||
return Grant{}
|
||||
}
|
||||
|
||||
grantee, err := sdk.AccAddressFromBech32(a.Grantee)
|
||||
if err != nil {
|
||||
return Grant{}
|
||||
}
|
||||
|
||||
grant, err := NewGrant(granter, grantee, feegrant)
|
||||
if err != nil {
|
||||
return Grant{}
|
||||
}
|
||||
|
||||
return grant
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/simapp"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
@ -18,57 +19,56 @@ func TestGrant(t *testing.T) {
|
||||
addr2, err := sdk.AccAddressFromBech32("cosmos1p9qh4ldfd6n0qehujsal4k7g0e37kel90rc4ts")
|
||||
require.NoError(t, err)
|
||||
atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555))
|
||||
ctx := app.BaseApp.NewContext(false, tmproto.Header{
|
||||
Time: time.Now(),
|
||||
})
|
||||
now := ctx.BlockTime()
|
||||
oneYear := now.AddDate(1, 0, 0)
|
||||
|
||||
zeroAtoms := sdk.NewCoins(sdk.NewInt64Coin("atom", 0))
|
||||
cdc := app.AppCodec()
|
||||
|
||||
cases := map[string]struct {
|
||||
granter sdk.AccAddress
|
||||
grantee sdk.AccAddress
|
||||
limit sdk.Coins
|
||||
expires types.ExpiresAt
|
||||
valid bool
|
||||
limit sdk.Coins
|
||||
expires time.Time
|
||||
valid bool
|
||||
}{
|
||||
"good": {
|
||||
granter: addr2,
|
||||
grantee: addr,
|
||||
limit: atom,
|
||||
expires: types.ExpiresAtHeight(100),
|
||||
valid: true,
|
||||
limit: atom,
|
||||
expires: oneYear,
|
||||
valid: true,
|
||||
},
|
||||
"no grantee": {
|
||||
granter: addr2,
|
||||
grantee: nil,
|
||||
limit: atom,
|
||||
expires: types.ExpiresAtHeight(100),
|
||||
valid: false,
|
||||
limit: atom,
|
||||
expires: oneYear,
|
||||
valid: false,
|
||||
},
|
||||
"no granter": {
|
||||
granter: nil,
|
||||
grantee: addr,
|
||||
limit: atom,
|
||||
expires: types.ExpiresAtHeight(100),
|
||||
valid: false,
|
||||
limit: atom,
|
||||
expires: oneYear,
|
||||
valid: false,
|
||||
},
|
||||
"self-grant": {
|
||||
granter: addr2,
|
||||
grantee: addr2,
|
||||
limit: atom,
|
||||
expires: types.ExpiresAtHeight(100),
|
||||
valid: false,
|
||||
},
|
||||
"bad height": {
|
||||
granter: addr2,
|
||||
grantee: addr,
|
||||
limit: atom,
|
||||
expires: types.ExpiresAtHeight(-100),
|
||||
valid: false,
|
||||
limit: atom,
|
||||
expires: oneYear,
|
||||
valid: false,
|
||||
},
|
||||
"zero allowance": {
|
||||
granter: addr2,
|
||||
grantee: addr,
|
||||
limit: zeroAtoms,
|
||||
expires: types.ExpiresAtTime(time.Now().Add(3 * time.Hour)),
|
||||
valid: false,
|
||||
limit: zeroAtoms,
|
||||
expires: oneYear,
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
@ -77,7 +77,7 @@ func TestGrant(t *testing.T) {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
grant, err := types.NewGrant(tc.granter, tc.grantee, &types.BasicAllowance{
|
||||
SpendLimit: tc.limit,
|
||||
Expiration: tc.expires,
|
||||
Expiration: &tc.expires,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = grant.ValidateBasic()
|
||||
@ -97,6 +97,7 @@ func TestGrant(t *testing.T) {
|
||||
|
||||
err = loaded.ValidateBasic()
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, grant, loaded)
|
||||
})
|
||||
}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
package types_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/cosmos/cosmos-sdk/codec"
|
||||
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
|
||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestMsgGrantFeeAllowance(t *testing.T) {
|
||||
@ -15,43 +16,45 @@ func TestMsgGrantFeeAllowance(t *testing.T) {
|
||||
addr, _ := sdk.AccAddressFromBech32("cosmos1aeuqja06474dfrj7uqsvukm6rael982kk89mqr")
|
||||
addr2, _ := sdk.AccAddressFromBech32("cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl")
|
||||
atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555))
|
||||
threeHours := time.Now().Add(3 * time.Hour)
|
||||
basic := &types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtTime(time.Now().Add(3 * time.Hour)),
|
||||
Expiration: &threeHours,
|
||||
}
|
||||
|
||||
cases := map[string]struct {
|
||||
grantee sdk.AccAddress
|
||||
granter sdk.AccAddress
|
||||
grant *types.BasicAllowance
|
||||
valid bool
|
||||
grant *types.BasicAllowance
|
||||
valid bool
|
||||
}{
|
||||
"valid":{
|
||||
"valid": {
|
||||
grantee: addr,
|
||||
granter: addr2,
|
||||
grant: basic,
|
||||
valid: true,
|
||||
grant: basic,
|
||||
valid: true,
|
||||
},
|
||||
"no grantee": {
|
||||
granter: addr2,
|
||||
grantee: sdk.AccAddress{},
|
||||
grant: basic,
|
||||
valid: false,
|
||||
grant: basic,
|
||||
valid: false,
|
||||
},
|
||||
"no granter": {
|
||||
granter: sdk.AccAddress{},
|
||||
grantee: addr,
|
||||
grant: basic,
|
||||
valid: false,
|
||||
grant: basic,
|
||||
valid: false,
|
||||
},
|
||||
"grantee == granter":{
|
||||
"grantee == granter": {
|
||||
grantee: addr,
|
||||
granter: addr,
|
||||
grant: basic,
|
||||
valid: false,
|
||||
grant: basic,
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _,tc := range cases {
|
||||
for _, tc := range cases {
|
||||
msg, err := types.NewMsgGrantAllowance(tc.grant, tc.granter, tc.grantee)
|
||||
require.NoError(t, err)
|
||||
err = msg.ValidateBasic()
|
||||
@ -78,43 +81,45 @@ func TestMsgRevokeFeeAllowance(t *testing.T) {
|
||||
addr, _ := sdk.AccAddressFromBech32("cosmos1aeuqja06474dfrj7uqsvukm6rael982kk89mqr")
|
||||
addr2, _ := sdk.AccAddressFromBech32("cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl")
|
||||
atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555))
|
||||
threeHours := time.Now().Add(3 * time.Hour)
|
||||
|
||||
basic := &types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtTime(time.Now().Add(3 * time.Hour)),
|
||||
Expiration: &threeHours,
|
||||
}
|
||||
cases := map[string]struct {
|
||||
grantee sdk.AccAddress
|
||||
granter sdk.AccAddress
|
||||
grant *types.BasicAllowance
|
||||
valid bool
|
||||
grant *types.BasicAllowance
|
||||
valid bool
|
||||
}{
|
||||
"valid":{
|
||||
"valid": {
|
||||
grantee: addr,
|
||||
granter: addr2,
|
||||
grant: basic,
|
||||
valid: true,
|
||||
grant: basic,
|
||||
valid: true,
|
||||
},
|
||||
"no grantee": {
|
||||
granter: addr2,
|
||||
grantee: sdk.AccAddress{},
|
||||
grant: basic,
|
||||
valid: false,
|
||||
grant: basic,
|
||||
valid: false,
|
||||
},
|
||||
"no granter": {
|
||||
granter: sdk.AccAddress{},
|
||||
grantee: addr,
|
||||
grant: basic,
|
||||
valid: false,
|
||||
grant: basic,
|
||||
valid: false,
|
||||
},
|
||||
"grantee == granter":{
|
||||
"grantee == granter": {
|
||||
grantee: addr,
|
||||
granter: addr,
|
||||
grant: basic,
|
||||
valid: false,
|
||||
grant: basic,
|
||||
valid: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _,tc := range cases {
|
||||
for _, tc := range cases {
|
||||
msg := types.NewMsgRevokeAllowance(tc.granter, tc.grantee)
|
||||
err := msg.ValidateBasic()
|
||||
if tc.valid {
|
||||
|
||||
@ -9,7 +9,7 @@ import (
|
||||
|
||||
var _ FeeAllowanceI = (*PeriodicAllowance)(nil)
|
||||
|
||||
// Accept can use fee payment requested as well as timestamp/height of the current block
|
||||
// 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.
|
||||
//
|
||||
@ -21,13 +21,12 @@ var _ FeeAllowanceI = (*PeriodicAllowance)(nil)
|
||||
// (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees)
|
||||
func (a *PeriodicAllowance) Accept(ctx sdk.Context, fee sdk.Coins, _ []sdk.Msg) (bool, error) {
|
||||
blockTime := ctx.BlockTime()
|
||||
blockHeight := ctx.BlockHeight()
|
||||
|
||||
if a.Basic.Expiration.IsExpired(&blockTime, blockHeight) {
|
||||
if a.Basic.Expiration != nil && blockTime.After(*a.Basic.Expiration) {
|
||||
return true, sdkerrors.Wrap(ErrFeeLimitExpired, "absolute limit")
|
||||
}
|
||||
|
||||
a.tryResetPeriod(blockTime, blockHeight)
|
||||
a.tryResetPeriod(blockTime)
|
||||
|
||||
// deduct from both the current period and the max amount
|
||||
var isNeg bool
|
||||
@ -54,8 +53,8 @@ func (a *PeriodicAllowance) Accept(ctx sdk.Context, fee sdk.Coins, _ []sdk.Msg)
|
||||
// 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 *PeriodicAllowance) tryResetPeriod(blockTime time.Time, blockHeight int64) {
|
||||
if !a.PeriodReset.Undefined() && !a.PeriodReset.IsExpired(&blockTime, blockHeight) {
|
||||
func (a *PeriodicAllowance) tryResetPeriod(blockTime time.Time) {
|
||||
if blockTime.Before(a.PeriodReset) {
|
||||
return
|
||||
}
|
||||
// set CanSpend to the lesser of PeriodSpendLimit and the TotalLimit
|
||||
@ -67,26 +66,9 @@ func (a *PeriodicAllowance) tryResetPeriod(blockTime time.Time, blockHeight int6
|
||||
|
||||
// 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 *PeriodicAllowance) PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceI {
|
||||
return &PeriodicAllowance{
|
||||
Basic: BasicAllowance{
|
||||
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),
|
||||
a.PeriodReset = a.PeriodReset.Add(a.Period)
|
||||
if blockTime.After(a.PeriodReset) {
|
||||
a.PeriodReset = blockTime.Add(a.Period)
|
||||
}
|
||||
}
|
||||
|
||||
@ -116,8 +98,9 @@ func (a PeriodicAllowance) ValidateBasic() error {
|
||||
}
|
||||
|
||||
// check times
|
||||
if err := a.Period.ValidateBasic(); err != nil {
|
||||
return err
|
||||
if a.Period.Seconds() < 0 {
|
||||
return sdkerrors.Wrap(ErrInvalidDuration, "negative clock step")
|
||||
}
|
||||
return a.PeriodReset.ValidateBasic()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -15,209 +15,20 @@ import (
|
||||
|
||||
func TestPeriodicFeeValidAllow(t *testing.T) {
|
||||
app := simapp.Setup(false)
|
||||
ctx := app.BaseApp.NewContext(false, tmproto.Header{
|
||||
Time: time.Now(),
|
||||
})
|
||||
|
||||
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 {
|
||||
allowance types.PeriodicAllowance
|
||||
// all other checks are ignored if valid=false
|
||||
fee sdk.Coins
|
||||
blockHeight int64
|
||||
valid bool
|
||||
accept bool
|
||||
remove bool
|
||||
remains sdk.Coins
|
||||
remainsPeriod sdk.Coins
|
||||
periodReset types.ExpiresAt
|
||||
}{
|
||||
"empty": {
|
||||
allowance: types.PeriodicAllowance{},
|
||||
valid: false,
|
||||
},
|
||||
"only basic": {
|
||||
allowance: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
},
|
||||
},
|
||||
valid: false,
|
||||
},
|
||||
"empty basic": {
|
||||
allowance: types.PeriodicAllowance{
|
||||
Period: types.BlockDuration(10),
|
||||
PeriodSpendLimit: smallAtom,
|
||||
PeriodReset: types.ExpiresAtHeight(70),
|
||||
},
|
||||
blockHeight: 75,
|
||||
valid: true,
|
||||
accept: true,
|
||||
remove: false,
|
||||
periodReset: types.ExpiresAtHeight(80),
|
||||
},
|
||||
"mismatched currencies": {
|
||||
allowance: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
},
|
||||
Period: types.BlockDuration(10),
|
||||
PeriodSpendLimit: eth,
|
||||
},
|
||||
valid: false,
|
||||
},
|
||||
"first time": {
|
||||
allowance: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
},
|
||||
Period: types.BlockDuration(10),
|
||||
PeriodSpendLimit: smallAtom,
|
||||
},
|
||||
valid: true,
|
||||
fee: smallAtom,
|
||||
blockHeight: 75,
|
||||
accept: true,
|
||||
remove: false,
|
||||
remainsPeriod: nil,
|
||||
remains: leftAtom,
|
||||
periodReset: types.ExpiresAtHeight(85),
|
||||
},
|
||||
"same period": {
|
||||
allowance: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
},
|
||||
Period: types.BlockDuration(10),
|
||||
PeriodReset: types.ExpiresAtHeight(80),
|
||||
PeriodSpendLimit: leftAtom,
|
||||
PeriodCanSpend: smallAtom,
|
||||
},
|
||||
valid: true,
|
||||
fee: smallAtom,
|
||||
blockHeight: 75,
|
||||
accept: true,
|
||||
remove: false,
|
||||
remainsPeriod: nil,
|
||||
remains: leftAtom,
|
||||
periodReset: types.ExpiresAtHeight(80),
|
||||
},
|
||||
"step one period": {
|
||||
allowance: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
},
|
||||
Period: types.BlockDuration(10),
|
||||
PeriodReset: types.ExpiresAtHeight(70),
|
||||
PeriodSpendLimit: leftAtom,
|
||||
},
|
||||
valid: true,
|
||||
fee: leftAtom,
|
||||
blockHeight: 75,
|
||||
accept: true,
|
||||
remove: false,
|
||||
remainsPeriod: nil,
|
||||
remains: smallAtom,
|
||||
periodReset: types.ExpiresAtHeight(80), // one step from last reset, not now
|
||||
},
|
||||
"step limited by global allowance": {
|
||||
allowance: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: smallAtom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
},
|
||||
Period: types.BlockDuration(10),
|
||||
PeriodReset: types.ExpiresAtHeight(70),
|
||||
PeriodSpendLimit: atom,
|
||||
},
|
||||
valid: true,
|
||||
fee: oneAtom,
|
||||
blockHeight: 75,
|
||||
accept: true,
|
||||
remove: false,
|
||||
remainsPeriod: smallAtom.Sub(oneAtom),
|
||||
remains: smallAtom.Sub(oneAtom),
|
||||
periodReset: types.ExpiresAtHeight(80), // one step from last reset, not now
|
||||
},
|
||||
"expired": {
|
||||
allowance: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
},
|
||||
Period: types.BlockDuration(10),
|
||||
PeriodSpendLimit: smallAtom,
|
||||
},
|
||||
valid: true,
|
||||
fee: smallAtom,
|
||||
blockHeight: 101,
|
||||
accept: false,
|
||||
remove: true,
|
||||
},
|
||||
"over period limit": {
|
||||
allowance: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
},
|
||||
Period: types.BlockDuration(10),
|
||||
PeriodReset: types.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.allowance.ValidateBasic()
|
||||
if !tc.valid {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
ctx := app.BaseApp.NewContext(false, tmproto.Header{}).WithBlockHeight(tc.blockHeight)
|
||||
// now try to deduct
|
||||
removed, err := tc.allowance.Accept(ctx, tc.fee, []sdk.Msg{})
|
||||
if !tc.accept {
|
||||
require.Error(t, err)
|
||||
return
|
||||
}
|
||||
require.NoError(t, err)
|
||||
|
||||
require.Equal(t, tc.remove, removed)
|
||||
if !removed {
|
||||
assert.Equal(t, tc.remains, tc.allowance.Basic.SpendLimit)
|
||||
assert.Equal(t, tc.remainsPeriod, tc.allowance.PeriodCanSpend)
|
||||
assert.Equal(t, tc.periodReset, tc.allowance.PeriodReset)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPeriodicFeeValidAllowTime(t *testing.T) {
|
||||
app := simapp.Setup(false)
|
||||
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))
|
||||
|
||||
now := time.Now()
|
||||
now := ctx.BlockTime()
|
||||
oneHour := now.Add(1 * time.Hour)
|
||||
twoHours := now.Add(2 * time.Hour)
|
||||
tenMinutes := time.Duration(10) * time.Minute
|
||||
|
||||
cases := map[string]struct {
|
||||
allow types.PeriodicAllowance
|
||||
@ -229,7 +40,7 @@ func TestPeriodicFeeValidAllowTime(t *testing.T) {
|
||||
remove bool
|
||||
remains sdk.Coins
|
||||
remainsPeriod sdk.Coins
|
||||
periodReset types.ExpiresAt
|
||||
periodReset time.Time
|
||||
}{
|
||||
"empty": {
|
||||
allow: types.PeriodicAllowance{},
|
||||
@ -239,30 +50,30 @@ func TestPeriodicFeeValidAllowTime(t *testing.T) {
|
||||
allow: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtTime(oneHour),
|
||||
Expiration: &oneHour,
|
||||
},
|
||||
},
|
||||
valid: false,
|
||||
},
|
||||
"empty basic": {
|
||||
allow: types.PeriodicAllowance{
|
||||
Period: types.ClockDuration(time.Duration(10) * time.Minute),
|
||||
Period: tenMinutes,
|
||||
PeriodSpendLimit: smallAtom,
|
||||
PeriodReset: types.ExpiresAtTime(now.Add(30 * time.Minute)),
|
||||
PeriodReset: now.Add(30 * time.Minute),
|
||||
},
|
||||
blockTime: now,
|
||||
valid: true,
|
||||
accept: true,
|
||||
remove: false,
|
||||
periodReset: types.ExpiresAtTime(now.Add(30 * time.Minute)),
|
||||
periodReset: now.Add(30 * time.Minute),
|
||||
},
|
||||
"mismatched currencies": {
|
||||
allow: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtTime(oneHour),
|
||||
Expiration: &oneHour,
|
||||
},
|
||||
Period: types.ClockDuration(10 * time.Minute),
|
||||
Period: tenMinutes,
|
||||
PeriodSpendLimit: eth,
|
||||
},
|
||||
valid: false,
|
||||
@ -271,10 +82,10 @@ func TestPeriodicFeeValidAllowTime(t *testing.T) {
|
||||
allow: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtTime(now.Add(2 * time.Hour)),
|
||||
Expiration: &twoHours,
|
||||
},
|
||||
Period: types.ClockDuration(10),
|
||||
PeriodReset: types.ExpiresAtTime(now.Add(1 * time.Hour)),
|
||||
Period: tenMinutes,
|
||||
PeriodReset: now.Add(1 * time.Hour),
|
||||
PeriodSpendLimit: leftAtom,
|
||||
PeriodCanSpend: smallAtom,
|
||||
},
|
||||
@ -285,16 +96,16 @@ func TestPeriodicFeeValidAllowTime(t *testing.T) {
|
||||
remove: false,
|
||||
remainsPeriod: nil,
|
||||
remains: leftAtom,
|
||||
periodReset: types.ExpiresAtTime(now.Add(1 * time.Hour)),
|
||||
periodReset: now.Add(1 * time.Hour),
|
||||
},
|
||||
"step one period": {
|
||||
allow: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtTime(now.Add(2 * time.Hour)),
|
||||
Expiration: &twoHours,
|
||||
},
|
||||
Period: types.ClockDuration(10 * time.Minute),
|
||||
PeriodReset: types.ExpiresAtTime(now),
|
||||
Period: tenMinutes,
|
||||
PeriodReset: now,
|
||||
PeriodSpendLimit: leftAtom,
|
||||
},
|
||||
valid: true,
|
||||
@ -304,16 +115,16 @@ func TestPeriodicFeeValidAllowTime(t *testing.T) {
|
||||
remove: false,
|
||||
remainsPeriod: nil,
|
||||
remains: smallAtom,
|
||||
periodReset: types.ExpiresAtTime(oneHour.Add(10 * time.Minute)), // one step from last reset, not now
|
||||
periodReset: oneHour.Add(10 * time.Minute), // one step from last reset, not now
|
||||
},
|
||||
"step limited by global allowance": {
|
||||
allow: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: smallAtom,
|
||||
Expiration: types.ExpiresAtTime(now.Add(2 * time.Hour)),
|
||||
Expiration: &twoHours,
|
||||
},
|
||||
Period: types.ClockDuration(10 * time.Minute),
|
||||
PeriodReset: types.ExpiresAtTime(now),
|
||||
Period: tenMinutes,
|
||||
PeriodReset: now,
|
||||
PeriodSpendLimit: atom,
|
||||
},
|
||||
valid: true,
|
||||
@ -323,15 +134,15 @@ func TestPeriodicFeeValidAllowTime(t *testing.T) {
|
||||
remove: false,
|
||||
remainsPeriod: smallAtom.Sub(oneAtom),
|
||||
remains: smallAtom.Sub(oneAtom),
|
||||
periodReset: types.ExpiresAtTime(oneHour.Add(10 * time.Minute)), // one step from last reset, not now
|
||||
periodReset: oneHour.Add(10 * time.Minute), // one step from last reset, not now
|
||||
},
|
||||
"expired": {
|
||||
allow: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtTime(now),
|
||||
Expiration: &now,
|
||||
},
|
||||
Period: types.ClockDuration(time.Hour),
|
||||
Period: time.Hour,
|
||||
PeriodSpendLimit: smallAtom,
|
||||
},
|
||||
valid: true,
|
||||
@ -344,10 +155,10 @@ func TestPeriodicFeeValidAllowTime(t *testing.T) {
|
||||
allow: types.PeriodicAllowance{
|
||||
Basic: types.BasicAllowance{
|
||||
SpendLimit: atom,
|
||||
Expiration: types.ExpiresAtHeight(100),
|
||||
Expiration: &now,
|
||||
},
|
||||
Period: types.ClockDuration(time.Hour),
|
||||
PeriodReset: types.ExpiresAtTime(now.Add(1 * time.Hour)),
|
||||
Period: time.Hour,
|
||||
PeriodReset: now.Add(1 * time.Hour),
|
||||
PeriodSpendLimit: leftAtom,
|
||||
PeriodCanSpend: smallAtom,
|
||||
},
|
||||
|
||||
Loading…
Reference in New Issue
Block a user