Merge pull request #254 from Kava-Labs/kd-validator-vesting

Kd validator vesting
This commit is contained in:
Ruaridh 2019-10-09 18:49:27 -04:00 committed by GitHub
commit 5672eccb99
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
25 changed files with 2919 additions and 21 deletions

View File

@ -4,6 +4,8 @@ import (
"io"
"os"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
abci "github.com/tendermint/tendermint/abci/types"
cmn "github.com/tendermint/tendermint/libs/common"
"github.com/tendermint/tendermint/libs/log"
@ -15,6 +17,7 @@ import (
"github.com/cosmos/cosmos-sdk/types/module"
"github.com/cosmos/cosmos-sdk/version"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/crisis"
distr "github.com/cosmos/cosmos-sdk/x/distribution"
@ -42,6 +45,7 @@ var (
ModuleBasics = module.NewBasicManager(
genutil.AppModuleBasic{},
auth.AppModuleBasic{},
validatorvesting.AppModuleBasic{},
bank.AppModuleBasic{},
staking.AppModuleBasic{},
mint.AppModuleBasic{},
@ -61,6 +65,7 @@ var (
staking.BondedPoolName: {supply.Burner, supply.Staking},
staking.NotBondedPoolName: {supply.Burner, supply.Staking},
gov.ModuleName: {supply.Burner},
validatorvesting.ModuleName: {supply.Burner},
}
)
@ -86,6 +91,7 @@ type App struct {
govKeeper gov.Keeper
crisisKeeper crisis.Keeper
paramsKeeper params.Keeper
vvKeeper validatorvesting.Keeper
// the module manager
mm *module.Manager
@ -108,7 +114,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
keys := sdk.NewKVStoreKeys(
bam.MainStoreKey, auth.StoreKey, staking.StoreKey,
supply.StoreKey, mint.StoreKey, distr.StoreKey, slashing.StoreKey,
gov.StoreKey, params.StoreKey,
gov.StoreKey, params.StoreKey, validatorvesting.StoreKey,
)
tkeys := sdk.NewTransientStoreKeys(params.TStoreKey)
@ -194,6 +200,13 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
&stakingKeeper,
gov.DefaultCodespace,
govRouter)
app.vvKeeper = validatorvesting.NewKeeper(
app.cdc,
keys[validatorvesting.StoreKey],
app.accountKeeper,
app.bankKeeper,
app.supplyKeeper,
&stakingKeeper)
// register the staking hooks
// NOTE: stakingKeeper above is passed by reference, so that it will contain these hooks
@ -213,12 +226,13 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
mint.NewAppModule(app.mintKeeper),
slashing.NewAppModule(app.slashingKeeper, app.stakingKeeper),
staking.NewAppModule(app.stakingKeeper, app.accountKeeper, app.supplyKeeper),
validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper),
)
// During begin block slashing happens after distr.BeginBlocker so that
// there is nothing left over in the validator fee pool, so as to keep the
// CanWithdrawInvariant invariant.
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName)
app.mm.SetOrderBeginBlockers(mint.ModuleName, distr.ModuleName, slashing.ModuleName, validatorvesting.ModuleName)
app.mm.SetOrderEndBlockers(crisis.ModuleName, gov.ModuleName, staking.ModuleName)
@ -228,7 +242,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
// Note: Changing the order of the auth module and modules that use module accounts
// results in subtle changes to the way accounts are loaded from genesis.
app.mm.SetOrderInitGenesis(
auth.ModuleName, distr.ModuleName,
auth.ModuleName, validatorvesting.ModuleName, distr.ModuleName,
staking.ModuleName, bank.ModuleName, slashing.ModuleName,
gov.ModuleName, mint.ModuleName, supply.ModuleName, crisis.ModuleName, genutil.ModuleName,
)
@ -242,6 +256,7 @@ func NewApp(logger log.Logger, db dbm.DB, traceStore io.Writer, loadLatest bool,
// transactions.
app.sm = module.NewSimulationManager(
auth.NewAppModule(app.accountKeeper),
validatorvesting.NewAppModule(app.vvKeeper, app.accountKeeper),
bank.NewAppModule(app.bankKeeper, app.accountKeeper),
supply.NewAppModule(app.supplyKeeper, app.accountKeeper),
gov.NewAppModule(app.govKeeper, app.supplyKeeper),
@ -279,6 +294,7 @@ func MakeCodec() *codec.Codec {
var cdc = codec.New()
ModuleBasics.RegisterCodec(cdc)
vesting.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
codec.RegisterEvidences(cdc)

View File

@ -3,18 +3,23 @@ package main
import (
"errors"
"fmt"
"io/ioutil"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
validatorvesting "github.com/kava-labs/kava/x/validator-vesting"
"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/version"
"github.com/cosmos/cosmos-sdk/x/auth"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/x/genutil"
)
@ -23,6 +28,8 @@ const (
flagVestingStart = "vesting-start-time"
flagVestingEnd = "vesting-end-time"
flagVestingAmt = "vesting-amount"
flagVestingPeriodsFile = "vesting-periods-file"
flagValidatorVestingFile = "validator-vesting-file"
)
// AddGenesisAccountCmd returns an add-genesis-account cobra Command.
@ -33,11 +40,17 @@ func AddGenesisAccountCmd(
cmd := &cobra.Command{
Use: "add-genesis-account [address_or_key_name] [coin][,[coin]]",
Short: "Add a genesis account to genesis.json",
Long: `Add a genesis account to genesis.json. The provided account must specify
Long: strings.TrimSpace(
fmt.Sprintf(`Add a genesis account to genesis.json. The provided account must specify
the account address or key name and a list of initial coins. If a key name is given,
the address will be looked up in the local Keybase. The list of initial tokens must
contain valid denominations. Accounts may optionally be supplied with vesting parameters.
`,
If the account is a periodic or validator vesting account, vesting periods must be suppleid
via a JSON file using the 'vesting-periods-file' flag or 'validator-vesting-file' flag,
respectively.
Example:
%s add-genesis-account <account-name> <amount> --vesting-amount <amount> --vesting-end-time <unix-timestamp> --vesting-start-time <unix-timestamp> --vesting-periods <path/to/vesting.json>`, version.ClientName),
),
Args: cobra.ExactArgs(2),
RunE: func(_ *cobra.Command, args []string) error {
config := ctx.Config
@ -70,22 +83,46 @@ func AddGenesisAccountCmd(
if err != nil {
return fmt.Errorf("failed to parse vesting amount: %w", err)
}
vestingPeriodsFile := viper.GetString(flagVestingPeriodsFile)
validatorVestingFile := viper.GetString(flagValidatorVestingFile)
if vestingPeriodsFile != "" && validatorVestingFile != "" {
return errors.New("Cannot specify both vesting-periods-file and validator-vesting-file")
}
// create concrete account type based on input parameters
var genAccount authexported.GenesisAccount
baseAccount := auth.NewBaseAccount(addr, coins.Sort(), nil, 0, 0)
if !vestingAmt.IsZero() {
baseVestingAccount := auth.NewBaseVestingAccount(
baseAccount, vestingAmt.Sort(), sdk.Coins{}, sdk.Coins{}, vestingEnd,
baseVestingAccount, err := vesting.NewBaseVestingAccount(
baseAccount, vestingAmt.Sort(), vestingEnd,
)
if err != nil {
return fmt.Errorf("Failed to create base vesting account: %w", err)
}
switch {
case vestingPeriodsFile != "":
vestingPeriodsJSON, err := ParsePeriodicVestingJSON(cdc, vestingPeriodsFile)
if err != nil {
return fmt.Errorf("failed to parse periodic vesting account json file: %w", err)
}
genAccount = vesting.NewPeriodicVestingAccountRaw(baseVestingAccount, vestingStart, vestingPeriodsJSON.Periods)
case validatorVestingFile != "":
validatorVestingJSON, err := ParseValidatorVestingJSON(cdc, validatorVestingFile)
if err != nil {
return fmt.Errorf("failed to parse validator vesting account json file: %w", err)
}
consAddr, err := sdk.ConsAddressFromHex(validatorVestingJSON.ValidatorAddress)
if err != nil {
return fmt.Errorf("failed to convert validator address to bytes: %w", err)
}
genAccount = validatorvesting.NewValidatorVestingAccountRaw(baseVestingAccount, vestingStart, validatorVestingJSON.Periods, consAddr, validatorVestingJSON.ReturnAddress, validatorVestingJSON.SigningThreshold)
case vestingStart != 0 && vestingEnd != 0:
genAccount = auth.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart)
genAccount = vesting.NewContinuousVestingAccountRaw(baseVestingAccount, vestingStart)
case vestingEnd != 0:
genAccount = auth.NewDelayedVestingAccountRaw(baseVestingAccount)
genAccount = vesting.NewDelayedVestingAccountRaw(baseVestingAccount)
default:
return errors.New("invalid vesting parameters; must supply start and end time or end time")
@ -136,6 +173,52 @@ func AddGenesisAccountCmd(
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")
cmd.Flags().String(flagVestingPeriodsFile, "", "path to file where periodic vesting schedule is specified")
cmd.Flags().String(flagValidatorVestingFile, "", "path to file where validator vesting schedule is specified")
return cmd
}
// ValidatorVestingJSON input json for validator-vesting-file flag
type ValidatorVestingJSON struct {
Periods vesting.Periods `json:"periods" yaml:"periods"`
ValidatorAddress string `json:"validator_address" yaml:"validator_address"`
SigningThreshold int64 `json:"signing_threshold" yaml:"signing_threshold"`
ReturnAddress sdk.AccAddress `json:"return_address,omitempty" yaml:"return_address,omitempty"`
}
// PeriodicVestingJSON input json for vesting-periods-file flag
type PeriodicVestingJSON struct {
Periods vesting.Periods `json:"periods" yaml:"periods"`
}
// ParsePeriodicVestingJSON reads and parses ParsePeriodicVestingJSON from the file
func ParsePeriodicVestingJSON(cdc *codec.Codec, inputFile string) (PeriodicVestingJSON, error) {
periodsInput := PeriodicVestingJSON{}
content, err := ioutil.ReadFile(inputFile)
if err != nil {
return periodsInput, err
}
if err := cdc.UnmarshalJSON(content, &periodsInput); err != nil {
return periodsInput, err
}
return periodsInput, nil
}
// ParseValidatorVestingJSON reads and parses ParseValidatorVestingJSON from the file
func ParseValidatorVestingJSON(cdc *codec.Codec, inputFile string) (ValidatorVestingJSON, error) {
validatorVestingInput := ValidatorVestingJSON{}
content, err := ioutil.ReadFile(inputFile)
if err != nil {
return validatorVestingInput, err
}
if err := cdc.UnmarshalJSON(content, &validatorVestingInput); err != nil {
return validatorVestingInput, err
}
return validatorVestingInput, nil
}

6
go.mod
View File

@ -4,10 +4,14 @@ go 1.13
require (
github.com/cosmos/cosmos-sdk v0.34.4-0.20190925161702-9d0bed8f4f4e
github.com/gorilla/mux v1.7.3
github.com/spf13/cobra v0.0.5
github.com/spf13/viper v1.4.0
github.com/stretchr/testify v1.4.0
github.com/tendermint/go-amino v0.15.0
github.com/tendermint/tendermint v0.32.3
github.com/tendermint/tendermint v0.32.5
github.com/tendermint/tm-db v0.2.0
gopkg.in/yaml.v2 v2.2.4
)
replace github.com/cosmos/cosmos-sdk => ../../cosmos/cosmos-sdk

20
go.sum
View File

@ -6,6 +6,7 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/Workiva/go-datastructures v1.0.50/go.mod h1:Z+F2Rca0qCsVYDS8z7bAGm8f3UkzuWYS/oBZz5a7VVA=
github.com/aead/siphash v1.0.1/go.mod h1:Nywa3cDsYNNK3gaciGTWPwHt0wlpNV15vwmswBAUSII=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
@ -37,6 +38,7 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cosmos/cosmos-sdk v0.34.4-0.20190925161702-9d0bed8f4f4e h1:V8WpJTIAjajE2PE+1wWCG5LUYkWQal+aH6uqPUiZ9Qc=
github.com/cosmos/cosmos-sdk v0.34.4-0.20190925161702-9d0bed8f4f4e/go.mod h1:gwKdI16dOjylNYJkaHbcx0TcEIHyRs1xyc5qROmjCJE=
github.com/cosmos/cosmos-sdk v0.37.1 h1:mz5W3Au32VIPPtrY65dheVYeVDSFfS3eSSmuIj+cXsI=
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8 h1:Iwin12wRQtyZhH6FV3ykFcdGNlYEzoeR0jN8Vn+JWsI=
github.com/cosmos/go-bip39 v0.0.0-20180618194314-52158e4697b8/go.mod h1:tSxLoYXyBmiFeKpvmq4dzayMdCjCnu8uqmCysIGBT2Y=
github.com/cosmos/ledger-cosmos-go v0.10.3 h1:Qhi5yTR5Pg1CaTpd00pxlGwNl4sFRdtK1J96OTjeFFc=
@ -72,6 +74,8 @@ github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeME
github.com/go-kit/kit v0.6.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/kit v0.9.0 h1:wDJmvq38kDhkVxi50ni9ykkdUr1PKgqKOoi01fa0Mdk=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
@ -142,8 +146,12 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/libp2p/go-buffer-pool v0.0.1 h1:9Rrn/H46cXjaA2HQ5Y8lyhOS1NhTkZ4yuEs2r3Eechg=
github.com/libp2p/go-buffer-pool v0.0.1/go.mod h1:xtyIz9PMobb13WaxR6Zo1Pd1zXJKYg0a8KiIvDp3TzQ=
github.com/libp2p/go-buffer-pool v0.0.2 h1:QNK2iAFa8gjAe1SPz6mHSMuCcjs+X1wlHzeOSqcmlfs=
github.com/libp2p/go-buffer-pool v0.0.2/go.mod h1:MvaB6xw5vOrDl8rYZGLFdKAuk/hRoRZd1Vi32+RXyFM=
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-isatty v0.0.9 h1:d5US/mDsogSGW37IV293h//ZFaeajb69h+EHFsv2xGg=
github.com/mattn/go-isatty v0.0.9/go.mod h1:YNRxwqDuOph6SZLI9vUUz6OYw3QyUt7WiY2yME+cCiQ=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
@ -162,6 +170,8 @@ github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1Cpa
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0 h1:u3Z1r+oOXJIkxqw34zVhyPgjBsm6X2wn21NWs/HfSeg=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.5.0 h1:5BakdOZdtKJ1FFk6QdL8iSGrMWsXgchNJcrnarjbmJQ=
github.com/pelletier/go-toml v1.5.0/go.mod h1:5N711Q9dKgbdkxHL+MEfF31hpT7l0S0s/t2kKREewys=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -188,6 +198,8 @@ github.com/rcrowley/go-metrics v0.0.0-20180503174638-e2704e165165/go.mod h1:bCqn
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rs/cors v1.6.0 h1:G9tHG9lebljV9mfp9SNPDL36nCDxmo3zTlAf1YgvzmI=
github.com/rs/cors v1.6.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/snikch/goodman v0.0.0-20171125024755-10e37e294daa/go.mod h1:oJyF+mSPHbB5mVY2iO9KV3pTt/QbIkGaO8gQ2WrDbP4=
@ -236,6 +248,8 @@ github.com/tendermint/iavl v0.12.4/go.mod h1:8LHakzt8/0G3/I8FUU0ReNx98S/EP6eyPJk
github.com/tendermint/tendermint v0.32.1/go.mod h1:jmPDAKuNkev9793/ivn/fTBnfpA9mGBww8MPRNPNxnU=
github.com/tendermint/tendermint v0.32.3 h1:GEnWpGQ795h5oTFNbfBLsY0LW/CW2j6p6HtiYNfxsgg=
github.com/tendermint/tendermint v0.32.3/go.mod h1:ZK2c29jl1QRYznIRyRWRDsmm1yvtPzBRT00x4t1JToY=
github.com/tendermint/tendermint v0.32.5 h1:2hCLwuzfCKZxXSe/+iMEl+ChJWKJx6g/Wcvq3NMxVN4=
github.com/tendermint/tendermint v0.32.5/go.mod h1:D2+A3pNjY+Po72X0mTfaXorFhiVI8dh/Zg640FGyGtE=
github.com/tendermint/tm-db v0.1.1 h1:G3Xezy3sOk9+ekhjZ/kjArYIs1SmwV+1OUgNkj7RgV0=
github.com/tendermint/tm-db v0.1.1/go.mod h1:0cPKWu2Mou3IlxecH+MEUSYc1Ch537alLe6CpFrKzgw=
github.com/tendermint/tm-db v0.2.0 h1:rJxgdqn6fIiVJZy4zLpY1qVlyD0TU6vhkT4kEf71TQQ=
@ -309,6 +323,8 @@ google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZi
google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
google.golang.org/grpc v1.22.0 h1:J0UbZOIrCAl+fpTOf8YLs4dJo8L/owV4LYVtAXQoPkw=
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.23.1 h1:q4XQuHFC6I28BKZpo6IYyb3mNO+l7lSOxRuYTCiDfXk=
google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
@ -322,5 +338,9 @@ gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bl
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI=
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View File

@ -0,0 +1,69 @@
package validatorvesting
import (
"bytes"
"time"
tmtime "github.com/tendermint/tendermint/types/time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/validator-vesting/internal/keeper"
abci "github.com/tendermint/tendermint/abci/types"
)
// BeginBlocker updates the vote signing information for each validator vesting account, updates account when period changes, and updates the previousBlockTime value in the store.
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) {
previousBlockTime := tmtime.Canonical(time.Unix(0, 0))
if ctx.BlockHeight() > 1 {
previousBlockTime = k.GetPreviousBlockTime(ctx)
}
currentBlockTime := ctx.BlockTime()
var voteInfos VoteInfos
voteInfos = req.LastCommitInfo.GetVotes()
validatorVestingKeys := k.GetAllAccountKeys(ctx)
for _, key := range validatorVestingKeys {
acc := k.GetAccountFromAuthKeeper(ctx, key)
if k.AccountIsVesting(ctx, acc.GetAddress()) {
vote, found := voteInfos.FilterByValidatorAddress(acc.ValidatorAddress)
if !found || !vote.SignedLastBlock {
if ctx.BlockHeight() <= 1 {
// don't count missed blocks on block 1 since there is no vote history
k.UpdateMissingSignCount(ctx, acc.GetAddress(), false)
} else {
// if the validator was not found or explicitly didn't sign, increment the missing sign count
k.UpdateMissingSignCount(ctx, acc.GetAddress(), true)
}
} else {
k.UpdateMissingSignCount(ctx, acc.GetAddress(), false)
}
// check if a period ended in the last block
endTimes := k.GetPeriodEndTimes(ctx, key)
for i, t := range endTimes {
if currentBlockTime.Unix() >= t && previousBlockTime.Unix() < t {
k.UpdateVestedCoinsProgress(ctx, key, i)
}
}
// handle any new/remaining debt on the account
k.HandleVestingDebt(ctx, key, currentBlockTime)
}
}
k.SetPreviousBlockTime(ctx, currentBlockTime)
}
// VoteInfos an array of abci.VoteInfo
type VoteInfos []abci.VoteInfo
// FilterByValidatorAddress returns the VoteInfo of the validator address matching the input validator address
// and a boolean for if the address was found.
func (vis VoteInfos) FilterByValidatorAddress(consAddress sdk.ConsAddress) (abci.VoteInfo, bool) {
for i, vi := range vis {
votingAddress := sdk.ConsAddress(vi.Validator.Address)
if bytes.Equal(consAddress, votingAddress) {
return vis[i], true
}
}
return abci.VoteInfo{}, false
}

View File

@ -0,0 +1,369 @@
package validatorvesting
import (
"testing"
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
tmtime "github.com/tendermint/tendermint/types/time"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported"
"github.com/kava-labs/kava/x/validator-vesting/internal/keeper"
"github.com/kava-labs/kava/x/validator-vesting/internal/types"
)
func TestBeginBlockerZeroHeight(t *testing.T) {
ctx, ak, _, stakingKeeper, _, vvk := keeper.CreateTestInput(t, false, 1000)
now := tmtime.Now()
vva := keeper.ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
delTokens := sdk.TokensFromConsensusPower(30)
vvk.SetValidatorVestingAccountKey(ctx, vva.Address)
keeper.CreateValidators(ctx, stakingKeeper, []int64{5, 5, 5})
val1, found := stakingKeeper.GetValidator(ctx, keeper.ValOpAddr1)
require.True(t, found)
_, err := stakingKeeper.Delegate(ctx, vva.Address, delTokens, sdk.Unbonded, val1, true)
require.NoError(t, err)
_ = staking.EndBlocker(ctx, stakingKeeper)
// require that there exists one delegation
var delegations int
stakingKeeper.IterateDelegations(ctx, vva.Address, func(index int64, d stakingexported.DelegationI) (stop bool) {
delegations++
return false
})
require.Equal(t, 1, delegations)
val := abci.Validator{
Address: val1.ConsPubKey.Address(),
Power: val1.ConsensusPower(),
}
vva.ValidatorAddress = val1.ConsAddress()
ak.SetAccount(ctx, vva)
height := int64(1)
blockTime := now
addHour := func(t time.Time) time.Time { return t.Add(1 * time.Hour) }
header := abci.Header{Height: height, Time: addHour(blockTime)}
ctx = ctx.WithBlockHeader(header)
// mark the validator as absent
req := abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: abci.Validator{},
SignedLastBlock: false,
}},
},
}
BeginBlocker(ctx, req, vvk)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
// require missed block counter doesn't increment because there's no voting history
require.Equal(t, types.CurrentPeriodProgress{0, 1}, vva.CurrentPeriodProgress)
// mark the validator as having missed
req = abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: val,
SignedLastBlock: false,
}},
},
}
BeginBlocker(ctx, req, vvk)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, types.CurrentPeriodProgress{0, 2}, vva.CurrentPeriodProgress)
}
func TestBeginBlockerSignedBlock(t *testing.T) {
ctx, ak, _, stakingKeeper, _, vvk := keeper.CreateTestInput(t, false, 1000)
now := tmtime.Now()
vva := keeper.ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
delTokens := sdk.TokensFromConsensusPower(30)
vvk.SetValidatorVestingAccountKey(ctx, vva.Address)
keeper.CreateValidators(ctx, stakingKeeper, []int64{5, 5, 5})
val1, found := stakingKeeper.GetValidator(ctx, keeper.ValOpAddr1)
require.True(t, found)
_, err := stakingKeeper.Delegate(ctx, vva.Address, delTokens, sdk.Unbonded, val1, true)
require.NoError(t, err)
_ = staking.EndBlocker(ctx, stakingKeeper)
// require that there exists one delegation
var delegations int
stakingKeeper.IterateDelegations(ctx, vva.Address, func(index int64, d stakingexported.DelegationI) (stop bool) {
delegations++
return false
})
require.Equal(t, 1, delegations)
val := abci.Validator{
Address: val1.ConsPubKey.Address(),
Power: val1.ConsensusPower(),
}
vva.ValidatorAddress = val1.ConsAddress()
ak.SetAccount(ctx, vva)
height := int64(1)
blockTime := now
addHour := func(t time.Time) time.Time { return t.Add(1 * time.Hour) }
header := abci.Header{Height: height, Time: addHour(blockTime)}
ctx = ctx.WithBlockHeader(header)
// mark the validator as having signed
req := abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: val,
SignedLastBlock: true,
}},
},
}
BeginBlocker(ctx, req, vvk)
height++
blockTime = addHour(blockTime)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, types.CurrentPeriodProgress{0, 1}, vva.CurrentPeriodProgress)
header = abci.Header{Height: height, Time: addHour(blockTime)}
// mark the validator as having signed
ctx = ctx.WithBlockHeader(header)
req = abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: val,
SignedLastBlock: true,
}},
},
}
BeginBlocker(ctx, req, vvk)
height++
blockTime = addHour(blockTime)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, types.CurrentPeriodProgress{0, 2}, vva.CurrentPeriodProgress)
header = abci.Header{Height: height, Time: addHour(blockTime)}
ctx = ctx.WithBlockHeader(header)
// mark the validator as having missed
req = abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: val,
SignedLastBlock: false,
}},
},
}
BeginBlocker(ctx, req, vvk)
height++
blockTime = addHour(blockTime)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, types.CurrentPeriodProgress{1, 3}, vva.CurrentPeriodProgress)
header = abci.Header{Height: height, Time: addHour(blockTime)}
ctx = ctx.WithBlockHeader(header)
// mark the validator as being absent
req = abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: abci.Validator{},
SignedLastBlock: true,
}},
},
}
BeginBlocker(ctx, req, vvk)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, types.CurrentPeriodProgress{2, 4}, vva.CurrentPeriodProgress)
}
func TestBeginBlockerSuccessfulPeriod(t *testing.T) {
height := int64(1)
now := tmtime.Now()
blockTime := now
numBlocks := int64(14)
addHour := func(t time.Time) time.Time { return t.Add(1 * time.Hour) }
ctx, ak, _, stakingKeeper, _, vvk := keeper.CreateTestInput(t, false, 1000)
vva := keeper.ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
vvk.SetValidatorVestingAccountKey(ctx, vva.Address)
keeper.CreateValidators(ctx, stakingKeeper, []int64{5, 5, 5})
val1, found := stakingKeeper.GetValidator(ctx, keeper.ValOpAddr1)
require.True(t, found)
_ = staking.EndBlocker(ctx, stakingKeeper)
val := abci.Validator{
Address: val1.ConsPubKey.Address(),
Power: val1.ConsensusPower(),
}
vva.ValidatorAddress = val1.ConsAddress()
ak.SetAccount(ctx, vva)
for ; height < numBlocks; height++ {
header := abci.Header{Height: height, Time: addHour(blockTime)}
// mark the validator as having signed
req := abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: val,
SignedLastBlock: true,
}},
},
}
ctx = ctx.WithBlockHeader(header)
BeginBlocker(ctx, req, vvk)
blockTime = addHour(blockTime)
if height == 12 {
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
// require that missing sign count is set back to zero after the period increments.
require.Equal(t, types.CurrentPeriodProgress{0, 0}, vva.CurrentPeriodProgress)
}
}
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
// t.Log(vva.MarshalYAML())
require.Equal(t, []types.VestingProgress{types.VestingProgress{true, true}, types.VestingProgress{false, false}, types.VestingProgress{false, false}}, vva.VestingPeriodProgress)
}
func TestBeginBlockerUnsuccessfulPeriod(t *testing.T) {
height := int64(1)
now := tmtime.Now()
blockTime := now
numBlocks := int64(13)
addHour := func(t time.Time) time.Time { return t.Add(1 * time.Hour) }
ctx, ak, _, stakingKeeper, supplyKeeper, vvk := keeper.CreateTestInput(t, false, 1000)
initialSupply := supplyKeeper.GetSupply(ctx).GetTotal()
keeper.CreateValidators(ctx, stakingKeeper, []int64{5, 5, 5})
vva := keeper.ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
// delegate all coins
delTokens := sdk.TokensFromConsensusPower(60)
vvk.SetValidatorVestingAccountKey(ctx, vva.Address)
val1, found := stakingKeeper.GetValidator(ctx, keeper.ValOpAddr1)
require.True(t, found)
_, err := stakingKeeper.Delegate(ctx, vva.Address, delTokens, sdk.Unbonded, val1, true)
require.NoError(t, err)
_ = staking.EndBlocker(ctx, stakingKeeper)
// note that delegation modifies the account's state!
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
val := abci.Validator{
Address: val1.ConsPubKey.Address(),
Power: val1.ConsensusPower(),
}
vva.ValidatorAddress = val1.ConsAddress()
ak.SetAccount(ctx, vva)
// run one period's worth of blocks
for ; height < numBlocks; height++ {
header := abci.Header{Height: height, Time: addHour(blockTime)}
// mark the validator as having missed
req := abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: val,
SignedLastBlock: false,
}},
},
}
ctx = ctx.WithBlockHeader(header)
BeginBlocker(ctx, req, vvk)
blockTime = addHour(blockTime)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
}
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
// check that the period was unsucessful
require.Equal(t, []types.VestingProgress{types.VestingProgress{true, false}, types.VestingProgress{false, false}, types.VestingProgress{false, false}}, vva.VestingPeriodProgress)
// check that there is debt after the period.
require.Equal(t, sdk.Coins{sdk.NewInt64Coin("stake", 30000000)}, vva.DebtAfterFailedVesting)
var delegations int
stakingKeeper.IterateDelegations(ctx, vva.Address, func(index int64, d stakingexported.DelegationI) (stop bool) {
delegations++
return false
})
// require that all delegations were unbonded
require.Equal(t, 0, delegations)
// complete the unbonding period
header := abci.Header{Height: height, Time: blockTime.Add(time.Hour * 2)}
req := abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: val,
SignedLastBlock: false,
}},
},
}
ctx = ctx.WithBlockHeader(header)
BeginBlocker(ctx, req, vvk)
_ = staking.EndBlocker(ctx, stakingKeeper)
header = abci.Header{Height: height, Time: blockTime.Add(time.Hour * 2)}
req = abci.RequestBeginBlock{
Header: header,
LastCommitInfo: abci.LastCommitInfo{
Votes: []abci.VoteInfo{{
Validator: val,
SignedLastBlock: false,
}},
},
}
ctx = ctx.WithBlockHeader(header)
BeginBlocker(ctx, req, vvk)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
// require that debt has reset to zero and coins balance is reduced by period 1 amount.
require.Equal(t, vva.GetCoins(), sdk.Coins{sdk.NewInt64Coin("stake", 30000000)})
require.Equal(t, sdk.Coins(nil), vva.DebtAfterFailedVesting)
// require that the supply has decreased by period 1 amount
require.Equal(t, initialSupply.Sub(vva.VestingPeriods[0].Amount), supplyKeeper.GetSupply(ctx).GetTotal())
}

