Merge cosmos-sdk master

This commit is contained in:
psaradev 2021-02-03 22:36:29 +10:00
parent 2ce6d4bcba
commit cc4ef50d2a
159 changed files with 10601 additions and 722 deletions

View File

@ -42,6 +42,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}

View File

@ -31,6 +31,7 @@ jobs:
uses: docker/setup-buildx-action@v1
- name: Login to DockerHub
if: ${{ github.event_name != 'pull_request' }}
uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUBTM_USERNAME }}

View File

@ -31,6 +31,26 @@ jobs:
path: ~/go/bin
key: ${{ runner.os }}-go-tparse-binary
build:
runs-on: ubuntu-latest
strategy:
matrix:
go-arch: ["amd64", "arm", "arm64"]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2.1.3
with:
go-version: 1.15
- uses: technote-space/get-diff-action@v4
id: git_diff
with:
PATTERNS: |
**/**.go
go.mod
go.sum
- name: Build
run: GOARCH=${{ matrix.go-arch }} LEDGER_ENABLED=false make build
test-cosmovisor:
runs-on: ubuntu-latest
steps:

View File

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

View File

@ -479,4 +479,4 @@ rosetta-data:
docker run --name data_dir_build -t rosetta-ci:latest sh /rosetta/data.sh
docker cp data_dir_build:/tmp/data.tar.gz "$(CURDIR)/contrib/rosetta/node/data.tar.gz"
docker container rm data_dir_build
.PHONY: rosetta-data
.PHONY: rosetta-data

View File

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

View File

@ -97,8 +97,7 @@ func TestSetCmdClientContextHandler(t *testing.T) {
tc := tc
t.Run(tc.name, func(t *testing.T) {
ctx := context.Background()
ctx = context.WithValue(ctx, client.ClientContextKey, &client.Context{})
ctx := context.WithValue(context.Background(), client.ClientContextKey, &client.Context{})
cmd := newCmd()
_ = testutil.ApplyMockIODiscardOutErr(cmd)

View File

@ -44,6 +44,7 @@ type Context struct {
TxConfig TxConfig
AccountRetriever AccountRetriever
NodeURI string
FeeGranter sdk.AccAddress
// TODO: Deprecated (remove).
LegacyAmino *codec.LegacyAmino
@ -166,6 +167,13 @@ func (ctx Context) WithFromAddress(addr sdk.AccAddress) Context {
return ctx
}
// WithFeeGranterAddress returns a copy of the context with an updated fee granter account
// address.
func (ctx Context) WithFeeGranterAddress(addr sdk.AccAddress) Context {
ctx.FeeGranter = addr
return ctx
}
// WithBroadcastMode returns a copy of the context with an updated broadcast
// mode.
func (ctx Context) WithBroadcastMode(mode string) Context {

View File

@ -70,6 +70,7 @@ const (
FlagCountTotal = "count-total"
FlagTimeoutHeight = "timeout-height"
FlagKeyAlgorithm = "algo"
FlagFeeAccount = "fee-account"
// Tendermint logging flags
FlagLogLevel = "log_level"
@ -112,6 +113,7 @@ func AddTxFlagsToCmd(cmd *cobra.Command) {
cmd.Flags().String(FlagKeyringBackend, DefaultKeyringBackend, "Select keyring's backend (os|file|kwallet|pass|test)")
cmd.Flags().String(FlagSignMode, "", "Choose sign mode (direct|amino-json), this is an advanced feature")
cmd.Flags().Uint64(FlagTimeoutHeight, 0, "Set a block timeout height to prevent the tx from being committed past a certain height")
cmd.Flags().String(FlagFeeAccount, "", "Fee account pays fees for the transaction instead of deducting from the signer")
// --gas can accept integers and "auto"
cmd.Flags().String(FlagGas, "", fmt.Sprintf("gas limit to set per-transaction; set to %q to calculate sufficient gas automatically (default %d)", GasFlagAuto, DefaultGasLimit))

View File

@ -8,12 +8,12 @@ import (
"github.com/cosmos/cosmos-sdk/client"
)
func getBlock(clientCtx client.Context, height *int64) (*ctypes.ResultBlock, error) {
func getBlock(ctx context.Context, clientCtx client.Context, height *int64) (*ctypes.ResultBlock, error) {
// get the node
node, err := clientCtx.GetNode()
if err != nil {
return nil, err
}
return node.Block(context.Background(), height)
return node.Block(ctx, height)
}

View File

@ -34,8 +34,8 @@ func NewQueryServer(clientCtx client.Context, interfaceRegistry codectypes.Inter
}
// GetSyncing implements ServiceServer.GetSyncing
func (s queryServer) GetSyncing(_ context.Context, _ *GetSyncingRequest) (*GetSyncingResponse, error) {
status, err := getNodeStatus(s.clientCtx)
func (s queryServer) GetSyncing(ctx context.Context, _ *GetSyncingRequest) (*GetSyncingResponse, error) {
status, err := getNodeStatus(ctx, s.clientCtx)
if err != nil {
return nil, err
}
@ -45,8 +45,8 @@ func (s queryServer) GetSyncing(_ context.Context, _ *GetSyncingRequest) (*GetSy
}
// GetLatestBlock implements ServiceServer.GetLatestBlock
func (s queryServer) GetLatestBlock(context.Context, *GetLatestBlockRequest) (*GetLatestBlockResponse, error) {
status, err := getBlock(s.clientCtx, nil)
func (s queryServer) GetLatestBlock(ctx context.Context, _ *GetLatestBlockRequest) (*GetLatestBlockResponse, error) {
status, err := getBlock(ctx, s.clientCtx, nil)
if err != nil {
return nil, err
}
@ -64,7 +64,7 @@ func (s queryServer) GetLatestBlock(context.Context, *GetLatestBlockRequest) (*G
}
// GetBlockByHeight implements ServiceServer.GetBlockByHeight
func (s queryServer) GetBlockByHeight(_ context.Context, req *GetBlockByHeightRequest) (*GetBlockByHeightResponse, error) {
func (s queryServer) GetBlockByHeight(ctx context.Context, req *GetBlockByHeightRequest) (*GetBlockByHeightResponse, error) {
chainHeight, err := rpc.GetChainHeight(s.clientCtx)
if err != nil {
return nil, err
@ -74,7 +74,7 @@ func (s queryServer) GetBlockByHeight(_ context.Context, req *GetBlockByHeightRe
return nil, status.Error(codes.InvalidArgument, "requested block height is bigger then the chain length")
}
res, err := getBlock(s.clientCtx, &req.Height)
res, err := getBlock(ctx, s.clientCtx, &req.Height)
if err != nil {
return nil, err
}
@ -96,7 +96,7 @@ func (s queryServer) GetLatestValidatorSet(ctx context.Context, req *GetLatestVa
return nil, err
}
validatorsRes, err := rpc.GetValidators(s.clientCtx, nil, &page, &limit)
validatorsRes, err := rpc.GetValidators(ctx, s.clientCtx, nil, &page, &limit)
if err != nil {
return nil, err
}
@ -147,7 +147,7 @@ func (s queryServer) GetValidatorSetByHeight(ctx context.Context, req *GetValida
return nil, status.Error(codes.InvalidArgument, "requested block height is bigger then the chain length")
}
validatorsRes, err := rpc.GetValidators(s.clientCtx, &req.Height, &page, &limit)
validatorsRes, err := rpc.GetValidators(ctx, s.clientCtx, &req.Height, &page, &limit)
if err != nil {
return nil, err
@ -175,7 +175,7 @@ func (s queryServer) GetValidatorSetByHeight(ctx context.Context, req *GetValida
// GetNodeInfo implements ServiceServer.GetNodeInfo
func (s queryServer) GetNodeInfo(ctx context.Context, req *GetNodeInfoRequest) (*GetNodeInfoResponse, error) {
status, err := getNodeStatus(s.clientCtx)
status, err := getNodeStatus(ctx, s.clientCtx)
if err != nil {
return nil, err
}

View File

@ -8,10 +8,10 @@ import (
"github.com/cosmos/cosmos-sdk/client"
)
func getNodeStatus(clientCtx client.Context) (*ctypes.ResultStatus, error) {
func getNodeStatus(ctx context.Context, clientCtx client.Context) (*ctypes.ResultStatus, error) {
node, err := clientCtx.GetNode()
if err != nil {
return &ctypes.ResultStatus{}, err
}
return node.Status(context.Background())
return node.Status(ctx)
}

View File

@ -62,7 +62,7 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error {
var (
tmpDir string
migrator keyring.InfoImporter
migrator keyring.Importer
)
if dryRun, _ := cmd.Flags().GetBool(flags.FlagDryRun); dryRun {
@ -73,10 +73,10 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error {
defer os.RemoveAll(tmpDir)
migrator, err = keyring.NewInfoImporter(keyringServiceName, "test", tmpDir, buf)
migrator, err = keyring.New(keyringServiceName, keyring.BackendTest, tmpDir, buf)
} else {
backend, _ := cmd.Flags().GetString(flags.FlagKeyringBackend)
migrator, err = keyring.NewInfoImporter(keyringServiceName, backend, rootDir, buf)
migrator, err = keyring.New(keyringServiceName, backend, rootDir, buf)
}
if err != nil {
@ -86,12 +86,12 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error {
))
}
for _, key := range oldKeys {
legKeyInfo, err := legacyKb.Export(key.GetName())
if err != nil {
return err
}
if len(oldKeys) == 0 {
cmd.Print("Migration Aborted: no keys to migrate")
return nil
}
for _, key := range oldKeys {
keyName := key.GetName()
keyType := key.GetType()
@ -107,7 +107,12 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error {
}
if keyType != keyring.TypeLocal {
if err := migrator.Import(keyName, legKeyInfo); err != nil {
pubkeyArmor, err := legacyKb.ExportPubKey(keyName)
if err != nil {
return err
}
if err := migrator.ImportPubKey(keyName, pubkeyArmor); err != nil {
return err
}
@ -127,10 +132,11 @@ func runMigrateCmd(cmd *cobra.Command, args []string) error {
return err
}
if err := migrator.Import(keyName, armoredPriv); err != nil {
if err := migrator.ImportPrivKey(keyName, armoredPriv, migratePassphrase); err != nil {
return err
}
}
cmd.Print("Migration Complete")
return err
}

View File

@ -1,7 +1,6 @@
package keys
import (
"context"
"encoding/hex"
"errors"
"fmt"
@ -86,7 +85,7 @@ hexadecimal into bech32 cosmos prefixed format and vice versa.
}
func parseKey(cmd *cobra.Command, args []string) error {
config, _ := sdk.GetSealedConfig(context.Background())
config, _ := sdk.GetSealedConfig(cmd.Context())
return doParseKey(cmd, config, args)
}

Binary file not shown.

View File

@ -1 +1 @@
MANIFEST-000004
MANIFEST-000005

View File

@ -1 +1 @@
MANIFEST-000000
MANIFEST-000003

View File

@ -1,18 +1,30 @@
=============== Mar 30, 2020 (CEST) ===============
02:07:34.137606 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
02:07:34.144547 db@open opening
02:07:34.144770 version@stat F·[] S·0B[] Sc·[]
02:07:34.145843 db@janitor F·2 G·0
02:07:34.145875 db@open done T·1.315251ms
02:07:34.335635 db@close closing
02:07:34.335736 db@close done T·98.95µs
=============== Mar 30, 2020 (CEST) ===============
02:08:33.239115 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
02:08:33.239264 version@stat F·[] S·0B[] Sc·[]
02:08:33.239281 db@open opening
02:08:33.239310 journal@recovery F·1
02:08:33.239398 journal@recovery recovering @1
02:08:33.322008 memdb@flush created L0@2 N·4 S·391B "cos..ess,v4":"run..nfo,v3"
02:08:33.323091 version@stat F·[1] S·391B[391B] Sc·[0.25]
02:08:33.421979 db@janitor F·3 G·0
02:08:33.422153 db@open done T·182.707962ms
=============== Feb 2, 2021 (IST) ===============
00:03:25.348369 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
00:03:25.350695 db@open opening
00:03:25.350888 version@stat F·[] S·0B[] Sc·[]
00:03:25.351864 db@janitor F·2 G·0
00:03:25.351881 db@open done T·1.169825ms
00:03:25.351895 db@close closing
00:03:25.351929 db@close done T·33.042µs
=============== Feb 2, 2021 (IST) ===============
00:03:34.450638 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
00:03:34.450722 version@stat F·[] S·0B[] Sc·[]
00:03:34.450737 db@open opening
00:03:34.450765 journal@recovery F·1
00:03:34.450851 journal@recovery recovering @1
00:03:34.451173 version@stat F·[] S·0B[] Sc·[]
00:03:34.454278 db@janitor F·2 G·0
00:03:34.454298 db@open done T·3.548046ms
00:03:34.454307 db@close closing
00:03:34.454327 db@close done T·19.017µs
=============== Feb 2, 2021 (IST) ===============
00:03:42.025705 log@legend F·NumFile S·FileSize N·Entry C·BadEntry B·BadBlock Ke·KeyError D·DroppedEntry L·Level Q·SeqNum T·TimeElapsed
00:03:42.025892 version@stat F·[] S·0B[] Sc·[]
00:03:42.025907 db@open opening
00:03:42.025943 journal@recovery F·1
00:03:42.026790 journal@recovery recovering @2
00:03:42.026946 version@stat F·[] S·0B[] Sc·[]
00:03:42.031645 db@janitor F·2 G·0
00:03:42.031661 db@open done T·5.750008ms
00:03:42.283102 db@close closing
00:03:42.283162 db@close done T·58.775µs

Binary file not shown.

Binary file not shown.

View File

@ -57,6 +57,11 @@ func (ctx Context) GetFromAddress() sdk.AccAddress {
return ctx.FromAddress
}
// GetFeeGranterAddress returns the fee granter address from the context
func (ctx Context) GetFeeGranterAddress() sdk.AccAddress {
return ctx.FeeGranter
}
// GetFromName returns the key name for the current context.
func (ctx Context) GetFromName() string {
return ctx.FromName

View File

@ -50,7 +50,7 @@ func ValidatorCommand() *cobra.Command {
page, _ := cmd.Flags().GetInt(flags.FlagPage)
limit, _ := cmd.Flags().GetInt(flags.FlagLimit)
result, err := GetValidators(clientCtx, height, &page, &limit)
result, err := GetValidators(cmd.Context(), clientCtx, height, &page, &limit)
if err != nil {
return err
}
@ -117,14 +117,14 @@ func validatorOutput(validator *tmtypes.Validator) (ValidatorOutput, error) {
}
// GetValidators from client
func GetValidators(clientCtx client.Context, height *int64, page, limit *int) (ResultValidatorsOutput, error) {
func GetValidators(ctx context.Context, clientCtx client.Context, height *int64, page, limit *int) (ResultValidatorsOutput, error) {
// get the node
node, err := clientCtx.GetNode()
if err != nil {
return ResultValidatorsOutput{}, err
}
validatorsRes, err := node.Validators(context.Background(), height, page, limit)
validatorsRes, err := node.Validators(ctx, height, page, limit)
if err != nil {
return ResultValidatorsOutput{}, err
}
@ -172,7 +172,7 @@ func ValidatorSetRequestHandlerFn(clientCtx client.Context) http.HandlerFunc {
return
}
output, err := GetValidators(clientCtx, &height, &page, &limit)
output, err := GetValidators(r.Context(), clientCtx, &height, &page, &limit)
if rest.CheckInternalServerError(w, err) {
return
}
@ -189,7 +189,7 @@ func LatestValidatorSetRequestHandlerFn(clientCtx client.Context) http.HandlerFu
return
}
output, err := GetValidators(clientCtx, nil, &page, &limit)
output, err := GetValidators(r.Context(), clientCtx, nil, &page, &limit)
if rest.CheckInternalServerError(w, err) {
return
}

View File

@ -117,6 +117,7 @@ func BroadcastTx(clientCtx client.Context, txf Factory, msgs ...sdk.Msg) error {
}
}
tx.SetFeeGranter(clientCtx.GetFeeGranterAddress())
err = Sign(txf, clientCtx.GetFromName(), tx, true)
if err != nil {
return err

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

View File

@ -158,6 +158,8 @@ func TestUnarmorInfoBytesErrors(t *testing.T) {
}
func BenchmarkBcryptGenerateFromPassword(b *testing.B) {
b.ReportAllocs()
passphrase := []byte("passphrase")
for securityParam := 9; securityParam < 16; securityParam++ {
param := securityParam

View File

@ -108,6 +108,7 @@ type Signer interface {
type Importer interface {
// ImportPrivKey imports ASCII armored passphrase-encrypted private keys.
ImportPrivKey(uid, armor, passphrase string) error
// ImportPubKey imports ASCII armored public keys.
ImportPubKey(uid string, armor string) error
}

View File

@ -25,6 +25,7 @@ func (zeroReader) Read(buf []byte) (int, error) {
// BenchmarkKeyGeneration benchmarks the given key generation algorithm using
// a dummy reader.
func BenchmarkKeyGeneration(b *testing.B, generateKey func(reader io.Reader) types.PrivKey) {
b.ReportAllocs()
var zero zeroReader
for i := 0; i < b.N; i++ {
generateKey(zero)

View File

@ -9,6 +9,7 @@ import (
)
func BenchmarkKeyGeneration(b *testing.B) {
b.ReportAllocs()
benchmarkKeygenWrapper := func(reader io.Reader) types.PrivKey {
priv := genPrivKey(reader)
return &PrivKey{Key: priv}
@ -17,11 +18,13 @@ func BenchmarkKeyGeneration(b *testing.B) {
}
func BenchmarkSigning(b *testing.B) {
b.ReportAllocs()
priv := GenPrivKey()
benchmarking.BenchmarkSigning(b, priv)
}
func BenchmarkVerification(b *testing.B) {
b.ReportAllocs()
priv := GenPrivKey()
benchmarking.BenchmarkVerification(b, priv)
}

View File

@ -24,7 +24,7 @@ The actual implementation of `BeginBlocker` and `EndBlocker` in `./abci.go` are
A specificity of the `EndBlocker` is that it can return validator updates to the underlying consensus engine in the form of an [`[]abci.ValidatorUpdates`](https://tendermint.com/docs/app-dev/abci-spec.html#validatorupdate). This is the preferred way to implement custom validator changes.
It is possible for developers to defined the order of execution between the `BeginBlocker`/`EndBlocker` functions of each of their application's modules via the module's manager `SetOrderBeginBlocker`/`SetOrderEndBlocker` methods. For more on the module manager, click [here](./module-manager.md#manager).
It is possible for developers to define the order of execution between the `BeginBlocker`/`EndBlocker` functions of each of their application's modules via the module's manager `SetOrderBeginBlocker`/`SetOrderEndBlocker` methods. For more on the module manager, click [here](./module-manager.md#manager).
See an example implementation of `BeginBlocker` from the `distr` module:

View File

@ -74,7 +74,8 @@ Let us go through the methods of `AppModule`:
- `LegacyQuerierHandler(*codec.LegacyAmino)` (deprecated): Returns a [`querier`](./query-services.md#legacy-queriers) given the query `path`, in order to process the `query`.
- `RegisterServices(Configurator)`: Allows a module to register services.
- `BeginBlock(sdk.Context, abci.RequestBeginBlock)`: This method gives module developers the option to implement logic that is automatically triggered at the beginning of each block. Implement empty if no logic needs to be triggered at the beginning of each block for this module.
- `EndBlock(sdk.Context, abci.RequestEndBlock)`: This method gives module developers the option to implement logic that is automatically triggered at the beginning of each block. This is also where the module can inform the underlying consensus engine of validator set changes (e.g. the `staking` module). Implement empty if no logic needs to be triggered at the beginning of each block for this module.
- `EndBlock(sdk.Context, abci.RequestEndBlock)`: This method gives module developers the option to implement logic that is automatically triggered at the end of each block. This is also where the module can inform the underlying consensus engine of validator set changes (e.g. the `staking` module). Implement empty if no logic needs to be triggered at the end of each block for this module.
### Implementing the Application Module Interfaces
@ -132,7 +133,7 @@ The module manager is used throughout the application whenever an action on a co
- `SetOrderInitGenesis(moduleNames ...string)`: Sets the order in which the [`InitGenesis`](./genesis.md#initgenesis) function of each module will be called when the application is first started. This function is generally called from the application's main [constructor function](../basics/app-anatomy.md#constructor-function).
- `SetOrderExportGenesis(moduleNames ...string)`: Sets the order in which the [`ExportGenesis`](./genesis.md#exportgenesis) function of each module will be called in case of an export. This function is generally called from the application's main [constructor function](../basics/app-anatomy.md#constructor-function).
- `SetOrderBeginBlockers(moduleNames ...string)`: Sets the order in which the `BeginBlock()` function of each module will be called at the beginning of each block. This function is generally called from the application's main [constructor function](../basics/app-anatomy.md#constructor-function).
- `SetOrderEndBlockers(moduleNames ...string)`: Sets the order in which the `EndBlock()` function of each module will be called at the beginning of each block. This function is generally called from the application's main [constructor function](../basics/app-anatomy.md#constructor-function).
- `SetOrderEndBlockers(moduleNames ...string)`: Sets the order in which the `EndBlock()` function of each module will be called at the end of each block. This function is generally called from the application's main [constructor function](../basics/app-anatomy.md#constructor-function).
- `RegisterInvariants(ir sdk.InvariantRegistry)`: Registers the [invariants](./invariants.md) of each module.
- `RegisterRoutes(router sdk.Router, queryRouter sdk.QueryRouter, legacyQuerierCdc *codec.LegacyAmino)`: Registers legacy [`Msg`](./messages-and-queries.md#messages) and [`querier`](./query-services.md#legacy-queriers) routes.
- `RegisterServices(cfg Configurator)`: Registers all module services.

View File

@ -108,6 +108,24 @@ Flags are added to commands directly (generally in the [module's CLI file](../bu
+++ https://github.com/cosmos/cosmos-sdk/blob/v0.40.0/simapp/simd/cmd/root.go#L118
## Environment variables
Each flag is bound to it's respecteve named environment variable. Then name of the environment variable consist of two parts - capital case `basename` followed by flag name of the flag. `-` must be substituted with `_`. For example flag `--home` for application with basename `GAIA` is bound to `GAIA_HOME`. It allows to reduce amount of flags typed for routine operations. For example instead of:
```sh
gaia --home=./ --node=<node address> --chain-id="testchain-1" --keyring-backend=test tx ... --from=<key name>
```
this will be more convinient:
```sh
# define env variables in .env, .envrc etc
GAIA_HOME=<path to home>
GAIA_NODE=<node address>
GAIA_CHAIN_ID="testchain-1"
GAIA_KEYRING_BACKEND="test"
# and later just use
gaia tx ... --from=<key name>
```
## Configurations
It is vital that the root command of an application uses `PersistentPreRun()` cobra command property for executing the command, so all child commands have access to the server and client contexts. These contexts are set as their default values initially and maybe modified, scoped to the command, in their respective `PersistentPreRun()` functions. Note that the `client.Context` is typically pre-populated with "default" values that may be useful for all commands to inherit and override if necessary.

View File

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

View File

@ -16,7 +16,7 @@ There are multiple ways to interact with a node: using the CLI, using gRPC or us
Now that your chain is running, it is time to try sending tokens from the first account you created to a second account. In a new terminal window, start by running the following query command:
```bash
simd query account $MY_VALIDATOR_ADDRESS --chain-id my-test-chain
simd query bank balances $MY_VALIDATOR_ADDRESS --chain-id my-test-chain
```
You should see the current balance of the account you created, equal to the original balance of `stake` you granted it minus the amount you delegated via the `gentx`. Now, create a second account:
@ -31,16 +31,16 @@ RECIPIENT=$(simd keys show recipient -a --keyring-backend test)
The command above creates a local key-pair that is not yet registered on the chain. An account is created the first time it receives tokens from another account. Now, run the following command to send tokens to the `recipient` account:
```bash
simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000stake --chain-id my-test-chain
simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000000stake --chain-id my-test-chain --keyring-backend test
# Check that the recipient account did receive the tokens.
simd query account $RECIPIENT --chain-id my-test-chain
simd query bank balances $RECIPIENT --chain-id my-test-chain
```
Finally, delegate some of the stake tokens sent to the `recipient` account to the validator:
```bash
simd tx staking delegate $(simd keys show my_validator --bech val -a --keyring-backend test) 500stake --from recipient --chain-id my-test-chain
simd tx staking delegate $(simd keys show my_validator --bech val -a --keyring-backend test) 500stake --from recipient --chain-id my-test-chain --keyring-backend test
# Query the total delegations to `validator`.
simd query staking delegations-to $(simd keys show my_validator --bech val -a --keyring-backend test) --chain-id my-test-chain

View File

@ -44,7 +44,7 @@ Before starting the chain, you need to populate the state with at least one acco
Now that you have created a local account, go ahead and grant it some `stake` tokens in your chain's genesis file. Doing so will also make sure your chain is aware of this account's existence:
```bash
simd add-genesis-account $MY_VALIDATOR_ADDRESS 100000000stake
simd add-genesis-account $MY_VALIDATOR_ADDRESS 100000000000stake
```
Recall that `$MY_VALIDATOR_ADDRESS` is a variable that holds the address of the `my_validator` key in the [keyring](./keyring.md#adding-keys-to-the-keyring). Also note that the tokens in the SDK have the `{amount}{denom}` format: `amount` is is a 18-digit-precision decimal number, and `denom` is the unique token identifier with its denomination key (e.g. `atom` or `uatom`). Here, we are granting `stake` tokens, as `stake` is the token identifier used for staking in [`simapp`](https://github.com/cosmos/cosmos-sdk/tree/v0.40.0-rc3/simapp). For your own chain with its own staking denom, that token identifier should be used instead.
@ -53,7 +53,7 @@ Now that your account has some tokens, you need to add a validator to your chain
```bash
# Create a gentx.
simd gentx my_validator 100000stake --chain-id my-test-chain --keyring-backend test
simd gentx my_validator 100000000stake --chain-id my-test-chain --keyring-backend test
# Add the gentx to the genesis file.
simd collect-gentxs

View File

@ -11,7 +11,7 @@ This document describes how to generate an (unsigned) transaction, signing it (w
The easiest way to send transactions is using the CLI, as we have seen in the previous page when [interacting with a node](./interact-node.md#using-the-cli). For example, running the following command
```bash
simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000stake --chain-id my-test-chain
simd tx bank send $MY_VALIDATOR_ADDRESS $RECIPIENT 1000stake --chain-id my-test-chain --keyring-backend test
```
will run the following steps:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@ func ShowNodeIDCmd() *cobra.Command {
serverCtx := GetServerContextFromCmd(cmd)
cfg := serverCtx.Config
nodeKey, err := p2p.LoadOrGenNodeKey(cfg.NodeKeyFile())
nodeKey, err := p2p.LoadNodeKey(cfg.NodeKeyFile())
if err != nil {
return err
}
@ -48,7 +48,7 @@ func ShowValidatorCmd() *cobra.Command {
serverCtx := GetServerContextFromCmd(cmd)
cfg := serverCtx.Config
privValidator := pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile())
privValidator := pvm.LoadFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile())
valPubKey, err := privValidator.GetPubKey()
if err != nil {
return err
@ -86,7 +86,7 @@ func ShowAddressCmd() *cobra.Command {
serverCtx := GetServerContextFromCmd(cmd)
cfg := serverCtx.Config
privValidator := pvm.LoadOrGenFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile())
privValidator := pvm.LoadFilePV(cfg.PrivValidatorKeyFile(), cfg.PrivValidatorStateFile())
valConsAddr := (sdk.ConsAddress)(privValidator.GetAddress())
output, _ := cmd.Flags().GetString(cli.OutputFlag)

View File

@ -17,6 +17,7 @@ import (
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/spf13/viper"
tmcfg "github.com/tendermint/tendermint/config"
tmlog "github.com/tendermint/tendermint/libs/log"
@ -63,6 +64,37 @@ func NewContext(v *viper.Viper, config *tmcfg.Config, logger tmlog.Logger) *Cont
return &Context{v, config, logger}
}
func bindFlags(basename string, cmd *cobra.Command, v *viper.Viper) (err error) {
defer func() {
recover()
}()
cmd.Flags().VisitAll(func(f *pflag.Flag) {
// Environment variables can't have dashes in them, so bind them to their equivalent
// keys with underscores, e.g. --favorite-color to STING_FAVORITE_COLOR
err = v.BindEnv(f.Name, fmt.Sprintf("%s_%s", basename, strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_"))))
if err != nil {
panic(err)
}
err = v.BindPFlag(f.Name, f)
if err != nil {
panic(err)
}
// Apply the viper config value to the flag when the flag is not set and viper has a value
if !f.Changed && v.IsSet(f.Name) {
val := v.Get(f.Name)
err = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val))
if err != nil {
panic(err)
}
}
})
return
}
// InterceptConfigsPreRunHandler performs a pre-run function for the root daemon
// application command. It will create a Viper literal and a default server
// Context. The server Tendermint configuration will either be read and parsed
@ -98,6 +130,9 @@ func InterceptConfigsPreRunHandler(cmd *cobra.Command) error {
// return value is a tendermint configuration object
serverCtx.Config = config
if err = bindFlags(basename, cmd, serverCtx.Viper); err != nil {
return err
}
var logWriter io.Writer
if strings.ToLower(serverCtx.Viper.GetString(flags.FlagLogFormat)) == tmcfg.LogFormatPlain {

View File

@ -55,6 +55,10 @@ import (
"github.com/cosmos/cosmos-sdk/x/evidence"
evidencekeeper "github.com/cosmos/cosmos-sdk/x/evidence/keeper"
evidencetypes "github.com/cosmos/cosmos-sdk/x/evidence/types"
feegrant "github.com/cosmos/cosmos-sdk/x/feegrant"
feegrantante "github.com/cosmos/cosmos-sdk/x/feegrant/ante"
feegrantkeeper "github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
feegranttypes "github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/cosmos/cosmos-sdk/x/genutil"
genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types"
"github.com/cosmos/cosmos-sdk/x/gov"
@ -120,6 +124,7 @@ var (
crisis.AppModuleBasic{},
slashing.AppModuleBasic{},
ibc.AppModuleBasic{},
feegrant.AppModuleBasic{},
upgrade.AppModuleBasic{},
evidence.AppModuleBasic{},
transfer.AppModuleBasic{},
@ -181,6 +186,7 @@ type SimApp struct {
IBCKeeper *ibckeeper.Keeper // IBC Keeper must be a pointer in the app, so we can SetRouter on it correctly
EvidenceKeeper evidencekeeper.Keeper
TransferKeeper ibctransferkeeper.Keeper
FeeGrantKeeper feegrantkeeper.Keeper
// make scoped keepers public for test purposes
ScopedIBCKeeper capabilitykeeper.ScopedKeeper
@ -222,7 +228,7 @@ func NewSimApp(
keys := sdk.NewKVStoreKeys(
authtypes.StoreKey, banktypes.StoreKey, stakingtypes.StoreKey,
minttypes.StoreKey, distrtypes.StoreKey, slashingtypes.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey,
govtypes.StoreKey, paramstypes.StoreKey, ibchost.StoreKey, upgradetypes.StoreKey, feegranttypes.StoreKey,
evidencetypes.StoreKey, ibctransfertypes.StoreKey, capabilitytypes.StoreKey,
authztypes.StoreKey,
)
@ -277,6 +283,8 @@ func NewSimApp(
app.CrisisKeeper = crisiskeeper.NewKeeper(
app.GetSubspace(crisistypes.ModuleName), invCheckPeriod, app.BankKeeper, authtypes.FeeCollectorName,
)
app.FeeGrantKeeper = feegrantkeeper.NewKeeper(appCodec, keys[feegranttypes.StoreKey], app.AccountKeeper)
app.UpgradeKeeper = upgradekeeper.NewKeeper(skipUpgradeHeights, keys[upgradetypes.StoreKey], appCodec, homePath)
// register the staking hooks
@ -347,6 +355,7 @@ func NewSimApp(
bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
capability.NewAppModule(appCodec, *app.CapabilityKeeper),
crisis.NewAppModule(&app.CrisisKeeper, skipGenesisInvariants),
feegrant.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry),
gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper),
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
slashing.NewAppModule(appCodec, app.SlashingKeeper, app.AccountKeeper, app.BankKeeper, app.StakingKeeper),
@ -379,6 +388,7 @@ func NewSimApp(
capabilitytypes.ModuleName, authtypes.ModuleName, banktypes.ModuleName, distrtypes.ModuleName, stakingtypes.ModuleName,
slashingtypes.ModuleName, govtypes.ModuleName, minttypes.ModuleName, crisistypes.ModuleName,
ibchost.ModuleName, genutiltypes.ModuleName, evidencetypes.ModuleName, authztypes.ModuleName, ibctransfertypes.ModuleName,
feegranttypes.ModuleName,
)
app.mm.RegisterInvariants(&app.CrisisKeeper)
@ -396,6 +406,7 @@ func NewSimApp(
auth.NewAppModule(appCodec, app.AccountKeeper, authsims.RandomGenesisAccounts),
bank.NewAppModule(appCodec, app.BankKeeper, app.AccountKeeper),
capability.NewAppModule(appCodec, *app.CapabilityKeeper),
feegrant.NewAppModule(appCodec, app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, app.interfaceRegistry),
gov.NewAppModule(appCodec, app.GovKeeper, app.AccountKeeper, app.BankKeeper),
mint.NewAppModule(appCodec, app.MintKeeper, app.AccountKeeper),
staking.NewAppModule(appCodec, app.StakingKeeper, app.AccountKeeper, app.BankKeeper),
@ -419,8 +430,8 @@ func NewSimApp(
app.SetInitChainer(app.InitChainer)
app.SetBeginBlocker(app.BeginBlocker)
app.SetAnteHandler(
ante.NewAnteHandler(
app.AccountKeeper, app.BankKeeper, ante.DefaultSigVerificationGasConsumer,
feegrantante.NewAnteHandler(
app.AccountKeeper, app.BankKeeper, app.FeeGrantKeeper, ante.DefaultSigVerificationGasConsumer,
encodingConfig.TxConfig.SignModeHandler(),
),
)

View File

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

View File

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

View File

@ -14,6 +14,7 @@ import (
// Profile with:
// /usr/local/go/bin/go test -benchmem -run=^$ github.com/cosmos/cosmos-sdk/simapp -bench ^BenchmarkFullAppSimulation$ -Commit=true -cpuprofile cpu.out
func BenchmarkFullAppSimulation(b *testing.B) {
b.ReportAllocs()
config, db, dir, logger, _, err := SetupSimulation("goleveldb-app-sim", "Simulation")
if err != nil {
b.Fatalf("simulation setup failed: %s", err.Error())
@ -57,6 +58,7 @@ func BenchmarkFullAppSimulation(b *testing.B) {
}
func BenchmarkInvariants(b *testing.B) {
b.ReportAllocs()
config, db, dir, logger, _, err := SetupSimulation("leveldb-app-invariant-bench", "Simulation")
if err != nil {
b.Fatalf("simulation setup failed: %s", err.Error())

View File

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

View File

@ -91,7 +91,7 @@ func (s *Store) Get(height uint64, format uint32) (*types.Snapshot, error) {
// Get fetches the latest snapshot from the database, if any.
func (s *Store) GetLatest() (*types.Snapshot, error) {
iter, err := s.db.ReverseIterator(encodeKey(0, 0), encodeKey(math.MaxUint64, math.MaxUint32))
iter, err := s.db.ReverseIterator(encodeKey(0, 0), encodeKey(uint64(math.MaxUint64), math.MaxUint32))
if err != nil {
return nil, sdkerrors.Wrap(err, "failed to find latest snapshot")
}
@ -111,7 +111,7 @@ func (s *Store) GetLatest() (*types.Snapshot, error) {
// List lists snapshots, in reverse order (newest first).
func (s *Store) List() ([]*types.Snapshot, error) {
iter, err := s.db.ReverseIterator(encodeKey(0, 0), encodeKey(math.MaxUint64, math.MaxUint32))
iter, err := s.db.ReverseIterator(encodeKey(0, 0), encodeKey(uint64(math.MaxUint64), math.MaxUint32))
if err != nil {
return nil, sdkerrors.Wrap(err, "failed to list snapshots")
}
@ -181,7 +181,7 @@ func (s *Store) loadChunkFile(height uint64, format uint32, chunk uint32) (io.Re
// Prune removes old snapshots. The given number of most recent heights (regardless of format) are retained.
func (s *Store) Prune(retain uint32) (uint64, error) {
iter, err := s.db.ReverseIterator(encodeKey(0, 0), encodeKey(math.MaxUint64, math.MaxUint32))
iter, err := s.db.ReverseIterator(encodeKey(0, 0), encodeKey(uint64(math.MaxUint64), math.MaxUint32))
if err != nil {
return 0, sdkerrors.Wrap(err, "failed to prune snapshots")
}

View File

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

View File

@ -22,6 +22,7 @@ func populate(mgr *CommitKVStoreCacheManager) {
}
func BenchmarkReset(b *testing.B) {
b.ReportAllocs()
mgr := freshMgr()
b.ResetTimer()

View File

@ -12,6 +12,7 @@ import (
)
func benchmarkCacheKVStoreIterator(numKVs int, b *testing.B) {
b.ReportAllocs()
mem := dbadapter.Store{DB: dbm.NewMemDB()}
cstore := cachekv.NewStore(mem)
keys := make([]string, numKVs)

View File

@ -516,6 +516,7 @@ func (krc *keyRangeCounter) key() int {
func bz(s string) []byte { return []byte(s) }
func BenchmarkCacheKVStoreGetNoKeyFound(b *testing.B) {
b.ReportAllocs()
st := newCacheKVStore()
b.ResetTimer()
// assumes b.N < 2**24
@ -525,6 +526,7 @@ func BenchmarkCacheKVStoreGetNoKeyFound(b *testing.B) {
}
func BenchmarkCacheKVStoreGetKeyFound(b *testing.B) {
b.ReportAllocs()
st := newCacheKVStore()
for i := 0; i < b.N; i++ {
arr := []byte{byte((i & 0xFF0000) >> 16), byte((i & 0xFF00) >> 8), byte(i & 0xFF)}

View File

@ -556,6 +556,7 @@ func TestIAVLStoreQuery(t *testing.T) {
}
func BenchmarkIAVLIteratorNext(b *testing.B) {
b.ReportAllocs()
db := dbm.NewMemDB()
treeSize := 1000
tree, err := iavl.NewMutableTree(db, cacheSize)

View File

@ -711,9 +711,9 @@ func (rs *Store) Restore(
if height == 0 {
return sdkerrors.Wrap(sdkerrors.ErrLogic, "cannot restore snapshot at height 0")
}
if height > math.MaxInt64 {
if height > uint64(math.MaxInt64) {
return sdkerrors.Wrapf(snapshottypes.ErrInvalidMetadata,
"snapshot height %v cannot exceed %v", height, math.MaxInt64)
"snapshot height %v cannot exceed %v", height, int64(math.MaxInt64))
}
// Signal readiness. Must be done before the readers below are set up, since the zlib

View File

@ -689,6 +689,7 @@ func BenchmarkMultistoreSnapshotRestore1M(b *testing.B) {
}
func benchmarkMultistoreSnapshot(b *testing.B, stores uint8, storeKeys uint64) {
b.ReportAllocs()
b.StopTimer()
source := newMultiStoreWithGeneratedData(dbm.NewMemDB(), stores, storeKeys)
version := source.LastCommitID().Version
@ -716,6 +717,7 @@ func benchmarkMultistoreSnapshot(b *testing.B, stores uint8, storeKeys uint64) {
}
func benchmarkMultistoreSnapshotRestore(b *testing.B, stores uint8, storeKeys uint64) {
b.ReportAllocs()
b.StopTimer()
source := newMultiStoreWithGeneratedData(dbm.NewMemDB(), stores, storeKeys)
version := uint64(source.LastCommitID().Version)

View File

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

View File

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

View File

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

View File

@ -12,6 +12,7 @@ import (
)
func BenchmarkBech32ifyPubKey(b *testing.B) {
b.ReportAllocs()
pkBz := make([]byte, ed25519.PubKeySize)
pk := &ed25519.PubKey{Key: pkBz}
rng := rand.New(rand.NewSource(time.Now().Unix()))
@ -29,6 +30,7 @@ func BenchmarkBech32ifyPubKey(b *testing.B) {
}
func BenchmarkGetPubKeyFromBech32(b *testing.B) {
b.ReportAllocs()
pkBz := make([]byte, ed25519.PubKeySize)
pk := &ed25519.PubKey{Key: pkBz}
rng := rand.New(rand.NewSource(time.Now().Unix()))

View File

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

View File

@ -6,10 +6,11 @@ import (
)
func coinName(suffix int) string {
return fmt.Sprintf("COINZ_%d", suffix)
return fmt.Sprintf("coinz%d", suffix)
}
func BenchmarkCoinsAdditionIntersect(b *testing.B) {
b.ReportAllocs()
benchmarkingFunc := func(numCoinsA int, numCoinsB int) func(b *testing.B) {
return func(b *testing.B) {
coinsA := Coins(make([]Coin, numCoinsA))
@ -39,6 +40,7 @@ func BenchmarkCoinsAdditionIntersect(b *testing.B) {
}
func BenchmarkCoinsAdditionNoIntersect(b *testing.B) {
b.ReportAllocs()
benchmarkingFunc := func(numCoinsA int, numCoinsB int) func(b *testing.B) {
return func(b *testing.B) {
coinsA := Coins(make([]Coin, numCoinsA))

View File

@ -487,6 +487,7 @@ func (s *decimalTestSuite) TestOperationOrders() {
}
func BenchmarkMarshalTo(b *testing.B) {
b.ReportAllocs()
bis := []struct {
in sdk.Dec
want []byte

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package cli
import (
"context"
"fmt"
"strings"
@ -58,7 +57,7 @@ $ <appd> query auth params
}
queryClient := types.NewQueryClient(clientCtx)
res, err := queryClient.Params(context.Background(), &types.QueryParamsRequest{})
res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{})
if err != nil {
return err
}
@ -90,7 +89,7 @@ func GetAccountCmd() *cobra.Command {
}
queryClient := types.NewQueryClient(clientCtx)
res, err := queryClient.Account(context.Background(), &types.QueryAccountRequest{Address: key.String()})
res, err := queryClient.Account(cmd.Context(), &types.QueryAccountRequest{Address: key.String()})
if err != nil {
return err
}

View File

@ -7,6 +7,7 @@ import (
)
func BenchmarkAccountMapperGetAccountFound(b *testing.B) {
b.ReportAllocs()
app, ctx := createTestApp(false)
// assumes b.N < 2**24

View File

@ -84,6 +84,9 @@ func (s *StdTxBuilder) SetTimeoutHeight(height uint64) {
s.TimeoutHeight = height
}
// SetFeeGranter does nothing for stdtx
func (s *StdTxBuilder) SetFeeGranter(_ sdk.AccAddress) {}
// StdTxConfig is a context.TxConfig for StdTx
type StdTxConfig struct {
Cdc *codec.LegacyAmino

View File

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

View File

@ -18,6 +18,7 @@ import (
var moduleAccAddr = authtypes.NewModuleAddress(stakingtypes.BondedPoolName)
func BenchmarkOneBankSendTxPerBlock(b *testing.B) {
b.ReportAllocs()
// Add an account at genesis
acc := authtypes.BaseAccount{
Address: addr1.String(),

View File

@ -74,11 +74,10 @@ Example:
if err != nil {
return err
}
ctx := cmd.Context()
if denom == "" {
params := types.NewQueryAllBalancesRequest(addr, pageReq)
res, err := queryClient.AllBalances(cmd.Context(), params)
res, err := queryClient.AllBalances(ctx, params)
if err != nil {
return err
}
@ -86,7 +85,7 @@ Example:
}
params := types.NewQueryBalanceRequest(addr, denom)
res, err := queryClient.Balance(cmd.Context(), params)
res, err := queryClient.Balance(ctx, params)
if err != nil {
return err
}
@ -183,9 +182,9 @@ To query for the total supply of a specific coin denomination use:
}
queryClient := types.NewQueryClient(clientCtx)
ctx := cmd.Context()
if denom == "" {
res, err := queryClient.TotalSupply(cmd.Context(), &types.QueryTotalSupplyRequest{})
res, err := queryClient.TotalSupply(ctx, &types.QueryTotalSupplyRequest{})
if err != nil {
return err
}
@ -193,7 +192,7 @@ To query for the total supply of a specific coin denomination use:
return clientCtx.PrintProto(res)
}
res, err := queryClient.SupplyOf(cmd.Context(), &types.QuerySupplyOfRequest{Denom: denom})
res, err := queryClient.SupplyOf(ctx, &types.QuerySupplyOfRequest{Denom: denom})
if err != nil {
return err
}

View File

@ -1,7 +1,6 @@
package testutil
import (
"context"
"fmt"
"github.com/spf13/cobra"
@ -59,7 +58,7 @@ ignored as it is implied from [from_key_or_address].`,
msg := types.NewMsgSend(clientCtx.GetFromAddress(), toAddr, coins)
svcMsgClientConn := &msgservice.ServiceMsgClientConn{}
bankMsgClient := types.NewMsgClient(svcMsgClientConn)
_, err = bankMsgClient.Send(context.Background(), msg)
_, err = bankMsgClient.Send(cmd.Context(), msg)
if err != nil {
return err
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,6 @@
package cli
import (
"context"
"fmt"
"strconv"
"strings"
@ -50,7 +49,7 @@ func GetCmdQueryParams() *cobra.Command {
}
queryClient := types.NewQueryClient(clientCtx)
res, err := queryClient.Params(context.Background(), &types.QueryParamsRequest{})
res, err := queryClient.Params(cmd.Context(), &types.QueryParamsRequest{})
if err != nil {
return err
}
@ -94,7 +93,7 @@ $ %s query distribution validator-outstanding-rewards %s1lwjmdnks33xwnmfayc64ycp
}
res, err := queryClient.ValidatorOutstandingRewards(
context.Background(),
cmd.Context(),
&types.QueryValidatorOutstandingRewardsRequest{ValidatorAddress: validatorAddr.String()},
)
if err != nil {
@ -139,7 +138,7 @@ $ %s query distribution commission %s1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj
}
res, err := queryClient.ValidatorCommission(
context.Background(),
cmd.Context(),
&types.QueryValidatorCommissionRequest{ValidatorAddress: validatorAddr.String()},
)
if err != nil {
@ -199,7 +198,7 @@ $ %s query distribution slashes %svaloper1gghjut3ccd8ay0zduzj64hwre2fxs9ldmqhffj
}
res, err := queryClient.ValidatorSlashes(
context.Background(),
cmd.Context(),
&types.QueryValidatorSlashesRequest{
ValidatorAddress: validatorAddr.String(),
StartingHeight: startHeight,
@ -252,6 +251,7 @@ $ %s query distribution rewards %s1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p %s1ggh
}
// query for rewards from a particular delegation
ctx := cmd.Context()
if len(args) == 2 {
validatorAddr, err := sdk.ValAddressFromBech32(args[1])
if err != nil {
@ -259,7 +259,7 @@ $ %s query distribution rewards %s1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p %s1ggh
}
res, err := queryClient.DelegationRewards(
context.Background(),
ctx,
&types.QueryDelegationRewardsRequest{DelegatorAddress: delegatorAddr.String(), ValidatorAddress: validatorAddr.String()},
)
if err != nil {
@ -270,7 +270,7 @@ $ %s query distribution rewards %s1gghjut3ccd8ay0zduzj64hwre2fxs9ld75ru9p %s1ggh
}
res, err := queryClient.DelegationTotalRewards(
context.Background(),
ctx,
&types.QueryDelegationTotalRewardsRequest{DelegatorAddress: delegatorAddr.String()},
)
if err != nil {
@ -307,7 +307,7 @@ $ %s query distribution community-pool
}
queryClient := types.NewQueryClient(clientCtx)
res, err := queryClient.CommunityPool(context.Background(), &types.QueryCommunityPoolRequest{})
res, err := queryClient.CommunityPool(cmd.Context(), &types.QueryCommunityPoolRequest{})
if err != nil {
return err
}

View File

@ -1,7 +1,6 @@
package cli
import (
"context"
"fmt"
"strings"
@ -155,7 +154,7 @@ $ %s tx distribution withdraw-all-rewards --from mykey
}
queryClient := types.NewQueryClient(clientCtx)
delValsRes, err := queryClient.DelegatorValidators(context.Background(), &types.QueryDelegatorValidatorsRequest{DelegatorAddress: delAddr.String()})
delValsRes, err := queryClient.DelegatorValidators(cmd.Context(), &types.QueryDelegatorValidatorsRequest{DelegatorAddress: delAddr.String()})
if err != nil {
return err
}

View File

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

View File

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

View File

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

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

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

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

@ -0,0 +1,82 @@
package ante
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
authante "github.com/cosmos/cosmos-sdk/x/auth/ante"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
// DeductGrantedFeeDecorator deducts fees from fee_payer or fee_granter (if exists a valid fee allowance) of the tx
// If the fee_payer or fee_granter does not have the funds to pay for the fees, return with InsufficientFunds error
// Call next AnteHandler if fees successfully deducted
// CONTRACT: Tx must implement GrantedFeeTx interface to use DeductGrantedFeeDecorator
type DeductGrantedFeeDecorator struct {
ak types.AccountKeeper
k keeper.Keeper
bk types.BankKeeper
}
func NewDeductGrantedFeeDecorator(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keeper) DeductGrantedFeeDecorator {
return DeductGrantedFeeDecorator{
ak: ak,
k: k,
bk: bk,
}
}
// AnteHandle performs a decorated ante-handler responsible for deducting transaction
// fees. Fees will be deducted from the account designated by the FeePayer on a
// transaction by default. However, if the fee payer differs from the transaction
// signer, the handler will check if a fee grant has been authorized. If the
// transaction's signer does not exist, it will be created.
func (d DeductGrantedFeeDecorator) AnteHandle(ctx sdk.Context, tx sdk.Tx, simulate bool, next sdk.AnteHandler) (newCtx sdk.Context, err error) {
feeTx, ok := tx.(sdk.FeeTx)
if !ok {
return ctx, sdkerrors.Wrap(sdkerrors.ErrTxDecode, "Tx must be a GrantedFeeTx")
}
// sanity check from DeductFeeDecorator
if addr := d.ak.GetModuleAddress(authtypes.FeeCollectorName); addr == nil {
panic(fmt.Sprintf("%s module account has not been set", authtypes.FeeCollectorName))
}
fee := feeTx.GetFee()
feePayer := feeTx.FeePayer()
feeGranter := feeTx.FeeGranter()
deductFeesFrom := feePayer
// ensure the grant is allowed, if we request a different fee payer
if feeGranter != nil && !feeGranter.Equals(feePayer) {
err := d.k.UseGrantedFees(ctx, feeGranter, feePayer, fee)
if err != nil {
return ctx, sdkerrors.Wrapf(err, "%s not allowed to pay fees from %s", feeGranter, feePayer)
}
deductFeesFrom = feeGranter
}
// now, either way, we know that we are authorized to deduct the fees from the deductFeesFrom account
deductFeesFromAcc := d.ak.GetAccount(ctx, deductFeesFrom)
if deductFeesFromAcc == nil {
return ctx, sdkerrors.Wrapf(sdkerrors.ErrUnknownAddress, "fee payer address: %s does not exist", deductFeesFrom)
}
// move on if there is no fee to deduct
if fee.IsZero() {
return next(ctx, tx, simulate)
}
// deduct fee if non-zero
err = authante.DeductFees(d.bk, ctx, deductFeesFromAcc, fee)
if err != nil {
return ctx, err
}
return next(ctx, tx, simulate)
}

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

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

View File

@ -0,0 +1,621 @@
// +build norace
package cli_test
import (
"fmt"
"testing"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/crypto/hd"
"github.com/cosmos/cosmos-sdk/crypto/keyring"
clitestutil "github.com/cosmos/cosmos-sdk/testutil/cli"
"github.com/cosmos/cosmos-sdk/testutil/network"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/feegrant/client/cli"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
govtestutil "github.com/cosmos/cosmos-sdk/x/gov/client/testutil"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"github.com/gogo/protobuf/proto"
"github.com/stretchr/testify/suite"
tmcli "github.com/tendermint/tendermint/libs/cli"
)
type IntegrationTestSuite struct {
suite.Suite
cfg network.Config
network *network.Network
addedGranter sdk.AccAddress
addedGrantee sdk.AccAddress
addedGrant types.FeeAllowanceGrant
}
func (s *IntegrationTestSuite) SetupSuite() {
s.T().Log("setting up integration test suite")
if testing.Short() {
s.T().Skip("skipping test in unit-tests mode.")
}
cfg := network.DefaultConfig()
cfg.NumValidators = 2
s.cfg = cfg
s.network = network.New(s.T(), cfg)
_, err := s.network.WaitForHeight(1)
s.Require().NoError(err)
val := s.network.Validators[0]
granter := val.Address
grantee := s.network.Validators[1].Address
clientCtx := val.ClientCtx
commonFlags := []string{
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
}
fee := sdk.NewCoin("stake", sdk.NewInt(100))
duration := 365 * 24 * 60 * 60
args := append(
[]string{
granter.String(),
grantee.String(),
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, fee.String()),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%v", cli.FlagExpiration, duration),
},
commonFlags...,
)
cmd := cli.NewCmdFeeGrant()
_, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, args)
s.Require().NoError(err)
_, err = s.network.WaitForHeight(1)
s.Require().NoError(err)
s.addedGranter = granter
s.addedGrantee = grantee
grant, err := types.NewFeeAllowanceGrant(granter, grantee, &types.BasicFeeAllowance{
SpendLimit: sdk.NewCoins(fee),
})
s.Require().NoError(err)
s.addedGrant = grant
}
func (s *IntegrationTestSuite) TearDownSuite() {
s.T().Log("tearing down integration test suite")
s.network.Cleanup()
}
func (s *IntegrationTestSuite) TestCmdGetFeeGrant() {
val := s.network.Validators[0]
granter := val.Address
grantee := s.addedGrantee
clientCtx := val.ClientCtx
testCases := []struct {
name string
args []string
expectErrMsg string
expectErr bool
respType *types.FeeAllowanceGrant
resp *types.FeeAllowanceGrant
}{
{
"wrong granter",
[]string{
"wrong_granter",
grantee.String(),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
"decoding bech32 failed",
true, nil, nil,
},
{
"wrong grantee",
[]string{
granter.String(),
"wrong_grantee",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
"decoding bech32 failed",
true, nil, nil,
},
{
"non existed grant",
[]string{
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
grantee.String(),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
"no fee allowance found",
true, nil, nil,
},
{
"valid req",
[]string{
granter.String(),
grantee.String(),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
"",
false,
&types.FeeAllowanceGrant{},
&s.addedGrant,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
cmd := cli.GetCmdQueryFeeGrant()
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
if tc.expectErr {
s.Require().Error(err)
s.Require().Contains(err.Error(), tc.expectErrMsg)
} else {
s.Require().NoError(err)
s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
s.Require().Equal(tc.respType.Grantee, tc.respType.Grantee)
s.Require().Equal(tc.respType.Granter, tc.respType.Granter)
s.Require().Equal(
tc.respType.GetFeeGrant().(*types.BasicFeeAllowance).SpendLimit,
tc.resp.GetFeeGrant().(*types.BasicFeeAllowance).SpendLimit,
)
}
})
}
}
func (s *IntegrationTestSuite) TestCmdGetFeeGrants() {
val := s.network.Validators[0]
grantee := s.addedGrantee
clientCtx := val.ClientCtx
testCases := []struct {
name string
args []string
expectErr bool
resp *types.QueryFeeAllowancesResponse
expectLength int
}{
{
"wrong grantee",
[]string{
"wrong_grantee",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
true, nil, 0,
},
{
"non existed grantee",
[]string{
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, &types.QueryFeeAllowancesResponse{}, 0,
},
{
"valid req",
[]string{
grantee.String(),
fmt.Sprintf("--%s=json", tmcli.OutputFlag),
},
false, &types.QueryFeeAllowancesResponse{}, 1,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
cmd := cli.GetCmdQueryFeeGrants()
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err)
s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.resp), out.String())
s.Require().Len(tc.resp.FeeAllowances, tc.expectLength)
}
})
}
}
func (s *IntegrationTestSuite) TestNewCmdFeeGrant() {
val := s.network.Validators[0]
granter := val.Address
alreadyExistedGrantee := s.addedGrantee
clientCtx := val.ClientCtx
commonFlags := []string{
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
}
testCases := []struct {
name string
args []string
expectErr bool
respType proto.Message
expectedCode uint32
}{
{
"wrong granter address",
append(
[]string{
"wrong_granter",
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
true, nil, 0,
},
{
"wrong grantee address",
append(
[]string{
granter.String(),
"wrong_grantee",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
true, nil, 0,
},
{
"valid basic fee grant",
append(
[]string{
granter.String(),
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid basic fee grant without spend limit",
append(
[]string{
granter.String(),
"cosmos17h5lzptx3ghvsuhk7wx4c4hnl7rsswxjer97em",
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid basic fee grant without expiration",
append(
[]string{
granter.String(),
"cosmos16dlc38dcqt0uralyd8hksxyrny6kaeqfjvjwp5",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid basic fee grant without spend-limit and expiration",
append(
[]string{
granter.String(),
"cosmos1ku40qup9vwag4wtf8cls9mkszxfthaklxkp3c8",
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"try to add existed grant",
append(
[]string{
granter.String(),
alreadyExistedGrantee.String(),
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 18,
},
{
"invalid number of args(periodic fee grant)",
append(
[]string{
granter.String(),
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%d", cli.FlagExpiration, 10*60*60),
},
commonFlags...,
),
true, nil, 0,
},
{
"period mentioned and period limit omitted, invalid periodic grant",
append(
[]string{
granter.String(),
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 10*60*60),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%d", cli.FlagExpiration, 60*60),
},
commonFlags...,
),
true, nil, 0,
},
{
"period cannot be greater than the actual expiration(periodic fee grant)",
append(
[]string{
granter.String(),
"cosmos1nph3cfzk6trsmfxkeu943nvach5qw4vwstnvkl",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 10*60*60),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%d", cli.FlagExpiration, 60*60),
},
commonFlags...,
),
true, nil, 0,
},
{
"valid periodic fee grant",
append(
[]string{
granter.String(),
"cosmos1w55kgcf3ltaqdy4ww49nge3klxmrdavrr6frmp",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 60*60),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%d", cli.FlagExpiration, 10*60*60),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid periodic fee grant without spend-limit",
append(
[]string{
granter.String(),
"cosmos1vevyks8pthkscvgazc97qyfjt40m6g9xe85ry8",
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 60*60),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%d", cli.FlagExpiration, 10*60*60),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid periodic fee grant without expiration",
append(
[]string{
granter.String(),
"cosmos14cm33pvnrv2497tyt8sp9yavhmw83nwej3m0e8",
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, "100stake"),
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 60*60),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
{
"valid periodic fee grant without spend-limit and expiration",
append(
[]string{
granter.String(),
"cosmos12nyk4pcf4arshznkpz882e4l4ts0lt0ap8ce54",
fmt.Sprintf("--%s=%d", cli.FlagPeriod, 60*60),
fmt.Sprintf("--%s=%s", cli.FlagPeriodLimit, "10stake"),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false, &sdk.TxResponse{}, 0,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
cmd := cli.NewCmdFeeGrant()
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err)
s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
txResp := tc.respType.(*sdk.TxResponse)
s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
}
})
}
}
func (s *IntegrationTestSuite) TestNewCmdRevokeFeegrant() {
val := s.network.Validators[0]
granter := s.addedGranter
grantee := s.addedGrantee
clientCtx := val.ClientCtx
commonFlags := []string{
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
}
testCases := []struct {
name string
args []string
expectErr bool
respType proto.Message
expectedCode uint32
}{
{
"invalid grantee",
append(
[]string{
"wrong_granter",
grantee.String(),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
true,
nil,
0,
},
{
"invalid grantee",
append(
[]string{
granter.String(),
"wrong_grantee",
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
true,
nil,
0,
},
{
"Non existed grant",
append(
[]string{
granter.String(),
"cosmos1aeuqja06474dfrj7uqsvukm6rael982kk89mqr",
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false,
&sdk.TxResponse{},
4,
},
{
"Valid revoke",
append(
[]string{
granter.String(),
grantee.String(),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
},
commonFlags...,
),
false,
&sdk.TxResponse{},
0,
},
}
for _, tc := range testCases {
tc := tc
s.Run(tc.name, func() {
cmd := cli.NewCmdRevokeFeegrant()
out, err := clitestutil.ExecTestCLICmd(clientCtx, cmd, tc.args)
if tc.expectErr {
s.Require().Error(err)
} else {
s.Require().NoError(err)
s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), tc.respType), out.String())
txResp := tc.respType.(*sdk.TxResponse)
s.Require().Equal(tc.expectedCode, txResp.Code, out.String())
}
})
}
}
func (s *IntegrationTestSuite) TestTxWithFeeGrant() {
val := s.network.Validators[0]
clientCtx := val.ClientCtx
granter := val.Address
// creating an account manually (This account won't be exist in state)
info, _, err := val.ClientCtx.Keyring.NewMnemonic("grantee", keyring.English, sdk.FullFundraiserPath, hd.Secp256k1)
s.Require().NoError(err)
grantee := sdk.AccAddress(info.GetPubKey().Address())
commonFlags := []string{
fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock),
fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation),
fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()),
}
fee := sdk.NewCoin("stake", sdk.NewInt(100))
duration := 365 * 24 * 60 * 60
args := append(
[]string{
granter.String(),
grantee.String(),
fmt.Sprintf("--%s=%s", cli.FlagSpendLimit, fee.String()),
fmt.Sprintf("--%s=%s", flags.FlagFrom, granter),
fmt.Sprintf("--%s=%v", cli.FlagExpiration, duration),
},
commonFlags...,
)
cmd := cli.NewCmdFeeGrant()
_, err = clitestutil.ExecTestCLICmd(clientCtx, cmd, args)
s.Require().NoError(err)
_, err = s.network.WaitForHeight(1)
s.Require().NoError(err)
// granted fee allowance for an account which is not in state and creating
// any tx with it by using --fee-account shouldn't fail
out, err := govtestutil.MsgSubmitProposal(val.ClientCtx, grantee.String(),
"Text Proposal", "No desc", govtypes.ProposalTypeText,
fmt.Sprintf("--%s=%s", flags.FlagFeeAccount, granter.String()),
)
s.Require().NoError(err)
var resp sdk.TxResponse
s.Require().NoError(clientCtx.JSONMarshaler.UnmarshalJSON(out.Bytes(), &resp), out.String())
s.Require().Equal(uint32(0), resp.Code)
}
func TestIntegrationTestSuite(t *testing.T) {
suite.Run(t, new(IntegrationTestSuite))
}

View File

@ -0,0 +1,129 @@
package cli
import (
"fmt"
"strings"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/spf13/cobra"
)
// GetQueryCmd returns the cli query commands for this module
func GetQueryCmd() *cobra.Command {
feegrantQueryCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Querying commands for the feegrant module",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
feegrantQueryCmd.AddCommand(
GetCmdQueryFeeGrant(),
GetCmdQueryFeeGrants(),
)
return feegrantQueryCmd
}
// GetCmdQueryFeeGrant returns cmd to query for a grant between granter and grantee.
func GetCmdQueryFeeGrant() *cobra.Command {
cmd := &cobra.Command{
Use: "grant [granter] [grantee]",
Args: cobra.ExactArgs(2),
Short: "Query details of a single grant",
Long: strings.TrimSpace(
fmt.Sprintf(`Query details for a grant.
You can find the fee-grant of a granter and grantee.
Example:
$ %s query feegrant grant [granter] [grantee]
`, version.AppName),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)
granterAddr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
granteeAddr, err := sdk.AccAddressFromBech32(args[1])
if err != nil {
return err
}
res, err := queryClient.FeeAllowance(
cmd.Context(),
&types.QueryFeeAllowanceRequest{
Granter: granterAddr.String(),
Grantee: granteeAddr.String(),
},
)
if err != nil {
return err
}
return clientCtx.PrintProto(res.FeeAllowance)
},
}
flags.AddQueryFlagsToCmd(cmd)
return cmd
}
// GetCmdQueryFeeGrants returns cmd to query for all grants for a grantee.
func GetCmdQueryFeeGrants() *cobra.Command {
cmd := &cobra.Command{
Use: "grants [grantee]",
Args: cobra.ExactArgs(1),
Short: "Query all grants of a grantee",
Long: strings.TrimSpace(
fmt.Sprintf(`Queries all the grants for a grantee address.
Example:
$ %s query feegrant grants [grantee]
`, version.AppName),
),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx := client.GetClientContextFromCmd(cmd)
queryClient := types.NewQueryClient(clientCtx)
granteeAddr, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
pageReq, err := client.ReadPageRequest(cmd.Flags())
if err != nil {
return err
}
res, err := queryClient.FeeAllowances(
cmd.Context(),
&types.QueryFeeAllowancesRequest{
Grantee: granteeAddr.String(),
Pagination: pageReq,
},
)
if err != nil {
return err
}
return clientCtx.PrintProto(res)
},
}
flags.AddQueryFlagsToCmd(cmd)
flags.AddPaginationFlagsToCmd(cmd, "grants")
return cmd
}

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

@ -0,0 +1,211 @@
package cli
import (
"fmt"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/cosmos/cosmos-sdk/client"
"github.com/cosmos/cosmos-sdk/client/flags"
"github.com/cosmos/cosmos-sdk/client/tx"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/msgservice"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
// flag for feegrant module
const (
FlagExpiration = "expiration"
FlagPeriod = "period"
FlagPeriodLimit = "period-limit"
FlagSpendLimit = "spend-limit"
)
// GetTxCmd returns the transaction commands for this module
func GetTxCmd() *cobra.Command {
feegrantTxCmd := &cobra.Command{
Use: types.ModuleName,
Short: "Feegrant transactions subcommands",
Long: "Grant and revoke fee allowance for a grantee by a granter",
DisableFlagParsing: true,
SuggestionsMinimumDistance: 2,
RunE: client.ValidateCmd,
}
feegrantTxCmd.AddCommand(
NewCmdFeeGrant(),
NewCmdRevokeFeegrant(),
)
return feegrantTxCmd
}
// NewCmdFeeGrant returns a CLI command handler for creating a MsgGrantFeeAllowance transaction.
func NewCmdFeeGrant() *cobra.Command {
cmd := &cobra.Command{
Use: "grant [granter] [grantee]",
Short: "Grant Fee allowance to an address",
Long: strings.TrimSpace(
fmt.Sprintf(
`Grant authorization to pay fees from your address. Note, the'--from' flag is
ignored as it is implied from [granter].
Examples:
%s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --expiration 36000 or
%s tx %s grant cosmos1skjw... cosmos1skjw... --spend-limit 100stake --period 3600 --period-limit 10stake --expiration 36000
`, version.AppName, types.ModuleName, version.AppName, types.ModuleName,
),
),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
_, err := sdk.AccAddressFromBech32(args[0])
if err != nil {
return err
}
cmd.Flags().Set(flags.FlagFrom, args[0])
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
grantee, err := sdk.AccAddressFromBech32(args[1])
if err != nil {
return err
}
granter := clientCtx.GetFromAddress()
sl, err := cmd.Flags().GetString(FlagSpendLimit)
if err != nil {
return err
}
// if `FlagSpendLimit` isn't set, limit will be nil
limit, err := sdk.ParseCoinsNormalized(sl)
if err != nil {
return err
}
exp, err := cmd.Flags().GetInt64(FlagExpiration)
if err != nil {
return err
}
basic := types.BasicFeeAllowance{
SpendLimit: limit,
}
if exp != 0 {
expDuration := time.Duration(exp) * time.Second
basic.Expiration = types.ExpiresAtTime(time.Now().Add(expDuration))
}
var grant types.FeeAllowanceI
grant = &basic
periodClock, err := cmd.Flags().GetInt64(FlagPeriod)
if err != nil {
return err
}
periodLimitVal, err := cmd.Flags().GetString(FlagPeriodLimit)
if err != nil {
return err
}
// Check any of period or periodLimit flags set, If set consider it as periodic fee allowance.
if periodClock > 0 || periodLimitVal != "" {
periodLimit, err := sdk.ParseCoinsNormalized(periodLimitVal)
if err != nil {
return err
}
if periodClock > 0 && periodLimit != nil {
if exp > 0 && periodClock > exp {
return fmt.Errorf("period(%d) cannot be greater than the expiration(%d)", periodClock, exp)
}
periodic := types.PeriodicFeeAllowance{
Basic: basic,
Period: types.ClockDuration(time.Duration(periodClock) * time.Second),
PeriodReset: types.ExpiresAtTime(time.Now().Add(time.Duration(periodClock) * time.Second)),
PeriodSpendLimit: periodLimit,
PeriodCanSpend: periodLimit,
}
grant = &periodic
} else {
return fmt.Errorf("invalid number of args %d", len(args))
}
}
msg, err := types.NewMsgGrantFeeAllowance(grant, granter, grantee)
if err != nil {
return err
}
svcMsgClientConn := &msgservice.ServiceMsgClientConn{}
feeGrantMsgClient := types.NewMsgClient(svcMsgClientConn)
_, err = feeGrantMsgClient.GrantFeeAllowance(cmd.Context(), msg)
if err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), svcMsgClientConn.GetMsgs()...)
},
}
flags.AddTxFlagsToCmd(cmd)
cmd.Flags().Int64(FlagExpiration, 0, "The second unit of time duration which the grant is active for the user")
cmd.Flags().String(FlagSpendLimit, "", "Spend limit specifies the max limit can be used, if not mentioned there is no limit")
cmd.Flags().Int64(FlagPeriod, 0, "period specifies the time duration in which period_spend_limit coins can be spent before that allowance is reset")
cmd.Flags().String(FlagPeriodLimit, "", "// period limit specifies the maximum number of coins that can be spent in the period")
return cmd
}
// NewCmdRevokeFeegrant returns a CLI command handler for creating a MsgRevokeFeeAllowance transaction.
func NewCmdRevokeFeegrant() *cobra.Command {
cmd := &cobra.Command{
Use: "revoke [granter_address] [grantee_address]",
Short: "revoke fee-grant",
Long: strings.TrimSpace(
fmt.Sprintf(`revoke fee grant from a granter to a grantee. Note, the'--from' flag is
ignored as it is implied from [granter_address].
Example:
$ %s tx %s revoke cosmos1skj.. cosmos1skj..
`, version.AppName, types.ModuleName),
),
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
cmd.Flags().Set(flags.FlagFrom, args[0])
clientCtx, err := client.GetClientTxContext(cmd)
if err != nil {
return err
}
grantee, err := sdk.AccAddressFromBech32(args[1])
if err != nil {
return err
}
msg := types.NewMsgRevokeFeeAllowance(clientCtx.GetFromAddress(), grantee)
svcMsgClientConn := &msgservice.ServiceMsgClientConn{}
feeGrantMsgClient := types.NewMsgClient(svcMsgClientConn)
_, err = feeGrantMsgClient.RevokeFeeAllowance(cmd.Context(), &msg)
if err != nil {
return err
}
return tx.GenerateOrBroadcastTxCLI(clientCtx, cmd.Flags(), svcMsgClientConn.GetMsgs()...)
},
}
flags.AddTxFlagsToCmd(cmd)
return cmd
}

View File

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

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

@ -0,0 +1,29 @@
/*
Package feegrant provides functionality for authorizing the payment of transaction
fees from one account (key) to another account (key).
Effectively, this allows for a user to pay fees using the balance of an account
different from their own. Example use cases would be allowing a key on a device to
pay for fees using a master wallet, or a third party service allowing users to
pay for transactions without ever really holding their own tokens. This package
provides ways for specifying fee allowances such that authorizing fee payment to
another account can be done with clear and safe restrictions.
A user would authorize granting fee payment to another user using
MsgDelegateFeeAllowance and revoke that delegation using MsgRevokeFeeAllowance.
In both cases, Granter is the one who is authorizing fee payment and Grantee is
the one who is receiving the fee payment authorization. So grantee would correspond
to the one who is signing a transaction and the granter would be the address that
pays the fees.
The fee allowance that a grantee receives is specified by an implementation of
the FeeAllowance interface. Two FeeAllowance implementations are provided in
this package: BasicFeeAllowance and PeriodicFeeAllowance.
In order to integrate this into an application, we must use the DeductGrantedFeeDecorator
ante handler from this package instead of the default DeductFeeDecorator from x/auth.
To allow handling txs from empty accounts (with fees paid from an existing account),
we have to re-order the decorators as well.
*/
package feegrant

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

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

