feat: add tests

This commit is contained in:
Kevin Davis 2019-09-27 14:48:57 -04:00
parent f7cb937d81
commit a6285e84fd
13 changed files with 754 additions and 194 deletions

View File

@ -16,12 +16,12 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper)
previousBlockTime = k.GetPreviousBlockTime(ctx)
}
currentBlockTime := req.Header.GetTime()
currentBlockTime := ctx.BlockTime()
var voteInfos VoteInfos
voteInfos = ctx.VoteInfos()
voteInfos = req.LastCommitInfo.GetVotes()
validatorVestingKeys := k.GetAllAccountKeys(ctx)
for _, key := range validatorVestingKeys {
acc := k.GetAccountFromAuthKeeper(ctx, key)
acc := k.GetAccountFromAuthKeeper(ctx, key[1:])
if voteInfos.ContainsValidatorAddress(acc.ValidatorAddress) {
vote := voteInfos.MustFilterByValidatorAddress(acc.ValidatorAddress)
if !vote.SignedLastBlock {
@ -36,14 +36,15 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper)
}
// check if a period ended in the last block
endTimes := k.GetPeriodEndTimes(ctx, key)
endTimes := k.GetPeriodEndTimes(ctx, key[1:])
for i, t := range endTimes {
if currentBlockTime.Unix() >= t && previousBlockTime.Unix() < t {
k.UpdateVestedCoinsProgress(ctx, key, i)
k.UpdateVestedCoinsProgress(ctx, key[1:], i)
}
}
// handle any new/remaining debt on the account
k.HandleVestingDebt(ctx, key, currentBlockTime)
k.HandleVestingDebt(ctx, key[1:], currentBlockTime)
}
k.SetPreviousBlockTime(ctx, currentBlockTime)
}

View File

@ -0,0 +1,237 @@
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/cosmos/cosmos-sdk/x/validator-vesting/internal/keeper"
)
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(0)
blockTime := now
addHour := func(t time.Time) time.Time { return t.Add(1 * time.Hour) }
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,
}},
},
}
BeginBlocker(ctx, req, vvk)
height++
blockTime = addHour(blockTime)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, []int64{0, 1}, vva.MissingSignCount)
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,
}},
},
}
BeginBlocker(ctx, req, vvk)
height++
blockTime = addHour(blockTime)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, []int64{1, 2}, vva.MissingSignCount)
// 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)
height++
blockTime = addHour(blockTime)
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
require.Equal(t, []int64{2, 3}, vva.MissingSignCount)
}
func TestBeginBlockerSuccessfulPeriod(t *testing.T) {
height := int64(0)
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 == 11 {
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
// require that missing sign count is set back to zero after the period increments.
require.Equal(t, []int64{0, 0}, vva.MissingSignCount)
}
}
vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address)
// t.Log(vva.MarshalYAML())
require.Equal(t, [][]int{[]int{1, 1}, []int{0, 0}, []int{0, 0}}, vva.VestingPeriodProgress)
}
func TestBeginBlockerUnsuccessfulPeriod(t *testing.T) {
height := int64(0)
now := tmtime.Now()
blockTime := now
numBlocks := int64(12)
addHour := func(t time.Time) time.Time { return t.Add(1 * time.Hour) }
ctx, ak, _, stakingKeeper, _, vvk := keeper.CreateTestInput(t, false, 1000)
keeper.CreateValidators(ctx, stakingKeeper, []int64{5, 5, 5})
vva := keeper.ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
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, [][]int{[]int{1, 0}, []int{0, 0}, []int{0, 0}}, 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)
}

View File