View File

@ -0,0 +1,64 @@
// nolint
// autogenerated code using github.com/rigelrozanski/multitool
// aliases generated for the following subdirectories:
// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/internal/types/
// ALIASGEN: github.com/kava-labs/kava/x/validator-vesting/internal/keeper/
package validatorvesting
import (
"github.com/kava-labs/kava/x/validator-vesting/internal/keeper"
"github.com/kava-labs/kava/x/validator-vesting/internal/types"
)
const (
ModuleName = types.ModuleName
StoreKey = types.StoreKey
)
var (
// functions aliases
RegisterCodec = types.RegisterCodec
NewGenesisState = types.NewGenesisState
DefaultGenesisState = types.DefaultGenesisState
ValidateGenesis = types.ValidateGenesis
ValidatorVestingAccountKey = types.ValidatorVestingAccountKey
CreateTestAddrs = types.CreateTestAddrs
TestAddr = types.TestAddr
CreateTestPubKeys = types.CreateTestPubKeys
NewPubKey = types.NewPubKey
NewValidatorVestingAccountRaw = types.NewValidatorVestingAccountRaw
NewValidatorVestingAccount = types.NewValidatorVestingAccount
NewKeeper = keeper.NewKeeper
MakeTestCodec = keeper.MakeTestCodec
CreateTestInput = keeper.CreateTestInput
ValidatorVestingTestAccount = keeper.ValidatorVestingTestAccount
ValidatorVestingTestAccounts = keeper.ValidatorVestingTestAccounts
ValidatorVestingDelegatorTestAccount = keeper.ValidatorVestingDelegatorTestAccount
CreateValidators = keeper.CreateValidators
// variable aliases
ModuleCdc = types.ModuleCdc
BlocktimeKey = types.BlocktimeKey
ValidatorVestingAccountPrefix = types.ValidatorVestingAccountPrefix
ValOpPk1 = keeper.ValOpPk1
ValOpPk2 = keeper.ValOpPk2
ValOpPk3 = keeper.ValOpPk3
ValOpAddr1 = keeper.ValOpAddr1
ValOpAddr2 = keeper.ValOpAddr2
ValOpAddr3 = keeper.ValOpAddr3
ValConsPk11 = keeper.ValConsPk11
ValConsPk12 = keeper.ValConsPk12
ValConsPk13 = keeper.ValConsPk13
ValConsAddr1 = keeper.ValConsAddr1
ValConsAddr2 = keeper.ValConsAddr2
ValConsAddr3 = keeper.ValConsAddr3
TestAddrs = keeper.TestAddrs
)
type (
GenesisState = types.GenesisState
VestingProgress = types.VestingProgress
CurrentPeriodProgress = types.CurrentPeriodProgress
ValidatorVestingAccount = types.ValidatorVestingAccount
Keeper = keeper.Keeper
)

