Skip to content

Commit

Permalink
x/feegrant remove height base expiration (cosmos#9206)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>

* fix errors

* fix keeper tests

* Update x/feegrant/keeper/keeper_test.go

Co-authored-by: Marie Gauthier <[email protected]>

* review changes

* review changes

* fix tests

* run make proto-gen

* fix errors

* Update x/feegrant/keeper/keeper_test.go

Co-authored-by: atheeshp <[email protected]>

* Update x/feegrant/keeper/keeper_test.go

* update ADR

* add test

* review changes

* review changes

Co-authored-by: technicallyty <[email protected]>
Co-authored-by: Marie Gauthier <[email protected]>
Co-authored-by: atheeshp <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
5 people authored May 5, 2021
1 parent 68d4610 commit 1e1c812
Show file tree
Hide file tree
Showing 25 changed files with 342 additions and 1,660 deletions.
108 changes: 49 additions & 59 deletions docs/architecture/adr-029-fee-grant-module.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## Changelog

- 2020/08/18: Initial Draft
- 2021/05/05: Removed height based expiration support and simplified naming.

## Status

Expand Down Expand Up @@ -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;
// expires_at specifies an optional time when this allowance expires
ExpiresAt expiration = 2;
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;
// 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;
// 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_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;
}
message PeriodicAllowance {
BasicAllowance basic = 1;
// 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 specifies the time duration in which period_spend_limit coins can
// be spent before that allowance is reset
google.protobuf.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_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;
}
Expand Down
42 changes: 3 additions & 39 deletions docs/core/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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 |



Expand Down Expand Up @@ -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 |



Expand Down
26 changes: 3 additions & 23 deletions proto/cosmos/feegrant/v1beta1/feegrant.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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
Expand All @@ -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.
Expand All @@ -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.
Expand Down
16 changes: 12 additions & 4 deletions x/feegrant/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ Examples:
if err != nil {
return err
}
basic.Expiration = types.ExpiresAtTime(expiresAtTime)
basic.Expiration = &expiresAtTime
}

var grant types.FeeAllowanceI
Expand All @@ -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,
}
Expand Down Expand Up @@ -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
}
11 changes: 2 additions & 9 deletions x/feegrant/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
})

Expand Down
3 changes: 2 additions & 1 deletion x/feegrant/genesis_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
3 changes: 2 additions & 1 deletion x/feegrant/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Loading

0 comments on commit 1e1c812

Please sign in to comment.