@ -1,6 +1,7 @@
// nolint
package validatorvesting
// nolint
// DONTCOVER
import (
"github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/keeper"
"github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types"
@ -18,6 +19,7 @@ var (
DefaultGenesisState = types.DefaultGenesisState
RegisterCodec = types.RegisterCodec
ValidatorVestingAccountPrefix = types.ValidatorVestingAccountPrefix
BlocktimeKey = types.BlocktimeKey
ValidatorVestingAccountKey = types.ValidatorVestingAccountKey
NewKeeper = keeper.NewKeeper
)

View File

@ -7,7 +7,7 @@ import (
)
// InitGenesis stores the account address of each ValidatorVestingAccount in the validator vesting keeper, for faster lookup.
// CONTRACT: Accounts created by the account keeper must have already been initialized/created
// CONTRACT: Accounts created by the account keeper must have already been initialized/created by AccountKeeper
func InitGenesis(ctx sdk.Context, keeper Keeper, accountKeeper types.AccountKeeper, data GenesisState) {
data.Accounts = auth.SanitizeGenesisAccounts(data.Accounts)
for _, a := range data.Accounts {

View File

@ -11,11 +11,6 @@ import (
"github.com/tendermint/tendermint/libs/log"
)
var (
// BlocktimeKey key for the time of the previous block
BlocktimeKey = []byte{0x00}
)
// Keeper of the validatorvesting store
type Keeper struct {
storeKey sdk.StoreKey
@ -47,7 +42,7 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {
// 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(BlocktimeKey)
b := store.Get(types.BlocktimeKey)
if b == nil {
panic("Previous block time not set")
}
@ -59,78 +54,7 @@ func (k Keeper) GetPreviousBlockTime(ctx sdk.Context) (blockTime time.Time) {
func (k Keeper) SetPreviousBlockTime(ctx sdk.Context, blockTime time.Time) {
store := ctx.KVStore(k.storeKey)
b := k.cdc.MustMarshalBinaryLengthPrefixed(blockTime)
store.Set(BlocktimeKey, b)
}
// 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.MissingSignCount[0]++
}
vv.MissingSignCount[1]++
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)
threshold := sdk.NewDec(vv.SigningThreshold)
blocksMissed := sdk.NewDec(vv.MissingSignCount[0])
blockCount := sdk.NewDec(vv.MissingSignCount[1])
blocksSigned := blockCount.Sub(blocksMissed)
percentageBlocksSigned := blocksSigned.Quo(blockCount).Mul(sdk.NewDec(100))
successfulVest := percentageBlocksSigned.GTE(threshold)
if successfulVest {
vv.VestingPeriodProgress[period] = 1
} else {
vv.VestingPeriodProgress[period] = 0
notVestedTokens := vv.VestingPeriods[period].VestingAmount
// add the tokens that did not vest to DebtAfterFailedVesting
vv.DebtAfterFailedVesting = vv.DebtAfterFailedVesting.Add(notVestedTokens)
}
// reset the number of missed blocks and total number of blocks in the period to zero
vv.MissingSignCount = []int64{0, 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)
remainingDebt := !vv.DebtAfterFailedVesting.IsZero()
if remainingDebt {
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)
}
}
vv.DebtAfterFailedVesting = sdk.NewCoins()
k.ak.SetAccount(ctx, vv)
} 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
})
}
}
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.
@ -175,6 +99,84 @@ func (k Keeper) GetAccountFromAuthKeeper(ctx sdk.Context, addr sdk.AccAddress) *
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.MissingSignCount[0]++
}
vv.MissingSignCount[1]++
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)
threshold := sdk.NewDec(vv.SigningThreshold)
blocksMissed := sdk.NewDec(vv.MissingSignCount[0])
blockCount := sdk.NewDec(vv.MissingSignCount[1])
var successfulVest bool
if blockCount.IsZero() {
successfulVest = true
} else {
blocksSigned := blockCount.Sub(blocksMissed)
percentageBlocksSigned := blocksSigned.Quo(blockCount).Mul(sdk.NewDec(100))
successfulVest = percentageBlocksSigned.GTE(threshold)
}
if successfulVest {
vv.VestingPeriodProgress[period][1] = 1
} else {
vv.VestingPeriodProgress[period][1] = 0
notVestedTokens := vv.VestingPeriods[period].VestingAmount
// add the tokens that did not vest to DebtAfterFailedVesting
vv.DebtAfterFailedVesting = vv.DebtAfterFailedVesting.Add(notVestedTokens)
}
vv.VestingPeriodProgress[period][0] = 1
// reset the number of missed blocks and total number of blocks in the period to zero
vv.MissingSignCount = []int64{0, 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)
remainingDebt := !vv.DebtAfterFailedVesting.IsZero()
if remainingDebt {
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)
}
}
vv.DebtAfterFailedVesting = sdk.NewCoins()
k.ak.SetAccount(ctx, vv)
} 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
})
}
}
}
// 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