View File

@ -0,0 +1,26 @@
package validatorvesting
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/kava-labs/kava/x/validator-vesting/internal/types"
)
// InitGenesis stores the account address of each ValidatorVestingAccount in the validator vesting keeper, for faster lookup.
// CONTRACT: Accounts must have already been initialized/created by AccountKeeper
func InitGenesis(ctx sdk.Context, keeper Keeper, accountKeeper types.AccountKeeper, data GenesisState) {
accounts := accountKeeper.GetAllAccounts(ctx)
for _, a := range accounts {
vv, ok := a.(*ValidatorVestingAccount)
if ok {
keeper.SetValidatorVestingAccountKey(ctx, vv.Address)
}
}
keeper.SetPreviousBlockTime(ctx, data.PreviousBlockTime)
}
// ExportGenesis returns empty genesis state because auth exports all the genesis state we need.
func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState {
prevBlockTime := keeper.GetPreviousBlockTime(ctx)
return GenesisState{PreviousBlockTime: prevBlockTime}
}

View File

@ -0,0 +1,220 @@
package keeper
import (
"fmt"
"time"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported"
"github.com/kava-labs/kava/x/validator-vesting/internal/types"
"github.com/tendermint/tendermint/libs/log"
)
// Keeper of the validatorvesting store
type Keeper struct {
storeKey sdk.StoreKey
cdc *codec.Codec
ak types.AccountKeeper
bk types.BankKeeper
supplyKeeper types.SupplyKeeper
stakingKeeper types.StakingKeeper
}
// NewKeeper creates a new Keeper instance
func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, ak types.AccountKeeper, bk types.BankKeeper, sk types.SupplyKeeper, stk types.StakingKeeper) Keeper {
return Keeper{
cdc: cdc,
storeKey: key,
ak: ak,
bk: bk,
supplyKeeper: sk,
stakingKeeper: stk,
}
}
// Logger returns a module-specific logger.
func (k Keeper) Logger(ctx sdk.Context) log.Logger {
return ctx.Logger().With("module", fmt.Sprintf("x/%s", types.ModuleName))
}
// GetPreviousBlockTime get the blocktime for the previous block
func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time) {
store := ctx.KVStore(k.storeKey)
b := store.Get(types.BlocktimeKey)
if b == nil {
panic("Previous block time not set")
}
k.cdc.MustUnmarshalBinaryLengthPrefixed(b, &blockTime)
return blockTime
}
// SetPreviousBlockTime set the time of the previous block
func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(blockTime)
store.Set(types.BlocktimeKey, b)
}
// SetValidatorVestingAccountKey stores the account key in the store. This is useful for when we want to iterate over all ValidatorVestingAcounts, so we can avoid iterating over any other accounts stored in the auth keeper.
func (k Keeper) SetValidatorVestingAccountKey(ctx sdk.Context, addr sdk.AccAddress) {
store := ctx.KVStore(k.storeKey)
// using empty bytes as value since the only thing we want to do is iterate over the keys.
store.Set(types.ValidatorVestingAccountKey(addr), []byte{0})
}
// IterateAccountKeys iterates over all the stored account keys and performs a callback function
func (k Keeper) IterateAccountKeys(ctx sdk.Context, cb func(accountKey []byte) (stop bool)) {
store := ctx.KVStore(k.storeKey)
iterator := sdk.KVStorePrefixIterator(store, types.ValidatorVestingAccountPrefix)
defer iterator.Close()
for ; iterator.Valid(); iterator.Next() {
accountKey := iterator.Key()
if cb(accountKey) {
break
}
}
}
// GetAllAccountKeys returns all account keys in the validator vesting keeper.
func (k Keeper) GetAllAccountKeys(ctx sdk.Context) (keys [][]byte) {
k.IterateAccountKeys(ctx,
func(key []byte) (stop bool) {
keys = append(keys, key[1:])
return false
})
return keys
}
// GetAccountFromAuthKeeper returns a ValidatorVestingAccount from the auth keeper
func (k Keeper) GetAccountFromAuthKeeper(ctx sdk.Context, addr sdk.AccAddress) *types.ValidatorVestingAccount {
acc := k.ak.GetAccount(ctx, addr)
vv, ok := acc.(*types.ValidatorVestingAccount)
if ok {
return vv
}
panic("validator vesting account not found")
}
// UpdateMissingSignCount increments the count of blocks missed during the current period
func (k Keeper) UpdateMissingSignCount(ctx sdk.Context, addr sdk.AccAddress, missedBlock bool) {
vv := k.GetAccountFromAuthKeeper(ctx, addr)
if missedBlock {
vv.CurrentPeriodProgress.MissedBlocks++
}
vv.CurrentPeriodProgress.TotalBlocks++
k.ak.SetAccount(ctx, vv)
}
// UpdateVestedCoinsProgress sets the VestingPeriodProgress variable (0 = coins did not vest for the period, 1 = coins did vest for the period) for the given address and period. If coins did not vest, those coins are added to DebtAfterFailedVesting. Finally, MissingSignCount is reset to [0,0], representing that the next period has started and no blocks have been missed.
func (k Keeper) UpdateVestedCoinsProgress(ctx sdk.Context, addr sdk.AccAddress, period int) {
vv := k.GetAccountFromAuthKeeper(ctx, addr)
var successfulVest bool
if sdk.NewDec(vv.CurrentPeriodProgress.TotalBlocks).IsZero() {
successfulVest = true
} else {
successfulVest = vv.CurrentPeriodProgress.SignedPercetageIsOverThreshold(vv.SigningThreshold)
}
if successfulVest {
k.SetVestingProgress(ctx, addr, period, true)
} else {
k.SetVestingProgress(ctx, addr, period, false)
k.AddDebt(ctx, addr, vv.VestingPeriods[period].Amount)
}
k.ResetCurrentPeriodProgress(ctx, addr)
}
// SetVestingProgress sets VestingPeriodProgress for the input period
func (k Keeper) SetVestingProgress(ctx sdk.Context, addr sdk.AccAddress, period int, success bool) {
vv := k.GetAccountFromAuthKeeper(ctx, addr)
vv.VestingPeriodProgress[period] = types.VestingProgress{PeriodComplete: true, VestingSuccessful: success}
k.ak.SetAccount(ctx, vv)
}
// AddDebt adds the input amount to DebtAfterFailedVesting field
func (k Keeper) AddDebt(ctx sdk.Context, addr sdk.AccAddress, amount sdk.Coins) {
vv := k.GetAccountFromAuthKeeper(ctx, addr)
vv.DebtAfterFailedVesting = vv.DebtAfterFailedVesting.Add(amount)
k.ak.SetAccount(ctx, vv)
}
// ResetCurrentPeriodProgress resets CurrentPeriodProgress to zero values
func (k Keeper) ResetCurrentPeriodProgress(ctx sdk.Context, addr sdk.AccAddress) {
vv := k.GetAccountFromAuthKeeper(ctx, addr)
vv.CurrentPeriodProgress = types.CurrentPeriodProgress{TotalBlocks: 0, MissedBlocks: 0}
k.ak.SetAccount(ctx, vv)
}
// HandleVestingDebt removes coins after a vesting period in which the vesting
// threshold was not met. Sends/Burns tokens if there is enough spendable tokens,
// otherwise unbonds all existing tokens.
func (k Keeper) HandleVestingDebt(ctx sdk.Context, addr sdk.AccAddress, blockTime time.Time) {
vv := k.GetAccountFromAuthKeeper(ctx, addr)
if !vv.DebtAfterFailedVesting.IsZero() {
spendableCoins := vv.SpendableCoins(blockTime)
if spendableCoins.IsAllGTE(vv.DebtAfterFailedVesting) {
if vv.ReturnAddress != nil {
err := k.bk.SendCoins(ctx, addr, vv.ReturnAddress, vv.DebtAfterFailedVesting)
if err != nil {
panic(err)
}
} else {
err := k.supplyKeeper.SendCoinsFromAccountToModule(ctx, addr, types.ModuleName, vv.DebtAfterFailedVesting)
if err != nil {
panic(err)
}
err = k.supplyKeeper.BurnCoins(ctx, types.ModuleName, vv.DebtAfterFailedVesting)
if err != nil {
panic(err)
}
}
k.ResetDebt(ctx, addr)
} else {
// iterate over all delegations made from the validator vesting account and undelegate
// note that we cannot safely undelegate only an amount of shares that covers the debt,
// because the value of those shares could change if a validator gets slashed.
k.stakingKeeper.IterateDelegations(ctx, vv.Address, func(index int64, d stakingexported.DelegationI) (stop bool) {
k.stakingKeeper.Undelegate(ctx, d.GetDelegatorAddr(), d.GetValidatorAddr(), d.GetShares())
return false
})
}
}
}
// ResetDebt sets DebtAfterFailedVesting to zero
func (k Keeper) ResetDebt(ctx sdk.Context, addr sdk.AccAddress) {
vv := k.GetAccountFromAuthKeeper(ctx, addr)
vv.DebtAfterFailedVesting = sdk.NewCoins()
k.ak.SetAccount(ctx, vv)
}
// GetPeriodEndTimes returns an array of the times when each period ends
func (k Keeper) GetPeriodEndTimes(ctx sdk.Context, addr sdk.AccAddress) []int64 {
var endTimes []int64
vv := k.GetAccountFromAuthKeeper(ctx, addr)
currentEndTime := vv.StartTime
for _, p := range vv.VestingPeriods {
currentEndTime += p.Length
endTimes = append(endTimes, currentEndTime)
}
return endTimes
}
// AccountIsVesting returns true if all vesting periods is complete and there is no debt
func (k Keeper) AccountIsVesting(ctx sdk.Context, addr sdk.AccAddress) bool {
vv := k.GetAccountFromAuthKeeper(ctx, addr)
if !vv.DebtAfterFailedVesting.IsZero() {
return false
}
for _, p := range vv.VestingPeriodProgress {
if !p.PeriodComplete {
return false
}
}
return true
}

