mirror of
https://github.com/0glabs/0g-chain.git
synced 2024-12-25 07:45:18 +00:00
Merge pull request #254 from Kava-Labs/kd-validator-vesting
Kd validator vesting
This commit is contained in:
commit
5672eccb99
34
app/app.go
34
app/app.go
@ -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{},
|
||||
@ -55,12 +59,13 @@ var (
|
||||
|
||||
// module account permissions
|
||||
mAccPerms = map[string][]string{
|
||||
auth.FeeCollectorName: nil,
|
||||
distr.ModuleName: nil,
|
||||
mint.ModuleName: {supply.Minter},
|
||||
staking.BondedPoolName: {supply.Burner, supply.Staking},
|
||||
staking.NotBondedPoolName: {supply.Burner, supply.Staking},
|
||||
gov.ModuleName: {supply.Burner},
|
||||
auth.FeeCollectorName: nil,
|
||||
distr.ModuleName: nil,
|
||||
mint.ModuleName: {supply.Minter},
|
||||
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)
|
||||
|
@ -3,26 +3,33 @@ 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"
|
||||
)
|
||||
|
||||
const (
|
||||
flagClientHome = "home-client"
|
||||
flagVestingStart = "vesting-start-time"
|
||||
flagVestingEnd = "vesting-end-time"
|
||||
flagVestingAmt = "vesting-amount"
|
||||
flagClientHome = "home-client"
|
||||
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
6
go.mod
@ -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
20
go.sum
@ -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=
|
||||
|
69
x/validator-vesting/abci.go
Normal file
69
x/validator-vesting/abci.go
Normal 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
|
||||
}
|
369
x/validator-vesting/abci_test.go
Normal file
369
x/validator-vesting/abci_test.go
Normal 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())
|
||||
}
|
64
x/validator-vesting/alias.go
Normal file
64
x/validator-vesting/alias.go
Normal 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
|
||||
)
|
26
x/validator-vesting/genesis.go
Normal file
26
x/validator-vesting/genesis.go
Normal 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}
|
||||
}
|
220
x/validator-vesting/internal/keeper/keeper.go
Normal file
220
x/validator-vesting/internal/keeper/keeper.go
Normal 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
|
||||
}
|
368
x/validator-vesting/internal/keeper/keeper_test.go
Normal file
368
x/validator-vesting/internal/keeper/keeper_test.go
Normal 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)
|
||||
}
|
248
x/validator-vesting/internal/keeper/test_common.go
Normal file
248
x/validator-vesting/internal/keeper/test_common.go
Normal 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)
|
||||
}
|
19
x/validator-vesting/internal/types/codec.go
Normal file
19
x/validator-vesting/internal/types/codec.go
Normal 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()
|
||||
}
|
38
x/validator-vesting/internal/types/expected_keepers.go
Normal file
38
x/validator-vesting/internal/types/expected_keepers.go
Normal 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)
|
||||
}
|
46
x/validator-vesting/internal/types/genesis.go
Normal file
46
x/validator-vesting/internal/types/genesis.go
Normal 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
|
||||
}
|
25
x/validator-vesting/internal/types/key.go
Normal file
25
x/validator-vesting/internal/types/key.go
Normal 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()...)
|
||||
}
|
86
x/validator-vesting/internal/types/test_common.go
Normal file
86
x/validator-vesting/internal/types/test_common.go
Normal 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
|
||||
}
|
252
x/validator-vesting/internal/types/validator_vesting_account.go
Normal file
252
x/validator-vesting/internal/types/validator_vesting_account.go
Normal 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
|
||||
}
|
@ -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)
|
||||
})
|
||||
}
|
||||
}
|
148
x/validator-vesting/module.go
Normal file
148
x/validator-vesting/module.go
Normal 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{}
|
||||
}
|
31
x/validator-vesting/simulation/decoder.go
Normal file
31
x/validator-vesting/simulation/decoder.go
Normal 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))
|
||||
}
|
||||
}
|
127
x/validator-vesting/simulation/genesis.go
Normal file
127
x/validator-vesting/simulation/genesis.go
Normal 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
|
||||
}
|
8
x/validator-vesting/spec/01_concepts.md
Normal file
8
x/validator-vesting/spec/01_concepts.md
Normal 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.
|
||||
|
25
x/validator-vesting/spec/02_state.md
Normal file
25
x/validator-vesting/spec/02_state.md
Normal 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.
|
45
x/validator-vesting/spec/03_begin_block.md
Normal file
45
x/validator-vesting/spec/03_begin_block.md
Normal 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)
|
||||
}
|
||||
```
|
||||
|
133
x/validator-vesting/test_common.go
Normal file
133
x/validator-vesting/test_common.go
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user