View File

@ -0,0 +1,56 @@
package feegrant_test
import (
"testing"
"github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
feegrant "github.com/cosmos/cosmos-sdk/x/feegrant"
"github.com/cosmos/cosmos-sdk/x/feegrant/keeper"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
"github.com/stretchr/testify/suite"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
)
type GenesisTestSuite struct {
suite.Suite
ctx sdk.Context
keeper keeper.Keeper
}
func (suite *GenesisTestSuite) SetupTest() {
checkTx := false
app := simapp.Setup(checkTx)
suite.ctx = app.BaseApp.NewContext(checkTx, tmproto.Header{Height: 1})
suite.keeper = app.FeeGrantKeeper
}
var (
granteePub = secp256k1.GenPrivKey().PubKey()
granterPub = secp256k1.GenPrivKey().PubKey()
granteeAddr = sdk.AccAddress(granteePub.Address())
granterAddr = sdk.AccAddress(granterPub.Address())
)
func (suite *GenesisTestSuite) TestImportExportGenesis() {
coins := sdk.NewCoins(sdk.NewCoin("foo", sdk.NewInt(1_000)))
now := suite.ctx.BlockHeader().Time
allowance := &types.BasicFeeAllowance{SpendLimit: coins, Expiration: types.ExpiresAtTime(now.AddDate(1, 0, 0))}
err := suite.keeper.GrantFeeAllowance(suite.ctx, granterAddr, granteeAddr, allowance)
suite.Require().NoError(err)
genesis, err := feegrant.ExportGenesis(suite.ctx, suite.keeper)
suite.Require().NoError(err)
// Clear keeper
suite.keeper.RevokeFeeAllowance(suite.ctx, granterAddr, granteeAddr)
feegrant.InitGenesis(suite.ctx, suite.keeper, genesis)
newGenesis, err := feegrant.ExportGenesis(suite.ctx, suite.keeper)
suite.Require().NoError(err)
suite.Require().Equal(genesis, newGenesis)
}
func TestGenesisTestSuite(t *testing.T) {
suite.Run(t, new(GenesisTestSuite))
}