View File

@ -0,0 +1,368 @@
package keeper
import (
"bytes"
"testing"
"time"
"github.com/stretchr/testify/require"
tmtime "github.com/tendermint/tendermint/types/time"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported"
"github.com/kava-labs/kava/x/validator-vesting/internal/types"
)
func TestGetSetValidatorVestingAccounts(t *testing.T) {
ctx, ak, _, _, _, keeper := CreateTestInput(t, false, 1000)
vva := ValidatorVestingTestAccount()
// Add the validator vesting account to the auth store
ak.SetAccount(ctx, vva)
// require that the keeper can set the account key without panic
require.NotPanics(t, func() { keeper.SetValidatorVestingAccountKey(ctx, vva.Address) })
// require that we can get the account from auth keeper as a validator vesting account.
require.NotPanics(t, func() { keeper.GetAccountFromAuthKeeper(ctx, vva.Address) })
// fetching a regular account from the auth keeper does not panic
require.NotPanics(t, func() { ak.GetAccount(ctx, TestAddrs[0]) })
// fetching a regular account from the validator vesting keeper panics.
require.Panics(t, func() { keeper.GetAccountFromAuthKeeper(ctx, TestAddrs[0]) })
// require that GetAllAccountKeys returns one account
keys := keeper.GetAllAccountKeys(ctx)
require.Equal(t, 1, len(keys))
for _, k := range keys {
require.NotPanics(t, func() { keeper.GetAccountFromAuthKeeper(ctx, k) })
}
vvAccounts := ValidatorVestingTestAccounts(10)
for _, a := range vvAccounts {
ak.SetAccount(ctx, a)
keeper.SetValidatorVestingAccountKey(ctx, a.Address)
}
keys = keeper.GetAllAccountKeys(ctx)
require.Equal(t, 10, len(keys))
var ikeys [][]byte
keeper.IterateAccountKeys(ctx, func(accountKey []byte) bool {
if bytes.Equal(accountKey[1:], keys[0]) {
ikeys = append(ikeys, accountKey)
return true
}
return false
})
require.Equal(t, 1, len(ikeys))
}
func TestGetSetPreviousBlock(t *testing.T) {
ctx, _, _, _, _, keeper := CreateTestInput(t, false, 1000)
now := tmtime.Now()
// require panic if the previous blocktime was never set
require.Panics(t, func() { keeper.GetPreviousBlockTime(ctx) })
// require that passing a valid time to SetPreviousBlockTime does not panic
require.NotPanics(t, func() { keeper.SetPreviousBlockTime(ctx, now) })
// require that the value from GetPreviousBlockTime equals what was set
bpt := keeper.GetPreviousBlockTime(ctx)
require.Equal(t, now, bpt)
// require that the zero value is safe
require.NotPanics(t, func() { keeper.SetPreviousBlockTime(ctx, tmtime.Canonical(time.Unix(0, 0))) })
bpt = keeper.GetPreviousBlockTime(ctx)
require.Equal(t, tmtime.Canonical(time.Unix(0, 0)), bpt)
}
func TestGetEndTImes(t *testing.T) {
ctx, ak, _, _, _, keeper := CreateTestInput(t, false, 1000)
now := tmtime.Now()
vva := ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
keeper.SetValidatorVestingAccountKey(ctx, vva.Address)
expectedEndTimes := []int64{
now.Add(12 * time.Hour).Unix(),
now.Add(18 * time.Hour).Unix(),
now.Add(24 * time.Hour).Unix(),
}
endTimes := keeper.GetPeriodEndTimes(ctx, vva.Address)
require.Equal(t, expectedEndTimes, endTimes)
}
func TestAccountIsVesting(t *testing.T) {
ctx, ak, _, _, _, keeper := CreateTestInput(t, false, 1000)
now := tmtime.Now()
vva := ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
keeper.SetValidatorVestingAccountKey(ctx, vva.Address)
require.Equal(t, false, keeper.AccountIsVesting(ctx, vva.Address))
for i := range vva.VestingPeriodProgress {
vva.VestingPeriodProgress[i] = types.VestingProgress{true, true}
ak.SetAccount(ctx, vva)
}
require.Equal(t, true, keeper.AccountIsVesting(ctx, vva.Address))
}
func TestSetMissingSignCount(t *testing.T) {
ctx, ak, _, _, _, keeper := CreateTestInput(t, false, 1000)
vva := ValidatorVestingTestAccount()
// Add the validator vesting account to the auth store
ak.SetAccount(ctx, vva)
// require empty array after ValidatorVestingAccount is initialized
require.Equal(t, types.CurrentPeriodProgress{0, 0}, vva.CurrentPeriodProgress)
// validator signs a block
keeper.UpdateMissingSignCount(ctx, vva.Address, false)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, types.CurrentPeriodProgress{0, 1}, vva.CurrentPeriodProgress)
// validator misses a block
keeper.UpdateMissingSignCount(ctx, vva.Address, true)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, types.CurrentPeriodProgress{1, 2}, vva.CurrentPeriodProgress)
}
func TestUpdateVestedCoinsProgress(t *testing.T) {
ctx, ak, _, _, _, keeper := CreateTestInput(t, false, 1000)
vva := ValidatorVestingTestAccount()
// Add the validator vesting account to the auth store
ak.SetAccount(ctx, vva)
// require all vesting period tracking variables to be zero after validator vesting account is initialized
require.Equal(t, []types.VestingProgress{types.VestingProgress{false, false}, types.VestingProgress{false, false}, types.VestingProgress{false, false}}, vva.VestingPeriodProgress)
// period 0 passes with all blocks signed
vva.CurrentPeriodProgress.MissedBlocks = 0
vva.CurrentPeriodProgress.TotalBlocks = 100
ak.SetAccount(ctx, vva)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
// require that debt is zerox
require.Equal(t, sdk.Coins(nil), vva.DebtAfterFailedVesting)
// require that the first vesting progress variable is successful
require.Equal(t, []types.VestingProgress{types.VestingProgress{true, true}, types.VestingProgress{false, false}, types.VestingProgress{false, false}}, vva.VestingPeriodProgress)
// require that the missing block counter has reset
require.Equal(t, types.CurrentPeriodProgress{0, 0}, vva.CurrentPeriodProgress)
vva = ValidatorVestingTestAccount()
ak.SetAccount(ctx, vva)
// period 0 passes with no blocks signed
// this is an edge case that shouldn't happen,
// the vest is considered successful in this case.
vva.CurrentPeriodProgress.MissedBlocks = 0
vva.CurrentPeriodProgress.TotalBlocks = 0
ak.SetAccount(ctx, vva)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
// require that debt is zero
require.Equal(t, sdk.Coins(nil), vva.DebtAfterFailedVesting)
// require that the first vesting progress variable is successful
require.Equal(t, []types.VestingProgress{types.VestingProgress{true, true}, types.VestingProgress{false, false}, types.VestingProgress{false, false}}, vva.VestingPeriodProgress)
// require that the missing block counter has reset
require.Equal(t, types.CurrentPeriodProgress{0, 0}, vva.CurrentPeriodProgress)
vva = ValidatorVestingTestAccount()
ak.SetAccount(ctx, vva)
// period 0 passes with 50% of blocks signed (below threshold)
vva.CurrentPeriodProgress.MissedBlocks = 50
vva.CurrentPeriodProgress.TotalBlocks = 100
ak.SetAccount(ctx, vva)
keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
// require that period 1 coins have become debt
require.Equal(t, sdk.NewCoins(sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)), vva.DebtAfterFailedVesting)
// require that the first vesting progress variable is {true, false}
require.Equal(t, []types.VestingProgress{types.VestingProgress{true, false}, types.VestingProgress{false, false}, types.VestingProgress{false, false}}, vva.VestingPeriodProgress)
// require that the missing block counter has reset
require.Equal(t, types.CurrentPeriodProgress{0, 0}, vva.CurrentPeriodProgress)
}
func TestHandleVestingDebtNoDebt(t *testing.T) {
// ctx, ak, bk, stakingKeeper, supplyKeeper, keeper := CreateTestInput(t, false, 1000)
ctx, ak, _, _, _, keeper := CreateTestInput(t, false, 1000)
vva := ValidatorVestingTestAccount()
// Delegate all coins
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
now := tmtime.Now()
vva.TrackDelegation(now, origCoins)
// Add the validator vesting account to the auth store
ak.SetAccount(ctx, vva)
// Require that calling HandleVestingDebt when debt is zero doesn't alter the delegation
keeper.HandleVestingDebt(ctx, vva.Address, now)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, origCoins, vva.DelegatedVesting)
require.Nil(t, vva.DelegatedFree)
}
func TestHandleVestingDebtForcedUnbond(t *testing.T) {
// ctx, ak, bk, stakingKeeper, supplyKeeper, keeper := CreateTestInput(t, false, 1000)
ctx, ak, _, stakingKeeper, _, keeper := CreateTestInput(t, false, 1000)
now := tmtime.Now()
// Create validators and a delegation from the validator vesting account
CreateValidators(ctx, stakingKeeper, []int64{5, 5, 5})
vva := ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
delTokens := sdk.TokensFromConsensusPower(60)
val1, found := stakingKeeper.GetValidator(ctx, ValOpAddr1)
require.True(t, found)
_, err := stakingKeeper.Delegate(ctx, vva.Address, delTokens, sdk.Unbonded, val1, true)
require.NoError(t, err)
_ = staking.EndBlocker(ctx, stakingKeeper)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
// t.Log(vva.GetDelegatedFree())
t.Log(vva.GetDelegatedVesting())
// require that there exists one delegation
var delegations int
stakingKeeper.IterateDelegations(ctx, vva.Address, func(index int64, d stakingexported.DelegationI) (stop bool) {
delegations++
return false
})
require.Equal(t, 1, delegations)
// period 0 passes and the threshold is not met
vva.CurrentPeriodProgress.MissedBlocks = 50
vva.CurrentPeriodProgress.TotalBlocks = 100
ak.SetAccount(ctx, vva)
keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
// require that period 0 coins have become debt
require.Equal(t, sdk.NewCoins(sdk.NewInt64Coin(stakeDenom, 30000000)), vva.DebtAfterFailedVesting)
// when there are no additional liquid coins in the account, require that there are no delegations after HandleVestingDebt (ie the account has been force unbonded)
keeper.HandleVestingDebt(ctx, vva.Address, now.Add(12*time.Hour))
delegations = 0
stakingKeeper.IterateDelegations(ctx, vva.Address, func(index int64, d stakingexported.DelegationI) (stop bool) {
delegations++
return false
})
require.Equal(t, 0, delegations)
}
func TestHandleVestingDebtBurn(t *testing.T) {
ctx, ak, _, stakingKeeper, supplyKeeper, keeper := CreateTestInput(t, false, 1000)
CreateValidators(ctx, stakingKeeper, []int64{5, 5, 5})
now := tmtime.Now()
vva := ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
delTokens := sdk.TokensFromConsensusPower(30)
val1, found := stakingKeeper.GetValidator(ctx, ValOpAddr1)
require.True(t, found)
// delegate half the tokens, which will make the period 1 coins that fail to vest immediately cover the debt.
_, err := stakingKeeper.Delegate(ctx, vva.Address, delTokens, sdk.Unbonded, val1, true)
require.NoError(t, err)
_ = staking.EndBlocker(ctx, stakingKeeper)
// period 0 passes and the threshold is not met
vva.CurrentPeriodProgress.MissedBlocks = 50
vva.CurrentPeriodProgress.TotalBlocks = 100
ak.SetAccount(ctx, vva)
keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
// require that period 0 coins have become debt
require.Equal(t, sdk.NewCoins(sdk.NewInt64Coin(stakeDenom, 30000000)), vva.DebtAfterFailedVesting)
initialSupply := supplyKeeper.GetSupply(ctx).GetTotal()
expectedSupply := initialSupply.Sub(vva.DebtAfterFailedVesting)
// Context needs the block time because bank keeper calls 'SpendableCoins' by getting the header from the context.
ctx = ctx.WithBlockTime(now.Add(12 * time.Hour))
keeper.HandleVestingDebt(ctx, vva.Address, now.Add(12*time.Hour))
// in the case when the return address is not set require that the total supply has decreased by the debt amount
require.Equal(t, expectedSupply, supplyKeeper.GetSupply(ctx).GetTotal())
// require that there is still one delegation
delegations := 0
stakingKeeper.IterateDelegations(ctx, vva.Address, func(index int64, d stakingexported.DelegationI) (stop bool) {
delegations++
return false
})
require.Equal(t, 1, delegations)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
//require that debt is now zero
require.Equal(t, sdk.Coins(nil), vva.DebtAfterFailedVesting)
}
func TestHandleVestingDebtReturn(t *testing.T) {
ctx, ak, _, stakingKeeper, _, keeper := CreateTestInput(t, false, 1000)
CreateValidators(ctx, stakingKeeper, []int64{5, 5, 5})
now := tmtime.Now()
vva := ValidatorVestingDelegatorTestAccount(now)
vva.ReturnAddress = TestAddrs[2]
ak.SetAccount(ctx, vva)
delTokens := sdk.TokensFromConsensusPower(30)
val1, found := stakingKeeper.GetValidator(ctx, ValOpAddr1)
require.True(t, found)
_, err := stakingKeeper.Delegate(ctx, vva.Address, delTokens, sdk.Unbonded, val1, true)
require.NoError(t, err)
_ = staking.EndBlocker(ctx, stakingKeeper)
// period 0 passes and the threshold is not met
vva.CurrentPeriodProgress.MissedBlocks = 50
vva.CurrentPeriodProgress.TotalBlocks = 100
ak.SetAccount(ctx, vva)
keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
// require that period 0 coins have become debt
require.Equal(t, sdk.NewCoins(sdk.NewInt64Coin(stakeDenom, 30000000)), vva.DebtAfterFailedVesting)
initialBalance := ak.GetAccount(ctx, TestAddrs[2]).GetCoins()
expectedBalance := initialBalance.Add(vva.DebtAfterFailedVesting)
// Context needs the block time because bank keeper calls 'SpendableCoins' by getting the header from the context.
ctx = ctx.WithBlockTime(now.Add(12 * time.Hour))
keeper.HandleVestingDebt(ctx, vva.Address, now.Add(12*time.Hour))
// in the case when the return address is, set require that return address balance has increased by the debt amount
require.Equal(t, expectedBalance, ak.GetAccount(ctx, TestAddrs[2]).GetCoins())
// require that there is still one delegation
delegations := 0
stakingKeeper.IterateDelegations(ctx, vva.Address, func(index int64, d stakingexported.DelegationI) (stop bool) {
delegations++
return false
})
require.Equal(t, 1, delegations)
vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address)
//require that debt is now zero
require.Equal(t, sdk.Coins(nil), vva.DebtAfterFailedVesting)
}

