Merge branch 'master' into aaronc/6513-textual-json-proto

This commit is contained in:
Aaron Craelius 2021-02-01 11:56:44 -05:00 committed by GitHub
commit a58bb49d00
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 10337 additions and 566 deletions

View File

@ -36,17 +36,25 @@ Ref: https://keepachangelog.com/en/1.0.0/
## [Unreleased]
### Client Breaking Changes
* [\#8363](https://github.com/cosmos/cosmos-sdk/issues/8363) Addresses no longer have a fixed 20-byte length. From the SDK modules' point of view, any 1-255 bytes-long byte array is a valid address.
### State Machine Breaking
* (x/{bank,distrib,gov,slashing,staking}) [\#8363](https://github.com/cosmos/cosmos-sdk/issues/8363) Store keys have been modified to allow for variable-length addresses.
* (x/ibc) [\#8266](https://github.com/cosmos/cosmos-sdk/issues/8266) Add amino JSON for IBC messages in order to support Ledger text signing.
### Improvements
* (x/ibc) [\#8458](https://github.com/cosmos/cosmos-sdk/pull/8458) Add `packet_connection` attribute to ibc events to enable relayer filtering
* (x/bank) [\#8479](https://github.com/cosmos/cosmos-sdk/pull/8479) Adittional client denom metadata validation for `base` and `display` denoms.
* (x/ibc) [\#8404](https://github.com/cosmos/cosmos-sdk/pull/8404) Reorder IBC `ChanOpenAck` and `ChanOpenConfirm` handler execution to perform core handler first, followed by application callbacks.
* [\#8396](https://github.com/cosmos/cosmos-sdk/pull/8396) Add support for ARM platform
### Bug Fixes
* (x/evidence) [#8461](https://github.com/cosmos/cosmos-sdk/pull/8461) Fix bech32 prefix in evidence validator address conversion
* (x/slashing) [\#8427](https://github.com/cosmos/cosmos-sdk/pull/8427) Fix query signing infos command
* (simapp) [\#8418](https://github.com/cosmos/cosmos-sdk/pull/8418) Add balance coin to supply when adding a new genesis account
* (x/bank) [\#8417](https://github.com/cosmos/cosmos-sdk/pull/8417) Validate balances and coin denom metadata on genesis

View File

@ -221,6 +221,19 @@ func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, err
clientCtx = clientCtx.WithSignModeStr(signModeStr)
}
if clientCtx.FeeGranter == nil || flagSet.Changed(flags.FlagFeeAccount) {
granter, _ := flagSet.GetString(flags.FlagFeeAccount)
if granter != "" {
granterAcc, err := sdk.AccAddressFromBech32(granter)
if err != nil {
return clientCtx, err
}
clientCtx = clientCtx.WithFeeGranterAddress(granterAcc)
}
}
if clientCtx.From == "" || flagSet.Changed(flags.FlagFrom) {
from, _ := flagSet.GetString(flags.FlagFrom)
fromAddr, fromName, keyType, err := GetFromFields(clientCtx.Keyring, from, clientCtx.GenerateOnly)

View File

@ -44,6 +44,7 @@ type Context struct {
TxConfig TxConfig
AccountRetriever AccountRetriever
NodeURI string
FeeGranter sdk.AccAddress
// TODO: Deprecated (remove).
LegacyAmino *codec.LegacyAmino
@ -166,6 +167,13 @@ func (ctx Context) WithFromAddress(addr sdk.AccAddress) 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
}
// WithBroadcastMode returns a copy of the context with an updated broadcast
// mode.
func (ctx Context) WithBroadcastMode(mode string) Context {

View File

@ -70,6 +70,7 @@ const (
FlagCountTotal = "count-total"
FlagTimeoutHeight = "timeout-height"
FlagKeyAlgorithm = "algo"
FlagFeeAccount = "fee-account"
// Tendermint logging flags
FlagLogLevel = "log_level"
@ -112,6 +113,7 @@ func AddTxFlagsToCmd(cmd *cobra.Command) {
cmd.Flags().String(FlagKeyringBackend, DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")
cmd.Flags().String(FlagSignMode, "", "Choose sign mode (direct|amino-json), this is an advanced feature")
cmd.Flags().Uint64(FlagTimeoutHeight, 0, "Set a block timeout height to prevent the tx from being committed past a certain height")
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))

View File

@ -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

View File

@ -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

View File

@ -42,5 +42,6 @@ type (
SetFeeAmount(amount sdk.Coins)
SetGasLimit(limit uint64)
SetTimeoutHeight(height uint64)
SetFeeGranter(feeGranter sdk.AccAddress)
}
)

View File

@ -1,12 +1,12 @@
[
{
"account_identifier": {
"address":"cosmos1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjjqfl87e"
"address":"cosmos158nkd0l9tyemv2crp579rmj8dg37qty8lzff88"
},
"currency":{
"symbol":"stake",
"decimals":0
},
"value": "999900000000"
"value": "999990000000"
}
]

View File

@ -16,12 +16,13 @@ simd init simd --chain-id testing
simd keys add fd --keyring-backend=test
addr=$(simd keys show fd -a --keyring-backend=test)
val_addr=$(simd keys show fd --keyring-backend=test --bech val -a)
# give the accounts some money
simd add-genesis-account "$addr" 1000000000000stake --keyring-backend=test
# save configs for the daemon
simd gentx fd --chain-id testing --keyring-backend=test
simd gentx fd 10000000stake --chain-id testing --keyring-backend=test
# input genTx to the genesis file
simd collect-gentxs
@ -55,4 +56,4 @@ echo zipping data dir and saving to /tmp/data.tar.gz
tar -czvf /tmp/data.tar.gz /root/.simapp
echo new address for bootstrap.json "$addr"
echo new address for bootstrap.json "$addr" "$val_addr"

View File

@ -94,7 +94,7 @@ staking(1){
"account": {
"address": "staking_account",
"sub_account": {
"address" : "cosmosvaloper1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjj9atjj2"
"address" : "cosmosvaloper158nkd0l9tyemv2crp579rmj8dg37qty86kaut5"
}
},
"amount":{
@ -134,7 +134,7 @@ staking(1){
"account": {
"address": "staking_account",
"sub_account": {
"address" : "cosmosvaloper1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjj9atjj2"
"address" : "cosmosvaloper158nkd0l9tyemv2crp579rmj8dg37qty86kaut5"
}
},
"amount":{

Binary file not shown.

View File

@ -264,6 +264,32 @@
- [Msg](#cosmos.evidence.v1beta1.Msg)
- [cosmos/feegrant/v1beta1/feegrant.proto](#cosmos/feegrant/v1beta1/feegrant.proto)
- [BasicFeeAllowance](#cosmos.feegrant.v1beta1.BasicFeeAllowance)
- [Duration](#cosmos.feegrant.v1beta1.Duration)
- [ExpiresAt](#cosmos.feegrant.v1beta1.ExpiresAt)
- [FeeAllowanceGrant](#cosmos.feegrant.v1beta1.FeeAllowanceGrant)
- [PeriodicFeeAllowance](#cosmos.feegrant.v1beta1.PeriodicFeeAllowance)
- [cosmos/feegrant/v1beta1/genesis.proto](#cosmos/feegrant/v1beta1/genesis.proto)
- [GenesisState](#cosmos.feegrant.v1beta1.GenesisState)
- [cosmos/feegrant/v1beta1/query.proto](#cosmos/feegrant/v1beta1/query.proto)
- [QueryFeeAllowanceRequest](#cosmos.feegrant.v1beta1.QueryFeeAllowanceRequest)
- [QueryFeeAllowanceResponse](#cosmos.feegrant.v1beta1.QueryFeeAllowanceResponse)
- [QueryFeeAllowancesRequest](#cosmos.feegrant.v1beta1.QueryFeeAllowancesRequest)
- [QueryFeeAllowancesResponse](#cosmos.feegrant.v1beta1.QueryFeeAllowancesResponse)
- [Query](#cosmos.feegrant.v1beta1.Query)
- [cosmos/feegrant/v1beta1/tx.proto](#cosmos/feegrant/v1beta1/tx.proto)
- [MsgGrantFeeAllowance](#cosmos.feegrant.v1beta1.MsgGrantFeeAllowance)
- [MsgGrantFeeAllowanceResponse](#cosmos.feegrant.v1beta1.MsgGrantFeeAllowanceResponse)
- [MsgRevokeFeeAllowance](#cosmos.feegrant.v1beta1.MsgRevokeFeeAllowance)
- [MsgRevokeFeeAllowanceResponse](#cosmos.feegrant.v1beta1.MsgRevokeFeeAllowanceResponse)
- [Msg](#cosmos.feegrant.v1beta1.Msg)
- [cosmos/genutil/v1beta1/genesis.proto](#cosmos/genutil/v1beta1/genesis.proto)
- [GenesisState](#cosmos.genutil.v1beta1.GenesisState)
@ -4097,6 +4123,312 @@ Msg defines the evidence Msg service.
<a name="cosmos/feegrant/v1beta1/feegrant.proto"></a>
<p align="right"><a href="#top">Top</a></p>
## cosmos/feegrant/v1beta1/feegrant.proto
<a name="cosmos.feegrant.v1beta1.BasicFeeAllowance"></a>
### BasicFeeAllowance
BasicFeeAllowance implements FeeAllowance with a one-time grant of tokens
that optionally expires. The delegatee can use up to SpendLimit to cover fees.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `spend_limit` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | spend_limit specifies the maximum amount of tokens that can be spent by this allowance and will be updated as tokens are spent. If it is empty, there is no spend limit and any amount of coins can be spent. |
| `expiration` | [ExpiresAt](#cosmos.feegrant.v1beta1.ExpiresAt) | | expiration specifies an optional time when this allowance expires |
<a name="cosmos.feegrant.v1beta1.Duration"></a>
### Duration
Duration is a span of a clock time or number of blocks.
This is designed to be added to an ExpiresAt struct.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `duration` | [google.protobuf.Duration](#google.protobuf.Duration) | | |
| `blocks` | [uint64](#uint64) | | |
<a name="cosmos.feegrant.v1beta1.ExpiresAt"></a>
### ExpiresAt
ExpiresAt is a point in time where something expires.
It may be *either* block time or block height
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
| `height` | [int64](#int64) | | |
<a name="cosmos.feegrant.v1beta1.FeeAllowanceGrant"></a>
### FeeAllowanceGrant
FeeAllowanceGrant is stored in the KVStore to record a grant with full context
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `granter` | [string](#string) | | |
| `grantee` | [string](#string) | | |
| `allowance` | [google.protobuf.Any](#google.protobuf.Any) | | |
<a name="cosmos.feegrant.v1beta1.PeriodicFeeAllowance"></a>
### PeriodicFeeAllowance
PeriodicFeeAllowance extends FeeAllowance to allow for both a maximum cap,
as well as a limit per time period.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `basic` | [BasicFeeAllowance](#cosmos.feegrant.v1beta1.BasicFeeAllowance) | | basic specifies a struct of `BasicFeeAllowance` |
| `period` | [Duration](#cosmos.feegrant.v1beta1.Duration) | | period specifies the time duration in which period_spend_limit coins can be spent before that allowance is reset |
| `period_spend_limit` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | period_spend_limit specifies the maximum number of coins that can be spent in the period |
| `period_can_spend` | [cosmos.base.v1beta1.Coin](#cosmos.base.v1beta1.Coin) | repeated | period_can_spend is the number of coins left to be spent before the period_reset time |
| `period_reset` | [ExpiresAt](#cosmos.feegrant.v1beta1.ExpiresAt) | | period_reset is the time at which this period resets and a new one begins, it is calculated from the start time of the first transaction after the last period ended |
<!-- end messages -->
<!-- end enums -->
<!-- end HasExtensions -->
<!-- end services -->
<a name="cosmos/feegrant/v1beta1/genesis.proto"></a>
<p align="right"><a href="#top">Top</a></p>
## cosmos/feegrant/v1beta1/genesis.proto
<a name="cosmos.feegrant.v1beta1.GenesisState"></a>
### GenesisState
GenesisState contains a set of fee allowances, persisted from the store
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `fee_allowances` | [FeeAllowanceGrant](#cosmos.feegrant.v1beta1.FeeAllowanceGrant) | repeated | |
<!-- end messages -->
<!-- end enums -->
<!-- end HasExtensions -->
<!-- end services -->
<a name="cosmos/feegrant/v1beta1/query.proto"></a>
<p align="right"><a href="#top">Top</a></p>
## cosmos/feegrant/v1beta1/query.proto
<a name="cosmos.feegrant.v1beta1.QueryFeeAllowanceRequest"></a>
### QueryFeeAllowanceRequest
QueryFeeAllowanceRequest is the request type for the Query/FeeAllowance RPC method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `granter` | [string](#string) | | |
| `grantee` | [string](#string) | | |
<a name="cosmos.feegrant.v1beta1.QueryFeeAllowanceResponse"></a>
### QueryFeeAllowanceResponse
QueryFeeAllowanceResponse is the response type for the Query/FeeAllowance RPC method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `fee_allowance` | [FeeAllowanceGrant](#cosmos.feegrant.v1beta1.FeeAllowanceGrant) | | fee_allowance is a fee_allowance granted for grantee by granter. |
<a name="cosmos.feegrant.v1beta1.QueryFeeAllowancesRequest"></a>
### QueryFeeAllowancesRequest
QueryFeeAllowancesRequest is the request type for the Query/FeeAllowances RPC method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `grantee` | [string](#string) | | |
| `pagination` | [cosmos.base.query.v1beta1.PageRequest](#cosmos.base.query.v1beta1.PageRequest) | | pagination defines an pagination for the request. |
<a name="cosmos.feegrant.v1beta1.QueryFeeAllowancesResponse"></a>
### QueryFeeAllowancesResponse
QueryFeeAllowancesResponse is the response type for the Query/FeeAllowances RPC method.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `fee_allowances` | [FeeAllowanceGrant](#cosmos.feegrant.v1beta1.FeeAllowanceGrant) | repeated | fee_allowances are fee_allowance's granted for grantee by granter. |
| `pagination` | [cosmos.base.query.v1beta1.PageResponse](#cosmos.base.query.v1beta1.PageResponse) | | pagination defines an pagination for the response. |
<!-- end messages -->
<!-- end enums -->
<!-- end HasExtensions -->
<a name="cosmos.feegrant.v1beta1.Query"></a>
### Query
Query defines the gRPC querier service.
| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
| ----------- | ------------ | ------------- | ------------| ------- | -------- |
| `FeeAllowance` | [QueryFeeAllowanceRequest](#cosmos.feegrant.v1beta1.QueryFeeAllowanceRequest) | [QueryFeeAllowanceResponse](#cosmos.feegrant.v1beta1.QueryFeeAllowanceResponse) | FeeAllowance returns fee granted to the grantee by the granter. | GET|/cosmos/feegrant/v1beta1/fee_allowance/{granter}/{grantee}|
| `FeeAllowances` | [QueryFeeAllowancesRequest](#cosmos.feegrant.v1beta1.QueryFeeAllowancesRequest) | [QueryFeeAllowancesResponse](#cosmos.feegrant.v1beta1.QueryFeeAllowancesResponse) | FeeAllowances returns all the grants for address. | GET|/cosmos/feegrant/v1beta1/fee_allowances/{grantee}|
<!-- end services -->
<a name="cosmos/feegrant/v1beta1/tx.proto"></a>
<p align="right"><a href="#top">Top</a></p>
## cosmos/feegrant/v1beta1/tx.proto
<a name="cosmos.feegrant.v1beta1.MsgGrantFeeAllowance"></a>
### MsgGrantFeeAllowance
MsgGrantFeeAllowance adds permission for Grantee to spend up to Allowance
of fees from the account of Granter.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `granter` | [string](#string) | | |
| `grantee` | [string](#string) | | |
| `allowance` | [google.protobuf.Any](#google.protobuf.Any) | | |
<a name="cosmos.feegrant.v1beta1.MsgGrantFeeAllowanceResponse"></a>
### MsgGrantFeeAllowanceResponse
MsgGrantFeeAllowanceResponse defines the Msg/GrantFeeAllowanceResponse response type.
<a name="cosmos.feegrant.v1beta1.MsgRevokeFeeAllowance"></a>
### MsgRevokeFeeAllowance
MsgRevokeFeeAllowance removes any existing FeeAllowance from Granter to Grantee.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `granter` | [string](#string) | | |
| `grantee` | [string](#string) | | |
<a name="cosmos.feegrant.v1beta1.MsgRevokeFeeAllowanceResponse"></a>
### MsgRevokeFeeAllowanceResponse
MsgRevokeFeeAllowanceResponse defines the Msg/RevokeFeeAllowanceResponse response type.
<!-- end messages -->
<!-- end enums -->
<!-- end HasExtensions -->
<a name="cosmos.feegrant.v1beta1.Msg"></a>
### Msg
Msg defines the feegrant msg service.
| Method Name | Request Type | Response Type | Description | HTTP Verb | Endpoint |
| ----------- | ------------ | ------------- | ------------| ------- | -------- |
| `GrantFeeAllowance` | [MsgGrantFeeAllowance](#cosmos.feegrant.v1beta1.MsgGrantFeeAllowance) | [MsgGrantFeeAllowanceResponse](#cosmos.feegrant.v1beta1.MsgGrantFeeAllowanceResponse) | GrantFeeAllowance grants fee allowance to the grantee on the granter's account with the provided expiration time. | |
| `RevokeFeeAllowance` | [MsgRevokeFeeAllowance](#cosmos.feegrant.v1beta1.MsgRevokeFeeAllowance) | [MsgRevokeFeeAllowanceResponse](#cosmos.feegrant.v1beta1.MsgRevokeFeeAllowanceResponse) | RevokeFeeAllowance revokes any fee allowance of granter's account that has been granted to the grantee. | |
<!-- end services -->
<a name="cosmos/genutil/v1beta1/genesis.proto"></a>
<p align="right"><a href="#top">Top</a></p>
@ -5375,8 +5707,8 @@ Commission defines commission parameters for a given validator.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `commission_rates` | [CommissionRates](#cosmos.staking.v1beta1.CommissionRates) | | |
| `update_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
| `commission_rates` | [CommissionRates](#cosmos.staking.v1beta1.CommissionRates) | | commission_rates defines the initial commission rates to be used for creating a validator. |
| `update_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | update_time is the last time the commission rate was changed. |
@ -5392,9 +5724,9 @@ a validator.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `rate` | [string](#string) | | |
| `max_rate` | [string](#string) | | |
| `max_change_rate` | [string](#string) | | |
| `rate` | [string](#string) | | rate is the commission rate charged to delegators, as a fraction. |
| `max_rate` | [string](#string) | | max_rate defines the maximum commission rate which validator can ever charge, as a fraction. |
| `max_change_rate` | [string](#string) | | max_change_rate defines the maximum daily increase of the validator commission, as a fraction. |
@ -5479,9 +5811,9 @@ validator.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `delegator_address` | [string](#string) | | |
| `validator_address` | [string](#string) | | |
| `shares` | [string](#string) | | |
| `delegator_address` | [string](#string) | | delegator_address is the bech32-encoded address of the delegator. |
| `validator_address` | [string](#string) | | validator_address is the bech32-encoded address of the validator. |
| `shares` | [string](#string) | | shares define the delegation shares received. |
@ -5513,11 +5845,11 @@ Description defines a validator description.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `moniker` | [string](#string) | | |
| `identity` | [string](#string) | | |
| `website` | [string](#string) | | |
| `security_contact` | [string](#string) | | |
| `details` | [string](#string) | | |
| `moniker` | [string](#string) | | moniker defines a human-readable name for the validator. |
| `identity` | [string](#string) | | identity defines an optional identity signature (ex. UPort or Keybase). |
| `website` | [string](#string) | | website defines an optional website link. |
| `security_contact` | [string](#string) | | security_contact defines an optional email for security contact. |
| `details` | [string](#string) | | details define other optional details. |
@ -5551,11 +5883,11 @@ Params defines the parameters for the staking module.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `unbonding_time` | [google.protobuf.Duration](#google.protobuf.Duration) | | |
| `max_validators` | [uint32](#uint32) | | |
| `max_entries` | [uint32](#uint32) | | |
| `historical_entries` | [uint32](#uint32) | | |
| `bond_denom` | [string](#string) | | |
| `unbonding_time` | [google.protobuf.Duration](#google.protobuf.Duration) | | unbonding_time is the time duration of unbonding. |
| `max_validators` | [uint32](#uint32) | | max_validators is the maximum number of validators. |
| `max_entries` | [uint32](#uint32) | | max_entries is the max entries for either unbonding delegation or redelegation (per pair/trio). |
| `historical_entries` | [uint32](#uint32) | | historical_entries is the number of historical entries to persist. |
| `bond_denom` | [string](#string) | | bond_denom defines the bondable coin denomination. |
@ -5588,10 +5920,12 @@ from a particular source validator to a particular destination validator.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `delegator_address` | [string](#string) | | |
| `validator_src_address` | [string](#string) | | |
| `validator_dst_address` | [string](#string) | | |
| `entries` | [RedelegationEntry](#cosmos.staking.v1beta1.RedelegationEntry) | repeated | redelegation entries |
| `delegator_address` | [string](#string) | | delegator_address is the bech32-encoded address of the delegator. |
| `validator_src_address` | [string](#string) | | validator_src_address is the validator redelegation source operator address. |
| `validator_dst_address` | [string](#string) | | validator_dst_address is the validator redelegation destination operator address. |
| `entries` | [RedelegationEntry](#cosmos.staking.v1beta1.RedelegationEntry) | repeated | entries are the redelegation entries.
redelegation entries |
@ -5606,10 +5940,10 @@ RedelegationEntry defines a redelegation object with relevant metadata.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `creation_height` | [int64](#int64) | | |
| `completion_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
| `initial_balance` | [string](#string) | | |
| `shares_dst` | [string](#string) | | |
| `creation_height` | [int64](#int64) | | creation_height defines the height which the redelegation took place. |
| `completion_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | completion_time defines the unix time for redelegation completion. |
| `initial_balance` | [string](#string) | | initial_balance defines the initial balance when redelegation started. |
| `shares_dst` | [string](#string) | | shares_dst is the amount of destination-validator shares created by redelegation. |
@ -5661,9 +5995,11 @@ for a single validator in an time-ordered list.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `delegator_address` | [string](#string) | | |
| `validator_address` | [string](#string) | | |
| `entries` | [UnbondingDelegationEntry](#cosmos.staking.v1beta1.UnbondingDelegationEntry) | repeated | unbonding delegation entries |
| `delegator_address` | [string](#string) | | delegator_address is the bech32-encoded address of the delegator. |
| `validator_address` | [string](#string) | | validator_address is the bech32-encoded address of the validator. |
| `entries` | [UnbondingDelegationEntry](#cosmos.staking.v1beta1.UnbondingDelegationEntry) | repeated | entries are the unbonding delegation entries.
unbonding delegation entries |
@ -5678,10 +6014,10 @@ UnbondingDelegationEntry defines an unbonding object with relevant metadata.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `creation_height` | [int64](#int64) | | |
| `completion_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
| `initial_balance` | [string](#string) | | |
| `balance` | [string](#string) | | |
| `creation_height` | [int64](#int64) | | creation_height is the height which the unbonding took place. |
| `completion_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | completion_time is the unix time for unbonding completion. |
| `initial_balance` | [string](#string) | | initial_balance defines the tokens initially scheduled to receive at completion. |
| `balance` | [string](#string) | | balance defines the tokens to receive at completion. |
@ -5718,17 +6054,17 @@ multiplied by exchange rate.
| Field | Type | Label | Description |
| ----- | ---- | ----- | ----------- |
| `operator_address` | [string](#string) | | |
| `consensus_pubkey` | [google.protobuf.Any](#google.protobuf.Any) | | |
| `jailed` | [bool](#bool) | | |
| `status` | [BondStatus](#cosmos.staking.v1beta1.BondStatus) | | |
| `tokens` | [string](#string) | | |
| `delegator_shares` | [string](#string) | | |
| `description` | [Description](#cosmos.staking.v1beta1.Description) | | |
| `unbonding_height` | [int64](#int64) | | |
| `unbonding_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
| `commission` | [Commission](#cosmos.staking.v1beta1.Commission) | | |
| `min_self_delegation` | [string](#string) | | |
| `operator_address` | [string](#string) | | operator_address defines the address of the validator's operator; bech encoded in JSON. |
| `consensus_pubkey` | [google.protobuf.Any](#google.protobuf.Any) | | consensus_pubkey is the consensus public key of the validator, as a Protobuf Any. |
| `jailed` | [bool](#bool) | | jailed defined whether the validator has been jailed from bonded status or not. |
| `status` | [BondStatus](#cosmos.staking.v1beta1.BondStatus) | | status is the validator status (bonded/unbonding/unbonded). |
| `tokens` | [string](#string) | | tokens define the delegated tokens (incl. self-delegation). |
| `delegator_shares` | [string](#string) | | delegator_shares defines total shares issued to a validator's delegators. |
| `description` | [Description](#cosmos.staking.v1beta1.Description) | | description defines the description terms for the validator. |
| `unbonding_height` | [int64](#int64) | | unbonding_height defines, if unbonding, the height at which this validator has begun unbonding. |
| `unbonding_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | unbonding_time defines, if unbonding, the min time for the validator to complete unbonding. |
| `commission` | [Commission](#cosmos.staking.v1beta1.Commission) | | commission defines the commission parameters. |
| `min_self_delegation` | [string](#string) | | min_self_delegation is the validator's self declared minimum self delegation. |

View File

@ -0,0 +1,81 @@
syntax = "proto3";
package cosmos.feegrant.v1beta1;
import "gogoproto/gogo.proto";
import "google/protobuf/any.proto";
import "cosmos_proto/cosmos.proto";
import "cosmos/base/v1beta1/coin.proto";
import "google/protobuf/timestamp.proto";
import "google/protobuf/duration.proto";
option go_package = "github.com/cosmos/cosmos-sdk/x/feegrant/types";
// BasicFeeAllowance implements FeeAllowance with a one-time grant of tokens
// that optionally expires. The delegatee can use up to SpendLimit to cover fees.
message BasicFeeAllowance {
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 cosmos.base.v1beta1.Coin spend_limit = 1
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// expiration specifies an optional time when this allowance expires
ExpiresAt expiration = 2 [(gogoproto.nullable) = false];
}
// PeriodicFeeAllowance extends FeeAllowance to allow for both a maximum cap,
// as well as a limit per time period.
message PeriodicFeeAllowance {
option (cosmos_proto.implements_interface) = "FeeAllowanceI";
// basic specifies a struct of `BasicFeeAllowance`
BasicFeeAllowance basic = 1 [(gogoproto.nullable) = false];
// period specifies the time duration in which period_spend_limit coins can
// be spent before that allowance is reset
Duration period = 2 [(gogoproto.nullable) = false];
// period_spend_limit specifies the maximum number of coins that can be spent
// in the period
repeated cosmos.base.v1beta1.Coin period_spend_limit = 3
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"];
// period_can_spend is the number of coins left to be spent before the period_reset time
repeated cosmos.base.v1beta1.Coin period_can_spend = 4
[(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-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
ExpiresAt period_reset = 5 [(gogoproto.nullable) = false];
}
// Duration is a span of a clock time or number of blocks.
// This is designed to be added to an ExpiresAt struct.
message Duration {
// sum is the oneof that represents either duration or block
oneof sum {
google.protobuf.Duration duration = 1 [(gogoproto.stdduration) = true];
uint64 blocks = 2;
}
}
// ExpiresAt is a point in time where something expires.
// It may be *either* block time or block height
message ExpiresAt {
// sum is the oneof that represents either time or height
oneof sum {
google.protobuf.Timestamp time = 1 [(gogoproto.stdtime) = true];
int64 height = 2;
}
}
// FeeAllowanceGrant is stored in the KVStore to record a grant with full context
message FeeAllowanceGrant {
string granter = 1;
string grantee = 2;
google.protobuf.Any allowance = 3 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"];
}

View File

@ -0,0 +1,12 @@
syntax = "proto3";
package cosmos.feegrant.v1beta1;
import "gogoproto/gogo.proto";
import "cosmos/feegrant/v1beta1/feegrant.proto";
option go_package = "github.com/cosmos/cosmos-sdk/x/feegrant/types";
// GenesisState contains a set of fee allowances, persisted from the store
message GenesisState {
repeated FeeAllowanceGrant fee_allowances = 1 [(gogoproto.nullable) = false];
}

View File

@ -0,0 +1,52 @@
syntax = "proto3";
package cosmos.feegrant.v1beta1;
import "gogoproto/gogo.proto";
import "cosmos/feegrant/v1beta1/feegrant.proto";
import "cosmos/base/query/v1beta1/pagination.proto";
import "google/api/annotations.proto";
option go_package = "github.com/cosmos/cosmos-sdk/x/feegrant/types";
// Query defines the gRPC querier service.
service Query {
// FeeAllowance returns fee granted to the grantee by the granter.
rpc FeeAllowance(QueryFeeAllowanceRequest) returns (QueryFeeAllowanceResponse) {
option (google.api.http).get = "/cosmos/feegrant/v1beta1/fee_allowance/{granter}/{grantee}";
}
// FeeAllowances returns all the grants for address.
rpc FeeAllowances(QueryFeeAllowancesRequest) returns (QueryFeeAllowancesResponse) {
option (google.api.http).get = "/cosmos/feegrant/v1beta1/fee_allowances/{grantee}";
}
}
// QueryFeeAllowanceRequest is the request type for the Query/FeeAllowance RPC method.
message QueryFeeAllowanceRequest {
string granter = 1;
string grantee = 2;
}
// QueryFeeAllowanceResponse is the response type for the Query/FeeAllowance RPC method.
message QueryFeeAllowanceResponse {
// fee_allowance is a fee_allowance granted for grantee by granter.
cosmos.feegrant.v1beta1.FeeAllowanceGrant fee_allowance = 1;
}
// QueryFeeAllowancesRequest is the request type for the Query/FeeAllowances RPC method.
message QueryFeeAllowancesRequest {
string grantee = 1;
// pagination defines an pagination for the request.
cosmos.base.query.v1beta1.PageRequest pagination = 2;
}
// QueryFeeAllowancesResponse is the response type for the Query/FeeAllowances RPC method.
message QueryFeeAllowancesResponse {
// fee_allowances are fee_allowance's granted for grantee by granter.
repeated cosmos.feegrant.v1beta1.FeeAllowanceGrant fee_allowances = 1;
// pagination defines an pagination for the response.
cosmos.base.query.v1beta1.PageResponse pagination = 2;
}

View File

@ -0,0 +1,40 @@
syntax = "proto3";
package cosmos.feegrant.v1beta1;
import "gogoproto/gogo.proto";
import "google/protobuf/any.proto";
import "cosmos_proto/cosmos.proto";
option go_package = "github.com/cosmos/cosmos-sdk/x/feegrant/types";
// Msg defines the feegrant msg service.
service Msg {
// GrantFeeAllowance grants fee allowance to the grantee on the granter's
// account with the provided expiration time.
rpc GrantFeeAllowance(MsgGrantFeeAllowance) returns (MsgGrantFeeAllowanceResponse);
// RevokeFeeAllowance revokes any fee allowance of granter's account that
// has been granted to the grantee.
rpc RevokeFeeAllowance(MsgRevokeFeeAllowance) returns (MsgRevokeFeeAllowanceResponse);
}
// MsgGrantFeeAllowance adds permission for Grantee to spend up to Allowance
// of fees from the account of Granter.
message MsgGrantFeeAllowance {
string granter = 1;
string grantee = 2;
google.protobuf.Any allowance = 3 [(cosmos_proto.accepts_interface) = "FeeAllowanceI"];
}
// MsgGrantFeeAllowanceResponse defines the Msg/GrantFeeAllowanceResponse response type.
message MsgGrantFeeAllowanceResponse {}
// MsgRevokeFeeAllowance removes any existing FeeAllowance from Granter to Grantee.
message MsgRevokeFeeAllowance {
string granter = 1;
string grantee = 2;
}
// MsgRevokeFeeAllowanceResponse defines the Msg/RevokeFeeAllowanceResponse response type.
message MsgRevokeFeeAllowanceResponse {}

View File

@ -27,12 +27,15 @@ message CommissionRates {
option (gogoproto.equal) = true;
option (gogoproto.goproto_stringer) = false;
// rate is the commission rate charged to delegators, as a fraction.
string rate = 1 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
// max_rate defines the maximum commission rate which validator can ever charge, as a fraction.
string max_rate = 2 [
(gogoproto.moretags) = "yaml:\"max_rate\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// max_change_rate defines the maximum daily increase of the validator commission, as a fraction.
string max_change_rate = 3 [
(gogoproto.moretags) = "yaml:\"max_change_rate\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
@ -45,7 +48,9 @@ message Commission {
option (gogoproto.equal) = true;
option (gogoproto.goproto_stringer) = false;
// commission_rates defines the initial commission rates to be used for creating a validator.
CommissionRates commission_rates = 1 [(gogoproto.embed) = true, (gogoproto.nullable) = false];
// update_time is the last time the commission rate was changed.
google.protobuf.Timestamp update_time = 2
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"update_time\""];
}
@ -55,10 +60,15 @@ message Description {
option (gogoproto.equal) = true;
option (gogoproto.goproto_stringer) = false;
// moniker defines a human-readable name for the validator.
string moniker = 1;
// identity defines an optional identity signature (ex. UPort or Keybase).
string identity = 2;
// website defines an optional website link.
string website = 3;
// security_contact defines an optional email for security contact.
string security_contact = 4 [(gogoproto.moretags) = "yaml:\"security_contact\""];
// details define other optional details.
string details = 5;
}
@ -75,22 +85,33 @@ message Validator {
option (gogoproto.goproto_stringer) = false;
option (gogoproto.goproto_getters) = false;
// operator_address defines the address of the validator's operator; bech encoded in JSON.
string operator_address = 1 [(gogoproto.moretags) = "yaml:\"operator_address\""];
// consensus_pubkey is the consensus public key of the validator, as a Protobuf Any.
google.protobuf.Any consensus_pubkey = 2
[(cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey", (gogoproto.moretags) = "yaml:\"consensus_pubkey\""];
// jailed defined whether the validator has been jailed from bonded status or not.
bool jailed = 3;
// status is the validator status (bonded/unbonding/unbonded).
BondStatus status = 4;
// tokens define the delegated tokens (incl. self-delegation).
string tokens = 5 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
// delegator_shares defines total shares issued to a validator's delegators.
string delegator_shares = 6 [
(gogoproto.moretags) = "yaml:\"delegator_shares\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];
// description defines the description terms for the validator.
Description description = 7 [(gogoproto.nullable) = false];
// unbonding_height defines, if unbonding, the height at which this validator has begun unbonding.
int64 unbonding_height = 8 [(gogoproto.moretags) = "yaml:\"unbonding_height\""];
// unbonding_time defines, if unbonding, the min time for the validator to complete unbonding.
google.protobuf.Timestamp unbonding_time = 9
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"unbonding_time\""];
// commission defines the commission parameters.
Commission commission = 10 [(gogoproto.nullable) = false];
// min_self_delegation is the validator's self declared minimum self delegation.
string min_self_delegation = 11 [
(gogoproto.moretags) = "yaml:\"min_self_delegation\"",
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
@ -164,8 +185,11 @@ message Delegation {
option (gogoproto.goproto_getters) = false;
option (gogoproto.goproto_stringer) = false;
// delegator_address is the bech32-encoded address of the delegator.
string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""];
// validator_address is the bech32-encoded address of the validator.
string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""];
// shares define the delegation shares received.
string shares = 3 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}
@ -176,8 +200,11 @@ message UnbondingDelegation {
option (gogoproto.goproto_getters) = false;
option (gogoproto.goproto_stringer) = false;
// delegator_address is the bech32-encoded address of the delegator.
string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""];
// validator_address is the bech32-encoded address of the validator.
string validator_address = 2 [(gogoproto.moretags) = "yaml:\"validator_address\""];
// entries are the unbonding delegation entries.
repeated UnbondingDelegationEntry entries = 3 [(gogoproto.nullable) = false]; // unbonding delegation entries
}
@ -186,14 +213,18 @@ message UnbondingDelegationEntry {
option (gogoproto.equal) = true;
option (gogoproto.goproto_stringer) = false;
// creation_height is the height which the unbonding took place.
int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""];
// completion_time is the unix time for unbonding completion.
google.protobuf.Timestamp completion_time = 2
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"completion_time\""];
// initial_balance defines the tokens initially scheduled to receive at completion.
string initial_balance = 3 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"initial_balance\""
];
// balance defines the tokens to receive at completion.
string balance = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
}
@ -202,14 +233,18 @@ message RedelegationEntry {
option (gogoproto.equal) = true;
option (gogoproto.goproto_stringer) = false;
// creation_height defines the height which the redelegation took place.
int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""];
// completion_time defines the unix time for redelegation completion.
google.protobuf.Timestamp completion_time = 2
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"completion_time\""];
// initial_balance defines the initial balance when redelegation started.
string initial_balance = 3 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.nullable) = false,
(gogoproto.moretags) = "yaml:\"initial_balance\""
];
// shares_dst is the amount of destination-validator shares created by redelegation.
string shares_dst = 4
[(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
}
@ -221,9 +256,13 @@ message Redelegation {
option (gogoproto.goproto_getters) = false;
option (gogoproto.goproto_stringer) = false;
// delegator_address is the bech32-encoded address of the delegator.
string delegator_address = 1 [(gogoproto.moretags) = "yaml:\"delegator_address\""];
// validator_src_address is the validator redelegation source operator address.
string validator_src_address = 2 [(gogoproto.moretags) = "yaml:\"validator_src_address\""];
// validator_dst_address is the validator redelegation destination operator address.
string validator_dst_address = 3 [(gogoproto.moretags) = "yaml:\"validator_dst_address\""];
// entries are the redelegation entries.
repeated RedelegationEntry entries = 4 [(gogoproto.nullable) = false]; // redelegation entries
}
@ -232,11 +271,16 @@ message Params {
option (gogoproto.equal) = true;
option (gogoproto.goproto_stringer) = false;
// unbonding_time is the time duration of unbonding.
google.protobuf.Duration unbonding_time = 1
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true, (gogoproto.moretags) = "yaml:\"unbonding_time\""];
// max_validators is the maximum number of validators.
uint32 max_validators = 2 [(gogoproto.moretags) = "yaml:\"max_validators\""];
// max_entries is the max entries for either unbonding delegation or redelegation (per pair/trio).
uint32 max_entries = 3 [(gogoproto.moretags) = "yaml:\"max_entries\""];
// historical_entries is the number of historical entries to persist.
uint32 historical_entries = 4 [(gogoproto.moretags) = "yaml:\"historical_entries\""];
// bond_denom defines the bondable coin denomination.
string bond_denom = 5 [(gogoproto.moretags) = "yaml:\"bond_denom\""];
}

View File

@ -55,6 +55,10 @@ import (
"github.com/cosmos/cosmos-sdk/x/evidence"
evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper"
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
feegrant "github.com/cosmos/cosmos-sdk/x/feegrant"
feegrantante "github.com/cosmos/cosmos-sdk/x/feegrant/ante"
feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
feegranttypes "github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
"github.com/cosmos/cosmos-sdk/x/gov"
@ -120,6 +124,7 @@ var (
crisis.AppModuleBasic{},
slashing.AppModuleBasic{},
ibc.AppModuleBasic{},
feegrant.AppModuleBasic{},
upgrade.AppModuleBasic{},
evidence.AppModuleBasic{},
transfer.AppModuleBasic{},
@ -181,6 +186,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
@ -222,7 +228,7 @@ func NewSimApp(
keys := sdk.NewKVStoreKeys(
authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey,
minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, feegranttypes.StoreKey,
evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey,
authztypes.StoreKey,
)
@ -277,6 +283,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
@ -347,6 +355,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),
@ -379,6 +388,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, authztypes.ModuleName, ibctransfertypes.ModuleName,
feegranttypes.ModuleName,
)
app.mm.RegisterInvariants(&app.CrisisKeeper)
@ -396,6 +406,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),
@ -419,8 +430,8 @@ func NewSimApp(
app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
app.SetAnteHandler(
ante.NewAnteHandler(
app.AccountKeeper, app.BankKeeper, ante.DefaultSigVerificationGasConsumer,
feegrantante.NewAnteHandler(
app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, ante.DefaultSigVerificationGasConsumer,
encodingConfig.TxConfig.SignModeHandler(),
),
)

View File

@ -157,7 +157,7 @@ func (app *SimApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []
counter := int16(0)
for ; iter.Valid(); iter.Next() {
addr := sdk.ValAddress(iter.Key()[1:])
addr := sdk.ValAddress(stakingtypes.AddressFromValidatorsKey(iter.Key()))
validator, found := app.StakingKeeper.GetValidator(ctx, addr)
if !found {
panic("expected validator, not found")

View File

@ -20,4 +20,8 @@ const (
DefaultWeightCommunitySpendProposal int = 5
DefaultWeightTextProposal int = 5
DefaultWeightParamChangeProposal int = 5
// feegrant
DefaultWeightGrantFeeAllowance int = 100
DefaultWeightRevokeFeeAllowance int = 100
)

View File

@ -6,6 +6,7 @@ import (
"errors"
"io"
"io/ioutil"
"os"
"testing"
"time"
@ -100,7 +101,10 @@ func (m *mockSnapshotter) Snapshot(height uint64, format uint32) (<-chan io.Read
// setupBusyManager creates a manager with an empty store that is busy creating a snapshot at height 1.
// The snapshot will complete when the returned closer is called.
func setupBusyManager(t *testing.T) *snapshots.Manager {
tempdir := t.TempDir()
tempdir, err := ioutil.TempDir("", "")
require.NoError(t, err)
t.Cleanup(func() { _ = os.RemoveAll(tempdir) })
store, err := snapshots.NewStore(db.NewMemDB(), tempdir)
require.NoError(t, err)
hung := newHungSnapshotter()

View File

@ -5,6 +5,7 @@ import (
"errors"
"io"
"io/ioutil"
"os"
"path/filepath"
"testing"
"time"
@ -19,7 +20,10 @@ import (
)
func setupStore(t *testing.T) *snapshots.Store {
tempdir := t.TempDir()
tempdir, err := ioutil.TempDir("", "")
require.NoError(t, err)
t.Cleanup(func() { _ = os.RemoveAll(tempdir) })
store, err := snapshots.NewStore(db.NewMemDB(), tempdir)
require.NoError(t, err)

View File

@ -711,7 +711,7 @@ func (rs *Store) Restore(
if height == 0 {
return sdkerrors.Wrap(sdkerrors.ErrLogic, "cannot restore snapshot at height 0")
}
if height > uint64(math.MaxUint64) {
if height > uint64(math.MaxInt64) {
return sdkerrors.Wrapf(snapshottypes.ErrInvalidMetadata,
"snapshot height %v cannot exceed %v", height, int64(math.MaxInt64))
}

View File

@ -12,7 +12,9 @@ import (
"github.com/cosmos/cosmos-sdk/codec/legacy"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/types/bech32"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
const (
@ -28,8 +30,6 @@ const (
// config.SetFullFundraiserPath(yourFullFundraiserPath)
// config.Seal()
// AddrLen defines a valid address length
AddrLen = 20
// Bech32MainPrefix defines the main SDK Bech32 prefix of an account's address
Bech32MainPrefix = "cosmos"
@ -110,9 +110,15 @@ func VerifyAddressFormat(bz []byte) error {
if verifier != nil {
return verifier(bz)
}
if len(bz) != AddrLen {
return fmt.Errorf("incorrect address length (expected: %d, actual: %d)", AddrLen, len(bz))
if len(bz) == 0 {
return sdkerrors.Wrap(sdkerrors.ErrUnknownAddress, "addresses cannot be empty")
}
if len(bz) > address.MaxAddrLen {
return sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "address max length is %d, got %d", address.MaxAddrLen, len(bz))
}
return nil
}

View File

@ -0,0 +1,33 @@
package address
import (
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// MaxAddrLen is the maximum allowed length (in bytes) for an address.
const MaxAddrLen = 255
// LengthPrefix prefixes the address bytes with its length, this is used
// for example for variable-length components in store keys.
func LengthPrefix(bz []byte) ([]byte, error) {
bzLen := len(bz)
if bzLen == 0 {
return bz, nil
}
if bzLen > MaxAddrLen {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "address length should be max %d bytes, got %d", MaxAddrLen, bzLen)
}
return append([]byte{byte(bzLen)}, bz...), nil
}
// MustLengthPrefix is LengthPrefix with panic on error.
func MustLengthPrefix(bz []byte) []byte {
res, err := LengthPrefix(bz)
if err != nil {
panic(err)
}
return res
}

View File

@ -0,0 +1,38 @@
package address_test
import (
"testing"
"github.com/stretchr/testify/require"
"github.com/cosmos/cosmos-sdk/types/address"
)
func TestLengthPrefixedAddressStoreKey(t *testing.T) {
addr10byte := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
addr20byte := []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19}
addr256byte := make([]byte, 256)
tests := []struct {
name string
addr []byte
expStoreKey []byte
expErr bool
}{
{"10-byte address", addr10byte, append([]byte{byte(10)}, addr10byte...), false},
{"20-byte address", addr20byte, append([]byte{byte(20)}, addr20byte...), false},
{"256-byte address (too long)", addr256byte, nil, true},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
storeKey, err := address.LengthPrefix(tt.addr)
if tt.expErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.expStoreKey, storeKey)
}
})
}
}

View File

@ -347,15 +347,18 @@ func (s *addressTestSuite) TestVerifyAddressFormat() {
addr5 := make([]byte, 5)
addr20 := make([]byte, 20)
addr32 := make([]byte, 32)
addr256 := make([]byte, 256)
err := types.VerifyAddressFormat(addr0)
s.Require().EqualError(err, "incorrect address length 0")
s.Require().EqualError(err, "addresses cannot be empty: unknown address")
err = types.VerifyAddressFormat(addr5)
s.Require().EqualError(err, "incorrect address length 5")
s.Require().NoError(err)
err = types.VerifyAddressFormat(addr20)
s.Require().Nil(err)
s.Require().NoError(err)
err = types.VerifyAddressFormat(addr32)
s.Require().EqualError(err, "incorrect address length 32")
s.Require().NoError(err)
err = types.VerifyAddressFormat(addr256)
s.Require().EqualError(err, "address max length is 255, got 256: unknown address")
}
func (s *addressTestSuite) TestCustomAddressVerifier() {
@ -364,34 +367,39 @@ func (s *addressTestSuite) TestCustomAddressVerifier() {
accBech := types.AccAddress(addr).String()
valBech := types.ValAddress(addr).String()
consBech := types.ConsAddress(addr).String()
// Verifiy that the default logic rejects this 10 byte address
// Verify that the default logic doesn't reject this 10 byte address
// The default verifier is nil, we're only checking address length is
// between 1-255 bytes.
err := types.VerifyAddressFormat(addr)
s.Require().NotNil(err)
s.Require().Nil(err)
_, err = types.AccAddressFromBech32(accBech)
s.Require().NotNil(err)
s.Require().Nil(err)
_, err = types.ValAddressFromBech32(valBech)
s.Require().NotNil(err)
s.Require().Nil(err)
_, err = types.ConsAddressFromBech32(consBech)
s.Require().NotNil(err)
s.Require().Nil(err)
// Set a custom address verifier that accepts 10 or 20 byte addresses
// Set a custom address verifier only accepts 20 byte addresses
types.GetConfig().SetAddressVerifier(func(bz []byte) error {
n := len(bz)
if n == 10 || n == types.AddrLen {
if n == 20 {
return nil
}
return fmt.Errorf("incorrect address length %d", n)
})
// Verifiy that the custom logic accepts this 10 byte address
// Verifiy that the custom logic rejects this 10 byte address
err = types.VerifyAddressFormat(addr)
s.Require().Nil(err)
s.Require().NotNil(err)
_, err = types.AccAddressFromBech32(accBech)
s.Require().Nil(err)
s.Require().NotNil(err)
_, err = types.ValAddressFromBech32(valBech)
s.Require().Nil(err)
s.Require().NotNil(err)
_, err = types.ConsAddressFromBech32(consBech)
s.Require().Nil(err)
s.Require().NotNil(err)
// Reinitialize the global config to default address verifier (nil)
types.GetConfig().SetAddressVerifier(nil)
}
func (s *addressTestSuite) TestBech32ifyAddressBytes() {

View File

@ -6,6 +6,7 @@ import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/types/query"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/bank/types"
@ -111,7 +112,7 @@ func ExampleFilteredPaginate() {
pageReq := &query.PageRequest{Key: nil, Limit: 1, CountTotal: true}
store := ctx.KVStore(app.GetKey(authtypes.StoreKey))
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr1.Bytes())
accountStore := prefix.NewStore(balancesStore, address.MustLengthPrefix(addr1))
var balResult sdk.Coins
pageRes, err := query.FilteredPaginate(accountStore, pageReq, func(key []byte, value []byte, accumulate bool) (bool, error) {
@ -143,7 +144,7 @@ func ExampleFilteredPaginate() {
func execFilterPaginate(store sdk.KVStore, pageReq *query.PageRequest, appCodec codec.Marshaler) (balances sdk.Coins, res *query.PageResponse, err error) {
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr1.Bytes())
accountStore := prefix.NewStore(balancesStore, address.MustLengthPrefix(addr1))
var balResult sdk.Coins
res, err = query.FilteredPaginate(accountStore, pageReq, func(key []byte, value []byte, accumulate bool) (bool, error) {

View File

@ -17,6 +17,7 @@ import (
"github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/types/query"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
@ -193,7 +194,7 @@ func ExamplePaginate() {
balResult := sdk.NewCoins()
authStore := ctx.KVStore(app.GetKey(authtypes.StoreKey))
balancesStore := prefix.NewStore(authStore, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr1.Bytes())
accountStore := prefix.NewStore(balancesStore, address.MustLengthPrefix(addr1))
pageRes, err := query.Paginate(accountStore, request.Pagination, func(key []byte, value []byte) error {
var tempRes sdk.Coin
err := app.AppCodec().UnmarshalBinaryBare(value, &tempRes)

View File

@ -84,6 +84,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

View File

@ -2,6 +2,7 @@ package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
)
const (
@ -21,7 +22,7 @@ const (
// Keys for authz store
// Items are stored with the following key: values
//
// - 0x01<granterAddress_Bytes><granteeAddress_Bytes><msgType_Bytes>: Grant
// - 0x01<granterAddressLen (1 Byte)><granterAddress_Bytes><granteeAddressLen (1 Byte)><granteeAddress_Bytes><msgType_Bytes>: Grant
var (
// Keys for store prefixes
@ -30,12 +31,22 @@ var (
// GetAuthorizationStoreKey - return authorization store key
func GetAuthorizationStoreKey(grantee sdk.AccAddress, granter sdk.AccAddress, msgType string) []byte {
return append(append(append(GrantKey, granter.Bytes()...), grantee.Bytes()...), []byte(msgType)...)
return append(append(append(
GrantKey,
address.MustLengthPrefix(granter)...),
address.MustLengthPrefix(grantee)...),
[]byte(msgType)...,
)
}
// ExtractAddressesFromGrantKey - split granter & grantee address from the authorization key
func ExtractAddressesFromGrantKey(key []byte) (granterAddr, granteeAddr sdk.AccAddress) {
granterAddr = sdk.AccAddress(key[1 : sdk.AddrLen+1])
granteeAddr = sdk.AccAddress(key[sdk.AddrLen+1 : sdk.AddrLen*2+1])
// key if of format:
// 0x01<granterAddressLen (1 Byte)><granterAddress_Bytes><granteeAddressLen (1 Byte)><granteeAddress_Bytes><msgType_Bytes>
granterAddrLen := key[1] // remove prefix key
granterAddr = sdk.AccAddress(key[2 : 2+granterAddrLen])
granteeAddrLen := int(key[2+granterAddrLen])
granteeAddr = sdk.AccAddress(key[3+granterAddrLen : 3+granterAddrLen+byte(granteeAddrLen)])
return granterAddr, granteeAddr
}

View File

@ -57,9 +57,7 @@ func (k BaseKeeper) AllBalances(ctx context.Context, req *types.QueryAllBalances
sdkCtx := sdk.UnwrapSDKContext(ctx)
balances := sdk.NewCoins()
store := sdkCtx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
accountStore := k.getAccountStore(sdkCtx, addr)
pageRes, err := query.Paginate(accountStore, req.Pagination, func(_, value []byte) error {
var result sdk.Coin

View File

@ -2,7 +2,6 @@ package keeper
import (
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store/prefix"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
@ -233,9 +232,7 @@ func (k BaseSendKeeper) ClearBalances(ctx sdk.Context, addr sdk.AccAddress) {
return false
})
store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
accountStore := k.getAccountStore(ctx, addr)
for _, key := range keys {
accountStore.Delete(key)
@ -264,9 +261,7 @@ func (k BaseSendKeeper) SetBalance(ctx sdk.Context, addr sdk.AccAddress, balance
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, balance.String())
}
store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
accountStore := k.getAccountStore(ctx, addr)
bz := k.cdc.MustMarshalBinaryBare(&balance)
accountStore.Set([]byte(balance.Denom), bz)

View File

@ -97,9 +97,7 @@ func (k BaseViewKeeper) GetAccountsBalances(ctx sdk.Context) []types.Balance {
// GetBalance returns the balance of a specific denomination for a given account
// by address.
func (k BaseViewKeeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin {
store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
accountStore := k.getAccountStore(ctx, addr)
bz := accountStore.Get([]byte(denom))
if bz == nil {
@ -116,9 +114,7 @@ func (k BaseViewKeeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom s
// provides the token balance to a callback. If true is returned from the
// callback, iteration is halted.
func (k BaseViewKeeper) IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(sdk.Coin) bool) {
store := ctx.KVStore(k.storeKey)
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
accountStore := k.getAccountStore(ctx, addr)
iterator := accountStore.Iterator(nil, nil)
defer iterator.Close()
@ -214,3 +210,10 @@ func (k BaseViewKeeper) ValidateBalance(ctx sdk.Context, addr sdk.AccAddress) er
return nil
}
// getAccountStore gets the account store of the given address.
func (k BaseViewKeeper) getAccountStore(ctx sdk.Context, addr sdk.AccAddress) prefix.Store {
store := ctx.KVStore(k.storeKey)
return prefix.NewStore(store, types.CreateAccountBalancesPrefix(addr))
}

View File

@ -1,9 +1,8 @@
package types
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
)
const (
@ -22,7 +21,9 @@ const (
// KVStore keys
var (
BalancesPrefix = []byte("balances")
// BalancesPrefix is the for the account balances store. We use a byte
// (instead of say `[]]byte("balances")` to save some disk space).
BalancesPrefix = []byte{0x02}
SupplyKey = []byte{0x00}
DenomMetadataPrefix = []byte{0x1}
)
@ -37,10 +38,13 @@ func DenomMetadataKey(denom string) []byte {
// store. The key must not contain the perfix BalancesPrefix as the prefix store
// iterator discards the actual prefix.
func AddressFromBalancesStore(key []byte) sdk.AccAddress {
addr := key[:sdk.AddrLen]
if len(addr) != sdk.AddrLen {
panic(fmt.Sprintf("unexpected account address key length; got: %d, expected: %d", len(addr), sdk.AddrLen))
}
addrLen := key[0]
addr := key[1 : addrLen+1]
return sdk.AccAddress(addr)
}
// CreateAccountBalancesPrefix creates the prefix for an account's balances.
func CreateAccountBalancesPrefix(addr []byte) []byte {
return append(BalancesPrefix, address.MustLengthPrefix(addr)...)
}

View File

@ -6,6 +6,7 @@ import (
"github.com/stretchr/testify/require"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
"github.com/cosmos/cosmos-sdk/x/bank/types"
)
@ -19,8 +20,10 @@ func cloneAppend(bz []byte, tail []byte) (res []byte) {
func TestAddressFromBalancesStore(t *testing.T) {
addr, err := sdk.AccAddressFromBech32("cosmos1n88uc38xhjgxzw9nwre4ep2c8ga4fjxcar6mn7")
require.NoError(t, err)
addrLen := len(addr)
require.Equal(t, 20, addrLen)
key := cloneAppend(addr.Bytes(), []byte("stake"))
key := cloneAppend(address.MustLengthPrefix(addr), []byte("stake"))
res := types.AddressFromBalancesStore(key)
require.Equal(t, res, addr)
}

View File

@ -1,13 +1,19 @@
package types
import (
"errors"
"fmt"
"strings"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Validate performs a basic validation of the coin metadata fields
// Validate performs a basic validation of the coin metadata fields. It checks:
// - Base and Display denominations are valid coin denominations
// - Base and Display denominations are present in the DenomUnit slice
// - Base denomination has exponent 0
// - Denomination units are sorted in ascending order
// - Denomination units not duplicated
func (m Metadata) Validate() error {
if err := sdk.ValidateDenom(m.Base); err != nil {
return fmt.Errorf("invalid metadata base denom: %w", err)
@ -17,12 +23,37 @@ func (m Metadata) Validate() error {
return fmt.Errorf("invalid metadata display denom: %w", err)
}
var (
hasDisplay bool
currentExponent uint32 // check that the exponents are increasing
)
seenUnits := make(map[string]bool)
for _, denomUnit := range m.DenomUnits {
for i, denomUnit := range m.DenomUnits {
// The first denomination unit MUST be the base
if i == 0 {
// validate denomination and exponent
if denomUnit.Denom != m.Base {
return fmt.Errorf("metadata's first denomination unit must be the one with base denom '%s'", m.Base)
}
if denomUnit.Exponent != 0 {
return fmt.Errorf("the exponent for base denomination unit %s must be 0", m.Base)
}
} else if currentExponent >= denomUnit.Exponent {
return errors.New("denom units should be sorted asc by exponent")
}
currentExponent = denomUnit.Exponent
if seenUnits[denomUnit.Denom] {
return fmt.Errorf("duplicate denomination unit %s", denomUnit.Denom)
}
if denomUnit.Denom == m.Display {
hasDisplay = true
}
if err := denomUnit.Validate(); err != nil {
return err
}
@ -30,6 +61,10 @@ func (m Metadata) Validate() error {
seenUnits[denomUnit.Denom] = true
}
if !hasDisplay {
return fmt.Errorf("metadata must contain a denomination unit with display denom '%s'", m.Display)
}
return nil
}

View File

@ -51,7 +51,7 @@ func TestMetadataValidate(t *testing.T) {
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{"uatom", uint32(0), []string{"microatom"}},
{"uatom", uint32(0), []string{"microatom"}},
{"uatom", uint32(1), []string{"microatom"}},
},
Base: "uatom",
Display: "atom",
@ -94,6 +94,59 @@ func TestMetadataValidate(t *testing.T) {
},
true,
},
{
"no base denom unit",
types.Metadata{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{"matom", uint32(3), []string{"milliatom"}},
{"atom", uint32(6), nil},
},
Base: "uatom",
Display: "atom",
},
true,
},
{
"base denom exponent not zero",
types.Metadata{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{"uatom", uint32(1), []string{"microatom"}},
{"matom", uint32(3), []string{"milliatom"}},
{"atom", uint32(6), nil},
},
Base: "uatom",
Display: "atom",
},
true,
},
{
"no display denom unit",
types.Metadata{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{"uatom", uint32(0), []string{"microatom"}},
},
Base: "uatom",
Display: "atom",
},
true,
},
{
"denom units not sorted",
types.Metadata{
Description: "The native staking token of the Cosmos Hub.",
DenomUnits: []*types.DenomUnit{
{"uatom", uint32(0), []string{"microatom"}},
{"atom", uint32(6), nil},
{"matom", uint32(3), []string{"milliatom"}},
},
Base: "uatom",
Display: "atom",
},
true,
},
}
for _, tc := range testCases {

View File

@ -23,7 +23,7 @@ func TestMsgSendValidation(t *testing.T) {
addr1 := sdk.AccAddress([]byte("from________________"))
addr2 := sdk.AccAddress([]byte("to__________________"))
addrEmpty := sdk.AccAddress([]byte(""))
addrTooLong := sdk.AccAddress([]byte("Accidentally used 33 bytes pubkey"))
addrLong := sdk.AccAddress([]byte("Purposefully long address"))
atom123 := sdk.NewCoins(sdk.NewInt64Coin("atom", 123))
atom0 := sdk.NewCoins(sdk.NewInt64Coin("atom", 0))
@ -36,12 +36,12 @@ func TestMsgSendValidation(t *testing.T) {
}{
{"", NewMsgSend(addr1, addr2, atom123)}, // valid send
{"", NewMsgSend(addr1, addr2, atom123eth123)}, // valid send with multiple coins
{"", NewMsgSend(addrLong, addr2, atom123)}, // valid send with long addr sender
{"", NewMsgSend(addr1, addrLong, atom123)}, // valid send with long addr recipient
{": invalid coins", NewMsgSend(addr1, addr2, atom0)}, // non positive coin
{"123atom,0eth: invalid coins", NewMsgSend(addr1, addr2, atom123eth0)}, // non positive coin in multicoins
{"Invalid sender address (empty address string is not allowed): invalid address", NewMsgSend(addrEmpty, addr2, atom123)},
{"Invalid sender address (incorrect address length (expected: 20, actual: 33)): invalid address", NewMsgSend(addrTooLong, addr2, atom123)},
{"Invalid recipient address (empty address string is not allowed): invalid address", NewMsgSend(addr1, addrEmpty, atom123)},
{"Invalid recipient address (incorrect address length (expected: 20, actual: 33)): invalid address", NewMsgSend(addr1, addrTooLong, atom123)},
}
for _, tc := range cases {
@ -91,7 +91,7 @@ func TestInputValidation(t *testing.T) {
addr1 := sdk.AccAddress([]byte("_______alice________"))
addr2 := sdk.AccAddress([]byte("________bob_________"))
addrEmpty := sdk.AccAddress([]byte(""))
addrTooLong := sdk.AccAddress([]byte("Accidentally used 33 bytes pubkey"))
addrLong := sdk.AccAddress([]byte("Purposefully long address"))
someCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123))
multiCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20))
@ -109,9 +109,9 @@ func TestInputValidation(t *testing.T) {
{"", NewInput(addr1, someCoins)},
{"", NewInput(addr2, someCoins)},
{"", NewInput(addr2, multiCoins)},
{"", NewInput(addrLong, someCoins)},
{"empty address string is not allowed", NewInput(addrEmpty, someCoins)},
{"incorrect address length (expected: 20, actual: 33)", NewInput(addrTooLong, someCoins)},
{": invalid coins", NewInput(addr1, emptyCoins)}, // invalid coins
{": invalid coins", NewInput(addr1, emptyCoins2)}, // invalid coins
{"10eth,0atom: invalid coins", NewInput(addr1, someEmptyCoins)}, // invalid coins
@ -132,7 +132,7 @@ func TestOutputValidation(t *testing.T) {
addr1 := sdk.AccAddress([]byte("_______alice________"))
addr2 := sdk.AccAddress([]byte("________bob_________"))
addrEmpty := sdk.AccAddress([]byte(""))
addrTooLong := sdk.AccAddress([]byte("Accidentally used 33 bytes pubkey"))
addrLong := sdk.AccAddress([]byte("Purposefully long address"))
someCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123))
multiCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20))
@ -150,9 +150,9 @@ func TestOutputValidation(t *testing.T) {
{"", NewOutput(addr1, someCoins)},
{"", NewOutput(addr2, someCoins)},
{"", NewOutput(addr2, multiCoins)},
{"", NewOutput(addrLong, someCoins)},
{"Invalid output address (empty address string is not allowed): invalid address", NewOutput(addrEmpty, someCoins)},
{"Invalid output address (incorrect address length (expected: 20, actual: 33)): invalid address", NewOutput(addrTooLong, someCoins)},
{": invalid coins", NewOutput(addr1, emptyCoins)}, // invalid coins
{": invalid coins", NewOutput(addr1, emptyCoins2)}, // invalid coins
{"10eth,0atom: invalid coins", NewOutput(addr1, someEmptyCoins)}, // invalid coins
@ -251,8 +251,6 @@ func TestMsgMultiSendGetSigners(t *testing.T) {
require.Equal(t, "[696E707574313131313131313131313131313131 696E707574323232323232323232323232323232 696E707574333333333333333333333333333333]", fmt.Sprintf("%v", res))
}
/*
// what to do w/ this test?
func TestMsgSendSigners(t *testing.T) {
signers := []sdk.AccAddress{
{1, 2, 3},
@ -265,8 +263,7 @@ func TestMsgSendSigners(t *testing.T) {
for i, signer := range signers {
inputs[i] = NewInput(signer, someCoins)
}
tx := NewMsgSend(inputs, nil)
tx := NewMsgMultiSend(inputs, nil)
require.Equal(t, signers, tx.Signers())
require.Equal(t, signers, tx.GetSigners())
}
*/

View File

@ -4,6 +4,7 @@ import (
"encoding/binary"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
)
const (
@ -27,19 +28,19 @@ const (
//
// - 0x01: sdk.ConsAddress
//
// - 0x02<valAddr_Bytes>: ValidatorOutstandingRewards
// - 0x02<valAddrLen (1 Byte)><valAddr_Bytes>: ValidatorOutstandingRewards
//
// - 0x03<accAddr_Bytes>: sdk.AccAddress
// - 0x03<accAddrLen (1 Byte)><accAddr_Bytes>: sdk.AccAddress
//
// - 0x04<valAddr_Bytes><accAddr_Bytes>: DelegatorStartingInfo
// - 0x04<valAddrLen (1 Byte)><valAddr_Bytes><accAddrLen (1 Byte)><accAddr_Bytes>: DelegatorStartingInfo
//
// - 0x05<valAddr_Bytes><period_Bytes>: ValidatorHistoricalRewards
// - 0x05<valAddrLen (1 Byte)><valAddr_Bytes><period_Bytes>: ValidatorHistoricalRewards
//
// - 0x06<valAddr_Bytes>: ValidatorCurrentRewards
// - 0x06<valAddrLen (1 Byte)><valAddr_Bytes>: ValidatorCurrentRewards
//
// - 0x07<valAddr_Bytes>: ValidatorCurrentRewards
// - 0x07<valAddrLen (1 Byte)><valAddr_Bytes>: ValidatorCurrentRewards
//
// - 0x08<valAddr_Bytes><height>: ValidatorSlashEvent
// - 0x08<valAddrLen (1 Byte)><valAddr_Bytes><height>: ValidatorSlashEvent
var (
FeePoolKey = []byte{0x00} // key for global distribution state
ProposerKey = []byte{0x01} // key for the proposer operator address
@ -53,47 +54,56 @@ var (
ValidatorSlashEventPrefix = []byte{0x08} // key for validator slash fraction
)
// gets an address from a validator's outstanding rewards key
// GetValidatorOutstandingRewardsAddress creates an address from a validator's outstanding rewards key.
func GetValidatorOutstandingRewardsAddress(key []byte) (valAddr sdk.ValAddress) {
addr := key[1:]
if len(addr) != sdk.AddrLen {
// key is in the format:
// 0x02<valAddrLen (1 Byte)><valAddr_Bytes>
// Remove prefix and address length.
addr := key[2:]
if len(addr) != int(key[1]) {
panic("unexpected key length")
}
return sdk.ValAddress(addr)
}
// gets an address from a delegator's withdraw info key
// GetDelegatorWithdrawInfoAddress creates an address from a delegator's withdraw info key.
func GetDelegatorWithdrawInfoAddress(key []byte) (delAddr sdk.AccAddress) {
addr := key[1:]
if len(addr) != sdk.AddrLen {
// key is in the format:
// 0x03<accAddrLen (1 Byte)><accAddr_Bytes>
// Remove prefix and address length.
addr := key[2:]
if len(addr) != int(key[1]) {
panic("unexpected key length")
}
return sdk.AccAddress(addr)
}
// gets the addresses from a delegator starting info key
// GetDelegatorStartingInfoAddresses creates the addresses from a delegator starting info key.
func GetDelegatorStartingInfoAddresses(key []byte) (valAddr sdk.ValAddress, delAddr sdk.AccAddress) {
addr := key[1 : 1+sdk.AddrLen]
if len(addr) != sdk.AddrLen {
// key is in the format:
// 0x04<valAddrLen (1 Byte)><valAddr_Bytes><accAddrLen (1 Byte)><accAddr_Bytes>
valAddrLen := int(key[1])
valAddr = sdk.ValAddress(key[2 : 2+valAddrLen])
delAddrLen := int(key[2+valAddrLen])
delAddr = sdk.AccAddress(key[3+valAddrLen:])
if len(delAddr.Bytes()) != delAddrLen {
panic("unexpected key length")
}
valAddr = sdk.ValAddress(addr)
addr = key[1+sdk.AddrLen:]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
delAddr = sdk.AccAddress(addr)
return
}
// gets the address & period from a validator's historical rewards key
// GetValidatorHistoricalRewardsAddressPeriod creates the address & period from a validator's historical rewards key.
func GetValidatorHistoricalRewardsAddressPeriod(key []byte) (valAddr sdk.ValAddress, period uint64) {
addr := key[1 : 1+sdk.AddrLen]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
valAddr = sdk.ValAddress(addr)
b := key[1+sdk.AddrLen:]
// key is in the format:
// 0x05<valAddrLen (1 Byte)><valAddr_Bytes><period_Bytes>
valAddrLen := int(key[1])
valAddr = sdk.ValAddress(key[2 : 2+valAddrLen])
b := key[2+valAddrLen:]
if len(b) != 8 {
panic("unexpected key length")
}
@ -101,93 +111,104 @@ func GetValidatorHistoricalRewardsAddressPeriod(key []byte) (valAddr sdk.ValAddr
return
}
// gets the address from a validator's current rewards key
// GetValidatorCurrentRewardsAddress creates the address from a validator's current rewards key.
func GetValidatorCurrentRewardsAddress(key []byte) (valAddr sdk.ValAddress) {
addr := key[1:]
if len(addr) != sdk.AddrLen {
// key is in the format:
// 0x06<valAddrLen (1 Byte)><valAddr_Bytes>: ValidatorCurrentRewards
// Remove prefix and address length.
addr := key[2:]
if len(addr) != int(key[1]) {
panic("unexpected key length")
}
return sdk.ValAddress(addr)
}
// gets the address from a validator's accumulated commission key
// GetValidatorAccumulatedCommissionAddress creates the address from a validator's accumulated commission key.
func GetValidatorAccumulatedCommissionAddress(key []byte) (valAddr sdk.ValAddress) {
addr := key[1:]
if len(addr) != sdk.AddrLen {
// key is in the format:
// 0x07<valAddrLen (1 Byte)><valAddr_Bytes>: ValidatorCurrentRewards
// Remove prefix and address length.
addr := key[2:]
if len(addr) != int(key[1]) {
panic("unexpected key length")
}
return sdk.ValAddress(addr)
}
// gets the height from a validator's slash event key
// GetValidatorSlashEventAddressHeight creates the height from a validator's slash event key.
func GetValidatorSlashEventAddressHeight(key []byte) (valAddr sdk.ValAddress, height uint64) {
addr := key[1 : 1+sdk.AddrLen]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
valAddr = sdk.ValAddress(addr)
startB := 1 + sdk.AddrLen
// key is in the format:
// 0x08<valAddrLen (1 Byte)><valAddr_Bytes><height>: ValidatorSlashEvent
valAddrLen := int(key[1])
valAddr = key[2 : 2+valAddrLen]
startB := 2 + valAddrLen
b := key[startB : startB+8] // the next 8 bytes represent the height
height = binary.BigEndian.Uint64(b)
return
}
// gets the outstanding rewards key for a validator
// GetValidatorOutstandingRewardsKey creates the outstanding rewards key for a validator.
func GetValidatorOutstandingRewardsKey(valAddr sdk.ValAddress) []byte {
return append(ValidatorOutstandingRewardsPrefix, valAddr.Bytes()...)
return append(ValidatorOutstandingRewardsPrefix, address.MustLengthPrefix(valAddr.Bytes())...)
}
// gets the key for a delegator's withdraw addr
// GetDelegatorWithdrawAddrKey creates the key for a delegator's withdraw addr.
func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte {
return append(DelegatorWithdrawAddrPrefix, delAddr.Bytes()...)
return append(DelegatorWithdrawAddrPrefix, address.MustLengthPrefix(delAddr.Bytes())...)
}
// gets the key for a delegator's starting info
// GetDelegatorStartingInfoKey creates the key for a delegator's starting info.
func GetDelegatorStartingInfoKey(v sdk.ValAddress, d sdk.AccAddress) []byte {
return append(append(DelegatorStartingInfoPrefix, v.Bytes()...), d.Bytes()...)
return append(append(DelegatorStartingInfoPrefix, address.MustLengthPrefix(v.Bytes())...), address.MustLengthPrefix(d.Bytes())...)
}
// gets the prefix key for a validator's historical rewards
// GetValidatorHistoricalRewardsPrefix creates the prefix key for a validator's historical rewards.
func GetValidatorHistoricalRewardsPrefix(v sdk.ValAddress) []byte {
return append(ValidatorHistoricalRewardsPrefix, v.Bytes()...)
return append(ValidatorHistoricalRewardsPrefix, address.MustLengthPrefix(v.Bytes())...)
}
// gets the key for a validator's historical rewards
// GetValidatorHistoricalRewardsKey creates the key for a validator's historical rewards.
func GetValidatorHistoricalRewardsKey(v sdk.ValAddress, k uint64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, k)
return append(append(ValidatorHistoricalRewardsPrefix, v.Bytes()...), b...)
return append(append(ValidatorHistoricalRewardsPrefix, address.MustLengthPrefix(v.Bytes())...), b...)
}
// gets the key for a validator's current rewards
// GetValidatorCurrentRewardsKey creates the key for a validator's current rewards.
func GetValidatorCurrentRewardsKey(v sdk.ValAddress) []byte {
return append(ValidatorCurrentRewardsPrefix, v.Bytes()...)
return append(ValidatorCurrentRewardsPrefix, address.MustLengthPrefix(v.Bytes())...)
}
// gets the key for a validator's current commission
// GetValidatorAccumulatedCommissionKey creates the key for a validator's current commission.
func GetValidatorAccumulatedCommissionKey(v sdk.ValAddress) []byte {
return append(ValidatorAccumulatedCommissionPrefix, v.Bytes()...)
return append(ValidatorAccumulatedCommissionPrefix, address.MustLengthPrefix(v.Bytes())...)
}
// gets the prefix key for a validator's slash fractions
// GetValidatorSlashEventPrefix creates the prefix key for a validator's slash fractions.
func GetValidatorSlashEventPrefix(v sdk.ValAddress) []byte {
return append(ValidatorSlashEventPrefix, v.Bytes()...)
return append(ValidatorSlashEventPrefix, address.MustLengthPrefix(v.Bytes())...)
}
// gets the prefix key for a validator's slash fraction (ValidatorSlashEventPrefix + height)
// GetValidatorSlashEventKeyPrefix creates the prefix key for a validator's slash fraction (ValidatorSlashEventPrefix + height).
func GetValidatorSlashEventKeyPrefix(v sdk.ValAddress, height uint64) []byte {
heightBz := make([]byte, 8)
binary.BigEndian.PutUint64(heightBz, height)
return append(
ValidatorSlashEventPrefix,
append(v.Bytes(), heightBz...)...,
append(address.MustLengthPrefix(v.Bytes()), heightBz...)...,
)
}
// gets the key for a validator's slash fraction
// GetValidatorSlashEventKey creates the key for a validator's slash fraction.
func GetValidatorSlashEventKey(v sdk.ValAddress, height, period uint64) []byte {
periodBz := make([]byte, 8)
binary.BigEndian.PutUint64(periodBz, period)
prefix := GetValidatorSlashEventKeyPrefix(v, height)
return append(prefix, periodBz...)
}

View File

@ -88,7 +88,8 @@ func (e Equivocation) GetTotalPower() int64 { return 0 }
// FromABCIEvidence converts a Tendermint concrete Evidence type to
// SDK Evidence using Equivocation as the concrete type.
func FromABCIEvidence(e abci.Evidence) exported.Evidence {
consAddr, err := sdk.Bech32ifyAddressBytes(sdk.Bech32PrefixConsAddr, e.Validator.Address)
bech32PrefixConsAddr := sdk.GetConfig().GetBech32ConsensusAddrPrefix()
consAddr, err := sdk.Bech32ifyAddressBytes(bech32PrefixConsAddr, e.Validator.Address)
if err != nil {
panic(err)
}

View File

@ -5,6 +5,7 @@ import (
"time"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/evidence/types"
@ -57,3 +58,22 @@ func TestEquivocationValidateBasic(t *testing.T) {
})
}
}
func TestEvidenceAddressConversion(t *testing.T) {
sdk.GetConfig().SetBech32PrefixForConsensusNode("testcnclcons", "testcnclconspub")
tmEvidence := abci.Evidence{
Type: abci.EvidenceType_DUPLICATE_VOTE,
Validator: abci.Validator{
Address: []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
Power: 100,
},
Height: 1,
Time: time.Now(),
TotalVotingPower: 100,
}
evidence := types.FromABCIEvidence(tmEvidence).(*types.Equivocation)
consAddr := evidence.GetConsensusAddress()
// Check the address is the same after conversion
require.Equal(t, tmEvidence.Validator.Address, consAddr.Bytes())
sdk.GetConfig().SetBech32PrefixForConsensusNode(sdk.Bech32PrefixConsAddr, sdk.Bech32PrefixConsPub)
}

35
x/feegrant/ante/ante.go Normal file
View File

@ -0,0 +1,35 @@
package ante
import (
sdk "github.com/cosmos/cosmos-sdk/types"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
"github.com/cosmos/cosmos-sdk/x/auth/signing"
feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
feegranttypes "github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
// NewAnteHandler returns an AnteHandler that checks and increments sequence
// numbers, checks signatures & account numbers, and deducts fees from the
// fee_payer or from fee_granter (if valid grant exist).
func NewAnteHandler(
ak authkeeper.AccountKeeper, bankKeeper feegranttypes.BankKeeper, feeGrantKeeper feegrantkeeper.Keeper,
sigGasConsumer authante.SignatureVerificationGasConsumer, signModeHandler signing.SignModeHandler,
) sdk.AnteHandler {
return sdk.ChainAnteDecorators(
authante.NewSetUpContextDecorator(), // outermost AnteDecorator. SetUpContext must be called first
authante.NewRejectExtensionOptionsDecorator(),
authante.NewMempoolFeeDecorator(),
authante.NewValidateBasicDecorator(),
authante.TxTimeoutHeightDecorator{},
authante.NewValidateMemoDecorator(ak),
authante.NewConsumeGasForTxSizeDecorator(ak),
NewDeductGrantedFeeDecorator(ak, bankKeeper, feeGrantKeeper),
authante.NewSetPubKeyDecorator(ak), // SetPubKeyDecorator must be called before all signature verification decorators
authante.NewValidateSigCountDecorator(ak),
authante.NewSigGasConsumeDecorator(ak, sigGasConsumer),
authante.NewSigVerificationDecorator(ak, signModeHandler),
authante.NewIncrementSequenceDecorator(ak), // innermost AnteDecorator
)
}

82
x/feegrant/ante/fee.go Normal file
View File

@ -0,0 +1,82 @@
package ante
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
// 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 types.AccountKeeper
k keeper.Keeper
bk types.BankKeeper
}
func NewDeductGrantedFeeDecorator(ak types.AccountKeeper, bk types.BankKeeper, k keeper.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(authtypes.FeeCollectorName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", authtypes.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 != nil && !feeGranter.Equals(feePayer) {
err := d.k.UseGrantedFees(ctx, feeGranter, feePayer, fee)
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 = authante.DeductFees(d.bk, ctx, deductFeesFromAcc, fee)
if err != nil {
return ctx, err
}
return next(ctx, tx, simulate)
}

287
x/feegrant/ante/fee_test.go Normal file
View File

@ -0,0 +1,287 @@
package ante_test
import (
"math/rand"
"testing"
"time"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/crypto"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/cosmos/cosmos-sdk/simapp"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/types/tx/signing"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authsign "github.com/cosmos/cosmos-sdk/x/auth/signing"
"github.com/cosmos/cosmos-sdk/x/auth/tx"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/feegrant/ante"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
// AnteTestSuite is a test suite to be used with ante handler tests.
type AnteTestSuite struct {
suite.Suite
app *simapp.SimApp
anteHandler sdk.AnteHandler
ctx sdk.Context
clientCtx client.Context
txBuilder client.TxBuilder
}
// SetupTest setups a new test, with new app, context, and anteHandler.
func (suite *AnteTestSuite) SetupTest(isCheckTx bool) {
suite.app, suite.ctx = createTestApp(isCheckTx)
suite.ctx = suite.ctx.WithBlockHeight(1)
// Set up TxConfig.
encodingConfig := simapp.MakeTestEncodingConfig()
// We're using TestMsg encoding in some tests, so register it here.
encodingConfig.Amino.RegisterConcrete(&testdata.TestMsg{}, "testdata.TestMsg", nil)
testdata.RegisterInterfaces(encodingConfig.InterfaceRegistry)
suite.clientCtx = client.Context{}.
WithTxConfig(encodingConfig.TxConfig)
suite.anteHandler = ante.NewAnteHandler(suite.app.AccountKeeper, suite.app.BankKeeper, suite.app.FeeGrantKeeper, authante.DefaultSigVerificationGasConsumer, encodingConfig.TxConfig.SignModeHandler())
}
func (suite *AnteTestSuite) TestDeductFeesNoDelegation() {
suite.SetupTest(true)
// setup
app, ctx := suite.app, suite.ctx
protoTxCfg := tx.NewTxConfig(codec.NewProtoCodec(app.InterfaceRegistry()), tx.DefaultSignModes)
// this just tests our handler
dfd := ante.NewDeductGrantedFeeDecorator(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper)
ourAnteHandler := sdk.ChainAnteDecorators(dfd)
// this tests the whole stack
anteHandlerStack := suite.anteHandler
// keys and addresses
priv1, _, addr1 := testdata.KeyTestPubAddr()
priv2, _, addr2 := testdata.KeyTestPubAddr()
priv3, _, addr3 := testdata.KeyTestPubAddr()
priv4, _, addr4 := testdata.KeyTestPubAddr()
priv5, _, addr5 := testdata.KeyTestPubAddr()
// Set addr1 with insufficient funds
acc1 := app.AccountKeeper.NewAccountWithAddress(ctx, addr1)
app.AccountKeeper.SetAccount(ctx, acc1)
app.BankKeeper.SetBalances(ctx, addr1, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(10))})
// Set addr2 with more funds
acc2 := app.AccountKeeper.NewAccountWithAddress(ctx, addr2)
app.AccountKeeper.SetAccount(ctx, acc2)
app.BankKeeper.SetBalances(ctx, addr2, []sdk.Coin{sdk.NewCoin("atom", sdk.NewInt(99999))})
// grant fee allowance from `addr2` to `addr3` (plenty to pay)
err := app.FeeGrantKeeper.GrantFeeAllowance(ctx, addr2, addr3, &types.BasicFeeAllowance{
SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 500)),
})
suite.Require().NoError(err)
// grant low fee allowance (20atom), to check the tx requesting more than allowed.
err = app.FeeGrantKeeper.GrantFeeAllowance(ctx, addr2, addr4, &types.BasicFeeAllowance{
SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 20)),
})
suite.Require().NoError(err)
cases := map[string]struct {
signerKey cryptotypes.PrivKey
signer sdk.AccAddress
feeAccount sdk.AccAddress
feeAccountKey cryptotypes.PrivKey
handler sdk.AnteHandler
fee int64
valid bool
}{
"paying with low funds (only ours)": {
signerKey: priv1,
signer: addr1,
fee: 50,
handler: ourAnteHandler,
valid: false,
},
"paying with good funds (only ours)": {
signerKey: priv2,
signer: addr2,
fee: 50,
handler: ourAnteHandler,
valid: true,
},
"paying with no account (only ours)": {
signerKey: priv3,
signer: addr3,
fee: 1,
handler: ourAnteHandler,
valid: false,
},
"no fee with real account (only ours)": {
signerKey: priv1,
signer: addr1,
fee: 0,
handler: ourAnteHandler,
valid: true,
},
"no fee with no account (only ours)": {
signerKey: priv5,
signer: addr5,
fee: 0,
handler: ourAnteHandler,
valid: false,
},
"valid fee grant without account (only ours)": {
signerKey: priv3,
signer: addr3,
feeAccount: addr2,
fee: 50,
handler: ourAnteHandler,
valid: true,
},
"no fee grant (only ours)": {
signerKey: priv3,
signer: addr3,
feeAccount: addr1,
fee: 2,
handler: ourAnteHandler,
valid: false,
},
"allowance smaller than requested fee (only ours)": {
signerKey: priv4,
signer: addr4,
feeAccount: addr2,
fee: 50,
handler: ourAnteHandler,
valid: false,
},
"granter cannot cover allowed fee grant (only ours)": {
signerKey: priv4,
signer: addr4,
feeAccount: addr1,
fee: 50,
handler: ourAnteHandler,
valid: false,
},
}
for name, stc := range cases {
tc := stc // to make scopelint happy
suite.T().Run(name, func(t *testing.T) {
fee := sdk.NewCoins(sdk.NewInt64Coin("atom", tc.fee))
msgs := []sdk.Msg{testdata.NewTestMsg(tc.signer)}
acc := app.AccountKeeper.GetAccount(ctx, tc.signer)
privs, accNums, seqs := []cryptotypes.PrivKey{tc.signerKey}, []uint64{0}, []uint64{0}
if acc != nil {
accNums, seqs = []uint64{acc.GetAccountNumber()}, []uint64{acc.GetSequence()}
}
tx, err := genTxWithFeeGranter(protoTxCfg, msgs, fee, helpers.DefaultGenTxGas, ctx.ChainID(), accNums, seqs, tc.feeAccount, privs...)
suite.Require().NoError(err)
_, err = ourAnteHandler(ctx, tx, false)
if tc.valid {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
_, err = anteHandlerStack(ctx, tx, false)
if tc.valid {
suite.Require().NoError(err)
} else {
suite.Require().Error(err)
}
})
}
}
// returns context and app with params set on account keeper
func createTestApp(isCheckTx bool) (*simapp.SimApp, sdk.Context) {
app := simapp.Setup(isCheckTx)
ctx := app.BaseApp.NewContext(isCheckTx, tmproto.Header{})
app.AccountKeeper.SetParams(ctx, authtypes.DefaultParams())
return app, ctx
}
// don't consume any gas
func SigGasNoConsumer(meter sdk.GasMeter, sig []byte, pubkey crypto.PubKey, params authtypes.Params) error {
return nil
}
func genTxWithFeeGranter(gen client.TxConfig, msgs []sdk.Msg, feeAmt sdk.Coins, gas uint64, chainID string, accNums,
accSeqs []uint64, feeGranter sdk.AccAddress, priv ...cryptotypes.PrivKey) (sdk.Tx, error) {
sigs := make([]signing.SignatureV2, len(priv))
// create a random length memo
r := rand.New(rand.NewSource(time.Now().UnixNano()))
memo := simulation.RandStringOfLength(r, simulation.RandIntBetween(r, 0, 100))
signMode := gen.SignModeHandler().DefaultMode()
// 1st round: set SignatureV2 with empty signatures, to set correct
// signer infos.
for i, p := range priv {
sigs[i] = signing.SignatureV2{
PubKey: p.PubKey(),
Data: &signing.SingleSignatureData{
SignMode: signMode,
},
Sequence: accSeqs[i],
}
}
tx := gen.NewTxBuilder()
err := tx.SetMsgs(msgs...)
if err != nil {
return nil, err
}
err = tx.SetSignatures(sigs...)
if err != nil {
return nil, err
}
tx.SetMemo(memo)
tx.SetFeeAmount(feeAmt)
tx.SetGasLimit(gas)
tx.SetFeeGranter(feeGranter)
// 2nd round: once all signer infos are set, every signer can sign.
for i, p := range priv {
signerData := authsign.SignerData{
ChainID: chainID,
AccountNumber: accNums[i],
Sequence: accSeqs[i],
}
signBytes, err := gen.SignModeHandler().GetSignBytes(signMode, signerData, tx.GetTx())
if err != nil {
panic(err)
}
sig, err := p.Sign(signBytes)
if err != nil {
panic(err)
}
sigs[i].Data.(*signing.SingleSignatureData).Signature = sig
err = tx.SetSignatures(sigs...)
if err != nil {
panic(err)
}
}
return tx.GetTx(), nil
}
func TestAnteTestSuite(t *testing.T) {
suite.Run(t, new(AnteTestSuite))
}

View File

@ -0,0 +1,621 @@
// +build norace
package cli_test
import (
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
"github.com/cosmos/cosmos-sdk/testutil/network"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/feegrant/client/cli"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
govtestutil "github.com/cosmos/cosmos-sdk/x/gov/client/testutil"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/suite"
tmcli "github.com/tendermint/tendermint/libs/cli"
)
type IntegrationTestSuite struct {
suite.Suite
cfg network.Config
network *network.Network
addedGranter sdk.AccAddress
addedGrantee sdk.AccAddress
addedGrant types.FeeAllowanceGrant
}
func (s *IntegrationTestSuite) SetupSuite() {
s.T().Log("setting up integration test suite")
if testing.Short() {
s.T().Skip("skipping test in unit-tests mode.")
}
cfg := network.DefaultConfig()
cfg.NumValidators = 2
s.cfg = cfg
s.network = network.New(s.T(), cfg)
_, err := s.network.WaitForHeight(1)
s.Require().NoError(err)
val := s.network.Validators[0]
granter := val.Address
grantee := s.network.Validators[1].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()),
}
fee := sdk.NewCoin("stake", sdk.NewInt(100))
duration := 365 * 24 * 60 * 60
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=%v", cli.FlagExpiration, duration),
},
commonFlags...,
)
cmd := cli.NewCmdFeeGrant()
_, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, args)
s.Require().NoError(err)
_, err = s.network.WaitForHeight(1)
s.Require().NoError(err)
s.addedGranter = granter
s.addedGrantee = grantee
grant, err := types.NewFeeAllowanceGrant(granter, grantee, &types.BasicFeeAllowance{
SpendLimit: sdk.NewCoins(fee),
})
s.Require().NoError(err)
s.addedGrant = grant
}
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.FeeAllowanceGrant
resp *types.FeeAllowanceGrant
}{
{
"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{
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
grantee.String(),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
"no fee allowance found",
true, nil, nil,
},
{
"valid req",
[]string{
granter.String(),
grantee.String(),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
"",
false,
&types.FeeAllowanceGrant{},
&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)
s.Require().Equal(
tc.respType.GetFeeGrant().(*types.BasicFeeAllowance).SpendLimit,
tc.resp.GetFeeGrant().(*types.BasicFeeAllowance).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.QueryFeeAllowancesResponse
expectLength int
}{
{
"wrong grantee",
[]string{
"wrong_grantee",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, nil, 0,
},
{
"non existed grantee",
[]string{
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, &types.QueryFeeAllowancesResponse{}, 0,
},
{
"valid req",
[]string{
grantee.String(),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, &types.QueryFeeAllowancesResponse{}, 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.FeeAllowances, tc.expectLength)
}
})
}
}
func (s *IntegrationTestSuite) TestNewCmdFeeGrant() {
val := s.network.Validators[0]
granter := val.Address
alreadyExistedGrantee := 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()),
}
testCases := []struct {
name string
args []string
expectErr bool
respType proto.Message
expectedCode uint32
}{
{
"wrong granter address",
append(
[]string{
"wrong_granter",
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
true, nil, 0,
},
{
"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, nil, 0,
},
{
"valid basic fee grant",
append(
[]string{
granter.String(),
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid basic fee grant without spend limit",
append(
[]string{
granter.String(),
"cosmos17h5lzptx3ghvsuhk7wx4c4hnl7rsswxjer97em",
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid basic fee grant without expiration",
append(
[]string{
granter.String(),
"cosmos16dlc38dcqt0uralyd8hksxyrny6kaeqfjvjwp5",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid basic fee grant without spend-limit and expiration",
append(
[]string{
granter.String(),
"cosmos1ku40qup9vwag4wtf8cls9mkszxfthaklxkp3c8",
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"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, &sdk.TxResponse{}, 18,
},
{
"invalid number of args(periodic fee grant)",
append(
[]string{
granter.String(),
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%d", cli.FlagExpiration, 10*60*60),
},
commonFlags...,
),
true, nil, 0,
},
{
"period mentioned and period limit omitted, invalid periodic grant",
append(
[]string{
granter.String(),
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 10*60*60),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%d", cli.FlagExpiration, 60*60),
},
commonFlags...,
),
true, nil, 0,
},
{
"period cannot be greater than the actual expiration(periodic fee grant)",
append(
[]string{
granter.String(),
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 10*60*60),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%d", cli.FlagExpiration, 60*60),
},
commonFlags...,
),
true, nil, 0,
},
{
"valid periodic fee grant",
append(
[]string{
granter.String(),
"cosmos1w55kgcf3ltaqdy4ww49nge3klxmrdavrr6frmp",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 60*60),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%d", cli.FlagExpiration, 10*60*60),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid periodic fee grant without spend-limit",
append(
[]string{
granter.String(),
"cosmos1vevyks8pthkscvgazc97qyfjt40m6g9xe85ry8",
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 60*60),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%d", cli.FlagExpiration, 10*60*60),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid periodic fee grant without expiration",
append(
[]string{
granter.String(),
"cosmos14cm33pvnrv2497tyt8sp9yavhmw83nwej3m0e8",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 60*60),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid periodic fee grant without spend-limit and expiration",
append(
[]string{
granter.String(),
"cosmos12nyk4pcf4arshznkpz882e4l4ts0lt0ap8ce54",
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 60*60),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
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())
}
})
}
}
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()),
}
testCases := []struct {
name string
args []string
expectErr bool
respType proto.Message
expectedCode uint32
}{
{
"invalid grantee",
append(
[]string{
"wrong_granter",
grantee.String(),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
true,
nil,
0,
},
{
"invalid grantee",
append(
[]string{
granter.String(),
"wrong_grantee",
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
true,
nil,
0,
},
{
"Non existed grant",
append(
[]string{
granter.String(),
"cosmos1aeuqja06474dfrj7uqsvukm6rael982kk89mqr",
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false,
&sdk.TxResponse{},
4,
},
{
"Valid revoke",
append(
[]string{
granter.String(),
grantee.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.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)
info, _, err := val.ClientCtx.Keyring.NewMnemonic("grantee", keyring.English, sdk.FullFundraiserPath, hd.Secp256k1)
s.Require().NoError(err)
grantee := sdk.AccAddress(info.GetPubKey().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))
duration := 365 * 24 * 60 * 60
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=%v", cli.FlagExpiration, duration),
},
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 TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

View File

@ -0,0 +1,129 @@
package cli
import (
"fmt"
"strings"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/spf13/cobra"
)
// 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)
granterAddr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
granteeAddr, err := sdk.AccAddressFromBech32(args[1])
if err != nil {
return err
}
res, err := queryClient.FeeAllowance(
cmd.Context(),
&types.QueryFeeAllowanceRequest{
Granter: granterAddr.String(),
Grantee: granteeAddr.String(),
},
)
if err != nil {
return err
}
return clientCtx.PrintProto(res.FeeAllowance)
},
}
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)
granteeAddr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}
res, err := queryClient.FeeAllowances(
cmd.Context(),
&types.QueryFeeAllowancesRequest{
Grantee: granteeAddr.String(),
Pagination: pageReq,
},
)
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "grants")
return cmd
}

211
x/feegrant/client/cli/tx.go Normal file
View File

@ -0,0 +1,211 @@
package cli
import (
"fmt"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
// flag for feegrant module
const (
FlagExpiration = "expiration"
FlagPeriod = "period"
FlagPeriodLimit = "period-limit"
FlagSpendLimit = "spend-limit"
)
// 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 MsgGrantFeeAllowance transaction.
func NewCmdFeeGrant() *cobra.Command {
cmd := &cobra.Command{
Use: "grant [granter] [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 36000 or
%s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --period 3600 --period-limit 10stake --expiration 36000
`, version.AppName, types.ModuleName, version.AppName, types.ModuleName,
),
),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
_, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
cmd.Flags().Set(flags.FlagFrom, args[0])
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
grantee, err := sdk.AccAddressFromBech32(args[1])
if err != nil {
return err
}
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().GetInt64(FlagExpiration)
if err != nil {
return err
}
basic := types.BasicFeeAllowance{
SpendLimit: limit,
}
if exp != 0 {
expDuration := time.Duration(exp) * time.Second
basic.Expiration = types.ExpiresAtTime(time.Now().Add(expDuration))
}
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 && periodLimit != nil {
if exp > 0 && periodClock > exp {
return fmt.Errorf("period(%d) cannot be greater than the expiration(%d)", periodClock, exp)
}
periodic := types.PeriodicFeeAllowance{
Basic: basic,
Period: types.ClockDuration(time.Duration(periodClock) * time.Second),
PeriodReset: types.ExpiresAtTime(time.Now().Add(time.Duration(periodClock) * time.Second)),
PeriodSpendLimit: periodLimit,
PeriodCanSpend: periodLimit,
}
grant = &periodic
} else {
return fmt.Errorf("invalid number of args %d", len(args))
}
}
msg, err := types.NewMsgGrantFeeAllowance(grant, granter, grantee)
if err != nil {
return err
}
svcMsgClientConn := &msgservice.ServiceMsgClientConn{}
feeGrantMsgClient := types.NewMsgClient(svcMsgClientConn)
_, err = feeGrantMsgClient.GrantFeeAllowance(cmd.Context(), msg)
if err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), svcMsgClientConn.GetMsgs()...)
},
}
flags.AddTxFlagsToCmd(cmd)
cmd.Flags().Int64(FlagExpiration, 0, "The second unit of time duration which the grant is active 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 MsgRevokeFeeAllowance transaction.
func NewCmdRevokeFeegrant() *cobra.Command {
cmd := &cobra.Command{
Use: "revoke [granter_address] [grantee_address]",
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_address].
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
}
grantee, err := sdk.AccAddressFromBech32(args[1])
if err != nil {
return err
}
msg := types.NewMsgRevokeFeeAllowance(clientCtx.GetFromAddress(), grantee)
svcMsgClientConn := &msgservice.ServiceMsgClientConn{}
feeGrantMsgClient := types.NewMsgClient(svcMsgClientConn)
_, err = feeGrantMsgClient.RevokeFeeAllowance(cmd.Context(), &msg)
if err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), svcMsgClientConn.GetMsgs()...)
},
}
flags.AddTxFlagsToCmd(cmd)
return cmd
}

View File

@ -0,0 +1,209 @@
package rest_test
import (
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
"github.com/cosmos/cosmos-sdk/testutil/network"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/rest"
banktestutil "github.com/cosmos/cosmos-sdk/x/bank/client/testutil"
"github.com/cosmos/cosmos-sdk/x/feegrant/client/cli"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/stretchr/testify/suite"
)
type IntegrationTestSuite struct {
suite.Suite
cfg network.Config
network *network.Network
grantee sdk.AccAddress
}
func (s *IntegrationTestSuite) SetupSuite() {
s.T().Log("setting up integration test suite")
cfg := network.DefaultConfig()
cfg.NumValidators = 1
s.cfg = cfg
s.network = network.New(s.T(), cfg)
val := s.network.Validators[0]
// Create new account in the keyring.
info, _, err := val.ClientCtx.Keyring.NewMnemonic("grantee", keyring.English, sdk.FullFundraiserPath, hd.Secp256k1)
s.Require().NoError(err)
newAddr := sdk.AccAddress(info.GetPubKey().Address())
// Send some funds to the new account.
_, err = banktestutil.MsgSendExec(
val.ClientCtx,
val.Address,
newAddr,
sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(200))), fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
)
s.Require().NoError(err)
s.grantee = newAddr
_, 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) TestQueryFeeAllowance() {
val := s.network.Validators[0]
baseURL := val.APIAddress
testCases := []struct {
name string
url string
expectErr bool
errorMsg string
preRun func()
postRun func(_ types.QueryFeeAllowanceResponse)
}{
{
"fail: invalid granter",
fmt.Sprintf("%s/cosmos/feegrant/v1beta1/fee_allowance/%s/%s", baseURL, "invalid_granter", s.grantee.String()),
true,
"decoding bech32 failed: invalid index of 1: invalid request",
func() {},
func(types.QueryFeeAllowanceResponse) {},
},
{
"fail: invalid grantee",
fmt.Sprintf("%s/cosmos/feegrant/v1beta1/fee_allowance/%s/%s", baseURL, val.Address.String(), "invalid_grantee"),
true,
"decoding bech32 failed: invalid index of 1: invalid request",
func() {},
func(types.QueryFeeAllowanceResponse) {},
},
{
"fail: no grants",
fmt.Sprintf("%s/cosmos/feegrant/v1beta1/fee_allowance/%s/%s", baseURL, val.Address.String(), s.grantee.String()),
true,
"no fee allowance found",
func() {},
func(types.QueryFeeAllowanceResponse) {},
},
{
"valid query: expect single grant",
fmt.Sprintf("%s/cosmos/feegrant/v1beta1/fee_allowance/%s/%s", baseURL, val.Address.String(), s.grantee.String()),
false,
"",
func() {
execFeeAllowance(val, s)
},
func(allowance types.QueryFeeAllowanceResponse) {
s.Require().Equal(allowance.FeeAllowance.Granter, val.Address.String())
s.Require().Equal(allowance.FeeAllowance.Grantee, s.grantee.String())
},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.preRun()
resp, _ := rest.GetRequest(tc.url)
if tc.expectErr {
s.Require().Contains(string(resp), tc.errorMsg)
} else {
var allowance types.QueryFeeAllowanceResponse
err := val.ClientCtx.JSONMarshaler.UnmarshalJSON(resp, &allowance)
s.Require().NoError(err)
tc.postRun(allowance)
}
})
}
}
func (s *IntegrationTestSuite) TestQueryGranteeAllowances() {
val := s.network.Validators[0]
baseURL := val.APIAddress
testCases := []struct {
name string
url string
expectErr bool
errorMsg string
preRun func()
postRun func(_ types.QueryFeeAllowancesResponse)
}{
{
"fail: invalid grantee",
fmt.Sprintf("%s/cosmos/feegrant/v1beta1/fee_allowances/%s", baseURL, "invalid_grantee"),
true,
"decoding bech32 failed: invalid index of 1: invalid request",
func() {},
func(types.QueryFeeAllowancesResponse) {},
},
{
"success: no grants",
fmt.Sprintf("%s/cosmos/feegrant/v1beta1/fee_allowances/%s?pagination.offset=1", baseURL, s.grantee.String()),
false,
"",
func() {},
func(allowances types.QueryFeeAllowancesResponse) {
s.Require().Equal(len(allowances.FeeAllowances), 0)
},
},
{
"valid query: expect single grant",
fmt.Sprintf("%s/cosmos/feegrant/v1beta1/fee_allowances/%s", baseURL, s.grantee.String()),
false,
"",
func() {
execFeeAllowance(val, s)
},
func(allowances types.QueryFeeAllowancesResponse) {
s.Require().Equal(len(allowances.FeeAllowances), 1)
s.Require().Equal(allowances.FeeAllowances[0].Granter, val.Address.String())
s.Require().Equal(allowances.FeeAllowances[0].Grantee, s.grantee.String())
},
},
}
for _, tc := range testCases {
s.Run(tc.name, func() {
tc.preRun()
resp, _ := rest.GetRequest(tc.url)
if tc.expectErr {
s.Require().Contains(string(resp), tc.errorMsg)
} else {
var allowance types.QueryFeeAllowancesResponse
err := val.ClientCtx.JSONMarshaler.UnmarshalJSON(resp, &allowance)
s.Require().NoError(err)
tc.postRun(allowance)
}
})
}
}
func execFeeAllowance(val *network.Validator, s *IntegrationTestSuite) {
fee := sdk.NewCoin("steak", sdk.NewInt(100))
duration := 365 * 24 * 60 * 60
args := []string{
val.Address.String(),
s.grantee.String(),
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, fee.String()),
fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()),
fmt.Sprintf("--%s=%v", cli.FlagExpiration, duration),
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()),
}
cmd := cli.NewCmdFeeGrant()
_, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args)
s.Require().NoError(err)
}
func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

29
x/feegrant/doc.go Normal file
View File

@ -0,0 +1,29 @@
/*
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
MsgDelegateFeeAllowance and revoke that delegation using MsgRevokeFeeAllowance.
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: BasicFeeAllowance and PeriodicFeeAllowance.
In order to integrate this into an application, we must use the DeductGrantedFeeDecorator
ante handler from this package instead of the default DeductFeeDecorator from x/auth.
To allow handling txs from empty accounts (with fees paid from an existing account),
we have to re-order the decorators as well.
*/
package feegrant

61
x/feegrant/genesis.go Normal file
View File

@ -0,0 +1,61 @@
package feegrant
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
// GenesisState contains a set of fee allowances, persisted from the store
type GenesisState []types.FeeAllowanceGrant
// ValidateBasic ensures all grants in the genesis state are valid
func (g GenesisState) ValidateBasic() error {
for _, f := range g {
err := f.GetFeeGrant().ValidateBasic()
if err != nil {
return err
}
}
return nil
}
// InitGenesis will initialize the keeper from a *previously validated* GenesisState
func InitGenesis(ctx sdk.Context, k keeper.Keeper, data *types.GenesisState) {
for _, f := range data.FeeAllowances {
granter, err := sdk.AccAddressFromBech32(f.Granter)
if err != nil {
panic(err)
}
grantee, err := sdk.AccAddressFromBech32(f.Grantee)
if err != nil {
panic(err)
}
err = k.GrantFeeAllowance(ctx, granter, grantee, f.GetFeeGrant())
if err != nil {
panic(err)
}
}
}
// ExportGenesis will dump the contents of the keeper into a serializable GenesisState
//
// All expiration heights will be thrown off if we dump state and start at a new
// chain at height 0. Thus, we allow the Allowances to "prepare themselves"
// for export, like if they have expiry at 5000 and current is 4000, they export with
// expiry of 1000. Every FeeAllowance has a method `PrepareForExport` that allows
// them to perform any changes needed prior to export.
func ExportGenesis(ctx sdk.Context, k keeper.Keeper) (*types.GenesisState, error) {
time, height := ctx.BlockTime(), ctx.BlockHeight()
var grants []types.FeeAllowanceGrant
err := k.IterateAllFeeAllowances(ctx, func(grant types.FeeAllowanceGrant) bool {
grants = append(grants, grant.PrepareForExport(time, height))
return false
})
return &types.GenesisState{
FeeAllowances: grants,
}, err
}

View File

@ -0,0 +1,56 @@
package feegrant_test
import (
"testing"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
feegrant "github.com/cosmos/cosmos-sdk/x/feegrant"
"github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/stretchr/testify/suite"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
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.AccAddress(granteePub.Address())
granterAddr = sdk.AccAddress(granterPub.Address())
)
func (suite *GenesisTestSuite) TestImportExportGenesis() {
coins := sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1_000)))
now := suite.ctx.BlockHeader().Time
allowance := &types.BasicFeeAllowance{SpendLimit: coins, Expiration: types.ExpiresAtTime(now.AddDate(1, 0, 0))}
err := suite.keeper.GrantFeeAllowance(suite.ctx, granterAddr, granteeAddr, allowance)
suite.Require().NoError(err)
genesis, err := feegrant.ExportGenesis(suite.ctx, suite.keeper)
suite.Require().NoError(err)
// Clear keeper
suite.keeper.RevokeFeeAllowance(suite.ctx, granterAddr, granteeAddr)
feegrant.InitGenesis(suite.ctx, suite.keeper, genesis)
newGenesis, err := feegrant.ExportGenesis(suite.ctx, suite.keeper)
suite.Require().NoError(err)
suite.Require().Equal(genesis, newGenesis)
}
func TestGenesisTestSuite(t *testing.T) {
suite.Run(t, new(GenesisTestSuite))
}

View File

@ -0,0 +1,94 @@
package keeper
import (
"context"
"github.com/gogo/protobuf/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
var _ types.QueryServer = Keeper{}
// FeeAllowance returns fee granted to the grantee by the granter.
func (q Keeper) FeeAllowance(c context.Context, req *types.QueryFeeAllowanceRequest) (*types.QueryFeeAllowanceResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
granterAddr, err := sdk.AccAddressFromBech32(req.Granter)
if err != nil {
return nil, err
}
granteeAddr, err := sdk.AccAddressFromBech32(req.Grantee)
if err != nil {
return nil, err
}
ctx := sdk.UnwrapSDKContext(c)
feeAllowance := q.GetFeeAllowance(ctx, granterAddr, granteeAddr)
if feeAllowance == nil {
return nil, status.Errorf(codes.NotFound, "no fee allowance found")
}
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.QueryFeeAllowanceResponse{
FeeAllowance: &types.FeeAllowanceGrant{
Granter: granterAddr.String(),
Grantee: granteeAddr.String(),
Allowance: feeAllowanceAny,
},
}, nil
}
func (q Keeper) FeeAllowances(c context.Context, req *types.QueryFeeAllowancesRequest) (*types.QueryFeeAllowancesResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
granteeAddr, err := sdk.AccAddressFromBech32(req.Grantee)
if err != nil {
return nil, err
}
ctx := sdk.UnwrapSDKContext(c)
var grants []*types.FeeAllowanceGrant
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.FeeAllowanceGrant
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.QueryFeeAllowancesResponse{FeeAllowances: grants, Pagination: pageRes}, nil
}

190
x/feegrant/keeper/keeper.go Normal file
View File

@ -0,0 +1,190 @@
package keeper
import (
"fmt"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
// 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.BinaryMarshaler
storeKey sdk.StoreKey
authKeeper types.AccountKeeper
}
// NewKeeper creates a fee grant Keeper
func NewKeeper(cdc codec.BinaryMarshaler, storeKey sdk.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))
}
// GrantFeeAllowance creates a new grant
func (k Keeper) GrantFeeAllowance(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.NewFeeAllowanceGrant(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
}
// RevokeFeeAllowance removes an existing grant
func (k Keeper) RevokeFeeAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress) error {
store := ctx.KVStore(k.storeKey)
key := types.FeeAllowanceKey(granter, grantee)
_, found := k.GetFeeGrant(ctx, granter, grantee)
if !found {
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "fee-grant not found")
}
store.Delete(key)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeRevokeFeeGrant,
sdk.NewAttribute(types.AttributeKeyGranter, granter.String()),
sdk.NewAttribute(types.AttributeKeyGrantee, grantee.String()),
),
)
return nil
}
// GetFeeAllowance 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) GetFeeAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress) types.FeeAllowanceI {
grant, found := k.GetFeeGrant(ctx, granter, grantee)
if !found {
return nil
}
return grant.GetFeeGrant()
}
// GetFeeGrant returns entire grant between both accounts
func (k Keeper) GetFeeGrant(ctx sdk.Context, granter sdk.AccAddress, grantee sdk.AccAddress) (types.FeeAllowanceGrant, bool) {
store := ctx.KVStore(k.storeKey)
key := types.FeeAllowanceKey(granter, grantee)
bz := store.Get(key)
if len(bz) == 0 {
return types.FeeAllowanceGrant{}, false
}
var feegrant types.FeeAllowanceGrant
k.cdc.MustUnmarshalBinaryBare(bz, &feegrant)
return feegrant, true
}
// IterateAllGranteeFeeAllowances iterates over all the grants from anyone to the given grantee.
// Callback to get all data, returns true to stop, false to keep reading
func (k Keeper) IterateAllGranteeFeeAllowances(ctx sdk.Context, grantee sdk.AccAddress, cb func(types.FeeAllowanceGrant) bool) error {
store := ctx.KVStore(k.storeKey)
prefix := types.FeeAllowancePrefixByGrantee(grantee)
iter := sdk.KVStorePrefixIterator(store, prefix)
defer iter.Close()
stop := false
for ; iter.Valid() && !stop; iter.Next() {
bz := iter.Value()
var feeGrant types.FeeAllowanceGrant
k.cdc.MustUnmarshalBinaryBare(bz, &feeGrant)
stop = cb(feeGrant)
}
return 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(types.FeeAllowanceGrant) 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.FeeAllowanceGrant
k.cdc.MustUnmarshalBinaryBare(bz, &feeGrant)
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) error {
grant, found := k.GetFeeGrant(ctx, granter, grantee)
if !found || grant.GetFeeGrant() == nil {
return sdkerrors.Wrapf(types.ErrNoAllowance, "grant missing")
}
remove, err := grant.GetFeeGrant().Accept(fee, ctx.BlockTime(), ctx.BlockHeight())
if err == nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeUseFeeGrant,
sdk.NewAttribute(types.AttributeKeyGranter, granter.String()),
sdk.NewAttribute(types.AttributeKeyGrantee, grantee.String()),
),
)
}
if remove {
k.RevokeFeeAllowance(ctx, granter, grantee)
// note this returns nil if err == nil
return sdkerrors.Wrap(err, "removed grant")
}
if err != nil {
return sdkerrors.Wrap(err, "invalid grant")
}
// if we accepted, store the updated state of the allowance
return k.GrantFeeAllowance(ctx, granter, grantee, grant.GetFeeGrant())
}

View File

@ -0,0 +1,257 @@
package keeper_test
import (
"testing"
"github.com/stretchr/testify/suite"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
type KeeperTestSuite struct {
suite.Suite
app *simapp.SimApp
ctx sdk.Context
addrs []sdk.AccAddress
}
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.ctx = ctx
suite.addrs = simapp.AddTestAddrsIncremental(app, ctx, 4, sdk.NewInt(30000000))
}
func (suite *KeeperTestSuite) TestKeeperCrud() {
ctx := suite.ctx
k := suite.app.FeeGrantKeeper
// some helpers
atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555))
eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 123))
basic := &types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(334455),
}
basic2 := &types.BasicFeeAllowance{
SpendLimit: eth,
Expiration: types.ExpiresAtHeight(172436),
}
// let's set up some initial state here
err := k.GrantFeeAllowance(ctx, suite.addrs[0], suite.addrs[1], basic)
suite.Require().NoError(err)
err = k.GrantFeeAllowance(ctx, suite.addrs[0], suite.addrs[2], basic2)
suite.Require().NoError(err)
err = k.GrantFeeAllowance(ctx, suite.addrs[1], suite.addrs[2], basic)
suite.Require().NoError(err)
err = k.GrantFeeAllowance(ctx, suite.addrs[1], suite.addrs[3], basic)
suite.Require().NoError(err)
err = k.GrantFeeAllowance(ctx, suite.addrs[3], suite.addrs[0], basic2)
suite.Require().NoError(err)
// remove some, overwrite other
k.RevokeFeeAllowance(ctx, suite.addrs[0], suite.addrs[1])
k.RevokeFeeAllowance(ctx, suite.addrs[0], suite.addrs[2])
err = k.GrantFeeAllowance(ctx, suite.addrs[0], suite.addrs[2], basic)
suite.Require().NoError(err)
err = k.GrantFeeAllowance(ctx, 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 := k.GetFeeAllowance(ctx, tc.granter, tc.grantee)
if tc.allowance == nil {
suite.Nil(allow)
return
}
suite.NotNil(allow)
suite.Equal(tc.allowance, allow)
})
}
grant1, err := types.NewFeeAllowanceGrant(suite.addrs[3], suite.addrs[0], basic2)
suite.NoError(err)
grant2, err := types.NewFeeAllowanceGrant(suite.addrs[1], suite.addrs[2], basic2)
suite.NoError(err)
grant3, err := types.NewFeeAllowanceGrant(suite.addrs[0], suite.addrs[2], basic)
suite.NoError(err)
allCases := map[string]struct {
grantee sdk.AccAddress
grants []types.FeeAllowanceGrant
}{
"addr2 has none": {
grantee: suite.addrs[1],
},
"addr has one": {
grantee: suite.addrs[0],
grants: []types.FeeAllowanceGrant{
grant1,
},
},
"addr3 has two": {
grantee: suite.addrs[2],
grants: []types.FeeAllowanceGrant{
grant3,
grant2,
},
},
}
for name, tc := range allCases {
tc := tc
suite.Run(name, func() {
var grants []types.FeeAllowanceGrant
err := k.IterateAllGranteeFeeAllowances(ctx, tc.grantee, func(grant types.FeeAllowanceGrant) bool {
grants = append(grants, grant)
return false
})
suite.NoError(err)
suite.Equal(tc.grants, grants)
})
}
}
func (suite *KeeperTestSuite) TestUseGrantedFee() {
ctx := suite.ctx
k := suite.app.FeeGrantKeeper
// some helpers
atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555))
eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 123))
future := &types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(5678),
}
expired := &types.BasicFeeAllowance{
SpendLimit: eth,
Expiration: types.ExpiresAtHeight(55),
}
// for testing limits of the contract
hugeAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 9999))
_ = hugeAtom
smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 1))
_ = smallAtom
futureAfterSmall := &types.BasicFeeAllowance{
SpendLimit: sdk.NewCoins(sdk.NewInt64Coin("atom", 554)),
Expiration: types.ExpiresAtHeight(5678),
}
// 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: atom,
allowed: true,
final: nil,
},
"expired and removed": {
granter: suite.addrs[0],
grantee: suite.addrs[2],
fee: eth,
allowed: false,
final: nil,
},
"too high": {
granter: suite.addrs[0],
grantee: suite.addrs[1],
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() {
// let's set up some initial state here
// addr -> addr2 (future)
// addr -> addr3 (expired)
err := k.GrantFeeAllowance(ctx, suite.addrs[0], suite.addrs[1], future)
suite.Require().NoError(err)
err = k.GrantFeeAllowance(ctx, suite.addrs[0], suite.addrs[3], expired)
suite.Require().NoError(err)
err = k.UseGrantedFees(ctx, tc.granter, tc.grantee, tc.fee)
if tc.allowed {
suite.NoError(err)
} else {
suite.Error(err)
}
loaded := k.GetFeeAllowance(ctx, tc.granter, tc.grantee)
suite.Equal(tc.final, loaded)
})
}
}

View File

@ -0,0 +1,72 @@
package keeper
import (
"context"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
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{}
func (k msgServer) GrantFeeAllowance(goCtx context.Context, msg *types.MsgGrantFeeAllowance) (*types.MsgGrantFeeAllowanceResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
grantee, err := sdk.AccAddressFromBech32(msg.Grantee)
if err != nil {
return nil, err
}
granter, err := sdk.AccAddressFromBech32(msg.Granter)
if err != nil {
return nil, err
}
// Checking for duplicate entry
f := k.Keeper.GetFeeAllowance(ctx, granter, grantee)
if f != nil {
return nil, sdkerrors.Wrap(sdkerrors.ErrInvalidRequest, "fee allowance already exists")
}
err = k.Keeper.GrantFeeAllowance(ctx, granter, grantee, msg.GetFeeAllowanceI())
if err != nil {
return nil, err
}
return &types.MsgGrantFeeAllowanceResponse{}, nil
}
func (k msgServer) RevokeFeeAllowance(goCtx context.Context, msg *types.MsgRevokeFeeAllowance) (*types.MsgRevokeFeeAllowanceResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
grantee, err := sdk.AccAddressFromBech32(msg.Grantee)
if err != nil {
return nil, err
}
granter, err := sdk.AccAddressFromBech32(msg.Granter)
if err != nil {
return nil, err
}
err = k.Keeper.RevokeFeeAllowance(ctx, granter, grantee)
if err != nil {
return nil, err
}
return &types.MsgRevokeFeeAllowanceResponse{}, nil
}

210
x/feegrant/module.go Normal file
View File

@ -0,0 +1,210 @@
package feegrant
import (
"context"
"encoding/json"
"math/rand"
"github.com/gorilla/mux"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/spf13/cobra"
abci "github.com/tendermint/tendermint/abci/types"
sdkclient "github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/codec"
cdctypes "github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/feegrant/client/cli"
"github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
"github.com/cosmos/cosmos-sdk/x/feegrant/simulation"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
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.
func (AppModuleBasic) RegisterRESTRoutes(ctx sdkclient.Context, rtr *mux.Router) {}
// RegisterGRPCGatewayRoutes registers the gRPC Gateway routes for the feegrant module.
func (AppModuleBasic) RegisterGRPCGatewayRoutes(clientCtx sdkclient.Context, mux *runtime.ServeMux) {
types.RegisterQueryHandlerClient(context.Background(), mux, types.NewQueryClient(clientCtx))
}
// 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) {}
// Route returns the message routing key for the feegrant module.
func (am AppModule) Route() sdk.Route {
return sdk.NewRoute(types.RouterKey, nil)
}
// 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)
InitGenesis(ctx, am.keeper, &gs)
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 := ExportGenesis(ctx, am.keeper)
if err != nil {
panic(err)
}
return cdc.MustMarshalJSON(gs)
}
// 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 {
protoCdc := codec.NewProtoCodec(am.registry)
return simulation.WeightedOperations(
simState.AppParams, simState.Cdc, am.accountKeeper, am.bankKeeper, am.keeper, protoCdc,
)
}

View File

@ -0,0 +1,26 @@
package simulation
import (
"bytes"
"fmt"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/types/kv"
"github.com/cosmos/cosmos-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.FeeAllowanceGrant
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))
}
}
}

View File

@ -0,0 +1,62 @@
package simulation_test
import (
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/crypto/keys/ed25519"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/kv"
"github.com/cosmos/cosmos-sdk/x/feegrant/simulation"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/stretchr/testify/require"
)
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.MakeTestEncodingConfig().Marshaler
dec := simulation.NewDecodeStore(cdc)
grant, err := types.NewFeeAllowanceGrant(granterAddr, granteeAddr, &types.BasicFeeAllowance{
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)
}
})
}
}

View File

@ -0,0 +1,38 @@
package simulation
import (
"encoding/json"
"fmt"
"math/rand"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
// Simulation parameter constants
const feegrant = "feegrant"
// GenFeeGrants returns an empty slice of evidences.
func GenFeeGrants(_ *rand.Rand, _ []simtypes.Account) []types.FeeAllowanceGrant {
return []types.FeeAllowanceGrant{}
}
// RandomizedGenState generates a random GenesisState for feegrant
func RandomizedGenState(simState *module.SimulationState) {
var feegrants []types.FeeAllowanceGrant
simState.AppParams.GetOrGenerate(
simState.Cdc, feegrant, &feegrants, simState.Rand,
func(r *rand.Rand) { feegrants = GenFeeGrants(r, simState.Accounts) },
)
feegrantGenesis := types.NewGenesisState(feegrants)
bz, err := json.MarshalIndent(&feegrantGenesis, "", " ")
if err != nil {
panic(err)
}
fmt.Printf("Selected randomly generated %s parameters:\n%s\n", types.ModuleName, bz)
simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(feegrantGenesis)
}

View File

@ -0,0 +1,39 @@
package simulation_test
import (
"encoding/json"
"math/rand"
"testing"
"github.com/cosmos/cosmos-sdk/codec"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/types/module"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/feegrant/simulation"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/stretchr/testify/require"
)
func TestRandomizedGenState(t *testing.T) {
interfaceRegistry := codectypes.NewInterfaceRegistry()
cdc := codec.NewProtoCodec(interfaceRegistry)
s := rand.NewSource(1)
r := rand.New(s)
simState := module.SimulationState{
AppParams: make(simtypes.AppParams),
Cdc: cdc,
Rand: r,
NumBonded: 3,
Accounts: simtypes.RandomAccounts(r, 3),
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.FeeAllowances, 0)
}

View File

@ -0,0 +1,200 @@
package simulation
import (
"context"
"math/rand"
"time"
"github.com/cosmos/cosmos-sdk/baseapp"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp/helpers"
simappparams "github.com/cosmos/cosmos-sdk/simapp/params"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/cosmos/cosmos-sdk/x/simulation"
)
// Simulation operation weights constants
const (
OpWeightMsgGrantFeeAllowance = "op_weight_msg_grant_fee_allowance"
OpWeightMsgRevokeFeeAllowance = "op_weight_msg_grant_revoke_allowance"
TypeMsgGrantFeeAllowance = "/cosmos.feegrant.v1beta1.Msg/GrantFeeAllowance"
TypeMsgRevokeFeeAllowance = "/cosmos.feegrant.v1beta1.Msg/RevokeFeeAllowance"
)
func WeightedOperations(
appParams simtypes.AppParams, cdc codec.JSONMarshaler,
ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper,
protoCdc *codec.ProtoCodec,
) simulation.WeightedOperations {
var (
weightMsgGrantFeeAllowance int
weightMsgRevokeFeeAllowance int
)
appParams.GetOrGenerate(cdc, OpWeightMsgGrantFeeAllowance, &weightMsgGrantFeeAllowance, nil,
func(_ *rand.Rand) {
weightMsgGrantFeeAllowance = simappparams.DefaultWeightGrantFeeAllowance
},
)
appParams.GetOrGenerate(cdc, OpWeightMsgRevokeFeeAllowance, &weightMsgRevokeFeeAllowance, nil,
func(_ *rand.Rand) {
weightMsgRevokeFeeAllowance = simappparams.DefaultWeightRevokeFeeAllowance
},
)
return simulation.WeightedOperations{
simulation.NewWeightedOperation(
weightMsgGrantFeeAllowance,
SimulateMsgGrantFeeAllowance(ak, bk, k, protoCdc),
),
simulation.NewWeightedOperation(
weightMsgRevokeFeeAllowance,
SimulateMsgRevokeFeeAllowance(ak, bk, k, protoCdc),
),
}
}
// SimulateMsgGrantFeeAllowance generates MsgGrantFeeAllowance with random values.
func SimulateMsgGrantFeeAllowance(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper, protoCdc *codec.ProtoCodec) 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, types.TypeMsgGrantFeeAllowance, "grantee and granter cannot be same"), nil, nil
}
f := k.GetFeeAllowance(ctx, granter.Address, grantee.Address)
if f != nil {
return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgGrantFeeAllowance, "fee allowance exists"), nil, nil
}
account := ak.GetAccount(ctx, granter.Address)
spendableCoins := bk.SpendableCoins(ctx, account.GetAddress())
fees, err := simtypes.RandomFees(r, ctx, spendableCoins)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgGrantFeeAllowance, err.Error()), nil, err
}
spendableCoins = spendableCoins.Sub(fees)
if spendableCoins.Empty() {
return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgGrantFeeAllowance, "unable to grant empty coins as SpendLimit"), nil, nil
}
msg, err := types.NewMsgGrantFeeAllowance(&types.BasicFeeAllowance{
SpendLimit: spendableCoins,
Expiration: types.ExpiresAtTime(ctx.BlockTime().Add(30 * time.Hour)),
}, granter.Address, grantee.Address)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgGrantFeeAllowance, err.Error()), nil, err
}
txGen := simappparams.MakeTestEncodingConfig().TxConfig
svcMsgClientConn := &msgservice.ServiceMsgClientConn{}
feegrantMsgClient := types.NewMsgClient(svcMsgClientConn)
_, err = feegrantMsgClient.GrantFeeAllowance(context.Background(), msg)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgGrantFeeAllowance, err.Error()), nil, err
}
tx, err := helpers.GenTx(
txGen,
svcMsgClientConn.GetMsgs(),
fees,
helpers.DefaultGenTxGas,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
granter.PrivKey,
)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, TypeMsgGrantFeeAllowance, "unable to generate mock tx"), nil, err
}
_, _, err = app.Deliver(txGen.TxEncoder(), tx)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, svcMsgClientConn.GetMsgs()[0].Type(), "unable to deliver tx"), nil, err
}
return simtypes.NewOperationMsg(svcMsgClientConn.GetMsgs()[0], true, "", protoCdc), nil, err
}
}
// SimulateMsgRevokeFeeAllowance generates a MsgRevokeFeeAllowance with random values.
func SimulateMsgRevokeFeeAllowance(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper, protoCdc *codec.ProtoCodec) 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.FeeAllowanceGrant) bool {
granter, err := sdk.AccAddressFromBech32(grant.Granter)
if err != nil {
panic(err)
}
grantee, err := sdk.AccAddressFromBech32(grant.Grantee)
if err != nil {
panic(err)
}
granterAddr = granter
granteeAddr = grantee
hasGrant = true
return true
})
if !hasGrant {
return simtypes.NoOpMsg(types.ModuleName, TypeMsgRevokeFeeAllowance, "no grants"), nil, nil
}
granter, ok := simtypes.FindAccount(accs, granterAddr)
if !ok {
return simtypes.NoOpMsg(types.ModuleName, TypeMsgRevokeFeeAllowance, "Account not found"), nil, nil
}
account := ak.GetAccount(ctx, granter.Address)
spendableCoins := bk.SpendableCoins(ctx, account.GetAddress())
fees, err := simtypes.RandomFees(r, ctx, spendableCoins)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, TypeMsgRevokeFeeAllowance, err.Error()), nil, err
}
msg := types.NewMsgRevokeFeeAllowance(granterAddr, granteeAddr)
txGen := simappparams.MakeTestEncodingConfig().TxConfig
svcMsgClientConn := &msgservice.ServiceMsgClientConn{}
feegrantMsgClient := types.NewMsgClient(svcMsgClientConn)
_, err = feegrantMsgClient.RevokeFeeAllowance(context.Background(), &msg)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgGrantFeeAllowance, err.Error()), nil, err
}
tx, err := helpers.GenTx(
txGen,
svcMsgClientConn.GetMsgs(),
fees,
helpers.DefaultGenTxGas,
chainID,
[]uint64{account.GetAccountNumber()},
[]uint64{account.GetSequence()},
granter.PrivKey,
)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, TypeMsgRevokeFeeAllowance, err.Error()), nil, err
}
_, _, err = app.Deliver(txGen.TxEncoder(), tx)
return simtypes.NewOperationMsg(svcMsgClientConn.GetMsgs()[0], true, "", protoCdc), nil, err
}
}

View File

@ -0,0 +1,173 @@
package simulation_test
import (
"math/rand"
"testing"
"time"
"github.com/stretchr/testify/suite"
abci "github.com/tendermint/tendermint/abci/types"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/simapp"
simappparams "github.com/cosmos/cosmos-sdk/simapp/params"
sdk "github.com/cosmos/cosmos-sdk/types"
simtypes "github.com/cosmos/cosmos-sdk/types/simulation"
"github.com/cosmos/cosmos-sdk/x/feegrant/simulation"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
type SimTestSuite struct {
suite.Suite
ctx sdk.Context
app *simapp.SimApp
protoCdc *codec.ProtoCodec
}
func (suite *SimTestSuite) SetupTest() {
checkTx := false
app := simapp.Setup(checkTx)
suite.app = app
suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{})
suite.protoCdc = codec.NewProtoCodec(suite.app.InterfaceRegistry())
}
func (suite *SimTestSuite) getTestingAccounts(r *rand.Rand, n int) []simtypes.Account {
app, ctx := suite.app, suite.ctx
accounts := simtypes.RandomAccounts(r, n)
require := suite.Require()
initAmt := sdk.TokensFromConsensusPower(200)
initCoins := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initAmt))
// add coins to the accounts
for _, account := range accounts {
acc := app.AccountKeeper.NewAccountWithAddress(ctx, account.Address)
app.AccountKeeper.SetAccount(ctx, acc)
err := app.BankKeeper.SetBalances(ctx, account.Address, initCoins)
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)
weightesOps := simulation.WeightedOperations(
appParams, cdc, app.AccountKeeper,
app.BankKeeper, app.FeeGrantKeeper,
suite.protoCdc,
)
s := rand.NewSource(1)
r := rand.New(s)
accs := suite.getTestingAccounts(r, 3)
expected := []struct {
weight int
opMsgRoute string
opMsgName string
}{
{
simappparams.DefaultWeightGrantFeeAllowance,
types.ModuleName,
simulation.TypeMsgGrantFeeAllowance,
},
{
simappparams.DefaultWeightRevokeFeeAllowance,
types.ModuleName,
simulation.TypeMsgRevokeFeeAllowance,
},
}
for i, w := range weightesOps {
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) TestSimulateMsgGrantFeeAllowance() {
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.SimulateMsgGrantFeeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, suite.protoCdc)
operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "")
require.NoError(err)
var msg types.MsgGrantFeeAllowance
suite.app.AppCodec().UnmarshalJSON(operationMsg.Msg, &msg)
require.True(operationMsg.OK)
require.Equal("cosmos1ghekyjucln7y67ntx7cf27m9dpuxxemn4c8g4r", msg.Granter)
require.Equal("cosmos1p8wcgrjr4pjju90xg6u9cgq55dxwq8j7u4x9a0", msg.Grantee)
require.Len(futureOperations, 0)
}
func (suite *SimTestSuite) TestSimulateMsgRevokeFeeAllowance() {
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]
err := app.FeeGrantKeeper.GrantFeeAllowance(
ctx,
granter.Address,
grantee.Address,
&types.BasicFeeAllowance{
SpendLimit: feeCoins,
Expiration: types.ExpiresAtTime(ctx.BlockTime().Add(30 * time.Hour)),
},
)
require.NoError(err)
// execute operation
op := simulation.SimulateMsgRevokeFeeAllowance(app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, suite.protoCdc)
operationMsg, futureOperations, err := op(r, app.BaseApp, ctx, accounts, "")
require.NoError(err)
var msg types.MsgRevokeFeeAllowance
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))
}

View File

@ -0,0 +1,61 @@
package types
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var _ FeeAllowanceI = (*BasicFeeAllowance)(nil)
// Accept can use fee payment requested as well as timestamp/height of the current block
// to determine whether or not to process this. This is checked in
// Keeper.UseGrantedFees and the return values should match how it is handled there.
//
// If it returns an error, the fee payment is rejected, otherwise it is accepted.
// The FeeAllowance implementation is expected to update it's internal state
// and will be saved again after an acceptance.
//
// If remove is true (regardless of the error), the FeeAllowance will be deleted from storage
// (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees)
func (a *BasicFeeAllowance) Accept(fee sdk.Coins, blockTime time.Time, blockHeight int64) (bool, error) {
if a.Expiration.IsExpired(&blockTime, blockHeight) {
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
}
// PrepareForExport will adjust the expiration based on export time. In particular,
// it will subtract the dumpHeight from any height-based expiration to ensure that
// the elapsed number of blocks this allowance is valid for is fixed.
func (a *BasicFeeAllowance) PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceI {
return &BasicFeeAllowance{
SpendLimit: a.SpendLimit,
Expiration: a.Expiration.PrepareForExport(dumpTime, dumpHeight),
}
}
// ValidateBasic implements FeeAllowance and enforces basic sanity checks
func (a BasicFeeAllowance) 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")
}
}
return a.Expiration.ValidateBasic()
}

View File

@ -0,0 +1,140 @@
package types_test
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestBasicFeeValidAllow(t *testing.T) {
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))
cases := map[string]struct {
allow *types.BasicFeeAllowance
// all other checks are ignored if valid=false
fee sdk.Coins
blockTime time.Time
blockHeight int64
valid bool
accept bool
remove bool
remains sdk.Coins
}{
"empty": {
allow: &types.BasicFeeAllowance{},
valid: true,
accept: true,
},
"small fee without expire": {
allow: &types.BasicFeeAllowance{
SpendLimit: atom,
},
valid: true,
fee: smallAtom,
accept: true,
remove: false,
remains: leftAtom,
},
"all fee without expire": {
allow: &types.BasicFeeAllowance{
SpendLimit: smallAtom,
},
valid: true,
fee: smallAtom,
accept: true,
remove: true,
},
"wrong fee": {
allow: &types.BasicFeeAllowance{
SpendLimit: smallAtom,
},
valid: true,
fee: eth,
accept: false,
},
"non-expired": {
allow: &types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
},
valid: true,
fee: smallAtom,
blockHeight: 85,
accept: true,
remove: false,
remains: leftAtom,
},
"expired": {
allow: &types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
},
valid: true,
fee: smallAtom,
blockHeight: 121,
accept: false,
remove: true,
},
"fee more than allowed": {
allow: &types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
},
valid: true,
fee: bigAtom,
blockHeight: 85,
accept: false,
},
"with out spend limit": {
allow: &types.BasicFeeAllowance{
Expiration: types.ExpiresAtHeight(100),
},
valid: true,
fee: bigAtom,
blockHeight: 85,
accept: true,
},
"expired no spend limit": {
allow: &types.BasicFeeAllowance{
Expiration: types.ExpiresAtHeight(100),
},
valid: true,
fee: bigAtom,
blockHeight: 120,
accept: false,
},
}
for name, stc := range cases {
tc := stc // to make scopelint happy
t.Run(name, func(t *testing.T) {
err := tc.allow.ValidateBasic()
if !tc.valid {
require.Error(t, err)
return
}
require.NoError(t, err)
// now try to deduct
remove, err := tc.allow.Accept(tc.fee, tc.blockTime, tc.blockHeight)
if !tc.accept {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.remove, remove)
if !remove {
assert.Equal(t, tc.allow.SpendLimit, tc.remains)
}
})
}
}

24
x/feegrant/types/codec.go Normal file
View File

@ -0,0 +1,24 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
)
// RegisterInterfaces registers the interfaces types with the interface registry
func RegisterInterfaces(registry types.InterfaceRegistry) {
registry.RegisterImplementations((*sdk.MsgRequest)(nil),
&MsgGrantFeeAllowance{},
&MsgRevokeFeeAllowance{},
)
registry.RegisterInterface(
"cosmos.feegrant.v1beta1.FeeAllowanceI",
(*FeeAllowanceI)(nil),
&BasicFeeAllowance{},
&PeriodicFeeAllowance{},
)
msgservice.RegisterMsgServiceDesc(registry, &_Msg_serviceDesc)
}

View File

@ -0,0 +1,21 @@
package types
import (
sdkerrors "github.com/cosmos/cosmos-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")
)

View File

@ -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
)

View File

@ -0,0 +1,23 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
auth "github.com/cosmos/cosmos-sdk/x/auth/types"
// supply "github.com/cosmos/cosmos-sdk/x/supply/exported"
)
// AccountKeeper defines the expected auth Account Keeper (noalias)
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
}

View File

@ -0,0 +1,138 @@
package types
import (
"time"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// ExpiresAtTime creates an expiration at the given time
func ExpiresAtTime(t time.Time) ExpiresAt {
return ExpiresAt{
Sum: &ExpiresAt_Time{
Time: &t,
},
}
}
// ExpiresAtHeight creates an expiration at the given height
func ExpiresAtHeight(h int64) ExpiresAt {
return ExpiresAt{
&ExpiresAt_Height{
Height: h,
},
}
}
// ValidateBasic performs basic sanity checks.
// Note that empty expiration is allowed
func (e ExpiresAt) ValidateBasic() error {
if e.HasDefinedTime() && e.GetHeight() != 0 {
return sdkerrors.Wrap(ErrInvalidDuration, "both time and height are set")
}
if e.GetHeight() < 0 {
return sdkerrors.Wrap(ErrInvalidDuration, "negative height")
}
return nil
}
// Undefined returns true for an uninitialized struct
func (e ExpiresAt) Undefined() bool {
return (e.GetTime() == nil || e.GetTime().Unix() <= 0) && e.GetHeight() == 0
}
// HasDefinedTime returns true if `ExpiresAt` has valid time
func (e ExpiresAt) HasDefinedTime() bool {
t := e.GetTime()
return t != nil && t.Unix() > 0
}
// FastForward produces a new Expiration with the time or height set to the
// new value, depending on what was set on the original expiration
func (e ExpiresAt) FastForward(t time.Time, h int64) ExpiresAt {
if e.HasDefinedTime() {
return ExpiresAtTime(t)
}
return ExpiresAtHeight(h)
}
// IsExpired returns if the time or height is *equal to* or greater
// than the defined expiration point. Note that it is expired upon
// an exact match.
//
// Note a "zero" ExpiresAt is never expired
func (e ExpiresAt) IsExpired(t *time.Time, h int64) bool {
if e.HasDefinedTime() && t.After(*e.GetTime()) {
return true
}
return e.GetHeight() != 0 && h >= e.GetHeight()
}
// IsCompatible returns true iff the two use the same units.
// If false, they cannot be added.
func (e ExpiresAt) IsCompatible(d Duration) bool {
if e.HasDefinedTime() {
return d.GetDuration() != nil && d.GetDuration().Seconds() > float64(0)
}
return d.GetBlocks() > 0
}
// Step will increase the expiration point by one Duration
// It returns an error if the Duration is incompatible
func (e ExpiresAt) Step(d Duration) (ExpiresAt, error) {
if !e.IsCompatible(d) {
return ExpiresAt{}, sdkerrors.Wrap(ErrInvalidDuration, "expiration time and provided duration have different units")
}
if e.HasDefinedTime() {
return ExpiresAtTime(e.GetTime().Add(*d.GetDuration())), nil
}
return ExpiresAtHeight(e.GetHeight() + int64(d.GetBlocks())), nil
}
// MustStep is like Step, but panics on error
func (e ExpiresAt) MustStep(d Duration) ExpiresAt {
res, err := e.Step(d)
if err != nil {
panic(err)
}
return res
}
// PrepareForExport will deduct the dumpHeight from the expiration, so when this is
// reloaded after a hard fork, the actual number of allowed blocks is constant
func (e ExpiresAt) PrepareForExport(dumpTime time.Time, dumpHeight int64) ExpiresAt {
if e.GetHeight() != 0 {
return ExpiresAtHeight(e.GetHeight() - dumpHeight)
}
return ExpiresAt{}
}
// ClockDuration creates an Duration by clock time
func ClockDuration(d time.Duration) Duration {
return Duration{Sum: &Duration_Duration{
Duration: &d,
}}
}
// BlockDuration creates an Duration by block height
func BlockDuration(h uint64) Duration {
return Duration{Sum: &Duration_Blocks{
Blocks: h,
}}
}
// ValidateBasic performs basic sanity checks
// Note that exactly one must be set and it must be positive
func (d Duration) ValidateBasic() error {
if d.GetBlocks() == 0 && d.GetDuration() == nil {
return sdkerrors.Wrap(ErrInvalidDuration, "neither time and height are set")
}
if d.GetBlocks() != 0 && d.GetDuration() != nil && d.GetDuration().Seconds() != float64(0) {
return sdkerrors.Wrap(ErrInvalidDuration, "both time and height are set")
}
if d.GetDuration() != nil && d.GetDuration().Seconds() < 0 {
return sdkerrors.Wrap(ErrInvalidDuration, "negative clock step")
}
return nil
}

View File

@ -0,0 +1,161 @@
package types_test
import (
"testing"
"time"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestExpiresAt(t *testing.T) {
now := time.Now()
cases := map[string]struct {
example types.ExpiresAt
valid bool
zero bool
before types.ExpiresAt
after types.ExpiresAt
}{
"basic": {
example: types.ExpiresAtHeight(100),
valid: true,
before: types.ExpiresAtHeight(50),
after: types.ExpiresAtHeight(122),
},
"zero": {
example: types.ExpiresAt{},
zero: true,
valid: true,
before: types.ExpiresAtHeight(1),
},
"match height": {
example: types.ExpiresAtHeight(1000),
valid: true,
before: types.ExpiresAtHeight(999),
after: types.ExpiresAtHeight(1000),
},
"match time": {
example: types.ExpiresAtTime(now),
valid: true,
before: types.ExpiresAtTime(now.Add(-1 * time.Second)),
after: types.ExpiresAtTime(now.Add(1 * time.Second)),
},
}
for name, stc := range cases {
tc := stc // to make scopelint happy
t.Run(name, func(t *testing.T) {
err := tc.example.ValidateBasic()
assert.Equal(t, tc.zero, tc.example.Undefined())
if !tc.valid {
require.Error(t, err)
return
}
require.NoError(t, err)
if !tc.before.Undefined() {
assert.Equal(t, false, tc.example.IsExpired(tc.before.GetTime(), tc.before.GetHeight()))
}
if !tc.after.Undefined() {
assert.Equal(t, true, tc.example.IsExpired(tc.after.GetTime(), tc.after.GetHeight()))
}
})
}
}
func TestDurationValid(t *testing.T) {
now := time.Now()
cases := map[string]struct {
period types.Duration
valid bool
compatible types.ExpiresAt
incompatible types.ExpiresAt
}{
"basic height": {
period: types.BlockDuration(100),
valid: true,
compatible: types.ExpiresAtHeight(50),
incompatible: types.ExpiresAtTime(now),
},
"basic time": {
period: types.ClockDuration(time.Hour),
valid: true,
compatible: types.ExpiresAtTime(now),
incompatible: types.ExpiresAtHeight(50),
},
"zero": {
period: types.Duration{},
valid: false,
},
"negative clock": {
period: types.ClockDuration(-1 * time.Hour),
valid: false,
},
}
for name, stc := range cases {
tc := stc // to make scopelint happy
t.Run(name, func(t *testing.T) {
err := tc.period.ValidateBasic()
if !tc.valid {
require.Error(t, err)
return
}
require.NoError(t, err)
assert.Equal(t, true, tc.compatible.IsCompatible(tc.period))
assert.Equal(t, false, tc.incompatible.IsCompatible(tc.period))
})
}
}
func TestDurationStep(t *testing.T) {
now := time.Now()
cases := map[string]struct {
expires types.ExpiresAt
period types.Duration
valid bool
result types.ExpiresAt
}{
"add height": {
expires: types.ExpiresAtHeight(789),
period: types.BlockDuration(100),
valid: true,
result: types.ExpiresAtHeight(889),
},
"add time": {
expires: types.ExpiresAtTime(now),
period: types.ClockDuration(time.Hour),
valid: true,
result: types.ExpiresAtTime(now.Add(time.Hour)),
},
"mismatch": {
expires: types.ExpiresAtHeight(789),
period: types.ClockDuration(time.Hour),
valid: false,
},
}
for name, stc := range cases {
tc := stc // to make scopelint happy
t.Run(name, func(t *testing.T) {
err := tc.period.ValidateBasic()
require.NoError(t, err)
err = tc.expires.ValidateBasic()
require.NoError(t, err)
next, err := tc.expires.Step(tc.period)
if !tc.valid {
require.Error(t, err)
return
}
require.NoError(t, err)
require.Equal(t, tc.result, next)
})
}
}

File diff suppressed because it is too large Load Diff

32
x/feegrant/types/fees.go Normal file
View File

@ -0,0 +1,32 @@
package types
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// FeeAllowance implementations are tied to a given fee delegator and delegatee,
// and are used to enforce fee grant limits.
type FeeAllowanceI interface {
// Accept can use fee payment requested as well as timestamp/height of the current block
// to determine whether or not to process this. This is checked in
// Keeper.UseGrantedFees and the return values should match how it is handled there.
//
// If it returns an error, the fee payment is rejected, otherwise it is accepted.
// The FeeAllowance implementation is expected to update it's internal state
// and will be saved again after an acceptance.
//
// If remove is true (regardless of the error), the FeeAllowance will be deleted from storage
// (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees)
Accept(fee sdk.Coins, blockTime time.Time, blockHeight int64) (remove bool, err error)
// If we export fee allowances the timing info will be quite off (eg. go from height 100000 to 0)
// This callback allows the fee-allowance to change it's state and return a copy that is adjusted
// given the time and height of the actual dump (may safely return self if no changes needed)
PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceI
// ValidateBasic should evaluate this FeeAllowance for internal consistency.
// Don't allow negative amounts, or negative periods for example.
ValidateBasic() error
}

View File

@ -0,0 +1,40 @@
package types
import "github.com/cosmos/cosmos-sdk/codec/types"
var _ types.UnpackInterfacesMessage = GenesisState{}
// NewGenesisState creates new GenesisState object
func NewGenesisState(entries []FeeAllowanceGrant) *GenesisState {
return &GenesisState{
FeeAllowances: entries,
}
}
// ValidateGenesis ensures all grants in the genesis state are valid
func ValidateGenesis(data GenesisState) error {
for _, f := range data.FeeAllowances {
err := f.GetFeeGrant().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.FeeAllowances {
err := f.UnpackInterfaces(unpacker)
if err != nil {
return err
}
}
return nil
}

View File

@ -0,0 +1,333 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: cosmos/feegrant/v1beta1/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 {
FeeAllowances []FeeAllowanceGrant `protobuf:"bytes,1,rep,name=fee_allowances,json=feeAllowances,proto3" json:"fee_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_ac719d2d0954d1bf, []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) GetFeeAllowances() []FeeAllowanceGrant {
if m != nil {
return m.FeeAllowances
}
return nil
}
func init() {
proto.RegisterType((*GenesisState)(nil), "cosmos.feegrant.v1beta1.GenesisState")
}
func init() {
proto.RegisterFile("cosmos/feegrant/v1beta1/genesis.proto", fileDescriptor_ac719d2d0954d1bf)
}
var fileDescriptor_ac719d2d0954d1bf = []byte{
// 221 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x52, 0x4d, 0xce, 0x2f, 0xce,
0xcd, 0x2f, 0xd6, 0x4f, 0x4b, 0x4d, 0x4d, 0x2f, 0x4a, 0xcc, 0x2b, 0xd1, 0x2f, 0x33, 0x4c, 0x4a,
0x2d, 0x49, 0x34, 0xd4, 0x4f, 0x4f, 0xcd, 0x4b, 0x2d, 0xce, 0x2c, 0xd6, 0x2b, 0x28, 0xca, 0x2f,
0xc9, 0x17, 0x12, 0x87, 0x28, 0xd3, 0x83, 0x29, 0xd3, 0x83, 0x2a, 0x93, 0x12, 0x49, 0xcf, 0x4f,
0xcf, 0x07, 0xab, 0xd1, 0x07, 0xb1, 0x20, 0xca, 0xa5, 0xd4, 0x70, 0x99, 0x0a, 0xd7, 0x0f, 0x56,
0xa7, 0x94, 0xce, 0xc5, 0xe3, 0x0e, 0xb1, 0x27, 0xb8, 0x24, 0xb1, 0x24, 0x55, 0x28, 0x9c, 0x8b,
0x2f, 0x2d, 0x35, 0x35, 0x3e, 0x31, 0x27, 0x27, 0xbf, 0x3c, 0x31, 0x2f, 0x39, 0xb5, 0x58, 0x82,
0x51, 0x81, 0x59, 0x83, 0xdb, 0x48, 0x4b, 0x0f, 0x87, 0xfd, 0x7a, 0x6e, 0xa9, 0xa9, 0x8e, 0x30,
0xd5, 0xee, 0x20, 0x19, 0x27, 0x96, 0x13, 0xf7, 0xe4, 0x19, 0x82, 0x78, 0xd3, 0x90, 0x24, 0x8a,
0x9d, 0xdc, 0x4f, 0x3c, 0x92, 0x63, 0xbc, 0xf0, 0x48, 0x8e, 0xf1, 0xc1, 0x23, 0x39, 0xc6, 0x09,
0x8f, 0xe5, 0x18, 0x2e, 0x3c, 0x96, 0x63, 0xb8, 0xf1, 0x58, 0x8e, 0x21, 0x4a, 0x37, 0x3d, 0xb3,
0x24, 0xa3, 0x34, 0x49, 0x2f, 0x39, 0x3f, 0x57, 0x1f, 0xea, 0x6a, 0x08, 0xa5, 0x5b, 0x9c, 0x92,
0xad, 0x5f, 0x81, 0xf0, 0x42, 0x49, 0x65, 0x41, 0x6a, 0x71, 0x12, 0x1b, 0xd8, 0xe1, 0xc6, 0x80,
0x00, 0x00, 0x00, 0xff, 0xff, 0xaf, 0x53, 0x6e, 0xc5, 0x38, 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.FeeAllowances) > 0 {
for iNdEx := len(m.FeeAllowances) - 1; iNdEx >= 0; iNdEx-- {
{
size, err := m.FeeAllowances[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.FeeAllowances) > 0 {
for _, e := range m.FeeAllowances {
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 FeeAllowances", 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.FeeAllowances = append(m.FeeAllowances, FeeAllowanceGrant{})
if err := m.FeeAllowances[len(m.FeeAllowances)-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")
)

92
x/feegrant/types/grant.go Normal file
View File

@ -0,0 +1,92 @@
package types
import (
"time"
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
proto "github.com/gogo/protobuf/proto"
)
var (
_ types.UnpackInterfacesMessage = &FeeAllowanceGrant{}
)
// NewFeeAllowanceGrant creates a new FeeAllowanceGrant.
//nolint:interfacer
func NewFeeAllowanceGrant(granter, grantee sdk.AccAddress, feeAllowance FeeAllowanceI) (FeeAllowanceGrant, error) {
msg, ok := feeAllowance.(proto.Message)
if !ok {
return FeeAllowanceGrant{}, sdkerrors.Wrapf(sdkerrors.ErrPackAny, "cannot proto marshal %T", feeAllowance)
}
any, err := types.NewAnyWithValue(msg)
if err != nil {
return FeeAllowanceGrant{}, err
}
return FeeAllowanceGrant{
Granter: granter.String(),
Grantee: grantee.String(),
Allowance: any,
}, nil
}
// ValidateBasic performs basic validation on
// FeeAllowanceGrant
func (a FeeAllowanceGrant) 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")
}
return a.GetFeeGrant().ValidateBasic()
}
// GetFeeGrant unpacks allowance
func (a FeeAllowanceGrant) GetFeeGrant() FeeAllowanceI {
allowance, ok := a.Allowance.GetCachedValue().(FeeAllowanceI)
if !ok {
return nil
}
return allowance
}
// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
func (a FeeAllowanceGrant) UnpackInterfaces(unpacker types.AnyUnpacker) error {
var allowance FeeAllowanceI
return unpacker.UnpackAny(a.Allowance, &allowance)
}
// PrepareForExport will make all needed changes to the allowance to prepare to be
// re-imported at height 0, and return a copy of this grant.
func (a FeeAllowanceGrant) PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceGrant {
feegrant := a.GetFeeGrant().PrepareForExport(dumpTime, dumpHeight)
if feegrant == nil {
return FeeAllowanceGrant{}
}
granter, err := sdk.AccAddressFromBech32(a.Granter)
if err != nil {
return FeeAllowanceGrant{}
}
grantee, err := sdk.AccAddressFromBech32(a.Grantee)
if err != nil {
return FeeAllowanceGrant{}
}
grant, err := NewFeeAllowanceGrant(granter, grantee, feegrant)
if err != nil {
return FeeAllowanceGrant{}
}
return grant
}

View File

@ -0,0 +1,99 @@
package types_test
import (
"testing"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestGrant(t *testing.T) {
app := simapp.Setup(false)
addr, err := sdk.AccAddressFromBech32("cosmos1qk93t4j0yyzgqgt6k5qf8deh8fq6smpn3ntu3x")
require.NoError(t, err)
addr2, err := sdk.AccAddressFromBech32("cosmos1p9qh4ldfd6n0qehujsal4k7g0e37kel90rc4ts")
require.NoError(t, err)
atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555))
goodGrant, err := types.NewFeeAllowanceGrant(addr2, addr, &types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
})
require.NoError(t, err)
noGranteeGrant, err := types.NewFeeAllowanceGrant(addr2, nil, &types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
})
require.NoError(t, err)
noGranterGrant, err := types.NewFeeAllowanceGrant(nil, addr, &types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
})
require.NoError(t, err)
selfGrant, err := types.NewFeeAllowanceGrant(addr2, addr2, &types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
})
require.NoError(t, err)
badAllowanceGrant, err := types.NewFeeAllowanceGrant(addr2, addr, &types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(-1),
})
require.NoError(t, err)
cdc := app.AppCodec()
// RegisterLegacyAminoCodec(cdc)
cases := map[string]struct {
grant types.FeeAllowanceGrant
valid bool
}{
"good": {
grant: goodGrant,
valid: true,
},
"no grantee": {
grant: noGranteeGrant,
},
"no granter": {
grant: noGranterGrant,
},
"self-grant": {
grant: selfGrant,
},
"bad allowance": {
grant: badAllowanceGrant,
},
}
for name, tc := range cases {
tc := tc
t.Run(name, func(t *testing.T) {
err := tc.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(&tc.grant)
require.NoError(t, err)
var loaded types.FeeAllowanceGrant
err = cdc.UnmarshalBinaryBare(bz, &loaded)
require.NoError(t, err)
err = loaded.ValidateBasic()
require.NoError(t, err)
assert.Equal(t, tc.grant, loaded)
})
}
}

36
x/feegrant/types/key.go Normal file
View File

@ -0,0 +1,36 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
)
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), address.MustLengthPrefix(granter.Bytes())...)
}
// FeeAllowancePrefixByGrantee returns a prefix to scan for all grants to this given address.
func FeeAllowancePrefixByGrantee(grantee sdk.AccAddress) []byte {
return append(FeeAllowanceKeyPrefix, address.MustLengthPrefix(grantee.Bytes())...)
}

101
x/feegrant/types/msgs.go Normal file
View File

@ -0,0 +1,101 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/gogo/protobuf/proto"
)
var (
_, _ sdk.MsgRequest = &MsgGrantFeeAllowance{}, &MsgRevokeFeeAllowance{}
_ types.UnpackInterfacesMessage = &MsgGrantFeeAllowance{}
)
// feegrant message types
const (
TypeMsgGrantFeeAllowance = "grant_fee_allowance"
TypeMsgRevokeFeeAllowance = "revoke_fee_allowance"
)
// NewMsgGrantFeeAllowance creates a new MsgGrantFeeAllowance.
//nolint:interfacer
func NewMsgGrantFeeAllowance(feeAllowance FeeAllowanceI, granter, grantee sdk.AccAddress) (*MsgGrantFeeAllowance, 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 &MsgGrantFeeAllowance{
Granter: granter.String(),
Grantee: grantee.String(),
Allowance: any,
}, nil
}
// ValidateBasic implements the sdk.Msg interface.
func (msg MsgGrantFeeAllowance) ValidateBasic() error {
if msg.Granter == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing granter address")
}
if msg.Grantee == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing grantee address")
}
if msg.Grantee == msg.Granter {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "cannot self-grant fee authorization")
}
return msg.GetFeeAllowanceI().ValidateBasic()
}
func (msg MsgGrantFeeAllowance) GetSigners() []sdk.AccAddress {
granter, err := sdk.AccAddressFromBech32(msg.Granter)
if err != nil {
panic(err)
}
return []sdk.AccAddress{granter}
}
// GetFeeAllowanceI returns unpacked FeeAllowance
func (msg MsgGrantFeeAllowance) GetFeeAllowanceI() FeeAllowanceI {
allowance, ok := msg.Allowance.GetCachedValue().(FeeAllowanceI)
if !ok {
return nil
}
return allowance
}
// UnpackInterfaces implements UnpackInterfacesMessage.UnpackInterfaces
func (msg MsgGrantFeeAllowance) UnpackInterfaces(unpacker types.AnyUnpacker) error {
var allowance FeeAllowanceI
return unpacker.UnpackAny(msg.Allowance, &allowance)
}
//nolint:interfacer
func NewMsgRevokeFeeAllowance(granter sdk.AccAddress, grantee sdk.AccAddress) MsgRevokeFeeAllowance {
return MsgRevokeFeeAllowance{Granter: granter.String(), Grantee: grantee.String()}
}
func (msg MsgRevokeFeeAllowance) ValidateBasic() error {
if msg.Granter == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing granter address")
}
if msg.Grantee == "" {
return sdkerrors.Wrap(sdkerrors.ErrInvalidAddress, "missing grantee address")
}
return nil
}
func (msg MsgRevokeFeeAllowance) GetSigners() []sdk.AccAddress {
granter, err := sdk.AccAddressFromBech32(msg.Granter)
if err != nil {
panic(err)
}
return []sdk.AccAddress{granter}
}

View File

@ -0,0 +1,120 @@
package types
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
var _ FeeAllowanceI = (*PeriodicFeeAllowance)(nil)
// Accept can use fee payment requested as well as timestamp/height of the current block
// to determine whether or not to process this. This is checked in
// Keeper.UseGrantedFees and the return values should match how it is handled there.
//
// If it returns an error, the fee payment is rejected, otherwise it is accepted.
// The FeeAllowance implementation is expected to update it's internal state
// and will be saved again after an acceptance.
//
// If remove is true (regardless of the error), the FeeAllowance will be deleted from storage
// (eg. when it is used up). (See call to RevokeFeeAllowance in Keeper.UseGrantedFees)
func (a *PeriodicFeeAllowance) Accept(fee sdk.Coins, blockTime time.Time, blockHeight int64) (bool, error) {
if a.Basic.Expiration.IsExpired(&blockTime, blockHeight) {
return true, sdkerrors.Wrap(ErrFeeLimitExpired, "absolute limit")
}
a.tryResetPeriod(blockTime, blockHeight)
// 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(PeriodicSpendLimit, a.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 *PeriodicFeeAllowance) tryResetPeriod(blockTime time.Time, blockHeight int64) {
if !a.PeriodReset.Undefined() && !a.PeriodReset.IsExpired(&blockTime, blockHeight) {
return
}
// set CanSpend to the lesser of PeriodSpendLimit and the TotalLimit
if _, isNeg := a.Basic.SpendLimit.SafeSub(a.PeriodSpendLimit); isNeg {
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.MustStep(a.Period)
if a.PeriodReset.IsExpired(&blockTime, blockHeight) {
a.PeriodReset = a.PeriodReset.FastForward(blockTime, blockHeight).MustStep(a.Period)
}
}
// PrepareForExport will adjust the expiration based on export time. In particular,
// it will subtract the dumpHeight from any height-based expiration to ensure that
// the elapsed number of blocks this allowance is valid for is fixed.
// (For PeriodReset and Basic.Expiration)
func (a *PeriodicFeeAllowance) PrepareForExport(dumpTime time.Time, dumpHeight int64) FeeAllowanceI {
return &PeriodicFeeAllowance{
Basic: BasicFeeAllowance{
SpendLimit: a.Basic.SpendLimit,
Expiration: a.Basic.Expiration.PrepareForExport(dumpTime, dumpHeight),
},
PeriodSpendLimit: a.PeriodSpendLimit,
PeriodCanSpend: a.PeriodCanSpend,
Period: a.Period,
PeriodReset: a.PeriodReset.PrepareForExport(dumpTime, dumpHeight),
}
}
// ValidateBasic implements FeeAllowance and enforces basic sanity checks
func (a PeriodicFeeAllowance) 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 err := a.Period.ValidateBasic(); err != nil {
return err
}
return a.PeriodReset.ValidateBasic()
}

View File

@ -0,0 +1,205 @@
package types_test
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPeriodicFeeValidAllow(t *testing.T) {
atom := sdk.NewCoins(sdk.NewInt64Coin("atom", 555))
smallAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 43))
leftAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 512))
oneAtom := sdk.NewCoins(sdk.NewInt64Coin("atom", 1))
eth := sdk.NewCoins(sdk.NewInt64Coin("eth", 1))
cases := map[string]struct {
allow types.PeriodicFeeAllowance
// all other checks are ignored if valid=false
fee sdk.Coins
blockTime time.Time
blockHeight int64
valid bool
accept bool
remove bool
remains sdk.Coins
remainsPeriod sdk.Coins
periodReset types.ExpiresAt
}{
"empty": {
allow: types.PeriodicFeeAllowance{},
valid: false,
},
"only basic": {
allow: types.PeriodicFeeAllowance{
Basic: types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
},
},
valid: false,
},
"empty basic": {
allow: types.PeriodicFeeAllowance{
Period: types.BlockDuration(10),
PeriodSpendLimit: smallAtom,
PeriodReset: types.ExpiresAtHeight(70),
},
blockHeight: 75,
valid: true,
accept: true,
remove: false,
periodReset: types.ExpiresAtHeight(80),
},
"mismatched currencies": {
allow: types.PeriodicFeeAllowance{
Basic: types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
},
Period: types.BlockDuration(10),
PeriodSpendLimit: eth,
},
valid: false,
},
"first time": {
allow: types.PeriodicFeeAllowance{
Basic: types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
},
Period: types.BlockDuration(10),
PeriodSpendLimit: smallAtom,
},
valid: true,
fee: smallAtom,
blockHeight: 75,
accept: true,
remove: false,
remainsPeriod: nil,
remains: leftAtom,
periodReset: types.ExpiresAtHeight(85),
},
"same period": {
allow: types.PeriodicFeeAllowance{
Basic: types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
},
Period: types.BlockDuration(10),
PeriodReset: types.ExpiresAtHeight(80),
PeriodSpendLimit: leftAtom,
PeriodCanSpend: smallAtom,
},
valid: true,
fee: smallAtom,
blockHeight: 75,
accept: true,
remove: false,
remainsPeriod: nil,
remains: leftAtom,
periodReset: types.ExpiresAtHeight(80),
},
"step one period": {
allow: types.PeriodicFeeAllowance{
Basic: types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
},
Period: types.BlockDuration(10),
PeriodReset: types.ExpiresAtHeight(70),
PeriodSpendLimit: leftAtom,
},
valid: true,
fee: leftAtom,
blockHeight: 75,
accept: true,
remove: false,
remainsPeriod: nil,
remains: smallAtom,
periodReset: types.ExpiresAtHeight(80), // one step from last reset, not now
},
"step limited by global allowance": {
allow: types.PeriodicFeeAllowance{
Basic: types.BasicFeeAllowance{
SpendLimit: smallAtom,
Expiration: types.ExpiresAtHeight(100),
},
Period: types.BlockDuration(10),
PeriodReset: types.ExpiresAtHeight(70),
PeriodSpendLimit: atom,
},
valid: true,
fee: oneAtom,
blockHeight: 75,
accept: true,
remove: false,
remainsPeriod: smallAtom.Sub(oneAtom),
remains: smallAtom.Sub(oneAtom),
periodReset: types.ExpiresAtHeight(80), // one step from last reset, not now
},
"expired": {
allow: types.PeriodicFeeAllowance{
Basic: types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
},
Period: types.BlockDuration(10),
PeriodSpendLimit: smallAtom,
},
valid: true,
fee: smallAtom,
blockHeight: 101,
accept: false,
remove: true,
},
"over period limit": {
allow: types.PeriodicFeeAllowance{
Basic: types.BasicFeeAllowance{
SpendLimit: atom,
Expiration: types.ExpiresAtHeight(100),
},
Period: types.BlockDuration(10),
PeriodReset: types.ExpiresAtHeight(80),
PeriodSpendLimit: leftAtom,
PeriodCanSpend: smallAtom,
},
valid: true,
fee: leftAtom,
blockHeight: 70,
accept: false,
remove: true,
},
}
for name, stc := range cases {
tc := stc // to make scopelint happy
t.Run(name, func(t *testing.T) {
err := tc.allow.ValidateBasic()
if !tc.valid {
require.Error(t, err)
return
}
require.NoError(t, err)
// now try to deduct
remove, err := tc.allow.Accept(tc.fee, tc.blockTime, tc.blockHeight)
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, tc.allow.PeriodReset)
}
})
}
}

1172
x/feegrant/types/query.pb.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,322 @@
// Code generated by protoc-gen-grpc-gateway. DO NOT EDIT.
// source: cosmos/feegrant/v1beta1/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_FeeAllowance_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryFeeAllowanceRequest
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.FeeAllowance(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Query_FeeAllowance_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryFeeAllowanceRequest
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.FeeAllowance(ctx, &protoReq)
return msg, metadata, err
}
var (
filter_Query_FeeAllowances_0 = &utilities.DoubleArray{Encoding: map[string]int{"grantee": 0}, Base: []int{1, 1, 0}, Check: []int{0, 1, 2}}
)
func request_Query_FeeAllowances_0(ctx context.Context, marshaler runtime.Marshaler, client QueryClient, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryFeeAllowancesRequest
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_FeeAllowances_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := client.FeeAllowances(ctx, &protoReq, grpc.Header(&metadata.HeaderMD), grpc.Trailer(&metadata.TrailerMD))
return msg, metadata, err
}
func local_request_Query_FeeAllowances_0(ctx context.Context, marshaler runtime.Marshaler, server QueryServer, req *http.Request, pathParams map[string]string) (proto.Message, runtime.ServerMetadata, error) {
var protoReq QueryFeeAllowancesRequest
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_FeeAllowances_0); err != nil {
return nil, metadata, status.Errorf(codes.InvalidArgument, "%v", err)
}
msg, err := server.FeeAllowances(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_FeeAllowance_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_FeeAllowance_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_FeeAllowance_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Query_FeeAllowances_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_FeeAllowances_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_FeeAllowances_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_FeeAllowance_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_FeeAllowance_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_FeeAllowance_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
mux.Handle("GET", pattern_Query_FeeAllowances_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_FeeAllowances_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_FeeAllowances_0(ctx, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
return nil
}
var (
pattern_Query_FeeAllowance_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{"cosmos", "feegrant", "v1beta1", "fee_allowance", "granter", "grantee"}, "", runtime.AssumeColonVerbOpt(true)))
pattern_Query_FeeAllowances_0 = runtime.MustPattern(runtime.NewPattern(1, []int{2, 0, 2, 1, 2, 2, 2, 3, 1, 0, 4, 1, 5, 4}, []string{"cosmos", "feegrant", "v1beta1", "fee_allowances", "grantee"}, "", runtime.AssumeColonVerbOpt(true)))
)
var (
forward_Query_FeeAllowance_0 = runtime.ForwardResponseMessage
forward_Query_FeeAllowances_0 = runtime.ForwardResponseMessage
)

1033
x/feegrant/types/tx.pb.go Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,7 @@ import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
)
const (
@ -33,9 +34,9 @@ const (
//
// - 0x03: nextProposalID
//
// - 0x10<proposalID_Bytes><depositorAddr_Bytes>: Deposit
// - 0x10<proposalID_Bytes><depositorAddrLen (1 Byte)><depositorAddr_Bytes>: Deposit
//
// - 0x20<proposalID_Bytes><voterAddr_Bytes>: Voter
// - 0x20<proposalID_Bytes><voterAddrLen (1 Byte)><voterAddr_Bytes>: Voter
var (
ProposalsKeyPrefix = []byte{0x00}
ActiveProposalQueuePrefix = []byte{0x01}
@ -93,7 +94,7 @@ func DepositsKey(proposalID uint64) []byte {
// DepositKey key of a specific deposit from the store
func DepositKey(proposalID uint64, depositorAddr sdk.AccAddress) []byte {
return append(DepositsKey(proposalID), depositorAddr.Bytes()...)
return append(DepositsKey(proposalID), address.MustLengthPrefix(depositorAddr.Bytes())...)
}
// VotesKey gets the first part of the votes key based on the proposalID
@ -103,7 +104,7 @@ func VotesKey(proposalID uint64) []byte {
// VoteKey key of a specific vote from the store
func VoteKey(proposalID uint64, voterAddr sdk.AccAddress) []byte {
return append(VotesKey(proposalID), voterAddr.Bytes()...)
return append(VotesKey(proposalID), address.MustLengthPrefix(voterAddr.Bytes())...)
}
// Split keys function; used for iterators
@ -154,11 +155,9 @@ func splitKeyWithTime(key []byte) (proposalID uint64, endTime time.Time) {
}
func splitKeyWithAddress(key []byte) (proposalID uint64, addr sdk.AccAddress) {
if len(key[1:]) != 8+sdk.AddrLen {
panic(fmt.Sprintf("unexpected key length (%d ≠ %d)", len(key), 8+sdk.AddrLen))
}
// Both Vote and Deposit store keys are of format:
// <prefix (1 Byte)><proposalID (8 bytes)><addrLen (1 Byte)><addr_Bytes>
proposalID = GetProposalIDFromBytes(key[1:9])
addr = sdk.AccAddress(key[9:])
addr = sdk.AccAddress(key[10:])
return
}

View File

@ -46,11 +46,6 @@ func TestDepositKeys(t *testing.T) {
proposalID, depositorAddr := SplitKeyDeposit(key)
require.Equal(t, int(proposalID), 2)
require.Equal(t, addr, depositorAddr)
// invalid key
addr2 := sdk.AccAddress("test1")
key = DepositKey(5, addr2)
require.Panics(t, func() { SplitKeyDeposit(key) })
}
func TestVoteKeys(t *testing.T) {
@ -63,9 +58,4 @@ func TestVoteKeys(t *testing.T) {
proposalID, voterAddr := SplitKeyDeposit(key)
require.Equal(t, int(proposalID), 2)
require.Equal(t, addr, voterAddr)
// invalid key
addr2 := sdk.AccAddress("test1")
key = VoteKey(5, addr2)
require.Panics(t, func() { SplitKeyVote(key) })
}

View File

@ -129,6 +129,9 @@ func (k Keeper) SendPacket(
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()),
// we only support 1-hop packets now, and that is the most important hop for a relayer
// (is it going to a chain I am connected to)
sdk.NewAttribute(types.AttributeKeyConnection, channel.ConnectionHops[0]),
),
sdk.NewEvent(
sdk.EventTypeMessage,
@ -289,6 +292,9 @@ func (k Keeper) RecvPacket(
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()),
// we only support 1-hop packets now, and that is the most important hop for a relayer
// (is it going to a chain I am connected to)
sdk.NewAttribute(types.AttributeKeyConnection, channel.ConnectionHops[0]),
),
sdk.NewEvent(
sdk.EventTypeMessage,
@ -370,6 +376,9 @@ func (k Keeper) WriteAcknowledgement(
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
sdk.NewAttribute(types.AttributeKeyAck, string(acknowledgement)),
// we only support 1-hop packets now, and that is the most important hop for a relayer
// (is it going to a chain I am connected to)
sdk.NewAttribute(types.AttributeKeyConnection, channel.ConnectionHops[0]),
),
sdk.NewEvent(
sdk.EventTypeMessage,
@ -505,6 +514,9 @@ func (k Keeper) AcknowledgePacket(
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()),
// we only support 1-hop packets now, and that is the most important hop for a relayer
// (is it going to a chain I am connected to)
sdk.NewAttribute(types.AttributeKeyConnection, channel.ConnectionHops[0]),
),
sdk.NewEvent(
sdk.EventTypeMessage,

View File

@ -30,6 +30,7 @@ const (
AttributeKeyDstPort = "packet_dst_port"
AttributeKeyDstChannel = "packet_dst_channel"
AttributeKeyChannelOrdering = "packet_channel_ordering"
AttributeKeyConnection = "packet_connection"
)
// IBC channel events vars

View File

@ -9,7 +9,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/mint/types"
)
// NewDecodeStore returns a decoder function closure that umarshals the KVPair's
// NewDecodeStore returns a decoder function closure that unmarshals the KVPair's
// Value to the corresponding mint type.
func NewDecodeStore(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string {
return func(kvA, kvB kv.Pair) string {

View File

@ -4,6 +4,7 @@ import (
"encoding/binary"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
)
const (
@ -23,11 +24,11 @@ const (
// Keys for slashing store
// Items are stored with the following key: values
//
// - 0x01<consAddress_Bytes>: ValidatorSigningInfo
// - 0x01<consAddrLen (1 Byte)><consAddress_Bytes>: ValidatorSigningInfo
//
// - 0x02<consAddress_Bytes><period_Bytes>: bool
// - 0x02<consAddrLen (1 Byte)><consAddress_Bytes><period_Bytes>: bool
//
// - 0x03<accAddr_Bytes>: crypto.PubKey
// - 0x03<accAddrLen (1 Byte)><accAddr_Bytes>: cryptotypes.PubKey
var (
ValidatorSigningInfoKeyPrefix = []byte{0x01} // Prefix for signing info
ValidatorMissedBlockBitArrayKeyPrefix = []byte{0x02} // Prefix for missed block bit array
@ -36,31 +37,31 @@ var (
// ValidatorSigningInfoKey - stored by *Consensus* address (not operator address)
func ValidatorSigningInfoKey(v sdk.ConsAddress) []byte {
return append(ValidatorSigningInfoKeyPrefix, v.Bytes()...)
return append(ValidatorSigningInfoKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
}
// ValidatorSigningInfoAddress - extract the address from a validator signing info key
func ValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) {
addr := key[1:]
if len(addr) != sdk.AddrLen {
panic("unexpected key length")
}
// Remove prefix and address length.
addr := key[2:]
return sdk.ConsAddress(addr)
}
// ValidatorMissedBlockBitArrayPrefixKey - stored by *Consensus* address (not operator address)
func ValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte {
return append(ValidatorMissedBlockBitArrayKeyPrefix, v.Bytes()...)
return append(ValidatorMissedBlockBitArrayKeyPrefix, address.MustLengthPrefix(v.Bytes())...)
}
// ValidatorMissedBlockBitArrayKey - stored by *Consensus* address (not operator address)
func ValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte {
b := make([]byte, 8)
binary.LittleEndian.PutUint64(b, uint64(i))
return append(ValidatorMissedBlockBitArrayPrefixKey(v), b...)
}
// AddrPubkeyRelationKey gets pubkey relation key used to get the pubkey from the address
func AddrPubkeyRelationKey(address []byte) []byte {
return append(AddrPubkeyRelationKeyPrefix, address...)
func AddrPubkeyRelationKey(addr []byte) []byte {
return append(AddrPubkeyRelationKeyPrefix, address.MustLengthPrefix(addr)...)
}

View File

@ -12,7 +12,7 @@ import (
"github.com/cosmos/cosmos-sdk/x/staking/types"
)
// Calculate the ValidatorUpdates for the current block
// BlockValidatorUpdates calculates the ValidatorUpdates for the current block
// Called in each EndBlock
func (k Keeper) BlockValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate {
// Calculate validator set changes.
@ -97,7 +97,7 @@ func (k Keeper) BlockValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate {
return validatorUpdates
}
// Apply and return accumulated updates to the bonded validator set. Also,
// ApplyAndReturnValidatorSetUpdates applies and return accumulated updates to the bonded validator set. Also,
// * Updates the active valset as keyed by LastValidatorPowerKey.
// * Updates the total power as keyed by LastTotalPowerKey.
// * Updates validator status' according to updated powers.
@ -117,7 +117,10 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab
// Retrieve the last validator set.
// The persistent set is updated later in this function.
// (see LastValidatorPowerKey).
last := k.getLastValidatorsByAddr(ctx)
last, err := k.getLastValidatorsByAddr(ctx)
if err != nil {
return nil, err
}
// Iterate over validators, highest power to lowest.
iterator := k.ValidatorsPowerStoreIterator(ctx)
@ -160,10 +163,11 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab
}
// fetch the old power bytes
var valAddrBytes [sdk.AddrLen]byte
copy(valAddrBytes[:], valAddr[:])
oldPowerBytes, found := last[valAddrBytes]
valAddrStr, err := sdk.Bech32ifyAddressBytes(sdk.Bech32PrefixValAddr, valAddr)
if err != nil {
return nil, err
}
oldPowerBytes, found := last[valAddrStr]
newPower := validator.ConsensusPower()
newPowerBytes := k.cdc.MustMarshalBinaryBare(&gogotypes.Int64Value{Value: newPower})
@ -174,13 +178,17 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab
k.SetLastValidatorPower(ctx, valAddr, newPower)
}
delete(last, valAddrBytes)
delete(last, valAddrStr)
count++
totalPower = totalPower.Add(sdk.NewInt(newPower))
}
noLongerBonded := sortNoLongerBonded(last)
noLongerBonded, err := sortNoLongerBonded(last)
if err != nil {
return nil, err
}
for _, valAddrBytes := range noLongerBonded {
validator := k.mustGetValidator(ctx, sdk.ValAddress(valAddrBytes))
validator, err = k.bondedToUnbonding(ctx, validator)
@ -339,39 +347,46 @@ func (k Keeper) completeUnbondingValidator(ctx sdk.Context, validator types.Vali
return validator
}
// map of operator addresses to serialized power
type validatorsByAddr map[[sdk.AddrLen]byte][]byte
// map of operator bech32-addresses to serialized power
// We use bech32 strings here, because we can't have slices as keys: map[[]byte][]byte
type validatorsByAddr map[string][]byte
// get the last validator set
func (k Keeper) getLastValidatorsByAddr(ctx sdk.Context) validatorsByAddr {
func (k Keeper) getLastValidatorsByAddr(ctx sdk.Context) (validatorsByAddr, error) {
last := make(validatorsByAddr)
iterator := k.LastValidatorsIterator(ctx)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
var valAddr [sdk.AddrLen]byte
// extract the validator address from the key (prefix is 1-byte)
copy(valAddr[:], iterator.Key()[1:])
// extract the validator address from the key (prefix is 1-byte, addrLen is 1-byte)
valAddr := types.AddressFromLastValidatorPowerKey(iterator.Key())
valAddrStr, err := sdk.Bech32ifyAddressBytes(sdk.Bech32PrefixValAddr, valAddr)
if err != nil {
return nil, err
}
powerBytes := iterator.Value()
last[valAddr] = make([]byte, len(powerBytes))
copy(last[valAddr], powerBytes)
last[valAddrStr] = make([]byte, len(powerBytes))
copy(last[valAddrStr], powerBytes)
}
return last
return last, nil
}
// given a map of remaining validators to previous bonded power
// returns the list of validators to be unbonded, sorted by operator address
func sortNoLongerBonded(last validatorsByAddr) [][]byte {
func sortNoLongerBonded(last validatorsByAddr) ([][]byte, error) {
// sort the map keys for determinism
noLongerBonded := make([][]byte, len(last))
index := 0
for valAddrBytes := range last {
valAddr := make([]byte, sdk.AddrLen)
copy(valAddr, valAddrBytes[:])
noLongerBonded[index] = valAddr
for valAddrStr := range last {
valAddrBytes, err := sdk.ValAddressFromBech32(valAddrStr)
if err != nil {
return nil, err
}
noLongerBonded[index] = valAddrBytes
index++
}
// sorted by address - order doesn't matter
@ -380,5 +395,5 @@ func sortNoLongerBonded(last validatorsByAddr) [][]byte {
return bytes.Compare(noLongerBonded[i], noLongerBonded[j]) == -1
})
return noLongerBonded
return noLongerBonded, nil
}

View File

@ -327,7 +327,7 @@ func (k Keeper) IterateLastValidatorPowers(ctx sdk.Context, handler func(operato
defer iter.Close()
for ; iter.Valid(); iter.Next() {
addr := sdk.ValAddress(iter.Key()[len(types.LastValidatorPowerKey):])
addr := sdk.ValAddress(types.AddressFromLastValidatorPowerKey(iter.Key()))
intV := &gogotypes.Int64Value{}
k.cdc.MustUnmarshalBinaryBare(iter.Value(), intV)

View File

@ -8,23 +8,16 @@ order: 1
LastTotalPower tracks the total amounts of bonded tokens recorded during the previous end block.
- LastTotalPower: `0x12 -> amino(sdk.Int)`
- LastTotalPower: `0x12 -> ProtocolBuffer(sdk.Int)`
## Params
Params is a module-wide configuration structure that stores system parameters
and defines overall functioning of the staking module.
- Params: `Paramsspace("staking") -> amino(params)`
- Params: `Paramsspace("staking") -> legacy_amino(params)`
```go
type Params struct {
UnbondingTime time.Duration // time duration of unbonding
MaxValidators uint16 // maximum number of validators
MaxEntries uint16 // max entries for either unbonding delegation or redelegation (per pair/trio)
BondDenom string // bondable coin denomination
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.1/proto/cosmos/staking/v1beta1/staking.proto#L230-L241
## Validator
@ -37,8 +30,8 @@ Validators can have one of three statuses
They are signing blocks and receiving rewards. They can receive further delegations.
They can be slashed for misbehavior. Delegators to this validator who unbond their delegation
must wait the duration of the UnbondingTime, a chain-specific param. during which time
they are still slashable for offences of the source validator if those offences were committed
during the period of time that the tokens were bonded.
they are still slashable for offences of the source validator if those offences were committed
during the period of time that the tokens were bonded.
- `Unbonding`: When a validator leaves the active set, either by choice or due to slashing or
tombstoning, an unbonding of all their delegations begins. All delegations must then wait the UnbondingTime
before moving receiving their tokens to their accounts from the `BondedPool`.
@ -51,10 +44,10 @@ required lookups for slashing and validator-set updates. A third special index
throughout each block, unlike the first two indices which mirror the validator
records within a block.
- Validators: `0x21 | OperatorAddr -> amino(validator)`
- Validators: `0x21 | OperatorAddr -> ProtocolBuffer(validator)`
- ValidatorsByConsAddr: `0x22 | ConsAddr -> OperatorAddr`
- ValidatorsByPower: `0x23 | BigEndian(ConsensusPower) | OperatorAddr -> OperatorAddr`
- LastValidatorsPower: `0x11 OperatorAddr -> amino(ConsensusPower)`
- LastValidatorsPower: `0x11 OperatorAddr -> ProtocolBuffer(ConsensusPower)`
`Validators` is the primary index - it ensures that each operator can have only one
associated validator, where the public key of that validator can change in the
@ -68,7 +61,7 @@ address which can be derived from the validator's `ConsPubKey`.
`ValidatorsByPower` is an additional index that provides a sorted list o
potential validators to quickly determine the current active set. Here
ConsensusPower is validator.Tokens/10^6. Note that all validators where
ConsensusPower is validator.Tokens/10^6. Note that all validators where
`Jailed` is true are not stored within this index.
`LastValidatorsPower` is a special index that provides a historical list of the
@ -77,60 +70,23 @@ is updated during the validator set update process which takes place in [`EndBlo
Each validator's state is stored in a `Validator` struct:
```go
type Validator struct {
OperatorAddress sdk.ValAddress // address of the validator's operator; bech encoded in JSON
ConsPubKey crypto.PubKey // the consensus public key of the validator; bech encoded in JSON
Jailed bool // has the validator been jailed from bonded status?
Status sdk.BondStatus // validator status (bonded/unbonding/unbonded)
Tokens sdk.Int // delegated tokens (incl. self-delegation)
DelegatorShares sdk.Dec // total shares issued to a validator's delegators
Description Description // description terms for the validator
UnbondingHeight int64 // if unbonding, height at which this validator has begun unbonding
UnbondingCompletionTime time.Time // if unbonding, min time for the validator to complete unbonding
Commission Commission // commission parameters
MinSelfDelegation sdk.Int // validator's self declared minimum self delegation
}
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L65-L99
type Commission struct {
CommissionRates
UpdateTime time.Time // the last time the commission rate was changed
}
CommissionRates struct {
Rate sdk.Dec // the commission rate charged to delegators, as a fraction
MaxRate sdk.Dec // maximum commission rate which validator can ever charge, as a fraction
MaxChangeRate sdk.Dec // maximum daily increase of the validator commission, as a fraction
}
type Description struct {
Moniker string // name
Identity string // optional identity signature (ex. UPort or Keybase)
Website string // optional website link
SecurityContact string // optional email for security contact
Details string // optional details
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L24-L63
## Delegation
Delegations are identified by combining `DelegatorAddr` (the address of the delegator)
with the `ValidatorAddr` Delegators are indexed in the store as follows:
- Delegation: `0x31 | DelegatorAddr | ValidatorAddr -> amino(delegation)`
- Delegation: `0x31 | DelegatorAddr | ValidatorAddr -> ProtocolBuffer(delegation)`
Stake holders may delegate coins to validators; under this circumstance their
funds are held in a `Delegation` data structure. It is owned by one
delegator, and is associated with the shares for one validator. The sender of
the transaction is the owner of the bond.
```go
type Delegation struct {
DelegatorAddr sdk.AccAddress
ValidatorAddr sdk.ValAddress
Shares sdk.Dec // delegation shares received
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L159-L170
### Delegator Shares
@ -159,10 +115,8 @@ detected.
`UnbondingDelegation` are indexed in the store as:
- UnbondingDelegation: `0x32 | DelegatorAddr | ValidatorAddr ->
amino(unbondingDelegation)`
- UnbondingDelegationsFromValidator: `0x33 | ValidatorAddr | DelegatorAddr ->
nil`
- UnbondingDelegation: `0x32 | DelegatorAddr | ValidatorAddr -> ProtocolBuffer(unbondingDelegation)`
- UnbondingDelegationsFromValidator: `0x33 | ValidatorAddr | DelegatorAddr -> nil`
The first map here is used in queries, to lookup all unbonding delegations for
a given delegator, while the second map is used in slashing, to lookup all
@ -171,20 +125,7 @@ slashed.
A UnbondingDelegation object is created every time an unbonding is initiated.
```go
type UnbondingDelegation struct {
DelegatorAddr sdk.AccAddress // delegator
ValidatorAddr sdk.ValAddress // validator unbonding from operator addr
Entries []UnbondingDelegationEntry // unbonding delegation entries
}
type UnbondingDelegationEntry struct {
CreationHeight int64 // height which the unbonding took place
CompletionTime time.Time // unix time for unbonding completion
InitialBalance sdk.Coin // atoms initially scheduled to receive at completion
Balance sdk.Coin // atoms to receive at completion
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L172-L198
## Redelegation
@ -196,7 +137,7 @@ committed by the source validator.
`Redelegation` are indexed in the store as:
- Redelegations: `0x34 | DelegatorAddr | ValidatorSrcAddr | ValidatorDstAddr -> amino(redelegation)`
- Redelegations: `0x34 | DelegatorAddr | ValidatorSrcAddr | ValidatorDstAddr -> ProtocolBuffer(redelegation)`
- RedelegationsBySrc: `0x35 | ValidatorSrcAddr | ValidatorDstAddr | DelegatorAddr -> nil`
- RedelegationsByDst: `0x36 | ValidatorDstAddr | ValidatorSrcAddr | DelegatorAddr -> nil`
@ -204,30 +145,15 @@ The first map here is used for queries, to lookup all redelegations for a given
delegator. The second map is used for slashing based on the `ValidatorSrcAddr`,
while the third map is for slashing based on the `ValidatorDstAddr`.
A redelegation object is created every time a redelegation occurs. To prevent
A redelegation object is created every time a redelegation occurs. To prevent
"redelegation hopping" redelegations may not occur under the situation that:
- the (re)delegator already has another immature redelegation in progress
with a destination to a validator (let's call it `Validator X`)
with a destination to a validator (let's call it `Validator X`)
- and, the (re)delegator is attempting to create a _new_ redelegation
where the source validator for this new redelegation is `Validator-X`.
where the source validator for this new redelegation is `Validator-X`.
```go
type Redelegation struct {
DelegatorAddr sdk.AccAddress // delegator
ValidatorSrcAddr sdk.ValAddress // validator redelegation source operator addr
ValidatorDstAddr sdk.ValAddress // validator redelegation destination operator addr
Entries []RedelegationEntry // redelegation entries
}
type RedelegationEntry struct {
CreationHeight int64 // height which the redelegation took place
CompletionTime time.Time // unix time for redelegation completion
InitialBalance sdk.Coin // initial balance when redelegation started
Balance sdk.Coin // current balance (current value held in destination validator)
SharesDst sdk.Dec // amount of destination-validator shares created by redelegation
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L200-L228
## Queues
@ -249,12 +175,7 @@ delegations queue is kept.
- UnbondingDelegation: `0x41 | format(time) -> []DVPair`
```go
type DVPair struct {
DelegatorAddr sdk.AccAddress
ValidatorAddr sdk.ValAddress
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L123-L133
### RedelegationQueue
@ -263,13 +184,7 @@ kept.
- UnbondingDelegation: `0x42 | format(time) -> []DVVTriplet`
```go
type DVVTriplet struct {
DelegatorAddr sdk.AccAddress
ValidatorSrcAddr sdk.ValAddress
ValidatorDstAddr sdk.ValAddress
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L140-L152
### ValidatorQueue
@ -279,7 +194,7 @@ queue is kept.
- ValidatorQueueTime: `0x43 | format(time) -> []sdk.ValAddress`
The stored object as each key is an array of validator operator addresses from
which the validator object can be accessed. Typically it is expected that only
which the validator object can be accessed. Typically it is expected that only
a single validator record will be associated with a given timestamp however it is possible
that multiple validators exist in the queue at the same location.
@ -288,15 +203,10 @@ that multiple validators exist in the queue at the same location.
HistoricalInfo objects are stored and pruned at each block such that the staking keeper persists
the `n` most recent historical info defined by staking module parameter: `HistoricalEntries`.
```go
type HistoricalInfo struct {
Header tmproto.Header
ValSet []types.Validator
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L15-L22
At each BeginBlock, the staking keeper will persist the current Header and the Validators that committed
the current block in a `HistoricalInfo` object. The Validators are sorted on their address to ensure that
the current block in a `HistoricalInfo` object. The Validators are sorted on their address to ensure that
they are in a determisnistic order.
The oldest HistoricalEntries will be pruned to ensure that there only exist the parameter-defined number of
The oldest HistoricalEntries will be pruned to ensure that there only exist the parameter-defined number of
historical entries.

View File

@ -11,12 +11,13 @@ This document describes the state transition operations pertaining to:
3. [Slashing](./02_state_transitions.md#slashing)
## Validators
State transitions in validators are performed on every [`EndBlock`](./05_end_block.md#validator-set-changes)
State transitions in validators are performed on every [`EndBlock`](./05_end_block.md#validator-set-changes)
in order to check for changes in the active `ValidatorSet`.
### Unbonded to Bonded
The following transition occurs when a validator's ranking in the `ValidatorPowerIndex` surpasses
The following transition occurs when a validator's ranking in the `ValidatorPowerIndex` surpasses
that of the `LastValidator`.
- set `validator.Status` to `Bonded`
@ -98,9 +99,9 @@ Redelegations affect the delegation, source and destination validators.
- perform an `unbond` delegation from the source validator to retrieve the tokens worth of the unbonded shares
- using the unbonded tokens, `Delegate` them to the destination validator
- if the `sourceValidator.Status` is `Bonded`, and the `destinationValidator` is not,
- if the `sourceValidator.Status` is `Bonded`, and the `destinationValidator` is not,
transfer the newly delegated tokens from the `BondedPool` to the `NotBondedPool` `ModuleAccount`
- otherwise, if the `sourceValidator.Status` is not `Bonded`, and the `destinationValidator`
- otherwise, if the `sourceValidator.Status` is not `Bonded`, and the `destinationValidator`
is `Bonded`, transfer the newly delegated tokens from the `NotBondedPool` to the `BondedPool` `ModuleAccount`
- record the token amount in an new entry in the relevant `Redelegation`
@ -116,20 +117,20 @@ When a redelegations complete the following occurs:
When a Validator is slashed, the following occurs:
- The total `slashAmount` is calculated as the `slashFactor` (a chain parameter) * `TokensFromConsensusPower`,
the total number of tokens bonded to the validator at the time of the infraction.
- The total `slashAmount` is calculated as the `slashFactor` (a chain parameter) \* `TokensFromConsensusPower`,
the total number of tokens bonded to the validator at the time of the infraction.
- Every unbonding delegation and redelegation from the validator are slashed by the `slashFactor`
percentage of the initialBalance.
percentage of the initialBalance.
- Each amount slashed from redelegations and unbonding delegations is subtracted from the
total slash amount.
total slash amount.
- The `remaingSlashAmount` is then slashed from the validator's tokens in the `BondedPool` or
`NonBondedPool` depending on the validator's status. This reduces the total supply of tokens.
`NonBondedPool` depending on the validator's status. This reduces the total supply of tokens.
### Slash Unbonding Delegation
When a validator is slashed, so are those unbonding delegations from the validator that began unbonding
after the time of the infraction. Every entry in every unbonding delegation from the validator
is slashed by `slashFactor`. The amount slashed is calculated from the `InitialBalance` of the
When a validator is slashed, so are those unbonding delegations from the validator that began unbonding
after the time of the infraction. Every entry in every unbonding delegation from the validator
is slashed by `slashFactor`. The amount slashed is calculated from the `InitialBalance` of the
delegation and is capped to prevent a resulting negative balance. Completed (or mature) unbondings are not slashed.
### Slash Redelegation

View File

@ -6,23 +6,15 @@ order: 3
In this section we describe the processing of the staking messages and the corresponding updates to the state. All created/modified state objects specified by each message are defined within the [state](./02_state_transitions.md) section.
## MsgCreateValidator
## Msg/CreateValidator
A validator is created using the `MsgCreateValidator` message.
A validator is created using the `Msg/CreateValidator` service message.
```go
type MsgCreateValidator struct {
Description Description
Commission Commission
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L16-L17
DelegatorAddr sdk.AccAddress
ValidatorAddr sdk.ValAddress
PubKey crypto.PubKey
Delegation sdk.Coin
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L35-L51
This message is expected to fail if:
This service message is expected to fail if:
- another validator with this operator address is already registered
- another validator with this pubkey is already registered
@ -33,71 +25,63 @@ This message is expected to fail if:
- the initial `MaxChangeRate` is either negative or > `MaxRate`
- the description fields are too large
This message creates and stores the `Validator` object at appropriate indexes.
This service message creates and stores the `Validator` object at appropriate indexes.
Additionally a self-delegation is made with the initial tokens delegation
tokens `Delegation`. The validator always starts as unbonded but may be bonded
in the first end-block.
## MsgEditValidator
## Msg/EditValidator
The `Description`, `CommissionRate` of a validator can be updated using the
`MsgEditCandidacy`.
`Msg/EditCandidacy` service message.
```go
type MsgEditCandidacy struct {
Description Description
ValidatorAddr sdk.ValAddress
CommissionRate sdk.Dec
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L19-L20
This message is expected to fail if:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L56-L76
This service message is expected to fail if:
- the initial `CommissionRate` is either negative or > `MaxRate`
- the `CommissionRate` has already been updated within the previous 24 hours
- the `CommissionRate` is > `MaxChangeRate`
- the description fields are too large
This message stores the updated `Validator` object.
This service message stores the updated `Validator` object.
## MsgDelegate
## Msg/Delegate
Within this message the delegator provides coins, and in return receives
Within this service message the delegator provides coins, and in return receives
some amount of their validator's (newly created) delegator-shares that are
assigned to `Delegation.Shares`.
```go
type MsgDelegate struct {
DelegatorAddr sdk.AccAddress
ValidatorAddr sdk.ValAddress
Amount sdk.Coin
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L22-L24
This message is expected to fail if:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L81-L90
This service message is expected to fail if:
- the validator is does not exist
- the validator is jailed
- the `Amount` `Coin` has a denomination different than one defined by `params.BondDenom`
If an existing `Delegation` object for provided addresses does not already
exist than it is created as part of this message otherwise the existing
exist than it is created as part of this service message otherwise the existing
`Delegation` is updated to include the newly received shares.
## MsgBeginUnbonding
## Msg/Undelegate
The begin unbonding message allows delegators to undelegate their tokens from
The `Msg/Undelegate` service message allows delegators to undelegate their tokens from
validator.
```go
type MsgBeginUnbonding struct {
DelegatorAddr sdk.AccAddress
ValidatorAddr sdk.ValAddress
Amount sdk.Coin
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L30-L32
This message is expected to fail if:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L112-L121
This service message returns a response containing the completion time of the undelegation:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L123-L126
This service message is expected to fail if:
- the delegation doesn't exist
- the validator doesn't exist
@ -105,7 +89,7 @@ This message is expected to fail if:
- existing `UnbondingDelegation` has maximum entries as defined by `params.MaxEntries`
- the `Amount` has a denomination different than one defined by `params.BondDenom`
When this message is processed the following actions occur:
When this service message is processed the following actions occur:
- validator's `DelegatorShares` and the delegation's `Shares` are both reduced by the message `SharesAmount`
- calculate the token worth of the shares remove that amount tokens held within the validator
@ -116,22 +100,21 @@ When this message is processed the following actions occur:
- if there are no more `Shares` in the delegation, then the delegation object is removed from the store
- under this situation if the delegation is the validator's self-delegation then also jail the validator.
## MsgBeginRedelegate
## Msg/BeginRedelegate
The redelegation command allows delegators to instantly switch validators. Once
the unbonding period has passed, the redelegation is automatically completed in
the EndBlocker.
```go
type MsgBeginRedelegate struct {
DelegatorAddr sdk.AccAddress
ValidatorSrcAddr sdk.ValAddress
ValidatorDstAddr sdk.ValAddress
Amount sdk.Coin
}
```
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L26-L28
This message is expected to fail if:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L95-L105
This service message returns a response containing the completion time of the redelegation:
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L107-L110
This service message is expected to fail if:
- the delegation doesn't exist
- the source or destination validators don't exist
@ -140,7 +123,7 @@ This message is expected to fail if:
- existing `Redelegation` has maximum entries as defined by `params.MaxEntries`
- the `Amount` `Coin` has a denomination different than one defined by `params.BondDenom`
When this message is processed the following actions occur:
When this service message is processed the following actions occur:
- the source validator's `DelegatorShares` and the delegations `Shares` are both reduced by the message `SharesAmount`
- calculate the token worth of the shares remove that amount tokens held within the source validator.
@ -148,6 +131,6 @@ When this message is processed the following actions occur:
- `Bonded` - add an entry to the `Redelegation` (create `Redelegation` if it doesn't exist) with a completion time a full unbonding period from the current time. Update pool shares to reduce BondedTokens and increase NotBondedTokens by token worth of the shares (this may be effectively reversed in the next step however).
- `Unbonding` - add an entry to the `Redelegation` (create `Redelegation` if it doesn't exist) with the same completion time as the validator (`UnbondingMinTime`).
- `Unbonded` - no action required in this step
- Delegate the token worth to the destination validator, possibly moving tokens back to the bonded state.
- Delegate the token worth to the destination validator, possibly moving tokens back to the bonded state.
- if there are no more `Shares` in the source delegation, then the source delegation object is removed from the store
- under this situation if the delegation is the validator's self-delegation then also jail the validator.

View File

@ -18,9 +18,9 @@ The staking module emits the following events:
| complete_redelegation | destination_validator | {dstValidatorAddress} |
| complete_redelegation | delegator | {delegatorAddress} |
## Handlers
## Service Messages
### MsgCreateValidator
### Msg/CreateValidator
| Type | Attribute Key | Attribute Value |
| ---------------- | ------------- | ------------------ |
@ -30,7 +30,7 @@ The staking module emits the following events:
| message | action | create_validator |
| message | sender | {senderAddress} |
### MsgEditValidator
### Msg/EditValidator
| Type | Attribute Key | Attribute Value |
| -------------- | ------------------- | ------------------- |
@ -40,7 +40,7 @@ The staking module emits the following events:
| message | action | edit_validator |
| message | sender | {senderAddress} |
### MsgDelegate
### Msg/Delegate
| Type | Attribute Key | Attribute Value |
| -------- | ------------- | ------------------ |
@ -50,7 +50,7 @@ The staking module emits the following events:
| message | action | delegate |
| message | sender | {senderAddress} |
### MsgUndelegate
### Msg/Undelegate
| Type | Attribute Key | Attribute Value |
| ------- | ------------------- | ------------------ |
@ -61,9 +61,9 @@ The staking module emits the following events:
| message | action | begin_unbonding |
| message | sender | {senderAddress} |
* [0] Time is formatted in the RFC3339 standard
- [0] Time is formatted in the RFC3339 standard
### MsgBeginRedelegate
### Msg/BeginRedelegate
| Type | Attribute Key | Attribute Value |
| ---------- | --------------------- | --------------------- |
@ -75,4 +75,4 @@ The staking module emits the following events:
| message | action | begin_redelegate |
| message | sender | {senderAddress} |
* [0] Time is formatted in the RFC3339 standard
- [0] Time is formatted in the RFC3339 standard

View File

@ -38,18 +38,18 @@ network.
- [Delegations](02_state_transitions.md#delegations)
- [Slashing](02_state_transitions.md#slashing)
3. **[Messages](03_messages.md)**
- [MsgCreateValidator](03_messages.md#msgcreatevalidator)
- [MsgEditValidator](03_messages.md#msgeditvalidator)
- [MsgDelegate](03_messages.md#msgdelegate)
- [MsgBeginUnbonding](03_messages.md#msgbeginunbonding)
- [MsgBeginRedelegate](03_messages.md#msgbeginredelegate)
- [Msg/CreateValidator](03_messages.md#msgcreatevalidator)
- [Msg/EditValidator](03_messages.md#msgeditvalidator)
- [Msg/Delegate](03_messages.md#msgdelegate)
- [Msg/BeginUnbonding](03_messages.md#msgbeginunbonding)
- [Msg/BeginRedelegate](03_messages.md#msgbeginredelegate)
4. **[Begin-Block](04_begin_block.md)**
- [Historical Info Tracking](04_begin_block.md#historical-info-tracking)
4. **[End-Block ](05_end_block.md)**
5. **[End-Block ](05_end_block.md)**
- [Validator Set Changes](05_end_block.md#validator-set-changes)
- [Queues ](05_end_block.md#queues-)
5. **[Hooks](06_hooks.md)**
6. **[Events](07_events.md)**
6. **[Hooks](06_hooks.md)**
7. **[Events](07_events.md)**
- [EndBlocker](07_events.md#endblocker)
- [Handlers](07_events.md#handlers)
7. **[Parameters](08_params.md)**
8. **[Parameters](08_params.md)**

View File

@ -8,6 +8,7 @@ import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/address"
)
const (
@ -48,24 +49,29 @@ var (
HistoricalInfoKey = []byte{0x50} // prefix for the historical info
)
// gets the key for the validator with address
// GetValidatorKey creates the key for the validator with address
// VALUE: staking/Validator
func GetValidatorKey(operatorAddr sdk.ValAddress) []byte {
return append(ValidatorsKey, operatorAddr.Bytes()...)
return append(ValidatorsKey, address.MustLengthPrefix(operatorAddr)...)
}
// gets the key for the validator with pubkey
// GetValidatorByConsAddrKey creates the key for the validator with pubkey
// VALUE: validator operator address ([]byte)
func GetValidatorByConsAddrKey(addr sdk.ConsAddress) []byte {
return append(ValidatorsByConsAddrKey, addr.Bytes()...)
return append(ValidatorsByConsAddrKey, address.MustLengthPrefix(addr)...)
}
// Get the validator operator address from LastValidatorPowerKey
// AddressFromValidatorsKey creates the validator operator address from ValidatorsKey
func AddressFromValidatorsKey(key []byte) []byte {
return key[2:] // remove prefix bytes and address length
}
// AddressFromLastValidatorPowerKey creates the validator operator address from LastValidatorPowerKey
func AddressFromLastValidatorPowerKey(key []byte) []byte {
return key[1:] // remove prefix bytes
return key[2:] // remove prefix bytes and address length
}
// get the validator by power index.
// GetValidatorsByPowerIndexKey creates the validator by power index.
// Power index is the key used in the power-store, and represents the relative
// power ranking of the validator.
// VALUE: validator operator address ([]byte)
@ -80,39 +86,39 @@ func GetValidatorsByPowerIndexKey(validator Validator) []byte {
powerBytes := consensusPowerBytes
powerBytesLen := len(powerBytes) // 8
// key is of format prefix || powerbytes || addrBytes
key := make([]byte, 1+powerBytesLen+sdk.AddrLen)
key[0] = ValidatorsByPowerIndexKey[0]
copy(key[1:powerBytesLen+1], powerBytes)
addr, err := sdk.ValAddressFromBech32(validator.OperatorAddress)
if err != nil {
panic(err)
}
operAddrInvr := sdk.CopyBytes(addr)
addrLen := len(operAddrInvr)
for i, b := range operAddrInvr {
operAddrInvr[i] = ^b
}
copy(key[powerBytesLen+1:], operAddrInvr)
// key is of format prefix || powerbytes || addrLen (1byte) || addrBytes
key := make([]byte, 1+powerBytesLen+1+addrLen)
key[0] = ValidatorsByPowerIndexKey[0]
copy(key[1:powerBytesLen+1], powerBytes)
key[powerBytesLen+1] = byte(addrLen)
copy(key[powerBytesLen+2:], operAddrInvr)
return key
}
// get the bonded validator index key for an operator address
// GetLastValidatorPowerKey creates the bonded validator index key for an operator address
func GetLastValidatorPowerKey(operator sdk.ValAddress) []byte {
return append(LastValidatorPowerKey, operator...)
return append(LastValidatorPowerKey, address.MustLengthPrefix(operator)...)
}
// parse the validators operator address from power rank key
// ParseValidatorPowerRankKey parses the validators operator address from power rank key
func ParseValidatorPowerRankKey(key []byte) (operAddr []byte) {
powerBytesLen := 8
if len(key) != 1+powerBytesLen+sdk.AddrLen {
panic("Invalid validator power rank key length")
}
operAddr = sdk.CopyBytes(key[powerBytesLen+1:])
// key is of format prefix (1 byte) || powerbytes || addrLen (1byte) || addrBytes
operAddr = sdk.CopyBytes(key[powerBytesLen+2:])
for i, b := range operAddr {
operAddr[i] = ^b
@ -165,55 +171,51 @@ func ParseValidatorQueueKey(bz []byte) (time.Time, int64, error) {
return ts, int64(height), nil
}
// gets the key for delegator bond with validator
// GetDelegationKey creates the key for delegator bond with validator
// VALUE: staking/Delegation
func GetDelegationKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte {
return append(GetDelegationsKey(delAddr), valAddr.Bytes()...)
return append(GetDelegationsKey(delAddr), address.MustLengthPrefix(valAddr)...)
}
// gets the prefix for a delegator for all validators
// GetDelegationsKey creates the prefix for a delegator for all validators
func GetDelegationsKey(delAddr sdk.AccAddress) []byte {
return append(DelegationKey, delAddr.Bytes()...)
return append(DelegationKey, address.MustLengthPrefix(delAddr)...)
}
// gets the key for an unbonding delegation by delegator and validator addr
// GetUBDKey creates the key for an unbonding delegation by delegator and validator addr
// VALUE: staking/UnbondingDelegation
func GetUBDKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte {
return append(
GetUBDsKey(delAddr.Bytes()),
valAddr.Bytes()...)
return append(GetUBDsKey(delAddr.Bytes()), address.MustLengthPrefix(valAddr)...)
}
// gets the index-key for an unbonding delegation, stored by validator-index
// GetUBDByValIndexKey creates the index-key for an unbonding delegation, stored by validator-index
// VALUE: none (key rearrangement used)
func GetUBDByValIndexKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte {
return append(GetUBDsByValIndexKey(valAddr), delAddr.Bytes()...)
return append(GetUBDsByValIndexKey(valAddr), address.MustLengthPrefix(delAddr)...)
}
// rearranges the ValIndexKey to get the UBDKey
// GetUBDKeyFromValIndexKey rearranges the ValIndexKey to get the UBDKey
func GetUBDKeyFromValIndexKey(indexKey []byte) []byte {
addrs := indexKey[1:] // remove prefix bytes
if len(addrs) != 2*sdk.AddrLen {
panic("unexpected key length")
}
valAddr := addrs[:sdk.AddrLen]
delAddr := addrs[sdk.AddrLen:]
valAddrLen := addrs[0]
valAddr := addrs[1 : 1+valAddrLen]
delAddr := addrs[valAddrLen+2:]
return GetUBDKey(delAddr, valAddr)
}
// gets the prefix for all unbonding delegations from a delegator
// GetUBDsKey creates the prefix for all unbonding delegations from a delegator
func GetUBDsKey(delAddr sdk.AccAddress) []byte {
return append(UnbondingDelegationKey, delAddr.Bytes()...)
return append(UnbondingDelegationKey, address.MustLengthPrefix(delAddr)...)
}
// gets the prefix keyspace for the indexes of unbonding delegations for a validator
// GetUBDsByValIndexKey creates the prefix keyspace for the indexes of unbonding delegations for a validator
func GetUBDsByValIndexKey(valAddr sdk.ValAddress) []byte {
return append(UnbondingDelegationByValIndexKey, valAddr.Bytes()...)
return append(UnbondingDelegationByValIndexKey, address.MustLengthPrefix(valAddr)...)
}
// gets the prefix for all unbonding delegations from a delegator
// GetUnbondingDelegationTimeKey creates the prefix for all unbonding delegations from a delegator
func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte {
bz := sdk.FormatTimeBytes(timestamp)
return append(UnbondingQueueKey, bz...)
@ -222,69 +224,76 @@ func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte {
// GetREDKey returns a key prefix for indexing a redelegation from a delegator
// and source validator to a destination validator.
func GetREDKey(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) []byte {
key := make([]byte, 1+sdk.AddrLen*3)
// key is of the form GetREDsKey || valSrcAddrLen (1 byte) || valSrcAddr || valDstAddrLen (1 byte) || valDstAddr
key := make([]byte, 1+3+len(delAddr)+len(valSrcAddr)+len(valDstAddr))
copy(key[0:sdk.AddrLen+1], GetREDsKey(delAddr.Bytes()))
copy(key[sdk.AddrLen+1:2*sdk.AddrLen+1], valSrcAddr.Bytes())
copy(key[2*sdk.AddrLen+1:3*sdk.AddrLen+1], valDstAddr.Bytes())
copy(key[0:2+len(delAddr)], GetREDsKey(delAddr.Bytes()))
key[2+len(delAddr)] = byte(len(valSrcAddr))
copy(key[3+len(delAddr):3+len(delAddr)+len(valSrcAddr)], valSrcAddr.Bytes())
key[3+len(delAddr)+len(valSrcAddr)] = byte(len(valDstAddr))
copy(key[4+len(delAddr)+len(valSrcAddr):], valDstAddr.Bytes())
return key
}
// gets the index-key for a redelegation, stored by source-validator-index
// GetREDByValSrcIndexKey creates the index-key for a redelegation, stored by source-validator-index
// VALUE: none (key rearrangement used)
func GetREDByValSrcIndexKey(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) []byte {
REDSFromValsSrcKey := GetREDsFromValSrcIndexKey(valSrcAddr)
offset := len(REDSFromValsSrcKey)
// key is of the form REDSFromValsSrcKey || delAddr || valDstAddr
key := make([]byte, len(REDSFromValsSrcKey)+2*sdk.AddrLen)
// key is of the form REDSFromValsSrcKey || delAddrLen (1 byte) || delAddr || valDstAddrLen (1 byte) || valDstAddr
key := make([]byte, offset+2+len(delAddr)+len(valDstAddr))
copy(key[0:offset], REDSFromValsSrcKey)
copy(key[offset:offset+sdk.AddrLen], delAddr.Bytes())
copy(key[offset+sdk.AddrLen:offset+2*sdk.AddrLen], valDstAddr.Bytes())
key[offset] = byte(len(delAddr))
copy(key[offset+1:offset+1+len(delAddr)], delAddr.Bytes())
key[offset+1+len(delAddr)] = byte(len(valDstAddr))
copy(key[offset+2+len(delAddr):], valDstAddr.Bytes())
return key
}
// gets the index-key for a redelegation, stored by destination-validator-index
// GetREDByValDstIndexKey creates the index-key for a redelegation, stored by destination-validator-index
// VALUE: none (key rearrangement used)
func GetREDByValDstIndexKey(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) []byte {
REDSToValsDstKey := GetREDsToValDstIndexKey(valDstAddr)
offset := len(REDSToValsDstKey)
// key is of the form REDSToValsDstKey || delAddr || valSrcAddr
key := make([]byte, len(REDSToValsDstKey)+2*sdk.AddrLen)
// key is of the form REDSToValsDstKey || delAddrLen (1 byte) || delAddr || valSrcAddrLen (1 byte) || valSrcAddr
key := make([]byte, offset+2+len(delAddr)+len(valSrcAddr))
copy(key[0:offset], REDSToValsDstKey)
copy(key[offset:offset+sdk.AddrLen], delAddr.Bytes())
copy(key[offset+sdk.AddrLen:offset+2*sdk.AddrLen], valSrcAddr.Bytes())
key[offset] = byte(len(delAddr))
copy(key[offset+1:offset+1+len(delAddr)], delAddr.Bytes())
key[offset+1+len(delAddr)] = byte(len(valSrcAddr))
copy(key[offset+2+len(delAddr):], valSrcAddr.Bytes())
return key
}
// GetREDKeyFromValSrcIndexKey rearranges the ValSrcIndexKey to get the REDKey
func GetREDKeyFromValSrcIndexKey(indexKey []byte) []byte {
// note that first byte is prefix byte
if len(indexKey) != 3*sdk.AddrLen+1 {
panic("unexpected key length")
}
// note that first byte is prefix byte, which we remove
addrs := indexKey[1:]
valSrcAddr := indexKey[1 : sdk.AddrLen+1]
delAddr := indexKey[sdk.AddrLen+1 : 2*sdk.AddrLen+1]
valDstAddr := indexKey[2*sdk.AddrLen+1 : 3*sdk.AddrLen+1]
valSrcAddrLen := addrs[0]
valSrcAddr := addrs[1 : valSrcAddrLen+1]
delAddrLen := addrs[valSrcAddrLen+1]
delAddr := addrs[valSrcAddrLen+2 : valSrcAddrLen+2+delAddrLen]
valDstAddr := addrs[valSrcAddrLen+delAddrLen+3:]
return GetREDKey(delAddr, valSrcAddr, valDstAddr)
}
// GetREDKeyFromValDstIndexKey rearranges the ValDstIndexKey to get the REDKey
func GetREDKeyFromValDstIndexKey(indexKey []byte) []byte {
// note that first byte is prefix byte
if len(indexKey) != 3*sdk.AddrLen+1 {
panic("unexpected key length")
}
// note that first byte is prefix byte, which we remove
addrs := indexKey[1:]
valDstAddr := indexKey[1 : sdk.AddrLen+1]
delAddr := indexKey[sdk.AddrLen+1 : 2*sdk.AddrLen+1]
valSrcAddr := indexKey[2*sdk.AddrLen+1 : 3*sdk.AddrLen+1]
valDstAddrLen := addrs[0]
valDstAddr := addrs[1 : valDstAddrLen+1]
delAddrLen := addrs[valDstAddrLen+1]
delAddr := addrs[valDstAddrLen+2 : valDstAddrLen+2+delAddrLen]
valSrcAddr := addrs[valDstAddrLen+delAddrLen+3:]
return GetREDKey(delAddr, valSrcAddr, valDstAddr)
}
@ -299,25 +308,25 @@ func GetRedelegationTimeKey(timestamp time.Time) []byte {
// GetREDsKey returns a key prefix for indexing a redelegation from a delegator
// address.
func GetREDsKey(delAddr sdk.AccAddress) []byte {
return append(RedelegationKey, delAddr.Bytes()...)
return append(RedelegationKey, address.MustLengthPrefix(delAddr)...)
}
// GetREDsFromValSrcIndexKey returns a key prefix for indexing a redelegation to
// a source validator.
func GetREDsFromValSrcIndexKey(valSrcAddr sdk.ValAddress) []byte {
return append(RedelegationByValSrcIndexKey, valSrcAddr.Bytes()...)
return append(RedelegationByValSrcIndexKey, address.MustLengthPrefix(valSrcAddr)...)
}
// GetREDsToValDstIndexKey returns a key prefix for indexing a redelegation to a
// destination (target) validator.
func GetREDsToValDstIndexKey(valDstAddr sdk.ValAddress) []byte {
return append(RedelegationByValDstIndexKey, valDstAddr.Bytes()...)
return append(RedelegationByValDstIndexKey, address.MustLengthPrefix(valDstAddr)...)
}
// GetREDsByDelToValDstIndexKey returns a key prefix for indexing a redelegation
// from an address to a source validator.
func GetREDsByDelToValDstIndexKey(delAddr sdk.AccAddress, valDstAddr sdk.ValAddress) []byte {
return append(GetREDsToValDstIndexKey(valDstAddr), delAddr.Bytes()...)
return append(GetREDsToValDstIndexKey(valDstAddr), address.MustLengthPrefix(delAddr)...)
}
// GetHistoricalInfoKey returns a key prefix for indexing HistoricalInfo objects.

View File

@ -37,10 +37,10 @@ func TestGetValidatorPowerRank(t *testing.T) {
validator types.Validator
wantHex string
}{
{val1, "2300000000000000009c288ede7df62742fc3b7d0962045a8cef0f79f6"},
{val2, "2300000000000000019c288ede7df62742fc3b7d0962045a8cef0f79f6"},
{val3, "23000000000000000a9c288ede7df62742fc3b7d0962045a8cef0f79f6"},
{val4, "2300000100000000009c288ede7df62742fc3b7d0962045a8cef0f79f6"},
{val1, "230000000000000000149c288ede7df62742fc3b7d0962045a8cef0f79f6"},
{val2, "230000000000000001149c288ede7df62742fc3b7d0962045a8cef0f79f6"},
{val3, "23000000000000000a149c288ede7df62742fc3b7d0962045a8cef0f79f6"},
{val4, "230000010000000000149c288ede7df62742fc3b7d0962045a8cef0f79f6"},
}
for i, tt := range tests {
got := hex.EncodeToString(types.GetValidatorsByPowerIndexKey(tt.validator))
@ -57,11 +57,11 @@ func TestGetREDByValDstIndexKey(t *testing.T) {
wantHex string
}{
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr1),
"3663d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"},
"361463d771218209d8bd03c482f69dfba57310f086091463d771218209d8bd03c482f69dfba57310f086091463d771218209d8bd03c482f69dfba57310f08609"},
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr2), sdk.ValAddress(keysAddr3),
"363ab62f0d93849be495e21e3e9013a517038f45bd63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f2"},
"36143ab62f0d93849be495e21e3e9013a517038f45bd1463d771218209d8bd03c482f69dfba57310f08609145ef3b5f25c54946d4a89fc0d09d2f126614540f2"},
{sdk.AccAddress(keysAddr2), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr3),
"363ab62f0d93849be495e21e3e9013a517038f45bd5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f08609"},
"36143ab62f0d93849be495e21e3e9013a517038f45bd145ef3b5f25c54946d4a89fc0d09d2f126614540f21463d771218209d8bd03c482f69dfba57310f08609"},
}
for i, tt := range tests {
got := hex.EncodeToString(types.GetREDByValDstIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr))
@ -78,11 +78,11 @@ func TestGetREDByValSrcIndexKey(t *testing.T) {
wantHex string
}{
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr1),
"3563d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"},
"351463d771218209d8bd03c482f69dfba57310f086091463d771218209d8bd03c482f69dfba57310f086091463d771218209d8bd03c482f69dfba57310f08609"},
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr2), sdk.ValAddress(keysAddr3),
"355ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f086093ab62f0d93849be495e21e3e9013a517038f45bd"},
"35145ef3b5f25c54946d4a89fc0d09d2f126614540f21463d771218209d8bd03c482f69dfba57310f08609143ab62f0d93849be495e21e3e9013a517038f45bd"},
{sdk.AccAddress(keysAddr2), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr3),
"3563d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f23ab62f0d93849be495e21e3e9013a517038f45bd"},
"351463d771218209d8bd03c482f69dfba57310f08609145ef3b5f25c54946d4a89fc0d09d2f126614540f2143ab62f0d93849be495e21e3e9013a517038f45bd"},
}
for i, tt := range tests {
got := hex.EncodeToString(types.GetREDByValSrcIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr))

View File

@ -135,8 +135,11 @@ func (m *HistoricalInfo) GetValset() []Validator {
// CommissionRates defines the initial commission rates to be used for creating
// a validator.
type CommissionRates struct {
Rate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,1,opt,name=rate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"rate"`
MaxRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=max_rate,json=maxRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"max_rate" yaml:"max_rate"`
// rate is the commission rate charged to delegators, as a fraction.
Rate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,1,opt,name=rate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"rate"`
// max_rate defines the maximum commission rate which validator can ever charge, as a fraction.
MaxRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=max_rate,json=maxRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"max_rate" yaml:"max_rate"`
// max_change_rate defines the maximum daily increase of the validator commission, as a fraction.
MaxChangeRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=max_change_rate,json=maxChangeRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"max_change_rate" yaml:"max_change_rate"`
}
@ -174,8 +177,10 @@ var xxx_messageInfo_CommissionRates proto.InternalMessageInfo
// Commission defines commission parameters for a given validator.
type Commission struct {
// commission_rates defines the initial commission rates to be used for creating a validator.
CommissionRates `protobuf:"bytes,1,opt,name=commission_rates,json=commissionRates,proto3,embedded=commission_rates" json:"commission_rates"`
UpdateTime time.Time `protobuf:"bytes,2,opt,name=update_time,json=updateTime,proto3,stdtime" json:"update_time" yaml:"update_time"`
// update_time is the last time the commission rate was changed.
UpdateTime time.Time `protobuf:"bytes,2,opt,name=update_time,json=updateTime,proto3,stdtime" json:"update_time" yaml:"update_time"`
}
func (m *Commission) Reset() { *m = Commission{} }
@ -219,11 +224,16 @@ func (m *Commission) GetUpdateTime() time.Time {
// Description defines a validator description.
type Description struct {
Moniker string `protobuf:"bytes,1,opt,name=moniker,proto3" json:"moniker,omitempty"`
Identity string `protobuf:"bytes,2,opt,name=identity,proto3" json:"identity,omitempty"`
Website string `protobuf:"bytes,3,opt,name=website,proto3" json:"website,omitempty"`
// moniker defines a human-readable name for the validator.
Moniker string `protobuf:"bytes,1,opt,name=moniker,proto3" json:"moniker,omitempty"`
// identity defines an optional identity signature (ex. UPort or Keybase).
Identity string `protobuf:"bytes,2,opt,name=identity,proto3" json:"identity,omitempty"`
// website defines an optional website link.
Website string `protobuf:"bytes,3,opt,name=website,proto3" json:"website,omitempty"`
// security_contact defines an optional email for security contact.
SecurityContact string `protobuf:"bytes,4,opt,name=security_contact,json=securityContact,proto3" json:"security_contact,omitempty" yaml:"security_contact"`
Details string `protobuf:"bytes,5,opt,name=details,proto3" json:"details,omitempty"`
// details define other optional details.
Details string `protobuf:"bytes,5,opt,name=details,proto3" json:"details,omitempty"`
}
func (m *Description) Reset() { *m = Description{} }
@ -302,16 +312,27 @@ func (m *Description) GetDetails() string {
// exchange rate. Voting power can be calculated as total bonded shares
// multiplied by exchange rate.
type Validator struct {
OperatorAddress string `protobuf:"bytes,1,opt,name=operator_address,json=operatorAddress,proto3" json:"operator_address,omitempty" yaml:"operator_address"`
ConsensusPubkey *types1.Any `protobuf:"bytes,2,opt,name=consensus_pubkey,json=consensusPubkey,proto3" json:"consensus_pubkey,omitempty" yaml:"consensus_pubkey"`
Jailed bool `protobuf:"varint,3,opt,name=jailed,proto3" json:"jailed,omitempty"`
Status BondStatus `protobuf:"varint,4,opt,name=status,proto3,enum=cosmos.staking.v1beta1.BondStatus" json:"status,omitempty"`
Tokens github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,5,opt,name=tokens,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"tokens"`
DelegatorShares github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=delegator_shares,json=delegatorShares,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"delegator_shares" yaml:"delegator_shares"`
Description Description `protobuf:"bytes,7,opt,name=description,proto3" json:"description"`
UnbondingHeight int64 `protobuf:"varint,8,opt,name=unbonding_height,json=unbondingHeight,proto3" json:"unbonding_height,omitempty" yaml:"unbonding_height"`
UnbondingTime time.Time `protobuf:"bytes,9,opt,name=unbonding_time,json=unbondingTime,proto3,stdtime" json:"unbonding_time" yaml:"unbonding_time"`
Commission Commission `protobuf:"bytes,10,opt,name=commission,proto3" json:"commission"`
// operator_address defines the address of the validator's operator; bech encoded in JSON.
OperatorAddress string `protobuf:"bytes,1,opt,name=operator_address,json=operatorAddress,proto3" json:"operator_address,omitempty" yaml:"operator_address"`
// consensus_pubkey is the consensus public key of the validator, as a Protobuf Any.
ConsensusPubkey *types1.Any `protobuf:"bytes,2,opt,name=consensus_pubkey,json=consensusPubkey,proto3" json:"consensus_pubkey,omitempty" yaml:"consensus_pubkey"`
// jailed defined whether the validator has been jailed from bonded status or not.
Jailed bool `protobuf:"varint,3,opt,name=jailed,proto3" json:"jailed,omitempty"`
// status is the validator status (bonded/unbonding/unbonded).
Status BondStatus `protobuf:"varint,4,opt,name=status,proto3,enum=cosmos.staking.v1beta1.BondStatus" json:"status,omitempty"`
// tokens define the delegated tokens (incl. self-delegation).
Tokens github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,5,opt,name=tokens,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"tokens"`
// delegator_shares defines total shares issued to a validator's delegators.
DelegatorShares github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=delegator_shares,json=delegatorShares,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"delegator_shares" yaml:"delegator_shares"`
// description defines the description terms for the validator.
Description Description `protobuf:"bytes,7,opt,name=description,proto3" json:"description"`
// unbonding_height defines, if unbonding, the height at which this validator has begun unbonding.
UnbondingHeight int64 `protobuf:"varint,8,opt,name=unbonding_height,json=unbondingHeight,proto3" json:"unbonding_height,omitempty" yaml:"unbonding_height"`
// unbonding_time defines, if unbonding, the min time for the validator to complete unbonding.
UnbondingTime time.Time `protobuf:"bytes,9,opt,name=unbonding_time,json=unbondingTime,proto3,stdtime" json:"unbonding_time" yaml:"unbonding_time"`
// commission defines the commission parameters.
Commission Commission `protobuf:"bytes,10,opt,name=commission,proto3" json:"commission"`
// min_self_delegation is the validator's self declared minimum self delegation.
MinSelfDelegation github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,11,opt,name=min_self_delegation,json=minSelfDelegation,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"min_self_delegation" yaml:"min_self_delegation"`
}
@ -567,9 +588,12 @@ func (m *DVVTriplets) GetTriplets() []DVVTriplet {
// owned by one delegator, and is associated with the voting power of one
// validator.
type Delegation struct {
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty" yaml:"validator_address"`
Shares github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=shares,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"shares"`
// delegator_address is the bech32-encoded address of the delegator.
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
// validator_address is the bech32-encoded address of the validator.
ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty" yaml:"validator_address"`
// shares define the delegation shares received.
Shares github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=shares,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"shares"`
}
func (m *Delegation) Reset() { *m = Delegation{} }
@ -607,9 +631,12 @@ var xxx_messageInfo_Delegation proto.InternalMessageInfo
// UnbondingDelegation stores all of a single delegator's unbonding bonds
// for a single validator in an time-ordered list.
type UnbondingDelegation struct {
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty" yaml:"validator_address"`
Entries []UnbondingDelegationEntry `protobuf:"bytes,3,rep,name=entries,proto3" json:"entries"`
// delegator_address is the bech32-encoded address of the delegator.
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
// validator_address is the bech32-encoded address of the validator.
ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty" yaml:"validator_address"`
// entries are the unbonding delegation entries.
Entries []UnbondingDelegationEntry `protobuf:"bytes,3,rep,name=entries,proto3" json:"entries"`
}
func (m *UnbondingDelegation) Reset() { *m = UnbondingDelegation{} }
@ -646,10 +673,14 @@ var xxx_messageInfo_UnbondingDelegation proto.InternalMessageInfo
// UnbondingDelegationEntry defines an unbonding object with relevant metadata.
type UnbondingDelegationEntry struct {
CreationHeight int64 `protobuf:"varint,1,opt,name=creation_height,json=creationHeight,proto3" json:"creation_height,omitempty" yaml:"creation_height"`
CompletionTime time.Time `protobuf:"bytes,2,opt,name=completion_time,json=completionTime,proto3,stdtime" json:"completion_time" yaml:"completion_time"`
// creation_height is the height which the unbonding took place.
CreationHeight int64 `protobuf:"varint,1,opt,name=creation_height,json=creationHeight,proto3" json:"creation_height,omitempty" yaml:"creation_height"`
// completion_time is the unix time for unbonding completion.
CompletionTime time.Time `protobuf:"bytes,2,opt,name=completion_time,json=completionTime,proto3,stdtime" json:"completion_time" yaml:"completion_time"`
// initial_balance defines the tokens initially scheduled to receive at completion.
InitialBalance github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=initial_balance,json=initialBalance,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"initial_balance" yaml:"initial_balance"`
Balance github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=balance,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"balance"`
// balance defines the tokens to receive at completion.
Balance github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,4,opt,name=balance,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"balance"`
}
func (m *UnbondingDelegationEntry) Reset() { *m = UnbondingDelegationEntry{} }
@ -700,10 +731,14 @@ func (m *UnbondingDelegationEntry) GetCompletionTime() time.Time {
// RedelegationEntry defines a redelegation object with relevant metadata.
type RedelegationEntry struct {
CreationHeight int64 `protobuf:"varint,1,opt,name=creation_height,json=creationHeight,proto3" json:"creation_height,omitempty" yaml:"creation_height"`
CompletionTime time.Time `protobuf:"bytes,2,opt,name=completion_time,json=completionTime,proto3,stdtime" json:"completion_time" yaml:"completion_time"`
// creation_height defines the height which the redelegation took place.
CreationHeight int64 `protobuf:"varint,1,opt,name=creation_height,json=creationHeight,proto3" json:"creation_height,omitempty" yaml:"creation_height"`
// completion_time defines the unix time for redelegation completion.
CompletionTime time.Time `protobuf:"bytes,2,opt,name=completion_time,json=completionTime,proto3,stdtime" json:"completion_time" yaml:"completion_time"`
// initial_balance defines the initial balance when redelegation started.
InitialBalance github_com_cosmos_cosmos_sdk_types.Int `protobuf:"bytes,3,opt,name=initial_balance,json=initialBalance,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Int" json:"initial_balance" yaml:"initial_balance"`
SharesDst github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=shares_dst,json=sharesDst,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"shares_dst"`
// shares_dst is the amount of destination-validator shares created by redelegation.
SharesDst github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=shares_dst,json=sharesDst,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"shares_dst"`
}
func (m *RedelegationEntry) Reset() { *m = RedelegationEntry{} }
@ -755,10 +790,14 @@ func (m *RedelegationEntry) GetCompletionTime() time.Time {
// Redelegation contains the list of a particular delegator's redelegating bonds
// from a particular source validator to a particular destination validator.
type Redelegation struct {
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
ValidatorSrcAddress string `protobuf:"bytes,2,opt,name=validator_src_address,json=validatorSrcAddress,proto3" json:"validator_src_address,omitempty" yaml:"validator_src_address"`
ValidatorDstAddress string `protobuf:"bytes,3,opt,name=validator_dst_address,json=validatorDstAddress,proto3" json:"validator_dst_address,omitempty" yaml:"validator_dst_address"`
Entries []RedelegationEntry `protobuf:"bytes,4,rep,name=entries,proto3" json:"entries"`
// delegator_address is the bech32-encoded address of the delegator.
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
// validator_src_address is the validator redelegation source operator address.
ValidatorSrcAddress string `protobuf:"bytes,2,opt,name=validator_src_address,json=validatorSrcAddress,proto3" json:"validator_src_address,omitempty" yaml:"validator_src_address"`
// validator_dst_address is the validator redelegation destination operator address.
ValidatorDstAddress string `protobuf:"bytes,3,opt,name=validator_dst_address,json=validatorDstAddress,proto3" json:"validator_dst_address,omitempty" yaml:"validator_dst_address"`
// entries are the redelegation entries.
Entries []RedelegationEntry `protobuf:"bytes,4,rep,name=entries,proto3" json:"entries"`
}
func (m *Redelegation) Reset() { *m = Redelegation{} }
@ -795,11 +834,16 @@ var xxx_messageInfo_Redelegation proto.InternalMessageInfo
// Params defines the parameters for the staking module.
type Params struct {
UnbondingTime time.Duration `protobuf:"bytes,1,opt,name=unbonding_time,json=unbondingTime,proto3,stdduration" json:"unbonding_time" yaml:"unbonding_time"`
MaxValidators uint32 `protobuf:"varint,2,opt,name=max_validators,json=maxValidators,proto3" json:"max_validators,omitempty" yaml:"max_validators"`
MaxEntries uint32 `protobuf:"varint,3,opt,name=max_entries,json=maxEntries,proto3" json:"max_entries,omitempty" yaml:"max_entries"`
HistoricalEntries uint32 `protobuf:"varint,4,opt,name=historical_entries,json=historicalEntries,proto3" json:"historical_entries,omitempty" yaml:"historical_entries"`
BondDenom string `protobuf:"bytes,5,opt,name=bond_denom,json=bondDenom,proto3" json:"bond_denom,omitempty" yaml:"bond_denom"`
// unbonding_time is the time duration of unbonding.
UnbondingTime time.Duration `protobuf:"bytes,1,opt,name=unbonding_time,json=unbondingTime,proto3,stdduration" json:"unbonding_time" yaml:"unbonding_time"`
// max_validators is the maximum number of validators.
MaxValidators uint32 `protobuf:"varint,2,opt,name=max_validators,json=maxValidators,proto3" json:"max_validators,omitempty" yaml:"max_validators"`
// max_entries is the max entries for either unbonding delegation or redelegation (per pair/trio).
MaxEntries uint32 `protobuf:"varint,3,opt,name=max_entries,json=maxEntries,proto3" json:"max_entries,omitempty" yaml:"max_entries"`
// historical_entries is the number of historical entries to persist.
HistoricalEntries uint32 `protobuf:"varint,4,opt,name=historical_entries,json=historicalEntries,proto3" json:"historical_entries,omitempty" yaml:"historical_entries"`
// bond_denom defines the bondable coin denomination.
BondDenom string `protobuf:"bytes,5,opt,name=bond_denom,json=bondDenom,proto3" json:"bond_denom,omitempty" yaml:"bond_denom"`
}
func (m *Params) Reset() { *m = Params{} }