diff --git a/PENDING.md b/PENDING.md index e0b211ad3102..140397dfe38c 100644 --- a/PENDING.md +++ b/PENDING.md @@ -44,6 +44,8 @@ FEATURES * [\#3429](https://github.com/cosmos/cosmos-sdk/issues/3429) Support querying for all delegator distribution rewards. * \#3449 Proof verification now works with absence proofs + * [\#3484](https://github.com/cosmos/cosmos-sdk/issues/3484) Add support + vesting accounts to the add-genesis-account command. * Gaia - [\#3397](https://github.com/cosmos/cosmos-sdk/pull/3397) Implement genesis file sanitization to avoid failures at chain init. diff --git a/cmd/gaia/cli_test/test_helpers.go b/cmd/gaia/cli_test/test_helpers.go index f5a71b4afb9e..68182a3eb14f 100644 --- a/cmd/gaia/cli_test/test_helpers.go +++ b/cmd/gaia/cli_test/test_helpers.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" "testing" + "time" "github.com/stretchr/testify/require" @@ -34,15 +35,22 @@ const ( feeDenom = "feetoken" fee2Denom = "fee2token" keyBaz = "baz" + keyVesting = "vesting" keyFooBarBaz = "foobarbaz" ) -var startCoins = sdk.Coins{ - sdk.NewCoin(feeDenom, staking.TokensFromTendermintPower(1000000)), - sdk.NewCoin(fee2Denom, staking.TokensFromTendermintPower(1000000)), - sdk.NewCoin(fooDenom, staking.TokensFromTendermintPower(1000)), - sdk.NewCoin(denom, staking.TokensFromTendermintPower(150)), -} +var ( + startCoins = sdk.Coins{ + sdk.NewCoin(feeDenom, staking.TokensFromTendermintPower(1000000)), + sdk.NewCoin(fee2Denom, staking.TokensFromTendermintPower(1000000)), + sdk.NewCoin(fooDenom, staking.TokensFromTendermintPower(1000)), + sdk.NewCoin(denom, staking.TokensFromTendermintPower(150)), + } + + vestingCoins = sdk.Coins{ + sdk.NewCoin(feeDenom, staking.TokensFromTendermintPower(500000)), + } +) //___________________________________________________________________________________ // Fixtures @@ -108,6 +116,7 @@ func InitFixtures(t *testing.T) (f *Fixtures) { f.KeysAdd(keyFoo) f.KeysAdd(keyBar) f.KeysAdd(keyBaz) + f.KeysAdd(keyVesting) f.KeysAdd(keyFooBarBaz, "--multisig-threshold=2", fmt.Sprintf( "--multisig=%s,%s,%s", keyFoo, keyBar, keyBaz)) @@ -120,6 +129,12 @@ func InitFixtures(t *testing.T) (f *Fixtures) { // Start an account with tokens f.AddGenesisAccount(f.KeyAddress(keyFoo), startCoins) + f.AddGenesisAccount( + f.KeyAddress(keyVesting), startCoins, + fmt.Sprintf("--vesting-amount=%s", vestingCoins), + fmt.Sprintf("--vesting-start-time=%d", time.Now().UTC().UnixNano()), + fmt.Sprintf("--vesting-end-time=%d", time.Now().Add(60*time.Second).UTC().UnixNano()), + ) f.GenTx(keyFoo) f.CollectGenTxs() return diff --git a/cmd/gaia/init/genesis_accts.go b/cmd/gaia/init/genesis_accts.go index 4ec108dcb37d..5aa73dae94b4 100644 --- a/cmd/gaia/init/genesis_accts.go +++ b/cmd/gaia/init/genesis_accts.go @@ -16,7 +16,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth" ) -// AddGenesisAccountCmd returns add-genesis-account cobra Command +// AddGenesisAccountCmd returns add-genesis-account cobra Command. func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { cmd := &cobra.Command{ Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]", @@ -32,22 +32,32 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command if err != nil { return err } + info, err := kb.Get(args[0]) if err != nil { return err } + addr = info.GetAddress() } + coins, err := sdk.ParseCoins(args[1]) if err != nil { return err } - coins.Sort() + + vestingStart := viper.GetInt64(flagVestingStart) + vestingEnd := viper.GetInt64(flagVestingEnd) + vestingAmt, err := sdk.ParseCoins(viper.GetString(flagVestingAmt)) + if err != nil { + return err + } genFile := config.GenesisFile() if !common.FileExists(genFile) { return fmt.Errorf("%s does not exist, run `gaiad init` first", genFile) } + genDoc, err := LoadGenesisDoc(cdc, genFile) if err != nil { return err @@ -58,7 +68,7 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command return err } - appState, err = addGenesisAccount(cdc, appState, addr, coins) + appState, err = addGenesisAccount(cdc, appState, addr, coins, vestingAmt, vestingStart, vestingEnd) if err != nil { return err } @@ -74,10 +84,18 @@ func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command cmd.Flags().String(cli.HomeFlag, app.DefaultNodeHome, "node's home directory") cmd.Flags().String(flagClientHome, app.DefaultCLIHome, "client's home directory") + cmd.Flags().String(flagVestingAmt, "", "amount of coins for vesting accounts") + cmd.Flags().Uint64(flagVestingStart, 0, "schedule start time (unix epoch) for vesting accounts") + cmd.Flags().Uint64(flagVestingEnd, 0, "schedule end time (unix epoch) for vesting accounts") + return cmd } -func addGenesisAccount(cdc *codec.Codec, appState app.GenesisState, addr sdk.AccAddress, coins sdk.Coins) (app.GenesisState, error) { +func addGenesisAccount( + cdc *codec.Codec, appState app.GenesisState, addr sdk.AccAddress, + coins, vestingAmt sdk.Coins, vestingStart, vestingEnd int64, +) (app.GenesisState, error) { + for _, stateAcc := range appState.Accounts { if stateAcc.Address.Equals(addr) { return appState, fmt.Errorf("the application state already contains account %v", addr) @@ -86,6 +104,38 @@ func addGenesisAccount(cdc *codec.Codec, appState app.GenesisState, addr sdk.Acc acc := auth.NewBaseAccountWithAddress(addr) acc.Coins = coins - appState.Accounts = append(appState.Accounts, app.NewGenesisAccount(&acc)) + + if !vestingAmt.IsZero() { + var vacc auth.VestingAccount + + bvacc := &auth.BaseVestingAccount{ + BaseAccount: &acc, + OriginalVesting: vestingAmt, + EndTime: vestingEnd, + } + + if bvacc.OriginalVesting.IsAllGT(acc.Coins) { + return appState, fmt.Errorf("vesting amount cannot be greater than total amount") + } + if vestingStart >= vestingEnd { + return appState, fmt.Errorf("vesting start time must before end time") + } + + if vestingStart != 0 { + vacc = &auth.ContinuousVestingAccount{ + BaseVestingAccount: bvacc, + StartTime: vestingStart, + } + } else { + vacc = &auth.DelayedVestingAccount{ + BaseVestingAccount: bvacc, + } + } + + appState.Accounts = append(appState.Accounts, app.NewGenesisAccountI(vacc)) + } else { + appState.Accounts = append(appState.Accounts, app.NewGenesisAccount(&acc)) + } + return appState, nil } diff --git a/cmd/gaia/init/genesis_accts_test.go b/cmd/gaia/init/genesis_accts_test.go index 42a36b263720..2c17acc54c1e 100644 --- a/cmd/gaia/init/genesis_accts_test.go +++ b/cmd/gaia/init/genesis_accts_test.go @@ -15,9 +15,12 @@ func TestAddGenesisAccount(t *testing.T) { cdc := codec.New() addr1 := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) type args struct { - appState app.GenesisState - addr sdk.AccAddress - coins sdk.Coins + appState app.GenesisState + addr sdk.AccAddress + coins sdk.Coins + vestingAmt sdk.Coins + vestingStart int64 + vestingEnd int64 } tests := []struct { name string @@ -30,16 +33,55 @@ func TestAddGenesisAccount(t *testing.T) { app.GenesisState{}, addr1, sdk.Coins{}, + sdk.Coins{}, + 0, + 0, + }, + false, + }, + { + "dup account", + args{ + app.GenesisState{Accounts: []app.GenesisAccount{{Address: addr1}}}, + addr1, + sdk.Coins{}, + sdk.Coins{}, + 0, + 0, + }, + true, + }, + { + "invalid vesting amount", + args{ + app.GenesisState{}, + addr1, + sdk.Coins{sdk.NewInt64Coin("stake", 50)}, + sdk.Coins{sdk.NewInt64Coin("stake", 100)}, + 0, + 0, + }, + true, + }, + { + "invalid vesting times", + args{ + app.GenesisState{}, + addr1, + sdk.Coins{sdk.NewInt64Coin("stake", 50)}, + sdk.Coins{sdk.NewInt64Coin("stake", 50)}, + 1654668078, + 1554668078, }, - false}, - {"dup account", args{ - app.GenesisState{Accounts: []app.GenesisAccount{{Address: addr1}}}, - addr1, - sdk.Coins{}}, true}, + true, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - _, err := addGenesisAccount(cdc, tt.args.appState, tt.args.addr, tt.args.coins) + _, err := addGenesisAccount( + cdc, tt.args.appState, tt.args.addr, tt.args.coins, + tt.args.vestingAmt, tt.args.vestingStart, tt.args.vestingEnd, + ) require.Equal(t, tt.wantErr, (err != nil)) }) } diff --git a/cmd/gaia/init/init.go b/cmd/gaia/init/init.go index a2af2a1c27e2..506557d3f626 100644 --- a/cmd/gaia/init/init.go +++ b/cmd/gaia/init/init.go @@ -19,8 +19,11 @@ import ( ) const ( - flagOverwrite = "overwrite" - flagClientHome = "home-client" + flagOverwrite = "overwrite" + flagClientHome = "home-client" + flagVestingStart = "vesting-start-time" + flagVestingEnd = "vesting-end-time" + flagVestingAmt = "vesting-amount" ) type printInfo struct { @@ -31,19 +34,19 @@ type printInfo struct { AppMessage json.RawMessage `json:"app_message"` } -// nolint: errcheck func displayInfo(cdc *codec.Codec, info printInfo) error { out, err := codec.MarshalJSONIndent(cdc, info) if err != nil { return err } - fmt.Fprintf(os.Stderr, "%s\n", string(out)) + + fmt.Fprintf(os.Stderr, "%s\n", string(out)) // nolint: errcheck return nil } -// get cmd to initialize all files for tendermint and application -// nolint -func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { +// InitCmd returns a command that initializes all files needed for Tendermint +// and the respective application. +func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { // nolint: golint cmd := &cobra.Command{ Use: "init [moniker]", Short: "Initialize private validator, p2p, genesis, and application configuration files", @@ -80,7 +83,6 @@ func InitCmd(ctx *server.Context, cdc *codec.Codec) *cobra.Command { toPrint := newPrintInfo(config.Moniker, chainID, nodeID, "", appState) cfg.WriteConfigFile(filepath.Join(config.RootDir, "config", "config.toml"), config) - return displayInfo(cdc, toPrint) }, }