View File

@ -0,0 +1,248 @@
package keeper
// nolint:deadcode unused
// DONTCOVER
// noalias
import (
"testing"
"time"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
"github.com/tendermint/tendermint/libs/log"
tmtime "github.com/tendermint/tendermint/types/time"
dbm "github.com/tendermint/tm-db"
"github.com/cosmos/cosmos-sdk/codec"
"github.com/cosmos/cosmos-sdk/store"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"github.com/cosmos/cosmos-sdk/x/auth/vesting"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/params"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/supply"
"github.com/kava-labs/kava/x/validator-vesting/internal/types"
)
//nolint: deadcode unused
var (
delPk1 = ed25519.GenPrivKey().PubKey()
delPk2 = ed25519.GenPrivKey().PubKey()
delPk3 = ed25519.GenPrivKey().PubKey()
delAddr1 = sdk.AccAddress(delPk1.Address())
delAddr2 = sdk.AccAddress(delPk2.Address())
delAddr3 = sdk.AccAddress(delPk3.Address())
ValOpPk1 = ed25519.GenPrivKey().PubKey()
ValOpPk2 = ed25519.GenPrivKey().PubKey()
ValOpPk3 = ed25519.GenPrivKey().PubKey()
ValOpAddr1 = sdk.ValAddress(ValOpPk1.Address())
ValOpAddr2 = sdk.ValAddress(ValOpPk2.Address())
ValOpAddr3 = sdk.ValAddress(ValOpPk3.Address())
valAccAddr1 = sdk.AccAddress(ValOpPk1.Address()) // generate acc addresses for these validator keys too
valAccAddr2 = sdk.AccAddress(ValOpPk2.Address())
valAccAddr3 = sdk.AccAddress(ValOpPk3.Address())
ValConsPk11 = ed25519.GenPrivKey().PubKey()
ValConsPk12 = ed25519.GenPrivKey().PubKey()
ValConsPk13 = ed25519.GenPrivKey().PubKey()
ValConsAddr1 = sdk.ConsAddress(ValConsPk11.Address())
ValConsAddr2 = sdk.ConsAddress(ValConsPk12.Address())
ValConsAddr3 = sdk.ConsAddress(ValConsPk13.Address())
// TODO move to common testing package for all modules
// test addresses
TestAddrs = []sdk.AccAddress{
delAddr1, delAddr2, delAddr3,
valAccAddr1, valAccAddr2, valAccAddr3,
}
emptyDelAddr sdk.AccAddress
emptyValAddr sdk.ValAddress
emptyPubkey crypto.PubKey
stakeDenom = "stake"
feeDenom = "fee"
)
func MakeTestCodec() *codec.Codec {
var cdc = codec.New()
auth.RegisterCodec(cdc)
vesting.RegisterCodec(cdc)
types.RegisterCodec(cdc)
supply.RegisterCodec(cdc)
staking.RegisterCodec(cdc)
sdk.RegisterCodec(cdc)
codec.RegisterCrypto(cdc)
return cdc
}
// test common should produce a staking keeper, a supply keeper, a bank keeper, an auth keeper, a validatorvesting keeper, a context,
func CreateTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context, auth.AccountKeeper, bank.Keeper, staking.Keeper, supply.Keeper, Keeper) {
initTokens := sdk.TokensFromConsensusPower(initPower)
keyAcc := sdk.NewKVStoreKey(auth.StoreKey)
keyStaking := sdk.NewKVStoreKey(staking.StoreKey)
keySupply := sdk.NewKVStoreKey(supply.StoreKey)
keyParams := sdk.NewKVStoreKey(params.StoreKey)
tkeyParams := sdk.NewTransientStoreKey(params.TStoreKey)
keyValidatorVesting := sdk.NewKVStoreKey(types.StoreKey)
db := dbm.NewMemDB()
ms := store.NewCommitMultiStore(db)
ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(keySupply, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(keyValidatorVesting, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(keyStaking, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db)
ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db)
require.Nil(t, ms.LoadLatestVersion())
ctx := sdk.NewContext(ms, abci.Header{ChainID: "foo-chain"}, isCheckTx, log.NewNopLogger())
feeCollectorAcc := supply.NewEmptyModuleAccount(auth.FeeCollectorName)
notBondedPool := supply.NewEmptyModuleAccount(staking.NotBondedPoolName, supply.Burner, supply.Staking)
bondPool := supply.NewEmptyModuleAccount(staking.BondedPoolName, supply.Burner, supply.Staking)
validatorVestingAcc := supply.NewEmptyModuleAccount(types.ModuleName)
blacklistedAddrs := make(map[string]bool)
blacklistedAddrs[feeCollectorAcc.GetAddress().String()] = true
blacklistedAddrs[notBondedPool.GetAddress().String()] = true
blacklistedAddrs[bondPool.GetAddress().String()] = true
blacklistedAddrs[validatorVestingAcc.GetAddress().String()] = true
cdc := MakeTestCodec()
pk := params.NewKeeper(cdc, keyParams, tkeyParams, params.DefaultCodespace)
stakingParams := staking.NewParams(time.Hour, 100, uint16(7), sdk.DefaultBondDenom)
accountKeeper := auth.NewAccountKeeper(cdc, keyAcc, pk.Subspace(auth.DefaultParamspace), auth.ProtoBaseAccount)
bankKeeper := bank.NewBaseKeeper(accountKeeper, pk.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs)
maccPerms := map[string][]string{
auth.FeeCollectorName: nil,
staking.NotBondedPoolName: {supply.Burner, supply.Staking},
staking.BondedPoolName: {supply.Burner, supply.Staking},
types.ModuleName: {supply.Burner},
}
supplyKeeper := supply.NewKeeper(cdc, keySupply, accountKeeper, bankKeeper, maccPerms)
stakingKeeper := staking.NewKeeper(cdc, keyStaking, supplyKeeper, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace)
stakingKeeper.SetParams(ctx, stakingParams)
keeper := NewKeeper(cdc, keyValidatorVesting, accountKeeper, bankKeeper, supplyKeeper, stakingKeeper)
initCoins := sdk.NewCoins(sdk.NewCoin(stakingKeeper.BondDenom(ctx), initTokens))
totalSupply := sdk.NewCoins(sdk.NewCoin(stakingKeeper.BondDenom(ctx), initTokens.MulRaw(int64(len(TestAddrs)))))
supplyKeeper.SetSupply(ctx, supply.NewSupply(totalSupply))
// fill all the addresses with some coins, set the loose pool tokens simultaneously
for _, addr := range TestAddrs {
_, err := bankKeeper.AddCoins(ctx, addr, initCoins)
require.Nil(t, err)
}
// set module accounts
keeper.supplyKeeper.SetModuleAccount(ctx, feeCollectorAcc)
keeper.supplyKeeper.SetModuleAccount(ctx, notBondedPool)
keeper.supplyKeeper.SetModuleAccount(ctx, bondPool)
return ctx, accountKeeper, bankKeeper, stakingKeeper, supplyKeeper, keeper
}
func ValidatorVestingTestAccount() *types.ValidatorVestingAccount {
now := tmtime.Now()
periods := vesting.Periods{
vesting.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vesting.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vesting.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
}
testAddr := types.CreateTestAddrs(1)[0]
testPk := types.CreateTestPubKeys(1)[0]
testConsAddr := sdk.ConsAddress(testPk.Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
bacc := auth.NewBaseAccountWithAddress(testAddr)
bacc.SetCoins(origCoins)
vva := types.NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
err := vva.Validate()
if err != nil {
panic(err)
}
return vva
}
func ValidatorVestingTestAccounts(numAccounts int) []*types.ValidatorVestingAccount {
now := tmtime.Now()
periods := vesting.Periods{
vesting.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vesting.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vesting.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
}
testAddr := types.CreateTestAddrs(numAccounts)
testPk := types.CreateTestPubKeys(numAccounts)
var vvas []*types.ValidatorVestingAccount
for i := 0; i < numAccounts; i++ {
testConsAddr := sdk.ConsAddress(testPk[i].Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
bacc := auth.NewBaseAccountWithAddress(testAddr[i])
bacc.SetCoins(origCoins)
vva := types.NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
err := vva.Validate()
if err != nil {
panic(err)
}
vvas = append(vvas, vva)
}
return vvas
}
func ValidatorVestingDelegatorTestAccount(startTime time.Time) *types.ValidatorVestingAccount {
periods := vesting.Periods{
vesting.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(stakeDenom, 30000000)}},
vesting.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(stakeDenom, 15000000)}},
vesting.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(stakeDenom, 15000000)}},
}
testAddr := types.CreateTestAddrs(1)[0]
testPk := types.CreateTestPubKeys(1)[0]
testConsAddr := sdk.ConsAddress(testPk.Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(stakeDenom, 60000000)}
bacc := auth.NewBaseAccountWithAddress(testAddr)
bacc.SetCoins(origCoins)
vva := types.NewValidatorVestingAccount(&bacc, startTime.Unix(), periods, testConsAddr, nil, 90)
err := vva.Validate()
if err != nil {
panic(err)
}
return vva
}
func CreateValidators(ctx sdk.Context, sk staking.Keeper, powers []int64) {
val1 := staking.NewValidator(ValOpAddr1, ValOpPk1, staking.Description{})
val2 := staking.NewValidator(ValOpAddr2, ValOpPk2, staking.Description{})
val3 := staking.NewValidator(ValOpAddr3, ValOpPk3, staking.Description{})
sk.SetValidator(ctx, val1)
sk.SetValidator(ctx, val2)
sk.SetValidator(ctx, val3)
sk.SetValidatorByConsAddr(ctx, val1)
sk.SetValidatorByConsAddr(ctx, val2)
sk.SetValidatorByConsAddr(ctx, val3)
sk.SetNewValidatorByPowerIndex(ctx, val1)
sk.SetNewValidatorByPowerIndex(ctx, val2)
sk.SetNewValidatorByPowerIndex(ctx, val3)
_, _ = sk.Delegate(ctx, valAccAddr1, sdk.TokensFromConsensusPower(powers[0]), sdk.Unbonded, val1, true)
_, _ = sk.Delegate(ctx, valAccAddr2, sdk.TokensFromConsensusPower(powers[1]), sdk.Unbonded, val2, true)
_, _ = sk.Delegate(ctx, valAccAddr3, sdk.TokensFromConsensusPower(powers[2]), sdk.Unbonded, val3, true)
_ = staking.EndBlocker(ctx, sk)
}

View File

@ -0,0 +1,19 @@
package types
import (
"github.com/cosmos/cosmos-sdk/codec"
)
// RegisterCodec registers concrete types on the codec
func RegisterCodec(cdc *codec.Codec) {
cdc.RegisterConcrete(&ValidatorVestingAccount{}, "cosmos-sdk/ValidatorVestingAccount", nil)
}
// ModuleCdc module wide codec
var ModuleCdc *codec.Codec
func init() {
ModuleCdc = codec.New()
RegisterCodec(ModuleCdc)
ModuleCdc.Seal()
}

View File

