diff --git a/CHANGELOG.md b/CHANGELOG.md index ea39bc6d57..dd8e108ac0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -37,6 +37,14 @@ Ref: https://keepachangelog.com/en/1.0.0/ ## [Unreleased] +### State Machine Breaking + +* (genesis) [\#5017](https://github.com/cosmos/cosmos-sdk/pull/5017) The `x/genaccounts` module has been +deprecated and all components removed except the `legacy/` package. This requires changes to the +genesis state. Namely, `accounts` now exist under `app_state.auth.accounts`. The corresponding migration +logic has been implemented for v0.38 target version. Applications can migrate via: +`$ {appd} migrate v0.38 genesis.json`. + ### API Breaking Changes * (store) [\#4748](https://github.com/cosmos/cosmos-sdk/pull/4748) The `CommitMultiStore` interface @@ -45,6 +53,8 @@ have this method perform a no-op. * (modules) [\#4665](https://github.com/cosmos/cosmos-sdk/issues/4665) Refactored `x/gov` module structure and dev-UX: * Prepare for module spec integration * Update gov keys to use big endian encoding instead of little endian +* (modules) [\#5017](https://github.com/cosmos/cosmos-sdk/pull/5017) The `x/genaccounts` module has been +deprecated and all components removed except the `legacy/` package. ### Client Breaking Changes @@ -68,6 +78,8 @@ and tx hash will be returned for specific Tendermint errors: ### Improvements +* (modules) [\#5017](https://github.com/cosmos/cosmos-sdk/pull/5017) The `x/auth` package now supports +generalized genesis accounts through the `GenesisAccount` interface. * (modules) [\#4762](https://github.com/cosmos/cosmos-sdk/issues/4762) Deprecate remove and add permissions in ModuleAccount. * (modules) [\#4760](https://github.com/cosmos/cosmos-sdk/issues/4760) update `x/auth` to match module spec. * (modules) [\#4814](https://github.com/cosmos/cosmos-sdk/issues/4814) Add security contact to Validator description. diff --git a/docs/architecture/adr-011-generalize-genesis-accounts.md b/docs/architecture/adr-011-generalize-genesis-accounts.md index 09e61c120e..94e1dd1447 100644 --- a/docs/architecture/adr-011-generalize-genesis-accounts.md +++ b/docs/architecture/adr-011-generalize-genesis-accounts.md @@ -45,15 +45,14 @@ func InitGenesis(ctx sdk.Context, ak AccountKeeper, data GenesisState) { func ExportGenesis(ctx sdk.Context, ak AccountKeeper) GenesisState { params := ak.GetParams(ctx) - accounts := ak.GetAllAccounts(ctx) - // convert accounts to []GenesisAccounts type - genAccounts := make([]GenesisAccounts, len(accounts)) - for i := range accounts { - ga := accounts[i].(GenesisAccount) // will panic if an account doesn't implement GenesisAccount - genAccounts[i] = ga - } + var genAccounts []exported.GenesisAccount + ak.IterateAccounts(ctx, func(account exported.Account) bool { + genAccount := account.(exported.GenesisAccount) + genAccounts = append(genAccounts, genAccount) + return false + }) - return NewGenesisState(params, accounts) + return NewGenesisState(params, genAccounts) } ``` @@ -64,11 +63,11 @@ The `auth` codec must have all custom account types registered to marshal them. An example custom account definition: ```go -import authTypes "github.com/cosmos/cosmos-sdk/x/auth/types" +import authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" // Register the module account type with the auth module codec so it can decode module accounts stored in a genesis file func init() { - authTypes.RegisterAccountTypeCodec(ModuleAccount{}, "cosmos-sdk/ModuleAccount") + authtypes.RegisterAccountTypeCodec(ModuleAccount{}, "cosmos-sdk/ModuleAccount") } type ModuleAccount struct { diff --git a/simapp/app.go b/simapp/app.go index 5160f78617..bbfd46551e 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -18,7 +18,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/bank" "github.com/cosmos/cosmos-sdk/x/crisis" distr "github.com/cosmos/cosmos-sdk/x/distribution" - "github.com/cosmos/cosmos-sdk/x/genaccounts" "github.com/cosmos/cosmos-sdk/x/genutil" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/mint" @@ -43,9 +42,9 @@ var ( // non-dependant module elements, such as codec registration // and genesis verification. ModuleBasics = module.NewBasicManager( - genaccounts.AppModuleBasic{}, - genutil.AppModuleBasic{}, auth.AppModuleBasic{}, + supply.AppModuleBasic{}, + genutil.AppModuleBasic{}, bank.AppModuleBasic{}, staking.AppModuleBasic{}, mint.AppModuleBasic{}, @@ -55,7 +54,6 @@ var ( crisis.AppModuleBasic{}, slashing.AppModuleBasic{}, nft.AppModuleBasic{}, - supply.AppModuleBasic{}, ) // module account permissions @@ -178,7 +176,6 @@ func NewSimApp( // NOTE: Any module instantiated in the module manager that is later modified // must be passed by reference here. app.mm = module.NewManager( - genaccounts.NewAppModule(app.AccountKeeper), genutil.NewAppModule(app.AccountKeeper, app.StakingKeeper, app.BaseApp.DeliverTx), auth.NewAppModule(app.AccountKeeper), bank.NewAppModule(app.BankKeeper, app.AccountKeeper), @@ -202,8 +199,8 @@ func NewSimApp( // NOTE: The genutils moodule must occur after staking so that pools are // properly initialized with tokens from genesis accounts. app.mm.SetOrderInitGenesis( - genaccounts.ModuleName, distr.ModuleName, staking.ModuleName, - auth.ModuleName, bank.ModuleName, slashing.ModuleName, gov.ModuleName, + auth.ModuleName, distr.ModuleName, staking.ModuleName, + bank.ModuleName, slashing.ModuleName, gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, nft.ModuleName, genutil.ModuleName, ) @@ -216,7 +213,6 @@ func NewSimApp( // NOTE: this is not required apps that don't use the simulator for fuzz testing // transactions app.sm = module.NewSimulationManager( - genaccounts.NewAppModule(app.AccountKeeper), auth.NewAppModule(app.AccountKeeper), bank.NewAppModule(app.BankKeeper, app.AccountKeeper), supply.NewAppModule(app.SupplyKeeper, app.AccountKeeper), diff --git a/simapp/genesis_account.go b/simapp/genesis_account.go new file mode 100644 index 0000000000..e6bc7f97c0 --- /dev/null +++ b/simapp/genesis_account.go @@ -0,0 +1,52 @@ +package simapp + +import ( + "errors" + + sdk "github.com/cosmos/cosmos-sdk/types" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/supply" +) + +var _ authexported.GenesisAccount = (*SimGenesisAccount)(nil) + +// SimGenesisAccount defines a type that implements the GenesisAccount interface +// to be used for simulation accounts in the genesis state. +type SimGenesisAccount struct { + *authtypes.BaseAccount + + // vesting account fields + OriginalVesting sdk.Coins `json:"original_vesting" yaml:"original_vesting"` // total vesting coins upon initialization + DelegatedFree sdk.Coins `json:"delegated_free" yaml:"delegated_free"` // delegated vested coins at time of delegation + DelegatedVesting sdk.Coins `json:"delegated_vesting" yaml:"delegated_vesting"` // delegated vesting coins at time of delegation + StartTime int64 `json:"start_time" yaml:"start_time"` // vesting start time (UNIX Epoch time) + EndTime int64 `json:"end_time" yaml:"end_time"` // vesting end time (UNIX Epoch time) + + // module account fields + ModuleName string `json:"module_name" yaml:"module_name"` // name of the module account + ModulePermissions []string `json:"module_permissions" yaml:"module_permissions"` // permissions of module account +} + +// Validate checks for errors on the vesting and module account parameters +func (sga SimGenesisAccount) Validate() error { + if !sga.OriginalVesting.IsZero() { + if sga.OriginalVesting.IsAnyGT(sga.Coins) { + return errors.New("vesting amount cannot be greater than total amount") + } + if sga.StartTime >= sga.EndTime { + return errors.New("vesting start-time cannot be before end-time") + } + } + + if sga.ModuleName != "" { + ma := supply.ModuleAccount{ + BaseAccount: sga.BaseAccount, Name: sga.ModuleName, Permissions: sga.ModulePermissions, + } + if err := ma.Validate(); err != nil { + return err + } + } + + return sga.BaseAccount.Validate() +} diff --git a/simapp/genesis_account_test.go b/simapp/genesis_account_test.go new file mode 100644 index 0000000000..bbede51277 --- /dev/null +++ b/simapp/genesis_account_test.go @@ -0,0 +1,98 @@ +package simapp_test + +import ( + "testing" + "time" + + "github.com/cosmos/cosmos-sdk/simapp" + sdk "github.com/cosmos/cosmos-sdk/types" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/secp256k1" +) + +func TestSimGenesisAccountValidate(t *testing.T) { + pubkey := secp256k1.GenPrivKey().PubKey() + addr := sdk.AccAddress(pubkey.Address()) + + vestingStart := time.Now().UTC() + + coins := sdk.NewCoins(sdk.NewInt64Coin("test", 1000)) + baseAcc := authtypes.NewBaseAccount(addr, nil, pubkey, 0, 0) + require.NoError(t, baseAcc.SetCoins(coins)) + + testCases := []struct { + name string + sga simapp.SimGenesisAccount + wantErr bool + }{ + { + "valid basic account", + simapp.SimGenesisAccount{ + BaseAccount: baseAcc, + }, + false, + }, + { + "invalid basic account with mismatching address/pubkey", + simapp.SimGenesisAccount{ + BaseAccount: authtypes.NewBaseAccount(addr, nil, secp256k1.GenPrivKey().PubKey(), 0, 0), + }, + true, + }, + { + "valid basic account with module name", + simapp.SimGenesisAccount{ + BaseAccount: authtypes.NewBaseAccount(sdk.AccAddress(crypto.AddressHash([]byte("testmod"))), nil, nil, 0, 0), + ModuleName: "testmod", + }, + false, + }, + { + "valid basic account with invalid module name/pubkey pair", + simapp.SimGenesisAccount{ + BaseAccount: baseAcc, + ModuleName: "testmod", + }, + true, + }, + { + "valid basic account with valid vesting attributes", + simapp.SimGenesisAccount{ + BaseAccount: baseAcc, + OriginalVesting: coins, + StartTime: vestingStart.Unix(), + EndTime: vestingStart.Add(1 * time.Hour).Unix(), + }, + false, + }, + { + "valid basic account with invalid vesting end time", + simapp.SimGenesisAccount{ + BaseAccount: baseAcc, + OriginalVesting: coins, + StartTime: vestingStart.Add(2 * time.Hour).Unix(), + EndTime: vestingStart.Add(1 * time.Hour).Unix(), + }, + true, + }, + { + "valid basic account with invalid original vesting coins", + simapp.SimGenesisAccount{ + BaseAccount: baseAcc, + OriginalVesting: coins.Add(coins), + StartTime: vestingStart.Unix(), + EndTime: vestingStart.Add(1 * time.Hour).Unix(), + }, + true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + require.Equal(t, tc.wantErr, tc.sga.Validate() != nil) + }) + } +} diff --git a/simapp/state.go b/simapp/state.go index ea9ca29f0f..f00e478ec9 100644 --- a/simapp/state.go +++ b/simapp/state.go @@ -12,7 +12,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/x/genaccounts" + "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/simulation" ) @@ -126,9 +126,12 @@ func AppStateFromGenesisFileFn(r *rand.Rand, cdc *codec.Codec, genesisFile strin var appState GenesisState cdc.MustUnmarshalJSON(genesis.AppState, &appState) - accounts := genaccounts.GetGenesisStateFromAppState(cdc, appState) + var authGenesis auth.GenesisState + if appState[auth.ModuleName] != nil { + cdc.MustUnmarshalJSON(appState[auth.ModuleName], &authGenesis) + } - for _, acc := range accounts { + for _, acc := range authGenesis.Accounts { // Pick a random private key, since we don't know the actual key // This should be fine as it's only used for mock Tendermint validators // and these keys are never actually used to sign by mock Tendermint. @@ -140,7 +143,7 @@ func AppStateFromGenesisFileFn(r *rand.Rand, cdc *codec.Codec, genesisFile strin privKey := secp256k1.GenPrivKeySecp256k1(privkeySeed) // create simulator accounts - simAcc := simulation.Account{PrivKey: privKey, PubKey: privKey.PubKey(), Address: acc.Address} + simAcc := simulation.Account{PrivKey: privKey, PubKey: privKey.PubKey(), Address: acc.GetAddress()} newAccs = append(newAccs, simAcc) } diff --git a/simapp/test_helpers.go b/simapp/test_helpers.go index 85b9db48d5..c108d9f9f5 100644 --- a/simapp/test_helpers.go +++ b/simapp/test_helpers.go @@ -15,7 +15,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/genaccounts" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/supply" ) @@ -45,13 +45,17 @@ func Setup(isCheckTx bool) *SimApp { // SetupWithGenesisAccounts initializes a new SimApp with the passed in // genesis accounts. -func SetupWithGenesisAccounts(genAccs genaccounts.GenesisAccounts) *SimApp { +func SetupWithGenesisAccounts(genAccs []authexported.GenesisAccount) *SimApp { db := dbm.NewMemDB() app := NewSimApp(log.NewTMLogger(log.NewSyncWriter(os.Stdout)), db, nil, true, 0) // initialize the chain with the passed in genesis accounts genesisState := NewDefaultGenesisState() - genesisState = genaccounts.SetGenesisStateInAppState(app.Codec(), genesisState, genaccounts.GenesisState(genAccs)) + + authGenesis := auth.NewGenesisState(auth.DefaultParams(), genAccs) + genesisStateBz := app.cdc.MustMarshalJSON(authGenesis) + genesisState[auth.ModuleName] = genesisStateBz + stateBytes, err := codec.MarshalJSONIndent(app.cdc, genesisState) if err != nil { panic(err) diff --git a/x/auth/alias.go b/x/auth/alias.go index f6a721497c..bae90af4c6 100644 --- a/x/auth/alias.go +++ b/x/auth/alias.go @@ -51,9 +51,11 @@ var ( NewDelayedVestingAccount = types.NewDelayedVestingAccount NewAccountRetriever = types.NewAccountRetriever RegisterCodec = types.RegisterCodec + RegisterAccountTypeCodec = types.RegisterAccountTypeCodec NewGenesisState = types.NewGenesisState DefaultGenesisState = types.DefaultGenesisState ValidateGenesis = types.ValidateGenesis + Sanitize = types.Sanitize AddressStoreKey = types.AddressStoreKey NewParams = types.NewParams ParamKeyTable = types.ParamKeyTable diff --git a/x/auth/exported/exported.go b/x/auth/exported/exported.go index e78cd1ece1..16e66136bf 100644 --- a/x/auth/exported/exported.go +++ b/x/auth/exported/exported.go @@ -57,3 +57,9 @@ type VestingAccount interface { GetDelegatedFree() sdk.Coins GetDelegatedVesting() sdk.Coins } + +// GenesisAccount defines a genesis account that embeds an Account with validation capabilities. +type GenesisAccount interface { + Account + Validate() error +} diff --git a/x/auth/genesis.go b/x/auth/genesis.go index 6f4931b139..f83570e458 100644 --- a/x/auth/genesis.go +++ b/x/auth/genesis.go @@ -2,6 +2,7 @@ package auth import ( sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/exported" ) // InitGenesis - Init store state from genesis data @@ -10,10 +11,24 @@ import ( // a genesis port script to the new fee collector account func InitGenesis(ctx sdk.Context, ak AccountKeeper, data GenesisState) { ak.SetParams(ctx, data.Params) + data.Accounts = Sanitize(data.Accounts) + + for _, a := range data.Accounts { + acc := ak.NewAccount(ctx, a) + ak.SetAccount(ctx, acc) + } } // ExportGenesis returns a GenesisState for a given context and keeper func ExportGenesis(ctx sdk.Context, ak AccountKeeper) GenesisState { params := ak.GetParams(ctx) - return NewGenesisState(params) + + var genAccounts []exported.GenesisAccount + ak.IterateAccounts(ctx, func(account exported.Account) bool { + genAccount := account.(exported.GenesisAccount) + genAccounts = append(genAccounts, genAccount) + return false + }) + + return NewGenesisState(params, genAccounts) } diff --git a/x/auth/legacy/v0_38/migrate.go b/x/auth/legacy/v0_38/migrate.go new file mode 100644 index 0000000000..35c2c73715 --- /dev/null +++ b/x/auth/legacy/v0_38/migrate.go @@ -0,0 +1,13 @@ +package v038 + +import ( + "encoding/json" + + v036auth "github.com/cosmos/cosmos-sdk/x/auth/legacy/v0_36" +) + +// Migrate accepts exported genesis state from v0.34 and migrates it to v0.38 +// genesis state. +func Migrate(oldGenState v036auth.GenesisState, accounts json.RawMessage) GenesisState { + return NewGenesisState(oldGenState.Params, accounts) +} diff --git a/x/auth/legacy/v0_38/migrate_test.go b/x/auth/legacy/v0_38/migrate_test.go new file mode 100644 index 0000000000..67d401c64a --- /dev/null +++ b/x/auth/legacy/v0_38/migrate_test.go @@ -0,0 +1,124 @@ +package v038 + +import ( + "encoding/json" + "testing" + + v034auth "github.com/cosmos/cosmos-sdk/x/auth/legacy/v0_34" + v036auth "github.com/cosmos/cosmos-sdk/x/auth/legacy/v0_36" + + "github.com/stretchr/testify/require" +) + +func TestMigrate(t *testing.T) { + var genesisState GenesisState + + params := v034auth.Params{ + MaxMemoCharacters: 10, + TxSigLimit: 10, + TxSizeCostPerByte: 10, + SigVerifyCostED25519: 10, + SigVerifyCostSecp256k1: 10, + } + rawAccounts := `[ + { + "address": "cosmos1dfp05pasnts7a4lupn889vptjtrxzkk5f7027f", + "coins": [ + { + "denom": "node0token", + "amount": "1000000000" + }, + { + "denom": "stake", + "amount": "500000000" + } + ], + "sequence_number": "0", + "account_number": "0", + "original_vesting": [], + "delegated_free": [], + "delegated_vesting": [], + "start_time": "0", + "end_time": "0", + "module_name": "", + "module_permissions": null + }, + { + "address": "cosmos1f6dangl9ggdhuvkcwhswserr8fzra6vfzfjvh2", + "coins": [ + { + "denom": "node1token", + "amount": "1000000000" + }, + { + "denom": "stake", + "amount": "500000000" + } + ], + "sequence_number": "0", + "account_number": "0", + "original_vesting": [], + "delegated_free": [], + "delegated_vesting": [], + "start_time": "0", + "end_time": "0", + "module_name": "", + "module_permissions": null + }, + { + "address": "cosmos1gudmxhn5anh5m6m2rr4rsfhgvps8fchtgmk7a6", + "coins": [ + { + "denom": "node2token", + "amount": "1000000000" + }, + { + "denom": "stake", + "amount": "500000000" + } + ], + "sequence_number": "0", + "account_number": "0", + "original_vesting": [], + "delegated_free": [], + "delegated_vesting": [], + "start_time": "0", + "end_time": "0", + "module_name": "", + "module_permissions": null + }, + { + "address": "cosmos1kluvs8ff2s3hxad4jpmhvca4crqpcwn9xyhchv", + "coins": [ + { + "denom": "node3token", + "amount": "1000000000" + }, + { + "denom": "stake", + "amount": "500000000" + } + ], + "sequence_number": "0", + "account_number": "0", + "original_vesting": [], + "delegated_free": [], + "delegated_vesting": [], + "start_time": "0", + "end_time": "0", + "module_name": "", + "module_permissions": null + } + ]` + + require.NotPanics(t, func() { + genesisState = Migrate( + v036auth.GenesisState{ + Params: params, + }, + json.RawMessage(rawAccounts), + ) + }) + + require.Equal(t, genesisState, GenesisState{Params: params, Accounts: json.RawMessage(rawAccounts)}) +} diff --git a/x/auth/legacy/v0_38/types.go b/x/auth/legacy/v0_38/types.go new file mode 100644 index 0000000000..7c8f12ef0e --- /dev/null +++ b/x/auth/legacy/v0_38/types.go @@ -0,0 +1,26 @@ +package v038 + +import ( + "encoding/json" + + v034auth "github.com/cosmos/cosmos-sdk/x/auth/legacy/v0_34" +) + +// DONTCOVER + +// nolint +const ( + ModuleName = "auth" +) + +type GenesisState struct { + Params v034auth.Params `json:"params"` + Accounts json.RawMessage `json:"accounts"` +} + +func NewGenesisState(params v034auth.Params, accounts json.RawMessage) GenesisState { + return GenesisState{ + Params: params, + Accounts: accounts, + } +} diff --git a/x/auth/simulation/genesis.go b/x/auth/simulation/genesis.go index 69e85961da..5f922cec9c 100644 --- a/x/auth/simulation/genesis.go +++ b/x/auth/simulation/genesis.go @@ -7,7 +7,9 @@ import ( "math/rand" "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" + "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/simulation" ) @@ -78,11 +80,50 @@ func RandomizedGenState(simState *module.SimulationState) { func(r *rand.Rand) { sigVerifyCostED25519 = GenSigVerifyCostSECP256K1(r) }, ) - authGenesis := types.NewGenesisState( - types.NewParams(maxMemoChars, txSigLimit, txSizeCostPerByte, - sigVerifyCostED25519, sigVerifyCostSECP256K1), - ) + params := types.NewParams(maxMemoChars, txSigLimit, txSizeCostPerByte, + sigVerifyCostED25519, sigVerifyCostSECP256K1) + genesisAccs := RandomGenesisAccounts(simState) + + authGenesis := types.NewGenesisState(params, genesisAccs) fmt.Printf("Selected randomly generated auth parameters:\n%s\n", codec.MustMarshalJSONIndent(simState.Cdc, authGenesis.Params)) simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(authGenesis) } + +// RandomGenesisAccounts returns randomly generated genesis accounts +func RandomGenesisAccounts(simState *module.SimulationState) (genesisAccs []exported.GenesisAccount) { + for i, acc := range simState.Accounts { + coins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(simState.InitialStake))} + bacc := types.NewBaseAccountWithAddress(acc.Address) + if err := bacc.SetCoins(coins); err != nil { + panic(err) + } + + var gacc exported.GenesisAccount + gacc = &bacc + + // Only consider making a vesting account once the initial bonded validator + // set is exhausted due to needing to track DelegatedVesting. + if int64(i) > simState.NumBonded && simState.Rand.Intn(100) < 50 { + var endTime int64 + + startTime := simState.GenTimestamp.Unix() + + // Allow for some vesting accounts to vest very quickly while others very slowly. + if simState.Rand.Intn(100) < 50 { + endTime = int64(simulation.RandIntBetween(simState.Rand, int(startTime)+1, int(startTime+(60*60*24*30)))) + } else { + endTime = int64(simulation.RandIntBetween(simState.Rand, int(startTime)+1, int(startTime+(60*60*12)))) + } + + if simState.Rand.Intn(100) < 50 { + gacc = types.NewContinuousVestingAccount(&bacc, startTime, endTime) + } else { + gacc = types.NewDelayedVestingAccount(&bacc, endTime) + } + } + genesisAccs = append(genesisAccs, gacc) + } + + return +} diff --git a/x/auth/types/account.go b/x/auth/types/account.go index 31a77b15c8..d004044dff 100644 --- a/x/auth/types/account.go +++ b/x/auth/types/account.go @@ -1,6 +1,7 @@ package types import ( + "bytes" "errors" "fmt" "time" @@ -16,6 +17,7 @@ import ( // BaseAccount var _ exported.Account = (*BaseAccount)(nil) +var _ exported.GenesisAccount = (*BaseAccount)(nil) // BaseAccount - a base account structure. // This can be extended by embedding within in your AppAccount. @@ -169,6 +171,16 @@ func (acc BaseAccount) MarshalYAML() (interface{}, error) { return string(bs), err } +// Validate checks for errors on the account fields +func (acc BaseAccount) Validate() error { + if acc.PubKey != nil && acc.Address != nil && + !bytes.Equal(acc.PubKey.Address().Bytes(), acc.Address.Bytes()) { + return errors.New("pubkey and address pair is invalid") + } + + return nil +} + //----------------------------------------------------------------------------- // Base Vesting Account @@ -346,10 +358,20 @@ func (bva BaseVestingAccount) GetDelegatedVesting() sdk.Coins { return bva.DelegatedVesting } +// Validate checks for errors on the account fields +func (bva BaseVestingAccount) Validate() error { + if (bva.Coins.IsZero() && !bva.OriginalVesting.IsZero()) || + bva.OriginalVesting.IsAnyGT(bva.Coins) { + return errors.New("vesting amount cannot be greater than total amount") + } + return bva.BaseAccount.Validate() +} + //----------------------------------------------------------------------------- // Continuous Vesting Account var _ exported.VestingAccount = (*ContinuousVestingAccount)(nil) +var _ exported.GenesisAccount = (*ContinuousVestingAccount)(nil) // ContinuousVestingAccount implements the VestingAccount interface. It // continuously vests by unlocking coins linearly with respect to time. @@ -467,10 +489,20 @@ func (cva *ContinuousVestingAccount) GetEndTime() int64 { return cva.EndTime } +// Validate checks for errors on the account fields +func (cva ContinuousVestingAccount) Validate() error { + if cva.GetStartTime() >= cva.GetEndTime() { + return errors.New("vesting start-time cannot be before end-time") + } + + return cva.BaseVestingAccount.Validate() +} + //----------------------------------------------------------------------------- // Delayed Vesting Account var _ exported.VestingAccount = (*DelayedVestingAccount)(nil) +var _ exported.GenesisAccount = (*DelayedVestingAccount)(nil) // DelayedVestingAccount implements the VestingAccount interface. It vests all // coins after a specific time, but non prior. In other words, it keeps them @@ -535,3 +567,8 @@ func (dva *DelayedVestingAccount) GetStartTime() int64 { func (dva *DelayedVestingAccount) GetEndTime() int64 { return dva.EndTime } + +// Validate checks for errors on the account fields +func (dva DelayedVestingAccount) Validate() error { + return dva.BaseVestingAccount.Validate() +} diff --git a/x/auth/types/account_test.go b/x/auth/types/account_test.go index 1ea24f470d..e42e0d9d1d 100644 --- a/x/auth/types/account_test.go +++ b/x/auth/types/account_test.go @@ -1,14 +1,17 @@ package types import ( + "errors" "testing" "time" "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/secp256k1" tmtime "github.com/tendermint/tendermint/types/time" "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/exported" ) var ( @@ -479,3 +482,73 @@ func TestTrackUndelegationDelVestingAcc(t *testing.T) { require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 25)}, dva.DelegatedVesting) require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 75)}, dva.GetCoins()) } + +func TestGenesisAccountValidate(t *testing.T) { + pubkey := secp256k1.GenPrivKey().PubKey() + addr := sdk.AccAddress(pubkey.Address()) + baseAcc := NewBaseAccount(addr, nil, pubkey, 0, 0) + tests := []struct { + name string + acc exported.GenesisAccount + expErr error + }{ + { + "valid base account", + baseAcc, + nil, + }, + { + "invalid base valid account", + NewBaseAccount(addr, sdk.NewCoins(), secp256k1.GenPrivKey().PubKey(), 0, 0), + errors.New("pubkey and address pair is invalid"), + }, + { + "valid base vesting account", + NewBaseVestingAccount(baseAcc, sdk.NewCoins(), nil, nil, 100), + nil, + }, + { + "invalid vesting amount; empty Coins", + NewBaseVestingAccount( + NewBaseAccount(addr, sdk.NewCoins(), pubkey, 0, 0), + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 50)}, + nil, nil, 100, + ), + errors.New("vesting amount cannot be greater than total amount"), + }, + { + "invalid vesting amount; OriginalVesting > Coins", + NewBaseVestingAccount( + NewBaseAccount(addr, sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 10)), pubkey, 0, 0), + sdk.Coins{sdk.NewInt64Coin(sdk.DefaultBondDenom, 50)}, + nil, nil, 100, + ), + errors.New("vesting amount cannot be greater than total amount"), + }, + { + "invalid vesting amount with multi coins", + NewBaseVestingAccount( + NewBaseAccount(addr, sdk.NewCoins(sdk.NewInt64Coin("uatom", 50), sdk.NewInt64Coin("eth", 50)), pubkey, 0, 0), + sdk.NewCoins(sdk.NewInt64Coin("uatom", 100), sdk.NewInt64Coin("eth", 20)), + nil, nil, 100, + ), + errors.New("vesting amount cannot be greater than total amount"), + }, + { + "valid continuous vesting account", + NewContinuousVestingAccount(baseAcc, 100, 200), + nil, + }, + { + "invalid vesting times", + NewContinuousVestingAccount(baseAcc, 1654668078, 1554668078), + errors.New("vesting start-time cannot be before end-time"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.acc.Validate() + require.Equal(t, tt.expErr, err) + }) + } +} diff --git a/x/auth/types/codec.go b/x/auth/types/codec.go index 81a6ee8944..bc5f64bd99 100644 --- a/x/auth/types/codec.go +++ b/x/auth/types/codec.go @@ -5,8 +5,12 @@ import ( "github.com/cosmos/cosmos-sdk/x/auth/exported" ) +// ModuleCdc auth module wide codec +var ModuleCdc = codec.New() + // RegisterCodec registers concrete types on the codec func RegisterCodec(cdc *codec.Codec) { + cdc.RegisterInterface((*exported.GenesisAccount)(nil), nil) cdc.RegisterInterface((*exported.Account)(nil), nil) cdc.RegisterConcrete(&BaseAccount{}, "cosmos-sdk/Account", nil) cdc.RegisterInterface((*exported.VestingAccount)(nil), nil) @@ -16,12 +20,13 @@ func RegisterCodec(cdc *codec.Codec) { cdc.RegisterConcrete(StdTx{}, "cosmos-sdk/StdTx", nil) } -// module wide codec -var ModuleCdc *codec.Codec +// RegisterAccountTypeCodec registers an external account type defined in +// another module for the internal ModuleCdc. +func RegisterAccountTypeCodec(o interface{}, name string) { + ModuleCdc.RegisterConcrete(o, name, nil) +} func init() { - ModuleCdc = codec.New() RegisterCodec(ModuleCdc) codec.RegisterCrypto(ModuleCdc) - ModuleCdc.Seal() } diff --git a/x/auth/types/genesis.go b/x/auth/types/genesis.go index 4c02e68f4e..0d2b620fc3 100644 --- a/x/auth/types/genesis.go +++ b/x/auth/types/genesis.go @@ -2,40 +2,71 @@ package types import ( "fmt" + "sort" + + "github.com/cosmos/cosmos-sdk/x/auth/exported" ) // GenesisState - all auth state that must be provided at genesis type GenesisState struct { - Params Params `json:"params" yaml:"params"` + Params Params `json:"params" yaml:"params"` + Accounts []exported.GenesisAccount `json:"accounts" yaml:"accounts"` } // NewGenesisState - Create a new genesis state -func NewGenesisState(params Params) GenesisState { - return GenesisState{params} +func NewGenesisState(params Params, accounts []exported.GenesisAccount) GenesisState { + return GenesisState{ + Params: params, + Accounts: accounts, + } } // DefaultGenesisState - Return a default genesis state func DefaultGenesisState() GenesisState { - return NewGenesisState(DefaultParams()) + return NewGenesisState(DefaultParams(), []exported.GenesisAccount{}) } // ValidateGenesis performs basic validation of auth genesis data returning an // error for any failed validation criteria. func ValidateGenesis(data GenesisState) error { - if data.Params.TxSigLimit == 0 { - return fmt.Errorf("invalid tx signature limit: %d", data.Params.TxSigLimit) + if err := data.Params.Validate(); err != nil { + return err } - if data.Params.SigVerifyCostED25519 == 0 { - return fmt.Errorf("invalid ED25519 signature verification cost: %d", data.Params.SigVerifyCostED25519) + + return validateGenAccounts(data.Accounts) +} + +// Sanitize sorts accounts and coin sets. +func Sanitize(genAccs []exported.GenesisAccount) []exported.GenesisAccount { + sort.Slice(genAccs, func(i, j int) bool { + return genAccs[i].GetAccountNumber() < genAccs[j].GetAccountNumber() + }) + + for _, acc := range genAccs { + if err := acc.SetCoins(acc.GetCoins().Sort()); err != nil { + panic(err) + } } - if data.Params.SigVerifyCostSecp256k1 == 0 { - return fmt.Errorf("invalid SECK256k1 signature verification cost: %d", data.Params.SigVerifyCostSecp256k1) - } - if data.Params.MaxMemoCharacters == 0 { - return fmt.Errorf("invalid max memo characters: %d", data.Params.MaxMemoCharacters) - } - if data.Params.TxSizeCostPerByte == 0 { - return fmt.Errorf("invalid tx size cost per byte: %d", data.Params.TxSizeCostPerByte) + + return genAccs +} + +func validateGenAccounts(accounts []exported.GenesisAccount) error { + addrMap := make(map[string]bool, len(accounts)) + for _, acc := range accounts { + + // check for duplicated accounts + addrStr := acc.GetAddress().String() + if _, ok := addrMap[addrStr]; ok { + return fmt.Errorf("duplicate account found in genesis state; address: %s", addrStr) + } + + addrMap[addrStr] = true + + // check account specific validation + if err := acc.Validate(); err != nil { + return fmt.Errorf("invalid account found in genesis state; address: %s, error: %s", addrStr, err.Error()) + } } return nil } diff --git a/x/auth/types/genesis_test.go b/x/auth/types/genesis_test.go new file mode 100644 index 0000000000..1dff20bc0f --- /dev/null +++ b/x/auth/types/genesis_test.go @@ -0,0 +1,82 @@ +package types + +import ( + "testing" + + "github.com/stretchr/testify/require" + "github.com/tendermint/tendermint/crypto/ed25519" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth/exported" +) + +func TestSanitize(t *testing.T) { + addr1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + authAcc1 := NewBaseAccountWithAddress(addr1) + authAcc1.SetCoins(sdk.Coins{ + sdk.NewInt64Coin("bcoin", 150), + sdk.NewInt64Coin("acoin", 150), + }) + authAcc1.SetAccountNumber(1) + + addr2 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + authAcc2 := NewBaseAccountWithAddress(addr2) + authAcc2.SetCoins(sdk.Coins{ + sdk.NewInt64Coin("acoin", 150), + sdk.NewInt64Coin("bcoin", 150), + }) + + genAccs := []exported.GenesisAccount{&authAcc1, &authAcc2} + + require.True(t, genAccs[0].GetAccountNumber() > genAccs[1].GetAccountNumber()) + require.Equal(t, genAccs[0].GetCoins()[0].Denom, "bcoin") + require.Equal(t, genAccs[0].GetCoins()[1].Denom, "acoin") + require.Equal(t, genAccs[1].GetAddress(), addr2) + genAccs = Sanitize(genAccs) + + require.False(t, genAccs[0].GetAccountNumber() > genAccs[1].GetAccountNumber()) + require.Equal(t, genAccs[1].GetAddress(), addr1) + require.Equal(t, genAccs[1].GetCoins()[0].Denom, "acoin") + require.Equal(t, genAccs[1].GetCoins()[1].Denom, "bcoin") +} + +var ( + pk1 = ed25519.GenPrivKey().PubKey() + pk2 = ed25519.GenPrivKey().PubKey() + addr1 = sdk.ValAddress(pk1.Address()) + addr2 = sdk.ValAddress(pk2.Address()) +) + +// require duplicate accounts fails validation +func TestValidateGenesisDuplicateAccounts(t *testing.T) { + acc1 := NewBaseAccountWithAddress(sdk.AccAddress(addr1)) + acc1.Coins = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 150)) + + genAccs := make([]exported.GenesisAccount, 2) + genAccs[0] = &acc1 + genAccs[1] = &acc1 + + require.Error(t, validateGenAccounts(genAccs)) +} + +// require invalid vesting account fails validation (invalid end time) +func TestValidateGenesisInvalidAccounts(t *testing.T) { + acc1 := NewBaseAccountWithAddress(sdk.AccAddress(addr1)) + acc1.Coins = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 150)) + baseVestingAcc := NewBaseVestingAccount(&acc1, acc1.Coins.Add(acc1.Coins), nil, nil, 1548775410) + + acc2 := NewBaseAccountWithAddress(sdk.AccAddress(addr2)) + acc2.Coins = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 150)) + + genAccs := make([]exported.GenesisAccount, 2) + genAccs[0] = baseVestingAcc + genAccs[1] = &acc2 + + require.Error(t, validateGenAccounts(genAccs)) + baseVestingAcc.OriginalVesting = acc1.Coins + genAccs[0] = baseVestingAcc + require.NoError(t, validateGenAccounts(genAccs)) + + genAccs[0] = NewContinuousVestingAccountRaw(baseVestingAcc, 1548888000) + require.Error(t, validateGenAccounts(genAccs)) +} diff --git a/x/auth/types/params.go b/x/auth/types/params.go index 4d0768feac..288ed33434 100644 --- a/x/auth/types/params.go +++ b/x/auth/types/params.go @@ -100,3 +100,23 @@ func (p Params) String() string { sb.WriteString(fmt.Sprintf("SigVerifyCostSecp256k1: %d\n", p.SigVerifyCostSecp256k1)) return sb.String() } + +// Validate checks that the parameters have valid values. +func (p Params) Validate() error { + if p.TxSigLimit == 0 { + return fmt.Errorf("invalid tx signature limit: %d", p.TxSigLimit) + } + if p.SigVerifyCostED25519 == 0 { + return fmt.Errorf("invalid ED25519 signature verification cost: %d", p.SigVerifyCostED25519) + } + if p.SigVerifyCostSecp256k1 == 0 { + return fmt.Errorf("invalid SECK256k1 signature verification cost: %d", p.SigVerifyCostSecp256k1) + } + if p.MaxMemoCharacters == 0 { + return fmt.Errorf("invalid max memo characters: %d", p.MaxMemoCharacters) + } + if p.TxSizeCostPerByte == 0 { + return fmt.Errorf("invalid tx size cost per byte: %d", p.TxSizeCostPerByte) + } + return nil +} diff --git a/x/bank/app_test.go b/x/bank/app_test.go index df7246b928..35a42d8d60 100644 --- a/x/bank/app_test.go +++ b/x/bank/app_test.go @@ -12,8 +12,8 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/bank/internal/types" - "github.com/cosmos/cosmos-sdk/x/genaccounts" ) type ( @@ -93,7 +93,7 @@ func TestSendNotEnoughBalance(t *testing.T) { Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 67)}, } - genAccs := []genaccounts.GenesisAccount{genaccounts.NewGenesisAccount(acc)} + genAccs := []authexported.GenesisAccount{acc} app := simapp.SetupWithGenesisAccounts(genAccs) ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) @@ -125,7 +125,7 @@ func TestSendToModuleAcc(t *testing.T) { Coins: coins, } - genAccs := []genaccounts.GenesisAccount{genaccounts.NewGenesisAccount(acc)} + genAccs := []authexported.GenesisAccount{acc} app := simapp.SetupWithGenesisAccounts(genAccs) ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) @@ -156,7 +156,7 @@ func TestMsgMultiSendWithAccounts(t *testing.T) { Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 67)}, } - genAccs := []genaccounts.GenesisAccount{genaccounts.NewGenesisAccount(acc)} + genAccs := []authexported.GenesisAccount{acc} app := simapp.SetupWithGenesisAccounts(genAccs) ctxCheck := app.BaseApp.NewContext(true, abci.Header{}) @@ -217,7 +217,7 @@ func TestMsgMultiSendMultipleOut(t *testing.T) { Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}, } - genAccs := []genaccounts.GenesisAccount{genaccounts.NewGenesisAccount(acc1), genaccounts.NewGenesisAccount(acc2)} + genAccs := []authexported.GenesisAccount{acc1, acc2} app := simapp.SetupWithGenesisAccounts(genAccs) testCases := []appTestCase{ @@ -261,7 +261,7 @@ func TestMsgMultiSendMultipleInOut(t *testing.T) { Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 42)}, } - genAccs := []genaccounts.GenesisAccount{genaccounts.NewGenesisAccount(acc1), genaccounts.NewGenesisAccount(acc2), genaccounts.NewGenesisAccount(acc4)} + genAccs := []authexported.GenesisAccount{acc1, acc2, acc4} app := simapp.SetupWithGenesisAccounts(genAccs) testCases := []appTestCase{ @@ -299,7 +299,7 @@ func TestMsgMultiSendDependent(t *testing.T) { err = acc2.SetAccountNumber(1) require.NoError(t, err) - genAccs := []genaccounts.GenesisAccount{genaccounts.NewGenesisAccount(&acc1), genaccounts.NewGenesisAccount(&acc2)} + genAccs := []authexported.GenesisAccount{&acc1, &acc2} app := simapp.SetupWithGenesisAccounts(genAccs) testCases := []appTestCase{ diff --git a/x/bank/bench_test.go b/x/bank/bench_test.go index 803b315a15..9e0bd73548 100644 --- a/x/bank/bench_test.go +++ b/x/bank/bench_test.go @@ -8,7 +8,7 @@ import ( "github.com/cosmos/cosmos-sdk/simapp" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/genaccounts" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/supply" ) @@ -17,14 +17,14 @@ var moduleAccAddr = supply.NewModuleAddress(staking.BondedPoolName) func BenchmarkOneBankSendTxPerBlock(b *testing.B) { // Add an account at genesis - acc := &auth.BaseAccount{ + acc := auth.BaseAccount{ Address: addr1, // Some value conceivably higher than the benchmarks would ever go Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 100000000000)}, } // Construct genesis state - genAccs := []genaccounts.GenesisAccount{genaccounts.NewGenesisAccount(acc)} + genAccs := []authexported.GenesisAccount{&acc} benchmarkApp := simapp.SetupWithGenesisAccounts(genAccs) // Precompute all txs @@ -46,14 +46,14 @@ func BenchmarkOneBankSendTxPerBlock(b *testing.B) { func BenchmarkOneBankMultiSendTxPerBlock(b *testing.B) { // Add an account at genesis - acc := &auth.BaseAccount{ + acc := auth.BaseAccount{ Address: addr1, // Some value conceivably higher than the benchmarks would ever go Coins: sdk.Coins{sdk.NewInt64Coin("foocoin", 100000000000)}, } // Construct genesis state - genAccs := []genaccounts.GenesisAccount{genaccounts.NewGenesisAccount(acc)} + genAccs := []authexported.GenesisAccount{&acc} benchmarkApp := simapp.SetupWithGenesisAccounts(genAccs) // Precompute all txs diff --git a/x/genaccounts/alias.go b/x/genaccounts/alias.go deleted file mode 100644 index 159e537f1f..0000000000 --- a/x/genaccounts/alias.go +++ /dev/null @@ -1,32 +0,0 @@ -// nolint -// autogenerated code using github.com/rigelrozanski/multitool -// aliases generated for the following subdirectories: -// ALIASGEN: github.com/cosmos/cosmos-sdk/x/genaccounts/internal/types -package genaccounts - -import ( - "github.com/cosmos/cosmos-sdk/x/genaccounts/internal/types" -) - -const ( - ModuleName = types.ModuleName -) - -var ( - // functions aliases - NewGenesisAccountRaw = types.NewGenesisAccountRaw - NewGenesisAccount = types.NewGenesisAccount - NewGenesisAccountI = types.NewGenesisAccountI - GetGenesisStateFromAppState = types.GetGenesisStateFromAppState - SetGenesisStateInAppState = types.SetGenesisStateInAppState - ValidateGenesis = types.ValidateGenesis - - // variable aliases - ModuleCdc = types.ModuleCdc -) - -type ( - GenesisAccount = types.GenesisAccount - GenesisAccounts = types.GenesisAccounts - GenesisState = types.GenesisState -) diff --git a/x/genaccounts/client/cli/genesis_accts.go b/x/genaccounts/client/cli/genesis_accts.go deleted file mode 100644 index 6ae443c95a..0000000000 --- a/x/genaccounts/client/cli/genesis_accts.go +++ /dev/null @@ -1,107 +0,0 @@ -package cli - -import ( - "fmt" - - "github.com/spf13/cobra" - "github.com/spf13/viper" - "github.com/tendermint/tendermint/libs/cli" - - "github.com/cosmos/cosmos-sdk/client/keys" - "github.com/cosmos/cosmos-sdk/codec" - "github.com/cosmos/cosmos-sdk/server" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/genaccounts" - "github.com/cosmos/cosmos-sdk/x/genutil" -) - -const ( - flagClientHome = "home-client" - flagVestingStart = "vesting-start-time" - flagVestingEnd = "vesting-end-time" - flagVestingAmt = "vesting-amount" -) - -// AddGenesisAccountCmd returns add-genesis-account cobra Command. -func AddGenesisAccountCmd(ctx *server.Context, cdc *codec.Codec, - defaultNodeHome, defaultClientHome string) *cobra.Command { - cmd := &cobra.Command{ - Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]", - Short: "Add genesis account to genesis.json", - Args: cobra.ExactArgs(2), - RunE: func(_ *cobra.Command, args []string) error { - config := ctx.Config - config.SetRoot(viper.GetString(cli.HomeFlag)) - - addr, err := sdk.AccAddressFromBech32(args[0]) - if err != nil { - kb, err := keys.NewKeyBaseFromDir(viper.GetString(flagClientHome)) - 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 - } - - vestingStart := viper.GetInt64(flagVestingStart) - vestingEnd := viper.GetInt64(flagVestingEnd) - vestingAmt, err := sdk.ParseCoins(viper.GetString(flagVestingAmt)) - if err != nil { - return err - } - - genAcc := genaccounts.NewGenesisAccountRaw(addr, coins, vestingAmt, vestingStart, vestingEnd, "", "") - if err := genAcc.Validate(); err != nil { - return err - } - - // retrieve the app state - genFile := config.GenesisFile() - appState, genDoc, err := genutil.GenesisStateFromGenFile(cdc, genFile) - if err != nil { - return err - } - - // add genesis account to the app state - var genesisAccounts genaccounts.GenesisAccounts - - cdc.MustUnmarshalJSON(appState[genaccounts.ModuleName], &genesisAccounts) - - if genesisAccounts.Contains(addr) { - return fmt.Errorf("cannot add account at existing address %v", addr) - } - - genesisAccounts = append(genesisAccounts, genAcc) - - genesisStateBz := cdc.MustMarshalJSON(genaccounts.GenesisState(genesisAccounts)) - appState[genaccounts.ModuleName] = genesisStateBz - - appStateJSON, err := cdc.MarshalJSON(appState) - if err != nil { - return err - } - - // export app state - genDoc.AppState = appStateJSON - - return genutil.ExportGenesisFile(genDoc, genFile) - }, - } - - cmd.Flags().String(cli.HomeFlag, defaultNodeHome, "node's home directory") - cmd.Flags().String(flagClientHome, defaultClientHome, "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 -} diff --git a/x/genaccounts/doc.go b/x/genaccounts/doc.go index 60c6e3c730..ac2f1a8c1a 100644 --- a/x/genaccounts/doc.go +++ b/x/genaccounts/doc.go @@ -1,9 +1,15 @@ /* -Package genaccounts contains specialized functionality for initializing -accounts from genesis including: - - genesis account validation, - - initchain processing of genesis accounts, - - export processing (to genesis) of accounts, - - server command for adding accounts to the genesis file. +Package genaccounts is now deprecated. + +IMPORTANT: This module has been replaced by ADR 011: Generalize Module Accounts. +The ADR can be found here: https://github.com/cosmos/cosmos-sdk/blob/master/docs/architecture/adr-011-generalize-genesis-accounts.md. + +Genesis accounts that existed in the genesis application state under `app_state.accounts` +now exists under the x/auth module's genesis state under the `app_state.auth.accounts` key. +Migration can be performed via x/auth/legacy/v0_38/migrate.go. In addition, because genesis +accounts are now generalized via an interface, it is now up to the application to +define the concrete types and the respective client logic to add them to a genesis +state/file. For an example implementation of the `add-genesis-account` command please +refer to https://github.com/cosmos/gaia/pull/122. */ package genaccounts diff --git a/x/genaccounts/genesis.go b/x/genaccounts/genesis.go deleted file mode 100644 index 495d012ac4..0000000000 --- a/x/genaccounts/genesis.go +++ /dev/null @@ -1,39 +0,0 @@ -package genaccounts - -import ( - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" - "github.com/cosmos/cosmos-sdk/x/genaccounts/internal/types" -) - -// InitGenesis initializes accounts and deliver genesis transactions -func InitGenesis(ctx sdk.Context, _ *codec.Codec, accountKeeper types.AccountKeeper, genesisState GenesisState) { - genesisState.Sanitize() - - // load the accounts - for _, gacc := range genesisState { - acc := gacc.ToAccount() - acc = accountKeeper.NewAccount(ctx, acc) // set account number - accountKeeper.SetAccount(ctx, acc) - } -} - -// ExportGenesis exports genesis for all accounts -func ExportGenesis(ctx sdk.Context, accountKeeper types.AccountKeeper) GenesisState { - - // iterate to get the accounts - accounts := []GenesisAccount{} - accountKeeper.IterateAccounts(ctx, - func(acc authexported.Account) (stop bool) { - account, err := NewGenesisAccountI(acc) - if err != nil { - panic(err) - } - accounts = append(accounts, account) - return false - }, - ) - - return accounts -} diff --git a/x/genaccounts/internal/types/codec.go b/x/genaccounts/internal/types/codec.go deleted file mode 100644 index 35069bba5f..0000000000 --- a/x/genaccounts/internal/types/codec.go +++ /dev/null @@ -1,17 +0,0 @@ -package types - -import ( - "github.com/cosmos/cosmos-sdk/codec" -) - -// ModuleName is "accounts" -const ModuleName = "accounts" - -// ModuleCdc - generic sealed codec to be used throughout this module -var ModuleCdc *codec.Codec - -func init() { - ModuleCdc = codec.New() - codec.RegisterCrypto(ModuleCdc) - ModuleCdc.Seal() -} diff --git a/x/genaccounts/internal/types/expected_keepers.go b/x/genaccounts/internal/types/expected_keepers.go deleted file mode 100644 index 168082ee95..0000000000 --- a/x/genaccounts/internal/types/expected_keepers.go +++ /dev/null @@ -1,13 +0,0 @@ -package types - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" -) - -// AccountKeeper defines the expected account keeper (noalias) -type AccountKeeper interface { - NewAccount(sdk.Context, authexported.Account) authexported.Account - SetAccount(sdk.Context, authexported.Account) - IterateAccounts(ctx sdk.Context, process func(authexported.Account) (stop bool)) -} diff --git a/x/genaccounts/internal/types/genesis_account.go b/x/genaccounts/internal/types/genesis_account.go deleted file mode 100644 index 1093c7d554..0000000000 --- a/x/genaccounts/internal/types/genesis_account.go +++ /dev/null @@ -1,151 +0,0 @@ -package types - -import ( - "errors" - "fmt" - "strings" - - sdk "github.com/cosmos/cosmos-sdk/types" - authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/supply" - supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported" -) - -// GenesisAccount is a struct for account initialization used exclusively during genesis -type GenesisAccount struct { - Address sdk.AccAddress `json:"address" yaml:"address"` - Coins sdk.Coins `json:"coins" yaml:"coins"` - Sequence uint64 `json:"sequence_number" yaml:"sequence_number"` - AccountNumber uint64 `json:"account_number" yaml:"account_number"` - - // vesting account fields - OriginalVesting sdk.Coins `json:"original_vesting" yaml:"original_vesting"` // total vesting coins upon initialization - DelegatedFree sdk.Coins `json:"delegated_free" yaml:"delegated_free"` // delegated vested coins at time of delegation - DelegatedVesting sdk.Coins `json:"delegated_vesting" yaml:"delegated_vesting"` // delegated vesting coins at time of delegation - StartTime int64 `json:"start_time" yaml:"start_time"` // vesting start time (UNIX Epoch time) - EndTime int64 `json:"end_time" yaml:"end_time"` // vesting end time (UNIX Epoch time) - - // module account fields - ModuleName string `json:"module_name" yaml:"module_name"` // name of the module account - ModulePermissions []string `json:"module_permissions" yaml:"module_permissions"` // permissions of module account -} - -// Validate checks for errors on the vesting and module account parameters -func (ga GenesisAccount) Validate() error { - if !ga.OriginalVesting.IsZero() { - if ga.OriginalVesting.IsAnyGT(ga.Coins) { - return errors.New("vesting amount cannot be greater than total amount") - } - if ga.StartTime >= ga.EndTime { - return errors.New("vesting start-time cannot be before end-time") - } - } - - // don't allow blank (i.e just whitespaces) on the module name - if ga.ModuleName != "" && strings.TrimSpace(ga.ModuleName) == "" { - return errors.New("module account name cannot be blank") - } - - return nil -} - -// NewGenesisAccountRaw creates a new GenesisAccount object -func NewGenesisAccountRaw(address sdk.AccAddress, coins, - vestingAmount sdk.Coins, vestingStartTime, vestingEndTime int64, - module string, permissions ...string) GenesisAccount { - - return GenesisAccount{ - Address: address, - Coins: coins, - Sequence: 0, - AccountNumber: 0, // ignored set by the account keeper during InitGenesis - OriginalVesting: vestingAmount, - DelegatedFree: sdk.Coins{}, // ignored - DelegatedVesting: sdk.Coins{}, // ignored - StartTime: vestingStartTime, - EndTime: vestingEndTime, - ModuleName: module, - ModulePermissions: permissions, - } -} - -// NewGenesisAccount creates a GenesisAccount instance from a BaseAccount. -func NewGenesisAccount(acc *authtypes.BaseAccount) GenesisAccount { - return GenesisAccount{ - Address: acc.Address, - Coins: acc.Coins, - AccountNumber: acc.AccountNumber, - Sequence: acc.Sequence, - } -} - -// NewGenesisAccountI creates a GenesisAccount instance from an Account interface. -func NewGenesisAccountI(acc authexported.Account) (GenesisAccount, error) { - gacc := GenesisAccount{ - Address: acc.GetAddress(), - Coins: acc.GetCoins(), - AccountNumber: acc.GetAccountNumber(), - Sequence: acc.GetSequence(), - } - - if err := gacc.Validate(); err != nil { - return gacc, err - } - - switch acc := acc.(type) { - case authexported.VestingAccount: - gacc.OriginalVesting = acc.GetOriginalVesting() - gacc.DelegatedFree = acc.GetDelegatedFree() - gacc.DelegatedVesting = acc.GetDelegatedVesting() - gacc.StartTime = acc.GetStartTime() - gacc.EndTime = acc.GetEndTime() - case supplyexported.ModuleAccountI: - gacc.ModuleName = acc.GetName() - gacc.ModulePermissions = acc.GetPermissions() - } - - return gacc, nil -} - -// ToAccount converts a GenesisAccount to an Account interface -func (ga *GenesisAccount) ToAccount() authexported.Account { - bacc := authtypes.NewBaseAccount(ga.Address, ga.Coins.Sort(), nil, ga.AccountNumber, ga.Sequence) - - // vesting accounts - if !ga.OriginalVesting.IsZero() { - baseVestingAcc := authtypes.NewBaseVestingAccount( - bacc, ga.OriginalVesting, ga.DelegatedFree, - ga.DelegatedVesting, ga.EndTime, - ) - - switch { - case ga.StartTime != 0 && ga.EndTime != 0: - return authtypes.NewContinuousVestingAccountRaw(baseVestingAcc, ga.StartTime) - case ga.EndTime != 0: - return authtypes.NewDelayedVestingAccountRaw(baseVestingAcc) - default: - panic(fmt.Sprintf("invalid genesis vesting account: %+v", ga)) - } - } - - // module accounts - if ga.ModuleName != "" { - return supply.NewModuleAccount(bacc, ga.ModuleName, ga.ModulePermissions...) - } - - return bacc -} - -// GenesisAccounts defines a set of genesis account -type GenesisAccounts []GenesisAccount - -// Contains checks if a set of genesis accounts contain an address -func (gaccs GenesisAccounts) Contains(acc sdk.AccAddress) bool { - for _, gacc := range gaccs { - if gacc.Address.Equals(acc) { - return true - } - } - return false -} diff --git a/x/genaccounts/internal/types/genesis_account_test.go b/x/genaccounts/internal/types/genesis_account_test.go deleted file mode 100644 index 56dd140ec3..0000000000 --- a/x/genaccounts/internal/types/genesis_account_test.go +++ /dev/null @@ -1,97 +0,0 @@ -package types - -import ( - "errors" - "testing" - "time" - - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/ed25519" - "github.com/tendermint/tendermint/crypto/secp256k1" - - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/supply" -) - -func TestGenesisAccountValidate(t *testing.T) { - addr := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) - tests := []struct { - name string - acc GenesisAccount - expErr error - }{ - { - "valid account", - NewGenesisAccountRaw(addr, sdk.NewCoins(), sdk.NewCoins(), 0, 0, "", ""), - nil, - }, - { - "valid module account", - NewGenesisAccountRaw(addr, sdk.NewCoins(), sdk.NewCoins(), 0, 0, "mint", supply.Minter), - nil, - }, - { - "invalid vesting amount", - NewGenesisAccountRaw(addr, sdk.NewCoins(sdk.NewInt64Coin("stake", 50)), - sdk.NewCoins(sdk.NewInt64Coin("stake", 100)), 0, 0, "", ""), - errors.New("vesting amount cannot be greater than total amount"), - }, - { - "invalid vesting amount with multi coins", - NewGenesisAccountRaw(addr, - sdk.NewCoins(sdk.NewInt64Coin("uatom", 50), sdk.NewInt64Coin("eth", 50)), - sdk.NewCoins(sdk.NewInt64Coin("uatom", 100), sdk.NewInt64Coin("eth", 20)), - 0, 0, "", ""), - errors.New("vesting amount cannot be greater than total amount"), - }, - { - "invalid vesting times", - NewGenesisAccountRaw(addr, sdk.NewCoins(sdk.NewInt64Coin("stake", 50)), - sdk.NewCoins(sdk.NewInt64Coin("stake", 50)), 1654668078, 1554668078, "", ""), - errors.New("vesting start-time cannot be before end-time"), - }, - { - "invalid module account name", - NewGenesisAccountRaw(addr, sdk.NewCoins(), sdk.NewCoins(), 0, 0, " ", ""), - errors.New("module account name cannot be blank"), - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - err := tt.acc.Validate() - require.Equal(t, tt.expErr, err) - }) - } -} - -func TestToAccount(t *testing.T) { - priv := ed25519.GenPrivKey() - addr := sdk.AccAddress(priv.PubKey().Address()) - - // base account - authAcc := authtypes.NewBaseAccountWithAddress(addr) - authAcc.SetCoins(sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 150))) - genAcc := NewGenesisAccount(&authAcc) - acc := genAcc.ToAccount() - require.IsType(t, &authtypes.BaseAccount{}, acc) - require.Equal(t, &authAcc, acc.(*authtypes.BaseAccount)) - - // vesting account - vacc := authtypes.NewContinuousVestingAccount( - &authAcc, time.Now().Unix(), time.Now().Add(24*time.Hour).Unix(), - ) - genAcc, err := NewGenesisAccountI(vacc) - require.NoError(t, err) - acc = genAcc.ToAccount() - require.IsType(t, &authtypes.ContinuousVestingAccount{}, acc) - require.Equal(t, vacc, acc.(*authtypes.ContinuousVestingAccount)) - - // module account - macc := supply.NewEmptyModuleAccount("mint", supply.Minter) - genAcc, err = NewGenesisAccountI(macc) - require.NoError(t, err) - acc = genAcc.ToAccount() - require.IsType(t, &supply.ModuleAccount{}, acc) - require.Equal(t, macc, acc.(*supply.ModuleAccount)) -} diff --git a/x/genaccounts/internal/types/genesis_state.go b/x/genaccounts/internal/types/genesis_state.go deleted file mode 100644 index c639be138c..0000000000 --- a/x/genaccounts/internal/types/genesis_state.go +++ /dev/null @@ -1,77 +0,0 @@ -package types - -import ( - "encoding/json" - "fmt" - "sort" - "time" - - "github.com/cosmos/cosmos-sdk/codec" -) - -// GenesisState is a wrapper for GenAccounts -type GenesisState GenesisAccounts - -// GetGenesisStateFromAppState gets the genesis state from the expected app state -func GetGenesisStateFromAppState(cdc *codec.Codec, appState map[string]json.RawMessage) GenesisState { - var genesisState GenesisState - if appState[ModuleName] != nil { - cdc.MustUnmarshalJSON(appState[ModuleName], &genesisState) - } - - return genesisState -} - -// SetGenesisStateInAppState sets the genesis state within the expected app state -func SetGenesisStateInAppState(cdc *codec.Codec, - appState map[string]json.RawMessage, genesisState GenesisState) map[string]json.RawMessage { - - genesisStateBz := cdc.MustMarshalJSON(genesisState) - appState[ModuleName] = genesisStateBz - return appState -} - -// Sanitize sorts accounts and coin sets. -func (gs GenesisState) Sanitize() { - sort.Slice(gs, func(i, j int) bool { - return gs[i].AccountNumber < gs[j].AccountNumber - }) - - for _, acc := range gs { - acc.Coins = acc.Coins.Sort() - } -} - -// ValidateGenesis performs validation of genesis accounts. It -// ensures that there are no duplicate accounts in the genesis state and any -// provided vesting accounts are valid. -func ValidateGenesis(genesisState GenesisState) error { - addrMap := make(map[string]bool, len(genesisState)) - for _, acc := range genesisState { - addrStr := acc.Address.String() - - // disallow any duplicate accounts - if _, ok := addrMap[addrStr]; ok { - return fmt.Errorf("duplicate account found in genesis state; address: %s", addrStr) - } - - // validate any vesting fields - if !acc.OriginalVesting.IsZero() { - if acc.EndTime == 0 { - return fmt.Errorf("missing end time for vesting account; address: %s", addrStr) - } - - if acc.StartTime >= acc.EndTime { - return fmt.Errorf( - "vesting start time must before end time; address: %s, start: %s, end: %s", - addrStr, - time.Unix(acc.StartTime, 0).UTC().Format(time.RFC3339), - time.Unix(acc.EndTime, 0).UTC().Format(time.RFC3339), - ) - } - } - - addrMap[addrStr] = true - } - return nil -} diff --git a/x/genaccounts/internal/types/genesis_state_test.go b/x/genaccounts/internal/types/genesis_state_test.go deleted file mode 100644 index 90ed5ea8cd..0000000000 --- a/x/genaccounts/internal/types/genesis_state_test.go +++ /dev/null @@ -1,86 +0,0 @@ -package types - -import ( - "testing" - - "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto/ed25519" - - sdk "github.com/cosmos/cosmos-sdk/types" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" -) - -func TestSanitize(t *testing.T) { - - addr1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) - authAcc1 := authtypes.NewBaseAccountWithAddress(addr1) - authAcc1.SetCoins(sdk.Coins{ - sdk.NewInt64Coin("bcoin", 150), - sdk.NewInt64Coin("acoin", 150), - }) - authAcc1.SetAccountNumber(1) - genAcc1 := NewGenesisAccount(&authAcc1) - - addr2 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) - authAcc2 := authtypes.NewBaseAccountWithAddress(addr2) - authAcc2.SetCoins(sdk.Coins{ - sdk.NewInt64Coin("acoin", 150), - sdk.NewInt64Coin("bcoin", 150), - }) - genAcc2 := NewGenesisAccount(&authAcc2) - - genesisState := GenesisState([]GenesisAccount{genAcc1, genAcc2}) - require.NoError(t, ValidateGenesis(genesisState)) - require.True(t, genesisState[0].AccountNumber > genesisState[1].AccountNumber) - require.Equal(t, genesisState[0].Coins[0].Denom, "bcoin") - require.Equal(t, genesisState[0].Coins[1].Denom, "acoin") - require.Equal(t, genesisState[1].Address, addr2) - genesisState.Sanitize() - require.False(t, genesisState[0].AccountNumber > genesisState[1].AccountNumber) - require.Equal(t, genesisState[1].Address, addr1) - require.Equal(t, genesisState[1].Coins[0].Denom, "acoin") - require.Equal(t, genesisState[1].Coins[1].Denom, "bcoin") -} - -var ( - pk1 = ed25519.GenPrivKey().PubKey() - pk2 = ed25519.GenPrivKey().PubKey() - addr1 = sdk.ValAddress(pk1.Address()) - addr2 = sdk.ValAddress(pk2.Address()) -) - -// require duplicate accounts fails validation -func TestValidateGenesisDuplicateAccounts(t *testing.T) { - acc1 := authtypes.NewBaseAccountWithAddress(sdk.AccAddress(addr1)) - acc1.Coins = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 150)) - - genAccs := make([]GenesisAccount, 2) - genAccs[0] = NewGenesisAccount(&acc1) - genAccs[1] = NewGenesisAccount(&acc1) - - genesisState := GenesisState(genAccs) - err := ValidateGenesis(genesisState) - require.Error(t, err) -} - -// require invalid vesting account fails validation (invalid end time) -func TestValidateGenesisInvalidAccounts(t *testing.T) { - acc1 := authtypes.NewBaseAccountWithAddress(sdk.AccAddress(addr1)) - acc1.Coins = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 150)) - acc2 := authtypes.NewBaseAccountWithAddress(sdk.AccAddress(addr2)) - acc2.Coins = sdk.NewCoins(sdk.NewInt64Coin(sdk.DefaultBondDenom, 150)) - - genAccs := make([]GenesisAccount, 2) - genAccs[0] = NewGenesisAccount(&acc1) - genAccs[1] = NewGenesisAccount(&acc2) - - genesisState := GenesisState(genAccs) - genesisState[0].OriginalVesting = genesisState[0].Coins - err := ValidateGenesis(genesisState) - require.Error(t, err) - - genesisState[0].StartTime = 1548888000 - genesisState[0].EndTime = 1548775410 - err = ValidateGenesis(genesisState) - require.Error(t, err) -} diff --git a/x/genaccounts/module.go b/x/genaccounts/module.go deleted file mode 100644 index 2b5d4871cb..0000000000 --- a/x/genaccounts/module.go +++ /dev/null @@ -1,155 +0,0 @@ -package genaccounts - -import ( - "encoding/json" - "math/rand" - - "github.com/gorilla/mux" - "github.com/spf13/cobra" - - abci "github.com/tendermint/tendermint/abci/types" - - "github.com/cosmos/cosmos-sdk/client/context" - "github.com/cosmos/cosmos-sdk/codec" - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - "github.com/cosmos/cosmos-sdk/x/auth/exported" - "github.com/cosmos/cosmos-sdk/x/genaccounts/internal/types" - "github.com/cosmos/cosmos-sdk/x/genaccounts/simulation" - sim "github.com/cosmos/cosmos-sdk/x/simulation" -) - -var ( - _ module.AppModuleGenesis = AppModule{} - _ module.AppModuleBasic = AppModuleBasic{} - _ module.AppModuleSimulation = AppModuleSimulation{} -) - -// AppModuleBasic defines the basic application module used by the genesis accounts module. -type AppModuleBasic struct{} - -// Name returns the genesis accounts module's name. -func (AppModuleBasic) Name() string { - return ModuleName -} - -// RegisterCodec performs a no-op. -func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) {} - -// DefaultGenesis returns default genesis state as raw bytes for the genesis accounts -// module. -func (AppModuleBasic) DefaultGenesis() json.RawMessage { - return ModuleCdc.MustMarshalJSON(GenesisState{}) -} - -// ValidateGenesis performs genesis state validation for the genesis accounts module. -func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { - var data GenesisState - err := ModuleCdc.UnmarshalJSON(bz, &data) - if err != nil { - return err - } - return ValidateGenesis(data) -} - -// RegisterRESTRoutes registers no REST routes for the genesis accounts module. -func (AppModuleBasic) RegisterRESTRoutes(_ context.CLIContext, _ *mux.Router) {} - -// GetTxCmd returns no root tx command for the genesis accounts module. -func (AppModuleBasic) GetTxCmd(_ *codec.Codec) *cobra.Command { return nil } - -// GetQueryCmd returns no root query command for the genesis accounts module. -func (AppModuleBasic) GetQueryCmd(_ *codec.Codec) *cobra.Command { return nil } - -// extra function from sdk.AppModuleBasic - -// IterateGenesisAccounts iterates over the genesis accounts and perform an operation at each of them -// - to used by other modules -func (AppModuleBasic) IterateGenesisAccounts(cdc *codec.Codec, appGenesis map[string]json.RawMessage, - iterateFn func(exported.Account) (stop bool)) { - - genesisState := GetGenesisStateFromAppState(cdc, appGenesis) - for _, genAcc := range genesisState { - acc := genAcc.ToAccount() - if iterateFn(acc) { - break - } - } -} - -//____________________________________________________________________________ - -// AppModuleSimulation defines the module simulation functions used by the genesis accounts module. -type AppModuleSimulation struct{} - -// RegisterStoreDecoder performs a no-op. -func (AppModuleSimulation) RegisterStoreDecoder(_ sdk.StoreDecoderRegistry) {} - -// GenerateGenesisState creates a randomized GenState of the genesis accounts module. -func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) { - simulation.RandomizedGenState(simState) -} - -// RandomizedParams doesn't create randomized genaccounts param changes for the simulator. -func (AppModuleSimulation) RandomizedParams(_ *rand.Rand) []sim.ParamChange { - return nil -} - -//____________________________________________________________________________ - -// AppModule implements an application module for the genesis accounts module. -type AppModule struct { - AppModuleBasic - AppModuleSimulation - - accountKeeper types.AccountKeeper -} - -// NewAppModule creates a new AppModule object -func NewAppModule(accountKeeper types.AccountKeeper) AppModule { - - return AppModule{ - AppModuleBasic: AppModuleBasic{}, - AppModuleSimulation: AppModuleSimulation{}, - accountKeeper: accountKeeper, - } -} - -// RegisterInvariants is a placeholder function register no invariants -func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} - -// Route empty module message route -func (AppModule) Route() string { return "" } - -// NewHandler returns an empty module handler -func (AppModule) NewHandler() sdk.Handler { return nil } - -// QuerierRoute returns an empty module querier route -func (AppModule) QuerierRoute() string { return "" } - -// NewQuerierHandler returns an empty module querier -func (AppModule) NewQuerierHandler() sdk.Querier { return nil } - -// InitGenesis performs genesis initialization for the genesis accounts module. It returns -// no validator updates. -func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { - var genesisState GenesisState - ModuleCdc.MustUnmarshalJSON(data, &genesisState) - InitGenesis(ctx, ModuleCdc, am.accountKeeper, genesisState) - return []abci.ValidatorUpdate{} -} - -// ExportGenesis returns the exported genesis state as raw bytes for the genesis accounts -// module. -func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { - gs := ExportGenesis(ctx, am.accountKeeper) - return ModuleCdc.MustMarshalJSON(gs) -} - -// BeginBlock returns an empty module begin-block -func (AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) {} - -// EndBlock returns an empty module end-block -func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { - return []abci.ValidatorUpdate{} -} diff --git a/x/genaccounts/simulation/genesis.go b/x/genaccounts/simulation/genesis.go deleted file mode 100644 index ec7b0a4058..0000000000 --- a/x/genaccounts/simulation/genesis.go +++ /dev/null @@ -1,68 +0,0 @@ -package simulation - -// DONTCOVER - -import ( - sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/types/module" - authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" - authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" - "github.com/cosmos/cosmos-sdk/x/genaccounts/internal/types" - "github.com/cosmos/cosmos-sdk/x/simulation" -) - -// RandomizedGenState generates a random GenesisState for the genesis accounts -func RandomizedGenState(simState *module.SimulationState) { - var genesisAccounts []types.GenesisAccount - - // randomly generate some genesis accounts - for i, acc := range simState.Accounts { - coins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(simState.InitialStake))} - bacc := authtypes.NewBaseAccountWithAddress(acc.Address) - if err := bacc.SetCoins(coins); err != nil { - panic(err) - } - - var gacc types.GenesisAccount - - // Only consider making a vesting account once the initial bonded validator - // set is exhausted due to needing to track DelegatedVesting. - if int64(i) > simState.NumBonded && simState.Rand.Intn(100) < 50 { - var ( - vacc authexported.VestingAccount - endTime int64 - ) - - startTime := simState.GenTimestamp.Unix() - - // Allow for some vesting accounts to vest very quickly while others very slowly. - if simState.Rand.Intn(100) < 50 { - endTime = int64(simulation.RandIntBetween(simState.Rand, int(startTime), int(startTime+(60*60*24*30)))) - } else { - endTime = int64(simulation.RandIntBetween(simState.Rand, int(startTime), int(startTime+(60*60*12)))) - } - - if startTime == endTime { - endTime++ - } - - if simState.Rand.Intn(100) < 50 { - vacc = authtypes.NewContinuousVestingAccount(&bacc, startTime, endTime) - } else { - vacc = authtypes.NewDelayedVestingAccount(&bacc, endTime) - } - - var err error - gacc, err = types.NewGenesisAccountI(vacc) - if err != nil { - panic(err) - } - } else { - gacc = types.NewGenesisAccount(&bacc) - } - - genesisAccounts = append(genesisAccounts, gacc) - } - - simState.GenState[types.ModuleName] = simState.Cdc.MustMarshalJSON(genesisAccounts) -} diff --git a/x/genutil/client/cli/migrate.go b/x/genutil/client/cli/migrate.go index e1d8309230..3702b8a87e 100644 --- a/x/genutil/client/cli/migrate.go +++ b/x/genutil/client/cli/migrate.go @@ -15,10 +15,15 @@ import ( "github.com/cosmos/cosmos-sdk/version" extypes "github.com/cosmos/cosmos-sdk/x/genutil" v036 "github.com/cosmos/cosmos-sdk/x/genutil/legacy/v0_36" + v038 "github.com/cosmos/cosmos-sdk/x/genutil/legacy/v0_38" ) +// Allow applications to extend and modify the migration process. +// +// Ref: https://github.com/cosmos/cosmos-sdk/issues/5041 var migrationMap = extypes.MigrationMap{ "v0.36": v036.Migrate, + "v0.38": v038.Migrate, } const ( diff --git a/x/genutil/legacy/v0_36/migrate.go b/x/genutil/legacy/v0_36/migrate.go index 5ba4af490e..2ec6d01a2a 100644 --- a/x/genutil/legacy/v0_36/migrate.go +++ b/x/genutil/legacy/v0_36/migrate.go @@ -20,10 +20,10 @@ import ( func Migrate(appState genutil.AppMap) genutil.AppMap { v034Codec := codec.New() codec.RegisterCrypto(v034Codec) + v034gov.RegisterCodec(v034Codec) v036Codec := codec.New() codec.RegisterCrypto(v036Codec) - v034gov.RegisterCodec(v034Codec) v036gov.RegisterCodec(v036Codec) // migrate genesis accounts state diff --git a/x/genutil/legacy/v0_38/migrate.go b/x/genutil/legacy/v0_38/migrate.go new file mode 100644 index 0000000000..fef2091b43 --- /dev/null +++ b/x/genutil/legacy/v0_38/migrate.go @@ -0,0 +1,32 @@ +package v038 + +import ( + "github.com/cosmos/cosmos-sdk/codec" + v036auth "github.com/cosmos/cosmos-sdk/x/auth/legacy/v0_36" + v038auth "github.com/cosmos/cosmos-sdk/x/auth/legacy/v0_38" + v036genaccounts "github.com/cosmos/cosmos-sdk/x/genaccounts/legacy/v0_36" + "github.com/cosmos/cosmos-sdk/x/genutil" +) + +// Migrate migrates exported state from v0.34 to a v0.36 genesis state. +func Migrate(appState genutil.AppMap) genutil.AppMap { + v036Codec := codec.New() + codec.RegisterCrypto(v036Codec) + + v038Codec := codec.New() + codec.RegisterCrypto(v038Codec) + + if appState[v036genaccounts.ModuleName] != nil { + var authGenState v036auth.GenesisState + v036Codec.MustUnmarshalJSON(appState[v036auth.ModuleName], &authGenState) + + appState[v038auth.ModuleName] = v038Codec.MustMarshalJSON( + v038auth.Migrate(authGenState, appState[v036genaccounts.ModuleName]), + ) + + // delete deprecated genaccounts genesis state + delete(appState, v036genaccounts.ModuleName) + } + + return appState +} diff --git a/x/supply/internal/types/account.go b/x/supply/internal/types/account.go index b07bb31c91..7ab4546913 100644 --- a/x/supply/internal/types/account.go +++ b/x/supply/internal/types/account.go @@ -1,18 +1,31 @@ package types import ( + "errors" "fmt" + "strings" yaml "gopkg.in/yaml.v2" "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/cosmos/cosmos-sdk/x/supply/exported" ) -var _ exported.ModuleAccountI = (*ModuleAccount)(nil) +var ( + _ authexported.GenesisAccount = (*ModuleAccount)(nil) + _ exported.ModuleAccountI = (*ModuleAccount)(nil) +) + +func init() { + // Register the ModuleAccount type as a GenesisAccount so that when no + // concrete GenesisAccount types exist and **default** genesis state is used, + // the genesis state will serialize correctly. + authtypes.RegisterAccountTypeCodec(&ModuleAccount{}, "cosmos-sdk/ModuleAccount") +} // ModuleAccount defines an account for modules that holds coins on a pool type ModuleAccount struct { @@ -26,6 +39,7 @@ func NewModuleAddress(name string) sdk.AccAddress { return sdk.AccAddress(crypto.AddressHash([]byte(name))) } +// NewEmptyModuleAccount creates a empty ModuleAccount from a string func NewEmptyModuleAccount(name string, permissions ...string) *ModuleAccount { moduleAddress := NewModuleAddress(name) baseAcc := authtypes.NewBaseAccountWithAddress(moduleAddress) @@ -95,6 +109,18 @@ func (ma ModuleAccount) String() string { return string(b) } +// Validate checks for errors on the account fields +func (ma ModuleAccount) Validate() error { + if strings.TrimSpace(ma.Name) == "" { + return errors.New("module account name cannot be blank") + } + if !ma.Address.Equals(sdk.AccAddress(crypto.AddressHash([]byte(ma.Name)))) { + return fmt.Errorf("address %s cannot be derived from the module name '%s'", ma.Address, ma.Name) + } + + return ma.BaseAccount.Validate() +} + // MarshalYAML returns the YAML representation of a ModuleAccount. func (ma ModuleAccount) MarshalYAML() (interface{}, error) { bs, err := yaml.Marshal(struct { diff --git a/x/supply/internal/types/account_test.go b/x/supply/internal/types/account_test.go index e9d3a0760b..b8acb43304 100644 --- a/x/supply/internal/types/account_test.go +++ b/x/supply/internal/types/account_test.go @@ -1,13 +1,18 @@ package types import ( + "errors" "fmt" "testing" - "github.com/tendermint/tendermint/crypto" yaml "gopkg.in/yaml.v2" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/secp256k1" + sdk "github.com/cosmos/cosmos-sdk/types" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" "github.com/stretchr/testify/require" ) @@ -58,3 +63,35 @@ func TestHasPermissions(t *testing.T) { } } } + +func TestValidate(t *testing.T) { + addr := sdk.AccAddress(secp256k1.GenPrivKey().PubKey().Address()) + baseAcc := authtypes.NewBaseAccount(addr, sdk.Coins{}, nil, 0, 0) + tests := []struct { + name string + acc authexported.GenesisAccount + expErr error + }{ + { + "valid module account", + NewEmptyModuleAccount("test"), + nil, + }, + { + "invalid name and address pair", + NewModuleAccount(baseAcc, "test"), + fmt.Errorf("address %s cannot be derived from the module name 'test'", addr), + }, + { + "empty module account name", + NewModuleAccount(baseAcc, " "), + errors.New("module account name cannot be blank"), + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := tt.acc.Validate() + require.Equal(t, tt.expErr, err) + }) + } +}