From f6aec4634359387a7e3e953ceabbe0da761a185b Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 30 Sep 2019 22:53:14 -0400 Subject: [PATCH] add simulation to validator vesting --- x/validator-vesting/genesis.go | 7 +- x/validator-vesting/internal/types/genesis.go | 5 + x/validator-vesting/module.go | 146 ++++++++++++++++++ x/validator-vesting/simulation/decoder.go | 31 ++++ x/validator-vesting/simulation/genesis.go | 121 +++++++++++++++ x/validator-vesting/test_common.go | 4 +- 6 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 x/validator-vesting/module.go create mode 100644 x/validator-vesting/simulation/decoder.go create mode 100644 x/validator-vesting/simulation/genesis.go diff --git a/x/validator-vesting/genesis.go b/x/validator-vesting/genesis.go index e5f2bf9b..f8b674b7 100644 --- a/x/validator-vesting/genesis.go +++ b/x/validator-vesting/genesis.go @@ -8,7 +8,7 @@ import ( // InitGenesis stores the account address of each ValidatorVestingAccount in the validator vesting keeper, for faster lookup. // CONTRACT: Accounts created by the account keeper must have already been initialized/created by AccountKeeper -func InitGenesis(ctx sdk.Context, keeper Keeper, accountKeeper types.AccountKeeper, data GenesisState) { +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { data.Accounts = auth.SanitizeGenesisAccounts(data.Accounts) for _, a := range data.Accounts { vv, ok := a.(ValidatorVestingAccount) @@ -17,3 +17,8 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, accountKeeper types.AccountKeep } } } + +// ExportGenesis returns empty genesis state because auth exports all the genesis state we need. +func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { + return types.DefaultGenesisState() +} diff --git a/x/validator-vesting/internal/types/genesis.go b/x/validator-vesting/internal/types/genesis.go index 29013bd1..5133271e 100644 --- a/x/validator-vesting/internal/types/genesis.go +++ b/x/validator-vesting/internal/types/genesis.go @@ -34,3 +34,8 @@ func (data GenesisState) Equal(data2 GenesisState) bool { func (data GenesisState) IsEmpty() bool { return data.Equal(GenesisState{}) } + +// ValidateGenesis returns nil because accounts are validated by auth +func ValidateGenesis(data GenesisState) error { + return nil +} diff --git a/x/validator-vesting/module.go b/x/validator-vesting/module.go new file mode 100644 index 00000000..2855b02f --- /dev/null +++ b/x/validator-vesting/module.go @@ -0,0 +1,146 @@ +package validatorvesting + +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" + sim "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" + "github.com/cosmos/cosmos-sdk/x/validator-vesting/simulation" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModuleSimulation{} +) + +// AppModuleBasic defines the basic application module used by the auth module. +type AppModuleBasic struct{} + +// Name returns the auth module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterCodec registers the auth module's types for the given codec. +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + types.RegisterCodec(cdc) +} + +// DefaultGenesis returns default genesis state as raw bytes for the validator-vesting +// module. +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return types.ModuleCdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the validator-vesting module. +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + var data types.GenesisState + if err := types.ModuleCdc.UnmarshalJSON(bz, &data); err != nil { + return err + } + return types.ValidateGenesis(data) +} + +// RegisterRESTRoutes registers no REST routes for the crisis module. +func (AppModuleBasic) RegisterRESTRoutes(_ context.CLIContext, _ *mux.Router) {} + +// GetTxCmd returns no root tx command for the validator-vesting module. +func (AppModuleBasic) GetTxCmd(_ *codec.Codec) *cobra.Command { return nil } + +// GetQueryCmd returns no root query command for the validator-vesting module. +func (AppModuleBasic) GetQueryCmd(_ *codec.Codec) *cobra.Command { return nil } + +// AppModuleSimulation defines the module simulation functions used by the auth module. +type AppModuleSimulation struct{} + +// RegisterStoreDecoder registers a decoder for auth module's types +func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// GenerateGenesisState creates a randomized GenState of the auth module +func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// RandomizedParams returns nil because validatorvesting has no params. +func (AppModuleSimulation) RandomizedParams(_ *rand.Rand) []sim.ParamChange { + return []sim.ParamChange{} +} + +// AppModule implements an application module for the validator-vesting module. +type AppModule struct { + AppModuleBasic + AppModuleSimulation + keeper Keeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(keeper Keeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + AppModuleSimulation: AppModuleSimulation{}, + keeper: keeper, + } +} + +// Name returns the auth module's name. +func (AppModule) Name() string { + return types.ModuleName +} + +// RegisterInvariants performs a no-op. +func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// Route returns the message routing key for the auth module. +func (AppModule) Route() string { return "" } + +// NewHandler returns an sdk.Handler for the auth module. +func (AppModule) NewHandler() sdk.Handler { return nil } + +// QuerierRoute returns the auth module's querier route name. +func (AppModule) QuerierRoute() string { + return "" +} + +// NewQuerierHandler returns the auth module sdk.Querier. +func (am AppModule) NewQuerierHandler() sdk.Querier { + return nil +} + +// InitGenesis performs genesis initialization for the auth module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState GenesisState + types.ModuleCdc.MustUnmarshalJSON(data, &genesisState) + InitGenesis(ctx, am.keeper, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the auth +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return types.ModuleCdc.MustMarshalJSON(gs) +} + +// BeginBlock returns the begin blocker for the auth module. +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + BeginBlocker(ctx, req, am.keeper) +} + +// EndBlock returns the end blocker for the auth module. It returns no validator +// updates. +func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/validator-vesting/simulation/decoder.go b/x/validator-vesting/simulation/decoder.go new file mode 100644 index 00000000..3171b200 --- /dev/null +++ b/x/validator-vesting/simulation/decoder.go @@ -0,0 +1,31 @@ +package simulation + +import ( + "bytes" + "fmt" + "time" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" +) + +// DecodeStore unmarshals the KVPair's Value to the corresponding auth type +func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.ValidatorVestingAccountPrefix): + var accA, accB exported.Account + cdc.MustUnmarshalBinaryBare(kvA.Value, &accA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &accB) + return fmt.Sprintf("%v\n%v", accA, accB) + case bytes.Equal(kvA.Key, types.BlocktimeKey): + var btA, btB time.Time + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &btA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &btB) + return fmt.Sprintf("%v\n%v", btA, btB) + default: + panic(fmt.Sprintf("invalid account key %X", kvA.Key)) + } +} diff --git a/x/validator-vesting/simulation/genesis.go b/x/validator-vesting/simulation/genesis.go new file mode 100644 index 00000000..99f6c9e0 --- /dev/null +++ b/x/validator-vesting/simulation/genesis.go @@ -0,0 +1,121 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/types/module" + + 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" + vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" +) + +// RandomizedGenState generates a random GenesisState for validator-vesting +func RandomizedGenState(simState *module.SimulationState) { + var authGenState authtypes.GenesisState + authSimState := simState.GenState[authtypes.ModuleName] + simState.Cdc.MustUnmarshalJSON(authSimState, &authGenState) + var newGenesisAccs authexported.GenesisAccounts + for _, acc := range authGenState.Accounts { + va, ok := acc.(vestexported.VestingAccount) + if ok { + // 50% of the time convert the vesting account + + if simState.Rand.Intn(100) < 50 { + bacc := authtypes.NewBaseAccountWithAddress(va.GetAddress()) + err := bacc.SetCoins(va.GetCoins()) + if err != nil { + panic(err) + } + + duration := va.GetEndTime() - va.GetStartTime() + vestingPeriods := getRandomVestingPeriods(duration, simState.Rand, va.GetCoins()) + vestingCoins := getVestingCoins(vestingPeriods) + bva := vestingtypes.NewBaseVestingAccount(&bacc, vestingCoins, va.GetEndTime()) + var gacc authexported.GenesisAccount + if simState.Rand.Intn(100) < 50 { + // convert to periodic vesting account 50% + gacc = vestingtypes.NewPeriodicVestingAccountRaw(bva, va.GetStartTime(), vestingPeriods) + err = gacc.Validate() + if err != nil { + panic(err) + } + } else { + consAdd := getRandomValidatorConsAddr(simState, simulation.RandIntBetween(simState.Rand, 0, int(simState.NumBonded)-1)) + // convert to validator vesting account 50% + // set signing threshold to be anywhere between 1 and 100 + gacc = types.NewValidatorVestingAccountRaw( + bva, va.GetStartTime(), vestingPeriods, consAdd, nil, + int64(simulation.RandIntBetween(simState.Rand, 1, 100)), + ) + err = gacc.Validate() + if err != nil { + panic(err) + } + } + newGenesisAccs = append(newGenesisAccs, gacc) + } else { + newGenesisAccs = append(newGenesisAccs, acc) + } + } else { + newGenesisAccs = append(newGenesisAccs, acc) + } + } + newAuthGenesis := authtypes.NewGenesisState(authGenState.Params, newGenesisAccs) + simState.GenState[authtypes.ModuleName] = simState.Cdc.MustMarshalJSON(newAuthGenesis) +} + +func getRandomValidatorConsAddr(simState *module.SimulationState, rint int) sdk.ConsAddress { + acc := simState.Accounts[rint] + return sdk.ConsAddress(acc.PubKey.Address()) +} + +func getRandomVestingPeriods(duration int64, r *rand.Rand, origCoins sdk.Coins) vestingtypes.Periods { + maxPeriods := int64(50) + if duration < maxPeriods { + maxPeriods = duration + } + numPeriods := simulation.RandIntBetween(r, 1, int(maxPeriods)) + lenPeriod := duration / int64(numPeriods) + periodLengths := make([]int64, numPeriods) + totalLength := int64(0) + for i := 0; i < numPeriods; i++ { + periodLengths[i] = lenPeriod + totalLength += lenPeriod + } + if duration-totalLength != 0 { + periodLengths[len(periodLengths)-1] += (duration - totalLength) + } + + coinFraction := simulation.RandIntBetween(r, 1, 100) + vestingCoins := sdk.NewCoins() + for _, ic := range origCoins { + amountVesting := ic.Amount.Int64() / int64(coinFraction) + vestingCoins = vestingCoins.Add(sdk.NewCoins(sdk.NewInt64Coin(ic.Denom, amountVesting))) + } + periodCoins := sdk.NewCoins() + for _, c := range vestingCoins { + amountPeriod := c.Amount.Int64() / int64(numPeriods) + periodCoins = periodCoins.Add(sdk.NewCoins(sdk.NewInt64Coin(c.Denom, amountPeriod))) + } + + vestingPeriods := make([]vestingtypes.Period, numPeriods) + for i := 0; i < numPeriods; i++ { + vestingPeriods[i] = vestingtypes.Period{Length: int64(periodLengths[i]), Amount: periodCoins} + } + + return vestingPeriods + +} + +func getVestingCoins(periods vestingtypes.Periods) sdk.Coins { + vestingCoins := sdk.NewCoins() + for _, p := range periods { + vestingCoins = vestingCoins.Add(p.Amount) + } + return vestingCoins +} diff --git a/x/validator-vesting/test_common.go b/x/validator-vesting/test_common.go index c6b1d40b..af0c3783 100644 --- a/x/validator-vesting/test_common.go +++ b/x/validator-vesting/test_common.go @@ -122,9 +122,9 @@ func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, validators := staking.InitGenesis(ctx, stakingKeeper, mapp.AccountKeeper, supplyKeeper, stakingGenesis) if genState.IsEmpty() { - InitGenesis(ctx, keeper, mapp.AccountKeeper, types.DefaultGenesisState()) + InitGenesis(ctx, keeper, types.DefaultGenesisState()) } else { - InitGenesis(ctx, keeper, mapp.AccountKeeper, genState) + InitGenesis(ctx, keeper, genState) } return abci.ResponseInitChain{ Validators: validators,