@ -0,0 +1,38 @@
package types
import (
"time"
sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
)
// AccountKeeper defines the expected account keeper (noalias)
type AccountKeeper interface {
GetAccount(sdk.Context, sdk.AccAddress) authexported.Account
SetAccount(sdk.Context, authexported.Account)
GetAllAccounts(ctx sdk.Context) (accounts []authexported.Account)
}
// BankKeeper defines the expected bank keeper (noalias)
type BankKeeper interface {
SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) sdk.Error
}
// StakingKeeper defines the expected staking keeper (noalias)
type StakingKeeper interface {
IterateDelegations(ctx sdk.Context, delegator sdk.AccAddress,
fn func(index int64, delegation stakingexported.DelegationI) (stop bool))
Undelegate(
ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, sharesAmount sdk.Dec,
) (time.Time, sdk.Error)
}
// SupplyKeeper defines the expected supply keeper for module accounts (noalias)
type SupplyKeeper interface {
SendCoinsFromAccountToModule(ctx sdk.Context, senderAddr sdk.AccAddress, recipientModule string, amt sdk.Coins) sdk.Error
BurnCoins(ctx sdk.Context, name string, amt sdk.Coins) sdk.Error
SetModuleAccount(sdk.Context, supplyexported.ModuleAccountI)
}

View File

@ -0,0 +1,46 @@
package types
import (
"bytes"
"fmt"
"time"
tmtime "github.com/tendermint/tendermint/types/time"
)
// GenesisState - all auth state that must be provided at genesis
type GenesisState struct {
PreviousBlockTime time.Time
}
// NewGenesisState - Create a new genesis state
func NewGenesisState(prevBlockTime time.Time) GenesisState {
return GenesisState{
PreviousBlockTime: prevBlockTime,
}
}
// DefaultGenesisState - Return a default genesis state
func DefaultGenesisState() GenesisState {
return NewGenesisState(tmtime.Canonical(time.Unix(0, 0)))
}
// Equal checks whether two gov GenesisState structs are equivalent
func (data GenesisState) Equal(data2 GenesisState) bool {
b1 := ModuleCdc.MustMarshalBinaryBare(data)
b2 := ModuleCdc.MustMarshalBinaryBare(data2)
return bytes.Equal(b1, b2)
}
// IsEmpty returns true if a GenesisState is empty
func (data GenesisState) IsEmpty() bool {
return data.Equal(GenesisState{})
}
// ValidateGenesis returns nil because accounts are validated by auth
func ValidateGenesis(data GenesisState) error {
if data.PreviousBlockTime.Unix() < 0 {
return fmt.Errorf("Previous block time should be positive, is set to %v", data.PreviousBlockTime.Unix())
}
return nil
}

View File

@ -0,0 +1,25 @@
package types
import (
sdk "github.com/cosmos/cosmos-sdk/types"
)
const (
// ModuleName name used throughout module
ModuleName = "validatorvesting"
// StoreKey to be used when creating the KVStore
StoreKey = ModuleName
)
var (
// BlocktimeKey key for the time of the previous block
BlocktimeKey = []byte{0x00}
// ValidatorVestingAccountPrefix store prefix for validator vesting accounts
ValidatorVestingAccountPrefix = []byte{0x01}
)
// ValidatorVestingAccountKey returns the account address bytes prefixed by ValidatorVestingAccountPrefix
func ValidatorVestingAccountKey(addr sdk.AccAddress) []byte {
return append(ValidatorVestingAccountPrefix, addr.Bytes()...)
}

View File

