diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d5fef8d71..f01af2ac57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ * (x/gov) [\#368](https://github.com/line/lbm-sdk/pull/368) Governance Split Votes, use `MsgWeightedVote` to send a split vote. Sending a regular `MsgVote` will convert the underlying vote option into a weighted vote with weight 1. * (x/upgrade) [\#377] (https://github.com/line/lbm-sdk/pull/377) To smoothen the update to the latest stable release, the SDK includes vesion map for managing migrations between SDK versions. * (x/wasm) [\#358] (https://github.com/line/lbm-sdk/pull/358) change wasm metrics method to using prometheus directly +* (x/feegrant) [\#380] (https://github.com/line/lbm-sdk/pull/380) Feegrant module ### Improvements * (slashing) [\#347](https://github.com/line/lbm-sdk/pull/347) Introduce VoterSetCounter diff --git a/baseapp/iostats_nop.go b/baseapp/iostats_nop.go index 0bf5d8cacb..4f12157503 100644 --- a/baseapp/iostats_nop.go +++ b/baseapp/iostats_nop.go @@ -5,5 +5,4 @@ package baseapp import "github.com/line/ostracon/libs/log" func logIoStats(logger log.Logger) { - return } diff --git a/client/cmd.go b/client/cmd.go index b75325e4d2..42d18aad38 100644 --- a/client/cmd.go +++ b/client/cmd.go @@ -221,6 +221,18 @@ func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, err clientCtx = clientCtx.WithSignModeStr(signModeStr) } + if clientCtx.FeeGranter == "" || flagSet.Changed(flags.FlagFeeAccount) { + granter, _ := flagSet.GetString(flags.FlagFeeAccount) + + if granter != "" { + err := sdk.ValidateAccAddress(granter) + if err != nil { + return clientCtx, err + } + clientCtx = clientCtx.WithFeeGranterAddress(sdk.AccAddress(granter)) + } + } + if clientCtx.From == "" || flagSet.Changed(flags.FlagFrom) { from, _ := flagSet.GetString(flags.FlagFrom) fromAddr, fromName, keyType, err := GetFromFields(clientCtx.Keyring, from, clientCtx.GenerateOnly) diff --git a/client/context.go b/client/context.go index 6e2c91f16c..cb9412f120 100644 --- a/client/context.go +++ b/client/context.go @@ -44,6 +44,7 @@ type Context struct { TxConfig TxConfig AccountRetriever AccountRetriever NodeURI string + FeeGranter sdk.AccAddress // TODO: Deprecated (remove). LegacyAmino *codec.LegacyAmino @@ -86,6 +87,13 @@ func (ctx Context) WithFrom(from string) Context { return ctx } +// WithFeeGranterAddress returns a copy of the context with an updated fee granter account +// address. +func (ctx Context) WithFeeGranterAddress(addr sdk.AccAddress) Context { + ctx.FeeGranter = addr + return ctx +} + // WithOutputFormat returns a copy of the context with an updated OutputFormat field. func (ctx Context) WithOutputFormat(format string) Context { ctx.OutputFormat = format diff --git a/client/flags/flags.go b/client/flags/flags.go index daa18fb147..9fde16e616 100644 --- a/client/flags/flags.go +++ b/client/flags/flags.go @@ -72,6 +72,7 @@ const ( FlagTimeoutHeight = "timeout-height" FlagKeyAlgorithm = "algo" FlagPrivKeyType = "priv_key_type" + FlagFeeAccount = "fee-account" // Tendermint logging flags FlagLogLevel = "log_level" @@ -117,6 +118,7 @@ func AddTxFlagsToCmd(cmd *cobra.Command) { cmd.Flags().Uint64(FlagTimeoutHeight, 0, "Set a block timeout height to prevent the tx from being committed past a certain height") cmd.Flags().String(FlagPrivKeyType, DefaultPrivKeyType, "specify validator's private key type (ed25519|composite). \n"+ "set this to priv_key.type in priv_validator_key.json; default `ed25519`") + cmd.Flags().String(FlagFeeAccount, "", "Fee account pays fees for the transaction instead of deducting from the signer") // --gas can accept integers and "auto" cmd.Flags().String(FlagGas, "", fmt.Sprintf("gas limit to set per-transaction; set to %q to calculate sufficient gas automatically (default %d)", GasFlagAuto, DefaultGasLimit)) diff --git a/client/query.go b/client/query.go index 28745ccbf8..41507ab9aa 100644 --- a/client/query.go +++ b/client/query.go @@ -57,6 +57,11 @@ func (ctx Context) GetFromAddress() sdk.AccAddress { return ctx.FromAddress } +// GetFeeGranterAddress returns the fee granter address from the context +func (ctx Context) GetFeeGranterAddress() sdk.AccAddress { + return ctx.FeeGranter +} + // GetFromName returns the key name for the current context. func (ctx Context) GetFromName() string { return ctx.FromName diff --git a/client/tx/tx.go b/client/tx/tx.go index 0954e8a835..841fa5ed30 100644 --- a/client/tx/tx.go +++ b/client/tx/tx.go @@ -117,6 +117,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error { } } + tx.SetFeeGranter(clientCtx.GetFeeGranterAddress()) err = Sign(txf, clientCtx.GetFromName(), tx, true) if err != nil { return err diff --git a/client/tx_config.go b/client/tx_config.go index d55b0dde07..68bc73477d 100644 --- a/client/tx_config.go +++ b/client/tx_config.go @@ -43,5 +43,6 @@ type ( SetFeeAmount(amount sdk.Coins) SetGasLimit(limit uint64) SetTimeoutHeight(height uint64) + SetFeeGranter(feeGranter sdk.AccAddress) } ) diff --git a/docs/core/proto-docs.md b/docs/core/proto-docs.md index 1ab8f2d959..a85a7b969f 100644 --- a/docs/core/proto-docs.md +++ b/docs/core/proto-docs.md @@ -457,6 +457,31 @@ - [Msg](#lbm.evidence.v1.Msg) +- [lbm/feegrant/v1/feegrant.proto](#lbm/feegrant/v1/feegrant.proto) + - [AllowedMsgAllowance](#lbm.feegrant.v1.AllowedMsgAllowance) + - [BasicAllowance](#lbm.feegrant.v1.BasicAllowance) + - [Grant](#lbm.feegrant.v1.Grant) + - [PeriodicAllowance](#lbm.feegrant.v1.PeriodicAllowance) + +- [lbm/feegrant/v1/genesis.proto](#lbm/feegrant/v1/genesis.proto) + - [GenesisState](#lbm.feegrant.v1.GenesisState) + +- [lbm/feegrant/v1/query.proto](#lbm/feegrant/v1/query.proto) + - [QueryAllowanceRequest](#lbm.feegrant.v1.QueryAllowanceRequest) + - [QueryAllowanceResponse](#lbm.feegrant.v1.QueryAllowanceResponse) + - [QueryAllowancesRequest](#lbm.feegrant.v1.QueryAllowancesRequest) + - [QueryAllowancesResponse](#lbm.feegrant.v1.QueryAllowancesResponse) + + - [Query](#lbm.feegrant.v1.Query) + +- [lbm/feegrant/v1/tx.proto](#lbm/feegrant/v1/tx.proto) + - [MsgGrantAllowance](#lbm.feegrant.v1.MsgGrantAllowance) + - [MsgGrantAllowanceResponse](#lbm.feegrant.v1.MsgGrantAllowanceResponse) + - [MsgRevokeAllowance](#lbm.feegrant.v1.MsgRevokeAllowance) + - [MsgRevokeAllowanceResponse](#lbm.feegrant.v1.MsgRevokeAllowanceResponse) + + - [Msg](#lbm.feegrant.v1.Msg) + - [lbm/genutil/v1/genesis.proto](#lbm/genutil/v1/genesis.proto) - [GenesisState](#lbm.genutil.v1.GenesisState) @@ -6955,6 +6980,294 @@ Msg defines the evidence Msg service. + +

Top

+ +## lbm/feegrant/v1/feegrant.proto + + + + + +### AllowedMsgAllowance +AllowedMsgAllowance creates allowance only for specified message types. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `allowance` | [google.protobuf.Any](#google.protobuf.Any) | | allowance can be any of basic and filtered fee allowance. | +| `allowed_messages` | [string](#string) | repeated | allowed_messages are the messages for which the grantee has the access. | + + + + + + + + +### BasicAllowance +BasicAllowance implements Allowance with a one-time grant of tokens +that optionally expires. The grantee can use up to SpendLimit to cover fees. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `spend_limit` | [lbm.base.v1.Coin](#lbm.base.v1.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` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | expiration specifies an optional time when this allowance expires | + + + + + + + + +### Grant +Grant is stored in the KVStore to record a grant with full context + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `granter` | [string](#string) | | granter is the address of the user granting an allowance of their funds. | +| `grantee` | [string](#string) | | grantee is the address of the user being granted an allowance of another user's funds. | +| `allowance` | [google.protobuf.Any](#google.protobuf.Any) | | allowance can be any of basic and filtered fee allowance. | + + + + + + + + +### PeriodicAllowance +PeriodicAllowance extends Allowance to allow for both a maximum cap, +as well as a limit per time period. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `basic` | [BasicAllowance](#lbm.feegrant.v1.BasicAllowance) | | basic specifies a struct of `BasicAllowance` | +| `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` | [lbm.base.v1.Coin](#lbm.base.v1.Coin) | repeated | period_spend_limit specifies the maximum number of coins that can be spent in the period | +| `period_can_spend` | [lbm.base.v1.Coin](#lbm.base.v1.Coin) | repeated | period_can_spend is the number of coins left to be spent before the period_reset time | +| `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 | + + + + + + + + + + + + + + + + +

Top

+ +## lbm/feegrant/v1/genesis.proto + + + + + +### GenesisState +GenesisState contains a set of fee allowances, persisted from the store + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `allowances` | [Grant](#lbm.feegrant.v1.Grant) | repeated | | + + + + + + + + + + + + + + + + +

Top

+ +## lbm/feegrant/v1/query.proto + + + + + +### QueryAllowanceRequest +QueryAllowanceRequest is the request type for the Query/Allowance RPC method. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `granter` | [string](#string) | | granter is the address of the user granting an allowance of their funds. | +| `grantee` | [string](#string) | | grantee is the address of the user being granted an allowance of another user's funds. | + + + + + + + + +### QueryAllowanceResponse +QueryAllowanceResponse is the response type for the Query/Allowance RPC method. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `allowance` | [Grant](#lbm.feegrant.v1.Grant) | | allowance is a allowance granted for grantee by granter. | + + + + + + + + +### QueryAllowancesRequest +QueryAllowancesRequest is the request type for the Query/Allowances RPC method. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `grantee` | [string](#string) | | | +| `pagination` | [lbm.base.query.v1.PageRequest](#lbm.base.query.v1.PageRequest) | | pagination defines an pagination for the request. | + + + + + + + + +### QueryAllowancesResponse +QueryAllowancesResponse is the response type for the Query/Allowances RPC method. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `allowances` | [Grant](#lbm.feegrant.v1.Grant) | repeated | allowances are allowance's granted for grantee by granter. | +| `pagination` | [lbm.base.query.v1.PageResponse](#lbm.base.query.v1.PageResponse) | | pagination defines an pagination for the response. | + + + + + + + + + + + + + + +### Query +Query defines the gRPC querier service. + +| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint | +| ----------- | ------------ | ------------- | ------------| ------- | -------- | +| `Allowance` | [QueryAllowanceRequest](#lbm.feegrant.v1.QueryAllowanceRequest) | [QueryAllowanceResponse](#lbm.feegrant.v1.QueryAllowanceResponse) | Allowance returns fee granted to the grantee by the granter. | GET|/lbm/feegrant/v1/allowance/{granter}/{grantee}| +| `Allowances` | [QueryAllowancesRequest](#lbm.feegrant.v1.QueryAllowancesRequest) | [QueryAllowancesResponse](#lbm.feegrant.v1.QueryAllowancesResponse) | Allowances returns all the grants for address. | GET|/lbm/feegrant/v1/allowances/{grantee}| + + + + + + +

Top

+ +## lbm/feegrant/v1/tx.proto + + + + + +### MsgGrantAllowance +MsgGrantAllowance adds permission for Grantee to spend up to Allowance +of fees from the account of Granter. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `granter` | [string](#string) | | granter is the address of the user granting an allowance of their funds. | +| `grantee` | [string](#string) | | grantee is the address of the user being granted an allowance of another user's funds. | +| `allowance` | [google.protobuf.Any](#google.protobuf.Any) | | allowance can be any of basic and filtered fee allowance. | + + + + + + + + +### MsgGrantAllowanceResponse +MsgGrantAllowanceResponse defines the Msg/GrantAllowanceResponse response type. + + + + + + + + +### MsgRevokeAllowance +MsgRevokeAllowance removes any existing Allowance from Granter to Grantee. + + +| Field | Type | Label | Description | +| ----- | ---- | ----- | ----------- | +| `granter` | [string](#string) | | granter is the address of the user granting an allowance of their funds. | +| `grantee` | [string](#string) | | grantee is the address of the user being granted an allowance of another user's funds. | + + + + + + + + +### MsgRevokeAllowanceResponse +MsgRevokeAllowanceResponse defines the Msg/RevokeAllowanceResponse response type. + + + + + + + + + + + + + + +### Msg +Msg defines the feegrant msg service. + +| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint | +| ----------- | ------------ | ------------- | ------------| ------- | -------- | +| `GrantAllowance` | [MsgGrantAllowance](#lbm.feegrant.v1.MsgGrantAllowance) | [MsgGrantAllowanceResponse](#lbm.feegrant.v1.MsgGrantAllowanceResponse) | GrantAllowance grants fee allowance to the grantee on the granter's account with the provided expiration time. | | +| `RevokeAllowance` | [MsgRevokeAllowance](#lbm.feegrant.v1.MsgRevokeAllowance) | [MsgRevokeAllowanceResponse](#lbm.feegrant.v1.MsgRevokeAllowanceResponse) | RevokeAllowance revokes any fee allowance of granter's account that has been granted to the grantee. | | + + + + +

Top

diff --git a/proto/lbm/feegrant/v1/feegrant.proto b/proto/lbm/feegrant/v1/feegrant.proto new file mode 100644 index 0000000000..aab73c3935 --- /dev/null +++ b/proto/lbm/feegrant/v1/feegrant.proto @@ -0,0 +1,77 @@ +syntax = "proto3"; +package lbm.feegrant.v1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "cosmos_proto/cosmos.proto"; +import "lbm/base/v1/coin.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/duration.proto"; + +option go_package = "github.com/line/lbm-sdk/x/feegrant/types"; + +// BasicAllowance implements Allowance with a one-time grant of tokens +// that optionally expires. The grantee can use up to SpendLimit to cover fees. +message BasicAllowance { + option (cosmos_proto.implements_interface) = "FeeAllowanceI"; + + // 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 lbm.base.v1.Coin spend_limit = 1 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/line/lbm-sdk/types.Coins"]; + + // expiration specifies an optional time when this allowance expires + google.protobuf.Timestamp expiration = 2 [(gogoproto.stdtime) = true]; +} + +// PeriodicAllowance extends Allowance to allow for both a maximum cap, +// as well as a limit per time period. +message PeriodicAllowance { + option (cosmos_proto.implements_interface) = "FeeAllowanceI"; + + // basic specifies a struct of `BasicAllowance` + BasicAllowance basic = 1 [(gogoproto.nullable) = false]; + + // period specifies the time duration in which period_spend_limit coins can + // be spent before that allowance is reset + 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 + repeated lbm.base.v1.Coin period_spend_limit = 3 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/line/lbm-sdk/types.Coins"]; + + // period_can_spend is the number of coins left to be spent before the period_reset time + repeated lbm.base.v1.Coin period_can_spend = 4 + [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/line/lbm-sdk/types.Coins"]; + + // 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 [(gogoproto.stdtime) = true, (gogoproto.nullable) = false]; +} + +// AllowedMsgAllowance creates allowance only for specified message types. +message AllowedMsgAllowance { + option (gogoproto.goproto_getters) = false; + option (cosmos_proto.implements_interface) = "FeeAllowanceI"; + + // allowance can be any of basic and filtered fee allowance. + google.protobuf.Any allowance = 1 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"]; + + // allowed_messages are the messages for which the grantee has the access. + repeated string allowed_messages = 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. + string granter = 1 [(gogoproto.moretags) = "yaml:\"granter_address\""]; + + // grantee is the address of the user being granted an allowance of another user's funds. + string grantee = 2 [(gogoproto.moretags) = "yaml:\"grantee_address\""]; + + // allowance can be any of basic and filtered fee allowance. + google.protobuf.Any allowance = 3 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"]; +} diff --git a/proto/lbm/feegrant/v1/genesis.proto b/proto/lbm/feegrant/v1/genesis.proto new file mode 100644 index 0000000000..b1755309a1 --- /dev/null +++ b/proto/lbm/feegrant/v1/genesis.proto @@ -0,0 +1,12 @@ +syntax = "proto3"; +package lbm.feegrant.v1; + +import "gogoproto/gogo.proto"; +import "lbm/feegrant/v1/feegrant.proto"; + +option go_package = "github.com/line/lbm-sdk/x/feegrant/types"; + +// GenesisState contains a set of fee allowances, persisted from the store +message GenesisState { + repeated Grant allowances = 1 [(gogoproto.nullable) = false]; +} diff --git a/proto/lbm/feegrant/v1/query.proto b/proto/lbm/feegrant/v1/query.proto new file mode 100644 index 0000000000..50135a3c24 --- /dev/null +++ b/proto/lbm/feegrant/v1/query.proto @@ -0,0 +1,55 @@ +syntax = "proto3"; +package lbm.feegrant.v1; + +import "gogoproto/gogo.proto"; +import "lbm/feegrant/v1/feegrant.proto"; +import "lbm/base/query/v1/pagination.proto"; +import "google/api/annotations.proto"; + +option go_package = "github.com/line/lbm-sdk/x/feegrant/types"; + +// Query defines the gRPC querier service. +service Query { + + // Allowance returns fee granted to the grantee by the granter. + rpc Allowance(QueryAllowanceRequest) returns (QueryAllowanceResponse) { + option (google.api.http).get = "/lbm/feegrant/v1/allowance/{granter}/{grantee}"; + } + + // Allowances returns all the grants for address. + rpc Allowances(QueryAllowancesRequest) returns (QueryAllowancesResponse) { + option (google.api.http).get = "/lbm/feegrant/v1/allowances/{grantee}"; + } +} + +// QueryAllowanceRequest is the request type for the Query/Allowance RPC method. +message QueryAllowanceRequest { + // granter is the address of the user granting an allowance of their funds. + string granter = 1 [(gogoproto.moretags) = "yaml:\"granter_address\""]; + + // grantee is the address of the user being granted an allowance of another user's funds. + string grantee = 2 [(gogoproto.moretags) = "yaml:\"grantee_address\""]; +} + +// QueryAllowanceResponse is the response type for the Query/Allowance RPC method. +message QueryAllowanceResponse { + // allowance is a allowance granted for grantee by granter. + lbm.feegrant.v1.Grant allowance = 1; +} + +// QueryAllowancesRequest is the request type for the Query/Allowances RPC method. +message QueryAllowancesRequest { + string grantee = 1 [(gogoproto.moretags) = "yaml:\"grantee_address\""]; + + // pagination defines an pagination for the request. + lbm.base.query.v1.PageRequest pagination = 2; +} + +// QueryAllowancesResponse is the response type for the Query/Allowances RPC method. +message QueryAllowancesResponse { + // allowances are allowance's granted for grantee by granter. + repeated lbm.feegrant.v1.Grant allowances = 1; + + // pagination defines an pagination for the response. + lbm.base.query.v1.PageResponse pagination = 2; +} diff --git a/proto/lbm/feegrant/v1/tx.proto b/proto/lbm/feegrant/v1/tx.proto new file mode 100644 index 0000000000..8ff00a47f6 --- /dev/null +++ b/proto/lbm/feegrant/v1/tx.proto @@ -0,0 +1,48 @@ +syntax = "proto3"; +package lbm.feegrant.v1; + +import "gogoproto/gogo.proto"; +import "google/protobuf/any.proto"; +import "cosmos_proto/cosmos.proto"; + +option go_package = "github.com/line/lbm-sdk/x/feegrant/types"; + +// Msg defines the feegrant msg service. +service Msg { + + // GrantAllowance grants fee allowance to the grantee on the granter's + // account with the provided expiration time. + rpc GrantAllowance(MsgGrantAllowance) returns (MsgGrantAllowanceResponse); + + // RevokeAllowance revokes any fee allowance of granter's account that + // has been granted to the grantee. + rpc RevokeAllowance(MsgRevokeAllowance) returns (MsgRevokeAllowanceResponse); +} + +// MsgGrantAllowance adds permission for Grantee to spend up to Allowance +// of fees from the account of Granter. +message MsgGrantAllowance { + // granter is the address of the user granting an allowance of their funds. + string granter = 1 [(gogoproto.moretags) = "yaml:\"granter_address\""]; + + // grantee is the address of the user being granted an allowance of another user's funds. + string grantee = 2 [(gogoproto.moretags) = "yaml:\"grantee_address\""]; + + // allowance can be any of basic and filtered fee allowance. + google.protobuf.Any allowance = 3 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"]; +} + +// MsgGrantAllowanceResponse defines the Msg/GrantAllowanceResponse response type. +message MsgGrantAllowanceResponse {} + +// MsgRevokeAllowance removes any existing Allowance from Granter to Grantee. +message MsgRevokeAllowance { + // granter is the address of the user granting an allowance of their funds. + string granter = 1 [(gogoproto.moretags) = "yaml:\"granter_address\""]; + + // grantee is the address of the user being granted an allowance of another user's funds. + string grantee = 2 [(gogoproto.moretags) = "yaml:\"grantee_address\""]; +} + +// MsgRevokeAllowanceResponse defines the Msg/RevokeAllowanceResponse response type. +message MsgRevokeAllowanceResponse {} diff --git a/simapp/app.go b/simapp/app.go index 76303d1a98..dbb8125c19 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -7,6 +7,9 @@ import ( "path/filepath" "github.com/gorilla/mux" + "github.com/line/lbm-sdk/x/feegrant" + feegrantkeeper "github.com/line/lbm-sdk/x/feegrant/keeper" + feegranttypes "github.com/line/lbm-sdk/x/feegrant/types" abci "github.com/line/ostracon/abci/types" ostjson "github.com/line/ostracon/libs/json" "github.com/line/ostracon/libs/log" @@ -115,6 +118,7 @@ var ( crisis.AppModuleBasic{}, slashing.AppModuleBasic{}, ibc.AppModuleBasic{}, + feegrant.AppModuleBasic{}, upgrade.AppModuleBasic{}, evidence.AppModuleBasic{}, transfer.AppModuleBasic{}, @@ -168,6 +172,7 @@ type SimApp struct { IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly EvidenceKeeper evidencekeeper.Keeper TransferKeeper ibctransferkeeper.Keeper + FeeGrantKeeper feegrantkeeper.Keeper // make scoped keepers public for test purposes ScopedIBCKeeper capabilitykeeper.ScopedKeeper @@ -214,7 +219,7 @@ func NewSimApp( authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey, minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey, govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, - evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey, + evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey, feegranttypes.StoreKey, ) memKeys := sdk.NewMemoryStoreKeys(capabilitytypes.MemStoreKey) @@ -265,6 +270,8 @@ func NewSimApp( app.CrisisKeeper = crisiskeeper.NewKeeper( app.GetSubspace(crisistypes.ModuleName), invCheckPeriod, app.BankKeeper, authtypes.FeeCollectorName, ) + + app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, keys[feegranttypes.StoreKey], app.AccountKeeper) app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, keys[upgradetypes.StoreKey], appCodec, homePath) // register the staking hooks @@ -333,6 +340,7 @@ func NewSimApp( bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), capability.NewAppModule(appCodec, *app.CapabilityKeeper), crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants), + feegrant.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper), slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper), @@ -364,6 +372,7 @@ func NewSimApp( capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName, slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName, ibchost.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, ibctransfertypes.ModuleName, + feegranttypes.ModuleName, ) app.mm.RegisterInvariants(&app.CrisisKeeper) @@ -382,6 +391,7 @@ func NewSimApp( auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts), bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper), capability.NewAppModule(appCodec, *app.CapabilityKeeper), + feegrant.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry), gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper), mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper), staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper), @@ -404,7 +414,7 @@ func NewSimApp( app.SetBeginBlocker(app.BeginBlocker) app.SetAnteHandler( ante.NewAnteHandler( - app.AccountKeeper, app.BankKeeper, ante.DefaultSigVerificationGasConsumer, + app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, ante.DefaultSigVerificationGasConsumer, encodingConfig.TxConfig.SignModeHandler(), ), ) diff --git a/simapp/params/weights.go b/simapp/params/weights.go index 1581d2fc38..746e304de2 100644 --- a/simapp/params/weights.go +++ b/simapp/params/weights.go @@ -21,4 +21,8 @@ const ( DefaultWeightCommunitySpendProposal int = 5 DefaultWeightTextProposal int = 5 DefaultWeightParamChangeProposal int = 5 + + // feegrant + DefaultWeightGrantAllowance int = 100 + DefaultWeightRevokeAllowance int = 100 ) diff --git a/x/auth/ante/ante.go b/x/auth/ante/ante.go index 9414f6591a..5d476e4cae 100644 --- a/x/auth/ante/ante.go +++ b/x/auth/ante/ante.go @@ -2,15 +2,18 @@ package ante import ( sdk "github.com/line/lbm-sdk/types" + keeper2 "github.com/line/lbm-sdk/x/auth/keeper" "github.com/line/lbm-sdk/x/auth/signing" "github.com/line/lbm-sdk/x/auth/types" + "github.com/line/lbm-sdk/x/feegrant/keeper" + types2 "github.com/line/lbm-sdk/x/feegrant/types" ) // NewAnteHandler returns an AnteHandler that checks and increments sequence // numbers, checks signatures & sig block height, and deducts fees from the first // signer. func NewAnteHandler( - ak AccountKeeper, bankKeeper types.BankKeeper, + ak AccountKeeper, bankKeeper types.BankKeeper, feegrantKeeper keeper.Keeper, sigGasConsumer SignatureVerificationGasConsumer, signModeHandler signing.SignModeHandler, ) sdk.AnteHandler { @@ -23,12 +26,11 @@ func NewAnteHandler( TxTimeoutHeightDecorator{}, NewValidateMemoDecorator(ak), NewConsumeGasForTxSizeDecorator(ak), - NewRejectFeeGranterDecorator(), + NewDeductGrantedFeeDecorator(ak.(keeper2.AccountKeeper), bankKeeper.(types2.BankKeeper), feegrantKeeper), // The above handlers should not call `GetAccount` or `GetSignerAcc` for signer NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators // The handlers below may call `GetAccount` or `GetSignerAcc` for signer NewValidateSigCountDecorator(ak), - NewDeductFeeDecorator(ak, bankKeeper), NewSigGasConsumeDecorator(ak, sigGasConsumer), NewSigVerificationDecorator(ak, signModeHandler), NewIncrementSequenceDecorator(ak), diff --git a/x/auth/ante/ante_test.go b/x/auth/ante/ante_test.go index e1996ea624..65db7afb65 100644 --- a/x/auth/ante/ante_test.go +++ b/x/auth/ante/ante_test.go @@ -130,7 +130,7 @@ func (suite *AnteTestSuite) TestAnteHandlerSigErrors() { }, false, false, - sdkerrors.ErrInsufficientFunds, // unknown account may send tx, but he doesn't have enough balance to pay fee + sdkerrors.ErrUnknownAddress, // unknown account may send tx, but he doesn't have enough balance to pay fee }, { "save the first account, but second is still unrecognized", @@ -1010,7 +1010,8 @@ func (suite *AnteTestSuite) TestCustomSignatureVerificationGasConsumer() { suite.SetupTest(true) // setup // setup an ante handler that only accepts PubKeyEd25519 - suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.BankKeeper, func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error { + suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.FeeGrantKeeper, + func(meter sdk.GasMeter, sig signing.SignatureV2, params types.Params) error { switch pubkey := sig.PubKey.(type) { case *ed25519.PubKey: meter.ConsumeGas(params.SigVerifyCostED25519, "ante verify: ed25519") diff --git a/x/auth/ante/fee.go b/x/auth/ante/fee.go index 157d11c4ae..3ca399f1a0 100644 --- a/x/auth/ante/fee.go +++ b/x/auth/ante/fee.go @@ -63,6 +63,7 @@ type DeductFeeDecorator struct { bankKeeper types.BankKeeper } +// This DeductFeeDecorator is not used anymore. Instead of it, DeductGrantedFeeDecorator will be used. func NewDeductFeeDecorator(ak AccountKeeper, bk types.BankKeeper) DeductFeeDecorator { return DeductFeeDecorator{ ak: ak, diff --git a/x/auth/ante/fee_grant.go b/x/auth/ante/fee_grant.go index 871d31385b..eb99c901a0 100644 --- a/x/auth/ante/fee_grant.go +++ b/x/auth/ante/fee_grant.go @@ -1,8 +1,14 @@ package ante import ( - "github.com/line/lbm-sdk/types" + "fmt" + + sdk "github.com/line/lbm-sdk/types" sdkerrors "github.com/line/lbm-sdk/types/errors" + "github.com/line/lbm-sdk/x/auth/keeper" + types3 "github.com/line/lbm-sdk/x/auth/types" + feegrantkeeper "github.com/line/lbm-sdk/x/feegrant/keeper" + types2 "github.com/line/lbm-sdk/x/feegrant/types" ) // RejectFeeGranterDecorator is an AnteDecorator which rejects transactions which @@ -15,13 +21,83 @@ func NewRejectFeeGranterDecorator() RejectFeeGranterDecorator { return RejectFeeGranterDecorator{} } -var _ types.AnteDecorator = RejectFeeGranterDecorator{} +var _ sdk.AnteDecorator = RejectFeeGranterDecorator{} -func (d RejectFeeGranterDecorator) AnteHandle(ctx types.Context, tx types.Tx, simulate bool, next types.AnteHandler) (newCtx types.Context, err error) { - feeTx, ok := tx.(types.FeeTx) +func (d RejectFeeGranterDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + feeTx, ok := tx.(sdk.FeeTx) if ok && len(feeTx.FeeGranter()) != 0 { return ctx, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee grants are not supported") } return next(ctx, tx, simulate) } + +// DeductGrantedFeeDecorator deducts fees from fee_payer or fee_granter (if exists a valid fee allowance) of the tx +// If the fee_payer or fee_granter does not have the funds to pay for the fees, return with InsufficientFunds error +// Call next AnteHandler if fees successfully deducted +// CONTRACT: Tx must implement GrantedFeeTx interface to use DeductGrantedFeeDecorator +type DeductGrantedFeeDecorator struct { + ak keeper.AccountKeeper + k feegrantkeeper.Keeper + bk types2.BankKeeper +} + +func NewDeductGrantedFeeDecorator(ak keeper.AccountKeeper, bk types2.BankKeeper, k feegrantkeeper.Keeper) DeductGrantedFeeDecorator { + return DeductGrantedFeeDecorator{ + ak: ak, + k: k, + bk: bk, + } +} + +// AnteHandle performs a decorated ante-handler responsible for deducting transaction +// fees. Fees will be deducted from the account designated by the FeePayer on a +// transaction by default. However, if the fee payer differs from the transaction +// signer, the handler will check if a fee grant has been authorized. If the +// transaction's signer does not exist, it will be created. +func (d DeductGrantedFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) { + feeTx, ok := tx.(sdk.FeeTx) + if !ok { + return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a GrantedFeeTx") + } + + // sanity check from DeductFeeDecorator + if addr := d.ak.GetModuleAddress(types3.FeeCollectorName); addr == "" { + panic(fmt.Sprintf("%s module account has not been set", types3.FeeCollectorName)) + } + + fee := feeTx.GetFee() + feePayer := feeTx.FeePayer() + feeGranter := feeTx.FeeGranter() + + deductFeesFrom := feePayer + + // ensure the grant is allowed, if we request a different fee payer + if feeGranter != "" && !feeGranter.Equals(feePayer) { + err := d.k.UseGrantedFees(ctx, feeGranter, feePayer, fee, tx.GetMsgs()) + if err != nil { + return ctx, sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", feeGranter, feePayer) + } + + deductFeesFrom = feeGranter + } + + // now, either way, we know that we are authorized to deduct the fees from the deductFeesFrom account + deductFeesFromAcc := d.ak.GetAccount(ctx, deductFeesFrom) + if deductFeesFromAcc == nil { + return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", deductFeesFrom) + } + + // move on if there is no fee to deduct + if fee.IsZero() { + return next(ctx, tx, simulate) + } + + // deduct fee if non-zero + err = DeductFees(d.bk, ctx, deductFeesFromAcc, fee) + if err != nil { + return ctx, err + } + + return next(ctx, tx, simulate) +} diff --git a/x/auth/ante/sigverify_test.go b/x/auth/ante/sigverify_test.go index 007acfd69b..8340b7e55c 100644 --- a/x/auth/ante/sigverify_test.go +++ b/x/auth/ante/sigverify_test.go @@ -199,7 +199,8 @@ func (suite *AnteTestSuite) TestSigVerification_ExplicitAmino() { suite.clientCtx = client.Context{}. WithTxConfig(txConfig) - suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.BankKeeper, ante.DefaultSigVerificationGasConsumer, txConfig.SignModeHandler()) + suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.FeeGrantKeeper, + ante.DefaultSigVerificationGasConsumer, txConfig.SignModeHandler()) suite.txBuilder = suite.clientCtx.TxConfig.NewTxBuilder() diff --git a/x/auth/ante/testutil_test.go b/x/auth/ante/testutil_test.go index 827b7eff1f..951f881164 100644 --- a/x/auth/ante/testutil_test.go +++ b/x/auth/ante/testutil_test.go @@ -61,7 +61,8 @@ func (suite *AnteTestSuite) SetupTest(isCheckTx bool) { suite.clientCtx = client.Context{}. WithTxConfig(encodingConfig.TxConfig) - suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.BankKeeper, ante.DefaultSigVerificationGasConsumer, encodingConfig.TxConfig.SignModeHandler()) + suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.FeeGrantKeeper, + ante.DefaultSigVerificationGasConsumer, encodingConfig.TxConfig.SignModeHandler()) } // CreateTestAccounts creates `numAccs` accounts, and return all relevant diff --git a/x/auth/legacy/legacytx/stdtx_builder.go b/x/auth/legacy/legacytx/stdtx_builder.go index 95ece75288..09f8e73231 100644 --- a/x/auth/legacy/legacytx/stdtx_builder.go +++ b/x/auth/legacy/legacytx/stdtx_builder.go @@ -88,6 +88,9 @@ func (s *StdTxBuilder) SetTimeoutHeight(height uint64) { s.TimeoutHeight = height } +// SetFeeGranter does nothing for stdtx +func (s *StdTxBuilder) SetFeeGranter(_ sdk.AccAddress) {} + // StdTxConfig is a context.TxConfig for StdTx type StdTxConfig struct { Cdc *codec.LegacyAmino diff --git a/x/bank/testutil/test_helpers.go b/x/bank/testutil/test_helpers.go new file mode 100644 index 0000000000..418a777948 --- /dev/null +++ b/x/bank/testutil/test_helpers.go @@ -0,0 +1,35 @@ +package testutil + +import ( + sdk "github.com/line/lbm-sdk/types" + bankkeeper "github.com/line/lbm-sdk/x/bank/keeper" + minttypes "github.com/line/lbm-sdk/x/mint/types" +) + +// FundAccount is a utility function that funds an account by minting and +// sending the coins to the address. This should be used for testing purposes +// only! +// +// TODO: Instead of using the mint module account, which has the +// permission of minting, create a "faucet" account. (@fdymylja) +func FundAccount(bankKeeper bankkeeper.Keeper, ctx sdk.Context, addr sdk.AccAddress, amounts sdk.Coins) error { + if err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, amounts); err != nil { + return err + } + + return bankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, addr, amounts) +} + +// FundModuleAccount is a utility function that funds a module account by +// minting and sending the coins to the address. This should be used for testing +// purposes only! +// +// TODO: Instead of using the mint module account, which has the +// permission of minting, create a "faucet" account. (@fdymylja) +func FundModuleAccount(bankKeeper bankkeeper.Keeper, ctx sdk.Context, recipientMod string, amounts sdk.Coins) error { + if err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, amounts); err != nil { + return err + } + + return bankKeeper.SendCoinsFromModuleToModule(ctx, minttypes.ModuleName, recipientMod, amounts) +} diff --git a/x/feegrant/client/cli/query.go b/x/feegrant/client/cli/query.go new file mode 100644 index 0000000000..be45087acb --- /dev/null +++ b/x/feegrant/client/cli/query.go @@ -0,0 +1,133 @@ +package cli + +import ( + "fmt" + "strings" + + "github.com/line/lbm-sdk/x/feegrant/types" + "github.com/spf13/cobra" + + "github.com/line/lbm-sdk/client" + "github.com/line/lbm-sdk/client/flags" + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/version" +) + +// GetQueryCmd returns the cli query commands for this module +func GetQueryCmd() *cobra.Command { + feegrantQueryCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Querying commands for the feegrant module", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + feegrantQueryCmd.AddCommand( + GetCmdQueryFeeGrant(), + GetCmdQueryFeeGrants(), + ) + + return feegrantQueryCmd +} + +// GetCmdQueryFeeGrant returns cmd to query for a grant between granter and grantee. +func GetCmdQueryFeeGrant() *cobra.Command { + cmd := &cobra.Command{ + Use: "grant [granter] [grantee]", + Args: cobra.ExactArgs(2), + Short: "Query details of a single grant", + Long: strings.TrimSpace( + fmt.Sprintf(`Query details for a grant. +You can find the fee-grant of a granter and grantee. + +Example: +$ %s query feegrant grant [granter] [grantee] +`, version.AppName), + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + queryClient := types.NewQueryClient(clientCtx) + + err := sdk.ValidateAccAddress(args[0]) + if err != nil { + return err + } + granterAddr := sdk.AccAddress(args[0]) + + err = sdk.ValidateAccAddress(args[1]) + if err != nil { + return err + } + granteeAddr := sdk.AccAddress(args[1]) + + res, err := queryClient.Allowance( + cmd.Context(), + &types.QueryAllowanceRequest{ + Granter: granterAddr.String(), + Grantee: granteeAddr.String(), + }, + ) + + if err != nil { + return err + } + + return clientCtx.PrintProto(res.Allowance) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + + return cmd +} + +// GetCmdQueryFeeGrants returns cmd to query for all grants for a grantee. +func GetCmdQueryFeeGrants() *cobra.Command { + cmd := &cobra.Command{ + Use: "grants [grantee]", + Args: cobra.ExactArgs(1), + Short: "Query all grants of a grantee", + Long: strings.TrimSpace( + fmt.Sprintf(`Queries all the grants for a grantee address. + +Example: +$ %s query feegrant grants [grantee] +`, version.AppName), + ), + RunE: func(cmd *cobra.Command, args []string) error { + clientCtx := client.GetClientContextFromCmd(cmd) + queryClient := types.NewQueryClient(clientCtx) + + err := sdk.ValidateAccAddress(args[0]) + if err != nil { + return err + } + granteeAddr := sdk.AccAddress(args[0]) + + pageReq, err := client.ReadPageRequest(cmd.Flags()) + if err != nil { + return err + } + + res, err := queryClient.Allowances( + cmd.Context(), + &types.QueryAllowancesRequest{ + Grantee: granteeAddr.String(), + Pagination: pageReq, + }, + ) + + if err != nil { + return err + } + + return clientCtx.PrintProto(res) + }, + } + + flags.AddQueryFlagsToCmd(cmd) + flags.AddPaginationFlagsToCmd(cmd, "grants") + + return cmd +} diff --git a/x/feegrant/client/cli/tx.go b/x/feegrant/client/cli/tx.go new file mode 100644 index 0000000000..f028d9dff0 --- /dev/null +++ b/x/feegrant/client/cli/tx.go @@ -0,0 +1,227 @@ +package cli + +import ( + "fmt" + "strings" + "time" + + "github.com/line/lbm-sdk/x/feegrant/types" + "github.com/spf13/cobra" + + "github.com/line/lbm-sdk/client" + "github.com/line/lbm-sdk/client/flags" + "github.com/line/lbm-sdk/client/tx" + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/version" +) + +// flag for feegrant module +const ( + FlagExpiration = "expiration" + FlagPeriod = "period" + FlagPeriodLimit = "period-limit" + FlagSpendLimit = "spend-limit" + FlagAllowedMsgs = "allowed-messages" +) + +// GetTxCmd returns the transaction commands for this module +func GetTxCmd() *cobra.Command { + feegrantTxCmd := &cobra.Command{ + Use: types.ModuleName, + Short: "Feegrant transactions subcommands", + Long: "Grant and revoke fee allowance for a grantee by a granter", + DisableFlagParsing: true, + SuggestionsMinimumDistance: 2, + RunE: client.ValidateCmd, + } + + feegrantTxCmd.AddCommand( + NewCmdFeeGrant(), + NewCmdRevokeFeegrant(), + ) + + return feegrantTxCmd +} + +// NewCmdFeeGrant returns a CLI command handler for creating a MsgGrantAllowance transaction. +func NewCmdFeeGrant() *cobra.Command { + cmd := &cobra.Command{ + Use: "grant [granter_key_or_address] [grantee]", + Short: "Grant Fee allowance to an address", + Long: strings.TrimSpace( + fmt.Sprintf( + `Grant authorization to pay fees from your address. Note, the'--from' flag is + ignored as it is implied from [granter]. + +Examples: +%s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --expiration 2022-01-30T15:04:05Z or +%s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --period 3600 --period-limit 10stake --expiration 36000 or +%s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --expiration 2022-01-30T15:04:05Z + --allowed-messages "/cosmos.gov.v1beta1.MsgSubmitProposal,/cosmos.gov.v1beta1.MsgVote" + `, version.AppName, types.ModuleName, version.AppName, types.ModuleName, version.AppName, types.ModuleName, + ), + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + + cmd.Flags().Set(flags.FlagFrom, args[0]) + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + err = sdk.ValidateAccAddress(args[1]) + if err != nil { + return err + } + grantee := sdk.AccAddress(args[1]) + + granter := clientCtx.GetFromAddress() + sl, err := cmd.Flags().GetString(FlagSpendLimit) + if err != nil { + return err + } + + // if `FlagSpendLimit` isn't set, limit will be nil + limit, err := sdk.ParseCoinsNormalized(sl) + if err != nil { + return err + } + + exp, err := cmd.Flags().GetString(FlagExpiration) + if err != nil { + return err + } + + basic := types.BasicAllowance{ + SpendLimit: limit, + } + + var expiresAtTime time.Time + if exp != "" { + expiresAtTime, err = time.Parse(time.RFC3339, exp) + if err != nil { + return err + } + basic.Expiration = &expiresAtTime + } + + var grant types.FeeAllowanceI + grant = &basic + + periodClock, err := cmd.Flags().GetInt64(FlagPeriod) + if err != nil { + return err + } + + periodLimitVal, err := cmd.Flags().GetString(FlagPeriodLimit) + if err != nil { + return err + } + + // Check any of period or periodLimit flags set, If set consider it as periodic fee allowance. + if periodClock > 0 || periodLimitVal != "" { + periodLimit, err := sdk.ParseCoinsNormalized(periodLimitVal) + if err != nil { + return err + } + + if periodClock <= 0 { + return fmt.Errorf("period clock was not set") + } + + if periodLimit == nil { + return fmt.Errorf("period limit was not set") + } + + 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: getPeriod(periodClock), + PeriodReset: getPeriodReset(periodClock), + PeriodSpendLimit: periodLimit, + PeriodCanSpend: periodLimit, + } + + grant = &periodic + } + + allowedMsgs, err := cmd.Flags().GetStringSlice(FlagAllowedMsgs) + if err != nil { + return err + } + + if len(allowedMsgs) > 0 { + grant, err = types.NewAllowedMsgAllowance(grant, allowedMsgs) + if err != nil { + return err + } + } + + msg, err := types.NewMsgGrantAllowance(grant, granter, grantee) + if err != nil { + return err + } + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), msg) + }, + } + + flags.AddTxFlagsToCmd(cmd) + cmd.Flags().StringSlice(FlagAllowedMsgs, []string{}, "Set of allowed messages for fee allowance") + cmd.Flags().String(FlagExpiration, "", "The RFC 3339 timestamp after which the grant expires for the user") + cmd.Flags().String(FlagSpendLimit, "", "Spend limit specifies the max limit can be used, if not mentioned there is no limit") + cmd.Flags().Int64(FlagPeriod, 0, "period specifies the time duration in which period_spend_limit coins can be spent before that allowance is reset") + cmd.Flags().String(FlagPeriodLimit, "", "period limit specifies the maximum number of coins that can be spent in the period") + + return cmd +} + +// NewCmdRevokeFeegrant returns a CLI command handler for creating a MsgRevokeAllowance transaction. +func NewCmdRevokeFeegrant() *cobra.Command { + cmd := &cobra.Command{ + Use: "revoke [granter] [grantee]", + Short: "revoke fee-grant", + Long: strings.TrimSpace( + fmt.Sprintf(`revoke fee grant from a granter to a grantee. Note, the'--from' flag is + ignored as it is implied from [granter]. + +Example: + $ %s tx %s revoke cosmos1skj.. cosmos1skj.. + `, version.AppName, types.ModuleName), + ), + Args: cobra.ExactArgs(2), + RunE: func(cmd *cobra.Command, args []string) error { + cmd.Flags().Set(flags.FlagFrom, args[0]) + clientCtx, err := client.GetClientTxContext(cmd) + if err != nil { + return err + } + + err = sdk.ValidateAccAddress(args[1]) + if err != nil { + return err + } + grantee := sdk.AccAddress(args[1]) + + msg := types.NewMsgRevokeAllowance(clientCtx.GetFromAddress(), grantee) + + return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), &msg) + }, + } + + 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 +} diff --git a/x/feegrant/client/testutil/cli_test.go b/x/feegrant/client/testutil/cli_test.go new file mode 100644 index 0000000000..a4cc047579 --- /dev/null +++ b/x/feegrant/client/testutil/cli_test.go @@ -0,0 +1,17 @@ +// +build norace + +package testutil + +import ( + "testing" + + "github.com/line/lbm-sdk/testutil/network" + + "github.com/stretchr/testify/suite" +) + +func TestIntegrationTestSuite(t *testing.T) { + cfg := network.DefaultConfig() + cfg.NumValidators = 3 + suite.Run(t, NewIntegrationTestSuite(cfg)) +} diff --git a/x/feegrant/client/testutil/suite.go b/x/feegrant/client/testutil/suite.go new file mode 100644 index 0000000000..2be955ef26 --- /dev/null +++ b/x/feegrant/client/testutil/suite.go @@ -0,0 +1,887 @@ +package testutil + +import ( + "fmt" + "strings" + "testing" + "time" + + "github.com/gogo/protobuf/proto" + "github.com/line/lbm-sdk/x/feegrant/types" + tmcli "github.com/line/ostracon/libs/cli" + "github.com/stretchr/testify/suite" + + "github.com/line/lbm-sdk/client" + "github.com/line/lbm-sdk/client/flags" + "github.com/line/lbm-sdk/crypto/hd" + "github.com/line/lbm-sdk/crypto/keyring" + "github.com/line/lbm-sdk/testutil" + clitestutil "github.com/line/lbm-sdk/testutil/cli" + "github.com/line/lbm-sdk/testutil/network" + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/x/feegrant/client/cli" + govtestutil "github.com/line/lbm-sdk/x/gov/client/testutil" + govtypes "github.com/line/lbm-sdk/x/gov/types" +) + +const ( + oneYear = 365 * 24 * 60 * 60 + tenHours = 10 * 60 * 60 + oneHour = 60 * 60 +) + +type IntegrationTestSuite struct { + suite.Suite + + cfg network.Config + network *network.Network + addedGranter sdk.AccAddress + addedGrantee sdk.AccAddress + addedGrant types.Grant +} + +func NewIntegrationTestSuite(cfg network.Config) *IntegrationTestSuite { + return &IntegrationTestSuite{cfg: cfg} +} + +func (s *IntegrationTestSuite) SetupSuite() { + s.T().Log("setting up integration test suite") + + if testing.Short() { + s.T().Skip("skipping test in unit-tests mode.") + } + + var err error + s.network = network.New(s.T(), s.cfg) + + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) + + val := s.network.Validators[0] + granter := val.Address + grantee := s.network.Validators[1].Address + + s.createGrant(granter, grantee) + + grant, err := types.NewGrant(granter, grantee, &types.BasicAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(100))), + }) + s.Require().NoError(err) + + s.addedGrant = grant + s.addedGranter = granter + s.addedGrantee = grantee +} + +// createGrant creates a new basic allowance fee grant from granter to grantee. +func (s *IntegrationTestSuite) createGrant(granter, grantee sdk.Address) { + val := s.network.Validators[0] + + clientCtx := val.ClientCtx + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + fee := sdk.NewCoin("stake", sdk.NewInt(100)) + + args := append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, fee.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneYear)), + }, + commonFlags..., + ) + + cmd := cli.NewCmdFeeGrant() + + _, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) +} + +func (s *IntegrationTestSuite) TearDownSuite() { + s.T().Log("tearing down integration test suite") + s.network.Cleanup() +} + +func (s *IntegrationTestSuite) TestCmdGetFeeGrant() { + val := s.network.Validators[0] + granter := val.Address + grantee := s.addedGrantee + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErrMsg string + expectErr bool + respType *types.Grant + resp *types.Grant + }{ + { + "wrong granter", + []string{ + "wrong_granter", + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + "decoding bech32 failed", + true, nil, nil, + }, + { + "wrong grantee", + []string{ + granter.String(), + "wrong_grantee", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + "decoding bech32 failed", + true, nil, nil, + }, + { + "non existed grant", + []string{ + "link19lrl5da53xtd2yssw2799y53uyaskadqkzv0ky", + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + "fee-grant not found", + true, nil, nil, + }, + { + "valid req", + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + "", + false, + &types.Grant{}, + &s.addedGrant, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + s.Require().Contains(err.Error(), tc.expectErrMsg) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + s.Require().Equal(tc.respType.Grantee, tc.respType.Grantee) + s.Require().Equal(tc.respType.Granter, tc.respType.Granter) + grant, err := tc.respType.GetGrant() + s.Require().NoError(err) + grant1, err1 := tc.resp.GetGrant() + s.Require().NoError(err1) + s.Require().Equal( + grant.(*types.BasicAllowance).SpendLimit, + grant1.(*types.BasicAllowance).SpendLimit, + ) + } + }) + } +} + +func (s *IntegrationTestSuite) TestCmdGetFeeGrants() { + val := s.network.Validators[0] + grantee := s.addedGrantee + clientCtx := val.ClientCtx + + testCases := []struct { + name string + args []string + expectErr bool + resp *types.QueryAllowancesResponse + expectLength int + }{ + { + "wrong grantee", + []string{ + "wrong_grantee", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + true, nil, 0, + }, + { + "non existed grantee", + []string{ + "link19lrl5da53xtd2yssw2799y53uyaskadqkzv0ky", + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, &types.QueryAllowancesResponse{}, 0, + }, + { + "valid req", + []string{ + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, &types.QueryAllowancesResponse{}, 1, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.GetCmdQueryFeeGrants() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.resp), out.String()) + s.Require().Len(tc.resp.Allowances, tc.expectLength) + } + }) + } +} + +func (s *IntegrationTestSuite) TestNewCmdFeeGrant() { + val := s.network.Validators[0] + granter := val.Address + alreadyExistedGrantee := s.addedGrantee + clientCtx := val.ClientCtx + + fromAddr, fromName, _, err := client.GetFromFields(clientCtx.Keyring, granter.String(), clientCtx.GenerateOnly) + s.Require().Equal(fromAddr, granter) + s.Require().NoError(err) + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + testCases := []struct { + name string + args []string + expectErr bool + expectedCode uint32 + respType proto.Message + }{ + { + "wrong granter address", + append( + []string{ + "wrong_granter", + "link19lrl5da53xtd2yssw2799y53uyaskadqkzv0ky", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "wrong grantee address", + append( + []string{ + granter.String(), + "wrong_grantee", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "wrong granter key name", + append( + []string{ + "invalid_granter", + "link1rsx5wakg9kxfa05ueqjpkher9yddy3u4n47f9z", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "valid basic fee grant", + append( + []string{ + granter.String(), + "link19lrl5da53xtd2yssw2799y53uyaskadqkzv0ky", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant with granter key name", + append( + []string{ + fromName, + "link1rsx5wakg9kxfa05ueqjpkher9yddy3u4n47f9z", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, fromName), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant with amino", + append( + []string{ + granter.String(), + "link1yhxv9jv8kwtud80azf674l5yddv2pecfzgxmrw", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant without spend limit", + append( + []string{ + granter.String(), + "link1kgj5mx00euxeqjhu5uh0l4lwnt99aadvrzxl6t", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant without expiration", + append( + []string{ + granter.String(), + "link1y4hn2txq7eghuydjrmwvc6sqrr7z9aa3m3zjv2", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid basic fee grant without spend-limit and expiration", + append( + []string{ + granter.String(), + "link1mp668xdzzdulrc40wwasqqflmagxfyc29zgvav", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "try to add existed grant", + append( + []string{ + granter.String(), + alreadyExistedGrantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 18, &sdk.TxResponse{}, + }, + { + "invalid number of args(periodic fee grant)", + append( + []string{ + granter.String(), + "link19lrl5da53xtd2yssw2799y53uyaskadqkzv0ky", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(tenHours)), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "period mentioned and period limit omitted, invalid periodic grant", + append( + []string{ + granter.String(), + "link19lrl5da53xtd2yssw2799y53uyaskadqkzv0ky", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, tenHours), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneHour)), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "period cannot be greater than the actual expiration(periodic fee grant)", + append( + []string{ + granter.String(), + "link19lrl5da53xtd2yssw2799y53uyaskadqkzv0ky", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, tenHours), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneHour)), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "valid periodic fee grant", + append( + []string{ + granter.String(), + "link1xr0fqfdcen5ayqtch9n5j6rxfvjuul37mev25v", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(tenHours)), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid periodic fee grant without spend-limit", + append( + []string{ + granter.String(), + "link1xjqjff3zde5le2t0u26gutnq3kag76l4jsjaqq", + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(tenHours)), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid periodic fee grant without expiration", + append( + []string{ + granter.String(), + "link1j583lutp3vz6z43j62wgcjxzfuch0ucmxgepac", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "valid periodic fee grant without spend-limit and expiration", + append( + []string{ + granter.String(), + "link1dzthj3umpcnapfydrkt2lcv5nxgjk9kkndrfz5", + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "invalid expiration", + append( + []string{ + granter.String(), + "link1xjqjff3zde5le2t0u26gutnq3kag76l4jsjaqq", + fmt.Sprintf("--%s=%d", cli.FlagPeriod, oneHour), + fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, "invalid"), + }, + commonFlags..., + ), + true, 0, nil, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.NewCmdFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestNewCmdRevokeFeegrant() { + val := s.network.Validators[0] + granter := s.addedGranter + grantee := s.addedGrantee + clientCtx := val.ClientCtx + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + // Create new fee grant specifically to test amino. + aminoGrantee := sdk.AccAddress("link1zp4lzwuwzvhq7xhe8xj688vv00dxv2zyue4xuj") + s.createGrant(granter, aminoGrantee) + + testCases := []struct { + name string + args []string + expectErr bool + expectedCode uint32 + respType proto.Message + }{ + { + "invalid grantee", + append( + []string{ + "wrong_granter", + grantee.String(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "invalid grantee", + append( + []string{ + granter.String(), + "wrong_grantee", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, 0, nil, + }, + { + "Non existed grant", + append( + []string{ + granter.String(), + "link1qwvn8n0cs9lq4q46qjummvfkqah3paxx37etxm", + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 4, &sdk.TxResponse{}, + }, + { + "Valid revoke", + append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + { + "Valid revoke with amino", + append( + []string{ + granter.String(), + aminoGrantee.String(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", flags.FlagSignMode, flags.SignModeLegacyAminoJSON), + }, + commonFlags..., + ), + false, 0, &sdk.TxResponse{}, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.NewCmdRevokeFeegrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } +} + +func (s *IntegrationTestSuite) TestTxWithFeeGrant() { + val := s.network.Validators[0] + clientCtx := val.ClientCtx + granter := val.Address + + // creating an account manually (This account won't be exist in state) + k, _, err := val.ClientCtx.Keyring.NewMnemonic("grantee", keyring.English, sdk.FullFundraiserPath, hd.Secp256k1) + s.Require().NoError(err) + pub := k.GetPubKey() + grantee := sdk.BytesToAccAddress(pub.Address()) + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + + fee := sdk.NewCoin("stake", sdk.NewInt(100)) + + args := append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, fee.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + fmt.Sprintf("--%s=%s", cli.FlagExpiration, getFormattedExpiration(oneYear)), + }, + commonFlags..., + ) + + cmd := cli.NewCmdFeeGrant() + + _, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + _, err = s.network.WaitForHeight(1) + s.Require().NoError(err) + + // granted fee allowance for an account which is not in state and creating + // any tx with it by using --fee-account shouldn't fail + out, err := govtestutil.MsgSubmitProposal(val.ClientCtx, grantee.String(), + "Text Proposal", "No desc", govtypes.ProposalTypeText, + fmt.Sprintf("--%s=%s", flags.FlagFeeAccount, granter.String()), + ) + + s.Require().NoError(err) + var resp sdk.TxResponse + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &resp), out.String()) + s.Require().Equal(uint32(0), resp.Code) +} + +func (s *IntegrationTestSuite) TestFilteredFeeAllowance() { + val := s.network.Validators[0] + + granter := val.Address + k, _, err := val.ClientCtx.Keyring.NewMnemonic("grantee1", keyring.English, sdk.FullFundraiserPath, hd.Secp256k1) + s.Require().NoError(err) + pub := k.GetPubKey() + grantee := sdk.BytesToAccAddress(pub.Address()) + + clientCtx := val.ClientCtx + + commonFlags := []string{ + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + } + spendLimit := sdk.NewCoin("stake", sdk.NewInt(1000)) + + allowMsgs := strings.Join([]string{sdk.MsgTypeURL(&govtypes.MsgSubmitProposal{}), sdk.MsgTypeURL(&govtypes.MsgVoteWeighted{})}, ",") + + testCases := []struct { + name string + args []string + expectErr bool + respType proto.Message + expectedCode uint32 + }{ + { + "invalid granter address", + append( + []string{ + "not an address", + "link19lrl5da53xtd2yssw2799y53uyaskadqkzv0ky", + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, &sdk.TxResponse{}, 0, + }, + { + "invalid grantee address", + append( + []string{ + granter.String(), + "not an address", + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + true, &sdk.TxResponse{}, 0, + }, + { + "valid filter fee grant", + append( + []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=%s", cli.FlagAllowedMsgs, allowMsgs), + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, spendLimit.String()), + fmt.Sprintf("--%s=%s", flags.FlagFrom, granter), + }, + commonFlags..., + ), + false, &sdk.TxResponse{}, 0, + }, + } + + for _, tc := range testCases { + tc := tc + + s.Run(tc.name, func() { + cmd := cli.NewCmdFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args) + + if tc.expectErr { + s.Require().Error(err) + } else { + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + } + }) + } + + args := []string{ + granter.String(), + grantee.String(), + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + } + + // get filtered fee allowance and check info + cmd := cli.GetCmdQueryFeeGrant() + out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + s.Require().NoError(err) + + resp := &types.Grant{} + + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), resp), out.String()) + s.Require().Equal(resp.Grantee, resp.Grantee) + s.Require().Equal(resp.Granter, resp.Granter) + + grant, err := resp.GetGrant() + s.Require().NoError(err) + + filteredFeeGrant, err := grant.(*types.AllowedMsgAllowance).GetAllowance() + s.Require().NoError(err) + + s.Require().Equal( + filteredFeeGrant.(*types.BasicAllowance).SpendLimit.String(), + spendLimit.String(), + ) + + // exec filtered fee allowance + cases := []struct { + name string + malleate func() (testutil.BufferWriter, error) + respType proto.Message + expectedCode uint32 + }{ + { + "valid proposal tx", + func() (testutil.BufferWriter, error) { + return govtestutil.MsgSubmitProposal(val.ClientCtx, grantee.String(), + "Text Proposal", "No desc", govtypes.ProposalTypeText, + fmt.Sprintf("--%s=%s", flags.FlagFeeAccount, granter.String()), + ) + }, + &sdk.TxResponse{}, + 0, + }, + { + "valid weighted_vote tx", + func() (testutil.BufferWriter, error) { + return govtestutil.MsgVote(val.ClientCtx, grantee.String(), "0", "yes", + fmt.Sprintf("--%s=%s", flags.FlagFeeAccount, granter.String()), + ) + }, + &sdk.TxResponse{}, + 2, + }, + { + "should fail with unauthorized msgs", + func() (testutil.BufferWriter, error) { + args := append( + []string{ + grantee.String(), + "link1j583lutp3vz6z43j62wgcjxzfuch0ucmxgepac", + fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"), + fmt.Sprintf("--%s=%s", flags.FlagFeeAccount, granter), + }, + commonFlags..., + ) + cmd := cli.NewCmdFeeGrant() + return clitestutil.ExecTestCLICmd(clientCtx, cmd, args) + }, + &sdk.TxResponse{}, + 7, + }, + } + + for _, tc := range cases { + tc := tc + + s.Run(tc.name, func() { + out, err := tc.malleate() + s.Require().NoError(err) + s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String()) + txResp := tc.respType.(*sdk.TxResponse) + s.Require().Equal(tc.expectedCode, txResp.Code, out.String()) + }) + } +} + +func getFormattedExpiration(duration int64) string { + return time.Now().Add(time.Duration(duration) * time.Second).Format(time.RFC3339) +} diff --git a/x/feegrant/doc.go b/x/feegrant/doc.go new file mode 100644 index 0000000000..514d079c07 --- /dev/null +++ b/x/feegrant/doc.go @@ -0,0 +1,23 @@ +/* +Package feegrant provides functionality for authorizing the payment of transaction +fees from one account (key) to another account (key). + +Effectively, this allows for a user to pay fees using the balance of an account +different from their own. Example use cases would be allowing a key on a device to +pay for fees using a master wallet, or a third party service allowing users to +pay for transactions without ever really holding their own tokens. This package +provides ways for specifying fee allowances such that authorizing fee payment to +another account can be done with clear and safe restrictions. + +A user would authorize granting fee payment to another user using +MsgGrantAllowance and revoke that delegation using MsgRevokeAllowance. +In both cases, Granter is the one who is authorizing fee payment and Grantee is +the one who is receiving the fee payment authorization. So grantee would correspond +to the one who is signing a transaction and the granter would be the address that +pays the fees. + +The fee allowance that a grantee receives is specified by an implementation of +the FeeAllowance interface. Two FeeAllowance implementations are provided in +this package: BasicAllowance and PeriodicAllowance. +*/ +package feegrant diff --git a/x/feegrant/handler.go b/x/feegrant/handler.go new file mode 100644 index 0000000000..efa9de035e --- /dev/null +++ b/x/feegrant/handler.go @@ -0,0 +1,30 @@ +package feegrant + +import ( + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/types/errors" + "github.com/line/lbm-sdk/x/feegrant/keeper" + "github.com/line/lbm-sdk/x/feegrant/types" +) + +// NewHandler creates an sdk.Handler for all the gov type messages +func NewHandler(k keeper.Keeper) sdk.Handler { + msgServer := keeper.NewMsgServerImpl(k) + + return func(ctx sdk.Context, msg sdk.Msg) (*sdk.Result, error) { + ctx = ctx.WithEventManager(sdk.NewEventManager()) + + switch msg := msg.(type) { + case *types.MsgGrantAllowance: + res, err := msgServer.GrantAllowance(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) + + case *types.MsgRevokeAllowance: + res, err := msgServer.RevokeAllowance(sdk.WrapSDKContext(ctx), msg) + return sdk.WrapServiceResult(ctx, res, err) + + default: + return nil, errors.Wrapf(errors.ErrUnknownRequest, "unrecognized %s message type: %T", types.ModuleName, msg) + } + } +} diff --git a/x/feegrant/keeper/genesis_test.go b/x/feegrant/keeper/genesis_test.go new file mode 100644 index 0000000000..705c1b2696 --- /dev/null +++ b/x/feegrant/keeper/genesis_test.go @@ -0,0 +1,113 @@ +package keeper_test + +import ( + "testing" + + "github.com/line/lbm-sdk/x/feegrant/types" + tmproto "github.com/line/ostracon/proto/ostracon/types" + "github.com/stretchr/testify/suite" + + codectypes "github.com/line/lbm-sdk/codec/types" + "github.com/line/lbm-sdk/crypto/keys/secp256k1" + "github.com/line/lbm-sdk/simapp" + "github.com/line/lbm-sdk/testutil/testdata" + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/x/feegrant/keeper" +) + +type GenesisTestSuite struct { + suite.Suite + ctx sdk.Context + keeper keeper.Keeper +} + +func (suite *GenesisTestSuite) SetupTest() { + checkTx := false + app := simapp.Setup(checkTx) + suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1}) + suite.keeper = app.FeeGrantKeeper +} + +var ( + granteePub = secp256k1.GenPrivKey().PubKey() + granterPub = secp256k1.GenPrivKey().PubKey() + granteeAddr = sdk.BytesToAccAddress(granteePub.Address()) + granterAddr = sdk.BytesToAccAddress(granterPub.Address()) +) + +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: &oneYear} + err := suite.keeper.GrantAllowance(suite.ctx, granterAddr, granteeAddr, allowance) + suite.Require().NoError(err) + + genesis, err := suite.keeper.ExportGenesis(suite.ctx) + suite.Require().NoError(err) + // revoke fee allowance + _, err = msgSrvr.RevokeAllowance(sdk.WrapSDKContext(suite.ctx), &types.MsgRevokeAllowance{ + Granter: granterAddr.String(), + Grantee: granteeAddr.String(), + }) + suite.Require().NoError(err) + err = suite.keeper.InitGenesis(suite.ctx, genesis) + suite.Require().NoError(err) + + newGenesis, err := suite.keeper.ExportGenesis(suite.ctx) + suite.Require().NoError(err) + suite.Require().Equal(genesis, newGenesis) +} + +func (suite *GenesisTestSuite) TestInitGenesis() { + any, err := codectypes.NewAnyWithValue(&testdata.Dog{}) + suite.Require().NoError(err) + + testCases := []struct { + name string + feeAllowances []types.Grant + }{ + { + "invalid granter", + []types.Grant{ + { + Granter: "invalid granter", + Grantee: granteeAddr.String(), + }, + }, + }, + { + "invalid grantee", + []types.Grant{ + { + Granter: granterAddr.String(), + Grantee: "invalid grantee", + }, + }, + }, + { + "invalid allowance", + []types.Grant{ + { + Granter: granterAddr.String(), + Grantee: granteeAddr.String(), + Allowance: any, + }, + }, + }, + } + + for _, tc := range testCases { + tc := tc + suite.Run(tc.name, func() { + err := suite.keeper.InitGenesis(suite.ctx, &types.GenesisState{Allowances: tc.feeAllowances}) + suite.Require().Error(err) + }) + } +} + +func TestGenesisTestSuite(t *testing.T) { + suite.Run(t, new(GenesisTestSuite)) +} diff --git a/x/feegrant/keeper/grpc_query.go b/x/feegrant/keeper/grpc_query.go new file mode 100644 index 0000000000..3f0fd74f0e --- /dev/null +++ b/x/feegrant/keeper/grpc_query.go @@ -0,0 +1,89 @@ +package keeper + +import ( + "context" + + "github.com/gogo/protobuf/proto" + "github.com/line/lbm-sdk/x/feegrant/types" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/status" + + codectypes "github.com/line/lbm-sdk/codec/types" + "github.com/line/lbm-sdk/store/prefix" + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/types/query" +) + +var _ types.QueryServer = Keeper{} + +// Allowance returns fee granted to the grantee by the granter. +func (q Keeper) Allowance(c context.Context, req *types.QueryAllowanceRequest) (*types.QueryAllowanceResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + granterAddr := sdk.AccAddress(req.Granter) + granteeAddr := sdk.AccAddress(req.Grantee) + + ctx := sdk.UnwrapSDKContext(c) + + feeAllowance, err := q.GetAllowance(ctx, granterAddr, granteeAddr) + if err != nil { + return nil, status.Errorf(codes.Internal, err.Error()) + } + + msg, ok := feeAllowance.(proto.Message) + if !ok { + return nil, status.Errorf(codes.Internal, "can't proto marshal %T", msg) + } + + feeAllowanceAny, err := codectypes.NewAnyWithValue(msg) + if err != nil { + return nil, status.Errorf(codes.Internal, err.Error()) + } + + return &types.QueryAllowanceResponse{ + Allowance: &types.Grant{ + Granter: granterAddr.String(), + Grantee: granteeAddr.String(), + Allowance: feeAllowanceAny, + }, + }, nil +} + +// Allowances queries all the allowances granted to the given grantee. +func (q Keeper) Allowances(c context.Context, req *types.QueryAllowancesRequest) (*types.QueryAllowancesResponse, error) { + if req == nil { + return nil, status.Error(codes.InvalidArgument, "invalid request") + } + + err := sdk.ValidateAccAddress(req.Grantee) + if err != nil { + return nil, err + } + granteeAddr := sdk.AccAddress(req.Grantee) + + ctx := sdk.UnwrapSDKContext(c) + + var grants []*types.Grant + + store := ctx.KVStore(q.storeKey) + grantsStore := prefix.NewStore(store, types.FeeAllowancePrefixByGrantee(granteeAddr)) + + pageRes, err := query.Paginate(grantsStore, req.Pagination, func(key []byte, value []byte) error { + var grant types.Grant + + if err := q.cdc.UnmarshalBinaryBare(value, &grant); err != nil { + return err + } + + grants = append(grants, &grant) + return nil + }) + + if err != nil { + return nil, status.Error(codes.Internal, err.Error()) + } + + return &types.QueryAllowancesResponse{Allowances: grants, Pagination: pageRes}, nil +} diff --git a/x/feegrant/keeper/grpc_query_test.go b/x/feegrant/keeper/grpc_query_test.go new file mode 100644 index 0000000000..c54b15064a --- /dev/null +++ b/x/feegrant/keeper/grpc_query_test.go @@ -0,0 +1,158 @@ +package keeper_test + +import ( + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/x/feegrant/types" +) + +func (suite *KeeperTestSuite) TestFeeAllowance() { + + testCases := []struct { + name string + req *types.QueryAllowanceRequest + expectErr bool + preRun func() + postRun func(_ *types.QueryAllowanceResponse) + }{ + { + "nil request", + nil, + true, + func() {}, + func(*types.QueryAllowanceResponse) {}, + }, + { + "fail: invalid granter", + &types.QueryAllowanceRequest{ + Granter: "invalid_granter", + Grantee: suite.addrs[0].String(), + }, + true, + func() {}, + func(*types.QueryAllowanceResponse) {}, + }, + { + "fail: invalid grantee", + &types.QueryAllowanceRequest{ + Granter: suite.addrs[0].String(), + Grantee: "invalid_grantee", + }, + true, + func() {}, + func(*types.QueryAllowanceResponse) {}, + }, + { + "fail: no grants", + &types.QueryAllowanceRequest{ + Granter: suite.addrs[0].String(), + Grantee: suite.addrs[1].String(), + }, + true, + func() {}, + func(*types.QueryAllowanceResponse) {}, + }, + { + "valid query: expect single grant", + &types.QueryAllowanceRequest{ + Granter: suite.addrs[0].String(), + Grantee: suite.addrs[1].String(), + }, + false, + func() { + grantFeeAllowance(suite) + }, + func(response *types.QueryAllowanceResponse) { + suite.Require().Equal(response.Allowance.Granter, suite.addrs[0].String()) + suite.Require().Equal(response.Allowance.Grantee, suite.addrs[1].String()) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.preRun() + resp, err := suite.keeper.Allowance(suite.ctx, tc.req) + if tc.expectErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + tc.postRun(resp) + } + }) + } +} + +func (suite *KeeperTestSuite) TestFeeAllowances() { + testCases := []struct { + name string + req *types.QueryAllowancesRequest + expectErr bool + preRun func() + postRun func(_ *types.QueryAllowancesResponse) + }{ + { + "nil request", + nil, + true, + func() {}, + func(*types.QueryAllowancesResponse) {}, + }, + { + "fail: invalid grantee", + &types.QueryAllowancesRequest{ + Grantee: "invalid_grantee", + }, + true, + func() {}, + func(*types.QueryAllowancesResponse) {}, + }, + { + "no grants", + &types.QueryAllowancesRequest{ + Grantee: suite.addrs[1].String(), + }, + false, + func() {}, + func(resp *types.QueryAllowancesResponse) { + suite.Require().Equal(len(resp.Allowances), 0) + }, + }, + { + "valid query: expect single grant", + &types.QueryAllowancesRequest{ + Grantee: suite.addrs[1].String(), + }, + false, + func() { + grantFeeAllowance(suite) + }, + func(resp *types.QueryAllowancesResponse) { + suite.Require().Equal(len(resp.Allowances), 1) + suite.Require().Equal(resp.Allowances[0].Granter, suite.addrs[0].String()) + suite.Require().Equal(resp.Allowances[0].Grantee, suite.addrs[1].String()) + }, + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.preRun() + resp, err := suite.keeper.Allowances(suite.ctx, tc.req) + if tc.expectErr { + suite.Require().Error(err) + } else { + suite.Require().NoError(err) + tc.postRun(resp) + } + }) + } +} + +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: &exp, + }) + suite.Require().NoError(err) +} diff --git a/x/feegrant/keeper/keeper.go b/x/feegrant/keeper/keeper.go new file mode 100644 index 0000000000..f175f896bf --- /dev/null +++ b/x/feegrant/keeper/keeper.go @@ -0,0 +1,229 @@ +package keeper + +import ( + "fmt" + + storetypes "github.com/line/lbm-sdk/store/types" + "github.com/line/lbm-sdk/x/feegrant/types" + "github.com/line/ostracon/libs/log" + + "github.com/line/lbm-sdk/codec" + sdk "github.com/line/lbm-sdk/types" + sdkerrors "github.com/line/lbm-sdk/types/errors" +) + +// Keeper manages state of all fee grants, as well as calculating approval. +// It must have a codec with all available allowances registered. +type Keeper struct { + cdc codec.Marshaler + storeKey storetypes.StoreKey + authKeeper types.AccountKeeper +} + +// NewKeeper creates a fee grant Keeper +func NewKeeper(cdc codec.Marshaler, storeKey storetypes.StoreKey, ak types.AccountKeeper) Keeper { + return Keeper{ + cdc: cdc, + storeKey: storeKey, + authKeeper: ak, + } +} + +// Logger returns a module-specific logger. +func (k Keeper) Logger(ctx sdk.Context) log.Logger { + return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName)) +} + +// GrantAllowance creates a new grant +func (k Keeper) GrantAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress, feeAllowance types.FeeAllowanceI) error { + + // create the account if it is not in account state + granteeAcc := k.authKeeper.GetAccount(ctx, grantee) + if granteeAcc == nil { + granteeAcc = k.authKeeper.NewAccountWithAddress(ctx, grantee) + k.authKeeper.SetAccount(ctx, granteeAcc) + } + + store := ctx.KVStore(k.storeKey) + key := types.FeeAllowanceKey(granter, grantee) + grant, err := types.NewGrant(granter, grantee, feeAllowance) + if err != nil { + return err + } + + bz, err := k.cdc.MarshalBinaryBare(&grant) + if err != nil { + return err + } + + store.Set(key, bz) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeSetFeeGrant, + sdk.NewAttribute(types.AttributeKeyGranter, grant.Granter), + sdk.NewAttribute(types.AttributeKeyGrantee, grant.Grantee), + ), + ) + + return nil +} + +// revokeAllowance removes an existing grant +func (k Keeper) revokeAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress) error { + _, err := k.getGrant(ctx, granter, grantee) + if err != nil { + return err + } + + store := ctx.KVStore(k.storeKey) + key := types.FeeAllowanceKey(granter, grantee) + store.Delete(key) + + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeRevokeFeeGrant, + sdk.NewAttribute(types.AttributeKeyGranter, granter.String()), + sdk.NewAttribute(types.AttributeKeyGrantee, grantee.String()), + ), + ) + return nil +} + +// GetAllowance returns the allowance between the granter and grantee. +// If there is none, it returns nil, nil. +// Returns an error on parsing issues +func (k Keeper) GetAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress) (types.FeeAllowanceI, error) { + grant, err := k.getGrant(ctx, granter, grantee) + if err != nil { + return nil, err + } + + return grant.GetGrant() +} + +// getGrant returns entire grant between both accounts +func (k Keeper) getGrant(ctx sdk.Context, granter sdk.AccAddress, grantee sdk.AccAddress) (*types.Grant, error) { + store := ctx.KVStore(k.storeKey) + key := types.FeeAllowanceKey(granter, grantee) + bz := store.Get(key) + if len(bz) == 0 { + return nil, sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "fee-grant not found") + } + + var feegrant types.Grant + if err := k.cdc.UnmarshalBinaryBare(bz, &feegrant); err != nil { + return nil, err + } + + return &feegrant, nil +} + +// IterateAllFeeAllowances iterates over all the grants in the store. +// Callback to get all data, returns true to stop, false to keep reading +// Calling this without pagination is very expensive and only designed for export genesis +func (k Keeper) IterateAllFeeAllowances(ctx sdk.Context, cb func(grant types.Grant) bool) error { + store := ctx.KVStore(k.storeKey) + iter := sdk.KVStorePrefixIterator(store, types.FeeAllowanceKeyPrefix) + defer iter.Close() + + stop := false + for ; iter.Valid() && !stop; iter.Next() { + bz := iter.Value() + var feeGrant types.Grant + if err := k.cdc.UnmarshalBinaryBare(bz, &feeGrant); err != nil { + return err + } + + stop = cb(feeGrant) + } + + return nil +} + +// UseGrantedFees will try to pay the given fee from the granter's account as requested by the grantee +func (k Keeper) UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins, msgs []sdk.Msg) error { + f, err := k.getGrant(ctx, granter, grantee) + if err != nil { + return err + } + + grant, err := f.GetGrant() + if err != nil { + return err + } + + remove, err := grant.Accept(ctx, fee, msgs) + + if remove { + // Ignoring the `revokeFeeAllowance` error, because the user has enough grants to perform this transaction. + k.revokeAllowance(ctx, granter, grantee) + if err != nil { + return err + } + + emitUseGrantEvent(ctx, granter.String(), grantee.String()) + + return nil + } + + if err != nil { + return err + } + + emitUseGrantEvent(ctx, granter.String(), grantee.String()) + + // if fee allowance is accepted, store the updated state of the allowance + return k.GrantAllowance(ctx, granter, grantee, grant) +} + +func emitUseGrantEvent(ctx sdk.Context, granter, grantee string) { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeUseFeeGrant, + sdk.NewAttribute(types.AttributeKeyGranter, granter), + sdk.NewAttribute(types.AttributeKeyGrantee, grantee), + ), + ) +} + +// InitGenesis will initialize the keeper from a *previously validated* GenesisState +func (k Keeper) InitGenesis(ctx sdk.Context, data *types.GenesisState) error { + for _, f := range data.Allowances { + err := sdk.ValidateAccAddress(f.Granter) + if err != nil { + return err + } + granter := sdk.AccAddress(f.Granter) + err = sdk.ValidateAccAddress(f.Grantee) + if err != nil { + return err + } + grantee := sdk.AccAddress(f.Grantee) + + grant, err := f.GetGrant() + if err != nil { + return err + } + + err = k.GrantAllowance(ctx, granter, grantee, grant) + if err != nil { + return err + } + } + return nil +} + +// ExportGenesis will dump the contents of the keeper into a serializable GenesisState. +func (k Keeper) ExportGenesis(ctx sdk.Context) (*types.GenesisState, error) { + var grants []types.Grant + + err := k.IterateAllFeeAllowances(ctx, func(grant types.Grant) bool { + grants = append(grants, grant) + return false + }) + + return &types.GenesisState{ + Allowances: grants, + }, err +} diff --git a/x/feegrant/keeper/keeper_test.go b/x/feegrant/keeper/keeper_test.go new file mode 100644 index 0000000000..ff39684608 --- /dev/null +++ b/x/feegrant/keeper/keeper_test.go @@ -0,0 +1,261 @@ +package keeper_test + +import ( + "context" + "testing" + + "github.com/line/lbm-sdk/x/feegrant/types" + tmproto "github.com/line/ostracon/proto/ostracon/types" + "github.com/stretchr/testify/suite" + + "github.com/line/lbm-sdk/simapp" + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/x/feegrant/keeper" +) + +type KeeperTestSuite struct { + suite.Suite + + app *simapp.SimApp + sdkCtx sdk.Context + addrs []sdk.AccAddress + msgSrvr types.MsgServer + ctx context.Context + atom sdk.Coins + keeper keeper.Keeper +} + +func TestKeeperTestSuite(t *testing.T) { + suite.Run(t, new(KeeperTestSuite)) +} + +func (suite *KeeperTestSuite) SetupTest() { + app := simapp.Setup(false) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + suite.app = app + suite.sdkCtx = ctx + suite.addrs = simapp.AddTestAddrsIncremental(app, ctx, 4, sdk.NewInt(30000000)) + suite.ctx = sdk.WrapSDKContext(ctx) + suite.keeper = suite.app.FeeGrantKeeper + suite.msgSrvr = keeper.NewMsgServerImpl(suite.keeper) + suite.atom = sdk.NewCoins(sdk.NewCoin("atom", sdk.NewInt(555))) +} + +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: &exp, + } + + basic2 := &types.BasicAllowance{ + SpendLimit: eth, + Expiration: &exp, + } + + // let's set up some initial state here + err := suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[0], suite.addrs[1], basic) + suite.Require().NoError(err) + + err = suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[0], suite.addrs[2], basic2) + suite.Require().NoError(err) + + err = suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[1], suite.addrs[2], basic) + suite.Require().NoError(err) + + err = suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[1], suite.addrs[3], basic) + suite.Require().NoError(err) + + err = suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[3], suite.addrs[0], basic2) + suite.Require().NoError(err) + + // remove some, overwrite other + _, err = suite.msgSrvr.RevokeAllowance(suite.ctx, &types.MsgRevokeAllowance{Granter: suite.addrs[0].String(), Grantee: suite.addrs[1].String()}) + suite.Require().NoError(err) + _, err = suite.msgSrvr.RevokeAllowance(suite.ctx, &types.MsgRevokeAllowance{Granter: suite.addrs[0].String(), Grantee: suite.addrs[2].String()}) + suite.Require().NoError(err) + + // revoke non-exist fee allowance + _, err = suite.msgSrvr.RevokeAllowance(suite.ctx, &types.MsgRevokeAllowance{Granter: suite.addrs[0].String(), Grantee: suite.addrs[2].String()}) + suite.Require().Error(err) + + err = suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[0], suite.addrs[2], basic) + suite.Require().NoError(err) + + err = suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[1], suite.addrs[2], basic2) + suite.Require().NoError(err) + + // end state: + // addr -> addr3 (basic) + // addr2 -> addr3 (basic2), addr4(basic) + // addr4 -> addr (basic2) + + // then lots of queries + cases := map[string]struct { + grantee sdk.AccAddress + granter sdk.AccAddress + allowance types.FeeAllowanceI + }{ + "addr revoked": { + granter: suite.addrs[0], + grantee: suite.addrs[1], + }, + "addr revoked and added": { + granter: suite.addrs[0], + grantee: suite.addrs[2], + allowance: basic, + }, + "addr never there": { + granter: suite.addrs[0], + grantee: suite.addrs[3], + }, + "addr modified": { + granter: suite.addrs[1], + grantee: suite.addrs[2], + allowance: basic2, + }, + } + + for name, tc := range cases { + tc := tc + suite.Run(name, func() { + allow, _ := suite.keeper.GetAllowance(suite.sdkCtx, tc.granter, tc.grantee) + + if tc.allowance == nil { + suite.Nil(allow) + return + } + suite.NotNil(allow) + suite.Equal(tc.allowance, allow) + }) + } + accAddr := sdk.AccAddress("link1k907plrxssuh2dxsd8k2jtp3de2t4xyhq6rkxd") + suite.Require().NoError(err) + + // let's grant and revoke authorization to non existing account + err = suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[3], accAddr, basic2) + suite.Require().NoError(err) + + _, err = suite.keeper.GetAllowance(suite.sdkCtx, suite.addrs[3], accAddr) + suite.Require().NoError(err) + + _, err = suite.msgSrvr.RevokeAllowance(suite.ctx, &types.MsgRevokeAllowance{Granter: suite.addrs[3].String(), Grantee: accAddr.String()}) + suite.Require().NoError(err) + +} + +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: &oneYear, + } + + // for testing limits of the contract + hugeAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 9999)) + smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 1)) + futureAfterSmall := &types.BasicAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 554)), + Expiration: &oneYear, + } + + // then lots of queries + cases := map[string]struct { + grantee sdk.AccAddress + granter sdk.AccAddress + fee sdk.Coins + allowed bool + final types.FeeAllowanceI + }{ + "use entire pot": { + granter: suite.addrs[0], + grantee: suite.addrs[1], + fee: suite.atom, + allowed: true, + final: nil, + }, + "too high": { + granter: suite.addrs[0], + grantee: suite.addrs[1], + fee: hugeAtom, + allowed: false, + final: future, + }, + "use a little": { + granter: suite.addrs[0], + grantee: suite.addrs[1], + fee: smallAtom, + allowed: true, + final: futureAfterSmall, + }, + } + + for name, tc := range cases { + tc := tc + suite.Run(name, func() { + err := suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[0], suite.addrs[1], future) + suite.Require().NoError(err) + + err = suite.keeper.UseGrantedFees(suite.sdkCtx, tc.granter, tc.grantee, tc.fee, []sdk.Msg{}) + if tc.allowed { + suite.NoError(err) + } else { + suite.Error(err) + } + + 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: &exp, + } + + allowance1 := &types.BasicAllowance{ + SpendLimit: eth, + Expiration: &exp, + } + + suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[0], suite.addrs[1], allowance) + suite.keeper.GrantAllowance(suite.sdkCtx, suite.addrs[2], suite.addrs[1], allowance1) + + suite.keeper.IterateAllFeeAllowances(suite.sdkCtx, func(grant types.Grant) bool { + suite.Require().Equal(suite.addrs[1].String(), grant.Grantee) + suite.Require().Contains([]string{suite.addrs[0].String(), suite.addrs[2].String()}, grant.Granter) + return true + }) + +} diff --git a/x/feegrant/keeper/msg_server.go b/x/feegrant/keeper/msg_server.go new file mode 100644 index 0000000000..f50efb6817 --- /dev/null +++ b/x/feegrant/keeper/msg_server.go @@ -0,0 +1,82 @@ +package keeper + +import ( + "context" + + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/x/feegrant/types" + + sdkerrors "github.com/line/lbm-sdk/types/errors" +) + +type msgServer struct { + Keeper +} + +// NewMsgServerImpl returns an implementation of the feegrant MsgServer interface +// for the provided Keeper. +func NewMsgServerImpl(k Keeper) types.MsgServer { + return &msgServer{ + Keeper: k, + } +} + +var _ types.MsgServer = msgServer{} + +// GrantAllowance grants an allowance from the granter's funds to be used by the grantee. +func (k msgServer) GrantAllowance(goCtx context.Context, msg *types.MsgGrantAllowance) (*types.MsgGrantAllowanceResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + err := sdk.ValidateAccAddress(msg.Grantee) + if err != nil { + return nil, err + } + grantee := sdk.AccAddress(msg.Grantee) + + err = sdk.ValidateAccAddress(msg.Granter) + if err != nil { + return nil, err + } + granter := sdk.AccAddress(msg.Granter) + + // Checking for duplicate entry + if f, _ := k.Keeper.GetAllowance(ctx, granter, grantee); f != nil { + return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee allowance already exists") + } + + allowance, err := msg.GetFeeAllowanceI() + if err != nil { + return nil, err + } + + err = k.Keeper.GrantAllowance(ctx, granter, grantee, allowance) + if err != nil { + return nil, err + } + + return &types.MsgGrantAllowanceResponse{}, nil +} + +// RevokeAllowance revokes a fee allowance between a granter and grantee. +func (k msgServer) RevokeAllowance(goCtx context.Context, msg *types.MsgRevokeAllowance) (*types.MsgRevokeAllowanceResponse, error) { + ctx := sdk.UnwrapSDKContext(goCtx) + + err := sdk.ValidateAccAddress(msg.Grantee) + if err != nil { + return nil, err + } + grantee := sdk.AccAddress(msg.Grantee) + + err = sdk.ValidateAccAddress(msg.Granter) + if err != nil { + return nil, err + } + granter := sdk.AccAddress(msg.Granter) + + err = k.Keeper.revokeAllowance(ctx, granter, grantee) + if err != nil { + return nil, err + } + + return &types.MsgRevokeAllowanceResponse{}, nil +} diff --git a/x/feegrant/keeper/msg_server_test.go b/x/feegrant/keeper/msg_server_test.go new file mode 100644 index 0000000000..2b4da9fa14 --- /dev/null +++ b/x/feegrant/keeper/msg_server_test.go @@ -0,0 +1,222 @@ +package keeper_test + +import ( + codectypes "github.com/line/lbm-sdk/codec/types" + "github.com/line/lbm-sdk/x/feegrant/types" +) + +func (suite *KeeperTestSuite) TestGrantAllowance() { + oneYear := suite.sdkCtx.BlockTime().AddDate(1, 0, 0) + + testCases := []struct { + name string + req func() *types.MsgGrantAllowance + expectErr bool + errMsg string + }{ + { + "invalid granter address", + func() *types.MsgGrantAllowance { + any, err := codectypes.NewAnyWithValue(&types.BasicAllowance{}) + suite.Require().NoError(err) + return &types.MsgGrantAllowance{ + Granter: "invalid-granter", + Grantee: suite.addrs[1].String(), + Allowance: any, + } + }, + true, + "decoding bech32 failed", + }, + { + "invalid grantee address", + func() *types.MsgGrantAllowance { + any, err := codectypes.NewAnyWithValue(&types.BasicAllowance{}) + suite.Require().NoError(err) + return &types.MsgGrantAllowance{ + Granter: suite.addrs[0].String(), + Grantee: "invalid-grantee", + Allowance: any, + } + }, + true, + "decoding bech32 failed", + }, + { + "valid: basic fee allowance", + func() *types.MsgGrantAllowance { + any, err := codectypes.NewAnyWithValue(&types.BasicAllowance{ + SpendLimit: suite.atom, + Expiration: &oneYear, + }) + suite.Require().NoError(err) + return &types.MsgGrantAllowance{ + Granter: suite.addrs[0].String(), + Grantee: suite.addrs[1].String(), + Allowance: any, + } + }, + false, + "", + }, + { + "fail: fee allowance exists", + func() *types.MsgGrantAllowance { + any, err := codectypes.NewAnyWithValue(&types.BasicAllowance{ + SpendLimit: suite.atom, + Expiration: &oneYear, + }) + suite.Require().NoError(err) + return &types.MsgGrantAllowance{ + Granter: suite.addrs[0].String(), + Grantee: suite.addrs[1].String(), + Allowance: any, + } + }, + true, + "fee allowance already exists", + }, + { + "valid: periodic fee allowance", + func() *types.MsgGrantAllowance { + any, err := codectypes.NewAnyWithValue(&types.PeriodicAllowance{ + Basic: types.BasicAllowance{ + SpendLimit: suite.atom, + Expiration: &oneYear, + }, + }) + suite.Require().NoError(err) + return &types.MsgGrantAllowance{ + Granter: suite.addrs[1].String(), + Grantee: suite.addrs[2].String(), + Allowance: any, + } + }, + false, + "", + }, + { + "error: fee allowance exists", + func() *types.MsgGrantAllowance { + any, err := codectypes.NewAnyWithValue(&types.PeriodicAllowance{ + Basic: types.BasicAllowance{ + SpendLimit: suite.atom, + Expiration: &oneYear, + }, + }) + suite.Require().NoError(err) + return &types.MsgGrantAllowance{ + Granter: suite.addrs[1].String(), + Grantee: suite.addrs[2].String(), + Allowance: any, + } + }, + true, + "fee allowance already exists", + }, + } + for _, tc := range testCases { + suite.Run(tc.name, func() { + _, err := suite.msgSrvr.GrantAllowance(suite.ctx, tc.req()) + if tc.expectErr { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.errMsg) + } + }) + } +} + +func (suite *KeeperTestSuite) TestRevokeAllowance() { + oneYear := suite.sdkCtx.BlockTime().AddDate(1, 0, 0) + + testCases := []struct { + name string + request *types.MsgRevokeAllowance + preRun func() + expectErr bool + errMsg string + }{ + { + "error: invalid granter", + &types.MsgRevokeAllowance{ + Granter: "invalid-granter", + Grantee: suite.addrs[1].String(), + }, + func() {}, + true, + "decoding bech32 failed", + }, + { + "error: invalid grantee", + &types.MsgRevokeAllowance{ + Granter: suite.addrs[0].String(), + Grantee: "invalid-grantee", + }, + func() {}, + true, + "decoding bech32 failed", + }, + { + "error: fee allowance not found", + &types.MsgRevokeAllowance{ + Granter: suite.addrs[0].String(), + Grantee: suite.addrs[1].String(), + }, + func() {}, + true, + "fee-grant not found", + }, + { + "success: revoke fee allowance", + &types.MsgRevokeAllowance{ + Granter: suite.addrs[0].String(), + Grantee: suite.addrs[1].String(), + }, + func() { + // removing fee allowance from previous tests if exists + suite.msgSrvr.RevokeAllowance(suite.ctx, &types.MsgRevokeAllowance{ + Granter: suite.addrs[0].String(), + Grantee: suite.addrs[1].String(), + }) + any, err := codectypes.NewAnyWithValue(&types.PeriodicAllowance{ + Basic: types.BasicAllowance{ + SpendLimit: suite.atom, + Expiration: &oneYear, + }, + }) + suite.Require().NoError(err) + req := &types.MsgGrantAllowance{ + Granter: suite.addrs[0].String(), + Grantee: suite.addrs[1].String(), + Allowance: any, + } + _, err = suite.msgSrvr.GrantAllowance(suite.ctx, req) + suite.Require().NoError(err) + }, + false, + "", + }, + { + "error: check fee allowance revoked", + &types.MsgRevokeAllowance{ + Granter: suite.addrs[0].String(), + Grantee: suite.addrs[1].String(), + }, + func() {}, + true, + "fee-grant not found", + }, + } + + for _, tc := range testCases { + suite.Run(tc.name, func() { + tc.preRun() + _, err := suite.msgSrvr.RevokeAllowance(suite.ctx, tc.request) + if tc.expectErr { + suite.Require().Error(err) + suite.Require().Contains(err.Error(), tc.errMsg) + } + }) + } + +} diff --git a/x/feegrant/module.go b/x/feegrant/module.go new file mode 100644 index 0000000000..72575289bc --- /dev/null +++ b/x/feegrant/module.go @@ -0,0 +1,215 @@ +package feegrant + +import ( + "context" + "encoding/json" + "math/rand" + + "github.com/gorilla/mux" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/line/lbm-sdk/x/feegrant/types" + abci "github.com/line/ostracon/abci/types" + "github.com/spf13/cobra" + + sdkclient "github.com/line/lbm-sdk/client" + "github.com/line/lbm-sdk/codec" + cdctypes "github.com/line/lbm-sdk/codec/types" + sdk "github.com/line/lbm-sdk/types" + sdkerrors "github.com/line/lbm-sdk/types/errors" + "github.com/line/lbm-sdk/types/module" + simtypes "github.com/line/lbm-sdk/types/simulation" + "github.com/line/lbm-sdk/x/feegrant/client/cli" + "github.com/line/lbm-sdk/x/feegrant/keeper" + "github.com/line/lbm-sdk/x/feegrant/simulation" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModule{} +) + +// ---------------------------------------------------------------------------- +// AppModuleBasic +// ---------------------------------------------------------------------------- + +// AppModuleBasic defines the basic application module used by the feegrant module. +type AppModuleBasic struct { + cdc codec.Marshaler +} + +// Name returns the feegrant module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterServices registers a gRPC query service to respond to the +// module-specific gRPC queries. +func (am AppModule) RegisterServices(cfg module.Configurator) { + types.RegisterMsgServer(cfg.MsgServer(), keeper.NewMsgServerImpl(am.keeper)) + types.RegisterQueryServer(cfg.QueryServer(), am.keeper) +} + +// RegisterLegacyAminoCodec registers the feegrant module's types for the given codec. +func (AppModuleBasic) RegisterLegacyAminoCodec(cdc *codec.LegacyAmino) { +} + +// RegisterInterfaces registers the feegrant module's interface types +func (AppModuleBasic) RegisterInterfaces(registry cdctypes.InterfaceRegistry) { + types.RegisterInterfaces(registry) +} + +// LegacyQuerierHandler returns the feegrant module sdk.Querier. +func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sdk.Querier { + return nil +} + +// DefaultGenesis returns default genesis state as raw bytes for the feegrant +// module. +func (AppModuleBasic) DefaultGenesis(cdc codec.JSONMarshaler) json.RawMessage { + return cdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the feegrant module. +func (a AppModuleBasic) ValidateGenesis(cdc codec.JSONMarshaler, config sdkclient.TxEncodingConfig, bz json.RawMessage) error { + var data types.GenesisState + if err := cdc.UnmarshalJSON(bz, &data); err != nil { + sdkerrors.Wrapf(err, "failed to unmarshal %s genesis state", types.ModuleName) + } + + return types.ValidateGenesis(data) +} + +// RegisterRESTRoutes registers the REST routes for the feegrant module. +// Deprecated: RegisterRESTRoutes is deprecated. +func (AppModuleBasic) RegisterRESTRoutes(clientCtx sdkclient.Context, rtr *mux.Router) {} + +// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the feegrant module. +func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) { + if err := types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx)); err != nil { + panic(err) + } +} + +// GetTxCmd returns the root tx command for the feegrant module. +func (AppModuleBasic) GetTxCmd() *cobra.Command { + return cli.GetTxCmd() +} + +// GetQueryCmd returns no root query command for the feegrant module. +func (AppModuleBasic) GetQueryCmd() *cobra.Command { + return cli.GetQueryCmd() +} + +// ---------------------------------------------------------------------------- +// AppModule +// ---------------------------------------------------------------------------- + +// AppModule implements an application module for the feegrant module. +type AppModule struct { + AppModuleBasic + keeper keeper.Keeper + accountKeeper types.AccountKeeper + bankKeeper types.BankKeeper + registry cdctypes.InterfaceRegistry +} + +// NewAppModule creates a new AppModule object +func NewAppModule(cdc codec.Marshaler, ak types.AccountKeeper, bk types.BankKeeper, keeper keeper.Keeper, registry cdctypes.InterfaceRegistry) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{cdc: cdc}, + keeper: keeper, + accountKeeper: ak, + bankKeeper: bk, + registry: registry, + } +} + +// Name returns the feegrant module's name. +func (AppModule) Name() string { + return types.ModuleName +} + +// RegisterInvariants registers the feegrant module invariants. +func (am AppModule) RegisterInvariants(ir sdk.InvariantRegistry) {} + +// Deprecated: Route returns the message routing key for the feegrant module. +func (am AppModule) Route() sdk.Route { + return sdk.NewRoute(types.RouterKey, NewHandler(am.keeper)) +} + +// NewHandler returns an sdk.Handler for the feegrant module. +func (am AppModule) NewHandler() sdk.Handler { + return nil +} + +// QuerierRoute returns the feegrant module's querier route name. +func (AppModule) QuerierRoute() string { + return "" +} + +// InitGenesis performs genesis initialization for the feegrant module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, cdc codec.JSONMarshaler, bz json.RawMessage) []abci.ValidatorUpdate { + var gs types.GenesisState + cdc.MustUnmarshalJSON(bz, &gs) + + err := am.keeper.InitGenesis(ctx, &gs) + if err != nil { + panic(err) + } + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the feegrant +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json.RawMessage { + gs, err := am.keeper.ExportGenesis(ctx) + if err != nil { + panic(err) + } + + return cdc.MustMarshalJSON(gs) +} + +// ConsensusVersion implements AppModule/ConsensusVersion. +func (AppModule) ConsensusVersion() uint64 { return 1 } + +// BeginBlock returns the begin blocker for the feegrant module. +func (am AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {} + +// EndBlock returns the end blocker for the feegrant module. It returns no validator +// updates. +func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} + +// AppModuleSimulation functions + +// GenerateGenesisState creates a randomized GenState of the feegrant module. +func (AppModule) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// ProposalContents returns all the feegrant content functions used to +// simulate governance proposals. +func (AppModule) ProposalContents(simState module.SimulationState) []simtypes.WeightedProposalContent { + return nil +} + +// RandomizedParams creates randomized feegrant param changes for the simulator. +func (AppModule) RandomizedParams(r *rand.Rand) []simtypes.ParamChange { + return nil +} + +// RegisterStoreDecoder registers a decoder for feegrant module's types +func (am AppModule) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[types.StoreKey] = simulation.NewDecodeStore(am.cdc) +} + +// WeightedOperations returns all the feegrant module operations with their respective weights. +func (am AppModule) WeightedOperations(simState module.SimulationState) []simtypes.WeightedOperation { + return simulation.WeightedOperations( + simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.keeper, + ) +} diff --git a/x/feegrant/simulation/decoder.go b/x/feegrant/simulation/decoder.go new file mode 100644 index 0000000000..5de093f15e --- /dev/null +++ b/x/feegrant/simulation/decoder.go @@ -0,0 +1,26 @@ +package simulation + +import ( + "bytes" + "fmt" + + "github.com/line/lbm-sdk/codec" + "github.com/line/lbm-sdk/types/kv" + "github.com/line/lbm-sdk/x/feegrant/types" +) + +// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's +// Value to the corresponding feegrant type. +func NewDecodeStore(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string { + return func(kvA, kvB kv.Pair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.FeeAllowanceKeyPrefix): + var grantA, grantB types.Grant + cdc.MustUnmarshalBinaryBare(kvA.Value, &grantA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &grantB) + return fmt.Sprintf("%v\n%v", grantA, grantB) + default: + panic(fmt.Sprintf("invalid feegrant key %X", kvA.Key)) + } + } +} diff --git a/x/feegrant/simulation/decoder_test.go b/x/feegrant/simulation/decoder_test.go new file mode 100644 index 0000000000..bc1d31830d --- /dev/null +++ b/x/feegrant/simulation/decoder_test.go @@ -0,0 +1,63 @@ +package simulation_test + +import ( + "fmt" + "testing" + + "github.com/line/lbm-sdk/x/feegrant/types" + "github.com/stretchr/testify/require" + + "github.com/line/lbm-sdk/crypto/keys/ed25519" + "github.com/line/lbm-sdk/simapp" + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/types/kv" + "github.com/line/lbm-sdk/x/feegrant/simulation" +) + +var ( + granterPk = ed25519.GenPrivKey().PubKey() + granterAddr = sdk.AccAddress(granterPk.Address()) + granteePk = ed25519.GenPrivKey().PubKey() + granteeAddr = sdk.AccAddress(granterPk.Address()) +) + +func TestDecodeStore(t *testing.T) { + cdc, _ := simapp.MakeCodecs() + dec := simulation.NewDecodeStore(cdc) + + grant, err := types.NewGrant(granterAddr, granteeAddr, &types.BasicAllowance{ + SpendLimit: sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(100))), + }) + + require.NoError(t, err) + + grantBz, err := cdc.MarshalBinaryBare(&grant) + require.NoError(t, err) + + kvPairs := kv.Pairs{ + Pairs: []kv.Pair{ + {Key: []byte(types.FeeAllowanceKeyPrefix), Value: grantBz}, + {Key: []byte{0x99}, Value: []byte{0x99}}, + }, + } + + tests := []struct { + name string + expectedLog string + }{ + {"Grant", fmt.Sprintf("%v\n%v", grant, grant)}, + {"other", ""}, + } + + for i, tt := range tests { + i, tt := i, tt + t.Run(tt.name, func(t *testing.T) { + switch i { + case len(tests) - 1: + require.Panics(t, func() { dec(kvPairs.Pairs[i], kvPairs.Pairs[i]) }, tt.name) + default: + require.Equal(t, tt.expectedLog, dec(kvPairs.Pairs[i], kvPairs.Pairs[i]), tt.name) + } + }) + } +} diff --git a/x/feegrant/simulation/genesis.go b/x/feegrant/simulation/genesis.go new file mode 100644 index 0000000000..6ae2662af7 --- /dev/null +++ b/x/feegrant/simulation/genesis.go @@ -0,0 +1,77 @@ +package simulation + +import ( + "math/rand" + "time" + + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/types/module" + simtypes "github.com/line/lbm-sdk/types/simulation" + "github.com/line/lbm-sdk/x/feegrant/types" +) + +// genFeeGrants returns a slice of randomly generated allowances. +func genFeeGrants(r *rand.Rand, accounts []simtypes.Account) []types.Grant { + allowances := make([]types.Grant, len(accounts)-1) + for i := 0; i < len(accounts)-1; i++ { + granter := accounts[i].Address + grantee := accounts[i+1].Address + allowances[i] = generateRandomAllowances(granter, grantee, r) + } + return allowances +} + +func generateRandomAllowances(granter, grantee sdk.AccAddress, r *rand.Rand) types.Grant { + allowances := make([]types.Grant, 3) + spendLimit := sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(100))) + periodSpendLimit := sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(10))) + + basic := types.BasicAllowance{ + SpendLimit: spendLimit, + } + + basicAllowance, err := types.NewGrant(granter, grantee, &basic) + if err != nil { + panic(err) + } + allowances[0] = basicAllowance + + periodicAllowance, err := types.NewGrant(granter, grantee, &types.PeriodicAllowance{ + Basic: basic, + PeriodSpendLimit: periodSpendLimit, + Period: time.Hour, + }) + if err != nil { + panic(err) + } + allowances[1] = periodicAllowance + + filteredAllowance, err := types.NewGrant(granter, grantee, &types.AllowedMsgAllowance{ + Allowance: basicAllowance.GetAllowance(), + AllowedMessages: []string{"/cosmos.gov.v1beta1.MsgSubmitProposal"}, + }) + if err != nil { + panic(err) + } + allowances[2] = filteredAllowance + + return allowances[r.Intn(len(allowances))] +} + +// RandomizedGenState generates a random GenesisState for feegrant +func RandomizedGenState(simState *module.SimulationState) { + var feegrants []types.Grant + + simState.AppParams.GetOrGenerate( + simState.Cdc, "feegrant", &feegrants, simState.Rand, + func(r *rand.Rand) { feegrants = genFeeGrants(r, simState.Accounts) }, + ) + + feegrantGenesis := types.NewGenesisState(feegrants) + bz, err := simState.Cdc.MarshalJSON(feegrantGenesis) + if err != nil { + panic(err) + } + + simState.GenState[types.ModuleName] = bz +} diff --git a/x/feegrant/simulation/genesis_test.go b/x/feegrant/simulation/genesis_test.go new file mode 100644 index 0000000000..7a1d752bbb --- /dev/null +++ b/x/feegrant/simulation/genesis_test.go @@ -0,0 +1,40 @@ +package simulation_test + +import ( + "encoding/json" + "math/rand" + "testing" + + "github.com/line/lbm-sdk/x/feegrant/types" + "github.com/stretchr/testify/require" + + "github.com/line/lbm-sdk/simapp" + "github.com/line/lbm-sdk/types/module" + simtypes "github.com/line/lbm-sdk/types/simulation" + "github.com/line/lbm-sdk/x/feegrant/simulation" +) + +func TestRandomizedGenState(t *testing.T) { + app := simapp.Setup(false) + + s := rand.NewSource(1) + r := rand.New(s) + + accounts := simtypes.RandomAccounts(r, 3) + + simState := module.SimulationState{ + AppParams: make(simtypes.AppParams), + Cdc: app.AppCodec(), + Rand: r, + NumBonded: 3, + Accounts: accounts, + InitialStake: 1000, + GenState: make(map[string]json.RawMessage), + } + + simulation.RandomizedGenState(&simState) + var feegrantGenesis types.GenesisState + simState.Cdc.MustUnmarshalJSON(simState.GenState[types.ModuleName], &feegrantGenesis) + + require.Len(t, feegrantGenesis.Allowances, len(accounts)-1) +} diff --git a/x/feegrant/simulation/operations.go b/x/feegrant/simulation/operations.go new file mode 100644 index 0000000000..504af677bb --- /dev/null +++ b/x/feegrant/simulation/operations.go @@ -0,0 +1,162 @@ +package simulation + +import ( + "math/rand" + + "github.com/line/lbm-sdk/baseapp" + "github.com/line/lbm-sdk/codec" + simappparams "github.com/line/lbm-sdk/simapp/params" + sdk "github.com/line/lbm-sdk/types" + simtypes "github.com/line/lbm-sdk/types/simulation" + "github.com/line/lbm-sdk/x/feegrant/keeper" + "github.com/line/lbm-sdk/x/feegrant/types" + "github.com/line/lbm-sdk/x/simulation" +) + +// Simulation operation weights constants +const ( + OpWeightMsgGrantAllowance = "op_weight_msg_grant_fee_allowance" + OpWeightMsgRevokeAllowance = "op_weight_msg_grant_revoke_allowance" +) + +var ( + TypeMsgGrantAllowance = sdk.MsgTypeURL(&types.MsgGrantAllowance{}) + TypeMsgRevokeAllowance = sdk.MsgTypeURL(&types.MsgRevokeAllowance{}) +) + +func WeightedOperations( + appParams simtypes.AppParams, cdc codec.JSONMarshaler, + ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper, +) simulation.WeightedOperations { + + var ( + weightMsgGrantAllowance int + weightMsgRevokeAllowance int + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgGrantAllowance, &weightMsgGrantAllowance, nil, + func(_ *rand.Rand) { + weightMsgGrantAllowance = simappparams.DefaultWeightGrantAllowance + }, + ) + + appParams.GetOrGenerate(cdc, OpWeightMsgRevokeAllowance, &weightMsgRevokeAllowance, nil, + func(_ *rand.Rand) { + weightMsgRevokeAllowance = simappparams.DefaultWeightRevokeAllowance + }, + ) + + return simulation.WeightedOperations{ + simulation.NewWeightedOperation( + weightMsgGrantAllowance, + SimulateMsgGrantAllowance(ak, bk, k), + ), + simulation.NewWeightedOperation( + weightMsgRevokeAllowance, + SimulateMsgRevokeAllowance(ak, bk, k), + ), + } +} + +// SimulateMsgGrantAllowance generates MsgGrantAllowance with random values. +func SimulateMsgGrantAllowance(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + granter, _ := simtypes.RandomAcc(r, accs) + grantee, _ := simtypes.RandomAcc(r, accs) + if grantee.Address.String() == granter.Address.String() { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgGrantAllowance, "grantee and granter cannot be same"), nil, nil + } + + if f, _ := k.GetAllowance(ctx, granter.Address, grantee.Address); f != nil { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgGrantAllowance, "fee allowance exists"), nil, nil + } + + account := ak.GetAccount(ctx, granter.Address) + + spendableCoins := bk.SpendableCoins(ctx, account.GetAddress()) + if spendableCoins.Empty() { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgGrantAllowance, "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: &oneYear, + }, granter.Address, grantee.Address) + + if err != nil { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgGrantAllowance, err.Error()), nil, err + } + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: msg, + MsgType: TypeMsgGrantAllowance, + Context: ctx, + SimAccount: granter, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendableCoins, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} + +// SimulateMsgRevokeAllowance generates a MsgRevokeAllowance with random values. +func SimulateMsgRevokeAllowance(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) simtypes.Operation { + return func( + r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simtypes.Account, chainID string, + ) (simtypes.OperationMsg, []simtypes.FutureOperation, error) { + + hasGrant := false + var granterAddr sdk.AccAddress + var granteeAddr sdk.AccAddress + k.IterateAllFeeAllowances(ctx, func(grant types.Grant) bool { + + granter := sdk.AccAddress(grant.Granter) + grantee := sdk.AccAddress(grant.Grantee) + granterAddr = granter + granteeAddr = grantee + hasGrant = true + return true + }) + + if !hasGrant { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRevokeAllowance, "no grants"), nil, nil + } + granter, ok := simtypes.FindAccount(accs, granterAddr) + + if !ok { + return simtypes.NoOpMsg(types.ModuleName, TypeMsgRevokeAllowance, "Account not found"), nil, nil + } + + account := ak.GetAccount(ctx, granter.Address) + spendableCoins := bk.SpendableCoins(ctx, account.GetAddress()) + + msg := types.NewMsgRevokeAllowance(granterAddr, granteeAddr) + + txCtx := simulation.OperationInput{ + R: r, + App: app, + TxGen: simappparams.MakeTestEncodingConfig().TxConfig, + Cdc: nil, + Msg: &msg, + MsgType: TypeMsgRevokeAllowance, + Context: ctx, + SimAccount: granter, + AccountKeeper: ak, + Bankkeeper: bk, + ModuleName: types.ModuleName, + CoinsSpentInMsg: spendableCoins, + } + + return simulation.GenAndDeliverTxWithRandFees(txCtx) + } +} diff --git a/x/feegrant/simulation/operations_test.go b/x/feegrant/simulation/operations_test.go new file mode 100644 index 0000000000..37aa353aa2 --- /dev/null +++ b/x/feegrant/simulation/operations_test.go @@ -0,0 +1,168 @@ +package simulation_test + +import ( + "math/rand" + "testing" + "time" + + "github.com/line/lbm-sdk/x/feegrant/types" + abci "github.com/line/ostracon/abci/types" + tmproto "github.com/line/ostracon/proto/ostracon/types" + "github.com/stretchr/testify/suite" + + "github.com/line/lbm-sdk/simapp" + simappparams "github.com/line/lbm-sdk/simapp/params" + sdk "github.com/line/lbm-sdk/types" + simtypes "github.com/line/lbm-sdk/types/simulation" + "github.com/line/lbm-sdk/x/bank/testutil" + "github.com/line/lbm-sdk/x/feegrant/simulation" +) + +type SimTestSuite struct { + suite.Suite + + ctx sdk.Context + app *simapp.SimApp +} + +func (suite *SimTestSuite) SetupTest() { + checkTx := false + app := simapp.Setup(checkTx) + suite.app = app + suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{ + Time: time.Now(), + }) + +} + +func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account { + accounts := simtypes.RandomAccounts(r, n) + + initAmt := sdk.TokensFromConsensusPower(200) + initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt)) + + // add coins to the accounts + for _, account := range accounts { + err := testutil.FundAccount(suite.app.BankKeeper, suite.ctx, account.Address, initCoins) + suite.Require().NoError(err) + } + + return accounts +} + +func (suite *SimTestSuite) TestWeightedOperations() { + app, ctx := suite.app, suite.ctx + require := suite.Require() + + ctx.WithChainID("test-chain") + + cdc := app.AppCodec() + appParams := make(simtypes.AppParams) + + weightedOps := simulation.WeightedOperations( + appParams, cdc, app.AccountKeeper, + app.BankKeeper, app.FeeGrantKeeper, + ) + + s := rand.NewSource(1) + r := rand.New(s) + accs := suite.getTestingAccounts(r, 3) + + expected := []struct { + weight int + opMsgRoute string + opMsgName string + }{ + { + simappparams.DefaultWeightGrantAllowance, + types.MsgGrantAllowance{}.Route(), + simulation.TypeMsgGrantAllowance, + }, + { + simappparams.DefaultWeightRevokeAllowance, + types.MsgRevokeAllowance{}.Route(), + simulation.TypeMsgRevokeAllowance, + }, + } + + for i, w := range weightedOps { + operationMsg, _, _ := w.Op()(r, app.BaseApp, ctx, accs, ctx.ChainID()) + // the following checks are very much dependent from the ordering of the output given + // by WeightedOperations. if the ordering in WeightedOperations changes some tests + // will fail + require.Equal(expected[i].weight, w.Weight(), "weight should be the same") + require.Equal(expected[i].opMsgRoute, operationMsg.Route, "route should be the same") + require.Equal(expected[i].opMsgName, operationMsg.Name, "operation Msg name should be the same") + } +} + +func (suite *SimTestSuite) TestSimulateMsgGrantAllowance() { + app, ctx := suite.app, suite.ctx + require := suite.Require() + + s := rand.NewSource(1) + r := rand.New(s) + accounts := suite.getTestingAccounts(r, 3) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: app.LastBlockHeight() + 1, AppHash: app.LastCommitID().Hash}}) + + // execute operation + op := simulation.SimulateMsgGrantAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper) + operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") + require.NoError(err) + + var msg types.MsgGrantAllowance + suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + + require.True(operationMsg.OK) + require.Equal(accounts[2].Address.String(), msg.Granter) + require.Equal(accounts[1].Address.String(), msg.Grantee) + require.Len(futureOperations, 0) +} + +func (suite *SimTestSuite) TestSimulateMsgRevokeAllowance() { + app, ctx := suite.app, suite.ctx + require := suite.Require() + + s := rand.NewSource(1) + r := rand.New(s) + accounts := suite.getTestingAccounts(r, 3) + + // begin a new block + app.BeginBlock(abci.RequestBeginBlock{Header: tmproto.Header{Height: suite.app.LastBlockHeight() + 1, AppHash: suite.app.LastCommitID().Hash}}) + + feeAmt := sdk.TokensFromConsensusPower(200000) + feeCoins := sdk.NewCoins(sdk.NewCoin("foo", feeAmt)) + + 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: &oneYear, + }, + ) + require.NoError(err) + + // execute operation + op := simulation.SimulateMsgRevokeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper) + operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "") + require.NoError(err) + + var msg types.MsgRevokeAllowance + suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg) + + require.True(operationMsg.OK) + require.Equal(granter.Address.String(), msg.Granter) + require.Equal(grantee.Address.String(), msg.Grantee) + require.Len(futureOperations, 0) +} + +func TestSimTestSuite(t *testing.T) { + suite.Run(t, new(SimTestSuite)) +} diff --git a/x/feegrant/spec/01_concepts.md b/x/feegrant/spec/01_concepts.md new file mode 100644 index 0000000000..b99250eb7b --- /dev/null +++ b/x/feegrant/spec/01_concepts.md @@ -0,0 +1,78 @@ + + +# Concepts + +## Grant + +`Grant` is stored in the KVStore to record a grant with full context. Every grant will contain `granter`, `grantee` and what kind of `allowance` is granted. `granter` is an account address who is giving permission to `grantee` (the beneficiary account address) to pay for some or all of `grantee`'s transaction fees. `allowance` defines what kind of fee allowance (`BasicAllowance` or `PeriodicAllowance`, see below) is granted to `grantee`. `allowance` accepts an interface which implements `FeeAllowanceI`, encoded as `Any` type. There can be only one existing fee grant allowed for a `grantee` and `granter`, self grants are not allowed. + ++++ https://github.com/line/lbm-sdk/blob/691032b8be0f7539ec99f8882caecefc51f33d1f/proto/cosmos/feegrant/v1beta1/feegrant.proto#L75-L81 + +`FeeAllowanceI` looks like: + ++++ https://github.com/line/lbm-sdk/blob/691032b8be0f7539ec99f8882caecefc51f33d1f/x/feegrant/fees.go#L9-L32 + +## Fee Allowance types + +There are two types of fee allowances present at the moment: + +- `BasicAllowance` +- `PeriodicAllowance` + +## BasicAllowance + +`BasicAllowance` is permission for `grantee` to use fee from a `granter`'s account. If any of the `spend_limit` or `expiration` reaches its limit, the grant will be removed from the state. + ++++ https://github.com/line/lbm-sdk/blob/691032b8be0f7539ec99f8882caecefc51f33d1f/proto/cosmos/feegrant/v1beta1/feegrant.proto#L13-L26 + +- `spend_limit` is the limit of coins that are allowed to be used from the `granter` account. If it is empty, it assumes there's no spend limit, `grantee` can use any number of available tokens from `granter` account address before the expiration. + +- `expiration` specifies an optional time when this allowance expires. If the value is left empty, there is no expiry for the grant. + +- When a grant is created with empty values for `spend_limit` and `expiration`, it is still a valid grant. It won't restrict the `grantee` to use any number of tokens from `granter` and it won't have any expiration. The only way to restrict the `grantee` is by revoking the grant. + +## PeriodicAllowance + +`PeriodicAllowance` is a repeating fee allowance for the mentioned period, we can mention when the grant can expire as well as when a period can reset. We can also define the maximum number of coins that can be used in a mentioned period of time. + ++++ https://github.com/line/lbm-sdk/blob/691032b8be0f7539ec99f8882caecefc51f33d1f/proto/cosmos/feegrant/v1beta1/feegrant.proto#L28-L73 + +- `basic` is the instance of `BasicAllowance` which is optional for periodic fee allowance. If empty, the grant will have no `expiration` and no `spend_limit`. + +- `period` is the specific period of time, after each period passes, `period_spend_limit` will be reset. + +- `period_spend_limit` specifies the maximum number of coins that can be spent in the period. + +- `period_can_spend` is the number of coins left to be spent before the period_reset time. + +- `period_reset` keeps track of when a next period reset should happen. + +## FeeAccount flag + +`feegrant` module introduces a `FeeAccount` flag for CLI for the sake of executing transactions with fee granter. When this flag is set, `clientCtx` will append the granter account address for transactions generated through CLI. + ++++ https://github.com/line/lbm-sdk/blob/d97e7907f176777ed8a464006d360bb3e1a223e4/client/cmd.go#L224-L235 + ++++ https://github.com/line/lbm-sdk/blob/d97e7907f176777ed8a464006d360bb3e1a223e4/client/tx/tx.go#L120 + ++++ https://github.com/line/lbm-sdk/blob/d97e7907f176777ed8a464006d360bb3e1a223e4/x/auth/tx/builder.go#L268-L277 + ++++ https://github.com/line/lbm-sdk/blob/d97e7907f176777ed8a464006d360bb3e1a223e4/proto/cosmos/tx/v1beta1/tx.proto#L160-L181 + +Example cmd: + +```go +./simd tx gov submit-proposal --title="Test Proposal" --description="My awesome proposal" --type="Text" --from validator-key --fee-account=cosmos1xh44hxt7spr67hqaa7nyx5gnutrz5fraw6grxn --chain-id=testnet --fees="10stake" +``` + +## Granted Fee Deductions + +Fees are deducted from grants in the `x/auth` ante handler. To learn more about how ante handlers work, read the [Auth Module AnteHandlers Guide](../../auth/spec/03_antehandlers.md). + +## Gas + +In order to prevent DoS attacks, using a filtered `x/feegrant` incurs gas. The SDK must assure that the `grantee`'s transactions all conform to the filter set by the `granter`. The SDK does this by iterating over the allowed messages in the filter and charging 10 gas per filtered message. The SDK will then iterate over the messages being sent by the `grantee` to ensure the messages adhere to the filter, also charging 10 gas per message. The SDK will stop iterating and fail the transaction if it finds a message that does not conform to the filter. + +**WARNING**: The gas is charged against the granted allowance. Ensure your messages conform to the filter, if any, before sending transactions using your allowance. diff --git a/x/feegrant/spec/02_state.md b/x/feegrant/spec/02_state.md new file mode 100644 index 0000000000..fbf90d8b34 --- /dev/null +++ b/x/feegrant/spec/02_state.md @@ -0,0 +1,15 @@ + + +# State + +## FeeAllowance + +Fee Allowances are identified by combining `Grantee` (the account address of fee allowance grantee) with the `Granter` (the account address of fee allowance granter). + +Fee allowance grants are stored in the state as follows: + +- Grant: `0x00 | grantee_addr_len (1 byte) | grantee_addr_bytes | granter_addr_len (1 byte) | granter_addr_bytes -> ProtocolBuffer(Grant)` + ++++ https://github.com/line/lbm-sdk/blob/691032b8be0f7539ec99f8882caecefc51f33d1f/x/feegrant/feegrant.pb.go#L221-L229 diff --git a/x/feegrant/spec/03_messages.md b/x/feegrant/spec/03_messages.md new file mode 100644 index 0000000000..9aaccf9686 --- /dev/null +++ b/x/feegrant/spec/03_messages.md @@ -0,0 +1,17 @@ + + +# Messages + +## Msg/GrantAllowance + +A fee allowance grant will be created with the `MsgGrantAllowance` message. + ++++ https://github.com/line/lbm-sdk/blob/691032b8be0f7539ec99f8882caecefc51f33d1f/proto/cosmos/feegrant/v1beta1/tx.proto#L22-L33 + +## Msg/RevokeAllowance + +An allowed grant fee allowance can be removed with the `MsgRevokeAllowance` message. + ++++ https://github.com/line/lbm-sdk/blob/691032b8be0f7539ec99f8882caecefc51f33d1f/proto/cosmos/feegrant/v1beta1/tx.proto#L38-L45 diff --git a/x/feegrant/spec/04_events.md b/x/feegrant/spec/04_events.md new file mode 100644 index 0000000000..55012f8c68 --- /dev/null +++ b/x/feegrant/spec/04_events.md @@ -0,0 +1,33 @@ + + +# Events + +The feegrant module emits the following events: + +# Msg Server + +### MsgGrantAllowance + +| Type | Attribute Key | Attribute Value | +| -------- | ------------- | ------------------ | +| message | action | set_feegrant | +| message | granter | {granterAddress} | +| message | grantee | {granteeAddress} | + +### MsgRevokeAllowance + +| Type | Attribute Key | Attribute Value | +| -------- | ------------- | ------------------ | +| message | action | revoke_feegrant | +| message | granter | {granterAddress} | +| message | grantee | {granteeAddress} | + +### Exec fee allowance + +| Type | Attribute Key | Attribute Value | +| -------- | ------------- | ------------------ | +| message | action | use_feegrant | +| message | granter | {granterAddress} | +| message | grantee | {granteeAddress} | diff --git a/x/feegrant/spec/05_client.md b/x/feegrant/spec/05_client.md new file mode 100644 index 0000000000..dbb5005bca --- /dev/null +++ b/x/feegrant/spec/05_client.md @@ -0,0 +1,184 @@ + + +# Client + +## CLI + +A user can query and interact with the `feegrant` module using the CLI. + +### Query + +The `query` commands allow users to query `feegrant` state. + +``` +simd query feegrant --help +``` + +#### grant + +The `grant` command allows users to query a grant for a given granter-grantee pair. + +``` +simd query feegrant grant [granter] [grantee] [flags] +``` + +Example: + +``` +simd query feegrant grant cosmos1.. cosmos1.. +``` + +Example Output: + +``` +allowance: + '@type': /cosmos.feegrant.v1beta1.BasicAllowance + expiration: null + spend_limit: + - amount: "100" + denom: stake +grantee: cosmos1.. +granter: cosmos1.. +``` + +#### grants + +The `grants` command allows users to query all grants for a given grantee. + +``` +simd query feegrant grants [grantee] [flags] +``` + +Example: + +``` +simd query feegrant grants cosmos1.. +``` + +Example Output: + +``` +allowances: +- allowance: + '@type': /cosmos.feegrant.v1beta1.BasicAllowance + expiration: null + spend_limit: + - amount: "100" + denom: stake + grantee: cosmos1.. + granter: cosmos1.. +pagination: + next_key: null + total: "0" +``` + +### Transactions + +The `tx` commands allow users to interact with the `feegrant` module. + +``` +simd tx feegrant --help +``` + +#### grant + +The `grant` command allows users to grant fee allowances to another account. The fee allowance can have an expiration date, a total spend limit, and/or a periodic spend limit. + +``` +simd tx feegrant grant [granter] [grantee] [flags] +``` + +Example (one-time spend limit): + +``` +simd tx feegrant grant cosmos1.. cosmos1.. --spend-limit 100stake +``` + +Example (periodic spend limit): + +``` +simd tx feegrant grant cosmos1.. cosmos1.. --period 3600 --period-limit 10stake +``` + +#### revoke + +The `revoke` command allows users to revoke a granted fee allowance. + +``` +simd tx feegrant revoke [granter] [grantee] [flags] +``` + +Example: + +``` +simd tx feegrant revoke cosmos1.. cosmos1.. +``` + +## gRPC + +A user can query the `feegrant` module using gRPC endpoints. + +### Allowance + +The `Allowance` endpoint allows users to query a granted fee allowance. + +``` +cosmos.feegrant.v1beta1.Query/Allowance +``` + +Example: + +``` +grpcurl -plaintext \ + -d '{"grantee":"cosmos1..","granter":"cosmos1.."}' \ + localhost:9090 \ + cosmos.feegrant.v1beta1.Query/Allowance +``` + +Example Output: + +``` +{ + "allowance": { + "granter": "cosmos1..", + "grantee": "cosmos1..", + "allowance": {"@type":"/cosmos.feegrant.v1beta1.BasicAllowance","spendLimit":[{"denom":"stake","amount":"100"}]} + } +} +``` + +### Allowances + +The `Allowances` endpoint allows users to query all granted fee allowances for a given grantee. + +``` +cosmos.feegrant.v1beta1.Query/Allowances +``` + +Example: + +``` +grpcurl -plaintext \ + -d '{"address":"cosmos1.."}' \ + localhost:9090 \ + cosmos.feegrant.v1beta1.Query/Allowances +``` + +Example Output: + +``` +{ + "allowances": [ + { + "granter": "cosmos1..", + "grantee": "cosmos1..", + "allowance": {"@type":"/cosmos.feegrant.v1beta1.BasicAllowance","spendLimit":[{"denom":"stake","amount":"100"}]} + } + ], + "pagination": { + "total": "1" + } +} +``` diff --git a/x/feegrant/spec/README.md b/x/feegrant/spec/README.md new file mode 100644 index 0000000000..00cd685c62 --- /dev/null +++ b/x/feegrant/spec/README.md @@ -0,0 +1,35 @@ + + +## Abstract + +This document specifies the feegrant module. For the full ADR, please see [Fee Grant ADR-029](https://github.com/line/lbm-sdk/blob/v0.40.0/docs/architecture/adr-029-fee-grant-module.md). + +This module allows accounts to grant fee allowances and to use fees from their accounts. Grantees can execute any transaction without the need to maintain sufficient fees. + +## Contents + +1. **[Concepts](01_concepts.md)** + - [Grant](01_concepts.md#grant) + - [Fee Allowance types](01_concepts.md#fee-allowance-types) + - [BasicAllowance](01_concepts.md#basicallowance) + - [PeriodicAllowance](01_concepts.md#periodicallowance) + - [FeeAccount flag](01_concepts.md#feeaccount-flag) + - [Granted Fee Deductions](01_concepts.md#granted-fee-deductions) + - [Gas](01_concepts.md#gas) +2. **[State](02_state.md)** + - [FeeAllowance](02_state.md#feeallowance) +3. **[Messages](03_messages.md)** + - [Msg/GrantAllowance](03_messages.md#msggrantallowance) + - [Msg/RevokeAllowance](03_messages.md#msgrevokeallowance) +4. **[Events](04_events.md)** + - [MsgGrantAllowance](04_events.md#msggrantallowance) + - [MsgRevokeAllowance](04_events.md#msgrevokeallowance) + - [Exec fee allowance](04_events.md#exec-fee-allowance) +5. **[Client](05_client.md)** + - [CLI](05_client.md#cli) + - [gRPC](05_client.md#grpc) diff --git a/x/feegrant/types/basic_fee.go b/x/feegrant/types/basic_fee.go new file mode 100644 index 0000000000..e15565e09c --- /dev/null +++ b/x/feegrant/types/basic_fee.go @@ -0,0 +1,54 @@ +package types + +import ( + sdk "github.com/line/lbm-sdk/types" + sdkerrors "github.com/line/lbm-sdk/types/errors" +) + +var _ FeeAllowanceI = (*BasicAllowance)(nil) + +// 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 RevokeAllowance in Keeper.UseGrantedFees) +func (a *BasicAllowance) Accept(ctx sdk.Context, fee sdk.Coins, _ []sdk.Msg) (bool, error) { + if a.Expiration != nil && a.Expiration.Before(ctx.BlockTime()) { + return true, sdkerrors.Wrap(ErrFeeLimitExpired, "basic allowance") + } + + if a.SpendLimit != nil { + left, invalid := a.SpendLimit.SafeSub(fee) + if invalid { + return false, sdkerrors.Wrap(ErrFeeLimitExceeded, "basic allowance") + } + + a.SpendLimit = left + return left.IsZero(), nil + } + + return false, nil +} + +// ValidateBasic implements FeeAllowance and enforces basic sanity checks +func (a BasicAllowance) ValidateBasic() error { + if a.SpendLimit != nil { + if !a.SpendLimit.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "send amount is invalid: %s", a.SpendLimit) + } + if !a.SpendLimit.IsAllPositive() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "spend limit must be positive") + } + } + + if a.Expiration != nil && a.Expiration.Unix() < 0 { + return sdkerrors.Wrap(ErrInvalidDuration, "expiration time cannot be negative") + } + + return nil +} diff --git a/x/feegrant/types/basic_fee_test.go b/x/feegrant/types/basic_fee_test.go new file mode 100644 index 0000000000..57f127a0fd --- /dev/null +++ b/x/feegrant/types/basic_fee_test.go @@ -0,0 +1,150 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/line/lbm-sdk/x/feegrant/types" + ocproto "github.com/line/ostracon/proto/ostracon/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/line/lbm-sdk/simapp" + sdk "github.com/line/lbm-sdk/types" +) + +func TestBasicFeeValidAllow(t *testing.T) { + app := simapp.Setup(false) + + ctx := app.BaseApp.NewContext(false, ocproto.Header{}) + badTime := ctx.BlockTime().AddDate(0, 0, -1) + allowace := &types.BasicAllowance{ + Expiration: &badTime, + } + require.Error(t, allowace.ValidateBasic()) + + ctx = app.BaseApp.NewContext(false, ocproto.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 + blockTime time.Time + valid bool + accept bool + remove bool + remains sdk.Coins + }{ + "empty": { + allowance: &types.BasicAllowance{}, + accept: true, + }, + "small fee without expire": { + allowance: &types.BasicAllowance{ + SpendLimit: atom, + }, + fee: smallAtom, + accept: true, + remove: false, + remains: leftAtom, + }, + "all fee without expire": { + allowance: &types.BasicAllowance{ + SpendLimit: smallAtom, + }, + fee: smallAtom, + accept: true, + remove: true, + }, + "wrong fee": { + allowance: &types.BasicAllowance{ + SpendLimit: smallAtom, + }, + fee: eth, + accept: false, + }, + "non-expired": { + allowance: &types.BasicAllowance{ + SpendLimit: atom, + Expiration: &oneHour, + }, + valid: true, + fee: smallAtom, + blockTime: now, + accept: true, + remove: false, + remains: leftAtom, + }, + "expired": { + allowance: &types.BasicAllowance{ + SpendLimit: atom, + Expiration: &now, + }, + valid: true, + fee: smallAtom, + blockTime: oneHour, + accept: false, + remove: true, + }, + "fee more than allowed": { + allowance: &types.BasicAllowance{ + SpendLimit: atom, + Expiration: &oneHour, + }, + valid: true, + fee: bigAtom, + blockTime: now, + accept: false, + }, + "with out spend limit": { + allowance: &types.BasicAllowance{ + Expiration: &oneHour, + }, + valid: true, + fee: bigAtom, + blockTime: now, + accept: true, + }, + "expired no spend limit": { + allowance: &types.BasicAllowance{ + Expiration: &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.allowance.ValidateBasic() + require.NoError(t, err) + + ctx := app.BaseApp.NewContext(false, ocproto.Header{}).WithBlockTime(tc.blockTime) + + // 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.allowance.SpendLimit, tc.remains) + } + }) + } +} diff --git a/x/feegrant/types/codec.go b/x/feegrant/types/codec.go new file mode 100644 index 0000000000..f05916a965 --- /dev/null +++ b/x/feegrant/types/codec.go @@ -0,0 +1,25 @@ +package types + +import ( + "github.com/line/lbm-sdk/codec/types" + sdk "github.com/line/lbm-sdk/types" + "github.com/line/lbm-sdk/types/msgservice" +) + +// RegisterInterfaces registers the interfaces types with the interface registry +func RegisterInterfaces(registry types.InterfaceRegistry) { + registry.RegisterImplementations((*sdk.Msg)(nil), + &MsgGrantAllowance{}, + &MsgRevokeAllowance{}, + ) + + registry.RegisterInterface( + "cosmos.feegrant.v1beta1.FeeAllowanceI", + (*FeeAllowanceI)(nil), + &BasicAllowance{}, + &PeriodicAllowance{}, + &AllowedMsgAllowance{}, + ) + + msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc) +} diff --git a/x/feegrant/types/errors.go b/x/feegrant/types/errors.go new file mode 100644 index 0000000000..a12402d10d --- /dev/null +++ b/x/feegrant/types/errors.go @@ -0,0 +1,25 @@ +package types + +import ( + sdkerrors "github.com/line/lbm-sdk/types/errors" +) + +// Codes for governance errors +const ( + DefaultCodespace = ModuleName +) + +var ( + // ErrFeeLimitExceeded error if there are not enough allowance to cover the fees + ErrFeeLimitExceeded = sdkerrors.Register(DefaultCodespace, 2, "fee limit exceeded") + // ErrFeeLimitExpired error if the allowance has expired + ErrFeeLimitExpired = sdkerrors.Register(DefaultCodespace, 3, "fee allowance expired") + // ErrInvalidDuration error if the Duration is invalid or doesn't match the expiration + ErrInvalidDuration = sdkerrors.Register(DefaultCodespace, 4, "invalid duration") + // ErrNoAllowance error if there is no allowance for that pair + ErrNoAllowance = sdkerrors.Register(DefaultCodespace, 5, "no allowance") + // ErrNoMessages error if there is no message + ErrNoMessages = sdkerrors.Register(DefaultCodespace, 6, "allowed messages are empty") + // ErrMessageNotAllowed error if message is not allowed + ErrMessageNotAllowed = sdkerrors.Register(DefaultCodespace, 7, "message not allowed") +) diff --git a/x/feegrant/types/events.go b/x/feegrant/types/events.go new file mode 100644 index 0000000000..b82ccb7b1c --- /dev/null +++ b/x/feegrant/types/events.go @@ -0,0 +1,13 @@ +package types + +// evidence module events +const ( + EventTypeUseFeeGrant = "use_feegrant" + EventTypeRevokeFeeGrant = "revoke_feegrant" + EventTypeSetFeeGrant = "set_feegrant" + + AttributeKeyGranter = "granter" + AttributeKeyGrantee = "grantee" + + AttributeValueCategory = ModuleName +) diff --git a/x/feegrant/types/expected_keepers.go b/x/feegrant/types/expected_keepers.go new file mode 100644 index 0000000000..f90d2a516c --- /dev/null +++ b/x/feegrant/types/expected_keepers.go @@ -0,0 +1,22 @@ +package types + +import ( + sdk "github.com/line/lbm-sdk/types" + auth "github.com/line/lbm-sdk/x/auth/types" +) + +// AccountKeeper defines the expected auth Account Keeper (noalias) +type AccountKeeper interface { + GetModuleAddress(moduleName string) sdk.AccAddress + GetModuleAccount(ctx sdk.Context, moduleName string) auth.ModuleAccountI + + NewAccountWithAddress(ctx sdk.Context, addr sdk.AccAddress) auth.AccountI + GetAccount(ctx sdk.Context, addr sdk.AccAddress) auth.AccountI + SetAccount(ctx sdk.Context, acc auth.AccountI) +} + +// BankKeeper defines the expected supply Keeper (noalias) +type BankKeeper interface { + SpendableCoins(ctx sdk.Context, addr sdk.AccAddress) sdk.Coins + SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) error +} diff --git a/x/feegrant/types/feegrant.pb.go b/x/feegrant/types/feegrant.pb.go new file mode 100644 index 0000000000..3ab8355e03 --- /dev/null +++ b/x/feegrant/types/feegrant.pb.go @@ -0,0 +1,1343 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: lbm/feegrant/v1/feegrant.proto + +package types + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + github_com_gogo_protobuf_types "github.com/gogo/protobuf/types" + types1 "github.com/line/lbm-sdk/codec/types" + github_com_line_lbm_sdk_types "github.com/line/lbm-sdk/types" + types "github.com/line/lbm-sdk/types" + _ "github.com/regen-network/cosmos-proto" + _ "google.golang.org/protobuf/types/known/durationpb" + _ "google.golang.org/protobuf/types/known/timestamppb" + io "io" + math "math" + math_bits "math/bits" + time "time" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf +var _ = time.Kitchen + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// BasicAllowance implements Allowance with a one-time grant of tokens +// that optionally expires. The grantee can use up to SpendLimit to cover fees. +type BasicAllowance struct { + // 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. + SpendLimit github_com_line_lbm_sdk_types.Coins `protobuf:"bytes,1,rep,name=spend_limit,json=spendLimit,proto3,castrepeated=github.com/line/lbm-sdk/types.Coins" json:"spend_limit"` + // expiration specifies an optional time when this allowance expires + Expiration *time.Time `protobuf:"bytes,2,opt,name=expiration,proto3,stdtime" json:"expiration,omitempty"` +} + +func (m *BasicAllowance) Reset() { *m = BasicAllowance{} } +func (m *BasicAllowance) String() string { return proto.CompactTextString(m) } +func (*BasicAllowance) ProtoMessage() {} +func (*BasicAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_3cc1909be6bd11e2, []int{0} +} +func (m *BasicAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *BasicAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_BasicAllowance.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 *BasicAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_BasicAllowance.Merge(m, src) +} +func (m *BasicAllowance) XXX_Size() int { + return m.Size() +} +func (m *BasicAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_BasicAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_BasicAllowance proto.InternalMessageInfo + +func (m *BasicAllowance) GetSpendLimit() github_com_line_lbm_sdk_types.Coins { + if m != nil { + return m.SpendLimit + } + return nil +} + +func (m *BasicAllowance) GetExpiration() *time.Time { + if m != nil { + return m.Expiration + } + return nil +} + +// PeriodicAllowance extends Allowance to allow for both a maximum cap, +// as well as a limit per time period. +type PeriodicAllowance struct { + // basic specifies a struct of `BasicAllowance` + 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 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_line_lbm_sdk_types.Coins `protobuf:"bytes,3,rep,name=period_spend_limit,json=periodSpendLimit,proto3,castrepeated=github.com/line/lbm-sdk/types.Coins" json:"period_spend_limit"` + // period_can_spend is the number of coins left to be spent before the period_reset time + PeriodCanSpend github_com_line_lbm_sdk_types.Coins `protobuf:"bytes,4,rep,name=period_can_spend,json=periodCanSpend,proto3,castrepeated=github.com/line/lbm-sdk/types.Coins" json:"period_can_spend"` + // 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 time.Time `protobuf:"bytes,5,opt,name=period_reset,json=periodReset,proto3,stdtime" json:"period_reset"` +} + +func (m *PeriodicAllowance) Reset() { *m = PeriodicAllowance{} } +func (m *PeriodicAllowance) String() string { return proto.CompactTextString(m) } +func (*PeriodicAllowance) ProtoMessage() {} +func (*PeriodicAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_3cc1909be6bd11e2, []int{1} +} +func (m *PeriodicAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *PeriodicAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_PeriodicAllowance.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 *PeriodicAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_PeriodicAllowance.Merge(m, src) +} +func (m *PeriodicAllowance) XXX_Size() int { + return m.Size() +} +func (m *PeriodicAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_PeriodicAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_PeriodicAllowance proto.InternalMessageInfo + +func (m *PeriodicAllowance) GetBasic() BasicAllowance { + if m != nil { + return m.Basic + } + return BasicAllowance{} +} + +func (m *PeriodicAllowance) GetPeriod() time.Duration { + if m != nil { + return m.Period + } + return 0 +} + +func (m *PeriodicAllowance) GetPeriodSpendLimit() github_com_line_lbm_sdk_types.Coins { + if m != nil { + return m.PeriodSpendLimit + } + return nil +} + +func (m *PeriodicAllowance) GetPeriodCanSpend() github_com_line_lbm_sdk_types.Coins { + if m != nil { + return m.PeriodCanSpend + } + return nil +} + +func (m *PeriodicAllowance) GetPeriodReset() time.Time { + if m != nil { + return m.PeriodReset + } + return time.Time{} +} + +// AllowedMsgAllowance creates allowance only for specified message types. +type AllowedMsgAllowance struct { + // allowance can be any of basic and filtered fee allowance. + Allowance *types1.Any `protobuf:"bytes,1,opt,name=allowance,proto3" json:"allowance,omitempty"` + // allowed_messages are the messages for which the grantee has the access. + AllowedMessages []string `protobuf:"bytes,2,rep,name=allowed_messages,json=allowedMessages,proto3" json:"allowed_messages,omitempty"` +} + +func (m *AllowedMsgAllowance) Reset() { *m = AllowedMsgAllowance{} } +func (m *AllowedMsgAllowance) String() string { return proto.CompactTextString(m) } +func (*AllowedMsgAllowance) ProtoMessage() {} +func (*AllowedMsgAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_3cc1909be6bd11e2, []int{2} +} +func (m *AllowedMsgAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *AllowedMsgAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_AllowedMsgAllowance.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 *AllowedMsgAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_AllowedMsgAllowance.Merge(m, src) +} +func (m *AllowedMsgAllowance) XXX_Size() int { + return m.Size() +} +func (m *AllowedMsgAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_AllowedMsgAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_AllowedMsgAllowance proto.InternalMessageInfo + +// 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. + Granter string `protobuf:"bytes,1,opt,name=granter,proto3" json:"granter,omitempty" yaml:"granter_address"` + // grantee is the address of the user being granted an allowance of another user's funds. + Grantee string `protobuf:"bytes,2,opt,name=grantee,proto3" json:"grantee,omitempty" yaml:"grantee_address"` + // allowance can be any of basic and filtered fee allowance. + Allowance *types1.Any `protobuf:"bytes,3,opt,name=allowance,proto3" json:"allowance,omitempty"` +} + +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_3cc1909be6bd11e2, []int{3} +} +func (m *Grant) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *Grant) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_Grant.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 *Grant) XXX_Merge(src proto.Message) { + xxx_messageInfo_Grant.Merge(m, src) +} +func (m *Grant) XXX_Size() int { + return m.Size() +} +func (m *Grant) XXX_DiscardUnknown() { + xxx_messageInfo_Grant.DiscardUnknown(m) +} + +var xxx_messageInfo_Grant proto.InternalMessageInfo + +func (m *Grant) GetGranter() string { + if m != nil { + return m.Granter + } + return "" +} + +func (m *Grant) GetGrantee() string { + if m != nil { + return m.Grantee + } + return "" +} + +func (m *Grant) GetAllowance() *types1.Any { + if m != nil { + return m.Allowance + } + return nil +} + +func init() { + proto.RegisterType((*BasicAllowance)(nil), "lbm.feegrant.v1.BasicAllowance") + proto.RegisterType((*PeriodicAllowance)(nil), "lbm.feegrant.v1.PeriodicAllowance") + proto.RegisterType((*AllowedMsgAllowance)(nil), "lbm.feegrant.v1.AllowedMsgAllowance") + proto.RegisterType((*Grant)(nil), "lbm.feegrant.v1.Grant") +} + +func init() { proto.RegisterFile("lbm/feegrant/v1/feegrant.proto", fileDescriptor_3cc1909be6bd11e2) } + +var fileDescriptor_3cc1909be6bd11e2 = []byte{ + // 590 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x54, 0x3b, 0x6f, 0xd3, 0x50, + 0x14, 0x8e, 0x9b, 0xb6, 0x90, 0x1b, 0x68, 0x1b, 0x53, 0x2a, 0x37, 0x83, 0x1d, 0x85, 0x25, 0x08, + 0xd5, 0x56, 0x0a, 0x53, 0xba, 0x50, 0x17, 0xa8, 0x90, 0xa8, 0x84, 0x0c, 0x13, 0x8b, 0x75, 0x6d, + 0x9f, 0xba, 0x16, 0xb6, 0xaf, 0xe5, 0xeb, 0x84, 0xe6, 0x1f, 0x30, 0x76, 0x64, 0x42, 0xcc, 0xcc, + 0x4c, 0x6c, 0x2c, 0xa8, 0x62, 0xaa, 0x98, 0x98, 0x5a, 0x94, 0xfc, 0x03, 0x7e, 0x01, 0xba, 0x8f, + 0x3c, 0x48, 0x8a, 0x90, 0x50, 0x37, 0xdf, 0x73, 0xce, 0xf7, 0x38, 0x0f, 0x19, 0xe9, 0xb1, 0x97, + 0x58, 0x87, 0x00, 0x61, 0x8e, 0xd3, 0xc2, 0xea, 0xb5, 0xc7, 0xdf, 0x66, 0x96, 0x93, 0x82, 0xa8, + 0xab, 0xb1, 0x97, 0x98, 0xe3, 0x58, 0xaf, 0x5d, 0x5f, 0x0f, 0x49, 0x48, 0x78, 0xce, 0x62, 0x5f, + 0xa2, 0xac, 0xbe, 0x19, 0x12, 0x12, 0xc6, 0x60, 0xf1, 0x97, 0xd7, 0x3d, 0xb4, 0x70, 0xda, 0x1f, + 0xa5, 0x7c, 0x42, 0x13, 0x42, 0x5d, 0x81, 0x11, 0x0f, 0x99, 0xda, 0x60, 0xe2, 0x1e, 0xa6, 0xc0, + 0x84, 0x7d, 0x12, 0xa5, 0x32, 0x6e, 0xcc, 0xb2, 0x15, 0x51, 0x02, 0xb4, 0xc0, 0x49, 0x26, 0x0b, + 0xf4, 0xd9, 0x82, 0xa0, 0x9b, 0xe3, 0x22, 0x22, 0x92, 0xa0, 0xf9, 0x55, 0x41, 0x2b, 0x36, 0xa6, + 0x91, 0xbf, 0x1b, 0xc7, 0xe4, 0x0d, 0x4e, 0x7d, 0x50, 0x5d, 0x54, 0xa5, 0x19, 0xa4, 0x81, 0x1b, + 0x47, 0x49, 0x54, 0x68, 0x4a, 0xa3, 0xdc, 0xaa, 0x6e, 0xd7, 0x4c, 0xd6, 0x1e, 0x73, 0x60, 0xf6, + 0xda, 0xe6, 0x1e, 0x89, 0x52, 0xfb, 0xde, 0xe9, 0xb9, 0x51, 0xfa, 0x78, 0x61, 0xdc, 0x09, 0xa3, + 0xe2, 0xa8, 0xeb, 0x99, 0x3e, 0x49, 0xac, 0x38, 0x4a, 0xc1, 0x8a, 0xbd, 0x64, 0x8b, 0x06, 0xaf, + 0xad, 0xa2, 0x9f, 0x01, 0xe5, 0xb5, 0xd4, 0x41, 0x9c, 0xf2, 0x19, 0x63, 0x54, 0x1f, 0x22, 0x04, + 0xc7, 0x59, 0x24, 0x7c, 0x68, 0x0b, 0x0d, 0xa5, 0x55, 0xdd, 0xae, 0x9b, 0xc2, 0xa8, 0x39, 0x32, + 0x6a, 0xbe, 0x1c, 0x75, 0x62, 0x2f, 0x9e, 0x5c, 0x18, 0x8a, 0x33, 0x85, 0xe9, 0xd4, 0xbe, 0x7f, + 0xda, 0xba, 0xf9, 0x04, 0x60, 0x6c, 0xfa, 0x69, 0xf3, 0x4b, 0x19, 0xd5, 0x9e, 0x43, 0x1e, 0x91, + 0x60, 0xba, 0x97, 0x1d, 0xb4, 0xe4, 0xb1, 0xee, 0x34, 0x85, 0xab, 0x18, 0xe6, 0xcc, 0x92, 0xcc, + 0x3f, 0x7b, 0xb7, 0x17, 0x59, 0x4f, 0x8e, 0xc0, 0xa8, 0x3b, 0x68, 0x39, 0xe3, 0x8c, 0xd2, 0xe3, + 0xe6, 0x9c, 0xc7, 0x47, 0x72, 0x98, 0xf6, 0x75, 0x86, 0x7b, 0xc7, 0x6c, 0x4a, 0x88, 0x1a, 0x23, + 0x55, 0x7c, 0xb9, 0xd3, 0xc3, 0x2c, 0x5f, 0xc9, 0x30, 0xd7, 0x04, 0xf3, 0x8b, 0xc9, 0x48, 0x8f, + 0x90, 0x8c, 0xb9, 0x3e, 0x4e, 0x85, 0xa2, 0xb6, 0x78, 0x25, 0x5a, 0x2b, 0x82, 0x77, 0x0f, 0xa7, + 0x5c, 0x4e, 0xdd, 0x47, 0x37, 0xa4, 0x52, 0x0e, 0x14, 0x0a, 0x6d, 0xe9, 0x9f, 0xeb, 0xe3, 0xb3, + 0xe1, 0x2b, 0xac, 0x0a, 0xa4, 0xc3, 0x80, 0x97, 0xed, 0xf0, 0xbd, 0x82, 0x6e, 0xf1, 0x27, 0x04, + 0x07, 0x34, 0x9c, 0x6c, 0xf1, 0x31, 0xaa, 0xe0, 0xd1, 0x43, 0x6e, 0x72, 0x7d, 0x4e, 0x70, 0x37, + 0xed, 0xdb, 0xb5, 0x6f, 0xb3, 0x9c, 0xce, 0x04, 0xa9, 0xde, 0x45, 0x6b, 0x58, 0xb0, 0xbb, 0x09, + 0x50, 0x8a, 0x43, 0xa0, 0xda, 0x42, 0xa3, 0xdc, 0xaa, 0x38, 0xab, 0x32, 0x7e, 0x20, 0xc3, 0x9d, + 0xdb, 0x6f, 0x3f, 0x18, 0xa5, 0x79, 0x83, 0x9f, 0x15, 0xb4, 0xb4, 0xcf, 0x4e, 0x47, 0x7d, 0x80, + 0xae, 0xf1, 0x1b, 0x82, 0x9c, 0x1b, 0xaa, 0xd8, 0xf5, 0x5f, 0xe7, 0xc6, 0x46, 0x1f, 0x27, 0x71, + 0xa7, 0x29, 0x13, 0x2e, 0x0e, 0x82, 0x1c, 0x28, 0x6d, 0x3a, 0xa3, 0xd2, 0x09, 0x0a, 0xf8, 0x49, + 0x5d, 0x82, 0x82, 0x39, 0xd4, 0x4c, 0xfb, 0xe5, 0xff, 0x6d, 0xdf, 0xb6, 0x4f, 0x07, 0xba, 0x72, + 0x36, 0xd0, 0x95, 0x9f, 0x03, 0x5d, 0x39, 0x19, 0xea, 0xa5, 0xb3, 0xa1, 0x5e, 0xfa, 0x31, 0xd4, + 0x4b, 0xaf, 0x5a, 0x7f, 0x3b, 0x84, 0xe3, 0xc9, 0x0f, 0x8f, 0xdf, 0x84, 0xb7, 0xcc, 0xf5, 0xee, + 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0x2c, 0x40, 0x60, 0x50, 0x0d, 0x05, 0x00, 0x00, +} + +func (m *BasicAllowance) 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 *BasicAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *BasicAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + 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 -= n1 + i = encodeVarintFeegrant(dAtA, i, uint64(n1)) + i-- + dAtA[i] = 0x12 + } + if len(m.SpendLimit) > 0 { + for iNdEx := len(m.SpendLimit) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.SpendLimit[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFeegrant(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func (m *PeriodicAllowance) 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 *PeriodicAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *PeriodicAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + 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 { + for iNdEx := len(m.PeriodCanSpend) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.PeriodCanSpend[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFeegrant(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x22 + } + } + if len(m.PeriodSpendLimit) > 0 { + for iNdEx := len(m.PeriodSpendLimit) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.PeriodSpendLimit[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFeegrant(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } + 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 + { + size, err := m.Basic.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFeegrant(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + return len(dAtA) - i, nil +} + +func (m *AllowedMsgAllowance) 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 *AllowedMsgAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *AllowedMsgAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.AllowedMessages) > 0 { + for iNdEx := len(m.AllowedMessages) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AllowedMessages[iNdEx]) + copy(dAtA[i:], m.AllowedMessages[iNdEx]) + i = encodeVarintFeegrant(dAtA, i, uint64(len(m.AllowedMessages[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } + if m.Allowance != nil { + { + size, err := m.Allowance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFeegrant(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *Grant) 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 *Grant) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *Grant) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Allowance != nil { + { + size, err := m.Allowance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintFeegrant(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.Grantee) > 0 { + i -= len(m.Grantee) + copy(dAtA[i:], m.Grantee) + i = encodeVarintFeegrant(dAtA, i, uint64(len(m.Grantee))) + i-- + dAtA[i] = 0x12 + } + if len(m.Granter) > 0 { + i -= len(m.Granter) + copy(dAtA[i:], m.Granter) + i = encodeVarintFeegrant(dAtA, i, uint64(len(m.Granter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func encodeVarintFeegrant(dAtA []byte, offset int, v uint64) int { + offset -= sovFeegrant(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *BasicAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.SpendLimit) > 0 { + for _, e := range m.SpendLimit { + l = e.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 +} + +func (m *PeriodicAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = m.Basic.Size() + n += 1 + l + sovFeegrant(uint64(l)) + 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 { + l = e.Size() + n += 1 + l + sovFeegrant(uint64(l)) + } + } + if len(m.PeriodCanSpend) > 0 { + for _, e := range m.PeriodCanSpend { + l = e.Size() + n += 1 + l + sovFeegrant(uint64(l)) + } + } + l = github_com_gogo_protobuf_types.SizeOfStdTime(m.PeriodReset) + n += 1 + l + sovFeegrant(uint64(l)) + return n +} + +func (m *AllowedMsgAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Allowance != nil { + l = m.Allowance.Size() + n += 1 + l + sovFeegrant(uint64(l)) + } + if len(m.AllowedMessages) > 0 { + for _, s := range m.AllowedMessages { + l = len(s) + n += 1 + l + sovFeegrant(uint64(l)) + } + } + return n +} + +func (m *Grant) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Granter) + if l > 0 { + n += 1 + l + sovFeegrant(uint64(l)) + } + l = len(m.Grantee) + if l > 0 { + n += 1 + l + sovFeegrant(uint64(l)) + } + if m.Allowance != nil { + l = m.Allowance.Size() + n += 1 + l + sovFeegrant(uint64(l)) + } + return n +} + +func sovFeegrant(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozFeegrant(x uint64) (n int) { + return sovFeegrant(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *BasicAllowance) 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: BasicAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: BasicAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SpendLimit", 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 + } + m.SpendLimit = append(m.SpendLimit, types.Coin{}) + if err := m.SpendLimit[len(m.SpendLimit)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Expiration", 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 + } + 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 + 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 *PeriodicAllowance) 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: PeriodicAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: PeriodicAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Basic", 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 + } + if err := m.Basic.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Period", 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 + } + if err := github_com_gogo_protobuf_types.StdDurationUnmarshal(&m.Period, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeriodSpendLimit", 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 + } + m.PeriodSpendLimit = append(m.PeriodSpendLimit, types.Coin{}) + if err := m.PeriodSpendLimit[len(m.PeriodSpendLimit)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeriodCanSpend", 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 + } + m.PeriodCanSpend = append(m.PeriodCanSpend, types.Coin{}) + if err := m.PeriodCanSpend[len(m.PeriodCanSpend)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field PeriodReset", 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 + } + if err := github_com_gogo_protobuf_types.StdTimeUnmarshal(&m.PeriodReset, dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + 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 *AllowedMsgAllowance) 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: AllowedMsgAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: AllowedMsgAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Allowance", 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 + } + if m.Allowance == nil { + m.Allowance = &types1.Any{} + } + if err := m.Allowance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowedMessages", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeegrant + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFeegrant + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFeegrant + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowedMessages = append(m.AllowedMessages, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + 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 + 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: Grant: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: Grant: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Granter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeegrant + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFeegrant + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFeegrant + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Granter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Grantee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowFeegrant + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthFeegrant + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthFeegrant + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Grantee = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Allowance", 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 + } + if m.Allowance == nil { + m.Allowance = &types1.Any{} + } + if err := m.Allowance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + 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 skipFeegrant(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowFeegrant + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowFeegrant + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowFeegrant + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthFeegrant + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupFeegrant + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthFeegrant + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthFeegrant = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowFeegrant = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupFeegrant = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/feegrant/types/fees.go b/x/feegrant/types/fees.go new file mode 100644 index 0000000000..fbd4739fe9 --- /dev/null +++ b/x/feegrant/types/fees.go @@ -0,0 +1,25 @@ +package types + +import ( + sdk "github.com/line/lbm-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 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 RevokeAllowance 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 +} diff --git a/x/feegrant/types/filtered_fee.go b/x/feegrant/types/filtered_fee.go new file mode 100644 index 0000000000..42cc8f3d36 --- /dev/null +++ b/x/feegrant/types/filtered_fee.go @@ -0,0 +1,104 @@ +package types + +import ( + "github.com/gogo/protobuf/proto" + "github.com/line/lbm-sdk/codec/types" + sdk "github.com/line/lbm-sdk/types" + sdkerrors "github.com/line/lbm-sdk/types/errors" +) + +// TODO: Revisit this once we have propoer gas fee framework. +// Tracking issues https://github.com/line/lbm-sdk/issues/9054, https://github.com/line/lbm-sdk/discussions/9072 +const ( + gasCostPerIteration = uint64(10) +) + +var _ FeeAllowanceI = (*AllowedMsgAllowance)(nil) +var _ types.UnpackInterfacesMessage = (*AllowedMsgAllowance)(nil) + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (a *AllowedMsgAllowance) UnpackInterfaces(unpacker types.AnyUnpacker) error { + var allowance FeeAllowanceI + return unpacker.UnpackAny(a.Allowance, &allowance) +} + +// NewAllowedMsgFeeAllowance creates new filtered fee allowance. +func NewAllowedMsgAllowance(allowance FeeAllowanceI, allowedMsgs []string) (*AllowedMsgAllowance, error) { + msg, ok := allowance.(proto.Message) + if !ok { + return nil, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "cannot proto marshal %T", msg) + } + any, err := types.NewAnyWithValue(msg) + if err != nil { + return nil, err + } + + return &AllowedMsgAllowance{ + Allowance: any, + AllowedMessages: allowedMsgs, + }, nil +} + +// GetAllowance returns allowed fee allowance. +func (a *AllowedMsgAllowance) GetAllowance() (FeeAllowanceI, error) { + allowance, ok := a.Allowance.GetCachedValue().(FeeAllowanceI) + if !ok { + return nil, sdkerrors.Wrap(ErrNoAllowance, "failed to get allowance") + } + + return allowance, nil +} + +// Accept method checks for the filtered messages has valid expiry +func (a *AllowedMsgAllowance) Accept(ctx sdk.Context, fee sdk.Coins, msgs []sdk.Msg) (bool, error) { + if !a.allMsgTypesAllowed(ctx, msgs) { + return false, sdkerrors.Wrap(ErrMessageNotAllowed, "message does not exist in allowed messages") + } + + allowance, err := a.GetAllowance() + if err != nil { + return false, err + } + + return allowance.Accept(ctx, fee, msgs) +} + +func (a *AllowedMsgAllowance) allowedMsgsToMap(ctx sdk.Context) map[string]bool { + msgsMap := make(map[string]bool, len(a.AllowedMessages)) + for _, msg := range a.AllowedMessages { + ctx.GasMeter().ConsumeGas(gasCostPerIteration, "check msg") + msgsMap[msg] = true + } + + return msgsMap +} + +func (a *AllowedMsgAllowance) allMsgTypesAllowed(ctx sdk.Context, msgs []sdk.Msg) bool { + msgsMap := a.allowedMsgsToMap(ctx) + + for _, msg := range msgs { + ctx.GasMeter().ConsumeGas(gasCostPerIteration, "check msg") + if !msgsMap[sdk.MsgTypeURL(msg)] { + return false + } + } + + return true +} + +// ValidateBasic implements FeeAllowance and enforces basic sanity checks +func (a *AllowedMsgAllowance) ValidateBasic() error { + if a.Allowance == nil { + return sdkerrors.Wrap(ErrNoAllowance, "allowance should not be empty") + } + if len(a.AllowedMessages) == 0 { + return sdkerrors.Wrap(ErrNoMessages, "allowed messages shouldn't be empty") + } + + allowance, err := a.GetAllowance() + if err != nil { + return err + } + + return allowance.ValidateBasic() +} diff --git a/x/feegrant/types/genesis.go b/x/feegrant/types/genesis.go new file mode 100644 index 0000000000..559effeb68 --- /dev/null +++ b/x/feegrant/types/genesis.go @@ -0,0 +1,46 @@ +package types + +import ( + "github.com/line/lbm-sdk/codec/types" +) + +var _ types.UnpackInterfacesMessage = GenesisState{} + +// NewGenesisState creates new GenesisState object +func NewGenesisState(entries []Grant) *GenesisState { + return &GenesisState{ + Allowances: entries, + } +} + +// ValidateGenesis ensures all grants in the genesis state are valid +func ValidateGenesis(data GenesisState) error { + for _, f := range data.Allowances { + allowance, err := f.GetGrant() + if err != nil { + return err + } + err = allowance.ValidateBasic() + if err != nil { + return err + } + } + return nil +} + +// DefaultGenesisState returns default state for feegrant module. +func DefaultGenesisState() *GenesisState { + return &GenesisState{} +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (data GenesisState) UnpackInterfaces(unpacker types.AnyUnpacker) error { + for _, f := range data.Allowances { + err := f.UnpackInterfaces(unpacker) + if err != nil { + return err + } + } + + return nil +} diff --git a/x/feegrant/types/genesis.pb.go b/x/feegrant/types/genesis.pb.go new file mode 100644 index 0000000000..e990b78693 --- /dev/null +++ b/x/feegrant/types/genesis.pb.go @@ -0,0 +1,330 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: lbm/feegrant/v1/genesis.proto + +package types + +import ( + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + proto "github.com/gogo/protobuf/proto" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// GenesisState contains a set of fee allowances, persisted from the store +type GenesisState struct { + Allowances []Grant `protobuf:"bytes,1,rep,name=allowances,proto3" json:"allowances"` +} + +func (m *GenesisState) Reset() { *m = GenesisState{} } +func (m *GenesisState) String() string { return proto.CompactTextString(m) } +func (*GenesisState) ProtoMessage() {} +func (*GenesisState) Descriptor() ([]byte, []int) { + return fileDescriptor_5d0f05d61b086d9c, []int{0} +} +func (m *GenesisState) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *GenesisState) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_GenesisState.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 *GenesisState) XXX_Merge(src proto.Message) { + xxx_messageInfo_GenesisState.Merge(m, src) +} +func (m *GenesisState) XXX_Size() int { + return m.Size() +} +func (m *GenesisState) XXX_DiscardUnknown() { + xxx_messageInfo_GenesisState.DiscardUnknown(m) +} + +var xxx_messageInfo_GenesisState proto.InternalMessageInfo + +func (m *GenesisState) GetAllowances() []Grant { + if m != nil { + return m.Allowances + } + return nil +} + +func init() { + proto.RegisterType((*GenesisState)(nil), "lbm.feegrant.v1.GenesisState") +} + +func init() { proto.RegisterFile("lbm/feegrant/v1/genesis.proto", fileDescriptor_5d0f05d61b086d9c) } + +var fileDescriptor_5d0f05d61b086d9c = []byte{ + // 204 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xcd, 0x49, 0xca, 0xd5, + 0x4f, 0x4b, 0x4d, 0x4d, 0x2f, 0x4a, 0xcc, 0x2b, 0xd1, 0x2f, 0x33, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, + 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0xcf, 0x49, 0xca, 0xd5, 0x83, + 0x49, 0xeb, 0x95, 0x19, 0x4a, 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0xe5, 0xf4, 0x41, 0x2c, 0x88, + 0x32, 0x29, 0x39, 0x74, 0x53, 0xe0, 0x5a, 0xc0, 0xf2, 0x4a, 0x3e, 0x5c, 0x3c, 0xee, 0x10, 0x73, + 0x83, 0x4b, 0x12, 0x4b, 0x52, 0x85, 0x6c, 0xb8, 0xb8, 0x12, 0x73, 0x72, 0xf2, 0xcb, 0x13, 0xf3, + 0x92, 0x53, 0x8b, 0x25, 0x18, 0x15, 0x98, 0x35, 0xb8, 0x8d, 0xc4, 0xf4, 0xd0, 0xec, 0xd2, 0x73, + 0x07, 0x31, 0x9c, 0x58, 0x4e, 0xdc, 0x93, 0x67, 0x08, 0x42, 0x52, 0xef, 0xe4, 0x74, 0xe2, 0x91, + 0x1c, 0xe3, 0x85, 0x47, 0x72, 0x8c, 0x0f, 0x1e, 0xc9, 0x31, 0x4e, 0x78, 0x2c, 0xc7, 0x70, 0xe1, + 0xb1, 0x1c, 0xc3, 0x8d, 0xc7, 0x72, 0x0c, 0x51, 0x1a, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, + 0xc9, 0xf9, 0xb9, 0xfa, 0x39, 0x99, 0x79, 0xa9, 0xfa, 0x39, 0x49, 0xb9, 0xba, 0xc5, 0x29, 0xd9, + 0xfa, 0x15, 0x08, 0xd7, 0x95, 0x54, 0x16, 0xa4, 0x16, 0x27, 0xb1, 0x81, 0x1d, 0x66, 0x0c, 0x08, + 0x00, 0x00, 0xff, 0xff, 0x52, 0x20, 0x03, 0x5b, 0x00, 0x01, 0x00, 0x00, +} + +func (m *GenesisState) 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 *GenesisState) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *GenesisState) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Allowances) > 0 { + for iNdEx := len(m.Allowances) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Allowances[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintGenesis(dAtA []byte, offset int, v uint64) int { + offset -= sovGenesis(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *GenesisState) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Allowances) > 0 { + for _, e := range m.Allowances { + l = e.Size() + n += 1 + l + sovGenesis(uint64(l)) + } + } + return n +} + +func sovGenesis(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozGenesis(x uint64) (n int) { + return sovGenesis(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *GenesisState) 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 ErrIntOverflowGenesis + } + 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: GenesisState: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: GenesisState: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Allowances", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Allowances = append(m.Allowances, Grant{}) + if err := m.Allowances[len(m.Allowances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenesis(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthGenesis + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipGenesis(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowGenesis + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthGenesis + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupGenesis + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthGenesis + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthGenesis = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowGenesis = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupGenesis = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/feegrant/types/grant.go b/x/feegrant/types/grant.go new file mode 100644 index 0000000000..d98ff4e16c --- /dev/null +++ b/x/feegrant/types/grant.go @@ -0,0 +1,69 @@ +package types + +import ( + "github.com/gogo/protobuf/proto" + "github.com/line/lbm-sdk/codec/types" + sdk "github.com/line/lbm-sdk/types" + sdkerrors "github.com/line/lbm-sdk/types/errors" +) + +var ( + _ types.UnpackInterfacesMessage = &Grant{} +) + +// NewGrant creates a new FeeAllowanceGrant. +//nolint:interfacer +func NewGrant(granter, grantee sdk.AccAddress, feeAllowance FeeAllowanceI) (Grant, error) { + msg, ok := feeAllowance.(proto.Message) + if !ok { + return Grant{}, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "cannot proto marshal %T", feeAllowance) + } + + any, err := types.NewAnyWithValue(msg) + if err != nil { + return Grant{}, err + } + + return Grant{ + Granter: granter.String(), + Grantee: grantee.String(), + Allowance: any, + }, nil +} + +// ValidateBasic performs basic validation on +// FeeAllowanceGrant +func (a Grant) ValidateBasic() error { + if a.Granter == "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing granter address") + } + if a.Grantee == "" { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing grantee address") + } + if a.Grantee == a.Granter { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "cannot self-grant fee authorization") + } + + f, err := a.GetGrant() + if err != nil { + return err + } + + return f.ValidateBasic() +} + +// GetGrant unpacks allowance +func (a Grant) GetGrant() (FeeAllowanceI, error) { + allowance, ok := a.Allowance.GetCachedValue().(FeeAllowanceI) + if !ok { + return nil, sdkerrors.Wrap(ErrNoAllowance, "failed to get allowance") + } + + return allowance, nil +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (a Grant) UnpackInterfaces(unpacker types.AnyUnpacker) error { + var allowance FeeAllowanceI + return unpacker.UnpackAny(a.Allowance, &allowance) +} diff --git a/x/feegrant/types/grant_test.go b/x/feegrant/types/grant_test.go new file mode 100644 index 0000000000..3e5945cec7 --- /dev/null +++ b/x/feegrant/types/grant_test.go @@ -0,0 +1,102 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/line/lbm-sdk/x/feegrant/types" + ocproto "github.com/line/ostracon/proto/ostracon/types" + "github.com/stretchr/testify/require" + + "github.com/line/lbm-sdk/simapp" + sdk "github.com/line/lbm-sdk/types" +) + +func TestGrant(t *testing.T) { + app := simapp.Setup(false) + addr := sdk.AccAddress("link1qyqszqgpqyqszqgpqyqszqgpqyqszqgp8apuk5") + addr2 := sdk.AccAddress("link1ghekyjucln7y67ntx7cf27m9dpuxxemnqk82wt") + atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555)) + ctx := app.BaseApp.NewContext(false, ocproto.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 time.Time + valid bool + }{ + "good": { + granter: addr2, + grantee: addr, + limit: atom, + expires: oneYear, + valid: true, + }, + "no grantee": { + granter: addr2, + grantee: "", + limit: atom, + expires: oneYear, + valid: false, + }, + "no granter": { + granter: "", + grantee: addr, + limit: atom, + expires: oneYear, + valid: false, + }, + "self-grant": { + granter: addr2, + grantee: addr2, + limit: atom, + expires: oneYear, + valid: false, + }, + "zero allowance": { + granter: addr2, + grantee: addr, + limit: zeroAtoms, + expires: oneYear, + valid: false, + }, + } + + for name, tc := range cases { + tc := tc + t.Run(name, func(t *testing.T) { + grant, err := types.NewGrant(tc.granter, tc.grantee, &types.BasicAllowance{ + SpendLimit: tc.limit, + Expiration: &tc.expires, + }) + require.NoError(t, err) + err = grant.ValidateBasic() + + if !tc.valid { + require.Error(t, err) + return + } + require.NoError(t, err) + + // if it is valid, let's try to serialize, deserialize, and make sure it matches + bz, err := cdc.MarshalBinaryBare(&grant) + require.NoError(t, err) + var loaded types.Grant + err = cdc.UnmarshalBinaryBare(bz, &loaded) + require.NoError(t, err) + + err = loaded.ValidateBasic() + require.NoError(t, err) + + require.Equal(t, grant, loaded) + }) + } +} diff --git a/x/feegrant/types/key.go b/x/feegrant/types/key.go new file mode 100644 index 0000000000..9996a57155 --- /dev/null +++ b/x/feegrant/types/key.go @@ -0,0 +1,35 @@ +package types + +import ( + sdk "github.com/line/lbm-sdk/types" +) + +const ( + // ModuleName is the module name constant used in many places + ModuleName = "feegrant" + + // StoreKey is the store key string for supply + StoreKey = ModuleName + + // RouterKey is the message route for supply + RouterKey = ModuleName + + // QuerierRoute is the querier route for supply + QuerierRoute = ModuleName +) + +var ( + // FeeAllowanceKeyPrefix is the set of the kvstore for fee allowance data + FeeAllowanceKeyPrefix = []byte{0x00} +) + +// FeeAllowanceKey is the canonical key to store a grant from granter to grantee +// We store by grantee first to allow searching by everyone who granted to you +func FeeAllowanceKey(granter sdk.AccAddress, grantee sdk.AccAddress) []byte { + return append(FeeAllowancePrefixByGrantee(grantee), granter.Bytes()...) +} + +// FeeAllowancePrefixByGrantee returns a prefix to scan for all grants to this given address. +func FeeAllowancePrefixByGrantee(grantee sdk.AccAddress) []byte { + return append(FeeAllowanceKeyPrefix, grantee.Bytes()...) +} diff --git a/x/feegrant/types/msgs.go b/x/feegrant/types/msgs.go new file mode 100644 index 0000000000..a2d90f6c8d --- /dev/null +++ b/x/feegrant/types/msgs.go @@ -0,0 +1,134 @@ +package types + +import ( + "github.com/gogo/protobuf/proto" + "github.com/line/lbm-sdk/codec/legacy" + "github.com/line/lbm-sdk/codec/types" + sdk "github.com/line/lbm-sdk/types" + sdkerrors "github.com/line/lbm-sdk/types/errors" +) + +var ( + _, _ sdk.Msg = &MsgGrantAllowance{}, &MsgRevokeAllowance{} + + _ types.UnpackInterfacesMessage = &MsgGrantAllowance{} +) + +// NewMsgGrantAllowance creates a new MsgGrantAllowance. +//nolint:interfacer +func NewMsgGrantAllowance(feeAllowance FeeAllowanceI, granter, grantee sdk.AccAddress) (*MsgGrantAllowance, error) { + msg, ok := feeAllowance.(proto.Message) + if !ok { + return nil, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "cannot proto marshal %T", msg) + } + any, err := types.NewAnyWithValue(msg) + if err != nil { + return nil, err + } + + return &MsgGrantAllowance{ + Granter: granter.String(), + Grantee: grantee.String(), + Allowance: any, + }, nil +} + +// ValidateBasic implements the sdk.Msg interface. +func (msg MsgGrantAllowance) ValidateBasic() error { + if err := sdk.ValidateAccAddress(msg.Granter); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid granter address: %s", err) + } + if err := sdk.ValidateAccAddress(msg.Grantee); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid grantee address: %s", err) + } + if msg.Grantee == msg.Granter { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "cannot self-grant fee authorization") + } + allowance, err := msg.GetFeeAllowanceI() + if err != nil { + return err + } + + return allowance.ValidateBasic() +} + +// GetSigners gets the granter account associated with an allowance +func (msg MsgGrantAllowance) GetSigners() []sdk.AccAddress { + granter := sdk.AccAddress(msg.Granter) + return []sdk.AccAddress{granter} +} + +// Type implements the LegacyMsg.Type method. +func (msg MsgGrantAllowance) Type() string { + return sdk.MsgTypeURL(&msg) +} + +// Route implements the LegacyMsg.Route method. +func (msg MsgGrantAllowance) Route() string { + return RouterKey +} + +// GetSignBytes implements the LegacyMsg.GetSignBytes method. +func (msg MsgGrantAllowance) GetSignBytes() []byte { + return sdk.MustSortJSON(legacy.Cdc.MustMarshalJSON(&msg)) +} + +// GetFeeAllowanceI returns unpacked FeeAllowance +func (msg MsgGrantAllowance) GetFeeAllowanceI() (FeeAllowanceI, error) { + allowance, ok := msg.Allowance.GetCachedValue().(FeeAllowanceI) + if !ok { + return nil, sdkerrors.Wrap(ErrNoAllowance, "failed to get allowance") + } + + return allowance, nil +} + +// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces +func (msg MsgGrantAllowance) UnpackInterfaces(unpacker types.AnyUnpacker) error { + var allowance FeeAllowanceI + return unpacker.UnpackAny(msg.Allowance, &allowance) +} + +// NewMsgRevokeAllowance returns a message to revoke a fee allowance for a given +// granter and grantee +//nolint:interfacer +func NewMsgRevokeAllowance(granter sdk.AccAddress, grantee sdk.AccAddress) MsgRevokeAllowance { + return MsgRevokeAllowance{Granter: granter.String(), Grantee: grantee.String()} +} + +// ValidateBasic implements the sdk.Msg interface. +func (msg MsgRevokeAllowance) ValidateBasic() error { + if err := sdk.ValidateAccAddress(msg.Granter); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid granter address: %s", err) + } + if err := sdk.ValidateAccAddress(msg.Grantee); err != nil { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid grantee address: %s", err) + } + if msg.Grantee == msg.Granter { + return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "addresses must be different") + } + + return nil +} + +// GetSigners gets the granter address associated with an Allowance +// to revoke. +func (msg MsgRevokeAllowance) GetSigners() []sdk.AccAddress { + granter := sdk.AccAddress(msg.Granter) + return []sdk.AccAddress{granter} +} + +// Type implements the LegacyMsg.Type method. +func (msg MsgRevokeAllowance) Type() string { + return sdk.MsgTypeURL(&msg) +} + +// Route implements the LegacyMsg.Route method. +func (msg MsgRevokeAllowance) Route() string { + return RouterKey +} + +// GetSignBytes implements the LegacyMsg.GetSignBytes method. +func (msg MsgRevokeAllowance) GetSignBytes() []byte { + return sdk.MustSortJSON(legacy.Cdc.MustMarshalJSON(&msg)) +} diff --git a/x/feegrant/types/msgs_test.go b/x/feegrant/types/msgs_test.go new file mode 100644 index 0000000000..68001b2680 --- /dev/null +++ b/x/feegrant/types/msgs_test.go @@ -0,0 +1,134 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/line/lbm-sdk/x/feegrant/types" + "github.com/stretchr/testify/require" + + "github.com/line/lbm-sdk/codec" + codectypes "github.com/line/lbm-sdk/codec/types" + sdk "github.com/line/lbm-sdk/types" +) + +func TestMsgGrantAllowance(t *testing.T) { + cdc := codec.NewProtoCodec(codectypes.NewInterfaceRegistry()) + addr := sdk.AccAddress("link1ghekyjucln7y67ntx7cf27m9dpuxxemnqk82wt") + addr2 := sdk.AccAddress("link18vd8fpwxzck93qlwghaj6arh4p7c5n89fvcmzu") + atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555)) + threeHours := time.Now().Add(3 * time.Hour) + basic := &types.BasicAllowance{ + SpendLimit: atom, + Expiration: &threeHours, + } + + cases := map[string]struct { + grantee sdk.AccAddress + granter sdk.AccAddress + grant *types.BasicAllowance + valid bool + }{ + "valid": { + grantee: addr, + granter: addr2, + grant: basic, + valid: true, + }, + "no grantee": { + granter: addr2, + grantee: sdk.AccAddress(""), + grant: basic, + valid: false, + }, + "no granter": { + granter: sdk.AccAddress(""), + grantee: addr, + grant: basic, + valid: false, + }, + "grantee == granter": { + grantee: addr, + granter: addr, + grant: basic, + valid: false, + }, + } + + for _, tc := range cases { + msg, err := types.NewMsgGrantAllowance(tc.grant, tc.granter, tc.grantee) + require.NoError(t, err) + err = msg.ValidateBasic() + + if tc.valid { + require.NoError(t, err) + + addrSlice := msg.GetSigners() + require.True(t, tc.granter.Equals(addrSlice[0])) + + allowance, err := msg.GetFeeAllowanceI() + require.NoError(t, err) + require.Equal(t, tc.grant, allowance) + + err = msg.UnpackInterfaces(cdc) + require.NoError(t, err) + } else { + require.Error(t, err) + } + } +} + +func TestMsgRevokeAllowance(t *testing.T) { + addr := sdk.AccAddress("link1ghekyjucln7y67ntx7cf27m9dpuxxemnqk82wt") + addr2 := sdk.AccAddress("link18vd8fpwxzck93qlwghaj6arh4p7c5n89fvcmzu") + atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555)) + threeHours := time.Now().Add(3 * time.Hour) + + basic := &types.BasicAllowance{ + SpendLimit: atom, + Expiration: &threeHours, + } + cases := map[string]struct { + grantee sdk.AccAddress + granter sdk.AccAddress + grant *types.BasicAllowance + valid bool + }{ + "valid": { + grantee: addr, + granter: addr2, + grant: basic, + valid: true, + }, + "no grantee": { + granter: addr2, + grantee: sdk.AccAddress(""), + grant: basic, + valid: false, + }, + "no granter": { + granter: sdk.AccAddress(""), + grantee: addr, + grant: basic, + valid: false, + }, + "grantee == granter": { + grantee: addr, + granter: addr, + grant: basic, + valid: false, + }, + } + + for _, tc := range cases { + msg := types.NewMsgRevokeAllowance(tc.granter, tc.grantee) + err := msg.ValidateBasic() + if tc.valid { + require.NoError(t, err) + addrSlice := msg.GetSigners() + require.True(t, tc.granter.Equals(addrSlice[0])) + } else { + require.Error(t, err) + } + } +} diff --git a/x/feegrant/types/periodic_fee.go b/x/feegrant/types/periodic_fee.go new file mode 100644 index 0000000000..396f233635 --- /dev/null +++ b/x/feegrant/types/periodic_fee.go @@ -0,0 +1,107 @@ +package types + +import ( + "time" + + sdk "github.com/line/lbm-sdk/types" + sdkerrors "github.com/line/lbm-sdk/types/errors" +) + +var _ FeeAllowanceI = (*PeriodicAllowance)(nil) + +// 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 RevokeAllowance in Keeper.UseGrantedFees) +func (a *PeriodicAllowance) Accept(ctx sdk.Context, fee sdk.Coins, _ []sdk.Msg) (bool, error) { + blockTime := ctx.BlockTime() + + if a.Basic.Expiration != nil && blockTime.After(*a.Basic.Expiration) { + return true, sdkerrors.Wrap(ErrFeeLimitExpired, "absolute limit") + } + + a.tryResetPeriod(blockTime) + + // deduct from both the current period and the max amount + var isNeg bool + a.PeriodCanSpend, isNeg = a.PeriodCanSpend.SafeSub(fee) + if isNeg { + return false, sdkerrors.Wrap(ErrFeeLimitExceeded, "period limit") + } + + if a.Basic.SpendLimit != nil { + a.Basic.SpendLimit, isNeg = a.Basic.SpendLimit.SafeSub(fee) + if isNeg { + return false, sdkerrors.Wrap(ErrFeeLimitExceeded, "absolute limit") + } + + return a.Basic.SpendLimit.IsZero(), nil + } + + return false, nil +} + +// tryResetPeriod will check if the PeriodReset has been hit. If not, it is a no-op. +// If we hit the reset period, it will top up the PeriodCanSpend amount to +// min(PeriodSpendLimit, Basic.SpendLimit) so it is never more than the maximum allowed. +// 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) { + if blockTime.Before(a.PeriodReset) { + return + } + + // set PeriodCanSpend to the lesser of Basic.SpendLimit and PeriodSpendLimit + if _, isNeg := a.Basic.SpendLimit.SafeSub(a.PeriodSpendLimit); isNeg && !a.Basic.SpendLimit.Empty() { + a.PeriodCanSpend = a.Basic.SpendLimit + } else { + a.PeriodCanSpend = a.PeriodSpendLimit + } + + // 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.Add(a.Period) + if blockTime.After(a.PeriodReset) { + a.PeriodReset = blockTime.Add(a.Period) + } +} + +// ValidateBasic implements FeeAllowance and enforces basic sanity checks +func (a PeriodicAllowance) ValidateBasic() error { + if err := a.Basic.ValidateBasic(); err != nil { + return err + } + + if !a.PeriodSpendLimit.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "spend amount is invalid: %s", a.PeriodSpendLimit) + } + if !a.PeriodSpendLimit.IsAllPositive() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "spend limit must be positive") + } + if !a.PeriodCanSpend.IsValid() { + return sdkerrors.Wrapf(sdkerrors.ErrInvalidCoins, "can spend amount is invalid: %s", a.PeriodCanSpend) + } + // We allow 0 for CanSpend + if a.PeriodCanSpend.IsAnyNegative() { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "can spend must not be negative") + } + + // ensure PeriodSpendLimit can be subtracted from total (same coin types) + if a.Basic.SpendLimit != nil && !a.PeriodSpendLimit.DenomsSubsetOf(a.Basic.SpendLimit) { + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "period spend limit has different currency than basic spend limit") + } + + // check times + if a.Period.Seconds() < 0 { + return sdkerrors.Wrap(ErrInvalidDuration, "negative clock step") + } + + return nil +} diff --git a/x/feegrant/types/periodic_fee_test.go b/x/feegrant/types/periodic_fee_test.go new file mode 100644 index 0000000000..9949df65db --- /dev/null +++ b/x/feegrant/types/periodic_fee_test.go @@ -0,0 +1,212 @@ +package types_test + +import ( + "testing" + "time" + + "github.com/line/lbm-sdk/x/feegrant/types" + tmproto "github.com/line/ostracon/proto/ostracon/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/line/lbm-sdk/simapp" + sdk "github.com/line/lbm-sdk/types" +) + +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)) + + 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 + fee sdk.Coins + blockTime time.Time + valid bool // all other checks are ignored if valid=false + accept bool + remove bool + remains sdk.Coins + remainsPeriod sdk.Coins + periodReset time.Time + }{ + "empty": { + allow: types.PeriodicAllowance{}, + valid: false, + }, + "only basic": { + allow: types.PeriodicAllowance{ + Basic: types.BasicAllowance{ + SpendLimit: atom, + Expiration: &oneHour, + }, + }, + valid: false, + }, + "empty basic": { + allow: types.PeriodicAllowance{ + Period: tenMinutes, + PeriodSpendLimit: smallAtom, + PeriodReset: now.Add(30 * time.Minute), + }, + blockTime: now, + valid: true, + accept: true, + remove: false, + periodReset: now.Add(30 * time.Minute), + }, + "mismatched currencies": { + allow: types.PeriodicAllowance{ + Basic: types.BasicAllowance{ + SpendLimit: atom, + Expiration: &oneHour, + }, + Period: tenMinutes, + PeriodSpendLimit: eth, + }, + valid: false, + }, + "same period": { + allow: types.PeriodicAllowance{ + Basic: types.BasicAllowance{ + SpendLimit: atom, + Expiration: &twoHours, + }, + Period: tenMinutes, + PeriodReset: now.Add(1 * time.Hour), + PeriodSpendLimit: leftAtom, + PeriodCanSpend: smallAtom, + }, + valid: true, + fee: smallAtom, + blockTime: now, + accept: true, + remove: false, + remainsPeriod: nil, + remains: leftAtom, + periodReset: now.Add(1 * time.Hour), + }, + "step one period": { + allow: types.PeriodicAllowance{ + Basic: types.BasicAllowance{ + SpendLimit: atom, + Expiration: &twoHours, + }, + Period: tenMinutes, + PeriodReset: now, + PeriodSpendLimit: leftAtom, + }, + valid: true, + fee: leftAtom, + blockTime: now.Add(1 * time.Hour), + accept: true, + remove: false, + remainsPeriod: nil, + remains: smallAtom, + periodReset: oneHour.Add(tenMinutes), // one step from last reset, not now + }, + "step limited by global allowance": { + allow: types.PeriodicAllowance{ + Basic: types.BasicAllowance{ + SpendLimit: smallAtom, + Expiration: &twoHours, + }, + Period: tenMinutes, + PeriodReset: now, + PeriodSpendLimit: atom, + }, + valid: true, + fee: oneAtom, + blockTime: oneHour, + accept: true, + remove: false, + remainsPeriod: smallAtom.Sub(oneAtom), + remains: smallAtom.Sub(oneAtom), + periodReset: oneHour.Add(tenMinutes), // one step from last reset, not now + }, + "period reset no spend limit": { + allow: types.PeriodicAllowance{ + Period: tenMinutes, + PeriodReset: now, + PeriodSpendLimit: atom, + }, + valid: true, + fee: atom, + blockTime: oneHour, + accept: true, + remove: false, + periodReset: oneHour.Add(tenMinutes), // one step from last reset, not now + }, + "expired": { + allow: types.PeriodicAllowance{ + Basic: types.BasicAllowance{ + SpendLimit: atom, + Expiration: &now, + }, + Period: time.Hour, + PeriodSpendLimit: smallAtom, + }, + valid: true, + fee: smallAtom, + blockTime: oneHour, + accept: false, + remove: true, + }, + "over period limit": { + allow: types.PeriodicAllowance{ + Basic: types.BasicAllowance{ + SpendLimit: atom, + Expiration: &now, + }, + Period: time.Hour, + PeriodReset: now.Add(1 * time.Hour), + PeriodSpendLimit: leftAtom, + PeriodCanSpend: smallAtom, + }, + valid: true, + fee: leftAtom, + blockTime: now, + accept: false, + remove: true, + }, + } + + 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.remains, tc.allow.Basic.SpendLimit) + assert.Equal(t, tc.remainsPeriod, tc.allow.PeriodCanSpend) + assert.Equal(t, tc.periodReset.String(), tc.allow.PeriodReset.String()) + } + }) + } +} diff --git a/x/feegrant/types/query.pb.go b/x/feegrant/types/query.pb.go new file mode 100644 index 0000000000..3a9b57f8b4 --- /dev/null +++ b/x/feegrant/types/query.pb.go @@ -0,0 +1,1172 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: lbm/feegrant/v1/query.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + query "github.com/line/lbm-sdk/types/query" + _ "google.golang.org/genproto/googleapis/api/annotations" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// QueryAllowanceRequest is the request type for the Query/Allowance RPC method. +type QueryAllowanceRequest struct { + // granter is the address of the user granting an allowance of their funds. + Granter string `protobuf:"bytes,1,opt,name=granter,proto3" json:"granter,omitempty" yaml:"granter_address"` + // grantee is the address of the user being granted an allowance of another user's funds. + Grantee string `protobuf:"bytes,2,opt,name=grantee,proto3" json:"grantee,omitempty" yaml:"grantee_address"` +} + +func (m *QueryAllowanceRequest) Reset() { *m = QueryAllowanceRequest{} } +func (m *QueryAllowanceRequest) String() string { return proto.CompactTextString(m) } +func (*QueryAllowanceRequest) ProtoMessage() {} +func (*QueryAllowanceRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e2fd9dc33a9e9ee8, []int{0} +} +func (m *QueryAllowanceRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAllowanceRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAllowanceRequest.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 *QueryAllowanceRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAllowanceRequest.Merge(m, src) +} +func (m *QueryAllowanceRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryAllowanceRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAllowanceRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAllowanceRequest proto.InternalMessageInfo + +func (m *QueryAllowanceRequest) GetGranter() string { + if m != nil { + return m.Granter + } + return "" +} + +func (m *QueryAllowanceRequest) GetGrantee() string { + if m != nil { + return m.Grantee + } + return "" +} + +// QueryAllowanceResponse is the response type for the Query/Allowance RPC method. +type QueryAllowanceResponse struct { + // allowance is a allowance granted for grantee by granter. + Allowance *Grant `protobuf:"bytes,1,opt,name=allowance,proto3" json:"allowance,omitempty"` +} + +func (m *QueryAllowanceResponse) Reset() { *m = QueryAllowanceResponse{} } +func (m *QueryAllowanceResponse) String() string { return proto.CompactTextString(m) } +func (*QueryAllowanceResponse) ProtoMessage() {} +func (*QueryAllowanceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_e2fd9dc33a9e9ee8, []int{1} +} +func (m *QueryAllowanceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAllowanceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAllowanceResponse.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 *QueryAllowanceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAllowanceResponse.Merge(m, src) +} +func (m *QueryAllowanceResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryAllowanceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAllowanceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAllowanceResponse proto.InternalMessageInfo + +func (m *QueryAllowanceResponse) GetAllowance() *Grant { + if m != nil { + return m.Allowance + } + return nil +} + +// QueryAllowancesRequest is the request type for the Query/Allowances RPC method. +type QueryAllowancesRequest struct { + Grantee string `protobuf:"bytes,1,opt,name=grantee,proto3" json:"grantee,omitempty" yaml:"grantee_address"` + // pagination defines an pagination for the request. + Pagination *query.PageRequest `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryAllowancesRequest) Reset() { *m = QueryAllowancesRequest{} } +func (m *QueryAllowancesRequest) String() string { return proto.CompactTextString(m) } +func (*QueryAllowancesRequest) ProtoMessage() {} +func (*QueryAllowancesRequest) Descriptor() ([]byte, []int) { + return fileDescriptor_e2fd9dc33a9e9ee8, []int{2} +} +func (m *QueryAllowancesRequest) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAllowancesRequest) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAllowancesRequest.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 *QueryAllowancesRequest) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAllowancesRequest.Merge(m, src) +} +func (m *QueryAllowancesRequest) XXX_Size() int { + return m.Size() +} +func (m *QueryAllowancesRequest) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAllowancesRequest.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAllowancesRequest proto.InternalMessageInfo + +func (m *QueryAllowancesRequest) GetGrantee() string { + if m != nil { + return m.Grantee + } + return "" +} + +func (m *QueryAllowancesRequest) GetPagination() *query.PageRequest { + if m != nil { + return m.Pagination + } + return nil +} + +// QueryAllowancesResponse is the response type for the Query/Allowances RPC method. +type QueryAllowancesResponse struct { + // allowances are allowance's granted for grantee by granter. + Allowances []*Grant `protobuf:"bytes,1,rep,name=allowances,proto3" json:"allowances,omitempty"` + // pagination defines an pagination for the response. + Pagination *query.PageResponse `protobuf:"bytes,2,opt,name=pagination,proto3" json:"pagination,omitempty"` +} + +func (m *QueryAllowancesResponse) Reset() { *m = QueryAllowancesResponse{} } +func (m *QueryAllowancesResponse) String() string { return proto.CompactTextString(m) } +func (*QueryAllowancesResponse) ProtoMessage() {} +func (*QueryAllowancesResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_e2fd9dc33a9e9ee8, []int{3} +} +func (m *QueryAllowancesResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *QueryAllowancesResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_QueryAllowancesResponse.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 *QueryAllowancesResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_QueryAllowancesResponse.Merge(m, src) +} +func (m *QueryAllowancesResponse) XXX_Size() int { + return m.Size() +} +func (m *QueryAllowancesResponse) XXX_DiscardUnknown() { + xxx_messageInfo_QueryAllowancesResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_QueryAllowancesResponse proto.InternalMessageInfo + +func (m *QueryAllowancesResponse) GetAllowances() []*Grant { + if m != nil { + return m.Allowances + } + return nil +} + +func (m *QueryAllowancesResponse) GetPagination() *query.PageResponse { + if m != nil { + return m.Pagination + } + return nil +} + +func init() { + proto.RegisterType((*QueryAllowanceRequest)(nil), "lbm.feegrant.v1.QueryAllowanceRequest") + proto.RegisterType((*QueryAllowanceResponse)(nil), "lbm.feegrant.v1.QueryAllowanceResponse") + proto.RegisterType((*QueryAllowancesRequest)(nil), "lbm.feegrant.v1.QueryAllowancesRequest") + proto.RegisterType((*QueryAllowancesResponse)(nil), "lbm.feegrant.v1.QueryAllowancesResponse") +} + +func init() { proto.RegisterFile("lbm/feegrant/v1/query.proto", fileDescriptor_e2fd9dc33a9e9ee8) } + +var fileDescriptor_e2fd9dc33a9e9ee8 = []byte{ + // 464 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x84, 0x93, 0x31, 0x8f, 0xd3, 0x30, + 0x14, 0xc7, 0xeb, 0x22, 0x40, 0xf5, 0x0d, 0x48, 0x16, 0x94, 0x2a, 0x20, 0xdf, 0xc9, 0x12, 0x5c, + 0x97, 0xb3, 0x69, 0x39, 0xdd, 0xc0, 0x00, 0xa2, 0x0b, 0x1b, 0x82, 0x8e, 0x2c, 0xc8, 0xb9, 0x3e, + 0x4c, 0x44, 0x6a, 0xe7, 0xe2, 0xb4, 0x50, 0xa1, 0x5b, 0x60, 0x3f, 0x21, 0xe0, 0x7b, 0xf0, 0x35, + 0x18, 0x2b, 0xb1, 0x30, 0x21, 0xd4, 0xf2, 0x09, 0xf8, 0x04, 0x28, 0x4e, 0xd2, 0x54, 0x69, 0x4a, + 0x37, 0x2b, 0xff, 0xff, 0x7b, 0xef, 0xf7, 0x7f, 0x76, 0xf0, 0xad, 0xd0, 0x1f, 0x8b, 0x57, 0x00, + 0x2a, 0x96, 0x3a, 0x11, 0xd3, 0x9e, 0x38, 0x9b, 0x40, 0x3c, 0xe3, 0x51, 0x6c, 0x12, 0x43, 0xae, + 0x85, 0xfe, 0x98, 0x17, 0x22, 0x9f, 0xf6, 0xbc, 0xeb, 0xca, 0x28, 0xe3, 0x34, 0x91, 0x9e, 0x32, + 0x9b, 0x47, 0xab, 0x3d, 0x56, 0x25, 0x99, 0xce, 0x52, 0xdd, 0x97, 0x16, 0xb2, 0xe6, 0xa9, 0x23, + 0x92, 0x2a, 0xd0, 0x32, 0x09, 0x8c, 0xce, 0x3d, 0xb7, 0x95, 0x31, 0x2a, 0x04, 0x21, 0xa3, 0x40, + 0x48, 0xad, 0x4d, 0xe2, 0x44, 0x9b, 0xa9, 0xec, 0x23, 0xc2, 0x37, 0x9e, 0xa7, 0xb5, 0x8f, 0xc3, + 0xd0, 0xbc, 0x95, 0xfa, 0x14, 0x86, 0x70, 0x36, 0x01, 0x9b, 0x90, 0x63, 0x7c, 0xd5, 0x8d, 0x82, + 0xb8, 0x83, 0x0e, 0x50, 0xb7, 0x35, 0xf0, 0xfe, 0xfe, 0xda, 0x6f, 0xcf, 0xe4, 0x38, 0x7c, 0xc0, + 0x72, 0xe1, 0xa5, 0x1c, 0x8d, 0x62, 0xb0, 0x96, 0x0d, 0x0b, 0x6b, 0x59, 0x05, 0x9d, 0x66, 0x7d, + 0x15, 0x6c, 0x54, 0x01, 0x7b, 0x8a, 0xdb, 0x55, 0x08, 0x1b, 0x19, 0x6d, 0x81, 0x1c, 0xe3, 0x96, + 0x2c, 0x3e, 0x3a, 0x8e, 0xbd, 0x7e, 0x9b, 0x57, 0x96, 0xc7, 0x9f, 0xa4, 0x87, 0x61, 0x69, 0x64, + 0x17, 0xa8, 0xda, 0xd0, 0x6e, 0xc4, 0x82, 0x6d, 0xb1, 0x6a, 0x00, 0xc9, 0x43, 0x8c, 0xcb, 0xc5, + 0xba, 0x64, 0x7b, 0x7d, 0xea, 0x38, 0xd2, 0xed, 0xf3, 0xec, 0x6a, 0xa7, 0x3d, 0xfe, 0x4c, 0xaa, + 0x62, 0x81, 0xc3, 0xb5, 0x0a, 0xf6, 0x19, 0xe1, 0x9b, 0x1b, 0x40, 0x79, 0xc4, 0x13, 0x8c, 0x57, + 0xe4, 0xb6, 0x83, 0x0e, 0x2e, 0xfd, 0x27, 0xe3, 0x9a, 0x93, 0x3c, 0xaa, 0x61, 0xda, 0xdf, 0xca, + 0x94, 0x0d, 0x5b, 0x87, 0xea, 0x7f, 0x6b, 0xe2, 0xcb, 0x0e, 0x8a, 0x7c, 0x45, 0xb8, 0xb5, 0x22, + 0x23, 0x77, 0x37, 0x86, 0xd7, 0xbe, 0x10, 0xef, 0x70, 0xa7, 0x2f, 0x1b, 0xca, 0x4e, 0x3e, 0xfc, + 0xf8, 0xf3, 0xa5, 0x79, 0x8f, 0x70, 0x51, 0x7d, 0xcf, 0xab, 0x38, 0xe2, 0x7d, 0xfe, 0x82, 0xce, + 0x8b, 0x13, 0x9c, 0x93, 0x0b, 0x84, 0x71, 0xb9, 0x30, 0xb2, 0x6b, 0x5e, 0x71, 0xc7, 0x5e, 0x77, + 0xb7, 0x31, 0x27, 0x3b, 0x72, 0x64, 0x87, 0xe4, 0xce, 0x76, 0x32, 0x5b, 0x02, 0x0d, 0x06, 0xdf, + 0x17, 0x14, 0xcd, 0x17, 0x14, 0xfd, 0x5e, 0x50, 0xf4, 0x69, 0x49, 0x1b, 0xf3, 0x25, 0x6d, 0xfc, + 0x5c, 0xd2, 0xc6, 0x8b, 0xae, 0x0a, 0x92, 0xd7, 0x13, 0x9f, 0x9f, 0x9a, 0xb1, 0x08, 0x03, 0x0d, + 0x69, 0xbf, 0x23, 0x3b, 0x7a, 0x23, 0xde, 0x95, 0x5d, 0x93, 0x59, 0x04, 0xd6, 0xbf, 0xe2, 0x7e, + 0xbc, 0xfb, 0xff, 0x02, 0x00, 0x00, 0xff, 0xff, 0x83, 0xe4, 0xf8, 0x5c, 0x20, 0x04, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// QueryClient is the client API for Query service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type QueryClient interface { + // Allowance returns fee granted to the grantee by the granter. + Allowance(ctx context.Context, in *QueryAllowanceRequest, opts ...grpc.CallOption) (*QueryAllowanceResponse, error) + // Allowances returns all the grants for address. + Allowances(ctx context.Context, in *QueryAllowancesRequest, opts ...grpc.CallOption) (*QueryAllowancesResponse, error) +} + +type queryClient struct { + cc grpc1.ClientConn +} + +func NewQueryClient(cc grpc1.ClientConn) QueryClient { + return &queryClient{cc} +} + +func (c *queryClient) Allowance(ctx context.Context, in *QueryAllowanceRequest, opts ...grpc.CallOption) (*QueryAllowanceResponse, error) { + out := new(QueryAllowanceResponse) + err := c.cc.Invoke(ctx, "/lbm.feegrant.v1.Query/Allowance", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *queryClient) Allowances(ctx context.Context, in *QueryAllowancesRequest, opts ...grpc.CallOption) (*QueryAllowancesResponse, error) { + out := new(QueryAllowancesResponse) + err := c.cc.Invoke(ctx, "/lbm.feegrant.v1.Query/Allowances", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// QueryServer is the server API for Query service. +type QueryServer interface { + // Allowance returns fee granted to the grantee by the granter. + Allowance(context.Context, *QueryAllowanceRequest) (*QueryAllowanceResponse, error) + // Allowances returns all the grants for address. + Allowances(context.Context, *QueryAllowancesRequest) (*QueryAllowancesResponse, error) +} + +// UnimplementedQueryServer can be embedded to have forward compatible implementations. +type UnimplementedQueryServer struct { +} + +func (*UnimplementedQueryServer) Allowance(ctx context.Context, req *QueryAllowanceRequest) (*QueryAllowanceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Allowance not implemented") +} +func (*UnimplementedQueryServer) Allowances(ctx context.Context, req *QueryAllowancesRequest) (*QueryAllowancesResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method Allowances not implemented") +} + +func RegisterQueryServer(s grpc1.Server, srv QueryServer) { + s.RegisterService(&_Query_serviceDesc, srv) +} + +func _Query_Allowance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryAllowanceRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Allowance(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lbm.feegrant.v1.Query/Allowance", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Allowance(ctx, req.(*QueryAllowanceRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _Query_Allowances_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(QueryAllowancesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(QueryServer).Allowances(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lbm.feegrant.v1.Query/Allowances", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(QueryServer).Allowances(ctx, req.(*QueryAllowancesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +var _Query_serviceDesc = grpc.ServiceDesc{ + ServiceName: "lbm.feegrant.v1.Query", + HandlerType: (*QueryServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "Allowance", + Handler: _Query_Allowance_Handler, + }, + { + MethodName: "Allowances", + Handler: _Query_Allowances_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "lbm/feegrant/v1/query.proto", +} + +func (m *QueryAllowanceRequest) 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 *QueryAllowanceRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAllowanceRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Grantee) > 0 { + i -= len(m.Grantee) + copy(dAtA[i:], m.Grantee) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Grantee))) + i-- + dAtA[i] = 0x12 + } + if len(m.Granter) > 0 { + i -= len(m.Granter) + copy(dAtA[i:], m.Granter) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Granter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryAllowanceResponse) 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 *QueryAllowanceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAllowanceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Allowance != nil { + { + size, err := m.Allowance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryAllowancesRequest) 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 *QueryAllowancesRequest) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAllowancesRequest) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Grantee) > 0 { + i -= len(m.Grantee) + copy(dAtA[i:], m.Grantee) + i = encodeVarintQuery(dAtA, i, uint64(len(m.Grantee))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *QueryAllowancesResponse) 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 *QueryAllowancesResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *QueryAllowancesResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Pagination != nil { + { + size, err := m.Pagination.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x12 + } + if len(m.Allowances) > 0 { + for iNdEx := len(m.Allowances) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.Allowances[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintQuery(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0xa + } + } + return len(dAtA) - i, nil +} + +func encodeVarintQuery(dAtA []byte, offset int, v uint64) int { + offset -= sovQuery(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *QueryAllowanceRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Granter) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + l = len(m.Grantee) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryAllowanceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if m.Allowance != nil { + l = m.Allowance.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryAllowancesRequest) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Grantee) + if l > 0 { + n += 1 + l + sovQuery(uint64(l)) + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func (m *QueryAllowancesResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + if len(m.Allowances) > 0 { + for _, e := range m.Allowances { + l = e.Size() + n += 1 + l + sovQuery(uint64(l)) + } + } + if m.Pagination != nil { + l = m.Pagination.Size() + n += 1 + l + sovQuery(uint64(l)) + } + return n +} + +func sovQuery(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozQuery(x uint64) (n int) { + return sovQuery(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *QueryAllowanceRequest) 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 ErrIntOverflowQuery + } + 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: QueryAllowanceRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAllowanceRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Granter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Granter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Grantee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Grantee = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAllowanceResponse) 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 ErrIntOverflowQuery + } + 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: QueryAllowanceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAllowanceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Allowance", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Allowance == nil { + m.Allowance = &Grant{} + } + if err := m.Allowance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAllowancesRequest) 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 ErrIntOverflowQuery + } + 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: QueryAllowancesRequest: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAllowancesRequest: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Grantee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Grantee = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageRequest{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *QueryAllowancesResponse) 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 ErrIntOverflowQuery + } + 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: QueryAllowancesResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: QueryAllowancesResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Allowances", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Allowances = append(m.Allowances, &Grant{}) + if err := m.Allowances[len(m.Allowances)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Pagination", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowQuery + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthQuery + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthQuery + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Pagination == nil { + m.Pagination = &query.PageResponse{} + } + if err := m.Pagination.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipQuery(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthQuery + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipQuery(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowQuery + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthQuery + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupQuery + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthQuery + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthQuery = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowQuery = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupQuery = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/feegrant/types/query.pb.gw.go b/x/feegrant/types/query.pb.gw.go new file mode 100644 index 0000000000..3df57aa1b8 --- /dev/null +++ b/x/feegrant/types/query.pb.gw.go @@ -0,0 +1,322 @@ +// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT. +// source: lbm/feegrant/v1/query.proto + +/* +Package types is a reverse proxy. + +It translates gRPC into RESTful JSON APIs. +*/ +package types + +import ( + "context" + "io" + "net/http" + + "github.com/golang/protobuf/descriptor" + "github.com/golang/protobuf/proto" + "github.com/grpc-ecosystem/grpc-gateway/runtime" + "github.com/grpc-ecosystem/grpc-gateway/utilities" + "google.golang.org/grpc" + "google.golang.org/grpc/codes" + "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/status" +) + +// Suppress "imported and not used" errors +var _ codes.Code +var _ io.Reader +var _ status.Status +var _ = runtime.String +var _ = utilities.NewDoubleArray +var _ = descriptor.ForMessage + +func request_Query_Allowance_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAllowanceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["granter"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "granter") + } + + protoReq.Granter, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "granter", err) + } + + val, ok = pathParams["grantee"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "grantee") + } + + protoReq.Grantee, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "grantee", err) + } + + msg, err := client.Allowance(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Allowance_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAllowanceRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["granter"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "granter") + } + + protoReq.Granter, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "granter", err) + } + + val, ok = pathParams["grantee"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "grantee") + } + + protoReq.Grantee, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "grantee", err) + } + + msg, err := server.Allowance(ctx, &protoReq) + return msg, metadata, err + +} + +var ( + filter_Query_Allowances_0 = &utilities.DoubleArray{Encoding: map[string]int{"grantee": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}} +) + +func request_Query_Allowances_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAllowancesRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["grantee"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "grantee") + } + + protoReq.Grantee, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "grantee", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Allowances_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := client.Allowances(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD)) + return msg, metadata, err + +} + +func local_request_Query_Allowances_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) { + var protoReq QueryAllowancesRequest + var metadata runtime.ServerMetadata + + var ( + val string + ok bool + err error + _ = err + ) + + val, ok = pathParams["grantee"] + if !ok { + return nil, metadata, status.Errorf(codes.InvalidArgument, "missing parameter %s", "grantee") + } + + protoReq.Grantee, err = runtime.String(val) + + if err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "type mismatch, parameter: %s, error: %v", "grantee", err) + } + + if err := req.ParseForm(); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + if err := runtime.PopulateQueryParameters(&protoReq, req.Form, filter_Query_Allowances_0); err != nil { + return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err) + } + + msg, err := server.Allowances(ctx, &protoReq) + return msg, metadata, err + +} + +// RegisterQueryHandlerServer registers the http handlers for service Query to "mux". +// UnaryRPC :call QueryServer directly. +// StreamingRPC :currently unsupported pending https://github.com/grpc/grpc-go/issues/906. +// Note that using this registration option will cause many gRPC library features (such as grpc.SendHeader, etc) to stop working. Consider using RegisterQueryHandlerFromEndpoint instead. +func RegisterQueryHandlerServer(ctx context.Context, mux *runtime.ServeMux, server QueryServer) error { + + mux.Handle("GET", pattern_Query_Allowance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Allowance_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Allowance_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Allowances_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateIncomingContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := local_request_Query_Allowances_0(rctx, inboundMarshaler, server, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Allowances_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +// RegisterQueryHandlerFromEndpoint is same as RegisterQueryHandler but +// automatically dials to "endpoint" and closes the connection when "ctx" gets done. +func RegisterQueryHandlerFromEndpoint(ctx context.Context, mux *runtime.ServeMux, endpoint string, opts []grpc.DialOption) (err error) { + conn, err := grpc.Dial(endpoint, opts...) + if err != nil { + return err + } + defer func() { + if err != nil { + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + return + } + go func() { + <-ctx.Done() + if cerr := conn.Close(); cerr != nil { + grpclog.Infof("Failed to close conn to %s: %v", endpoint, cerr) + } + }() + }() + + return RegisterQueryHandler(ctx, mux, conn) +} + +// RegisterQueryHandler registers the http handlers for service Query to "mux". +// The handlers forward requests to the grpc endpoint over "conn". +func RegisterQueryHandler(ctx context.Context, mux *runtime.ServeMux, conn *grpc.ClientConn) error { + return RegisterQueryHandlerClient(ctx, mux, NewQueryClient(conn)) +} + +// RegisterQueryHandlerClient registers the http handlers for service Query +// to "mux". The handlers forward requests to the grpc endpoint over the given implementation of "QueryClient". +// Note: the gRPC framework executes interceptors within the gRPC handler. If the passed in "QueryClient" +// doesn't go through the normal gRPC flow (creating a gRPC client etc.) then it will be up to the passed in +// "QueryClient" to call the correct interceptors. +func RegisterQueryHandlerClient(ctx context.Context, mux *runtime.ServeMux, client QueryClient) error { + + mux.Handle("GET", pattern_Query_Allowance_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Allowance_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Allowance_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + mux.Handle("GET", pattern_Query_Allowances_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) { + ctx, cancel := context.WithCancel(req.Context()) + defer cancel() + inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req) + rctx, err := runtime.AnnotateContext(ctx, mux, req) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + resp, md, err := request_Query_Allowances_0(rctx, inboundMarshaler, client, req, pathParams) + ctx = runtime.NewServerMetadataContext(ctx, md) + if err != nil { + runtime.HTTPError(ctx, mux, outboundMarshaler, w, req, err) + return + } + + forward_Query_Allowances_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...) + + }) + + return nil +} + +var ( + pattern_Query_Allowance_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4, 1, 0, 4, 1, 5, 5}, []string{"lbm", "feegrant", "v1", "allowance", "granter", "grantee"}, "", runtime.AssumeColonVerbOpt(true))) + + pattern_Query_Allowances_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"lbm", "feegrant", "v1", "allowances", "grantee"}, "", runtime.AssumeColonVerbOpt(true))) +) + +var ( + forward_Query_Allowance_0 = runtime.ForwardResponseMessage + + forward_Query_Allowances_0 = runtime.ForwardResponseMessage +) diff --git a/x/feegrant/types/tx.pb.go b/x/feegrant/types/tx.pb.go new file mode 100644 index 0000000000..42fae6bb42 --- /dev/null +++ b/x/feegrant/types/tx.pb.go @@ -0,0 +1,1040 @@ +// Code generated by protoc-gen-gogo. DO NOT EDIT. +// source: lbm/feegrant/v1/tx.proto + +package types + +import ( + context "context" + fmt "fmt" + _ "github.com/gogo/protobuf/gogoproto" + grpc1 "github.com/gogo/protobuf/grpc" + proto "github.com/gogo/protobuf/proto" + types "github.com/line/lbm-sdk/codec/types" + _ "github.com/regen-network/cosmos-proto" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" + io "io" + math "math" + math_bits "math/bits" +) + +// Reference imports to suppress errors if they are not otherwise used. +var _ = proto.Marshal +var _ = fmt.Errorf +var _ = math.Inf + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the proto package it is being compiled against. +// A compilation error at this line likely means your copy of the +// proto package needs to be updated. +const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package + +// MsgGrantAllowance adds permission for Grantee to spend up to Allowance +// of fees from the account of Granter. +type MsgGrantAllowance struct { + // granter is the address of the user granting an allowance of their funds. + Granter string `protobuf:"bytes,1,opt,name=granter,proto3" json:"granter,omitempty" yaml:"granter_address"` + // grantee is the address of the user being granted an allowance of another user's funds. + Grantee string `protobuf:"bytes,2,opt,name=grantee,proto3" json:"grantee,omitempty" yaml:"grantee_address"` + // allowance can be any of basic and filtered fee allowance. + Allowance *types.Any `protobuf:"bytes,3,opt,name=allowance,proto3" json:"allowance,omitempty"` +} + +func (m *MsgGrantAllowance) Reset() { *m = MsgGrantAllowance{} } +func (m *MsgGrantAllowance) String() string { return proto.CompactTextString(m) } +func (*MsgGrantAllowance) ProtoMessage() {} +func (*MsgGrantAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_b4bf88d0a96288f9, []int{0} +} +func (m *MsgGrantAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgGrantAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgGrantAllowance.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 *MsgGrantAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgGrantAllowance.Merge(m, src) +} +func (m *MsgGrantAllowance) XXX_Size() int { + return m.Size() +} +func (m *MsgGrantAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_MsgGrantAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgGrantAllowance proto.InternalMessageInfo + +func (m *MsgGrantAllowance) GetGranter() string { + if m != nil { + return m.Granter + } + return "" +} + +func (m *MsgGrantAllowance) GetGrantee() string { + if m != nil { + return m.Grantee + } + return "" +} + +func (m *MsgGrantAllowance) GetAllowance() *types.Any { + if m != nil { + return m.Allowance + } + return nil +} + +// MsgGrantAllowanceResponse defines the Msg/GrantAllowanceResponse response type. +type MsgGrantAllowanceResponse struct { +} + +func (m *MsgGrantAllowanceResponse) Reset() { *m = MsgGrantAllowanceResponse{} } +func (m *MsgGrantAllowanceResponse) String() string { return proto.CompactTextString(m) } +func (*MsgGrantAllowanceResponse) ProtoMessage() {} +func (*MsgGrantAllowanceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_b4bf88d0a96288f9, []int{1} +} +func (m *MsgGrantAllowanceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgGrantAllowanceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgGrantAllowanceResponse.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 *MsgGrantAllowanceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgGrantAllowanceResponse.Merge(m, src) +} +func (m *MsgGrantAllowanceResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgGrantAllowanceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgGrantAllowanceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgGrantAllowanceResponse proto.InternalMessageInfo + +// MsgRevokeAllowance removes any existing Allowance from Granter to Grantee. +type MsgRevokeAllowance struct { + // granter is the address of the user granting an allowance of their funds. + Granter string `protobuf:"bytes,1,opt,name=granter,proto3" json:"granter,omitempty" yaml:"granter_address"` + // grantee is the address of the user being granted an allowance of another user's funds. + Grantee string `protobuf:"bytes,2,opt,name=grantee,proto3" json:"grantee,omitempty" yaml:"grantee_address"` +} + +func (m *MsgRevokeAllowance) Reset() { *m = MsgRevokeAllowance{} } +func (m *MsgRevokeAllowance) String() string { return proto.CompactTextString(m) } +func (*MsgRevokeAllowance) ProtoMessage() {} +func (*MsgRevokeAllowance) Descriptor() ([]byte, []int) { + return fileDescriptor_b4bf88d0a96288f9, []int{2} +} +func (m *MsgRevokeAllowance) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRevokeAllowance) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRevokeAllowance.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 *MsgRevokeAllowance) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRevokeAllowance.Merge(m, src) +} +func (m *MsgRevokeAllowance) XXX_Size() int { + return m.Size() +} +func (m *MsgRevokeAllowance) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRevokeAllowance.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRevokeAllowance proto.InternalMessageInfo + +func (m *MsgRevokeAllowance) GetGranter() string { + if m != nil { + return m.Granter + } + return "" +} + +func (m *MsgRevokeAllowance) GetGrantee() string { + if m != nil { + return m.Grantee + } + return "" +} + +// MsgRevokeAllowanceResponse defines the Msg/RevokeAllowanceResponse response type. +type MsgRevokeAllowanceResponse struct { +} + +func (m *MsgRevokeAllowanceResponse) Reset() { *m = MsgRevokeAllowanceResponse{} } +func (m *MsgRevokeAllowanceResponse) String() string { return proto.CompactTextString(m) } +func (*MsgRevokeAllowanceResponse) ProtoMessage() {} +func (*MsgRevokeAllowanceResponse) Descriptor() ([]byte, []int) { + return fileDescriptor_b4bf88d0a96288f9, []int{3} +} +func (m *MsgRevokeAllowanceResponse) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *MsgRevokeAllowanceResponse) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_MsgRevokeAllowanceResponse.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 *MsgRevokeAllowanceResponse) XXX_Merge(src proto.Message) { + xxx_messageInfo_MsgRevokeAllowanceResponse.Merge(m, src) +} +func (m *MsgRevokeAllowanceResponse) XXX_Size() int { + return m.Size() +} +func (m *MsgRevokeAllowanceResponse) XXX_DiscardUnknown() { + xxx_messageInfo_MsgRevokeAllowanceResponse.DiscardUnknown(m) +} + +var xxx_messageInfo_MsgRevokeAllowanceResponse proto.InternalMessageInfo + +func init() { + proto.RegisterType((*MsgGrantAllowance)(nil), "lbm.feegrant.v1.MsgGrantAllowance") + proto.RegisterType((*MsgGrantAllowanceResponse)(nil), "lbm.feegrant.v1.MsgGrantAllowanceResponse") + proto.RegisterType((*MsgRevokeAllowance)(nil), "lbm.feegrant.v1.MsgRevokeAllowance") + proto.RegisterType((*MsgRevokeAllowanceResponse)(nil), "lbm.feegrant.v1.MsgRevokeAllowanceResponse") +} + +func init() { proto.RegisterFile("lbm/feegrant/v1/tx.proto", fileDescriptor_b4bf88d0a96288f9) } + +var fileDescriptor_b4bf88d0a96288f9 = []byte{ + // 373 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0xc8, 0x49, 0xca, 0xd5, + 0x4f, 0x4b, 0x4d, 0x4d, 0x2f, 0x4a, 0xcc, 0x2b, 0xd1, 0x2f, 0x33, 0xd4, 0x2f, 0xa9, 0xd0, 0x2b, + 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0xcf, 0x49, 0xca, 0xd5, 0x83, 0xc9, 0xe8, 0x95, 0x19, 0x4a, + 0x89, 0xa4, 0xe7, 0xa7, 0xe7, 0x83, 0xe5, 0xf4, 0x41, 0x2c, 0x88, 0x32, 0x29, 0xc9, 0xf4, 0xfc, + 0xfc, 0xf4, 0x9c, 0x54, 0x7d, 0x30, 0x2f, 0xa9, 0x34, 0x4d, 0x3f, 0x31, 0xaf, 0x12, 0x26, 0x95, + 0x9c, 0x5f, 0x9c, 0x9b, 0x5f, 0x1c, 0x0f, 0xd1, 0x03, 0xe1, 0x40, 0xa4, 0x94, 0x8e, 0x31, 0x72, + 0x09, 0xfa, 0x16, 0xa7, 0xbb, 0x83, 0xcc, 0x76, 0xcc, 0xc9, 0xc9, 0x2f, 0x4f, 0xcc, 0x4b, 0x4e, + 0x15, 0x32, 0xe1, 0x62, 0x07, 0xdb, 0x96, 0x5a, 0x24, 0xc1, 0xa8, 0xc0, 0xa8, 0xc1, 0xe9, 0x24, + 0xf5, 0xe9, 0x9e, 0xbc, 0x58, 0x65, 0x62, 0x6e, 0x8e, 0x95, 0x12, 0x54, 0x22, 0x3e, 0x31, 0x25, + 0xa5, 0x28, 0xb5, 0xb8, 0x58, 0x29, 0x08, 0xa6, 0x14, 0xa1, 0x2b, 0x55, 0x82, 0x09, 0xbb, 0xae, + 0x54, 0x0c, 0x5d, 0xa9, 0x42, 0xae, 0x5c, 0x9c, 0x89, 0x30, 0x8b, 0x25, 0x98, 0x15, 0x18, 0x35, + 0xb8, 0x8d, 0x44, 0xf4, 0x20, 0x7e, 0xd1, 0x83, 0xf9, 0x45, 0xcf, 0x31, 0xaf, 0xd2, 0x49, 0xf0, + 0xd4, 0x16, 0x5d, 0x5e, 0xb7, 0xd4, 0x54, 0xb8, 0x33, 0x3d, 0x83, 0x10, 0x3a, 0x95, 0xa4, 0xb9, + 0x24, 0x31, 0xfc, 0x11, 0x94, 0x5a, 0x5c, 0x90, 0x9f, 0x57, 0x9c, 0xaa, 0xd4, 0xc0, 0xc8, 0x25, + 0xe4, 0x5b, 0x9c, 0x1e, 0x94, 0x5a, 0x96, 0x9f, 0x9d, 0x3a, 0x20, 0xde, 0x54, 0x92, 0xe1, 0x92, + 0xc2, 0x74, 0x01, 0xcc, 0x81, 0x46, 0x67, 0x18, 0xb9, 0x98, 0x7d, 0x8b, 0xd3, 0x85, 0x12, 0xb8, + 0xf8, 0xd0, 0xa2, 0x42, 0x49, 0x0f, 0x2d, 0xfa, 0xf5, 0x30, 0xbc, 0x29, 0xa5, 0x45, 0x58, 0x0d, + 0xcc, 0x26, 0xa1, 0x64, 0x2e, 0x7e, 0xf4, 0x60, 0x50, 0xc6, 0xa6, 0x1d, 0x4d, 0x91, 0x94, 0x36, + 0x11, 0x8a, 0x60, 0x96, 0x38, 0x39, 0x9d, 0x78, 0x24, 0xc7, 0x78, 0xe1, 0x91, 0x1c, 0xe3, 0x83, + 0x47, 0x72, 0x8c, 0x13, 0x1e, 0xcb, 0x31, 0x5c, 0x78, 0x2c, 0xc7, 0x70, 0xe3, 0xb1, 0x1c, 0x43, + 0x94, 0x46, 0x7a, 0x66, 0x49, 0x46, 0x69, 0x92, 0x5e, 0x72, 0x7e, 0xae, 0x7e, 0x4e, 0x66, 0x5e, + 0xaa, 0x7e, 0x4e, 0x52, 0xae, 0x6e, 0x71, 0x4a, 0xb6, 0x7e, 0x05, 0x22, 0xf1, 0x97, 0x54, 0x16, + 0xa4, 0x16, 0x27, 0xb1, 0x81, 0x23, 0xdf, 0x18, 0x10, 0x00, 0x00, 0xff, 0xff, 0x65, 0x04, 0x27, + 0xf4, 0x19, 0x03, 0x00, 0x00, +} + +// Reference imports to suppress errors if they are not otherwise used. +var _ context.Context +var _ grpc.ClientConn + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +const _ = grpc.SupportPackageIsVersion4 + +// MsgClient is the client API for Msg service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. +type MsgClient interface { + // GrantAllowance grants fee allowance to the grantee on the granter's + // account with the provided expiration time. + GrantAllowance(ctx context.Context, in *MsgGrantAllowance, opts ...grpc.CallOption) (*MsgGrantAllowanceResponse, error) + // RevokeAllowance revokes any fee allowance of granter's account that + // has been granted to the grantee. + RevokeAllowance(ctx context.Context, in *MsgRevokeAllowance, opts ...grpc.CallOption) (*MsgRevokeAllowanceResponse, error) +} + +type msgClient struct { + cc grpc1.ClientConn +} + +func NewMsgClient(cc grpc1.ClientConn) MsgClient { + return &msgClient{cc} +} + +func (c *msgClient) GrantAllowance(ctx context.Context, in *MsgGrantAllowance, opts ...grpc.CallOption) (*MsgGrantAllowanceResponse, error) { + out := new(MsgGrantAllowanceResponse) + err := c.cc.Invoke(ctx, "/lbm.feegrant.v1.Msg/GrantAllowance", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *msgClient) RevokeAllowance(ctx context.Context, in *MsgRevokeAllowance, opts ...grpc.CallOption) (*MsgRevokeAllowanceResponse, error) { + out := new(MsgRevokeAllowanceResponse) + err := c.cc.Invoke(ctx, "/lbm.feegrant.v1.Msg/RevokeAllowance", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// MsgServer is the server API for Msg service. +type MsgServer interface { + // GrantAllowance grants fee allowance to the grantee on the granter's + // account with the provided expiration time. + GrantAllowance(context.Context, *MsgGrantAllowance) (*MsgGrantAllowanceResponse, error) + // RevokeAllowance revokes any fee allowance of granter's account that + // has been granted to the grantee. + RevokeAllowance(context.Context, *MsgRevokeAllowance) (*MsgRevokeAllowanceResponse, error) +} + +// UnimplementedMsgServer can be embedded to have forward compatible implementations. +type UnimplementedMsgServer struct { +} + +func (*UnimplementedMsgServer) GrantAllowance(ctx context.Context, req *MsgGrantAllowance) (*MsgGrantAllowanceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method GrantAllowance not implemented") +} +func (*UnimplementedMsgServer) RevokeAllowance(ctx context.Context, req *MsgRevokeAllowance) (*MsgRevokeAllowanceResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method RevokeAllowance not implemented") +} + +func RegisterMsgServer(s grpc1.Server, srv MsgServer) { + s.RegisterService(&_Msg_serviceDesc, srv) +} + +func _Msg_GrantAllowance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgGrantAllowance) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).GrantAllowance(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lbm.feegrant.v1.Msg/GrantAllowance", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).GrantAllowance(ctx, req.(*MsgGrantAllowance)) + } + return interceptor(ctx, in, info, handler) +} + +func _Msg_RevokeAllowance_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(MsgRevokeAllowance) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(MsgServer).RevokeAllowance(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/lbm.feegrant.v1.Msg/RevokeAllowance", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(MsgServer).RevokeAllowance(ctx, req.(*MsgRevokeAllowance)) + } + return interceptor(ctx, in, info, handler) +} + +var _Msg_serviceDesc = grpc.ServiceDesc{ + ServiceName: "lbm.feegrant.v1.Msg", + HandlerType: (*MsgServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "GrantAllowance", + Handler: _Msg_GrantAllowance_Handler, + }, + { + MethodName: "RevokeAllowance", + Handler: _Msg_RevokeAllowance_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "lbm/feegrant/v1/tx.proto", +} + +func (m *MsgGrantAllowance) 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 *MsgGrantAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgGrantAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.Allowance != nil { + { + size, err := m.Allowance.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintTx(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + if len(m.Grantee) > 0 { + i -= len(m.Grantee) + copy(dAtA[i:], m.Grantee) + i = encodeVarintTx(dAtA, i, uint64(len(m.Grantee))) + i-- + dAtA[i] = 0x12 + } + if len(m.Granter) > 0 { + i -= len(m.Granter) + copy(dAtA[i:], m.Granter) + i = encodeVarintTx(dAtA, i, uint64(len(m.Granter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgGrantAllowanceResponse) 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 *MsgGrantAllowanceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgGrantAllowanceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func (m *MsgRevokeAllowance) 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 *MsgRevokeAllowance) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRevokeAllowance) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if len(m.Grantee) > 0 { + i -= len(m.Grantee) + copy(dAtA[i:], m.Grantee) + i = encodeVarintTx(dAtA, i, uint64(len(m.Grantee))) + i-- + dAtA[i] = 0x12 + } + if len(m.Granter) > 0 { + i -= len(m.Granter) + copy(dAtA[i:], m.Granter) + i = encodeVarintTx(dAtA, i, uint64(len(m.Granter))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + +func (m *MsgRevokeAllowanceResponse) 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 *MsgRevokeAllowanceResponse) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *MsgRevokeAllowanceResponse) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + return len(dAtA) - i, nil +} + +func encodeVarintTx(dAtA []byte, offset int, v uint64) int { + offset -= sovTx(v) + base := offset + for v >= 1<<7 { + dAtA[offset] = uint8(v&0x7f | 0x80) + v >>= 7 + offset++ + } + dAtA[offset] = uint8(v) + return base +} +func (m *MsgGrantAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Granter) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Grantee) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + if m.Allowance != nil { + l = m.Allowance.Size() + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgGrantAllowanceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func (m *MsgRevokeAllowance) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Granter) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + l = len(m.Grantee) + if l > 0 { + n += 1 + l + sovTx(uint64(l)) + } + return n +} + +func (m *MsgRevokeAllowanceResponse) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + return n +} + +func sovTx(x uint64) (n int) { + return (math_bits.Len64(x|1) + 6) / 7 +} +func sozTx(x uint64) (n int) { + return sovTx(uint64((x << 1) ^ uint64((int64(x) >> 63)))) +} +func (m *MsgGrantAllowance) 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 ErrIntOverflowTx + } + 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: MsgGrantAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgGrantAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Granter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Granter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Grantee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Grantee = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Allowance", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if m.Allowance == nil { + m.Allowance = &types.Any{} + } + if err := m.Allowance.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgGrantAllowanceResponse) 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 ErrIntOverflowTx + } + 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: MsgGrantAllowanceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgGrantAllowanceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRevokeAllowance) 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 ErrIntOverflowTx + } + 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: MsgRevokeAllowance: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRevokeAllowance: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Granter", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Granter = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Grantee", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowTx + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthTx + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthTx + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Grantee = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *MsgRevokeAllowanceResponse) 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 ErrIntOverflowTx + } + 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: MsgRevokeAllowanceResponse: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MsgRevokeAllowanceResponse: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + default: + iNdEx = preIndex + skippy, err := skipTx(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthTx + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func skipTx(dAtA []byte) (n int, err error) { + l := len(dAtA) + iNdEx := 0 + depth := 0 + for iNdEx < l { + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= (uint64(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + wireType := int(wire & 0x7) + switch wireType { + case 0: + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + iNdEx++ + if dAtA[iNdEx-1] < 0x80 { + break + } + } + case 1: + iNdEx += 8 + case 2: + var length int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return 0, ErrIntOverflowTx + } + if iNdEx >= l { + return 0, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + length |= (int(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + if length < 0 { + return 0, ErrInvalidLengthTx + } + iNdEx += length + case 3: + depth++ + case 4: + if depth == 0 { + return 0, ErrUnexpectedEndOfGroupTx + } + depth-- + case 5: + iNdEx += 4 + default: + return 0, fmt.Errorf("proto: illegal wireType %d", wireType) + } + if iNdEx < 0 { + return 0, ErrInvalidLengthTx + } + if depth == 0 { + return iNdEx, nil + } + } + return 0, io.ErrUnexpectedEOF +} + +var ( + ErrInvalidLengthTx = fmt.Errorf("proto: negative length found during unmarshaling") + ErrIntOverflowTx = fmt.Errorf("proto: integer overflow") + ErrUnexpectedEndOfGroupTx = fmt.Errorf("proto: unexpected end of group") +) diff --git a/x/simulation/util.go b/x/simulation/util.go index c600bc2a36..f58f9ff1aa 100644 --- a/x/simulation/util.go +++ b/x/simulation/util.go @@ -5,6 +5,13 @@ import ( "fmt" "math/rand" "testing" + + "github.com/line/lbm-sdk/baseapp" + "github.com/line/lbm-sdk/client" + "github.com/line/lbm-sdk/codec" + "github.com/line/lbm-sdk/simapp/helpers" + "github.com/line/lbm-sdk/types" + simtype "github.com/line/lbm-sdk/types/simulation" ) func getTestingMode(tb testing.TB) (testingMode bool, t *testing.T, b *testing.B) { @@ -53,3 +60,66 @@ func mustMarshalJSONIndent(o interface{}) []byte { return bz } + +// OperationInput is a struct that holds all the needed values to generate a tx and deliver it +type OperationInput struct { + R *rand.Rand + App *baseapp.BaseApp + TxGen client.TxConfig + Cdc *codec.ProtoCodec + Msg types.Msg + MsgType string + CoinsSpentInMsg types.Coins + Context types.Context + SimAccount simtype.Account + AccountKeeper AccountKeeper + Bankkeeper BankKeeper + ModuleName string +} + +// GenAndDeliverTxWithRandFees generates a transaction with a random fee and delivers it. +func GenAndDeliverTxWithRandFees(txCtx OperationInput) (simtype.OperationMsg, []simtype.FutureOperation, error) { + account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address) + spendable := txCtx.Bankkeeper.SpendableCoins(txCtx.Context, account.GetAddress()) + + var fees types.Coins + var err error + + coins, hasNeg := spendable.SafeSub(txCtx.CoinsSpentInMsg) + if hasNeg { + return simtype.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "message doesn't leave room for fees"), nil, err + } + + fees, err = simtype.RandomFees(txCtx.R, txCtx.Context, coins) + if err != nil { + return simtype.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate fees"), nil, err + } + return GenAndDeliverTx(txCtx, fees) +} + +// GenAndDeliverTx generates a transactions and delivers it. +func GenAndDeliverTx(txCtx OperationInput, fees types.Coins) (simtype.OperationMsg, []simtype.FutureOperation, error) { + account := txCtx.AccountKeeper.GetAccount(txCtx.Context, txCtx.SimAccount.Address) + tx, err := helpers.GenTx( + txCtx.TxGen, + []types.Msg{txCtx.Msg}, + fees, + helpers.DefaultGenTxGas, + txCtx.Context.ChainID(), + []uint64{0}, + []uint64{account.GetSequence()}, + txCtx.SimAccount.PrivKey, + ) + + if err != nil { + return simtype.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to generate mock tx"), nil, err + } + + _, _, err = txCtx.App.Deliver(txCtx.TxGen.TxEncoder(), tx) + if err != nil { + return simtype.NoOpMsg(txCtx.ModuleName, txCtx.MsgType, "unable to deliver tx"), nil, err + } + + return simtype.NewOperationMsg(txCtx.Msg, true, ""), nil, nil + +}