Merge branch 'master' into aaronc/6513-textual-json-proto
This commit is contained in:
commit
a58bb49d00
|
@ -36,17 +36,25 @@ Ref: https://keepachangelog.com/en/1.0.0/
|
||||||
|
|
||||||
## [Unreleased]
|
## [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
|
### 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.
|
* (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
|
### 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.
|
* (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
|
* [\#8396](https://github.com/cosmos/cosmos-sdk/pull/8396) Add support for ARM platform
|
||||||
|
|
||||||
### Bug Fixes
|
### 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
|
* (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
|
* (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
|
* (x/bank) [\#8417](https://github.com/cosmos/cosmos-sdk/pull/8417) Validate balances and coin denom metadata on genesis
|
||||||
|
|
|
@ -221,6 +221,19 @@ func readTxCommandFlags(clientCtx Context, flagSet *pflag.FlagSet) (Context, err
|
||||||
clientCtx = clientCtx.WithSignModeStr(signModeStr)
|
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) {
|
if clientCtx.From == "" || flagSet.Changed(flags.FlagFrom) {
|
||||||
from, _ := flagSet.GetString(flags.FlagFrom)
|
from, _ := flagSet.GetString(flags.FlagFrom)
|
||||||
fromAddr, fromName, keyType, err := GetFromFields(clientCtx.Keyring, from, clientCtx.GenerateOnly)
|
fromAddr, fromName, keyType, err := GetFromFields(clientCtx.Keyring, from, clientCtx.GenerateOnly)
|
||||||
|
|
|
@ -44,6 +44,7 @@ type Context struct {
|
||||||
TxConfig TxConfig
|
TxConfig TxConfig
|
||||||
AccountRetriever AccountRetriever
|
AccountRetriever AccountRetriever
|
||||||
NodeURI string
|
NodeURI string
|
||||||
|
FeeGranter sdk.AccAddress
|
||||||
|
|
||||||
// TODO: Deprecated (remove).
|
// TODO: Deprecated (remove).
|
||||||
LegacyAmino *codec.LegacyAmino
|
LegacyAmino *codec.LegacyAmino
|
||||||
|
@ -166,6 +167,13 @@ func (ctx Context) WithFromAddress(addr sdk.AccAddress) Context {
|
||||||
return ctx
|
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
|
// WithBroadcastMode returns a copy of the context with an updated broadcast
|
||||||
// mode.
|
// mode.
|
||||||
func (ctx Context) WithBroadcastMode(mode string) Context {
|
func (ctx Context) WithBroadcastMode(mode string) Context {
|
||||||
|
|
|
@ -70,6 +70,7 @@ const (
|
||||||
FlagCountTotal = "count-total"
|
FlagCountTotal = "count-total"
|
||||||
FlagTimeoutHeight = "timeout-height"
|
FlagTimeoutHeight = "timeout-height"
|
||||||
FlagKeyAlgorithm = "algo"
|
FlagKeyAlgorithm = "algo"
|
||||||
|
FlagFeeAccount = "fee-account"
|
||||||
|
|
||||||
// Tendermint logging flags
|
// Tendermint logging flags
|
||||||
FlagLogLevel = "log_level"
|
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(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().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().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"
|
// --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))
|
cmd.Flags().String(FlagGas, "", fmt.Sprintf("gas limit to set per-transaction; set to %q to calculate sufficient gas automatically (default %d)", GasFlagAuto, DefaultGasLimit))
|
||||||
|
|
|
@ -57,6 +57,11 @@ func (ctx Context) GetFromAddress() sdk.AccAddress {
|
||||||
return ctx.FromAddress
|
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.
|
// GetFromName returns the key name for the current context.
|
||||||
func (ctx Context) GetFromName() string {
|
func (ctx Context) GetFromName() string {
|
||||||
return ctx.FromName
|
return ctx.FromName
|
||||||
|
|
|
@ -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)
|
err = Sign(txf, clientCtx.GetFromName(), tx, true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -42,5 +42,6 @@ type (
|
||||||
SetFeeAmount(amount sdk.Coins)
|
SetFeeAmount(amount sdk.Coins)
|
||||||
SetGasLimit(limit uint64)
|
SetGasLimit(limit uint64)
|
||||||
SetTimeoutHeight(height uint64)
|
SetTimeoutHeight(height uint64)
|
||||||
|
SetFeeGranter(feeGranter sdk.AccAddress)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
"account_identifier": {
|
"account_identifier": {
|
||||||
"address":"cosmos1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjjqfl87e"
|
"address":"cosmos158nkd0l9tyemv2crp579rmj8dg37qty8lzff88"
|
||||||
},
|
},
|
||||||
"currency":{
|
"currency":{
|
||||||
"symbol":"stake",
|
"symbol":"stake",
|
||||||
"decimals":0
|
"decimals":0
|
||||||
},
|
},
|
||||||
"value": "999900000000"
|
"value": "999990000000"
|
||||||
}
|
}
|
||||||
]
|
]
|
|
@ -16,12 +16,13 @@ simd init simd --chain-id testing
|
||||||
simd keys add fd --keyring-backend=test
|
simd keys add fd --keyring-backend=test
|
||||||
|
|
||||||
addr=$(simd keys show fd -a --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
|
# give the accounts some money
|
||||||
simd add-genesis-account "$addr" 1000000000000stake --keyring-backend=test
|
simd add-genesis-account "$addr" 1000000000000stake --keyring-backend=test
|
||||||
|
|
||||||
# save configs for the daemon
|
# 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
|
# input genTx to the genesis file
|
||||||
simd collect-gentxs
|
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
|
tar -czvf /tmp/data.tar.gz /root/.simapp
|
||||||
|
|
||||||
echo new address for bootstrap.json "$addr"
|
echo new address for bootstrap.json "$addr" "$val_addr"
|
||||||
|
|
|
@ -94,7 +94,7 @@ staking(1){
|
||||||
"account": {
|
"account": {
|
||||||
"address": "staking_account",
|
"address": "staking_account",
|
||||||
"sub_account": {
|
"sub_account": {
|
||||||
"address" : "cosmosvaloper1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjj9atjj2"
|
"address" : "cosmosvaloper158nkd0l9tyemv2crp579rmj8dg37qty86kaut5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"amount":{
|
"amount":{
|
||||||
|
@ -134,7 +134,7 @@ staking(1){
|
||||||
"account": {
|
"account": {
|
||||||
"address": "staking_account",
|
"address": "staking_account",
|
||||||
"sub_account": {
|
"sub_account": {
|
||||||
"address" : "cosmosvaloper1hdmjfmqmf8ck4pv4evu0s3up0ucm0yjj9atjj2"
|
"address" : "cosmosvaloper158nkd0l9tyemv2crp579rmj8dg37qty86kaut5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"amount":{
|
"amount":{
|
||||||
|
|
Binary file not shown.
|
@ -264,6 +264,32 @@
|
||||||
|
|
||||||
- [Msg](#cosmos.evidence.v1beta1.Msg)
|
- [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)
|
- [cosmos/genutil/v1beta1/genesis.proto](#cosmos/genutil/v1beta1/genesis.proto)
|
||||||
- [GenesisState](#cosmos.genutil.v1beta1.GenesisState)
|
- [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>
|
<a name="cosmos/genutil/v1beta1/genesis.proto"></a>
|
||||||
<p align="right"><a href="#top">Top</a></p>
|
<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 |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `commission_rates` | [CommissionRates](#cosmos.staking.v1beta1.CommissionRates) | | |
|
| `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` | [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 |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `rate` | [string](#string) | | |
|
| `rate` | [string](#string) | | rate is the commission rate charged to delegators, as a fraction. |
|
||||||
| `max_rate` | [string](#string) | | |
|
| `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` | [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 |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `delegator_address` | [string](#string) | | |
|
| `delegator_address` | [string](#string) | | delegator_address is the bech32-encoded address of the delegator. |
|
||||||
| `validator_address` | [string](#string) | | |
|
| `validator_address` | [string](#string) | | validator_address is the bech32-encoded address of the validator. |
|
||||||
| `shares` | [string](#string) | | |
|
| `shares` | [string](#string) | | shares define the delegation shares received. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -5513,11 +5845,11 @@ Description defines a validator description.
|
||||||
|
|
||||||
| Field | Type | Label | Description |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `moniker` | [string](#string) | | |
|
| `moniker` | [string](#string) | | moniker defines a human-readable name for the validator. |
|
||||||
| `identity` | [string](#string) | | |
|
| `identity` | [string](#string) | | identity defines an optional identity signature (ex. UPort or Keybase). |
|
||||||
| `website` | [string](#string) | | |
|
| `website` | [string](#string) | | website defines an optional website link. |
|
||||||
| `security_contact` | [string](#string) | | |
|
| `security_contact` | [string](#string) | | security_contact defines an optional email for security contact. |
|
||||||
| `details` | [string](#string) | | |
|
| `details` | [string](#string) | | details define other optional details. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -5551,11 +5883,11 @@ Params defines the parameters for the staking module.
|
||||||
|
|
||||||
| Field | Type | Label | Description |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `unbonding_time` | [google.protobuf.Duration](#google.protobuf.Duration) | | |
|
| `unbonding_time` | [google.protobuf.Duration](#google.protobuf.Duration) | | unbonding_time is the time duration of unbonding. |
|
||||||
| `max_validators` | [uint32](#uint32) | | |
|
| `max_validators` | [uint32](#uint32) | | max_validators is the maximum number of validators. |
|
||||||
| `max_entries` | [uint32](#uint32) | | |
|
| `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` | [uint32](#uint32) | | historical_entries is the number of historical entries to persist. |
|
||||||
| `bond_denom` | [string](#string) | | |
|
| `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 |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `delegator_address` | [string](#string) | | |
|
| `delegator_address` | [string](#string) | | delegator_address is the bech32-encoded address of the delegator. |
|
||||||
| `validator_src_address` | [string](#string) | | |
|
| `validator_src_address` | [string](#string) | | validator_src_address is the validator redelegation source operator address. |
|
||||||
| `validator_dst_address` | [string](#string) | | |
|
| `validator_dst_address` | [string](#string) | | validator_dst_address is the validator redelegation destination operator address. |
|
||||||
| `entries` | [RedelegationEntry](#cosmos.staking.v1beta1.RedelegationEntry) | repeated | redelegation entries |
|
| `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 |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `creation_height` | [int64](#int64) | | |
|
| `creation_height` | [int64](#int64) | | creation_height defines the height which the redelegation took place. |
|
||||||
| `completion_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
|
| `completion_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | completion_time defines the unix time for redelegation completion. |
|
||||||
| `initial_balance` | [string](#string) | | |
|
| `initial_balance` | [string](#string) | | initial_balance defines the initial balance when redelegation started. |
|
||||||
| `shares_dst` | [string](#string) | | |
|
| `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 |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `delegator_address` | [string](#string) | | |
|
| `delegator_address` | [string](#string) | | delegator_address is the bech32-encoded address of the delegator. |
|
||||||
| `validator_address` | [string](#string) | | |
|
| `validator_address` | [string](#string) | | validator_address is the bech32-encoded address of the validator. |
|
||||||
| `entries` | [UnbondingDelegationEntry](#cosmos.staking.v1beta1.UnbondingDelegationEntry) | repeated | unbonding delegation entries |
|
| `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 |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `creation_height` | [int64](#int64) | | |
|
| `creation_height` | [int64](#int64) | | creation_height is the height which the unbonding took place. |
|
||||||
| `completion_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | |
|
| `completion_time` | [google.protobuf.Timestamp](#google.protobuf.Timestamp) | | completion_time is the unix time for unbonding completion. |
|
||||||
| `initial_balance` | [string](#string) | | |
|
| `initial_balance` | [string](#string) | | initial_balance defines the tokens initially scheduled to receive at completion. |
|
||||||
| `balance` | [string](#string) | | |
|
| `balance` | [string](#string) | | balance defines the tokens to receive at completion. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -5718,17 +6054,17 @@ multiplied by exchange rate.
|
||||||
|
|
||||||
| Field | Type | Label | Description |
|
| Field | Type | Label | Description |
|
||||||
| ----- | ---- | ----- | ----------- |
|
| ----- | ---- | ----- | ----------- |
|
||||||
| `operator_address` | [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` | [google.protobuf.Any](#google.protobuf.Any) | | consensus_pubkey is the consensus public key of the validator, as a Protobuf Any. |
|
||||||
| `jailed` | [bool](#bool) | | |
|
| `jailed` | [bool](#bool) | | jailed defined whether the validator has been jailed from bonded status or not. |
|
||||||
| `status` | [BondStatus](#cosmos.staking.v1beta1.BondStatus) | | |
|
| `status` | [BondStatus](#cosmos.staking.v1beta1.BondStatus) | | status is the validator status (bonded/unbonding/unbonded). |
|
||||||
| `tokens` | [string](#string) | | |
|
| `tokens` | [string](#string) | | tokens define the delegated tokens (incl. self-delegation). |
|
||||||
| `delegator_shares` | [string](#string) | | |
|
| `delegator_shares` | [string](#string) | | delegator_shares defines total shares issued to a validator's delegators. |
|
||||||
| `description` | [Description](#cosmos.staking.v1beta1.Description) | | |
|
| `description` | [Description](#cosmos.staking.v1beta1.Description) | | description defines the description terms for the validator. |
|
||||||
| `unbonding_height` | [int64](#int64) | | |
|
| `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` | [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` | [Commission](#cosmos.staking.v1beta1.Commission) | | commission defines the commission parameters. |
|
||||||
| `min_self_delegation` | [string](#string) | | |
|
| `min_self_delegation` | [string](#string) | | min_self_delegation is the validator's self declared minimum self delegation. |
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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"];
|
||||||
|
}
|
|
@ -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];
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
|
@ -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 {}
|
|
@ -27,12 +27,15 @@ message CommissionRates {
|
||||||
option (gogoproto.equal) = true;
|
option (gogoproto.equal) = true;
|
||||||
option (gogoproto.goproto_stringer) = false;
|
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];
|
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 [
|
string max_rate = 2 [
|
||||||
(gogoproto.moretags) = "yaml:\"max_rate\"",
|
(gogoproto.moretags) = "yaml:\"max_rate\"",
|
||||||
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
(gogoproto.nullable) = false
|
(gogoproto.nullable) = false
|
||||||
];
|
];
|
||||||
|
// max_change_rate defines the maximum daily increase of the validator commission, as a fraction.
|
||||||
string max_change_rate = 3 [
|
string max_change_rate = 3 [
|
||||||
(gogoproto.moretags) = "yaml:\"max_change_rate\"",
|
(gogoproto.moretags) = "yaml:\"max_change_rate\"",
|
||||||
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
|
@ -45,7 +48,9 @@ message Commission {
|
||||||
option (gogoproto.equal) = true;
|
option (gogoproto.equal) = true;
|
||||||
option (gogoproto.goproto_stringer) = false;
|
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];
|
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
|
google.protobuf.Timestamp update_time = 2
|
||||||
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"update_time\""];
|
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"update_time\""];
|
||||||
}
|
}
|
||||||
|
@ -55,10 +60,15 @@ message Description {
|
||||||
option (gogoproto.equal) = true;
|
option (gogoproto.equal) = true;
|
||||||
option (gogoproto.goproto_stringer) = false;
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
// moniker defines a human-readable name for the validator.
|
||||||
string moniker = 1;
|
string moniker = 1;
|
||||||
|
// identity defines an optional identity signature (ex. UPort or Keybase).
|
||||||
string identity = 2;
|
string identity = 2;
|
||||||
|
// website defines an optional website link.
|
||||||
string website = 3;
|
string website = 3;
|
||||||
|
// security_contact defines an optional email for security contact.
|
||||||
string security_contact = 4 [(gogoproto.moretags) = "yaml:\"security_contact\""];
|
string security_contact = 4 [(gogoproto.moretags) = "yaml:\"security_contact\""];
|
||||||
|
// details define other optional details.
|
||||||
string details = 5;
|
string details = 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -75,22 +85,33 @@ message Validator {
|
||||||
option (gogoproto.goproto_stringer) = false;
|
option (gogoproto.goproto_stringer) = false;
|
||||||
option (gogoproto.goproto_getters) = 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\""];
|
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
|
google.protobuf.Any consensus_pubkey = 2
|
||||||
[(cosmos_proto.accepts_interface) = "cosmos.crypto.PubKey", (gogoproto.moretags) = "yaml:\"consensus_pubkey\""];
|
[(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;
|
bool jailed = 3;
|
||||||
|
// status is the validator status (bonded/unbonding/unbonded).
|
||||||
BondStatus status = 4;
|
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];
|
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 [
|
string delegator_shares = 6 [
|
||||||
(gogoproto.moretags) = "yaml:\"delegator_shares\"",
|
(gogoproto.moretags) = "yaml:\"delegator_shares\"",
|
||||||
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
|
||||||
(gogoproto.nullable) = false
|
(gogoproto.nullable) = false
|
||||||
];
|
];
|
||||||
|
// description defines the description terms for the validator.
|
||||||
Description description = 7 [(gogoproto.nullable) = false];
|
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\""];
|
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
|
google.protobuf.Timestamp unbonding_time = 9
|
||||||
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"unbonding_time\""];
|
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"unbonding_time\""];
|
||||||
|
// commission defines the commission parameters.
|
||||||
Commission commission = 10 [(gogoproto.nullable) = false];
|
Commission commission = 10 [(gogoproto.nullable) = false];
|
||||||
|
// min_self_delegation is the validator's self declared minimum self delegation.
|
||||||
string min_self_delegation = 11 [
|
string min_self_delegation = 11 [
|
||||||
(gogoproto.moretags) = "yaml:\"min_self_delegation\"",
|
(gogoproto.moretags) = "yaml:\"min_self_delegation\"",
|
||||||
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||||
|
@ -164,8 +185,11 @@ message Delegation {
|
||||||
option (gogoproto.goproto_getters) = false;
|
option (gogoproto.goproto_getters) = false;
|
||||||
option (gogoproto.goproto_stringer) = 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\""];
|
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\""];
|
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];
|
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_getters) = false;
|
||||||
option (gogoproto.goproto_stringer) = 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\""];
|
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\""];
|
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
|
repeated UnbondingDelegationEntry entries = 3 [(gogoproto.nullable) = false]; // unbonding delegation entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -186,14 +213,18 @@ message UnbondingDelegationEntry {
|
||||||
option (gogoproto.equal) = true;
|
option (gogoproto.equal) = true;
|
||||||
option (gogoproto.goproto_stringer) = false;
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
// creation_height is the height which the unbonding took place.
|
||||||
int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""];
|
int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""];
|
||||||
|
// completion_time is the unix time for unbonding completion.
|
||||||
google.protobuf.Timestamp completion_time = 2
|
google.protobuf.Timestamp completion_time = 2
|
||||||
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"completion_time\""];
|
[(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 [
|
string initial_balance = 3 [
|
||||||
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||||
(gogoproto.nullable) = false,
|
(gogoproto.nullable) = false,
|
||||||
(gogoproto.moretags) = "yaml:\"initial_balance\""
|
(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];
|
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.equal) = true;
|
||||||
option (gogoproto.goproto_stringer) = false;
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
// creation_height defines the height which the redelegation took place.
|
||||||
int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""];
|
int64 creation_height = 1 [(gogoproto.moretags) = "yaml:\"creation_height\""];
|
||||||
|
// completion_time defines the unix time for redelegation completion.
|
||||||
google.protobuf.Timestamp completion_time = 2
|
google.protobuf.Timestamp completion_time = 2
|
||||||
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"completion_time\""];
|
[(gogoproto.nullable) = false, (gogoproto.stdtime) = true, (gogoproto.moretags) = "yaml:\"completion_time\""];
|
||||||
|
// initial_balance defines the initial balance when redelegation started.
|
||||||
string initial_balance = 3 [
|
string initial_balance = 3 [
|
||||||
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
|
||||||
(gogoproto.nullable) = false,
|
(gogoproto.nullable) = false,
|
||||||
(gogoproto.moretags) = "yaml:\"initial_balance\""
|
(gogoproto.moretags) = "yaml:\"initial_balance\""
|
||||||
];
|
];
|
||||||
|
// shares_dst is the amount of destination-validator shares created by redelegation.
|
||||||
string shares_dst = 4
|
string shares_dst = 4
|
||||||
[(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false];
|
[(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_getters) = false;
|
||||||
option (gogoproto.goproto_stringer) = 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\""];
|
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\""];
|
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\""];
|
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
|
repeated RedelegationEntry entries = 4 [(gogoproto.nullable) = false]; // redelegation entries
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -232,11 +271,16 @@ message Params {
|
||||||
option (gogoproto.equal) = true;
|
option (gogoproto.equal) = true;
|
||||||
option (gogoproto.goproto_stringer) = false;
|
option (gogoproto.goproto_stringer) = false;
|
||||||
|
|
||||||
|
// unbonding_time is the time duration of unbonding.
|
||||||
google.protobuf.Duration unbonding_time = 1
|
google.protobuf.Duration unbonding_time = 1
|
||||||
[(gogoproto.nullable) = false, (gogoproto.stdduration) = true, (gogoproto.moretags) = "yaml:\"unbonding_time\""];
|
[(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\""];
|
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\""];
|
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\""];
|
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\""];
|
string bond_denom = 5 [(gogoproto.moretags) = "yaml:\"bond_denom\""];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,10 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/x/evidence"
|
"github.com/cosmos/cosmos-sdk/x/evidence"
|
||||||
evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper"
|
evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper"
|
||||||
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
|
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"
|
"github.com/cosmos/cosmos-sdk/x/genutil"
|
||||||
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
|
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/gov"
|
"github.com/cosmos/cosmos-sdk/x/gov"
|
||||||
|
@ -120,6 +124,7 @@ var (
|
||||||
crisis.AppModuleBasic{},
|
crisis.AppModuleBasic{},
|
||||||
slashing.AppModuleBasic{},
|
slashing.AppModuleBasic{},
|
||||||
ibc.AppModuleBasic{},
|
ibc.AppModuleBasic{},
|
||||||
|
feegrant.AppModuleBasic{},
|
||||||
upgrade.AppModuleBasic{},
|
upgrade.AppModuleBasic{},
|
||||||
evidence.AppModuleBasic{},
|
evidence.AppModuleBasic{},
|
||||||
transfer.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
|
IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly
|
||||||
EvidenceKeeper evidencekeeper.Keeper
|
EvidenceKeeper evidencekeeper.Keeper
|
||||||
TransferKeeper ibctransferkeeper.Keeper
|
TransferKeeper ibctransferkeeper.Keeper
|
||||||
|
FeeGrantKeeper feegrantkeeper.Keeper
|
||||||
|
|
||||||
// make scoped keepers public for test purposes
|
// make scoped keepers public for test purposes
|
||||||
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
|
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
|
||||||
|
@ -222,7 +228,7 @@ func NewSimApp(
|
||||||
keys := sdk.NewKVStoreKeys(
|
keys := sdk.NewKVStoreKeys(
|
||||||
authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey,
|
authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey,
|
||||||
minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.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,
|
evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey,
|
||||||
authztypes.StoreKey,
|
authztypes.StoreKey,
|
||||||
)
|
)
|
||||||
|
@ -277,6 +283,8 @@ func NewSimApp(
|
||||||
app.CrisisKeeper = crisiskeeper.NewKeeper(
|
app.CrisisKeeper = crisiskeeper.NewKeeper(
|
||||||
app.GetSubspace(crisistypes.ModuleName), invCheckPeriod, app.BankKeeper, authtypes.FeeCollectorName,
|
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)
|
app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, keys[upgradetypes.StoreKey], appCodec, homePath)
|
||||||
|
|
||||||
// register the staking hooks
|
// register the staking hooks
|
||||||
|
@ -347,6 +355,7 @@ func NewSimApp(
|
||||||
bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
|
bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
|
||||||
capability.NewAppModule(appCodec, *app.CapabilityKeeper),
|
capability.NewAppModule(appCodec, *app.CapabilityKeeper),
|
||||||
crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants),
|
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),
|
gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper),
|
||||||
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
|
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
|
||||||
slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
|
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,
|
capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName,
|
||||||
slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName,
|
slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName,
|
||||||
ibchost.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authztypes.ModuleName, ibctransfertypes.ModuleName,
|
ibchost.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authztypes.ModuleName, ibctransfertypes.ModuleName,
|
||||||
|
feegranttypes.ModuleName,
|
||||||
)
|
)
|
||||||
|
|
||||||
app.mm.RegisterInvariants(&app.CrisisKeeper)
|
app.mm.RegisterInvariants(&app.CrisisKeeper)
|
||||||
|
@ -396,6 +406,7 @@ func NewSimApp(
|
||||||
auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts),
|
auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts),
|
||||||
bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
|
bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
|
||||||
capability.NewAppModule(appCodec, *app.CapabilityKeeper),
|
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),
|
gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper),
|
||||||
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
|
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
|
||||||
staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper),
|
staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper),
|
||||||
|
@ -419,8 +430,8 @@ func NewSimApp(
|
||||||
app.SetInitChainer(app.InitChainer)
|
app.SetInitChainer(app.InitChainer)
|
||||||
app.SetBeginBlocker(app.BeginBlocker)
|
app.SetBeginBlocker(app.BeginBlocker)
|
||||||
app.SetAnteHandler(
|
app.SetAnteHandler(
|
||||||
ante.NewAnteHandler(
|
feegrantante.NewAnteHandler(
|
||||||
app.AccountKeeper, app.BankKeeper, ante.DefaultSigVerificationGasConsumer,
|
app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, ante.DefaultSigVerificationGasConsumer,
|
||||||
encodingConfig.TxConfig.SignModeHandler(),
|
encodingConfig.TxConfig.SignModeHandler(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
@ -157,7 +157,7 @@ func (app *SimApp) prepForZeroHeightGenesis(ctx sdk.Context, jailAllowedAddrs []
|
||||||
counter := int16(0)
|
counter := int16(0)
|
||||||
|
|
||||||
for ; iter.Valid(); iter.Next() {
|
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)
|
validator, found := app.StakingKeeper.GetValidator(ctx, addr)
|
||||||
if !found {
|
if !found {
|
||||||
panic("expected validator, not found")
|
panic("expected validator, not found")
|
||||||
|
|
|
@ -20,4 +20,8 @@ const (
|
||||||
DefaultWeightCommunitySpendProposal int = 5
|
DefaultWeightCommunitySpendProposal int = 5
|
||||||
DefaultWeightTextProposal int = 5
|
DefaultWeightTextProposal int = 5
|
||||||
DefaultWeightParamChangeProposal int = 5
|
DefaultWeightParamChangeProposal int = 5
|
||||||
|
|
||||||
|
// feegrant
|
||||||
|
DefaultWeightGrantFeeAllowance int = 100
|
||||||
|
DefaultWeightRevokeFeeAllowance int = 100
|
||||||
)
|
)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"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.
|
// 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.
|
// The snapshot will complete when the returned closer is called.
|
||||||
func setupBusyManager(t *testing.T) *snapshots.Manager {
|
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)
|
store, err := snapshots.NewStore(db.NewMemDB(), tempdir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
hung := newHungSnapshotter()
|
hung := newHungSnapshotter()
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
@ -19,7 +20,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func setupStore(t *testing.T) *snapshots.Store {
|
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)
|
store, err := snapshots.NewStore(db.NewMemDB(), tempdir)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
|
|
@ -711,7 +711,7 @@ func (rs *Store) Restore(
|
||||||
if height == 0 {
|
if height == 0 {
|
||||||
return sdkerrors.Wrap(sdkerrors.ErrLogic, "cannot restore snapshot at 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,
|
return sdkerrors.Wrapf(snapshottypes.ErrInvalidMetadata,
|
||||||
"snapshot height %v cannot exceed %v", height, int64(math.MaxInt64))
|
"snapshot height %v cannot exceed %v", height, int64(math.MaxInt64))
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,9 @@ import (
|
||||||
|
|
||||||
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
"github.com/cosmos/cosmos-sdk/codec/legacy"
|
||||||
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
"github.com/cosmos/cosmos-sdk/types/bech32"
|
"github.com/cosmos/cosmos-sdk/types/bech32"
|
||||||
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -28,8 +30,6 @@ const (
|
||||||
// config.SetFullFundraiserPath(yourFullFundraiserPath)
|
// config.SetFullFundraiserPath(yourFullFundraiserPath)
|
||||||
// config.Seal()
|
// config.Seal()
|
||||||
|
|
||||||
// AddrLen defines a valid address length
|
|
||||||
AddrLen = 20
|
|
||||||
// Bech32MainPrefix defines the main SDK Bech32 prefix of an account's address
|
// Bech32MainPrefix defines the main SDK Bech32 prefix of an account's address
|
||||||
Bech32MainPrefix = "cosmos"
|
Bech32MainPrefix = "cosmos"
|
||||||
|
|
||||||
|
@ -110,9 +110,15 @@ func VerifyAddressFormat(bz []byte) error {
|
||||||
if verifier != nil {
|
if verifier != nil {
|
||||||
return verifier(bz)
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -347,15 +347,18 @@ func (s *addressTestSuite) TestVerifyAddressFormat() {
|
||||||
addr5 := make([]byte, 5)
|
addr5 := make([]byte, 5)
|
||||||
addr20 := make([]byte, 20)
|
addr20 := make([]byte, 20)
|
||||||
addr32 := make([]byte, 32)
|
addr32 := make([]byte, 32)
|
||||||
|
addr256 := make([]byte, 256)
|
||||||
|
|
||||||
err := types.VerifyAddressFormat(addr0)
|
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)
|
err = types.VerifyAddressFormat(addr5)
|
||||||
s.Require().EqualError(err, "incorrect address length 5")
|
s.Require().NoError(err)
|
||||||
err = types.VerifyAddressFormat(addr20)
|
err = types.VerifyAddressFormat(addr20)
|
||||||
s.Require().Nil(err)
|
s.Require().NoError(err)
|
||||||
err = types.VerifyAddressFormat(addr32)
|
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() {
|
func (s *addressTestSuite) TestCustomAddressVerifier() {
|
||||||
|
@ -364,34 +367,39 @@ func (s *addressTestSuite) TestCustomAddressVerifier() {
|
||||||
accBech := types.AccAddress(addr).String()
|
accBech := types.AccAddress(addr).String()
|
||||||
valBech := types.ValAddress(addr).String()
|
valBech := types.ValAddress(addr).String()
|
||||||
consBech := types.ConsAddress(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)
|
err := types.VerifyAddressFormat(addr)
|
||||||
s.Require().NotNil(err)
|
s.Require().Nil(err)
|
||||||
_, err = types.AccAddressFromBech32(accBech)
|
_, err = types.AccAddressFromBech32(accBech)
|
||||||
s.Require().NotNil(err)
|
s.Require().Nil(err)
|
||||||
_, err = types.ValAddressFromBech32(valBech)
|
_, err = types.ValAddressFromBech32(valBech)
|
||||||
s.Require().NotNil(err)
|
s.Require().Nil(err)
|
||||||
_, err = types.ConsAddressFromBech32(consBech)
|
_, 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 {
|
types.GetConfig().SetAddressVerifier(func(bz []byte) error {
|
||||||
n := len(bz)
|
n := len(bz)
|
||||||
if n == 10 || n == types.AddrLen {
|
if n == 20 {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return fmt.Errorf("incorrect address length %d", n)
|
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)
|
err = types.VerifyAddressFormat(addr)
|
||||||
s.Require().Nil(err)
|
s.Require().NotNil(err)
|
||||||
_, err = types.AccAddressFromBech32(accBech)
|
_, err = types.AccAddressFromBech32(accBech)
|
||||||
s.Require().Nil(err)
|
s.Require().NotNil(err)
|
||||||
_, err = types.ValAddressFromBech32(valBech)
|
_, err = types.ValAddressFromBech32(valBech)
|
||||||
s.Require().Nil(err)
|
s.Require().NotNil(err)
|
||||||
_, err = types.ConsAddressFromBech32(consBech)
|
_, 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() {
|
func (s *addressTestSuite) TestBech32ifyAddressBytes() {
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
"github.com/cosmos/cosmos-sdk/types/query"
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
||||||
|
@ -111,7 +112,7 @@ func ExampleFilteredPaginate() {
|
||||||
pageReq := &query.PageRequest{Key: nil, Limit: 1, CountTotal: true}
|
pageReq := &query.PageRequest{Key: nil, Limit: 1, CountTotal: true}
|
||||||
store := ctx.KVStore(app.GetKey(authtypes.StoreKey))
|
store := ctx.KVStore(app.GetKey(authtypes.StoreKey))
|
||||||
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
|
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
|
||||||
accountStore := prefix.NewStore(balancesStore, addr1.Bytes())
|
accountStore := prefix.NewStore(balancesStore, address.MustLengthPrefix(addr1))
|
||||||
|
|
||||||
var balResult sdk.Coins
|
var balResult sdk.Coins
|
||||||
pageRes, err := query.FilteredPaginate(accountStore, pageReq, func(key []byte, value []byte, accumulate bool) (bool, error) {
|
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) {
|
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)
|
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
|
||||||
accountStore := prefix.NewStore(balancesStore, addr1.Bytes())
|
accountStore := prefix.NewStore(balancesStore, address.MustLengthPrefix(addr1))
|
||||||
|
|
||||||
var balResult sdk.Coins
|
var balResult sdk.Coins
|
||||||
res, err = query.FilteredPaginate(accountStore, pageReq, func(key []byte, value []byte, accumulate bool) (bool, error) {
|
res, err = query.FilteredPaginate(accountStore, pageReq, func(key []byte, value []byte, accumulate bool) (bool, error) {
|
||||||
|
|
|
@ -17,6 +17,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/store"
|
"github.com/cosmos/cosmos-sdk/store"
|
||||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
"github.com/cosmos/cosmos-sdk/store/prefix"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
"github.com/cosmos/cosmos-sdk/types/query"
|
"github.com/cosmos/cosmos-sdk/types/query"
|
||||||
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
|
||||||
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
|
||||||
|
@ -193,7 +194,7 @@ func ExamplePaginate() {
|
||||||
balResult := sdk.NewCoins()
|
balResult := sdk.NewCoins()
|
||||||
authStore := ctx.KVStore(app.GetKey(authtypes.StoreKey))
|
authStore := ctx.KVStore(app.GetKey(authtypes.StoreKey))
|
||||||
balancesStore := prefix.NewStore(authStore, types.BalancesPrefix)
|
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 {
|
pageRes, err := query.Paginate(accountStore, request.Pagination, func(key []byte, value []byte) error {
|
||||||
var tempRes sdk.Coin
|
var tempRes sdk.Coin
|
||||||
err := app.AppCodec().UnmarshalBinaryBare(value, &tempRes)
|
err := app.AppCodec().UnmarshalBinaryBare(value, &tempRes)
|
||||||
|
|
|
@ -84,6 +84,9 @@ func (s *StdTxBuilder) SetTimeoutHeight(height uint64) {
|
||||||
s.TimeoutHeight = height
|
s.TimeoutHeight = height
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetFeeGranter does nothing for stdtx
|
||||||
|
func (s *StdTxBuilder) SetFeeGranter(_ sdk.AccAddress) {}
|
||||||
|
|
||||||
// StdTxConfig is a context.TxConfig for StdTx
|
// StdTxConfig is a context.TxConfig for StdTx
|
||||||
type StdTxConfig struct {
|
type StdTxConfig struct {
|
||||||
Cdc *codec.LegacyAmino
|
Cdc *codec.LegacyAmino
|
||||||
|
|
|
@ -2,6 +2,7 @@ package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -21,7 +22,7 @@ const (
|
||||||
// Keys for authz store
|
// Keys for authz store
|
||||||
// Items are stored with the following key: values
|
// 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 (
|
var (
|
||||||
// Keys for store prefixes
|
// Keys for store prefixes
|
||||||
|
@ -30,12 +31,22 @@ var (
|
||||||
|
|
||||||
// GetAuthorizationStoreKey - return authorization store key
|
// GetAuthorizationStoreKey - return authorization store key
|
||||||
func GetAuthorizationStoreKey(grantee sdk.AccAddress, granter sdk.AccAddress, msgType string) []byte {
|
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
|
// ExtractAddressesFromGrantKey - split granter & grantee address from the authorization key
|
||||||
func ExtractAddressesFromGrantKey(key []byte) (granterAddr, granteeAddr sdk.AccAddress) {
|
func ExtractAddressesFromGrantKey(key []byte) (granterAddr, granteeAddr sdk.AccAddress) {
|
||||||
granterAddr = sdk.AccAddress(key[1 : sdk.AddrLen+1])
|
// key if of format:
|
||||||
granteeAddr = sdk.AccAddress(key[sdk.AddrLen+1 : sdk.AddrLen*2+1])
|
// 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
|
return granterAddr, granteeAddr
|
||||||
}
|
}
|
||||||
|
|
|
@ -57,9 +57,7 @@ func (k BaseKeeper) AllBalances(ctx context.Context, req *types.QueryAllBalances
|
||||||
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
sdkCtx := sdk.UnwrapSDKContext(ctx)
|
||||||
|
|
||||||
balances := sdk.NewCoins()
|
balances := sdk.NewCoins()
|
||||||
store := sdkCtx.KVStore(k.storeKey)
|
accountStore := k.getAccountStore(sdkCtx, addr)
|
||||||
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
|
|
||||||
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
|
|
||||||
|
|
||||||
pageRes, err := query.Paginate(accountStore, req.Pagination, func(_, value []byte) error {
|
pageRes, err := query.Paginate(accountStore, req.Pagination, func(_, value []byte) error {
|
||||||
var result sdk.Coin
|
var result sdk.Coin
|
||||||
|
|
|
@ -2,7 +2,6 @@ package keeper
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/cosmos/cosmos-sdk/codec"
|
"github.com/cosmos/cosmos-sdk/codec"
|
||||||
"github.com/cosmos/cosmos-sdk/store/prefix"
|
|
||||||
"github.com/cosmos/cosmos-sdk/telemetry"
|
"github.com/cosmos/cosmos-sdk/telemetry"
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
|
||||||
|
@ -233,9 +232,7 @@ func (k BaseSendKeeper) ClearBalances(ctx sdk.Context, addr sdk.AccAddress) {
|
||||||
return false
|
return false
|
||||||
})
|
})
|
||||||
|
|
||||||
store := ctx.KVStore(k.storeKey)
|
accountStore := k.getAccountStore(ctx, addr)
|
||||||
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
|
|
||||||
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
|
|
||||||
|
|
||||||
for _, key := range keys {
|
for _, key := range keys {
|
||||||
accountStore.Delete(key)
|
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())
|
return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, balance.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
store := ctx.KVStore(k.storeKey)
|
accountStore := k.getAccountStore(ctx, addr)
|
||||||
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
|
|
||||||
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
|
|
||||||
|
|
||||||
bz := k.cdc.MustMarshalBinaryBare(&balance)
|
bz := k.cdc.MustMarshalBinaryBare(&balance)
|
||||||
accountStore.Set([]byte(balance.Denom), bz)
|
accountStore.Set([]byte(balance.Denom), bz)
|
||||||
|
|
|
@ -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
|
// GetBalance returns the balance of a specific denomination for a given account
|
||||||
// by address.
|
// by address.
|
||||||
func (k BaseViewKeeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin {
|
func (k BaseViewKeeper) GetBalance(ctx sdk.Context, addr sdk.AccAddress, denom string) sdk.Coin {
|
||||||
store := ctx.KVStore(k.storeKey)
|
accountStore := k.getAccountStore(ctx, addr)
|
||||||
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
|
|
||||||
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
|
|
||||||
|
|
||||||
bz := accountStore.Get([]byte(denom))
|
bz := accountStore.Get([]byte(denom))
|
||||||
if bz == nil {
|
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
|
// provides the token balance to a callback. If true is returned from the
|
||||||
// callback, iteration is halted.
|
// callback, iteration is halted.
|
||||||
func (k BaseViewKeeper) IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(sdk.Coin) bool) {
|
func (k BaseViewKeeper) IterateAccountBalances(ctx sdk.Context, addr sdk.AccAddress, cb func(sdk.Coin) bool) {
|
||||||
store := ctx.KVStore(k.storeKey)
|
accountStore := k.getAccountStore(ctx, addr)
|
||||||
balancesStore := prefix.NewStore(store, types.BalancesPrefix)
|
|
||||||
accountStore := prefix.NewStore(balancesStore, addr.Bytes())
|
|
||||||
|
|
||||||
iterator := accountStore.Iterator(nil, nil)
|
iterator := accountStore.Iterator(nil, nil)
|
||||||
defer iterator.Close()
|
defer iterator.Close()
|
||||||
|
@ -214,3 +210,10 @@ func (k BaseViewKeeper) ValidateBalance(ctx sdk.Context, addr sdk.AccAddress) er
|
||||||
|
|
||||||
return nil
|
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))
|
||||||
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -22,7 +21,9 @@ const (
|
||||||
|
|
||||||
// KVStore keys
|
// KVStore keys
|
||||||
var (
|
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}
|
SupplyKey = []byte{0x00}
|
||||||
DenomMetadataPrefix = []byte{0x1}
|
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
|
// store. The key must not contain the perfix BalancesPrefix as the prefix store
|
||||||
// iterator discards the actual prefix.
|
// iterator discards the actual prefix.
|
||||||
func AddressFromBalancesStore(key []byte) sdk.AccAddress {
|
func AddressFromBalancesStore(key []byte) sdk.AccAddress {
|
||||||
addr := key[:sdk.AddrLen]
|
addrLen := key[0]
|
||||||
if len(addr) != sdk.AddrLen {
|
addr := key[1 : addrLen+1]
|
||||||
panic(fmt.Sprintf("unexpected account address key length; got: %d, expected: %d", len(addr), sdk.AddrLen))
|
|
||||||
}
|
|
||||||
|
|
||||||
return sdk.AccAddress(addr)
|
return sdk.AccAddress(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CreateAccountBalancesPrefix creates the prefix for an account's balances.
|
||||||
|
func CreateAccountBalancesPrefix(addr []byte) []byte {
|
||||||
|
return append(BalancesPrefix, address.MustLengthPrefix(addr)...)
|
||||||
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
"github.com/cosmos/cosmos-sdk/x/bank/types"
|
"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) {
|
func TestAddressFromBalancesStore(t *testing.T) {
|
||||||
addr, err := sdk.AccAddressFromBech32("cosmos1n88uc38xhjgxzw9nwre4ep2c8ga4fjxcar6mn7")
|
addr, err := sdk.AccAddressFromBech32("cosmos1n88uc38xhjgxzw9nwre4ep2c8ga4fjxcar6mn7")
|
||||||
require.NoError(t, err)
|
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)
|
res := types.AddressFromBalancesStore(key)
|
||||||
require.Equal(t, res, addr)
|
require.Equal(t, res, addr)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,19 @@
|
||||||
package types
|
package types
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
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 {
|
func (m Metadata) Validate() error {
|
||||||
if err := sdk.ValidateDenom(m.Base); err != nil {
|
if err := sdk.ValidateDenom(m.Base); err != nil {
|
||||||
return fmt.Errorf("invalid metadata base denom: %w", err)
|
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)
|
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)
|
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] {
|
if seenUnits[denomUnit.Denom] {
|
||||||
return fmt.Errorf("duplicate denomination unit %s", denomUnit.Denom)
|
return fmt.Errorf("duplicate denomination unit %s", denomUnit.Denom)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if denomUnit.Denom == m.Display {
|
||||||
|
hasDisplay = true
|
||||||
|
}
|
||||||
|
|
||||||
if err := denomUnit.Validate(); err != nil {
|
if err := denomUnit.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -30,6 +61,10 @@ func (m Metadata) Validate() error {
|
||||||
seenUnits[denomUnit.Denom] = true
|
seenUnits[denomUnit.Denom] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !hasDisplay {
|
||||||
|
return fmt.Errorf("metadata must contain a denomination unit with display denom '%s'", m.Display)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ func TestMetadataValidate(t *testing.T) {
|
||||||
Description: "The native staking token of the Cosmos Hub.",
|
Description: "The native staking token of the Cosmos Hub.",
|
||||||
DenomUnits: []*types.DenomUnit{
|
DenomUnits: []*types.DenomUnit{
|
||||||
{"uatom", uint32(0), []string{"microatom"}},
|
{"uatom", uint32(0), []string{"microatom"}},
|
||||||
{"uatom", uint32(0), []string{"microatom"}},
|
{"uatom", uint32(1), []string{"microatom"}},
|
||||||
},
|
},
|
||||||
Base: "uatom",
|
Base: "uatom",
|
||||||
Display: "atom",
|
Display: "atom",
|
||||||
|
@ -94,6 +94,59 @@ func TestMetadataValidate(t *testing.T) {
|
||||||
},
|
},
|
||||||
true,
|
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 {
|
for _, tc := range testCases {
|
||||||
|
|
|
@ -23,7 +23,7 @@ func TestMsgSendValidation(t *testing.T) {
|
||||||
addr1 := sdk.AccAddress([]byte("from________________"))
|
addr1 := sdk.AccAddress([]byte("from________________"))
|
||||||
addr2 := sdk.AccAddress([]byte("to__________________"))
|
addr2 := sdk.AccAddress([]byte("to__________________"))
|
||||||
addrEmpty := sdk.AccAddress([]byte(""))
|
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))
|
atom123 := sdk.NewCoins(sdk.NewInt64Coin("atom", 123))
|
||||||
atom0 := sdk.NewCoins(sdk.NewInt64Coin("atom", 0))
|
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, atom123)}, // valid send
|
||||||
{"", NewMsgSend(addr1, addr2, atom123eth123)}, // valid send with multiple coins
|
{"", 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
|
{": invalid coins", NewMsgSend(addr1, addr2, atom0)}, // non positive coin
|
||||||
{"123atom,0eth: invalid coins", NewMsgSend(addr1, addr2, atom123eth0)}, // non positive coin in multicoins
|
{"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 (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 (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 {
|
for _, tc := range cases {
|
||||||
|
@ -91,7 +91,7 @@ func TestInputValidation(t *testing.T) {
|
||||||
addr1 := sdk.AccAddress([]byte("_______alice________"))
|
addr1 := sdk.AccAddress([]byte("_______alice________"))
|
||||||
addr2 := sdk.AccAddress([]byte("________bob_________"))
|
addr2 := sdk.AccAddress([]byte("________bob_________"))
|
||||||
addrEmpty := sdk.AccAddress([]byte(""))
|
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))
|
someCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123))
|
||||||
multiCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20))
|
multiCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20))
|
||||||
|
@ -109,9 +109,9 @@ func TestInputValidation(t *testing.T) {
|
||||||
{"", NewInput(addr1, someCoins)},
|
{"", NewInput(addr1, someCoins)},
|
||||||
{"", NewInput(addr2, someCoins)},
|
{"", NewInput(addr2, someCoins)},
|
||||||
{"", NewInput(addr2, multiCoins)},
|
{"", NewInput(addr2, multiCoins)},
|
||||||
|
{"", NewInput(addrLong, someCoins)},
|
||||||
|
|
||||||
{"empty address string is not allowed", NewInput(addrEmpty, 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, emptyCoins)}, // invalid coins
|
||||||
{": invalid coins", NewInput(addr1, emptyCoins2)}, // invalid coins
|
{": invalid coins", NewInput(addr1, emptyCoins2)}, // invalid coins
|
||||||
{"10eth,0atom: invalid coins", NewInput(addr1, someEmptyCoins)}, // 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________"))
|
addr1 := sdk.AccAddress([]byte("_______alice________"))
|
||||||
addr2 := sdk.AccAddress([]byte("________bob_________"))
|
addr2 := sdk.AccAddress([]byte("________bob_________"))
|
||||||
addrEmpty := sdk.AccAddress([]byte(""))
|
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))
|
someCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123))
|
||||||
multiCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20))
|
multiCoins := sdk.NewCoins(sdk.NewInt64Coin("atom", 123), sdk.NewInt64Coin("eth", 20))
|
||||||
|
@ -150,9 +150,9 @@ func TestOutputValidation(t *testing.T) {
|
||||||
{"", NewOutput(addr1, someCoins)},
|
{"", NewOutput(addr1, someCoins)},
|
||||||
{"", NewOutput(addr2, someCoins)},
|
{"", NewOutput(addr2, someCoins)},
|
||||||
{"", NewOutput(addr2, multiCoins)},
|
{"", NewOutput(addr2, multiCoins)},
|
||||||
|
{"", NewOutput(addrLong, someCoins)},
|
||||||
|
|
||||||
{"Invalid output address (empty address string is not allowed): invalid address", NewOutput(addrEmpty, 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, emptyCoins)}, // invalid coins
|
||||||
{": invalid coins", NewOutput(addr1, emptyCoins2)}, // invalid coins
|
{": invalid coins", NewOutput(addr1, emptyCoins2)}, // invalid coins
|
||||||
{"10eth,0atom: invalid coins", NewOutput(addr1, someEmptyCoins)}, // 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))
|
require.Equal(t, "[696E707574313131313131313131313131313131 696E707574323232323232323232323232323232 696E707574333333333333333333333333333333]", fmt.Sprintf("%v", res))
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
// what to do w/ this test?
|
|
||||||
func TestMsgSendSigners(t *testing.T) {
|
func TestMsgSendSigners(t *testing.T) {
|
||||||
signers := []sdk.AccAddress{
|
signers := []sdk.AccAddress{
|
||||||
{1, 2, 3},
|
{1, 2, 3},
|
||||||
|
@ -265,8 +263,7 @@ func TestMsgSendSigners(t *testing.T) {
|
||||||
for i, signer := range signers {
|
for i, signer := range signers {
|
||||||
inputs[i] = NewInput(signer, someCoins)
|
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())
|
||||||
}
|
}
|
||||||
*/
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -27,19 +28,19 @@ const (
|
||||||
//
|
//
|
||||||
// - 0x01: sdk.ConsAddress
|
// - 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 (
|
var (
|
||||||
FeePoolKey = []byte{0x00} // key for global distribution state
|
FeePoolKey = []byte{0x00} // key for global distribution state
|
||||||
ProposerKey = []byte{0x01} // key for the proposer operator address
|
ProposerKey = []byte{0x01} // key for the proposer operator address
|
||||||
|
@ -53,47 +54,56 @@ var (
|
||||||
ValidatorSlashEventPrefix = []byte{0x08} // key for validator slash fraction
|
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) {
|
func GetValidatorOutstandingRewardsAddress(key []byte) (valAddr sdk.ValAddress) {
|
||||||
addr := key[1:]
|
// key is in the format:
|
||||||
if len(addr) != sdk.AddrLen {
|
// 0x02<valAddrLen (1 Byte)><valAddr_Bytes>
|
||||||
|
|
||||||
|
// Remove prefix and address length.
|
||||||
|
addr := key[2:]
|
||||||
|
if len(addr) != int(key[1]) {
|
||||||
panic("unexpected key length")
|
panic("unexpected key length")
|
||||||
}
|
}
|
||||||
|
|
||||||
return sdk.ValAddress(addr)
|
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) {
|
func GetDelegatorWithdrawInfoAddress(key []byte) (delAddr sdk.AccAddress) {
|
||||||
addr := key[1:]
|
// key is in the format:
|
||||||
if len(addr) != sdk.AddrLen {
|
// 0x03<accAddrLen (1 Byte)><accAddr_Bytes>
|
||||||
|
|
||||||
|
// Remove prefix and address length.
|
||||||
|
addr := key[2:]
|
||||||
|
if len(addr) != int(key[1]) {
|
||||||
panic("unexpected key length")
|
panic("unexpected key length")
|
||||||
}
|
}
|
||||||
|
|
||||||
return sdk.AccAddress(addr)
|
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) {
|
func GetDelegatorStartingInfoAddresses(key []byte) (valAddr sdk.ValAddress, delAddr sdk.AccAddress) {
|
||||||
addr := key[1 : 1+sdk.AddrLen]
|
// key is in the format:
|
||||||
if len(addr) != sdk.AddrLen {
|
// 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")
|
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
|
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) {
|
func GetValidatorHistoricalRewardsAddressPeriod(key []byte) (valAddr sdk.ValAddress, period uint64) {
|
||||||
addr := key[1 : 1+sdk.AddrLen]
|
// key is in the format:
|
||||||
if len(addr) != sdk.AddrLen {
|
// 0x05<valAddrLen (1 Byte)><valAddr_Bytes><period_Bytes>
|
||||||
panic("unexpected key length")
|
valAddrLen := int(key[1])
|
||||||
}
|
valAddr = sdk.ValAddress(key[2 : 2+valAddrLen])
|
||||||
valAddr = sdk.ValAddress(addr)
|
b := key[2+valAddrLen:]
|
||||||
b := key[1+sdk.AddrLen:]
|
|
||||||
if len(b) != 8 {
|
if len(b) != 8 {
|
||||||
panic("unexpected key length")
|
panic("unexpected key length")
|
||||||
}
|
}
|
||||||
|
@ -101,93 +111,104 @@ func GetValidatorHistoricalRewardsAddressPeriod(key []byte) (valAddr sdk.ValAddr
|
||||||
return
|
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) {
|
func GetValidatorCurrentRewardsAddress(key []byte) (valAddr sdk.ValAddress) {
|
||||||
addr := key[1:]
|
// key is in the format:
|
||||||
if len(addr) != sdk.AddrLen {
|
// 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")
|
panic("unexpected key length")
|
||||||
}
|
}
|
||||||
|
|
||||||
return sdk.ValAddress(addr)
|
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) {
|
func GetValidatorAccumulatedCommissionAddress(key []byte) (valAddr sdk.ValAddress) {
|
||||||
addr := key[1:]
|
// key is in the format:
|
||||||
if len(addr) != sdk.AddrLen {
|
// 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")
|
panic("unexpected key length")
|
||||||
}
|
}
|
||||||
|
|
||||||
return sdk.ValAddress(addr)
|
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) {
|
func GetValidatorSlashEventAddressHeight(key []byte) (valAddr sdk.ValAddress, height uint64) {
|
||||||
addr := key[1 : 1+sdk.AddrLen]
|
// key is in the format:
|
||||||
if len(addr) != sdk.AddrLen {
|
// 0x08<valAddrLen (1 Byte)><valAddr_Bytes><height>: ValidatorSlashEvent
|
||||||
panic("unexpected key length")
|
valAddrLen := int(key[1])
|
||||||
}
|
valAddr = key[2 : 2+valAddrLen]
|
||||||
valAddr = sdk.ValAddress(addr)
|
startB := 2 + valAddrLen
|
||||||
startB := 1 + sdk.AddrLen
|
|
||||||
b := key[startB : startB+8] // the next 8 bytes represent the height
|
b := key[startB : startB+8] // the next 8 bytes represent the height
|
||||||
height = binary.BigEndian.Uint64(b)
|
height = binary.BigEndian.Uint64(b)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// gets the outstanding rewards key for a validator
|
// GetValidatorOutstandingRewardsKey creates the outstanding rewards key for a validator.
|
||||||
func GetValidatorOutstandingRewardsKey(valAddr sdk.ValAddress) []byte {
|
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 {
|
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 {
|
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 {
|
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 {
|
func GetValidatorHistoricalRewardsKey(v sdk.ValAddress, k uint64) []byte {
|
||||||
b := make([]byte, 8)
|
b := make([]byte, 8)
|
||||||
binary.LittleEndian.PutUint64(b, k)
|
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 {
|
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 {
|
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 {
|
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 {
|
func GetValidatorSlashEventKeyPrefix(v sdk.ValAddress, height uint64) []byte {
|
||||||
heightBz := make([]byte, 8)
|
heightBz := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(heightBz, height)
|
binary.BigEndian.PutUint64(heightBz, height)
|
||||||
|
|
||||||
return append(
|
return append(
|
||||||
ValidatorSlashEventPrefix,
|
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 {
|
func GetValidatorSlashEventKey(v sdk.ValAddress, height, period uint64) []byte {
|
||||||
periodBz := make([]byte, 8)
|
periodBz := make([]byte, 8)
|
||||||
binary.BigEndian.PutUint64(periodBz, period)
|
binary.BigEndian.PutUint64(periodBz, period)
|
||||||
prefix := GetValidatorSlashEventKeyPrefix(v, height)
|
prefix := GetValidatorSlashEventKeyPrefix(v, height)
|
||||||
|
|
||||||
return append(prefix, periodBz...)
|
return append(prefix, periodBz...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,7 +88,8 @@ func (e Equivocation) GetTotalPower() int64 { return 0 }
|
||||||
// FromABCIEvidence converts a Tendermint concrete Evidence type to
|
// FromABCIEvidence converts a Tendermint concrete Evidence type to
|
||||||
// SDK Evidence using Equivocation as the concrete type.
|
// SDK Evidence using Equivocation as the concrete type.
|
||||||
func FromABCIEvidence(e abci.Evidence) exported.Evidence {
|
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 {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
abci "github.com/tendermint/tendermint/abci/types"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
"github.com/cosmos/cosmos-sdk/x/evidence/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)
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
|
@ -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
|
|
@ -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
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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())
|
||||||
|
}
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
|
@ -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")
|
||||||
|
)
|
|
@ -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
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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
|
@ -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
|
||||||
|
}
|
|
@ -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
|
||||||
|
}
|
|
@ -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")
|
||||||
|
)
|
|
@ -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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())...)
|
||||||
|
}
|
|
@ -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}
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
||||||
|
)
|
File diff suppressed because it is too large
Load Diff
|
@ -6,6 +6,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -33,9 +34,9 @@ const (
|
||||||
//
|
//
|
||||||
// - 0x03: nextProposalID
|
// - 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 (
|
var (
|
||||||
ProposalsKeyPrefix = []byte{0x00}
|
ProposalsKeyPrefix = []byte{0x00}
|
||||||
ActiveProposalQueuePrefix = []byte{0x01}
|
ActiveProposalQueuePrefix = []byte{0x01}
|
||||||
|
@ -93,7 +94,7 @@ func DepositsKey(proposalID uint64) []byte {
|
||||||
|
|
||||||
// DepositKey key of a specific deposit from the store
|
// DepositKey key of a specific deposit from the store
|
||||||
func DepositKey(proposalID uint64, depositorAddr sdk.AccAddress) []byte {
|
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
|
// 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
|
// VoteKey key of a specific vote from the store
|
||||||
func VoteKey(proposalID uint64, voterAddr sdk.AccAddress) []byte {
|
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
|
// 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) {
|
func splitKeyWithAddress(key []byte) (proposalID uint64, addr sdk.AccAddress) {
|
||||||
if len(key[1:]) != 8+sdk.AddrLen {
|
// Both Vote and Deposit store keys are of format:
|
||||||
panic(fmt.Sprintf("unexpected key length (%d ≠ %d)", len(key), 8+sdk.AddrLen))
|
// <prefix (1 Byte)><proposalID (8 bytes)><addrLen (1 Byte)><addr_Bytes>
|
||||||
}
|
|
||||||
|
|
||||||
proposalID = GetProposalIDFromBytes(key[1:9])
|
proposalID = GetProposalIDFromBytes(key[1:9])
|
||||||
addr = sdk.AccAddress(key[9:])
|
addr = sdk.AccAddress(key[10:])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,11 +46,6 @@ func TestDepositKeys(t *testing.T) {
|
||||||
proposalID, depositorAddr := SplitKeyDeposit(key)
|
proposalID, depositorAddr := SplitKeyDeposit(key)
|
||||||
require.Equal(t, int(proposalID), 2)
|
require.Equal(t, int(proposalID), 2)
|
||||||
require.Equal(t, addr, depositorAddr)
|
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) {
|
func TestVoteKeys(t *testing.T) {
|
||||||
|
@ -63,9 +58,4 @@ func TestVoteKeys(t *testing.T) {
|
||||||
proposalID, voterAddr := SplitKeyDeposit(key)
|
proposalID, voterAddr := SplitKeyDeposit(key)
|
||||||
require.Equal(t, int(proposalID), 2)
|
require.Equal(t, int(proposalID), 2)
|
||||||
require.Equal(t, addr, voterAddr)
|
require.Equal(t, addr, voterAddr)
|
||||||
|
|
||||||
// invalid key
|
|
||||||
addr2 := sdk.AccAddress("test1")
|
|
||||||
key = VoteKey(5, addr2)
|
|
||||||
require.Panics(t, func() { SplitKeyVote(key) })
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -129,6 +129,9 @@ func (k Keeper) SendPacket(
|
||||||
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
|
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
|
||||||
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
|
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
|
||||||
sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()),
|
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.NewEvent(
|
||||||
sdk.EventTypeMessage,
|
sdk.EventTypeMessage,
|
||||||
|
@ -289,6 +292,9 @@ func (k Keeper) RecvPacket(
|
||||||
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
|
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
|
||||||
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
|
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
|
||||||
sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()),
|
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.NewEvent(
|
||||||
sdk.EventTypeMessage,
|
sdk.EventTypeMessage,
|
||||||
|
@ -370,6 +376,9 @@ func (k Keeper) WriteAcknowledgement(
|
||||||
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
|
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
|
||||||
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
|
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
|
||||||
sdk.NewAttribute(types.AttributeKeyAck, string(acknowledgement)),
|
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.NewEvent(
|
||||||
sdk.EventTypeMessage,
|
sdk.EventTypeMessage,
|
||||||
|
@ -505,6 +514,9 @@ func (k Keeper) AcknowledgePacket(
|
||||||
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
|
sdk.NewAttribute(types.AttributeKeyDstPort, packet.GetDestPort()),
|
||||||
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
|
sdk.NewAttribute(types.AttributeKeyDstChannel, packet.GetDestChannel()),
|
||||||
sdk.NewAttribute(types.AttributeKeyChannelOrdering, channel.Ordering.String()),
|
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.NewEvent(
|
||||||
sdk.EventTypeMessage,
|
sdk.EventTypeMessage,
|
||||||
|
|
|
@ -30,6 +30,7 @@ const (
|
||||||
AttributeKeyDstPort = "packet_dst_port"
|
AttributeKeyDstPort = "packet_dst_port"
|
||||||
AttributeKeyDstChannel = "packet_dst_channel"
|
AttributeKeyDstChannel = "packet_dst_channel"
|
||||||
AttributeKeyChannelOrdering = "packet_channel_ordering"
|
AttributeKeyChannelOrdering = "packet_channel_ordering"
|
||||||
|
AttributeKeyConnection = "packet_connection"
|
||||||
)
|
)
|
||||||
|
|
||||||
// IBC channel events vars
|
// IBC channel events vars
|
||||||
|
|
|
@ -9,7 +9,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/x/mint/types"
|
"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.
|
// Value to the corresponding mint type.
|
||||||
func NewDecodeStore(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string {
|
func NewDecodeStore(cdc codec.Marshaler) func(kvA, kvB kv.Pair) string {
|
||||||
return func(kvA, kvB kv.Pair) string {
|
return func(kvA, kvB kv.Pair) string {
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -23,11 +24,11 @@ const (
|
||||||
// Keys for slashing store
|
// Keys for slashing store
|
||||||
// Items are stored with the following key: values
|
// 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 (
|
var (
|
||||||
ValidatorSigningInfoKeyPrefix = []byte{0x01} // Prefix for signing info
|
ValidatorSigningInfoKeyPrefix = []byte{0x01} // Prefix for signing info
|
||||||
ValidatorMissedBlockBitArrayKeyPrefix = []byte{0x02} // Prefix for missed block bit array
|
ValidatorMissedBlockBitArrayKeyPrefix = []byte{0x02} // Prefix for missed block bit array
|
||||||
|
@ -36,31 +37,31 @@ var (
|
||||||
|
|
||||||
// ValidatorSigningInfoKey - stored by *Consensus* address (not operator address)
|
// ValidatorSigningInfoKey - stored by *Consensus* address (not operator address)
|
||||||
func ValidatorSigningInfoKey(v sdk.ConsAddress) []byte {
|
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
|
// ValidatorSigningInfoAddress - extract the address from a validator signing info key
|
||||||
func ValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) {
|
func ValidatorSigningInfoAddress(key []byte) (v sdk.ConsAddress) {
|
||||||
addr := key[1:]
|
// Remove prefix and address length.
|
||||||
if len(addr) != sdk.AddrLen {
|
addr := key[2:]
|
||||||
panic("unexpected key length")
|
|
||||||
}
|
|
||||||
return sdk.ConsAddress(addr)
|
return sdk.ConsAddress(addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidatorMissedBlockBitArrayPrefixKey - stored by *Consensus* address (not operator address)
|
// ValidatorMissedBlockBitArrayPrefixKey - stored by *Consensus* address (not operator address)
|
||||||
func ValidatorMissedBlockBitArrayPrefixKey(v sdk.ConsAddress) []byte {
|
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)
|
// ValidatorMissedBlockBitArrayKey - stored by *Consensus* address (not operator address)
|
||||||
func ValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte {
|
func ValidatorMissedBlockBitArrayKey(v sdk.ConsAddress, i int64) []byte {
|
||||||
b := make([]byte, 8)
|
b := make([]byte, 8)
|
||||||
binary.LittleEndian.PutUint64(b, uint64(i))
|
binary.LittleEndian.PutUint64(b, uint64(i))
|
||||||
|
|
||||||
return append(ValidatorMissedBlockBitArrayPrefixKey(v), b...)
|
return append(ValidatorMissedBlockBitArrayPrefixKey(v), b...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddrPubkeyRelationKey gets pubkey relation key used to get the pubkey from the address
|
// AddrPubkeyRelationKey gets pubkey relation key used to get the pubkey from the address
|
||||||
func AddrPubkeyRelationKey(address []byte) []byte {
|
func AddrPubkeyRelationKey(addr []byte) []byte {
|
||||||
return append(AddrPubkeyRelationKeyPrefix, address...)
|
return append(AddrPubkeyRelationKeyPrefix, address.MustLengthPrefix(addr)...)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,7 @@ import (
|
||||||
"github.com/cosmos/cosmos-sdk/x/staking/types"
|
"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
|
// Called in each EndBlock
|
||||||
func (k Keeper) BlockValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate {
|
func (k Keeper) BlockValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate {
|
||||||
// Calculate validator set changes.
|
// Calculate validator set changes.
|
||||||
|
@ -97,7 +97,7 @@ func (k Keeper) BlockValidatorUpdates(ctx sdk.Context) []abci.ValidatorUpdate {
|
||||||
return validatorUpdates
|
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 active valset as keyed by LastValidatorPowerKey.
|
||||||
// * Updates the total power as keyed by LastTotalPowerKey.
|
// * Updates the total power as keyed by LastTotalPowerKey.
|
||||||
// * Updates validator status' according to updated powers.
|
// * 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.
|
// Retrieve the last validator set.
|
||||||
// The persistent set is updated later in this function.
|
// The persistent set is updated later in this function.
|
||||||
// (see LastValidatorPowerKey).
|
// (see LastValidatorPowerKey).
|
||||||
last := k.getLastValidatorsByAddr(ctx)
|
last, err := k.getLastValidatorsByAddr(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
// Iterate over validators, highest power to lowest.
|
// Iterate over validators, highest power to lowest.
|
||||||
iterator := k.ValidatorsPowerStoreIterator(ctx)
|
iterator := k.ValidatorsPowerStoreIterator(ctx)
|
||||||
|
@ -160,10 +163,11 @@ func (k Keeper) ApplyAndReturnValidatorSetUpdates(ctx sdk.Context) (updates []ab
|
||||||
}
|
}
|
||||||
|
|
||||||
// fetch the old power bytes
|
// fetch the old power bytes
|
||||||
var valAddrBytes [sdk.AddrLen]byte
|
valAddrStr, err := sdk.Bech32ifyAddressBytes(sdk.Bech32PrefixValAddr, valAddr)
|
||||||
|
if err != nil {
|
||||||
copy(valAddrBytes[:], valAddr[:])
|
return nil, err
|
||||||
oldPowerBytes, found := last[valAddrBytes]
|
}
|
||||||
|
oldPowerBytes, found := last[valAddrStr]
|
||||||
newPower := validator.ConsensusPower()
|
newPower := validator.ConsensusPower()
|
||||||
newPowerBytes := k.cdc.MustMarshalBinaryBare(&gogotypes.Int64Value{Value: newPower})
|
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)
|
k.SetLastValidatorPower(ctx, valAddr, newPower)
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(last, valAddrBytes)
|
delete(last, valAddrStr)
|
||||||
count++
|
count++
|
||||||
|
|
||||||
totalPower = totalPower.Add(sdk.NewInt(newPower))
|
totalPower = totalPower.Add(sdk.NewInt(newPower))
|
||||||
}
|
}
|
||||||
|
|
||||||
noLongerBonded := sortNoLongerBonded(last)
|
noLongerBonded, err := sortNoLongerBonded(last)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
for _, valAddrBytes := range noLongerBonded {
|
for _, valAddrBytes := range noLongerBonded {
|
||||||
validator := k.mustGetValidator(ctx, sdk.ValAddress(valAddrBytes))
|
validator := k.mustGetValidator(ctx, sdk.ValAddress(valAddrBytes))
|
||||||
validator, err = k.bondedToUnbonding(ctx, validator)
|
validator, err = k.bondedToUnbonding(ctx, validator)
|
||||||
|
@ -339,39 +347,46 @@ func (k Keeper) completeUnbondingValidator(ctx sdk.Context, validator types.Vali
|
||||||
return validator
|
return validator
|
||||||
}
|
}
|
||||||
|
|
||||||
// map of operator addresses to serialized power
|
// map of operator bech32-addresses to serialized power
|
||||||
type validatorsByAddr map[[sdk.AddrLen]byte][]byte
|
// 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
|
// 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)
|
last := make(validatorsByAddr)
|
||||||
|
|
||||||
iterator := k.LastValidatorsIterator(ctx)
|
iterator := k.LastValidatorsIterator(ctx)
|
||||||
defer iterator.Close()
|
defer iterator.Close()
|
||||||
|
|
||||||
for ; iterator.Valid(); iterator.Next() {
|
for ; iterator.Valid(); iterator.Next() {
|
||||||
var valAddr [sdk.AddrLen]byte
|
// extract the validator address from the key (prefix is 1-byte, addrLen is 1-byte)
|
||||||
// extract the validator address from the key (prefix is 1-byte)
|
valAddr := types.AddressFromLastValidatorPowerKey(iterator.Key())
|
||||||
copy(valAddr[:], iterator.Key()[1:])
|
valAddrStr, err := sdk.Bech32ifyAddressBytes(sdk.Bech32PrefixValAddr, valAddr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
powerBytes := iterator.Value()
|
powerBytes := iterator.Value()
|
||||||
last[valAddr] = make([]byte, len(powerBytes))
|
last[valAddrStr] = make([]byte, len(powerBytes))
|
||||||
copy(last[valAddr], powerBytes)
|
copy(last[valAddrStr], powerBytes)
|
||||||
}
|
}
|
||||||
|
|
||||||
return last
|
return last, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// given a map of remaining validators to previous bonded power
|
// given a map of remaining validators to previous bonded power
|
||||||
// returns the list of validators to be unbonded, sorted by operator address
|
// 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
|
// sort the map keys for determinism
|
||||||
noLongerBonded := make([][]byte, len(last))
|
noLongerBonded := make([][]byte, len(last))
|
||||||
index := 0
|
index := 0
|
||||||
|
|
||||||
for valAddrBytes := range last {
|
for valAddrStr := range last {
|
||||||
valAddr := make([]byte, sdk.AddrLen)
|
valAddrBytes, err := sdk.ValAddressFromBech32(valAddrStr)
|
||||||
copy(valAddr, valAddrBytes[:])
|
if err != nil {
|
||||||
noLongerBonded[index] = valAddr
|
return nil, err
|
||||||
|
}
|
||||||
|
noLongerBonded[index] = valAddrBytes
|
||||||
index++
|
index++
|
||||||
}
|
}
|
||||||
// sorted by address - order doesn't matter
|
// 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 bytes.Compare(noLongerBonded[i], noLongerBonded[j]) == -1
|
||||||
})
|
})
|
||||||
|
|
||||||
return noLongerBonded
|
return noLongerBonded, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -327,7 +327,7 @@ func (k Keeper) IterateLastValidatorPowers(ctx sdk.Context, handler func(operato
|
||||||
defer iter.Close()
|
defer iter.Close()
|
||||||
|
|
||||||
for ; iter.Valid(); iter.Next() {
|
for ; iter.Valid(); iter.Next() {
|
||||||
addr := sdk.ValAddress(iter.Key()[len(types.LastValidatorPowerKey):])
|
addr := sdk.ValAddress(types.AddressFromLastValidatorPowerKey(iter.Key()))
|
||||||
intV := &gogotypes.Int64Value{}
|
intV := &gogotypes.Int64Value{}
|
||||||
|
|
||||||
k.cdc.MustUnmarshalBinaryBare(iter.Value(), intV)
|
k.cdc.MustUnmarshalBinaryBare(iter.Value(), intV)
|
||||||
|
|
|
@ -8,23 +8,16 @@ order: 1
|
||||||
|
|
||||||
LastTotalPower tracks the total amounts of bonded tokens recorded during the previous end block.
|
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
|
||||||
|
|
||||||
Params is a module-wide configuration structure that stores system parameters
|
Params is a module-wide configuration structure that stores system parameters
|
||||||
and defines overall functioning of the staking module.
|
and defines overall functioning of the staking module.
|
||||||
|
|
||||||
- Params: `Paramsspace("staking") -> amino(params)`
|
- Params: `Paramsspace("staking") -> legacy_amino(params)`
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.1/proto/cosmos/staking/v1beta1/staking.proto#L230-L241
|
||||||
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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Validator
|
## Validator
|
||||||
|
|
||||||
|
@ -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
|
throughout each block, unlike the first two indices which mirror the validator
|
||||||
records within a block.
|
records within a block.
|
||||||
|
|
||||||
- Validators: `0x21 | OperatorAddr -> amino(validator)`
|
- Validators: `0x21 | OperatorAddr -> ProtocolBuffer(validator)`
|
||||||
- ValidatorsByConsAddr: `0x22 | ConsAddr -> OperatorAddr`
|
- ValidatorsByConsAddr: `0x22 | ConsAddr -> OperatorAddr`
|
||||||
- ValidatorsByPower: `0x23 | BigEndian(ConsensusPower) | OperatorAddr -> 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
|
`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
|
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
|
`ValidatorsByPower` is an additional index that provides a sorted list o
|
||||||
potential validators to quickly determine the current active set. Here
|
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.
|
`Jailed` is true are not stored within this index.
|
||||||
|
|
||||||
`LastValidatorsPower` is a special index that provides a historical list of the
|
`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:
|
Each validator's state is stored in a `Validator` struct:
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L65-L99
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
type Commission struct {
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L24-L63
|
||||||
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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Delegation
|
## Delegation
|
||||||
|
|
||||||
Delegations are identified by combining `DelegatorAddr` (the address of the delegator)
|
Delegations are identified by combining `DelegatorAddr` (the address of the delegator)
|
||||||
with the `ValidatorAddr` Delegators are indexed in the store as follows:
|
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
|
Stake holders may delegate coins to validators; under this circumstance their
|
||||||
funds are held in a `Delegation` data structure. It is owned by one
|
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
|
delegator, and is associated with the shares for one validator. The sender of
|
||||||
the transaction is the owner of the bond.
|
the transaction is the owner of the bond.
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L159-L170
|
||||||
type Delegation struct {
|
|
||||||
DelegatorAddr sdk.AccAddress
|
|
||||||
ValidatorAddr sdk.ValAddress
|
|
||||||
Shares sdk.Dec // delegation shares received
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Delegator Shares
|
### Delegator Shares
|
||||||
|
|
||||||
|
@ -159,10 +115,8 @@ detected.
|
||||||
|
|
||||||
`UnbondingDelegation` are indexed in the store as:
|
`UnbondingDelegation` are indexed in the store as:
|
||||||
|
|
||||||
- UnbondingDelegation: `0x32 | DelegatorAddr | ValidatorAddr ->
|
- UnbondingDelegation: `0x32 | DelegatorAddr | ValidatorAddr -> ProtocolBuffer(unbondingDelegation)`
|
||||||
amino(unbondingDelegation)`
|
- UnbondingDelegationsFromValidator: `0x33 | ValidatorAddr | DelegatorAddr -> nil`
|
||||||
- UnbondingDelegationsFromValidator: `0x33 | ValidatorAddr | DelegatorAddr ->
|
|
||||||
nil`
|
|
||||||
|
|
||||||
The first map here is used in queries, to lookup all unbonding delegations for
|
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
|
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.
|
A UnbondingDelegation object is created every time an unbonding is initiated.
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L172-L198
|
||||||
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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Redelegation
|
## Redelegation
|
||||||
|
|
||||||
|
@ -196,7 +137,7 @@ committed by the source validator.
|
||||||
|
|
||||||
`Redelegation` are indexed in the store as:
|
`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`
|
- RedelegationsBySrc: `0x35 | ValidatorSrcAddr | ValidatorDstAddr | DelegatorAddr -> nil`
|
||||||
- RedelegationsByDst: `0x36 | ValidatorDstAddr | ValidatorSrcAddr | 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`,
|
delegator. The second map is used for slashing based on the `ValidatorSrcAddr`,
|
||||||
while the third map is for slashing based on the `ValidatorDstAddr`.
|
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:
|
"redelegation hopping" redelegations may not occur under the situation that:
|
||||||
|
|
||||||
- the (re)delegator already has another immature redelegation in progress
|
- 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
|
- 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
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L200-L228
|
||||||
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
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Queues
|
## Queues
|
||||||
|
|
||||||
|
@ -249,12 +175,7 @@ delegations queue is kept.
|
||||||
|
|
||||||
- UnbondingDelegation: `0x41 | format(time) -> []DVPair`
|
- UnbondingDelegation: `0x41 | format(time) -> []DVPair`
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L123-L133
|
||||||
type DVPair struct {
|
|
||||||
DelegatorAddr sdk.AccAddress
|
|
||||||
ValidatorAddr sdk.ValAddress
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### RedelegationQueue
|
### RedelegationQueue
|
||||||
|
|
||||||
|
@ -263,13 +184,7 @@ kept.
|
||||||
|
|
||||||
- UnbondingDelegation: `0x42 | format(time) -> []DVVTriplet`
|
- UnbondingDelegation: `0x42 | format(time) -> []DVVTriplet`
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L140-L152
|
||||||
type DVVTriplet struct {
|
|
||||||
DelegatorAddr sdk.AccAddress
|
|
||||||
ValidatorSrcAddr sdk.ValAddress
|
|
||||||
ValidatorDstAddr sdk.ValAddress
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### ValidatorQueue
|
### ValidatorQueue
|
||||||
|
|
||||||
|
@ -279,7 +194,7 @@ queue is kept.
|
||||||
- ValidatorQueueTime: `0x43 | format(time) -> []sdk.ValAddress`
|
- ValidatorQueueTime: `0x43 | format(time) -> []sdk.ValAddress`
|
||||||
|
|
||||||
The stored object as each key is an array of validator operator addresses from
|
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
|
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.
|
that multiple validators exist in the queue at the same location.
|
||||||
|
|
||||||
|
@ -288,12 +203,7 @@ 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
|
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`.
|
the `n` most recent historical info defined by staking module parameter: `HistoricalEntries`.
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/staking.proto#L15-L22
|
||||||
type HistoricalInfo struct {
|
|
||||||
Header tmproto.Header
|
|
||||||
ValSet []types.Validator
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
At each BeginBlock, the staking keeper will persist the current Header and the Validators that committed
|
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
|
||||||
|
|
|
@ -11,6 +11,7 @@ This document describes the state transition operations pertaining to:
|
||||||
3. [Slashing](./02_state_transitions.md#slashing)
|
3. [Slashing](./02_state_transitions.md#slashing)
|
||||||
|
|
||||||
## Validators
|
## 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`.
|
in order to check for changes in the active `ValidatorSet`.
|
||||||
|
|
||||||
|
@ -116,14 +117,14 @@ When a redelegations complete the following occurs:
|
||||||
|
|
||||||
When a Validator is slashed, 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 `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 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`
|
- 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
|
- 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
|
- 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
|
### Slash Unbonding Delegation
|
||||||
|
|
||||||
|
|
|
@ -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.
|
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
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L16-L17
|
||||||
type MsgCreateValidator struct {
|
|
||||||
Description Description
|
|
||||||
Commission Commission
|
|
||||||
|
|
||||||
DelegatorAddr sdk.AccAddress
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L35-L51
|
||||||
ValidatorAddr sdk.ValAddress
|
|
||||||
PubKey crypto.PubKey
|
|
||||||
Delegation sdk.Coin
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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 operator address is already registered
|
||||||
- another validator with this pubkey 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 initial `MaxChangeRate` is either negative or > `MaxRate`
|
||||||
- the description fields are too large
|
- 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
|
Additionally a self-delegation is made with the initial tokens delegation
|
||||||
tokens `Delegation`. The validator always starts as unbonded but may be bonded
|
tokens `Delegation`. The validator always starts as unbonded but may be bonded
|
||||||
in the first end-block.
|
in the first end-block.
|
||||||
|
|
||||||
## MsgEditValidator
|
## Msg/EditValidator
|
||||||
|
|
||||||
The `Description`, `CommissionRate` of a validator can be updated using the
|
The `Description`, `CommissionRate` of a validator can be updated using the
|
||||||
`MsgEditCandidacy`.
|
`Msg/EditCandidacy` service message.
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L19-L20
|
||||||
type MsgEditCandidacy struct {
|
|
||||||
Description Description
|
|
||||||
ValidatorAddr sdk.ValAddress
|
|
||||||
CommissionRate sdk.Dec
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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 initial `CommissionRate` is either negative or > `MaxRate`
|
||||||
- the `CommissionRate` has already been updated within the previous 24 hours
|
- the `CommissionRate` has already been updated within the previous 24 hours
|
||||||
- the `CommissionRate` is > `MaxChangeRate`
|
- the `CommissionRate` is > `MaxChangeRate`
|
||||||
- the description fields are too large
|
- 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
|
some amount of their validator's (newly created) delegator-shares that are
|
||||||
assigned to `Delegation.Shares`.
|
assigned to `Delegation.Shares`.
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L22-L24
|
||||||
type MsgDelegate struct {
|
|
||||||
DelegatorAddr sdk.AccAddress
|
|
||||||
ValidatorAddr sdk.ValAddress
|
|
||||||
Amount sdk.Coin
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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 does not exist
|
||||||
- the validator is jailed
|
- the validator is jailed
|
||||||
- the `Amount` `Coin` has a denomination different than one defined by `params.BondDenom`
|
- the `Amount` `Coin` has a denomination different than one defined by `params.BondDenom`
|
||||||
|
|
||||||
If an existing `Delegation` object for provided addresses does not already
|
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.
|
`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.
|
validator.
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L30-L32
|
||||||
type MsgBeginUnbonding struct {
|
|
||||||
DelegatorAddr sdk.AccAddress
|
|
||||||
ValidatorAddr sdk.ValAddress
|
|
||||||
Amount sdk.Coin
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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 delegation doesn't exist
|
||||||
- the validator 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`
|
- existing `UnbondingDelegation` has maximum entries as defined by `params.MaxEntries`
|
||||||
- the `Amount` has a denomination different than one defined by `params.BondDenom`
|
- 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`
|
- 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
|
- 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
|
- 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.
|
- 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 redelegation command allows delegators to instantly switch validators. Once
|
||||||
the unbonding period has passed, the redelegation is automatically completed in
|
the unbonding period has passed, the redelegation is automatically completed in
|
||||||
the EndBlocker.
|
the EndBlocker.
|
||||||
|
|
||||||
```go
|
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/proto/cosmos/staking/v1beta1/tx.proto#L26-L28
|
||||||
type MsgBeginRedelegate struct {
|
|
||||||
DelegatorAddr sdk.AccAddress
|
|
||||||
ValidatorSrcAddr sdk.ValAddress
|
|
||||||
ValidatorDstAddr sdk.ValAddress
|
|
||||||
Amount sdk.Coin
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
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 delegation doesn't exist
|
||||||
- the source or destination validators don'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`
|
- existing `Redelegation` has maximum entries as defined by `params.MaxEntries`
|
||||||
- the `Amount` `Coin` has a denomination different than one defined by `params.BondDenom`
|
- 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`
|
- 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.
|
- 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).
|
- `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`).
|
- `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
|
- `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
|
- 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.
|
- under this situation if the delegation is the validator's self-delegation then also jail the validator.
|
||||||
|
|
|
@ -18,9 +18,9 @@ The staking module emits the following events:
|
||||||
| complete_redelegation | destination_validator | {dstValidatorAddress} |
|
| complete_redelegation | destination_validator | {dstValidatorAddress} |
|
||||||
| complete_redelegation | delegator | {delegatorAddress} |
|
| complete_redelegation | delegator | {delegatorAddress} |
|
||||||
|
|
||||||
## Handlers
|
## Service Messages
|
||||||
|
|
||||||
### MsgCreateValidator
|
### Msg/CreateValidator
|
||||||
|
|
||||||
| Type | Attribute Key | Attribute Value |
|
| Type | Attribute Key | Attribute Value |
|
||||||
| ---------------- | ------------- | ------------------ |
|
| ---------------- | ------------- | ------------------ |
|
||||||
|
@ -30,7 +30,7 @@ The staking module emits the following events:
|
||||||
| message | action | create_validator |
|
| message | action | create_validator |
|
||||||
| message | sender | {senderAddress} |
|
| message | sender | {senderAddress} |
|
||||||
|
|
||||||
### MsgEditValidator
|
### Msg/EditValidator
|
||||||
|
|
||||||
| Type | Attribute Key | Attribute Value |
|
| Type | Attribute Key | Attribute Value |
|
||||||
| -------------- | ------------------- | ------------------- |
|
| -------------- | ------------------- | ------------------- |
|
||||||
|
@ -40,7 +40,7 @@ The staking module emits the following events:
|
||||||
| message | action | edit_validator |
|
| message | action | edit_validator |
|
||||||
| message | sender | {senderAddress} |
|
| message | sender | {senderAddress} |
|
||||||
|
|
||||||
### MsgDelegate
|
### Msg/Delegate
|
||||||
|
|
||||||
| Type | Attribute Key | Attribute Value |
|
| Type | Attribute Key | Attribute Value |
|
||||||
| -------- | ------------- | ------------------ |
|
| -------- | ------------- | ------------------ |
|
||||||
|
@ -50,7 +50,7 @@ The staking module emits the following events:
|
||||||
| message | action | delegate |
|
| message | action | delegate |
|
||||||
| message | sender | {senderAddress} |
|
| message | sender | {senderAddress} |
|
||||||
|
|
||||||
### MsgUndelegate
|
### Msg/Undelegate
|
||||||
|
|
||||||
| Type | Attribute Key | Attribute Value |
|
| Type | Attribute Key | Attribute Value |
|
||||||
| ------- | ------------------- | ------------------ |
|
| ------- | ------------------- | ------------------ |
|
||||||
|
@ -61,9 +61,9 @@ The staking module emits the following events:
|
||||||
| message | action | begin_unbonding |
|
| message | action | begin_unbonding |
|
||||||
| message | sender | {senderAddress} |
|
| 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 |
|
| Type | Attribute Key | Attribute Value |
|
||||||
| ---------- | --------------------- | --------------------- |
|
| ---------- | --------------------- | --------------------- |
|
||||||
|
@ -75,4 +75,4 @@ The staking module emits the following events:
|
||||||
| message | action | begin_redelegate |
|
| message | action | begin_redelegate |
|
||||||
| message | sender | {senderAddress} |
|
| message | sender | {senderAddress} |
|
||||||
|
|
||||||
* [0] Time is formatted in the RFC3339 standard
|
- [0] Time is formatted in the RFC3339 standard
|
||||||
|
|
|
@ -38,18 +38,18 @@ network.
|
||||||
- [Delegations](02_state_transitions.md#delegations)
|
- [Delegations](02_state_transitions.md#delegations)
|
||||||
- [Slashing](02_state_transitions.md#slashing)
|
- [Slashing](02_state_transitions.md#slashing)
|
||||||
3. **[Messages](03_messages.md)**
|
3. **[Messages](03_messages.md)**
|
||||||
- [MsgCreateValidator](03_messages.md#msgcreatevalidator)
|
- [Msg/CreateValidator](03_messages.md#msgcreatevalidator)
|
||||||
- [MsgEditValidator](03_messages.md#msgeditvalidator)
|
- [Msg/EditValidator](03_messages.md#msgeditvalidator)
|
||||||
- [MsgDelegate](03_messages.md#msgdelegate)
|
- [Msg/Delegate](03_messages.md#msgdelegate)
|
||||||
- [MsgBeginUnbonding](03_messages.md#msgbeginunbonding)
|
- [Msg/BeginUnbonding](03_messages.md#msgbeginunbonding)
|
||||||
- [MsgBeginRedelegate](03_messages.md#msgbeginredelegate)
|
- [Msg/BeginRedelegate](03_messages.md#msgbeginredelegate)
|
||||||
4. **[Begin-Block](04_begin_block.md)**
|
4. **[Begin-Block](04_begin_block.md)**
|
||||||
- [Historical Info Tracking](04_begin_block.md#historical-info-tracking)
|
- [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)
|
- [Validator Set Changes](05_end_block.md#validator-set-changes)
|
||||||
- [Queues ](05_end_block.md#queues-)
|
- [Queues ](05_end_block.md#queues-)
|
||||||
5. **[Hooks](06_hooks.md)**
|
6. **[Hooks](06_hooks.md)**
|
||||||
6. **[Events](07_events.md)**
|
7. **[Events](07_events.md)**
|
||||||
- [EndBlocker](07_events.md#endblocker)
|
- [EndBlocker](07_events.md#endblocker)
|
||||||
- [Handlers](07_events.md#handlers)
|
- [Handlers](07_events.md#handlers)
|
||||||
7. **[Parameters](08_params.md)**
|
8. **[Parameters](08_params.md)**
|
||||||
|
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
sdk "github.com/cosmos/cosmos-sdk/types"
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
||||||
|
"github.com/cosmos/cosmos-sdk/types/address"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -48,24 +49,29 @@ var (
|
||||||
HistoricalInfoKey = []byte{0x50} // prefix for the historical info
|
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
|
// VALUE: staking/Validator
|
||||||
func GetValidatorKey(operatorAddr sdk.ValAddress) []byte {
|
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)
|
// VALUE: validator operator address ([]byte)
|
||||||
func GetValidatorByConsAddrKey(addr sdk.ConsAddress) []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 {
|
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 index is the key used in the power-store, and represents the relative
|
||||||
// power ranking of the validator.
|
// power ranking of the validator.
|
||||||
// VALUE: validator operator address ([]byte)
|
// VALUE: validator operator address ([]byte)
|
||||||
|
@ -80,39 +86,39 @@ func GetValidatorsByPowerIndexKey(validator Validator) []byte {
|
||||||
powerBytes := consensusPowerBytes
|
powerBytes := consensusPowerBytes
|
||||||
powerBytesLen := len(powerBytes) // 8
|
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)
|
addr, err := sdk.ValAddressFromBech32(validator.OperatorAddress)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
operAddrInvr := sdk.CopyBytes(addr)
|
operAddrInvr := sdk.CopyBytes(addr)
|
||||||
|
addrLen := len(operAddrInvr)
|
||||||
|
|
||||||
for i, b := range operAddrInvr {
|
for i, b := range operAddrInvr {
|
||||||
operAddrInvr[i] = ^b
|
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
|
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 {
|
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) {
|
func ParseValidatorPowerRankKey(key []byte) (operAddr []byte) {
|
||||||
powerBytesLen := 8
|
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 {
|
for i, b := range operAddr {
|
||||||
operAddr[i] = ^b
|
operAddr[i] = ^b
|
||||||
|
@ -165,55 +171,51 @@ func ParseValidatorQueueKey(bz []byte) (time.Time, int64, error) {
|
||||||
return ts, int64(height), nil
|
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
|
// VALUE: staking/Delegation
|
||||||
func GetDelegationKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte {
|
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 {
|
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
|
// VALUE: staking/UnbondingDelegation
|
||||||
func GetUBDKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte {
|
func GetUBDKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte {
|
||||||
return append(
|
return append(GetUBDsKey(delAddr.Bytes()), address.MustLengthPrefix(valAddr)...)
|
||||||
GetUBDsKey(delAddr.Bytes()),
|
|
||||||
valAddr.Bytes()...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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)
|
// VALUE: none (key rearrangement used)
|
||||||
func GetUBDByValIndexKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte {
|
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 {
|
func GetUBDKeyFromValIndexKey(indexKey []byte) []byte {
|
||||||
addrs := indexKey[1:] // remove prefix bytes
|
addrs := indexKey[1:] // remove prefix bytes
|
||||||
if len(addrs) != 2*sdk.AddrLen {
|
|
||||||
panic("unexpected key length")
|
|
||||||
}
|
|
||||||
|
|
||||||
valAddr := addrs[:sdk.AddrLen]
|
valAddrLen := addrs[0]
|
||||||
delAddr := addrs[sdk.AddrLen:]
|
valAddr := addrs[1 : 1+valAddrLen]
|
||||||
|
delAddr := addrs[valAddrLen+2:]
|
||||||
|
|
||||||
return GetUBDKey(delAddr, valAddr)
|
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 {
|
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 {
|
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 {
|
func GetUnbondingDelegationTimeKey(timestamp time.Time) []byte {
|
||||||
bz := sdk.FormatTimeBytes(timestamp)
|
bz := sdk.FormatTimeBytes(timestamp)
|
||||||
return append(UnbondingQueueKey, bz...)
|
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
|
// GetREDKey returns a key prefix for indexing a redelegation from a delegator
|
||||||
// and source validator to a destination validator.
|
// and source validator to a destination validator.
|
||||||
func GetREDKey(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) []byte {
|
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[0:2+len(delAddr)], GetREDsKey(delAddr.Bytes()))
|
||||||
copy(key[sdk.AddrLen+1:2*sdk.AddrLen+1], valSrcAddr.Bytes())
|
key[2+len(delAddr)] = byte(len(valSrcAddr))
|
||||||
copy(key[2*sdk.AddrLen+1:3*sdk.AddrLen+1], valDstAddr.Bytes())
|
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
|
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)
|
// VALUE: none (key rearrangement used)
|
||||||
func GetREDByValSrcIndexKey(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) []byte {
|
func GetREDByValSrcIndexKey(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) []byte {
|
||||||
REDSFromValsSrcKey := GetREDsFromValSrcIndexKey(valSrcAddr)
|
REDSFromValsSrcKey := GetREDsFromValSrcIndexKey(valSrcAddr)
|
||||||
offset := len(REDSFromValsSrcKey)
|
offset := len(REDSFromValsSrcKey)
|
||||||
|
|
||||||
// key is of the form REDSFromValsSrcKey || delAddr || valDstAddr
|
// key is of the form REDSFromValsSrcKey || delAddrLen (1 byte) || delAddr || valDstAddrLen (1 byte) || valDstAddr
|
||||||
key := make([]byte, len(REDSFromValsSrcKey)+2*sdk.AddrLen)
|
key := make([]byte, offset+2+len(delAddr)+len(valDstAddr))
|
||||||
copy(key[0:offset], REDSFromValsSrcKey)
|
copy(key[0:offset], REDSFromValsSrcKey)
|
||||||
copy(key[offset:offset+sdk.AddrLen], delAddr.Bytes())
|
key[offset] = byte(len(delAddr))
|
||||||
copy(key[offset+sdk.AddrLen:offset+2*sdk.AddrLen], valDstAddr.Bytes())
|
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
|
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)
|
// VALUE: none (key rearrangement used)
|
||||||
func GetREDByValDstIndexKey(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) []byte {
|
func GetREDByValDstIndexKey(delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress) []byte {
|
||||||
REDSToValsDstKey := GetREDsToValDstIndexKey(valDstAddr)
|
REDSToValsDstKey := GetREDsToValDstIndexKey(valDstAddr)
|
||||||
offset := len(REDSToValsDstKey)
|
offset := len(REDSToValsDstKey)
|
||||||
|
|
||||||
// key is of the form REDSToValsDstKey || delAddr || valSrcAddr
|
// key is of the form REDSToValsDstKey || delAddrLen (1 byte) || delAddr || valSrcAddrLen (1 byte) || valSrcAddr
|
||||||
key := make([]byte, len(REDSToValsDstKey)+2*sdk.AddrLen)
|
key := make([]byte, offset+2+len(delAddr)+len(valSrcAddr))
|
||||||
copy(key[0:offset], REDSToValsDstKey)
|
copy(key[0:offset], REDSToValsDstKey)
|
||||||
copy(key[offset:offset+sdk.AddrLen], delAddr.Bytes())
|
key[offset] = byte(len(delAddr))
|
||||||
copy(key[offset+sdk.AddrLen:offset+2*sdk.AddrLen], valSrcAddr.Bytes())
|
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
|
return key
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetREDKeyFromValSrcIndexKey rearranges the ValSrcIndexKey to get the REDKey
|
// GetREDKeyFromValSrcIndexKey rearranges the ValSrcIndexKey to get the REDKey
|
||||||
func GetREDKeyFromValSrcIndexKey(indexKey []byte) []byte {
|
func GetREDKeyFromValSrcIndexKey(indexKey []byte) []byte {
|
||||||
// note that first byte is prefix byte
|
// note that first byte is prefix byte, which we remove
|
||||||
if len(indexKey) != 3*sdk.AddrLen+1 {
|
addrs := indexKey[1:]
|
||||||
panic("unexpected key length")
|
|
||||||
}
|
|
||||||
|
|
||||||
valSrcAddr := indexKey[1 : sdk.AddrLen+1]
|
valSrcAddrLen := addrs[0]
|
||||||
delAddr := indexKey[sdk.AddrLen+1 : 2*sdk.AddrLen+1]
|
valSrcAddr := addrs[1 : valSrcAddrLen+1]
|
||||||
valDstAddr := indexKey[2*sdk.AddrLen+1 : 3*sdk.AddrLen+1]
|
delAddrLen := addrs[valSrcAddrLen+1]
|
||||||
|
delAddr := addrs[valSrcAddrLen+2 : valSrcAddrLen+2+delAddrLen]
|
||||||
|
valDstAddr := addrs[valSrcAddrLen+delAddrLen+3:]
|
||||||
|
|
||||||
return GetREDKey(delAddr, valSrcAddr, valDstAddr)
|
return GetREDKey(delAddr, valSrcAddr, valDstAddr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetREDKeyFromValDstIndexKey rearranges the ValDstIndexKey to get the REDKey
|
// GetREDKeyFromValDstIndexKey rearranges the ValDstIndexKey to get the REDKey
|
||||||
func GetREDKeyFromValDstIndexKey(indexKey []byte) []byte {
|
func GetREDKeyFromValDstIndexKey(indexKey []byte) []byte {
|
||||||
// note that first byte is prefix byte
|
// note that first byte is prefix byte, which we remove
|
||||||
if len(indexKey) != 3*sdk.AddrLen+1 {
|
addrs := indexKey[1:]
|
||||||
panic("unexpected key length")
|
|
||||||
}
|
|
||||||
|
|
||||||
valDstAddr := indexKey[1 : sdk.AddrLen+1]
|
valDstAddrLen := addrs[0]
|
||||||
delAddr := indexKey[sdk.AddrLen+1 : 2*sdk.AddrLen+1]
|
valDstAddr := addrs[1 : valDstAddrLen+1]
|
||||||
valSrcAddr := indexKey[2*sdk.AddrLen+1 : 3*sdk.AddrLen+1]
|
delAddrLen := addrs[valDstAddrLen+1]
|
||||||
|
delAddr := addrs[valDstAddrLen+2 : valDstAddrLen+2+delAddrLen]
|
||||||
|
valSrcAddr := addrs[valDstAddrLen+delAddrLen+3:]
|
||||||
|
|
||||||
return GetREDKey(delAddr, valSrcAddr, valDstAddr)
|
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
|
// GetREDsKey returns a key prefix for indexing a redelegation from a delegator
|
||||||
// address.
|
// address.
|
||||||
func GetREDsKey(delAddr sdk.AccAddress) []byte {
|
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
|
// GetREDsFromValSrcIndexKey returns a key prefix for indexing a redelegation to
|
||||||
// a source validator.
|
// a source validator.
|
||||||
func GetREDsFromValSrcIndexKey(valSrcAddr sdk.ValAddress) []byte {
|
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
|
// GetREDsToValDstIndexKey returns a key prefix for indexing a redelegation to a
|
||||||
// destination (target) validator.
|
// destination (target) validator.
|
||||||
func GetREDsToValDstIndexKey(valDstAddr sdk.ValAddress) []byte {
|
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
|
// GetREDsByDelToValDstIndexKey returns a key prefix for indexing a redelegation
|
||||||
// from an address to a source validator.
|
// from an address to a source validator.
|
||||||
func GetREDsByDelToValDstIndexKey(delAddr sdk.AccAddress, valDstAddr sdk.ValAddress) []byte {
|
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.
|
// GetHistoricalInfoKey returns a key prefix for indexing HistoricalInfo objects.
|
||||||
|
|
|
@ -37,10 +37,10 @@ func TestGetValidatorPowerRank(t *testing.T) {
|
||||||
validator types.Validator
|
validator types.Validator
|
||||||
wantHex string
|
wantHex string
|
||||||
}{
|
}{
|
||||||
{val1, "2300000000000000009c288ede7df62742fc3b7d0962045a8cef0f79f6"},
|
{val1, "230000000000000000149c288ede7df62742fc3b7d0962045a8cef0f79f6"},
|
||||||
{val2, "2300000000000000019c288ede7df62742fc3b7d0962045a8cef0f79f6"},
|
{val2, "230000000000000001149c288ede7df62742fc3b7d0962045a8cef0f79f6"},
|
||||||
{val3, "23000000000000000a9c288ede7df62742fc3b7d0962045a8cef0f79f6"},
|
{val3, "23000000000000000a149c288ede7df62742fc3b7d0962045a8cef0f79f6"},
|
||||||
{val4, "2300000100000000009c288ede7df62742fc3b7d0962045a8cef0f79f6"},
|
{val4, "230000010000000000149c288ede7df62742fc3b7d0962045a8cef0f79f6"},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
got := hex.EncodeToString(types.GetValidatorsByPowerIndexKey(tt.validator))
|
got := hex.EncodeToString(types.GetValidatorsByPowerIndexKey(tt.validator))
|
||||||
|
@ -57,11 +57,11 @@ func TestGetREDByValDstIndexKey(t *testing.T) {
|
||||||
wantHex string
|
wantHex string
|
||||||
}{
|
}{
|
||||||
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr1),
|
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr1),
|
||||||
"3663d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"},
|
"361463d771218209d8bd03c482f69dfba57310f086091463d771218209d8bd03c482f69dfba57310f086091463d771218209d8bd03c482f69dfba57310f08609"},
|
||||||
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr2), sdk.ValAddress(keysAddr3),
|
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr2), sdk.ValAddress(keysAddr3),
|
||||||
"363ab62f0d93849be495e21e3e9013a517038f45bd63d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f2"},
|
"36143ab62f0d93849be495e21e3e9013a517038f45bd1463d771218209d8bd03c482f69dfba57310f08609145ef3b5f25c54946d4a89fc0d09d2f126614540f2"},
|
||||||
{sdk.AccAddress(keysAddr2), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr3),
|
{sdk.AccAddress(keysAddr2), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr3),
|
||||||
"363ab62f0d93849be495e21e3e9013a517038f45bd5ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f08609"},
|
"36143ab62f0d93849be495e21e3e9013a517038f45bd145ef3b5f25c54946d4a89fc0d09d2f126614540f21463d771218209d8bd03c482f69dfba57310f08609"},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
got := hex.EncodeToString(types.GetREDByValDstIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr))
|
got := hex.EncodeToString(types.GetREDByValDstIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr))
|
||||||
|
@ -78,11 +78,11 @@ func TestGetREDByValSrcIndexKey(t *testing.T) {
|
||||||
wantHex string
|
wantHex string
|
||||||
}{
|
}{
|
||||||
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr1),
|
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr1),
|
||||||
"3563d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f0860963d771218209d8bd03c482f69dfba57310f08609"},
|
"351463d771218209d8bd03c482f69dfba57310f086091463d771218209d8bd03c482f69dfba57310f086091463d771218209d8bd03c482f69dfba57310f08609"},
|
||||||
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr2), sdk.ValAddress(keysAddr3),
|
{sdk.AccAddress(keysAddr1), sdk.ValAddress(keysAddr2), sdk.ValAddress(keysAddr3),
|
||||||
"355ef3b5f25c54946d4a89fc0d09d2f126614540f263d771218209d8bd03c482f69dfba57310f086093ab62f0d93849be495e21e3e9013a517038f45bd"},
|
"35145ef3b5f25c54946d4a89fc0d09d2f126614540f21463d771218209d8bd03c482f69dfba57310f08609143ab62f0d93849be495e21e3e9013a517038f45bd"},
|
||||||
{sdk.AccAddress(keysAddr2), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr3),
|
{sdk.AccAddress(keysAddr2), sdk.ValAddress(keysAddr1), sdk.ValAddress(keysAddr3),
|
||||||
"3563d771218209d8bd03c482f69dfba57310f086095ef3b5f25c54946d4a89fc0d09d2f126614540f23ab62f0d93849be495e21e3e9013a517038f45bd"},
|
"351463d771218209d8bd03c482f69dfba57310f08609145ef3b5f25c54946d4a89fc0d09d2f126614540f2143ab62f0d93849be495e21e3e9013a517038f45bd"},
|
||||||
}
|
}
|
||||||
for i, tt := range tests {
|
for i, tt := range tests {
|
||||||
got := hex.EncodeToString(types.GetREDByValSrcIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr))
|
got := hex.EncodeToString(types.GetREDByValSrcIndexKey(tt.delAddr, tt.valSrcAddr, tt.valDstAddr))
|
||||||
|
|
|
@ -135,8 +135,11 @@ func (m *HistoricalInfo) GetValset() []Validator {
|
||||||
// CommissionRates defines the initial commission rates to be used for creating
|
// CommissionRates defines the initial commission rates to be used for creating
|
||||||
// a validator.
|
// a validator.
|
||||||
type CommissionRates struct {
|
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"`
|
// rate is the commission rate charged to delegators, 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"`
|
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"`
|
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.
|
// Commission defines commission parameters for a given validator.
|
||||||
type Commission struct {
|
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"`
|
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{} }
|
func (m *Commission) Reset() { *m = Commission{} }
|
||||||
|
@ -219,11 +224,16 @@ func (m *Commission) GetUpdateTime() time.Time {
|
||||||
|
|
||||||
// Description defines a validator description.
|
// Description defines a validator description.
|
||||||
type Description struct {
|
type Description struct {
|
||||||
Moniker string `protobuf:"bytes,1,opt,name=moniker,proto3" json:"moniker,omitempty"`
|
// moniker defines a human-readable name for the validator.
|
||||||
Identity string `protobuf:"bytes,2,opt,name=identity,proto3" json:"identity,omitempty"`
|
Moniker string `protobuf:"bytes,1,opt,name=moniker,proto3" json:"moniker,omitempty"`
|
||||||
Website string `protobuf:"bytes,3,opt,name=website,proto3" json:"website,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"`
|
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{} }
|
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
|
// exchange rate. Voting power can be calculated as total bonded shares
|
||||||
// multiplied by exchange rate.
|
// multiplied by exchange rate.
|
||||||
type Validator struct {
|
type Validator struct {
|
||||||
OperatorAddress string `protobuf:"bytes,1,opt,name=operator_address,json=operatorAddress,proto3" json:"operator_address,omitempty" yaml:"operator_address"`
|
// operator_address defines the address of the validator's operator; bech encoded in JSON.
|
||||||
ConsensusPubkey *types1.Any `protobuf:"bytes,2,opt,name=consensus_pubkey,json=consensusPubkey,proto3" json:"consensus_pubkey,omitempty" yaml:"consensus_pubkey"`
|
OperatorAddress string `protobuf:"bytes,1,opt,name=operator_address,json=operatorAddress,proto3" json:"operator_address,omitempty" yaml:"operator_address"`
|
||||||
Jailed bool `protobuf:"varint,3,opt,name=jailed,proto3" json:"jailed,omitempty"`
|
// consensus_pubkey is the consensus public key of the validator, as a Protobuf Any.
|
||||||
Status BondStatus `protobuf:"varint,4,opt,name=status,proto3,enum=cosmos.staking.v1beta1.BondStatus" json:"status,omitempty"`
|
ConsensusPubkey *types1.Any `protobuf:"bytes,2,opt,name=consensus_pubkey,json=consensusPubkey,proto3" json:"consensus_pubkey,omitempty" yaml:"consensus_pubkey"`
|
||||||
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"`
|
// jailed defined whether the validator has been jailed from bonded status or not.
|
||||||
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"`
|
Jailed bool `protobuf:"varint,3,opt,name=jailed,proto3" json:"jailed,omitempty"`
|
||||||
Description Description `protobuf:"bytes,7,opt,name=description,proto3" json:"description"`
|
// status is the validator status (bonded/unbonding/unbonded).
|
||||||
UnbondingHeight int64 `protobuf:"varint,8,opt,name=unbonding_height,json=unbondingHeight,proto3" json:"unbonding_height,omitempty" yaml:"unbonding_height"`
|
Status BondStatus `protobuf:"varint,4,opt,name=status,proto3,enum=cosmos.staking.v1beta1.BondStatus" json:"status,omitempty"`
|
||||||
UnbondingTime time.Time `protobuf:"bytes,9,opt,name=unbonding_time,json=unbondingTime,proto3,stdtime" json:"unbonding_time" yaml:"unbonding_time"`
|
// tokens define the delegated tokens (incl. self-delegation).
|
||||||
Commission Commission `protobuf:"bytes,10,opt,name=commission,proto3" json:"commission"`
|
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"`
|
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
|
// owned by one delegator, and is associated with the voting power of one
|
||||||
// validator.
|
// validator.
|
||||||
type Delegation struct {
|
type Delegation struct {
|
||||||
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
|
// delegator_address is the bech32-encoded address of the delegator.
|
||||||
ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty" yaml:"validator_address"`
|
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_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"`
|
// 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{} }
|
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
|
// UnbondingDelegation stores all of a single delegator's unbonding bonds
|
||||||
// for a single validator in an time-ordered list.
|
// for a single validator in an time-ordered list.
|
||||||
type UnbondingDelegation struct {
|
type UnbondingDelegation struct {
|
||||||
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
|
// delegator_address is the bech32-encoded address of the delegator.
|
||||||
ValidatorAddress string `protobuf:"bytes,2,opt,name=validator_address,json=validatorAddress,proto3" json:"validator_address,omitempty" yaml:"validator_address"`
|
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
|
||||||
Entries []UnbondingDelegationEntry `protobuf:"bytes,3,rep,name=entries,proto3" json:"entries"`
|
// 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{} }
|
func (m *UnbondingDelegation) Reset() { *m = UnbondingDelegation{} }
|
||||||
|
@ -646,10 +673,14 @@ var xxx_messageInfo_UnbondingDelegation proto.InternalMessageInfo
|
||||||
|
|
||||||
// UnbondingDelegationEntry defines an unbonding object with relevant metadata.
|
// UnbondingDelegationEntry defines an unbonding object with relevant metadata.
|
||||||
type UnbondingDelegationEntry struct {
|
type UnbondingDelegationEntry struct {
|
||||||
CreationHeight int64 `protobuf:"varint,1,opt,name=creation_height,json=creationHeight,proto3" json:"creation_height,omitempty" yaml:"creation_height"`
|
// creation_height is the height which the unbonding took place.
|
||||||
CompletionTime time.Time `protobuf:"bytes,2,opt,name=completion_time,json=completionTime,proto3,stdtime" json:"completion_time" yaml:"completion_time"`
|
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"`
|
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{} }
|
func (m *UnbondingDelegationEntry) Reset() { *m = UnbondingDelegationEntry{} }
|
||||||
|
@ -700,10 +731,14 @@ func (m *UnbondingDelegationEntry) GetCompletionTime() time.Time {
|
||||||
|
|
||||||
// RedelegationEntry defines a redelegation object with relevant metadata.
|
// RedelegationEntry defines a redelegation object with relevant metadata.
|
||||||
type RedelegationEntry struct {
|
type RedelegationEntry struct {
|
||||||
CreationHeight int64 `protobuf:"varint,1,opt,name=creation_height,json=creationHeight,proto3" json:"creation_height,omitempty" yaml:"creation_height"`
|
// creation_height defines the height which the redelegation took place.
|
||||||
CompletionTime time.Time `protobuf:"bytes,2,opt,name=completion_time,json=completionTime,proto3,stdtime" json:"completion_time" yaml:"completion_time"`
|
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"`
|
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{} }
|
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
|
// Redelegation contains the list of a particular delegator's redelegating bonds
|
||||||
// from a particular source validator to a particular destination validator.
|
// from a particular source validator to a particular destination validator.
|
||||||
type Redelegation struct {
|
type Redelegation struct {
|
||||||
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
|
// delegator_address is the bech32-encoded address of the delegator.
|
||||||
ValidatorSrcAddress string `protobuf:"bytes,2,opt,name=validator_src_address,json=validatorSrcAddress,proto3" json:"validator_src_address,omitempty" yaml:"validator_src_address"`
|
DelegatorAddress string `protobuf:"bytes,1,opt,name=delegator_address,json=delegatorAddress,proto3" json:"delegator_address,omitempty" yaml:"delegator_address"`
|
||||||
ValidatorDstAddress string `protobuf:"bytes,3,opt,name=validator_dst_address,json=validatorDstAddress,proto3" json:"validator_dst_address,omitempty" yaml:"validator_dst_address"`
|
// validator_src_address is the validator redelegation source operator address.
|
||||||
Entries []RedelegationEntry `protobuf:"bytes,4,rep,name=entries,proto3" json:"entries"`
|
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{} }
|
func (m *Redelegation) Reset() { *m = Redelegation{} }
|
||||||
|
@ -795,11 +834,16 @@ var xxx_messageInfo_Redelegation proto.InternalMessageInfo
|
||||||
|
|
||||||
// Params defines the parameters for the staking module.
|
// Params defines the parameters for the staking module.
|
||||||
type Params struct {
|
type Params struct {
|
||||||
UnbondingTime time.Duration `protobuf:"bytes,1,opt,name=unbonding_time,json=unbondingTime,proto3,stdduration" json:"unbonding_time" yaml:"unbonding_time"`
|
// unbonding_time is the time duration of unbonding.
|
||||||
MaxValidators uint32 `protobuf:"varint,2,opt,name=max_validators,json=maxValidators,proto3" json:"max_validators,omitempty" yaml:"max_validators"`
|
UnbondingTime time.Duration `protobuf:"bytes,1,opt,name=unbonding_time,json=unbondingTime,proto3,stdduration" json:"unbonding_time" yaml:"unbonding_time"`
|
||||||
MaxEntries uint32 `protobuf:"varint,3,opt,name=max_entries,json=maxEntries,proto3" json:"max_entries,omitempty" yaml:"max_entries"`
|
// max_validators is the maximum number of validators.
|
||||||
HistoricalEntries uint32 `protobuf:"varint,4,opt,name=historical_entries,json=historicalEntries,proto3" json:"historical_entries,omitempty" yaml:"historical_entries"`
|
MaxValidators uint32 `protobuf:"varint,2,opt,name=max_validators,json=maxValidators,proto3" json:"max_validators,omitempty" yaml:"max_validators"`
|
||||||
BondDenom string `protobuf:"bytes,5,opt,name=bond_denom,json=bondDenom,proto3" json:"bond_denom,omitempty" yaml:"bond_denom"`
|
// 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{} }
|
func (m *Params) Reset() { *m = Params{} }
|
||||||
|
|
Loading…
Reference in New Issue