@ -0,0 +1,86 @@
package types
// nolint:deadcode unused
// DONTCOVER
// noalias
import (
"bytes"
"encoding/hex"
"strconv"
"github.com/tendermint/tendermint/crypto"
"github.com/tendermint/tendermint/crypto/ed25519"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// nolint: unparam
func CreateTestAddrs(numAddrs int) []sdk.AccAddress {
var addresses []sdk.AccAddress
var buffer bytes.Buffer
// start at 100 so we can make up to 999 test addresses with valid test addresses
for i := 100; i < (numAddrs + 100); i++ {
numString := strconv.Itoa(i)
buffer.WriteString("A58856F0FD53BF058B4909A21AEC019107BA6") //base address string
buffer.WriteString(numString) //adding on final two digits to make addresses unique
res, _ := sdk.AccAddressFromHex(buffer.String())
bech := res.String()
addresses = append(addresses, TestAddr(buffer.String(), bech))
buffer.Reset()
}
return addresses
}
// TestAddr for incode address generation
func TestAddr(addr string, bech string) sdk.AccAddress {
res, err := sdk.AccAddressFromHex(addr)
if err != nil {
panic(err)
}
bechexpected := res.String()
if bech != bechexpected {
panic("Bech encoding doesn't match reference")
}
bechres, err := sdk.AccAddressFromBech32(bech)
if err != nil {
panic(err)
}
if !bytes.Equal(bechres, res) {
panic("Bech decode and hex decode don't match")
}
return res
}
// nolint: unparam
func CreateTestPubKeys(numPubKeys int) []crypto.PubKey {
var publicKeys []crypto.PubKey
var buffer bytes.Buffer
//start at 10 to avoid changing 1 to 01, 2 to 02, etc
for i := 100; i < (numPubKeys + 100); i++ {
numString := strconv.Itoa(i)
buffer.WriteString("0B485CFC0EECC619440448436F8FC9DF40566F2369E72400281454CB552AF") //base pubkey string
buffer.WriteString(numString) //adding on final two digits to make pubkeys unique
publicKeys = append(publicKeys, NewPubKey(buffer.String()))
buffer.Reset()
}
return publicKeys
}
// NewPubKey for incode pubkey generation
func NewPubKey(pk string) (res crypto.PubKey) {
pkBytes, err := hex.DecodeString(pk)
if err != nil {
panic(err)
}
//res, err = crypto.PubKeyFromBytes(pkBytes)
var pkEd ed25519.PubKeyEd25519
copy(pkEd[:], pkBytes[:])
return pkEd
}

View File

@ -0,0 +1,252 @@
package types
import (
"errors"
"time"
"gopkg.in/yaml.v2"
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"
)
// Assert ValidatorVestingAccount implements the vestexported.VestingAccount interface
// Assert ValidatorVestingAccount implements the authexported.GenesisAccount interface
var _ vestexported.VestingAccount = (*ValidatorVestingAccount)(nil)
var _ authexported.GenesisAccount = (*ValidatorVestingAccount)(nil)
// Register the ValidatorVestingAccount type on the auth module codec
func init() {
authtypes.RegisterAccountTypeCodec(&ValidatorVestingAccount{}, "cosmos-sdk/ValidatorVestingAccount")
}
// VestingProgress tracks the status of each vesting period
type VestingProgress struct {
PeriodComplete bool `json:"period_complete" yaml:"period_complete"`
VestingSuccessful bool `json:"vesting_successful" yaml:"vesting_successful"`
}
// CurrentPeriodProgress tracks the progress of the current vesting period
type CurrentPeriodProgress struct {
MissedBlocks int64 `json:"missed_blocks" yaml:"missed_blocks"`
TotalBlocks int64 `json:"total_blocks" yaml:"total_blocks"`
}
// GetSignedPercentage returns the percentage of blocks signed for the current vesting period
func (cpp CurrentPeriodProgress) GetSignedPercentage() sdk.Dec {
blocksSigned := cpp.TotalBlocks - cpp.MissedBlocks
// signed_percentage = blocksSigned/TotalBlocks * 100
signedPercentage := sdk.NewDec(blocksSigned).Quo(
sdk.NewDec(cpp.TotalBlocks)).Mul(
sdk.NewDec(100))
return signedPercentage
}
// SignedPercetageIsOverThreshold checks if the signed percentage exceeded the threshold
func (cpp CurrentPeriodProgress) SignedPercetageIsOverThreshold(threshold int64) bool {
signedPercentage := cpp.GetSignedPercentage()
return signedPercentage.GTE(sdk.NewDec(threshold))
}
// ValidatorVestingAccount implements the VestingAccount interface. It
// conditionally vests by unlocking coins during each specified period, provided
// that the validator address has validated at least **SigningThreshold** blocks during
// the previous vesting period. The signing threshold takes values 0 to 100 are represents the
// percentage of blocks that must be signed each period for the vesting to complete successfully.
// If the validator has not signed at least the threshold percentage of blocks during a period,
// the coins are returned to the return address, or burned if the return address is null.
type ValidatorVestingAccount struct {
*vestingtypes.PeriodicVestingAccount
ValidatorAddress sdk.ConsAddress `json:"validator_address" yaml:"validator_address"`
ReturnAddress sdk.AccAddress `json:"return_address" yaml:"return_address"`
SigningThreshold int64 `json:"signing_threshold" yaml:"signing_threshold"`
CurrentPeriodProgress CurrentPeriodProgress `json:"current_period_progress" yaml:"current_period_progress"`
VestingPeriodProgress []VestingProgress `json:"vesting_period_progress" yaml:"vesting_period_progress"`
DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"`
}
// NewValidatorVestingAccountRaw creates a new ValidatorVestingAccount object from BaseVestingAccount
func NewValidatorVestingAccountRaw(bva *vestingtypes.BaseVestingAccount,
startTime int64, periods vestingtypes.Periods, validatorAddress sdk.ConsAddress, returnAddress sdk.AccAddress, signingThreshold int64) *ValidatorVestingAccount {
pva := &vestingtypes.PeriodicVestingAccount{
BaseVestingAccount: bva,
StartTime: startTime,
VestingPeriods: periods,
}
var vestingPeriodProgress []VestingProgress
for i := 0; i < len(periods); i++ {
vestingPeriodProgress = append(vestingPeriodProgress, VestingProgress{false, false})
}
return &ValidatorVestingAccount{
PeriodicVestingAccount: pva,
ValidatorAddress: validatorAddress,
ReturnAddress: returnAddress,
SigningThreshold: signingThreshold,
CurrentPeriodProgress: CurrentPeriodProgress{0, 0},
VestingPeriodProgress: vestingPeriodProgress,
DebtAfterFailedVesting: sdk.NewCoins(),
}
}
// NewValidatorVestingAccount creates a ValidatorVestingAccount object from a BaseAccount
func NewValidatorVestingAccount(baseAcc *authtypes.BaseAccount, startTime int64, periods vestingtypes.Periods, validatorAddress sdk.ConsAddress, returnAddress sdk.AccAddress, signingThreshold int64) *ValidatorVestingAccount {
endTime := startTime
for _, p := range periods {
endTime += p.Length
}
baseVestingAcc := &vestingtypes.BaseVestingAccount{
BaseAccount: baseAcc,
OriginalVesting: baseAcc.Coins,
EndTime: endTime,
}
pva := &vestingtypes.PeriodicVestingAccount{
BaseVestingAccount: baseVestingAcc,
StartTime: startTime,
VestingPeriods: periods,
}
var vestingPeriodProgress []VestingProgress
for i := 0; i < len(periods); i++ {
vestingPeriodProgress = append(vestingPeriodProgress, VestingProgress{false, false})
}
return &ValidatorVestingAccount{
PeriodicVestingAccount: pva,
ValidatorAddress: validatorAddress,
ReturnAddress: returnAddress,
SigningThreshold: signingThreshold,
CurrentPeriodProgress: CurrentPeriodProgress{0, 0},
VestingPeriodProgress: vestingPeriodProgress,
DebtAfterFailedVesting: sdk.NewCoins(),
}
}
// GetVestedCoins returns the total number of vested coins.
func (vva ValidatorVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins {
var vestedCoins sdk.Coins
if blockTime.Unix() <= vva.StartTime {
return vestedCoins
}
currentPeriodStartTime := vva.StartTime
numberPeriods := len(vva.VestingPeriods)
for i := 0; i < numberPeriods; i++ {
x := blockTime.Unix() - currentPeriodStartTime
if x >= vva.VestingPeriods[i].Length {
if vva.VestingPeriodProgress[i].PeriodComplete {
vestedCoins = vestedCoins.Add(vva.VestingPeriods[i].Amount)
}
currentPeriodStartTime += vva.VestingPeriods[i].Length
} else {
break
}
}
return vestedCoins
}
// GetFailedVestedCoins returns the total number of coins for which the vesting period has passed but the vesting threshold was not met.
func (vva ValidatorVestingAccount) GetFailedVestedCoins() sdk.Coins {
var failedVestedCoins sdk.Coins
numberPeriods := len(vva.VestingPeriods)
for i := 0; i < numberPeriods; i++ {
if vva.VestingPeriodProgress[i].PeriodComplete {
if !vva.VestingPeriodProgress[i].VestingSuccessful {
failedVestedCoins = failedVestedCoins.Add(vva.VestingPeriods[i].Amount)
}
} else {
break
}
}
return failedVestedCoins
}
// GetVestingCoins returns the total number of vesting coins. For validator vesting accounts, this excludes coins for which the vesting period has passed, but the vesting threshold was not met.
func (vva ValidatorVestingAccount) GetVestingCoins(blockTime time.Time) sdk.Coins {
return vva.OriginalVesting.Sub(vva.GetVestedCoins(blockTime))
}
// SpendableCoins returns the total number of spendable coins per denom for a
// periodic vesting account.
func (vva ValidatorVestingAccount) SpendableCoins(blockTime time.Time) sdk.Coins {
return vva.BaseVestingAccount.SpendableCoinsVestingAccount(vva.GetVestingCoins(blockTime))
}
// TrackDelegation tracks a desired delegation amount by setting the appropriate
// values for the amount of delegated vesting, delegated free, and reducing the
// overall amount of base coins.
func (vva *ValidatorVestingAccount) TrackDelegation(blockTime time.Time, amount sdk.Coins) {
vva.BaseVestingAccount.TrackDelegation(vva.GetVestingCoins(blockTime), amount)
}
// Validate checks for errors on the account fields
func (vva ValidatorVestingAccount) Validate() error {
if vva.SigningThreshold > 100 || vva.SigningThreshold < 0 {
return errors.New("signing threshold must be between 0 and 100")
}
if vva.ReturnAddress.Equals(vva.Address) {
return errors.New("return address cannot be the same as the account address")
}
return vva.PeriodicVestingAccount.Validate()
}
// MarshalYAML returns the YAML representation of an account.
func (vva ValidatorVestingAccount) MarshalYAML() (interface{}, error) {
var bs []byte
var err error
var pubkey string
if vva.PubKey != nil {
pubkey, err = sdk.Bech32ifyAccPub(vva.PubKey)
if err != nil {
return nil, err
}
}
bs, err = yaml.Marshal(struct {
Address sdk.AccAddress
Coins sdk.Coins
PubKey string
AccountNumber uint64
Sequence uint64
OriginalVesting sdk.Coins
DelegatedFree sdk.Coins
DelegatedVesting sdk.Coins
EndTime int64
StartTime int64
VestingPeriods vestingtypes.Periods
ValidatorAddress sdk.ConsAddress
ReturnAddress sdk.AccAddress
SigningThreshold int64
CurrentPeriodProgress CurrentPeriodProgress
VestingPeriodProgress []VestingProgress
DebtAfterFailedVesting sdk.Coins
}{
Address: vva.Address,
Coins: vva.Coins,
PubKey: pubkey,
AccountNumber: vva.AccountNumber,
Sequence: vva.Sequence,
OriginalVesting: vva.OriginalVesting,
DelegatedFree: vva.DelegatedFree,
DelegatedVesting: vva.DelegatedVesting,
EndTime: vva.EndTime,
StartTime: vva.StartTime,
VestingPeriods: vva.VestingPeriods,
ValidatorAddress: vva.ValidatorAddress,
ReturnAddress: vva.ReturnAddress,
SigningThreshold: vva.SigningThreshold,
CurrentPeriodProgress: vva.CurrentPeriodProgress,
VestingPeriodProgress: vva.VestingPeriodProgress,
DebtAfterFailedVesting: vva.DebtAfterFailedVesting,
})
if err != nil {
return nil, err
}
return string(bs), err
}

View File

@ -0,0 +1,428 @@
package types
import (
"errors"
"testing"
"time"
"github.com/stretchr/testify/require"
tmtime "github.com/tendermint/tendermint/types/time"
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/auth/vesting"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
)
var (
stakeDenom = "stake"
feeDenom = "fee"
)
func TestNewAccount(t *testing.T) {
now := tmtime.Now()
endTime := now.Add(24 * time.Hour).Unix()
periods := vestingtypes.Periods{
vestingtypes.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
}
testAddr := CreateTestAddrs(1)[0]
testPk := CreateTestPubKeys(1)[0]
testConsAddr := sdk.ConsAddress(testPk.Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
bacc := auth.NewBaseAccountWithAddress(testAddr)
bacc.SetCoins(origCoins)
bva, _ := vesting.NewBaseVestingAccount(&bacc, origCoins, endTime)
require.NotPanics(t, func() { NewValidatorVestingAccountRaw(bva, now.Unix(), periods, testConsAddr, nil, 90) })
vva := NewValidatorVestingAccountRaw(bva, now.Unix(), periods, testConsAddr, nil, 90)
vva.PubKey = testPk
_, err := vva.MarshalYAML()
require.NoError(t, err)
}
func TestGetVestedCoinsValidatorVestingAcc(t *testing.T) {
now := tmtime.Now()
periods := vestingtypes.Periods{
vestingtypes.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
}
testAddr := CreateTestAddrs(1)[0]
testPk := CreateTestPubKeys(1)[0]
testConsAddr := sdk.ConsAddress(testPk.Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
bacc := auth.NewBaseAccountWithAddress(testAddr)
bacc.SetCoins(origCoins)
vva := NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
// require no coins vested at the beginning of the vesting schedule
vestedCoins := vva.GetVestedCoins(now)
require.Nil(t, vestedCoins)
// require no coins vested during first vesting period
vestedCoins = vva.GetVestedCoins(now.Add(6 * time.Hour))
require.Nil(t, vestedCoins)
// require 50% of coins vested after successful period 1 vesting
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vestedCoins = vva.GetVestedCoins(now.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestedCoins)
// require 50% of coins vested after unsuccessful period 1 vesting
// NOTE: There is a fairly important semantic distinction here. It seems tempting to say that a failed vesting period should mean that 'GetVestedCoins' should not return those coins. While the point of a validator vesting account is to 'seize' or 'burn' unsuccessfully vested coins, they do in fact vest and become spendable. The intuition is that they have to be spendable in order for the bank keeper to allow us to send/burn them. If they were not vested, then a validator vesting account that failed all of it's vesting periods would never return/burn the coins because it would never have a spendable balance by which to do so. They way we prevent them from being spent in a way other than return/burn is by sending them in the BeginBlock and thus beating any other transfers that would otherwise occur.
vva.VestingPeriodProgress[0] = VestingProgress{true, false}
vestedCoins = vva.GetVestedCoins(now.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestedCoins)
// require period 2 coins don't vest until period is over
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
// even if the vesting period was somehow successful, should still only return 50% of coins as vested, since the second vesting period hasn't completed.
vva.VestingPeriodProgress[1] = VestingProgress{true, true}
vestedCoins = vva.GetVestedCoins(now.Add(15 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestedCoins)
// require 75% of coins vested after successful period 2
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vva.VestingPeriodProgress[1] = VestingProgress{true, true}
vestedCoins = vva.GetVestedCoins(now.Add(18 * time.Hour))
require.Equal(t,
sdk.Coins{
sdk.NewInt64Coin(feeDenom, 750), sdk.NewInt64Coin(stakeDenom, 75)}, vestedCoins)
// require 75% of coins vested after successful period 1 and unsuccessful period 2.
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vva.VestingPeriodProgress[1] = VestingProgress{true, false}
vestedCoins = vva.GetVestedCoins(now.Add(18 * time.Hour))
require.Equal(t,
sdk.Coins{
sdk.NewInt64Coin(feeDenom, 750), sdk.NewInt64Coin(stakeDenom, 75)}, vestedCoins)
// require 100% of coins vested after all periods complete successfully
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vva.VestingPeriodProgress[1] = VestingProgress{true, true}
vva.VestingPeriodProgress[2] = VestingProgress{true, true}
vestedCoins = vva.GetVestedCoins(now.Add(48 * time.Hour))
require.Equal(t, origCoins, vestedCoins)
// require 100% of coins vested after all periods complete unsuccessfully
vva.VestingPeriodProgress[0] = VestingProgress{true, false}
vva.VestingPeriodProgress[1] = VestingProgress{true, false}
vva.VestingPeriodProgress[2] = VestingProgress{true, false}
vestedCoins = vva.GetVestedCoins(now.Add(48 * time.Hour))
require.Equal(t, origCoins, vestedCoins)
}
func TestGetVestingCoinsValidatorVestingAcc(t *testing.T) {
now := tmtime.Now()
periods := vestingtypes.Periods{
vestingtypes.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
}
testAddr := CreateTestAddrs(1)[0]
testPk := CreateTestPubKeys(1)[0]
testConsAddr := sdk.ConsAddress(testPk.Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
bacc := auth.NewBaseAccountWithAddress(testAddr)
bacc.SetCoins(origCoins)
vva := NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
// require all coins vesting at the beginning of the vesting schedule
vestingCoins := vva.GetVestingCoins(now)
require.Equal(t, origCoins, vestingCoins)
// require all coins vesting during first vesting period
vestingCoins = vva.GetVestingCoins(now.Add(6 * time.Hour))
require.Equal(t, origCoins, vestingCoins)
// require 50% of coins vesting after successful period 1 vesting
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vestingCoins = vva.GetVestingCoins(now.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestingCoins)
// require 50% of coins vesting after unsuccessful period 1 vesting
vva.VestingPeriodProgress[0] = VestingProgress{true, false}
vestingCoins = vva.GetVestingCoins(now.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestingCoins)
// require period 2 coins still vesting until period is over
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
// should never happen, but still won't affect vesting balance
vva.VestingPeriodProgress[1] = VestingProgress{true, true}
vestingCoins = vva.GetVestingCoins(now.Add(15 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestingCoins)
// require 25% of coins vesting after successful period 2
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vva.VestingPeriodProgress[1] = VestingProgress{true, true}
vestingCoins = vva.GetVestingCoins(now.Add(18 * time.Hour))
require.Equal(t,
sdk.Coins{
sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}, vestingCoins)
// require 25% of coins vesting after successful period 1 and unsuccessful period 2
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vva.VestingPeriodProgress[1] = VestingProgress{true, false}
vestingCoins = vva.GetVestingCoins(now.Add(18 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}, vestingCoins)
// require no coins vesting after all periods complete successfully
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vva.VestingPeriodProgress[1] = VestingProgress{true, true}
vva.VestingPeriodProgress[2] = VestingProgress{true, true}
vestingCoins = vva.GetVestingCoins(now.Add(48 * time.Hour))
require.Nil(t, vestingCoins)
// require no coins vesting after all periods complete unsuccessfully
vva.VestingPeriodProgress[0] = VestingProgress{true, false}
vva.VestingPeriodProgress[1] = VestingProgress{true, false}
vva.VestingPeriodProgress[2] = VestingProgress{true, false}
vestingCoins = vva.GetVestingCoins(now.Add(48 * time.Hour))
require.Nil(t, vestingCoins)
}
func TestSpendableCoinsValidatorVestingAccount(t *testing.T) {
now := tmtime.Now()
periods := vestingtypes.Periods{
vestingtypes.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
}
testAddr := CreateTestAddrs(1)[0]
testPk := CreateTestPubKeys(1)[0]
testConsAddr := sdk.ConsAddress(testPk.Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
bacc := auth.NewBaseAccountWithAddress(testAddr)
bacc.SetCoins(origCoins)
vva := NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
// require that there exist no spendable coins at the beginning of the vesting schedule
spendableCoins := vva.SpendableCoins(now)
require.Nil(t, spendableCoins)
// require that all vested coins (50%) are spendable when period 1 completes successfully
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
spendableCoins = vva.SpendableCoins(now.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, spendableCoins)
// require that 50% of coins are spendable after period 1 completes unsuccessfully. See note above. The reason the coins are still 'spendable' is that we need to be able to transfer the coins to the return address/burn them. Making them not spendable means that it would be impossible to recover the debt for a validator vesting account for which all periods failed.
vva.VestingPeriodProgress[0] = VestingProgress{true, false}
spendableCoins = vva.SpendableCoins(now.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, spendableCoins)
// receive some coins
recvAmt := sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}
vva.SetCoins(vva.GetCoins().Add(recvAmt))
// require that all vested coins (50%) are spendable plus any received after period 1 completes successfully
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
spendableCoins = vva.SpendableCoins(now.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 100)}, spendableCoins)
// spend all spendable coins
vva.SetCoins(vva.GetCoins().Sub(spendableCoins))
// require that no more coins are spendable
spendableCoins = vva.SpendableCoins(now.Add(12 * time.Hour))
require.Nil(t, spendableCoins)
}
func TestGetFailedVestedCoins(t *testing.T) {
now := tmtime.Now()
periods := vestingtypes.Periods{
vestingtypes.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
}
testAddr := CreateTestAddrs(1)[0]
testPk := CreateTestPubKeys(1)[0]
testConsAddr := sdk.ConsAddress(testPk.Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
bacc := auth.NewBaseAccountWithAddress(testAddr)
bacc.SetCoins(origCoins)
vva := NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
vva.VestingPeriodProgress[0] = VestingProgress{true, false}
// require that period 1 coins are failed if the period completed unsucessfully.
require.Equal(t,
sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)},
vva.GetFailedVestedCoins(),
)
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
require.Equal(t,
sdk.Coins(nil),
vva.GetFailedVestedCoins(),
)
}
func TestTrackDelegationValidatorVestingAcc(t *testing.T) {
now := tmtime.Now()
periods := vestingtypes.Periods{
vestingtypes.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
}
testAddr := CreateTestAddrs(1)[0]
testPk := CreateTestPubKeys(1)[0]
testConsAddr := sdk.ConsAddress(testPk.Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
bacc := auth.NewBaseAccountWithAddress(testAddr)
bacc.SetCoins(origCoins)
vva := NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
vva.TrackDelegation(now, origCoins)
require.Equal(t, origCoins, vva.DelegatedVesting)
require.Nil(t, vva.DelegatedFree)
// all periods pass successfully
bacc.SetCoins(origCoins)
vva = NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vva.VestingPeriodProgress[1] = VestingProgress{true, true}
vva.VestingPeriodProgress[2] = VestingProgress{true, true}
vva.TrackDelegation(now.Add(48*time.Hour), origCoins)
// require all delegated coins are free
require.Equal(t, origCoins, vva.DelegatedFree)
require.Nil(t, vva.DelegatedVesting)
// require the ability to delegate all vesting coins (50%) and all vested coins (50%)
bacc.SetCoins(origCoins)
vva = NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
vva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)})
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}, vva.DelegatedVesting)
require.Nil(t, vva.DelegatedFree)
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)})
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}, vva.DelegatedVesting)
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}, vva.DelegatedFree)
// require no modifications when delegation amount is zero or not enough funds
bacc.SetCoins(origCoins)
vva = NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
require.Panics(t, func() {
vva.TrackDelegation(now.Add(24*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 1000000)})
})
require.Nil(t, vva.DelegatedVesting)
require.Nil(t, vva.DelegatedFree)
}
func TestTrackUndelegationPeriodicVestingAcc(t *testing.T) {
now := tmtime.Now()
periods := vestingtypes.Periods{
vestingtypes.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
}
testAddr := CreateTestAddrs(1)[0]
testPk := CreateTestPubKeys(1)[0]
testConsAddr := sdk.ConsAddress(testPk.Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
bacc := auth.NewBaseAccountWithAddress(testAddr)
bacc.SetCoins(origCoins)
vva := NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
// require ability to delegate then undelegate all coins.
vva.TrackDelegation(now, origCoins)
vva.TrackUndelegation(origCoins)
require.Nil(t, vva.DelegatedFree)
require.Nil(t, vva.DelegatedVesting)
// require the ability to delegate all coins after they have successfully vested
bacc.SetCoins(origCoins)
vva = NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vva.VestingPeriodProgress[1] = VestingProgress{true, true}
vva.VestingPeriodProgress[2] = VestingProgress{true, true}
vva.TrackDelegation(now.Add(24*time.Hour), origCoins)
vva.TrackUndelegation(origCoins)
require.Nil(t, vva.DelegatedFree)
require.Nil(t, vva.DelegatedVesting)
// require panic and no modifications when attempting to undelegate zero coins
bacc.SetCoins(origCoins)
vva = NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
require.Panics(t, func() {
vva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(stakeDenom, 0)})
})
require.Nil(t, vva.DelegatedFree)
require.Nil(t, vva.DelegatedVesting)
// successfuly vest period 1 and delegate to two validators
vva = NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
vva.VestingPeriodProgress[0] = VestingProgress{true, true}
vva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)})
vva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)})
// undelegate from one validator that got slashed 50%
vva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(stakeDenom, 25)})
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 25)}, vva.DelegatedFree)
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}, vva.DelegatedVesting)
// undelegate from the other validator that did not get slashed
vva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)})
require.Nil(t, vva.DelegatedFree)
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 25)}, vva.DelegatedVesting)
}
func TestGenesisAccountValidate(t *testing.T) {
now := tmtime.Now()
periods := vestingtypes.Periods{
vestingtypes.Period{Length: int64(12 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vestingtypes.Period{Length: int64(6 * 60 * 60), Amount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
}
testAddr := CreateTestAddrs(1)[0]
testPk := CreateTestPubKeys(1)[0]
testConsAddr := sdk.ConsAddress(testPk.Address())
origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)}
bacc := auth.NewBaseAccountWithAddress(testAddr)
bacc.SetCoins(origCoins)
tests := []struct {
name string
acc authexported.GenesisAccount
expErr error
}{
{
"valid validator vesting account",
NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 100),
nil,
},
{
"invalid signing threshold",
NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, -1),
errors.New("signing threshold must be between 0 and 100"),
},
{
"invalid signing threshold",
NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 120),
errors.New("signing threshold must be between 0 and 100"),
},
{
"invalid return address",
NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, testAddr, 90),
errors.New("return address cannot be the same as the account address"),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.acc.Validate()
require.Equal(t, tt.expErr, err)
})
}
}