View File

@ -1,14 +1,7 @@
package keeper
// TODO
// 1. Test that a signed block by the ValidatorAddress increases the blocks counter, but not the missed blocks counter
// 2. Test that an unsigned block increass both the blocks counter and the missed blocks counter
// 3. Test that the previous block time gets updated at the end of each begin block
// 4. Test that the block before a pivital block doesn't reset the period
// 5. Test that a pivotal block results in coins being vested successfully if the treshold is met
// 6. Test that a pivotal block results in HandleVestingDebt
import (
"bytes"
"testing"
"time"
@ -42,6 +35,28 @@ func TestGetSetValidatorVestingAccounts(t *testing.T) {
// 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[1:]) })
}
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, keys[0]) {
ikeys = append(ikeys, accountKey)
return true
}
return false
})
require.Equal(t, 1, len(ikeys))
}
func TestGetSetPreviousBlock(t *testing.T) {
@ -60,6 +75,26 @@ func TestGetSetPreviousBlock(t *testing.T) {
}
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 TestSetMissingSignCount(t *testing.T) {
ctx, ak, _, _, _, keeper := CreateTestInput(t, false, 1000)
@ -91,7 +126,7 @@ func TestUpdateVestedCoinsProgress(t *testing.T) {
ak.SetAccount(ctx, vva)
// require all vesting period tracking variables to be zero after validator vesting account is initialized
require.Equal(t, []int{0, 0, 0}, vva.VestingPeriodProgress)
require.Equal(t, [][]int{{0, 0}, {0, 0}, {0, 0}}, vva.VestingPeriodProgress)
// period 0 passes with all blocks signed
vva.MissingSignCount[0] = 0
@ -103,13 +138,31 @@ func TestUpdateVestedCoinsProgress(t *testing.T) {
// require that debt is zero
require.Equal(t, sdk.Coins(nil), vva.DebtAfterFailedVesting)
// require that the first vesting progress variable is 1
require.Equal(t, []int{1, 0, 0}, vva.VestingPeriodProgress)
require.Equal(t, [][]int{{1, 1}, {0, 0}, {0, 0}}, vva.VestingPeriodProgress)
// require that the missing block counter has reset
require.Equal(t, []int64{0, 0}, vva.MissingSignCount)
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.MissingSignCount[0] = 0
vva.MissingSignCount[1] = 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 1
require.Equal(t, [][]int{{1, 1}, {0, 0}, {0, 0}}, vva.VestingPeriodProgress)
// require that the missing block counter has reset
require.Equal(t, []int64{0, 0}, vva.MissingSignCount)
vva = ValidatorVestingTestAccount()
// Add the validator vesting account to the auth store
ak.SetAccount(ctx, vva)
// period 0 passes with 50% of blocks signed (below threshold)
vva.MissingSignCount[0] = 50
@ -119,14 +172,16 @@ func TestUpdateVestedCoinsProgress(t *testing.T) {
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 0
require.Equal(t, []int{0, 0, 0}, vva.VestingPeriodProgress)
// require that the first vesting progress variable is {1,0}
require.Equal(t, [][]int{{1, 0}, {0, 0}, {0, 0}}, vva.VestingPeriodProgress)
// require that the missing block counter has reset
require.Equal(t, []int64{0, 0}, vva.MissingSignCount)
}
func TestHandleVestingDebtForcedUnbond(t *testing.T) {
ctx, ak, _, stakingKeeper, _, keeper := CreateTestInput(t, false, 1000)
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
@ -144,13 +199,21 @@ func TestHandleVestingDebtForcedUnbond(t *testing.T) {
require.Nil(t, vva.DelegatedFree)
require.Nil(t, vva.GetCoins())
}
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)
vva := ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
delTokens := sdk.TokensFromConsensusPower(30)
val1, found := stakingKeeper.GetValidator(ctx, valOpAddr1)
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)
@ -158,6 +221,10 @@ func TestHandleVestingDebtForcedUnbond(t *testing.T) {
_ = 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) {
@ -173,12 +240,13 @@ func TestHandleVestingDebtForcedUnbond(t *testing.T) {
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))
// _ = staking.EndBlocker(ctx, stakingKeeper)
delegations = 0
stakingKeeper.IterateDelegations(ctx, vva.Address, func(index int64, d stakingexported.DelegationI) (stop bool) {
delegations++
@ -195,17 +263,14 @@ func TestHandleVestingDebtBurn(t *testing.T) {
vva := ValidatorVestingDelegatorTestAccount(now)
ak.SetAccount(ctx, vva)
delTokens := sdk.TokensFromConsensusPower(30)
val1, found := stakingKeeper.GetValidator(ctx, valOpAddr1)
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)
// receive some coins so that the debt will be covered by liquid balance
recvAmt := sdk.Coins{sdk.NewInt64Coin(stakeDenom, 30000000)}
vva.SetCoins(vva.GetCoins().Add(recvAmt))
// period 0 passes and the threshold is not met
vva.MissingSignCount[0] = 50
vva.MissingSignCount[1] = 100
@ -242,17 +307,13 @@ func TestHandleVestingDebtReturn(t *testing.T) {
vva.ReturnAddress = TestAddrs[2]
ak.SetAccount(ctx, vva)
delTokens := sdk.TokensFromConsensusPower(30)
val1, found := stakingKeeper.GetValidator(ctx, valOpAddr1)
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)
// receive some coins so that the debt will be covered by liquid balance
recvAmt := sdk.Coins{sdk.NewInt64Coin(stakeDenom, 30000000)}
vva.SetCoins(vva.GetCoins().Add(recvAmt))
// period 0 passes and the threshold is not met
vva.MissingSignCount[0] = 50
vva.MissingSignCount[1] = 100

View File

@ -37,22 +37,22 @@ var (
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())
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())
valConsPk1 = ed25519.GenPrivKey().PubKey()
valConsPk2 = ed25519.GenPrivKey().PubKey()
valConsPk3 = ed25519.GenPrivKey().PubKey()
valConsAddr1 = sdk.ConsAddress(valConsPk1.Address())
valConsAddr2 = sdk.ConsAddress(valConsPk2.Address())
valConsAddr3 = sdk.ConsAddress(valConsPk3.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
@ -177,6 +177,32 @@ func ValidatorVestingTestAccount() *types.ValidatorVestingAccount {
return vva
}
func ValidatorVestingTestAccounts(numAccounts int) []*types.ValidatorVestingAccount {
now := tmtime.Now()
periods := vesting.VestingPeriods{
vesting.VestingPeriod{PeriodLength: int64(12 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vesting.VestingPeriod{PeriodLength: int64(6 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vesting.VestingPeriod{PeriodLength: int64(6 * 60 * 60), VestingAmount: 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.VestingPeriods{
vesting.VestingPeriod{PeriodLength: int64(12 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(stakeDenom, 30000000)}},
@ -198,9 +224,9 @@ func ValidatorVestingDelegatorTestAccount(startTime time.Time) *types.ValidatorV
}
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{})
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)

View File

@ -1,6 +1,8 @@
package types
import (
"bytes"
"github.com/cosmos/cosmos-sdk/x/auth/exported"
)
@ -20,3 +22,15 @@ func NewGenesisState(accounts exported.GenesisAccounts) GenesisState {
func DefaultGenesisState() GenesisState {
return NewGenesisState(exported.GenesisAccounts{})
}
// 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{})
}

View File

@ -13,11 +13,13 @@ const (
)
var (
// BlocktimeKey key for the time of the previous block
BlocktimeKey = []byte{0x00}
// ValidatorVestingAccountPrefix store prefix for validator vesting accounts
ValidatorVestingAccountPrefix = []byte{0x01}
ValidatorVestingAccountPrefix = []byte{0x01}
)
// ValidatorVestingAccountKey returns the account address bytes prefixed by ValidatorVestingAccountPrefix
func ValidatorVestingAccountKey(addr sdk.AccAddress) []byte {
return append(ValidatorVestingAccountPrefix, addr.Bytes()...)
}
}

View File

@ -1,4 +1,8 @@
package types // noalias
package types
// nolint:deadcode unused
// DONTCOVER
// noalias
import (
"bytes"

View File

@ -36,7 +36,7 @@ type ValidatorVestingAccount struct {
ReturnAddress sdk.AccAddress `json:"return_address" yaml:"return_address"`
SigningThreshold int64 `json:"signing_threshold" yaml:"signing_threshold"`
MissingSignCount []int64 `json:"missing_sign_count" yaml:"missing_sign_count"`
VestingPeriodProgress []int `json:"vesting_period_progress" yaml:"vesting_period_progress"`
VestingPeriodProgress [][]int `json:"vesting_period_progress" yaml:"vesting_period_progress"`
DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"`
}
@ -51,7 +51,10 @@ func NewValidatorVestingAccountRaw(bva *vesting.BaseVestingAccount,
ContinuousVestingAccount: cva,
VestingPeriods: periods,
}
var vestingPeriodProgress = make([]int, len(periods))
var vestingPeriodProgress = make([][]int, len(periods))
for i := range vestingPeriodProgress {
vestingPeriodProgress[i] = make([]int, 2)
}
return &ValidatorVestingAccount{
PeriodicVestingAccount: pva,
@ -84,7 +87,10 @@ func NewValidatorVestingAccount(baseAcc *auth.BaseAccount, startTime int64, peri
ContinuousVestingAccount: cva,
VestingPeriods: periods,
}
var vestingPeriodProgress = make([]int, len(periods))
var vestingPeriodProgress = make([][]int, len(periods))
for i := range vestingPeriodProgress {
vestingPeriodProgress[i] = make([]int, 2)
}
debt := sdk.NewCoins()
@ -110,8 +116,8 @@ func (vva ValidatorVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins
for i := 0; i < numberPeriods; i++ {
x := blockTime.Unix() - currentPeriodStartTime
if x >= vva.VestingPeriods[i].PeriodLength {
vestedSuccess := vva.VestingPeriodProgress[i] > 0
if vestedSuccess {
vestingComplete := vva.VestingPeriodProgress[i][0] == 1
if vestingComplete {
vestedCoins = vestedCoins.Add(vva.VestingPeriods[i].VestingAmount)
}
currentPeriodStartTime += vva.VestingPeriods[i].PeriodLength
@ -124,21 +130,15 @@ func (vva ValidatorVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins
}
// 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(blockTime time.Time) sdk.Coins {
func (vva ValidatorVestingAccount) GetFailedVestedCoins() sdk.Coins {
var failedVestedCoins sdk.Coins
if blockTime.Unix() <= vva.StartTime {
return failedVestedCoins
}
currentPeriodStartTime := vva.StartTime
numberPeriods := len(vva.VestingPeriods)
for i := 0; i < numberPeriods; i++ {
x := blockTime.Unix() - currentPeriodStartTime
if x >= vva.VestingPeriods[i].PeriodLength {
vestedFailure := vva.VestingPeriodProgress[i] == 0
if vva.VestingPeriodProgress[i][0] == 1 {
vestedFailure := vva.VestingPeriodProgress[i][1] == 0
if vestedFailure {
failedVestedCoins = failedVestedCoins.Add(vva.VestingPeriods[i].VestingAmount)
}
currentPeriodStartTime += vva.VestingPeriods[i].PeriodLength
} else {
break
}
@ -148,13 +148,13 @@ func (vva ValidatorVestingAccount) GetFailedVestedCoins(blockTime time.Time) sdk
// 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)).Sub(vva.GetFailedVestedCoins(blockTime))
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.SpendableCoinsFromVestingCoins(vva.GetVestingCoins(blockTime)).Sub(vva.DebtAfterFailedVesting)
return vva.BaseVestingAccount.SpendableCoinsFromVestingCoins(vva.GetVestingCoins(blockTime))
}
// TrackDelegation tracks a desired delegation amount by setting the appropriate
@ -204,7 +204,7 @@ func (vva ValidatorVestingAccount) MarshalYAML() (interface{}, error) {
ReturnAddress sdk.AccAddress
SigningThreshold int64
MissingSignCount []int64
VestingPeriodProgress []int
VestingPeriodProgress [][]int
DebtAfterFailedVesting sdk.Coins
}{
Address: vva.Address,

View File

@ -19,6 +19,29 @@ var (
feeDenom = "fee"
)
func TestNewAccount(t *testing.T) {
now := tmtime.Now()
endTime := now.Add(24 * time.Hour).Unix()
periods := vesting.VestingPeriods{
vesting.VestingPeriod{PeriodLength: int64(12 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vesting.VestingPeriod{PeriodLength: int64(6 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vesting.VestingPeriod{PeriodLength: int64(6 * 60 * 60), VestingAmount: 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 := vesting.VestingPeriods{
@ -44,40 +67,51 @@ func TestGetVestedCoinsValidatorVestingAcc(t *testing.T) {
require.Nil(t, vestedCoins)
// require 50% of coins vested after successful period 1 vesting
vva.VestingPeriodProgress[0] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
vestedCoins = vva.GetVestedCoins(now.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestedCoins)
// require no coins vested after unsuccessful period 1 vesting
vva.VestingPeriodProgress[0] = 0
// 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] = []int{1, 0}
vestedCoins = vva.GetVestedCoins(now.Add(12 * time.Hour))
require.Nil(t, vestedCoins)
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] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
// 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] = 1
vva.VestingPeriodProgress[1] = []int{1, 1}
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] = 1
vva.VestingPeriodProgress[1] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
vva.VestingPeriodProgress[1] = []int{1, 1}
vestedCoins = vva.GetVestedCoins(now.Add(18 * time.Hour))
require.Equal(t,
sdk.Coins{
sdk.NewInt64Coin(feeDenom, 750), sdk.NewInt64Coin(stakeDenom, 75)}, vestedCoins)
// require 50% of coins vested after successful period 1 and unsuccessful period 2
vva.VestingPeriodProgress[0] = 1
vva.VestingPeriodProgress[1] = 0
// require 75% of coins vested after successful period 1 and unsuccessful period 2.
vva.VestingPeriodProgress[0] = []int{1, 1}
vva.VestingPeriodProgress[1] = []int{1, 0}
vestedCoins = vva.GetVestedCoins(now.Add(18 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestedCoins)
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] = 1
vva.VestingPeriodProgress[1] = 1
vva.VestingPeriodProgress[2] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
vva.VestingPeriodProgress[1] = []int{1, 1}
vva.VestingPeriodProgress[2] = []int{1, 1}
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] = []int{1, 0}
vva.VestingPeriodProgress[1] = []int{1, 0}
vva.VestingPeriodProgress[2] = []int{1, 0}
vestedCoins = vva.GetVestedCoins(now.Add(48 * time.Hour))
require.Equal(t, origCoins, vestedCoins)
@ -108,48 +142,48 @@ func TestGetVestingCoinsValidatorVestingAcc(t *testing.T) {
require.Equal(t, origCoins, vestingCoins)
// require 50% of coins vesting after successful period 1 vesting
vva.VestingPeriodProgress[0] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
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] = 0
vva.VestingPeriodProgress[0] = []int{1, 0}
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] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
// should never happen, but still won't affect vesting balance
vva.VestingPeriodProgress[1] = 1
vva.VestingPeriodProgress[1] = []int{1, 1}
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] = 1
vva.VestingPeriodProgress[1] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
vva.VestingPeriodProgress[1] = []int{1, 1}
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] = 1
vva.VestingPeriodProgress[1] = 0
vva.VestingPeriodProgress[0] = []int{1, 1}
vva.VestingPeriodProgress[1] = []int{1, 1}
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] = 1
vva.VestingPeriodProgress[1] = 1
vva.VestingPeriodProgress[2] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
vva.VestingPeriodProgress[1] = []int{1, 1}
vva.VestingPeriodProgress[2] = []int{1, 1}
vestingCoins = vva.GetVestingCoins(now.Add(48 * time.Hour))
require.Nil(t, vestingCoins)
// require no coins vesting after all periods complete unsuccessfully
vva.VestingPeriodProgress[0] = 0
vva.VestingPeriodProgress[1] = 0
vva.VestingPeriodProgress[2] = 0
vva.VestingPeriodProgress[0] = []int{1, 0}
vva.VestingPeriodProgress[1] = []int{1, 0}
vva.VestingPeriodProgress[2] = []int{1, 0}
vestingCoins = vva.GetVestingCoins(now.Add(48 * time.Hour))
require.Nil(t, vestingCoins)
@ -176,21 +210,21 @@ func TestSpendableCoinsValidatorVestingAccount(t *testing.T) {
require.Nil(t, spendableCoins)
// require that all vested coins (50%) are spendable when period 1 completes successfully
vva.VestingPeriodProgress[0] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
spendableCoins = vva.SpendableCoins(now.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, spendableCoins)
// require that there exist no spendable coins after period 1 completes unsuccessfully.
vva.VestingPeriodProgress[0] = 0
spendableCoins = vva.SpendableCoins(now)
require.Nil(t, 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] = []int{1, 0}
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 unsuccessfully
vva.VestingPeriodProgress[0] = 1
// require that all vested coins (50%) are spendable plus any received after period 1 completes successfully
vva.VestingPeriodProgress[0] = []int{1, 1}
spendableCoins = vva.SpendableCoins(now.Add(12 * time.Hour))
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 100)}, spendableCoins)
@ -202,6 +236,36 @@ func TestSpendableCoinsValidatorVestingAccount(t *testing.T) {
require.Nil(t, spendableCoins)
}
func TestGetFailedVestedCoins(t *testing.T) {
now := tmtime.Now()
periods := vesting.VestingPeriods{
vesting.VestingPeriod{PeriodLength: int64(12 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}},
vesting.VestingPeriod{PeriodLength: int64(6 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(feeDenom, 250), sdk.NewInt64Coin(stakeDenom, 25)}},
vesting.VestingPeriod{PeriodLength: int64(6 * 60 * 60), VestingAmount: 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] = []int{1, 0}
// 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] = []int{1, 1}
require.Equal(t,
sdk.Coins(nil),
vva.GetFailedVestedCoins(),
)
}
func TestTrackDelegationValidatorVestingAcc(t *testing.T) {
now := tmtime.Now()
periods := vesting.VestingPeriods{
@ -222,6 +286,20 @@ func TestTrackDelegationValidatorVestingAcc(t *testing.T) {
require.Equal(t, origCoins, vva.DelegatedVesting)
require.Nil(t, vva.DelegatedFree)
require.Nil(t, vva.GetCoins())
require.Nil(t, vva.SpendableCoins(now))
// all periods pass successfully
bacc.SetCoins(origCoins)
vva = NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
vva.VestingPeriodProgress[0] = []int{1, 1}
vva.VestingPeriodProgress[1] = []int{1, 1}
vva.VestingPeriodProgress[2] = []int{1, 1}
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.Nil(t, vva.GetCoins())
require.Nil(t, vva.SpendableCoins(now.Add(48*time.Hour)))
// require the ability to delegate all vesting coins (50%) and all vested coins (50%)
bacc.SetCoins(origCoins)
@ -230,7 +308,7 @@ func TestTrackDelegationValidatorVestingAcc(t *testing.T) {
require.Equal(t, sdk.Coins{sdk.NewInt64Coin(stakeDenom, 50)}, vva.DelegatedVesting)
require.Nil(t, vva.DelegatedFree)
vva.VestingPeriodProgress[0] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
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)
@ -273,9 +351,9 @@ func TestTrackUndelegationPeriodicVestingAcc(t *testing.T) {
// 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] = 1
vva.VestingPeriodProgress[1] = 1
vva.VestingPeriodProgress[2] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
vva.VestingPeriodProgress[1] = []int{1, 1}
vva.VestingPeriodProgress[2] = []int{1, 1}
vva.TrackDelegation(now.Add(24*time.Hour), origCoins)
vva.TrackUndelegation(origCoins)
require.Nil(t, vva.DelegatedFree)
@ -294,7 +372,7 @@ func TestTrackUndelegationPeriodicVestingAcc(t *testing.T) {
// successfuly vest period 1 and delegate to two validators
vva = NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90)
vva.VestingPeriodProgress[0] = 1
vva.VestingPeriodProgress[0] = []int{1, 1}
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)})

View File

@ -0,0 +1,133 @@
package validatorvesting
// nolint
// DONTCOVER
import (
"testing"
"github.com/stretchr/testify/require"
abci "github.com/tendermint/tendermint/abci/types"
"github.com/tendermint/tendermint/crypto"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/cosmos-sdk/x/auth"
"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/cosmos/cosmos-sdk/x/validator-vesting/internal/keeper"
"github.com/cosmos/cosmos-sdk/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 []auth.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 []auth.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,
}
}
}