View File

@ -0,0 +1,94 @@
package keeper
import (
"context"
"github.com/gogo/protobuf/proto"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
codectypes "github.com/cosmos/cosmos-sdk/codec/types"
"github.com/cosmos/cosmos-sdk/store/prefix"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/types/query"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
var _ types.QueryServer = Keeper{}
// FeeAllowance returns fee granted to the grantee by the granter.
func (q Keeper) FeeAllowance(c context.Context, req *types.QueryFeeAllowanceRequest) (*types.QueryFeeAllowanceResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
granterAddr, err := sdk.AccAddressFromBech32(req.Granter)
if err != nil {
return nil, err
}
granteeAddr, err := sdk.AccAddressFromBech32(req.Grantee)
if err != nil {
return nil, err
}
ctx := sdk.UnwrapSDKContext(c)
feeAllowance := q.GetFeeAllowance(ctx, granterAddr, granteeAddr)
if feeAllowance == nil {
return nil, status.Errorf(codes.NotFound, "no fee allowance found")
}
msg, ok := feeAllowance.(proto.Message)
if !ok {
return nil, status.Errorf(codes.Internal, "can't proto marshal %T", msg)
}
feeAllowanceAny, err := codectypes.NewAnyWithValue(msg)
if err != nil {
return nil, status.Errorf(codes.Internal, err.Error())
}
return &types.QueryFeeAllowanceResponse{
FeeAllowance: &types.FeeAllowanceGrant{
Granter: granterAddr.String(),
Grantee: granteeAddr.String(),
Allowance: feeAllowanceAny,
},
}, nil
}
func (q Keeper) FeeAllowances(c context.Context, req *types.QueryFeeAllowancesRequest) (*types.QueryFeeAllowancesResponse, error) {
if req == nil {
return nil, status.Error(codes.InvalidArgument, "invalid request")
}
granteeAddr, err := sdk.AccAddressFromBech32(req.Grantee)
if err != nil {
return nil, err
}
ctx := sdk.UnwrapSDKContext(c)
var grants []*types.FeeAllowanceGrant
store := ctx.KVStore(q.storeKey)
grantsStore := prefix.NewStore(store, types.FeeAllowancePrefixByGrantee(granteeAddr))
pageRes, err := query.Paginate(grantsStore, req.Pagination, func(key []byte, value []byte) error {
var grant types.FeeAllowanceGrant
if err := q.cdc.UnmarshalBinaryBare(value, &grant); err != nil {
return err
}
grants = append(grants, &grant)
return nil
})
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
return &types.QueryFeeAllowancesResponse{FeeAllowances: grants, Pagination: pageRes}, nil
}

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

@ -0,0 +1,190 @@
package keeper
import (
"fmt"
"github.com/tendermint/tendermint/libs/log"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/feegrant/types"
)
// Keeper manages state of all fee grants, as well as calculating approval.
// It must have a codec with all available allowances registered.
type Keeper struct {
cdc codec.BinaryMarshaler
storeKey sdk.StoreKey
authKeeper types.AccountKeeper
}
// NewKeeper creates a fee grant Keeper
func NewKeeper(cdc codec.BinaryMarshaler, storeKey sdk.StoreKey, ak types.AccountKeeper) Keeper {
return Keeper{
cdc: cdc,
storeKey: storeKey,
authKeeper: ak,
}
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
// GrantFeeAllowance creates a new grant
func (k Keeper) GrantFeeAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress, feeAllowance types.FeeAllowanceI) error {
// create the account if it is not in account state
granteeAcc := k.authKeeper.GetAccount(ctx, grantee)
if granteeAcc == nil {
granteeAcc = k.authKeeper.NewAccountWithAddress(ctx, grantee)
k.authKeeper.SetAccount(ctx, granteeAcc)
}
store := ctx.KVStore(k.storeKey)
key := types.FeeAllowanceKey(granter, grantee)
grant, err := types.NewFeeAllowanceGrant(granter, grantee, feeAllowance)
if err != nil {
return err
}
bz, err := k.cdc.MarshalBinaryBare(&grant)
if err != nil {
return err
}
store.Set(key, bz)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeSetFeeGrant,
sdk.NewAttribute(types.AttributeKeyGranter, grant.Granter),
sdk.NewAttribute(types.AttributeKeyGrantee, grant.Grantee),
),
)
return nil
}
// RevokeFeeAllowance removes an existing grant
func (k Keeper) RevokeFeeAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress) error {
store := ctx.KVStore(k.storeKey)
key := types.FeeAllowanceKey(granter, grantee)
_, found := k.GetFeeGrant(ctx, granter, grantee)
if !found {
return sdkerrors.Wrap(sdkerrors.ErrUnauthorized, "fee-grant not found")
}
store.Delete(key)
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeRevokeFeeGrant,
sdk.NewAttribute(types.AttributeKeyGranter, granter.String()),
sdk.NewAttribute(types.AttributeKeyGrantee, grantee.String()),
),
)
return nil
}
// GetFeeAllowance returns the allowance between the granter and grantee.
// If there is none, it returns nil, nil.
// Returns an error on parsing issues
func (k Keeper) GetFeeAllowance(ctx sdk.Context, granter, grantee sdk.AccAddress) types.FeeAllowanceI {
grant, found := k.GetFeeGrant(ctx, granter, grantee)
if !found {
return nil
}
return grant.GetFeeGrant()
}
// GetFeeGrant returns entire grant between both accounts
func (k Keeper) GetFeeGrant(ctx sdk.Context, granter sdk.AccAddress, grantee sdk.AccAddress) (types.FeeAllowanceGrant, bool) {
store := ctx.KVStore(k.storeKey)
key := types.FeeAllowanceKey(granter, grantee)
bz := store.Get(key)
if len(bz) == 0 {
return types.FeeAllowanceGrant{}, false
}
var feegrant types.FeeAllowanceGrant
k.cdc.MustUnmarshalBinaryBare(bz, &feegrant)
return feegrant, true
}
// IterateAllGranteeFeeAllowances iterates over all the grants from anyone to the given grantee.
// Callback to get all data, returns true to stop, false to keep reading
func (k Keeper) IterateAllGranteeFeeAllowances(ctx sdk.Context, grantee sdk.AccAddress, cb func(types.FeeAllowanceGrant) bool) error {
store := ctx.KVStore(k.storeKey)
prefix := types.FeeAllowancePrefixByGrantee(grantee)
iter := sdk.KVStorePrefixIterator(store, prefix)
defer iter.Close()
stop := false
for ; iter.Valid() && !stop; iter.Next() {
bz := iter.Value()
var feeGrant types.FeeAllowanceGrant
k.cdc.MustUnmarshalBinaryBare(bz, &feeGrant)
stop = cb(feeGrant)
}
return nil
}
// IterateAllFeeAllowances iterates over all the grants in the store.
// Callback to get all data, returns true to stop, false to keep reading
// Calling this without pagination is very expensive and only designed for export genesis
func (k Keeper) IterateAllFeeAllowances(ctx sdk.Context, cb func(types.FeeAllowanceGrant) bool) error {
store := ctx.KVStore(k.storeKey)
iter := sdk.KVStorePrefixIterator(store, types.FeeAllowanceKeyPrefix)
defer iter.Close()
stop := false
for ; iter.Valid() && !stop; iter.Next() {
bz := iter.Value()
var feeGrant types.FeeAllowanceGrant
k.cdc.MustUnmarshalBinaryBare(bz, &feeGrant)
stop = cb(feeGrant)
}
return nil
}
// UseGrantedFees will try to pay the given fee from the granter's account as requested by the grantee
func (k Keeper) UseGrantedFees(ctx sdk.Context, granter, grantee sdk.AccAddress, fee sdk.Coins) error {
grant, found := k.GetFeeGrant(ctx, granter, grantee)
if !found || grant.GetFeeGrant() == nil {
return sdkerrors.Wrapf(types.ErrNoAllowance, "grant missing")
}
remove, err := grant.GetFeeGrant().Accept(fee, ctx.BlockTime(), ctx.BlockHeight())
if err == nil {
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeUseFeeGrant,
sdk.NewAttribute(types.AttributeKeyGranter, granter.String()),
sdk.NewAttribute(types.AttributeKeyGrantee, grantee.String()),
),
)
}
if remove {
k.RevokeFeeAllowance(ctx, granter, grantee)
// note this returns nil if err == nil
return sdkerrors.Wrap(err, "removed grant")
}
if err != nil {
return sdkerrors.Wrap(err, "invalid grant")
}
// if we accepted, store the updated state of the allowance
return k.GrantFeeAllowance(ctx, granter, grantee, grant.GetFeeGrant())
}

Some files were not shown because too many files have changed in this diff Show More