View File

@ -0,0 +1,148 @@
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/kava-labs/kava/x/validator-vesting/internal/types"
"github.com/kava-labs/kava/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
accountKeeper types.AccountKeeper
}
// NewAppModule creates a new AppModule object
func NewAppModule(keeper Keeper, ak types.AccountKeeper) AppModule {
return AppModule{
AppModuleBasic: AppModuleBasic{},
AppModuleSimulation: AppModuleSimulation{},
keeper: keeper,
accountKeeper: ak,
}
}
// 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, am.accountKeeper, 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{}
}

View File

@ -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/kava-labs/kava/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))
}
}

View File

@ -0,0 +1,127 @@
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/kava-labs/kava/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 {
var val int
if simState.NumBonded <= 1 {
val = 0
} else {
val = simulation.RandIntBetween(simState.Rand, 0, int(simState.NumBonded)-1)
}
consAdd := getRandomValidatorConsAddr(simState, val)
// 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
}

View File

@ -0,0 +1,8 @@
# Concepts
The validator-vesting module is responsible for managing Validator Vesting Accounts, a vesting account for which the release of coins is tied to the validation of the blockchain. Validator Vesting Accounts implemnt the cosmos-sdk vesting account spec, which can be found [here](https://github.com/cosmos/cosmos-sdk/tree/master/x/auth/spec).
The main concept the Validator Vesting Account introduces is that of _conditional vesting_, or vesting accounts in which it is possible for some or all of the vesting coins to fail to vest. For Validator Vesting Accounts, vesting is broken down into user-specified __vesting periods__. Each vesting period specifies an amount of coins that could vest in that period, and how long the vesting period lasts.
For each vesting period, a __signing threshold__ is specified, which is the percentage of blocks that must be signed for the coins to successfully vest. After a period ends, coins that are successfully vested become freely spendable. Coins that do not successfuly vest are burned, or sent to an optional return address.

View File

@ -0,0 +1,25 @@
# State
## Validator Vesting Account type
Validator Vesting Accounts implement the `cosmos-sdk` vesting account spec and extend the `PeriodicVestingAccountType`:
```go
type ValidatorVestingAccount struct {
*vesting.PeriodicVestingAccount
ValidatorAddress sdk.ConsAddress // The validator address which will be used to check if blocks were signed
ReturnAddress sdk.AccAddress `json:"return_address" yaml:"return_address"` // The account where coins will be returned in the event of a failed vesting period
SigningThreshold int64 `json:"signing_threshold" yaml:"signing_threshold"` // The percentage of blocks, as an integer between 0 and 100, that must be signed each period for coins to successfully vest.
MissingSignCount []int64 `json:"missing_sign_count" yaml:"missing_sign_count"` // An array of two integers which track the number of blocks that were not signed during the current period and the total number of blocks which have passed during the current period, respectively.
VestingPeriodProgress [][]int `json:"vesting_period_progress" yaml:"vesting_period_progress"` //An 2d array with length equal to the number of vesting periods. After each period, the value at the first index of that period is updated with 1 to represent that the period is over. The value at the second index is updated to 0 for unsucessful vesting and 1 for successful vesting.
DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"` // The debt currently owed by the account. Debt accumulates in the event of unsuccessful vesting periods.
}
```
## Stores
There is one `KVStore` in `validator-vesting` which stores
* A mapping from each ValidatorVestingAccount `address` to `[]Byte{0}`
* A mapping from `previous_block_time_prefix` to `time.Time`
The use of `[]Byte{0}` value for each `address` key reflects that this module only accesses the store to get or iterate over keys, and does not require storing an value.

View File

@ -0,0 +1,45 @@
# Begin Block
At each `BeginBlock`, all validator vesting accounts are iterated over to update the status of the current vesting period. Note that the address of each account is retreived by iterating over the keys in the `validator-vesting` store, while the account objects are stored and accessed using the `auth` module's `AccountKeeper`. For each account, the block count is incremented, the missed sign count is incremented if the validator did not sign the block or was not found in the validator set. By comparing the blocktime of the current `BeginBlock`, with the value of `previousBlockTime` stored in the `validator-vesting` store, it is determined if the end of the current period has been reached. If the current period has ended, the `VestingPeriodProgress` field is updated to reflect if the coins for the ending period successfully vested or not. After updates are made regarding the status of the current vesting period, any outstanding debt on the account is attempted to be collected. If there is enough `SpendableBalance` on the account to cover the debt, coins are sent to the `ReturnAdress` or burned. If there is not enough `SpendableBalance` to cover the debt, all delegations of the account are `Unbonded`. Once those unbonding events reach maturity, the coins freed from the undonding will be used to cover the debt. Finally, the time of the previous block is stored in the validator vesting account keeper, which is used to determine when a period has ended.
```go
func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) {
previousBlockTime := time.Time{}
if ctx.BlockHeight() > 1 {
previousBlockTime = k.GetPreviousBlockTime(ctx)
}
currentBlockTime := ctx.BlockTime()
var voteInfos VoteInfos
voteInfos = req.LastCommitInfo.GetVotes()
validatorVestingKeys := k.GetAllAccountKeys(ctx)
for _, key := range validatorVestingKeys {
acc := k.GetAccountFromAuthKeeper(ctx, key)
if voteInfos.ContainsValidatorAddress(acc.ValidatorAddress) {
vote := voteInfos.MustFilterByValidatorAddress(acc.ValidatorAddress)
if !vote.SignedLastBlock {
// if the validator explicitly missed signing the block, increment the missing sign count
k.UpdateMissingSignCount(ctx, acc.GetAddress(), true)
} else {
k.UpdateMissingSignCount(ctx, acc.GetAddress(), false)
}
} else {
// if the validator was not a voting member of the validator set, increment the missing sign count
k.UpdateMissingSignCount(ctx, acc.GetAddress(), true)
}
// check if a period ended in the last block
endTimes := k.GetPeriodEndTimes(ctx, key)
for i, t := range endTimes {
if currentBlockTime.Unix() >= t && previousBlockTime.Unix() < t {
k.UpdateVestedCoinsProgress(ctx, key, i)
}
}
// handle any new/remaining debt on the account
k.HandleVestingDebt(ctx, key, currentBlockTime)
}
k.SetPreviousBlockTime(ctx, currentBlockTime)
}
```

View File

@ -0,0 +1,133 @@
package validatorvesting
// nolint
// DONTCOVER
import (
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
authexported "github.com/cosmos/cosmos-sdk/x/auth/exported"
"github.com/cosmos/cosmos-sdk/x/bank"
"github.com/cosmos/cosmos-sdk/x/mock"
"github.com/cosmos/cosmos-sdk/x/staking"
"github.com/cosmos/cosmos-sdk/x/supply"
supplyexported "github.com/cosmos/cosmos-sdk/x/supply/exported"
"github.com/kava-labs/kava/x/validator-vesting/internal/keeper"
"github.com/kava-labs/kava/x/validator-vesting/internal/types"
)
var (
valTokens = sdk.TokensFromConsensusPower(42)
initTokens = sdk.TokensFromConsensusPower(100000)
valCoins = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, valTokens))
initCoins = sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens))
)
type testInput struct {
mApp *mock.App
keeper keeper.Keeper
sk staking.Keeper
addrs []sdk.AccAddress
pubKeys []crypto.PubKey
privKeys []crypto.PrivKey
}
func getMockApp(t *testing.T, numGenAccs int, genState types.GenesisState, genAccs []authexported.Account) testInput {
mApp := mock.NewApp()
staking.RegisterCodec(mApp.Cdc)
types.RegisterCodec(mApp.Cdc)
supply.RegisterCodec(mApp.Cdc)
keyStaking := sdk.NewKVStoreKey(staking.StoreKey)
keyValidatorVesting := sdk.NewKVStoreKey(types.StoreKey)
keySupply := sdk.NewKVStoreKey(supply.StoreKey)
validatorVestingAcc := supply.NewEmptyModuleAccount(types.ModuleName, supply.Burner)
notBondedPool := supply.NewEmptyModuleAccount(staking.NotBondedPoolName, supply.Burner, supply.Staking)
bondPool := supply.NewEmptyModuleAccount(staking.BondedPoolName, supply.Burner, supply.Staking)
blacklistedAddrs := make(map[string]bool)
blacklistedAddrs[validatorVestingAcc.GetAddress().String()] = true
blacklistedAddrs[notBondedPool.GetAddress().String()] = true
blacklistedAddrs[bondPool.GetAddress().String()] = true
pk := mApp.ParamsKeeper
bk := bank.NewBaseKeeper(mApp.AccountKeeper, mApp.ParamsKeeper.Subspace(bank.DefaultParamspace), bank.DefaultCodespace, blacklistedAddrs)
maccPerms := map[string][]string{
types.ModuleName: {supply.Burner},
staking.NotBondedPoolName: {supply.Burner, supply.Staking},
staking.BondedPoolName: {supply.Burner, supply.Staking},
}
supplyKeeper := supply.NewKeeper(mApp.Cdc, keySupply, mApp.AccountKeeper, bk, maccPerms)
sk := staking.NewKeeper(
mApp.Cdc, keyStaking, supplyKeeper, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace,
)
keeper := keeper.NewKeeper(
mApp.Cdc, keyValidatorVesting, mApp.AccountKeeper, bk, supplyKeeper, sk)
mApp.SetBeginBlocker(getBeginBlocker(keeper))
mApp.SetInitChainer(getInitChainer(mApp, keeper, sk, supplyKeeper, genAccs, genState,
[]supplyexported.ModuleAccountI{validatorVestingAcc, notBondedPool, bondPool}))
require.NoError(t, mApp.CompleteSetup(keyStaking, keyValidatorVesting, keySupply))
var (
addrs []sdk.AccAddress
pubKeys []crypto.PubKey
privKeys []crypto.PrivKey
)
if genAccs == nil || len(genAccs) == 0 {
genAccs, addrs, pubKeys, privKeys = mock.CreateGenAccounts(numGenAccs, valCoins)
}
mock.SetGenesis(mApp, genAccs)
return testInput{mApp, keeper, sk, addrs, pubKeys, privKeys}
}
// gov and staking endblocker
func getBeginBlocker(keeper Keeper) sdk.BeginBlocker {
return func(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock {
BeginBlocker(ctx, req, keeper)
return abci.ResponseBeginBlock{}
}
}
// gov and staking initchainer
func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, supplyKeeper supply.Keeper, accs []authexported.Account, genState GenesisState,
blacklistedAddrs []supplyexported.ModuleAccountI) sdk.InitChainer {
return func(ctx sdk.Context, req abci.RequestInitChain) abci.ResponseInitChain {
mapp.InitChainer(ctx, req)
stakingGenesis := staking.DefaultGenesisState()
totalSupply := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, initTokens.MulRaw(int64(len(mapp.GenesisAccounts)))))
supplyKeeper.SetSupply(ctx, supply.NewSupply(totalSupply))
// set module accounts
for _, macc := range blacklistedAddrs {
supplyKeeper.SetModuleAccount(ctx, macc)
}
validators := staking.InitGenesis(ctx, stakingKeeper, mapp.AccountKeeper, supplyKeeper, stakingGenesis)
if genState.IsEmpty() {
InitGenesis(ctx, keeper, mapp.AccountKeeper, types.DefaultGenesisState())
} else {
InitGenesis(ctx, keeper, mapp.AccountKeeper, genState)
}
return abci.ResponseInitChain{
Validators: validators,
}
}
}