From 918a43e7ab5053f6afc11c6c652e5660638105e0 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 23 Sep 2019 14:23:00 -0400 Subject: [PATCH 01/13] feat: validator vesting accounts --- x/validator-vesting/abci.go | 73 ++++ x/validator-vesting/alias.go | 22 ++ x/validator-vesting/genesis.go | 19 + x/validator-vesting/internal/keeper/keeper.go | 188 +++++++++ .../internal/keeper/keeper_test.go | 282 ++++++++++++++ .../internal/keeper/test_common.go | 220 +++++++++++ x/validator-vesting/internal/types/codec.go | 19 + .../internal/types/expected_keepers.go | 38 ++ x/validator-vesting/internal/types/genesis.go | 22 ++ x/validator-vesting/internal/types/key.go | 23 ++ .../internal/types/test_common.go | 82 ++++ .../types/validator_vesting_account.go | 231 +++++++++++ .../types/validator_vesting_account_test.go | 360 ++++++++++++++++++ 13 files changed, 1579 insertions(+) create mode 100644 x/validator-vesting/abci.go create mode 100644 x/validator-vesting/alias.go create mode 100644 x/validator-vesting/genesis.go create mode 100644 x/validator-vesting/internal/keeper/keeper.go create mode 100644 x/validator-vesting/internal/keeper/keeper_test.go create mode 100644 x/validator-vesting/internal/keeper/test_common.go create mode 100644 x/validator-vesting/internal/types/codec.go create mode 100644 x/validator-vesting/internal/types/expected_keepers.go create mode 100644 x/validator-vesting/internal/types/genesis.go create mode 100644 x/validator-vesting/internal/types/key.go create mode 100644 x/validator-vesting/internal/types/test_common.go create mode 100644 x/validator-vesting/internal/types/validator_vesting_account.go create mode 100644 x/validator-vesting/internal/types/validator_vesting_account_test.go diff --git a/x/validator-vesting/abci.go b/x/validator-vesting/abci.go new file mode 100644 index 00000000..c38dcaab --- /dev/null +++ b/x/validator-vesting/abci.go @@ -0,0 +1,73 @@ +package validatorvesting + +import ( + "bytes" + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/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 := time.Time{} + if ctx.BlockHeight() > 1 { + previousBlockTime = k.GetPreviousBlockTime(ctx) + } + + currentBlockTime := req.Header.GetTime() + var voteInfos VoteInfos + voteInfos = ctx.VoteInfos() + 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) + } + k.HandleVestingDebt(ctx, key, currentBlockTime) + } + } + k.SetPreviousBlockTime(ctx, currentBlockTime) +} + +// VoteInfos an array of abci.VoteInfo +type VoteInfos []abci.VoteInfo + +// ContainsValidatorAddress returns true if the input validator address is found in the VoteInfos array +func (vis VoteInfos) ContainsValidatorAddress(consAddress sdk.ConsAddress) bool { + for _, vi := range vis { + votingAddress := sdk.ConsAddress(vi.Validator.Address) + if bytes.Equal(consAddress, votingAddress) { + return true + } + } + return false +} + +// MustFilterByValidatorAddress returns the VoteInfo that has a validator address matching the input validator address +func (vis VoteInfos) MustFilterByValidatorAddress(consAddress sdk.ConsAddress) abci.VoteInfo { + for i, vi := range vis { + votingAddress := sdk.ConsAddress(vi.Validator.Address) + if bytes.Equal(consAddress, votingAddress) { + return vis[i] + } + } + panic("validator address not found") +} diff --git a/x/validator-vesting/alias.go b/x/validator-vesting/alias.go new file mode 100644 index 00000000..84e2aef0 --- /dev/null +++ b/x/validator-vesting/alias.go @@ -0,0 +1,22 @@ +// nolint +package validatorvesting + +import ( + "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/keeper" + "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" +) + +const ( + ModuleName = types.ModuleName + StoreKey = types.StoreKey +) + +var ( + NewGenesisState = types.NewGenesisState +) + +type ( + GenesisState = types.GenesisState + Keeper = keeper.Keeper + ValidatorVestingAccount = types.ValidatorVestingAccount +) diff --git a/x/validator-vesting/genesis.go b/x/validator-vesting/genesis.go new file mode 100644 index 00000000..b21fa3c1 --- /dev/null +++ b/x/validator-vesting/genesis.go @@ -0,0 +1,19 @@ +package validatorvesting + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" +) + +// 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 +func InitGenesis(ctx sdk.Context, keeper Keeper, accountKeeper types.AccountKeeper, data GenesisState) { + data.Accounts = auth.SanitizeGenesisAccounts(data.Accounts) + for _, a := range data.Accounts { + vv, ok := a.(ValidatorVestingAccount) + if ok { + keeper.SetValidatorVestingAccountKey(ctx, vv.Address) + } + } +} diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/internal/keeper/keeper.go new file mode 100644 index 00000000..b30c14ce --- /dev/null +++ b/x/validator-vesting/internal/keeper/keeper.go @@ -0,0 +1,188 @@ +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/cosmos/cosmos-sdk/x/validator-vesting/internal/types" + "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 + 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(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(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 + }) + } + } +} + +// 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) + 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") +} + +// 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.PeriodLength + endTimes = append(endTimes, currentEndTime) + } + return endTimes +} diff --git a/x/validator-vesting/internal/keeper/keeper_test.go b/x/validator-vesting/internal/keeper/keeper_test.go new file mode 100644 index 00000000..fe2bee7e --- /dev/null +++ b/x/validator-vesting/internal/keeper/keeper_test.go @@ -0,0 +1,282 @@ +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 ( + "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" +) + +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)) +} + +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) + +} + +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, []int64{0, 0}, vva.MissingSignCount) + + // validator signs a block + keeper.UpdateMissingSignCount(ctx, vva.Address, false) + vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address) + require.Equal(t, []int64{0, 1}, vva.MissingSignCount) + + // validator misses a block + keeper.UpdateMissingSignCount(ctx, vva.Address, true) + vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address) + require.Equal(t, []int64{1, 2}, vva.MissingSignCount) + +} + +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, []int{0, 0, 0}, vva.VestingPeriodProgress) + + // period 0 passes with all blocks signed + vva.MissingSignCount[0] = 0 + vva.MissingSignCount[1] = 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 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 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 + vva.MissingSignCount[1] = 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 0 + require.Equal(t, []int{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) + + 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) + require.Nil(t, vva.GetCoins()) + + // 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(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) + + // 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.MissingSignCount[0] = 50 + vva.MissingSignCount[1] = 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)) + // _ = staking.EndBlocker(ctx, stakingKeeper) + 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) + _, 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 + 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) + + // 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 + 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) +} diff --git a/x/validator-vesting/internal/keeper/test_common.go b/x/validator-vesting/internal/keeper/test_common.go new file mode 100644 index 00000000..a3ed9945 --- /dev/null +++ b/x/validator-vesting/internal/keeper/test_common.go @@ -0,0 +1,220 @@ +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/cosmos/cosmos-sdk/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()) + + 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()) + + // 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) + + 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, staking.DefaultParams()) + + 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.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(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 ValidatorVestingDelegatorTestAccount(startTime time.Time) *types.ValidatorVestingAccount { + periods := vesting.VestingPeriods{ + vesting.VestingPeriod{PeriodLength: int64(12 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(stakeDenom, 30000000)}}, + vesting.VestingPeriod{PeriodLength: int64(6 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(stakeDenom, 15000000)}}, + vesting.VestingPeriod{PeriodLength: int64(6 * 60 * 60), VestingAmount: 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) +} diff --git a/x/validator-vesting/internal/types/codec.go b/x/validator-vesting/internal/types/codec.go new file mode 100644 index 00000000..1f0fa25f --- /dev/null +++ b/x/validator-vesting/internal/types/codec.go @@ -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() +} diff --git a/x/validator-vesting/internal/types/expected_keepers.go b/x/validator-vesting/internal/types/expected_keepers.go new file mode 100644 index 00000000..572c767c --- /dev/null +++ b/x/validator-vesting/internal/types/expected_keepers.go @@ -0,0 +1,38 @@ +package types + +import ( + "time" + + sdk "github.com/cosmos/cosmos-sdk/types" + "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) exported.Account + SetAccount(sdk.Context, exported.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) +} diff --git a/x/validator-vesting/internal/types/genesis.go b/x/validator-vesting/internal/types/genesis.go new file mode 100644 index 00000000..60fd9a05 --- /dev/null +++ b/x/validator-vesting/internal/types/genesis.go @@ -0,0 +1,22 @@ +package types + +import ( + "github.com/cosmos/cosmos-sdk/x/auth/exported" +) + +// GenesisState - all auth state that must be provided at genesis +type GenesisState struct { + Accounts exported.GenesisAccounts `json:"accounts" yaml:"accounts"` +} + +// NewGenesisState - Create a new genesis state +func NewGenesisState(accounts exported.GenesisAccounts) GenesisState { + return GenesisState{ + Accounts: accounts, + } +} + +// DefaultGenesisState - Return a default genesis state +func DefaultGenesisState() GenesisState { + return NewGenesisState(exported.GenesisAccounts{}) +} diff --git a/x/validator-vesting/internal/types/key.go b/x/validator-vesting/internal/types/key.go new file mode 100644 index 00000000..a86c1aea --- /dev/null +++ b/x/validator-vesting/internal/types/key.go @@ -0,0 +1,23 @@ +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 ( + // 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()...) +} \ No newline at end of file diff --git a/x/validator-vesting/internal/types/test_common.go b/x/validator-vesting/internal/types/test_common.go new file mode 100644 index 00000000..6e6091ba --- /dev/null +++ b/x/validator-vesting/internal/types/test_common.go @@ -0,0 +1,82 @@ +package types // 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 +} diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/internal/types/validator_vesting_account.go new file mode 100644 index 00000000..c02c5350 --- /dev/null +++ b/x/validator-vesting/internal/types/validator_vesting_account.go @@ -0,0 +1,231 @@ +package types + +import ( + "errors" + "time" + + "gopkg.in/yaml.v2" + + 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" + vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" +) + +// Assert ValidatorVestingAccount implements the vestexported.VestingAccount interface +// Assert +var _ vestexported.VestingAccount = (*ValidatorVestingAccount)(nil) +var _ authexported.GenesisAccount = (*ValidatorVestingAccount)(nil) + +// Register the ValidatorVestingAccount type on the auth module codec +func init() { + auth.RegisterAccountTypeCodec(&ValidatorVestingAccount{}, "cosmos-sdk/ValidatorVestingAccount") +} + +// ValidatorVestingAccount implements the VestingAccount interface. It +// conditionally vests by unlocking coins during each specified period, provided +// that the validator address has validated at least **threshold** blocks during +// the previous vesting period. If the validator has not vested at least the threshold, +// the coins are returned to the return address, or burned if the return address is null. +type ValidatorVestingAccount struct { + *vesting.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"` + MissingSignCount []int64 `json:"missing_sign_count" yaml:"missing_sign_count"` + VestingPeriodProgress []int `json:"vesting_period_progress" yaml:"vesting_period_progress"` + DebtAfterFailedVesting sdk.Coins +} + +// NewValidatorVestingAccountRaw creates a new ValidatorVestingAccount object from BaseVestingAccount +func NewValidatorVestingAccountRaw(bva *vesting.BaseVestingAccount, + startTime int64, periods vesting.VestingPeriods, validatorAddress sdk.ConsAddress, returnAddress sdk.AccAddress, signingThreshold int64) *ValidatorVestingAccount { + cva := &vesting.ContinuousVestingAccount{ + StartTime: startTime, + BaseVestingAccount: bva, + } + pva := &vesting.PeriodicVestingAccount{ + ContinuousVestingAccount: cva, + VestingPeriods: periods, + } + var vestingPeriodProgress = make([]int, len(periods)) + + return &ValidatorVestingAccount{ + PeriodicVestingAccount: pva, + ValidatorAddress: validatorAddress, + ReturnAddress: returnAddress, + SigningThreshold: signingThreshold, + MissingSignCount: []int64{0, 0}, + VestingPeriodProgress: vestingPeriodProgress, + DebtAfterFailedVesting: sdk.NewCoins(), + } +} + +// NewValidatorVestingAccount creates a ValidatorVestingAccount object from a BaseAccount +func NewValidatorVestingAccount(baseAcc *auth.BaseAccount, startTime int64, periods vesting.VestingPeriods, validatorAddress sdk.ConsAddress, returnAddress sdk.AccAddress, signingThreshold int64) *ValidatorVestingAccount { + + endTime := startTime + for _, p := range periods { + endTime += p.PeriodLength + } + baseVestingAcc := &vesting.BaseVestingAccount{ + BaseAccount: baseAcc, + OriginalVesting: baseAcc.Coins, + EndTime: endTime, + } + cva := &vesting.ContinuousVestingAccount{ + StartTime: startTime, + BaseVestingAccount: baseVestingAcc, + } + pva := &vesting.PeriodicVestingAccount{ + ContinuousVestingAccount: cva, + VestingPeriods: periods, + } + var vestingPeriodProgress = make([]int, len(periods)) + + debt := sdk.NewCoins() + + return &ValidatorVestingAccount{ + PeriodicVestingAccount: pva, + ValidatorAddress: validatorAddress, + ReturnAddress: returnAddress, + SigningThreshold: signingThreshold, + MissingSignCount: []int64{0, 0}, + VestingPeriodProgress: vestingPeriodProgress, + DebtAfterFailedVesting: debt, + } +} + +// 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].PeriodLength { + vestedSuccess := vva.VestingPeriodProgress[i] > 0 + if vestedSuccess { + vestedCoins = vestedCoins.Add(vva.VestingPeriods[i].VestingAmount) + } + currentPeriodStartTime += vva.VestingPeriods[i].PeriodLength + } 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(blockTime time.Time) 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 vestedFailure { + failedVestedCoins = failedVestedCoins.Add(vva.VestingPeriods[i].VestingAmount) + } + currentPeriodStartTime += vva.VestingPeriods[i].PeriodLength + } 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)).Sub(vva.GetFailedVestedCoins(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) +} + +// 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 vesting.VestingPeriods + ValidatorAddress sdk.ConsAddress + ReturnAddress sdk.AccAddress + SigningThreshold int64 + MissingSignCount []int64 + VestingPeriodProgress []int + 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, + MissingSignCount: vva.MissingSignCount, + VestingPeriodProgress: vva.VestingPeriodProgress, + DebtAfterFailedVesting: vva.DebtAfterFailedVesting, + }) + if err != nil { + return nil, err + } + + return string(bs), err +} diff --git a/x/validator-vesting/internal/types/validator_vesting_account_test.go b/x/validator-vesting/internal/types/validator_vesting_account_test.go new file mode 100644 index 00000000..e3e81dd8 --- /dev/null +++ b/x/validator-vesting/internal/types/validator_vesting_account_test.go @@ -0,0 +1,360 @@ +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" +) + +var ( + stakeDenom = "stake" + feeDenom = "fee" +) + +func TestGetVestedCoinsValidatorVestingAcc(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) + + // 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] = 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 + vestedCoins = vva.GetVestedCoins(now.Add(12 * time.Hour)) + require.Nil(t, vestedCoins) + + // require period 2 coins don't vest until period is over + vva.VestingPeriodProgress[0] = 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 + 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 + 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 + vestedCoins = vva.GetVestedCoins(now.Add(18 * time.Hour)) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 500), sdk.NewInt64Coin(stakeDenom, 50)}, vestedCoins) + + // require 100% of coins vested after all periods complete successfully + vva.VestingPeriodProgress[0] = 1 + vva.VestingPeriodProgress[1] = 1 + vva.VestingPeriodProgress[2] = 1 + + vestedCoins = vva.GetVestedCoins(now.Add(48 * time.Hour)) + require.Equal(t, origCoins, vestedCoins) +} + +func TestGetVestingCoinsValidatorVestingAcc(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) + + // 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] = 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 + 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 + // should never happen, but still won't affect vesting balance + vva.VestingPeriodProgress[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 + 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 + 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 + + 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 + + vestingCoins = vva.GetVestingCoins(now.Add(48 * time.Hour)) + require.Nil(t, vestingCoins) +} + +func TestSpendableCoinsValidatorVestingAccount(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) + + // 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] = 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) + + // 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 + 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 TestTrackDelegationValidatorVestingAcc(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.TrackDelegation(now, origCoins) + require.Equal(t, origCoins, vva.DelegatedVesting) + require.Nil(t, vva.DelegatedFree) + require.Nil(t, vva.GetCoins()) + + // 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] = 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) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000)}, vva.GetCoins()) + + // 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) + require.Equal(t, origCoins, vva.GetCoins()) +} + +func TestTrackUndelegationPeriodicVestingAcc(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) + + // 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.Equal(t, origCoins, vva.GetCoins()) + + // 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.TrackDelegation(now.Add(24*time.Hour), origCoins) + vva.TrackUndelegation(origCoins) + require.Nil(t, vva.DelegatedFree) + require.Nil(t, vva.DelegatedVesting) + require.Equal(t, origCoins, vva.GetCoins()) + + // 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) + require.Equal(t, origCoins, vva.GetCoins()) + + // successfuly vest period 1 and delegate to two validators + vva = NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90) + vva.VestingPeriodProgress[0] = 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)}) + + // 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) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 25)}, vva.GetCoins()) + + // 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) + require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 75)}, vva.GetCoins()) +} + +func TestGenesisAccountValidate(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) + 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) + }) + } +} From 8359a819ad375642f3976a4b2215f67dc5a23815 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 23 Sep 2019 15:12:18 -0400 Subject: [PATCH 02/13] feat: add validator vesting spec --- x/validator-vesting/abci.go | 3 +- .../types/validator_vesting_account.go | 10 +++-- x/validator-vesting/spec/01_concepts.md | 8 ++++ x/validator-vesting/spec/02_state.md | 25 +++++++++++ x/validator-vesting/spec/03_begin_block.md | 44 +++++++++++++++++++ 5 files changed, 85 insertions(+), 5 deletions(-) create mode 100644 x/validator-vesting/spec/01_concepts.md create mode 100644 x/validator-vesting/spec/02_state.md create mode 100644 x/validator-vesting/spec/03_begin_block.md diff --git a/x/validator-vesting/abci.go b/x/validator-vesting/abci.go index c38dcaab..2467cb65 100644 --- a/x/validator-vesting/abci.go +++ b/x/validator-vesting/abci.go @@ -41,8 +41,9 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) if currentBlockTime.Unix() >= t && previousBlockTime.Unix() < t { k.UpdateVestedCoinsProgress(ctx, key, i) } - k.HandleVestingDebt(ctx, key, currentBlockTime) } + // handle any new/remaining debt on the account + k.HandleVestingDebt(ctx, key, currentBlockTime) } k.SetPreviousBlockTime(ctx, currentBlockTime) } diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/internal/types/validator_vesting_account.go index c02c5350..00f326df 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account.go +++ b/x/validator-vesting/internal/types/validator_vesting_account.go @@ -14,7 +14,7 @@ import ( ) // Assert ValidatorVestingAccount implements the vestexported.VestingAccount interface -// Assert +// Assert ValidatorVestingAccount implements the authexported.GenesisAccount interface var _ vestexported.VestingAccount = (*ValidatorVestingAccount)(nil) var _ authexported.GenesisAccount = (*ValidatorVestingAccount)(nil) @@ -25,8 +25,10 @@ func init() { // ValidatorVestingAccount implements the VestingAccount interface. It // conditionally vests by unlocking coins during each specified period, provided -// that the validator address has validated at least **threshold** blocks during -// the previous vesting period. If the validator has not vested at least the threshold, +// 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 { *vesting.PeriodicVestingAccount @@ -35,7 +37,7 @@ type ValidatorVestingAccount struct { 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"` - DebtAfterFailedVesting sdk.Coins + DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"` } // NewValidatorVestingAccountRaw creates a new ValidatorVestingAccount object from BaseVestingAccount diff --git a/x/validator-vesting/spec/01_concepts.md b/x/validator-vesting/spec/01_concepts.md new file mode 100644 index 00000000..01dd9f5f --- /dev/null +++ b/x/validator-vesting/spec/01_concepts.md @@ -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. + diff --git a/x/validator-vesting/spec/02_state.md b/x/validator-vesting/spec/02_state.md new file mode 100644 index 00000000..ac30bc89 --- /dev/null +++ b/x/validator-vesting/spec/02_state.md @@ -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 array with length equal to the number of vesting periods. After each period, the value at the index of that period is updated with 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. diff --git a/x/validator-vesting/spec/03_begin_block.md b/x/validator-vesting/spec/03_begin_block.md new file mode 100644 index 00000000..97fbe67c --- /dev/null +++ b/x/validator-vesting/spec/03_begin_block.md @@ -0,0 +1,44 @@ +# 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. + +```go +func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { + previousBlockTime := time.Time{} + if ctx.BlockHeight() > 1 { + previousBlockTime = k.GetPreviousBlockTime(ctx) + } + + currentBlockTime := req.Header.GetTime() + var voteInfos VoteInfos + voteInfos = ctx.VoteInfos() + 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) +} +``` + From f7cb937d81b7ee0dfb512a794356336230691af1 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 23 Sep 2019 15:17:40 -0400 Subject: [PATCH 03/13] fix: update exported aliases --- x/validator-vesting/alias.go | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/x/validator-vesting/alias.go b/x/validator-vesting/alias.go index 84e2aef0..5a9fcfdf 100644 --- a/x/validator-vesting/alias.go +++ b/x/validator-vesting/alias.go @@ -12,7 +12,14 @@ const ( ) var ( - NewGenesisState = types.NewGenesisState + NewValidatorVestingAccountRaw = types.NewValidatorVestingAccountRaw + NewValidatorVestingAccount = types.NewValidatorVestingAccount + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + RegisterCodec = types.RegisterCodec + ValidatorVestingAccountPrefix = types.ValidatorVestingAccountPrefix + ValidatorVestingAccountKey = types.ValidatorVestingAccountKey + NewKeeper = keeper.NewKeeper ) type ( From a6285e84fd1c271e455748a272fb10af932660dd Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Fri, 27 Sep 2019 14:48:57 -0400 Subject: [PATCH 04/13] feat: add tests --- x/validator-vesting/abci.go | 13 +- x/validator-vesting/abci_test.go | 237 ++++++++++++++++++ x/validator-vesting/alias.go | 4 +- x/validator-vesting/genesis.go | 2 +- x/validator-vesting/internal/keeper/keeper.go | 158 ++++++------ .../internal/keeper/keeper_test.go | 119 ++++++--- .../internal/keeper/test_common.go | 62 +++-- x/validator-vesting/internal/types/genesis.go | 14 ++ x/validator-vesting/internal/types/key.go | 6 +- .../internal/types/test_common.go | 6 +- .../types/validator_vesting_account.go | 34 +-- .../types/validator_vesting_account_test.go | 160 +++++++++--- x/validator-vesting/test_common.go | 133 ++++++++++ 13 files changed, 754 insertions(+), 194 deletions(-) create mode 100644 x/validator-vesting/abci_test.go create mode 100644 x/validator-vesting/test_common.go diff --git a/x/validator-vesting/abci.go b/x/validator-vesting/abci.go index 2467cb65..ed5a567b 100644 --- a/x/validator-vesting/abci.go +++ b/x/validator-vesting/abci.go @@ -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) } diff --git a/x/validator-vesting/abci_test.go b/x/validator-vesting/abci_test.go new file mode 100644 index 00000000..4c865775 --- /dev/null +++ b/x/validator-vesting/abci_test.go @@ -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) +} diff --git a/x/validator-vesting/alias.go b/x/validator-vesting/alias.go index 5a9fcfdf..5685f88d 100644 --- a/x/validator-vesting/alias.go +++ b/x/validator-vesting/alias.go @@ -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 ) diff --git a/x/validator-vesting/genesis.go b/x/validator-vesting/genesis.go index b21fa3c1..e5f2bf9b 100644 --- a/x/validator-vesting/genesis.go +++ b/x/validator-vesting/genesis.go @@ -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 { diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/internal/keeper/keeper.go index b30c14ce..ab5bb512 100644 --- a/x/validator-vesting/internal/keeper/keeper.go +++ b/x/validator-vesting/internal/keeper/keeper.go @@ -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 diff --git a/x/validator-vesting/internal/keeper/keeper_test.go b/x/validator-vesting/internal/keeper/keeper_test.go index fe2bee7e..3f73f32f 100644 --- a/x/validator-vesting/internal/keeper/keeper_test.go +++ b/x/validator-vesting/internal/keeper/keeper_test.go @@ -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 diff --git a/x/validator-vesting/internal/keeper/test_common.go b/x/validator-vesting/internal/keeper/test_common.go index a3ed9945..9930123e 100644 --- a/x/validator-vesting/internal/keeper/test_common.go +++ b/x/validator-vesting/internal/keeper/test_common.go @@ -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) diff --git a/x/validator-vesting/internal/types/genesis.go b/x/validator-vesting/internal/types/genesis.go index 60fd9a05..29013bd1 100644 --- a/x/validator-vesting/internal/types/genesis.go +++ b/x/validator-vesting/internal/types/genesis.go @@ -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{}) +} diff --git a/x/validator-vesting/internal/types/key.go b/x/validator-vesting/internal/types/key.go index a86c1aea..6b198440 100644 --- a/x/validator-vesting/internal/types/key.go +++ b/x/validator-vesting/internal/types/key.go @@ -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()...) -} \ No newline at end of file +} diff --git a/x/validator-vesting/internal/types/test_common.go b/x/validator-vesting/internal/types/test_common.go index 6e6091ba..b34c14b2 100644 --- a/x/validator-vesting/internal/types/test_common.go +++ b/x/validator-vesting/internal/types/test_common.go @@ -1,4 +1,8 @@ -package types // noalias +package types + +// nolint:deadcode unused +// DONTCOVER +// noalias import ( "bytes" diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/internal/types/validator_vesting_account.go index 00f326df..3aad1824 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account.go +++ b/x/validator-vesting/internal/types/validator_vesting_account.go @@ -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, diff --git a/x/validator-vesting/internal/types/validator_vesting_account_test.go b/x/validator-vesting/internal/types/validator_vesting_account_test.go index e3e81dd8..5401bd4f 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account_test.go +++ b/x/validator-vesting/internal/types/validator_vesting_account_test.go @@ -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)}) diff --git a/x/validator-vesting/test_common.go b/x/validator-vesting/test_common.go new file mode 100644 index 00000000..c6b1d40b --- /dev/null +++ b/x/validator-vesting/test_common.go @@ -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, + } + } +} From 3b35ecfea50323eaa6ddd7c06f26911a270630e5 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Fri, 27 Sep 2019 15:00:24 -0400 Subject: [PATCH 05/13] feat: update spec --- x/validator-vesting/spec/02_state.md | 2 +- x/validator-vesting/spec/03_begin_block.md | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/x/validator-vesting/spec/02_state.md b/x/validator-vesting/spec/02_state.md index ac30bc89..43d9b7bb 100644 --- a/x/validator-vesting/spec/02_state.md +++ b/x/validator-vesting/spec/02_state.md @@ -11,7 +11,7 @@ type ValidatorVestingAccount struct { 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 array with length equal to the number of vesting periods. After each period, the value at the index of that period is updated with 0 for unsucessful vesting and 1 for successful vesting. + 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. } ``` diff --git a/x/validator-vesting/spec/03_begin_block.md b/x/validator-vesting/spec/03_begin_block.md index 97fbe67c..b9528c51 100644 --- a/x/validator-vesting/spec/03_begin_block.md +++ b/x/validator-vesting/spec/03_begin_block.md @@ -1,6 +1,6 @@ # 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. +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) { @@ -9,12 +9,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 { @@ -29,14 +29,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) } From b57b36206273452fc7657c7c0c9f6158ed3b9558 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 30 Sep 2019 12:01:33 -0400 Subject: [PATCH 06/13] rebase vesting --- x/validator-vesting/internal/keeper/keeper.go | 4 +- .../internal/keeper/test_common.go | 24 +++---- .../types/validator_vesting_account.go | 36 +++++----- .../types/validator_vesting_account_test.go | 65 ++++++++++--------- 4 files changed, 65 insertions(+), 64 deletions(-) diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/internal/keeper/keeper.go index ab5bb512..be00172b 100644 --- a/x/validator-vesting/internal/keeper/keeper.go +++ b/x/validator-vesting/internal/keeper/keeper.go @@ -129,7 +129,7 @@ func (k Keeper) UpdateVestedCoinsProgress(ctx sdk.Context, addr sdk.AccAddress, vv.VestingPeriodProgress[period][1] = 1 } else { vv.VestingPeriodProgress[period][1] = 0 - notVestedTokens := vv.VestingPeriods[period].VestingAmount + notVestedTokens := vv.VestingPeriods[period].Amount // add the tokens that did not vest to DebtAfterFailedVesting vv.DebtAfterFailedVesting = vv.DebtAfterFailedVesting.Add(notVestedTokens) } @@ -183,7 +183,7 @@ func (k Keeper) GetPeriodEndTimes(ctx sdk.Context, addr sdk.AccAddress) []int64 vv := k.GetAccountFromAuthKeeper(ctx, addr) currentEndTime := vv.StartTime for _, p := range vv.VestingPeriods { - currentEndTime += p.PeriodLength + currentEndTime += p.Length endTimes = append(endTimes, currentEndTime) } return endTimes diff --git a/x/validator-vesting/internal/keeper/test_common.go b/x/validator-vesting/internal/keeper/test_common.go index 9930123e..a783bb90 100644 --- a/x/validator-vesting/internal/keeper/test_common.go +++ b/x/validator-vesting/internal/keeper/test_common.go @@ -157,10 +157,10 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context func ValidatorVestingTestAccount() *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)}}, + 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] @@ -179,10 +179,10 @@ func ValidatorVestingTestAccount() *types.ValidatorVestingAccount { 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)}}, + 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) @@ -204,10 +204,10 @@ func ValidatorVestingTestAccounts(numAccounts int) []*types.ValidatorVestingAcco } func ValidatorVestingDelegatorTestAccount(startTime time.Time) *types.ValidatorVestingAccount { - periods := vesting.VestingPeriods{ - vesting.VestingPeriod{PeriodLength: int64(12 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(stakeDenom, 30000000)}}, - vesting.VestingPeriod{PeriodLength: int64(6 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(stakeDenom, 15000000)}}, - vesting.VestingPeriod{PeriodLength: int64(6 * 60 * 60), VestingAmount: sdk.Coins{sdk.NewInt64Coin(stakeDenom, 15000000)}}, + 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] diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/internal/types/validator_vesting_account.go index 3aad1824..35092f55 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account.go +++ b/x/validator-vesting/internal/types/validator_vesting_account.go @@ -7,10 +7,10 @@ import ( "gopkg.in/yaml.v2" 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" + 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 @@ -20,7 +20,7 @@ var _ authexported.GenesisAccount = (*ValidatorVestingAccount)(nil) // Register the ValidatorVestingAccount type on the auth module codec func init() { - auth.RegisterAccountTypeCodec(&ValidatorVestingAccount{}, "cosmos-sdk/ValidatorVestingAccount") + authtypes.RegisterAccountTypeCodec(&ValidatorVestingAccount{}, "cosmos-sdk/ValidatorVestingAccount") } // ValidatorVestingAccount implements the VestingAccount interface. It @@ -31,7 +31,7 @@ func init() { // 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 { - *vesting.PeriodicVestingAccount + *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"` @@ -41,13 +41,13 @@ type ValidatorVestingAccount struct { } // NewValidatorVestingAccountRaw creates a new ValidatorVestingAccount object from BaseVestingAccount -func NewValidatorVestingAccountRaw(bva *vesting.BaseVestingAccount, - startTime int64, periods vesting.VestingPeriods, validatorAddress sdk.ConsAddress, returnAddress sdk.AccAddress, signingThreshold int64) *ValidatorVestingAccount { - cva := &vesting.ContinuousVestingAccount{ +func NewValidatorVestingAccountRaw(bva *vestingtypes.BaseVestingAccount, + startTime int64, periods vestingtypes.Periods, validatorAddress sdk.ConsAddress, returnAddress sdk.AccAddress, signingThreshold int64) *ValidatorVestingAccount { + cva := &vestingtypes.ContinuousVestingAccount{ StartTime: startTime, BaseVestingAccount: bva, } - pva := &vesting.PeriodicVestingAccount{ + pva := &vestingtypes.PeriodicVestingAccount{ ContinuousVestingAccount: cva, VestingPeriods: periods, } @@ -68,22 +68,22 @@ func NewValidatorVestingAccountRaw(bva *vesting.BaseVestingAccount, } // NewValidatorVestingAccount creates a ValidatorVestingAccount object from a BaseAccount -func NewValidatorVestingAccount(baseAcc *auth.BaseAccount, startTime int64, periods vesting.VestingPeriods, validatorAddress sdk.ConsAddress, returnAddress sdk.AccAddress, signingThreshold int64) *ValidatorVestingAccount { +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.PeriodLength + endTime += p.Length } - baseVestingAcc := &vesting.BaseVestingAccount{ + baseVestingAcc := &vestingtypes.BaseVestingAccount{ BaseAccount: baseAcc, OriginalVesting: baseAcc.Coins, EndTime: endTime, } - cva := &vesting.ContinuousVestingAccount{ + cva := &vestingtypes.ContinuousVestingAccount{ StartTime: startTime, BaseVestingAccount: baseVestingAcc, } - pva := &vesting.PeriodicVestingAccount{ + pva := &vestingtypes.PeriodicVestingAccount{ ContinuousVestingAccount: cva, VestingPeriods: periods, } @@ -115,12 +115,12 @@ func (vva ValidatorVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins numberPeriods := len(vva.VestingPeriods) for i := 0; i < numberPeriods; i++ { x := blockTime.Unix() - currentPeriodStartTime - if x >= vva.VestingPeriods[i].PeriodLength { + if x >= vva.VestingPeriods[i].Length { vestingComplete := vva.VestingPeriodProgress[i][0] == 1 if vestingComplete { - vestedCoins = vestedCoins.Add(vva.VestingPeriods[i].VestingAmount) + vestedCoins = vestedCoins.Add(vva.VestingPeriods[i].Amount) } - currentPeriodStartTime += vva.VestingPeriods[i].PeriodLength + currentPeriodStartTime += vva.VestingPeriods[i].Length } else { break } @@ -137,7 +137,7 @@ func (vva ValidatorVestingAccount) GetFailedVestedCoins() sdk.Coins { if vva.VestingPeriodProgress[i][0] == 1 { vestedFailure := vva.VestingPeriodProgress[i][1] == 0 if vestedFailure { - failedVestedCoins = failedVestedCoins.Add(vva.VestingPeriods[i].VestingAmount) + failedVestedCoins = failedVestedCoins.Add(vva.VestingPeriods[i].Amount) } } else { break @@ -199,7 +199,7 @@ func (vva ValidatorVestingAccount) MarshalYAML() (interface{}, error) { DelegatedVesting sdk.Coins EndTime int64 StartTime int64 - VestingPeriods vesting.VestingPeriods + VestingPeriods vestingtypes.Periods ValidatorAddress sdk.ConsAddress ReturnAddress sdk.AccAddress SigningThreshold int64 diff --git a/x/validator-vesting/internal/types/validator_vesting_account_test.go b/x/validator-vesting/internal/types/validator_vesting_account_test.go index 5401bd4f..b830cd0a 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account_test.go +++ b/x/validator-vesting/internal/types/validator_vesting_account_test.go @@ -12,6 +12,7 @@ import ( "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 ( @@ -22,10 +23,10 @@ var ( 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)}}, + 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] @@ -44,10 +45,10 @@ func TestNewAccount(t *testing.T) { func TestGetVestedCoinsValidatorVestingAcc(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)}}, + 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] @@ -119,10 +120,10 @@ func TestGetVestedCoinsValidatorVestingAcc(t *testing.T) { func TestGetVestingCoinsValidatorVestingAcc(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)}}, + 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] @@ -191,10 +192,10 @@ func TestGetVestingCoinsValidatorVestingAcc(t *testing.T) { func TestSpendableCoinsValidatorVestingAccount(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)}}, + 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] @@ -238,10 +239,10 @@ func TestSpendableCoinsValidatorVestingAccount(t *testing.T) { 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)}}, + 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] @@ -268,10 +269,10 @@ func TestGetFailedVestedCoins(t *testing.T) { } func TestTrackDelegationValidatorVestingAcc(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)}}, + 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] @@ -327,10 +328,10 @@ func TestTrackDelegationValidatorVestingAcc(t *testing.T) { func TestTrackUndelegationPeriodicVestingAcc(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)}}, + 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] @@ -391,10 +392,10 @@ func TestTrackUndelegationPeriodicVestingAcc(t *testing.T) { func TestGenesisAccountValidate(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)}}, + 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] From f6aec4634359387a7e3e953ceabbe0da761a185b Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 30 Sep 2019 22:53:14 -0400 Subject: [PATCH 07/13] add simulation to validator vesting --- x/validator-vesting/genesis.go | 7 +- x/validator-vesting/internal/types/genesis.go | 5 + x/validator-vesting/module.go | 146 ++++++++++++++++++ x/validator-vesting/simulation/decoder.go | 31 ++++ x/validator-vesting/simulation/genesis.go | 121 +++++++++++++++ x/validator-vesting/test_common.go | 4 +- 6 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 x/validator-vesting/module.go create mode 100644 x/validator-vesting/simulation/decoder.go create mode 100644 x/validator-vesting/simulation/genesis.go diff --git a/x/validator-vesting/genesis.go b/x/validator-vesting/genesis.go index e5f2bf9b..f8b674b7 100644 --- a/x/validator-vesting/genesis.go +++ b/x/validator-vesting/genesis.go @@ -8,7 +8,7 @@ import ( // InitGenesis stores the account address of each ValidatorVestingAccount in the validator vesting keeper, for faster lookup. // CONTRACT: Accounts created by the account keeper must have already been initialized/created by AccountKeeper -func InitGenesis(ctx sdk.Context, keeper Keeper, accountKeeper types.AccountKeeper, data GenesisState) { +func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { data.Accounts = auth.SanitizeGenesisAccounts(data.Accounts) for _, a := range data.Accounts { vv, ok := a.(ValidatorVestingAccount) @@ -17,3 +17,8 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, accountKeeper types.AccountKeep } } } + +// ExportGenesis returns empty genesis state because auth exports all the genesis state we need. +func ExportGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { + return types.DefaultGenesisState() +} diff --git a/x/validator-vesting/internal/types/genesis.go b/x/validator-vesting/internal/types/genesis.go index 29013bd1..5133271e 100644 --- a/x/validator-vesting/internal/types/genesis.go +++ b/x/validator-vesting/internal/types/genesis.go @@ -34,3 +34,8 @@ func (data GenesisState) Equal(data2 GenesisState) bool { func (data GenesisState) IsEmpty() bool { return data.Equal(GenesisState{}) } + +// ValidateGenesis returns nil because accounts are validated by auth +func ValidateGenesis(data GenesisState) error { + return nil +} diff --git a/x/validator-vesting/module.go b/x/validator-vesting/module.go new file mode 100644 index 00000000..2855b02f --- /dev/null +++ b/x/validator-vesting/module.go @@ -0,0 +1,146 @@ +package validatorvesting + +import ( + "encoding/json" + "math/rand" + + "github.com/gorilla/mux" + "github.com/spf13/cobra" + abci "github.com/tendermint/tendermint/abci/types" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/module" + sim "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" + "github.com/cosmos/cosmos-sdk/x/validator-vesting/simulation" +) + +var ( + _ module.AppModule = AppModule{} + _ module.AppModuleBasic = AppModuleBasic{} + _ module.AppModuleSimulation = AppModuleSimulation{} +) + +// AppModuleBasic defines the basic application module used by the auth module. +type AppModuleBasic struct{} + +// Name returns the auth module's name. +func (AppModuleBasic) Name() string { + return types.ModuleName +} + +// RegisterCodec registers the auth module's types for the given codec. +func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { + types.RegisterCodec(cdc) +} + +// DefaultGenesis returns default genesis state as raw bytes for the validator-vesting +// module. +func (AppModuleBasic) DefaultGenesis() json.RawMessage { + return types.ModuleCdc.MustMarshalJSON(types.DefaultGenesisState()) +} + +// ValidateGenesis performs genesis state validation for the validator-vesting module. +func (AppModuleBasic) ValidateGenesis(bz json.RawMessage) error { + var data types.GenesisState + if err := types.ModuleCdc.UnmarshalJSON(bz, &data); err != nil { + return err + } + return types.ValidateGenesis(data) +} + +// RegisterRESTRoutes registers no REST routes for the crisis module. +func (AppModuleBasic) RegisterRESTRoutes(_ context.CLIContext, _ *mux.Router) {} + +// GetTxCmd returns no root tx command for the validator-vesting module. +func (AppModuleBasic) GetTxCmd(_ *codec.Codec) *cobra.Command { return nil } + +// GetQueryCmd returns no root query command for the validator-vesting module. +func (AppModuleBasic) GetQueryCmd(_ *codec.Codec) *cobra.Command { return nil } + +// AppModuleSimulation defines the module simulation functions used by the auth module. +type AppModuleSimulation struct{} + +// RegisterStoreDecoder registers a decoder for auth module's types +func (AppModuleSimulation) RegisterStoreDecoder(sdr sdk.StoreDecoderRegistry) { + sdr[StoreKey] = simulation.DecodeStore +} + +// GenerateGenesisState creates a randomized GenState of the auth module +func (AppModuleSimulation) GenerateGenesisState(simState *module.SimulationState) { + simulation.RandomizedGenState(simState) +} + +// RandomizedParams returns nil because validatorvesting has no params. +func (AppModuleSimulation) RandomizedParams(_ *rand.Rand) []sim.ParamChange { + return []sim.ParamChange{} +} + +// AppModule implements an application module for the validator-vesting module. +type AppModule struct { + AppModuleBasic + AppModuleSimulation + keeper Keeper +} + +// NewAppModule creates a new AppModule object +func NewAppModule(keeper Keeper) AppModule { + return AppModule{ + AppModuleBasic: AppModuleBasic{}, + AppModuleSimulation: AppModuleSimulation{}, + keeper: keeper, + } +} + +// Name returns the auth module's name. +func (AppModule) Name() string { + return types.ModuleName +} + +// RegisterInvariants performs a no-op. +func (AppModule) RegisterInvariants(_ sdk.InvariantRegistry) {} + +// Route returns the message routing key for the auth module. +func (AppModule) Route() string { return "" } + +// NewHandler returns an sdk.Handler for the auth module. +func (AppModule) NewHandler() sdk.Handler { return nil } + +// QuerierRoute returns the auth module's querier route name. +func (AppModule) QuerierRoute() string { + return "" +} + +// NewQuerierHandler returns the auth module sdk.Querier. +func (am AppModule) NewQuerierHandler() sdk.Querier { + return nil +} + +// InitGenesis performs genesis initialization for the auth module. It returns +// no validator updates. +func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { + var genesisState GenesisState + types.ModuleCdc.MustUnmarshalJSON(data, &genesisState) + InitGenesis(ctx, am.keeper, genesisState) + return []abci.ValidatorUpdate{} +} + +// ExportGenesis returns the exported genesis state as raw bytes for the auth +// module. +func (am AppModule) ExportGenesis(ctx sdk.Context) json.RawMessage { + gs := ExportGenesis(ctx, am.keeper) + return types.ModuleCdc.MustMarshalJSON(gs) +} + +// BeginBlock returns the begin blocker for the auth module. +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + BeginBlocker(ctx, req, am.keeper) +} + +// EndBlock returns the end blocker for the auth module. It returns no validator +// updates. +func (AppModule) EndBlock(_ sdk.Context, _ abci.RequestEndBlock) []abci.ValidatorUpdate { + return []abci.ValidatorUpdate{} +} diff --git a/x/validator-vesting/simulation/decoder.go b/x/validator-vesting/simulation/decoder.go new file mode 100644 index 00000000..3171b200 --- /dev/null +++ b/x/validator-vesting/simulation/decoder.go @@ -0,0 +1,31 @@ +package simulation + +import ( + "bytes" + "fmt" + "time" + + cmn "github.com/tendermint/tendermint/libs/common" + + "github.com/cosmos/cosmos-sdk/codec" + "github.com/cosmos/cosmos-sdk/x/auth/exported" + "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" +) + +// DecodeStore unmarshals the KVPair's Value to the corresponding auth type +func DecodeStore(cdc *codec.Codec, kvA, kvB cmn.KVPair) string { + switch { + case bytes.Equal(kvA.Key[:1], types.ValidatorVestingAccountPrefix): + var accA, accB exported.Account + cdc.MustUnmarshalBinaryBare(kvA.Value, &accA) + cdc.MustUnmarshalBinaryBare(kvB.Value, &accB) + return fmt.Sprintf("%v\n%v", accA, accB) + case bytes.Equal(kvA.Key, types.BlocktimeKey): + var btA, btB time.Time + cdc.MustUnmarshalBinaryLengthPrefixed(kvA.Value, &btA) + cdc.MustUnmarshalBinaryLengthPrefixed(kvB.Value, &btB) + return fmt.Sprintf("%v\n%v", btA, btB) + default: + panic(fmt.Sprintf("invalid account key %X", kvA.Key)) + } +} diff --git a/x/validator-vesting/simulation/genesis.go b/x/validator-vesting/simulation/genesis.go new file mode 100644 index 00000000..99f6c9e0 --- /dev/null +++ b/x/validator-vesting/simulation/genesis.go @@ -0,0 +1,121 @@ +package simulation + +import ( + "math/rand" + + "github.com/cosmos/cosmos-sdk/types/module" + + sdk "github.com/cosmos/cosmos-sdk/types" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + "github.com/cosmos/cosmos-sdk/x/simulation" + "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" +) + +// RandomizedGenState generates a random GenesisState for validator-vesting +func RandomizedGenState(simState *module.SimulationState) { + var authGenState authtypes.GenesisState + authSimState := simState.GenState[authtypes.ModuleName] + simState.Cdc.MustUnmarshalJSON(authSimState, &authGenState) + var newGenesisAccs authexported.GenesisAccounts + for _, acc := range authGenState.Accounts { + va, ok := acc.(vestexported.VestingAccount) + if ok { + // 50% of the time convert the vesting account + + if simState.Rand.Intn(100) < 50 { + bacc := authtypes.NewBaseAccountWithAddress(va.GetAddress()) + err := bacc.SetCoins(va.GetCoins()) + if err != nil { + panic(err) + } + + duration := va.GetEndTime() - va.GetStartTime() + vestingPeriods := getRandomVestingPeriods(duration, simState.Rand, va.GetCoins()) + vestingCoins := getVestingCoins(vestingPeriods) + bva := vestingtypes.NewBaseVestingAccount(&bacc, vestingCoins, va.GetEndTime()) + var gacc authexported.GenesisAccount + if simState.Rand.Intn(100) < 50 { + // convert to periodic vesting account 50% + gacc = vestingtypes.NewPeriodicVestingAccountRaw(bva, va.GetStartTime(), vestingPeriods) + err = gacc.Validate() + if err != nil { + panic(err) + } + } else { + consAdd := getRandomValidatorConsAddr(simState, simulation.RandIntBetween(simState.Rand, 0, int(simState.NumBonded)-1)) + // convert to validator vesting account 50% + // set signing threshold to be anywhere between 1 and 100 + gacc = types.NewValidatorVestingAccountRaw( + bva, va.GetStartTime(), vestingPeriods, consAdd, nil, + int64(simulation.RandIntBetween(simState.Rand, 1, 100)), + ) + err = gacc.Validate() + if err != nil { + panic(err) + } + } + newGenesisAccs = append(newGenesisAccs, gacc) + } else { + newGenesisAccs = append(newGenesisAccs, acc) + } + } else { + newGenesisAccs = append(newGenesisAccs, acc) + } + } + newAuthGenesis := authtypes.NewGenesisState(authGenState.Params, newGenesisAccs) + simState.GenState[authtypes.ModuleName] = simState.Cdc.MustMarshalJSON(newAuthGenesis) +} + +func getRandomValidatorConsAddr(simState *module.SimulationState, rint int) sdk.ConsAddress { + acc := simState.Accounts[rint] + return sdk.ConsAddress(acc.PubKey.Address()) +} + +func getRandomVestingPeriods(duration int64, r *rand.Rand, origCoins sdk.Coins) vestingtypes.Periods { + maxPeriods := int64(50) + if duration < maxPeriods { + maxPeriods = duration + } + numPeriods := simulation.RandIntBetween(r, 1, int(maxPeriods)) + lenPeriod := duration / int64(numPeriods) + periodLengths := make([]int64, numPeriods) + totalLength := int64(0) + for i := 0; i < numPeriods; i++ { + periodLengths[i] = lenPeriod + totalLength += lenPeriod + } + if duration-totalLength != 0 { + periodLengths[len(periodLengths)-1] += (duration - totalLength) + } + + coinFraction := simulation.RandIntBetween(r, 1, 100) + vestingCoins := sdk.NewCoins() + for _, ic := range origCoins { + amountVesting := ic.Amount.Int64() / int64(coinFraction) + vestingCoins = vestingCoins.Add(sdk.NewCoins(sdk.NewInt64Coin(ic.Denom, amountVesting))) + } + periodCoins := sdk.NewCoins() + for _, c := range vestingCoins { + amountPeriod := c.Amount.Int64() / int64(numPeriods) + periodCoins = periodCoins.Add(sdk.NewCoins(sdk.NewInt64Coin(c.Denom, amountPeriod))) + } + + vestingPeriods := make([]vestingtypes.Period, numPeriods) + for i := 0; i < numPeriods; i++ { + vestingPeriods[i] = vestingtypes.Period{Length: int64(periodLengths[i]), Amount: periodCoins} + } + + return vestingPeriods + +} + +func getVestingCoins(periods vestingtypes.Periods) sdk.Coins { + vestingCoins := sdk.NewCoins() + for _, p := range periods { + vestingCoins = vestingCoins.Add(p.Amount) + } + return vestingCoins +} diff --git a/x/validator-vesting/test_common.go b/x/validator-vesting/test_common.go index c6b1d40b..af0c3783 100644 --- a/x/validator-vesting/test_common.go +++ b/x/validator-vesting/test_common.go @@ -122,9 +122,9 @@ func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, validators := staking.InitGenesis(ctx, stakingKeeper, mapp.AccountKeeper, supplyKeeper, stakingGenesis) if genState.IsEmpty() { - InitGenesis(ctx, keeper, mapp.AccountKeeper, types.DefaultGenesisState()) + InitGenesis(ctx, keeper, types.DefaultGenesisState()) } else { - InitGenesis(ctx, keeper, mapp.AccountKeeper, genState) + InitGenesis(ctx, keeper, genState) } return abci.ResponseInitChain{ Validators: validators, From cadb7baf2bfbd8a883e7d3baa34da1601128adee Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Wed, 2 Oct 2019 09:10:28 -0400 Subject: [PATCH 08/13] fix genesis state and sims --- app/app.go | 32 +++++++++++++------ go.mod | 4 +++ go.sum | 1 + x/validator-vesting/abci.go | 6 ++-- x/validator-vesting/abci_test.go | 2 +- x/validator-vesting/alias.go | 4 +-- x/validator-vesting/genesis.go | 16 ++++++---- x/validator-vesting/internal/keeper/keeper.go | 2 +- .../internal/keeper/keeper_test.go | 6 ++++ .../internal/keeper/test_common.go | 2 +- .../internal/types/expected_keepers.go | 8 ++--- x/validator-vesting/internal/types/genesis.go | 15 ++++++--- x/validator-vesting/module.go | 14 +++++--- x/validator-vesting/simulation/decoder.go | 2 +- x/validator-vesting/simulation/genesis.go | 2 +- x/validator-vesting/test_common.go | 14 ++++---- 16 files changed, 84 insertions(+), 46 deletions(-) diff --git a/app/app.go b/app/app.go index 1cc799d8..1826378c 100644 --- a/app/app.go +++ b/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" @@ -42,6 +44,7 @@ var ( ModuleBasics = module.NewBasicManager( genutil.AppModuleBasic{}, auth.AppModuleBasic{}, + validatorvesting.AppModuleBasic{}, bank.AppModuleBasic{}, staking.AppModuleBasic{}, mint.AppModuleBasic{}, @@ -55,12 +58,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 +90,7 @@ type App struct { govKeeper gov.Keeper crisisKeeper crisis.Keeper paramsKeeper params.Keeper + vvKeeper validatorvesting.Keeper // the module manager mm *module.Manager @@ -108,7 +113,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 +199,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 +225,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 +241,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 +255,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), diff --git a/go.mod b/go.mod index 68ed9437..6e688a90 100644 --- a/go.mod +++ b/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/tm-db v0.2.0 + gopkg.in/yaml.v2 v2.2.2 ) + +replace github.com/cosmos/cosmos-sdk => ../../cosmos/cosmos-sdk diff --git a/go.sum b/go.sum index bd5b3aea..5ddef612 100644 --- a/go.sum +++ b/go.sum @@ -37,6 +37,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= diff --git a/x/validator-vesting/abci.go b/x/validator-vesting/abci.go index ed5a567b..0300354f 100644 --- a/x/validator-vesting/abci.go +++ b/x/validator-vesting/abci.go @@ -4,14 +4,16 @@ import ( "bytes" "time" + tmtime "github.com/tendermint/tendermint/types/time" + sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/keeper" + "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 := time.Time{} + previousBlockTime := tmtime.Canonical(time.Unix(0, 0)) if ctx.BlockHeight() > 1 { previousBlockTime = k.GetPreviousBlockTime(ctx) } diff --git a/x/validator-vesting/abci_test.go b/x/validator-vesting/abci_test.go index 4c865775..918d2566 100644 --- a/x/validator-vesting/abci_test.go +++ b/x/validator-vesting/abci_test.go @@ -11,7 +11,7 @@ import ( "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" + "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" ) func TestBeginBlockerSignedBlock(t *testing.T) { diff --git a/x/validator-vesting/alias.go b/x/validator-vesting/alias.go index 5685f88d..9f9914e9 100644 --- a/x/validator-vesting/alias.go +++ b/x/validator-vesting/alias.go @@ -3,8 +3,8 @@ 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" + "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" + "github.com/kava-labs/kava/x/validator-vesting/internal/types" ) const ( diff --git a/x/validator-vesting/genesis.go b/x/validator-vesting/genesis.go index f8b674b7..7ee5b460 100644 --- a/x/validator-vesting/genesis.go +++ b/x/validator-vesting/genesis.go @@ -2,23 +2,25 @@ package validatorvesting import ( sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" - "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/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 created by the account keeper must have already been initialized/created by AccountKeeper -func InitGenesis(ctx sdk.Context, keeper Keeper, data GenesisState) { - data.Accounts = auth.SanitizeGenesisAccounts(data.Accounts) - for _, a := range data.Accounts { +// 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 { - return types.DefaultGenesisState() + prevBlockTime := keeper.GetPreviousBlockTime(ctx) + return GenesisState{PreviousBlockTime: prevBlockTime} } diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/internal/keeper/keeper.go index be00172b..96fc2ecd 100644 --- a/x/validator-vesting/internal/keeper/keeper.go +++ b/x/validator-vesting/internal/keeper/keeper.go @@ -7,7 +7,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" stakingexported "github.com/cosmos/cosmos-sdk/x/staking/exported" - "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/internal/types" "github.com/tendermint/tendermint/libs/log" ) diff --git a/x/validator-vesting/internal/keeper/keeper_test.go b/x/validator-vesting/internal/keeper/keeper_test.go index 3f73f32f..8299226a 100644 --- a/x/validator-vesting/internal/keeper/keeper_test.go +++ b/x/validator-vesting/internal/keeper/keeper_test.go @@ -73,6 +73,12 @@ func TestGetSetPreviousBlock(t *testing.T) { 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) { diff --git a/x/validator-vesting/internal/keeper/test_common.go b/x/validator-vesting/internal/keeper/test_common.go index a783bb90..5ae88d3c 100644 --- a/x/validator-vesting/internal/keeper/test_common.go +++ b/x/validator-vesting/internal/keeper/test_common.go @@ -25,7 +25,7 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/supply" - "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/internal/types" ) //nolint: deadcode unused diff --git a/x/validator-vesting/internal/types/expected_keepers.go b/x/validator-vesting/internal/types/expected_keepers.go index 572c767c..84b47ca9 100644 --- a/x/validator-vesting/internal/types/expected_keepers.go +++ b/x/validator-vesting/internal/types/expected_keepers.go @@ -4,15 +4,16 @@ import ( "time" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth/exported" + 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) exported.Account - SetAccount(sdk.Context, exported.Account) + 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) @@ -27,7 +28,6 @@ type StakingKeeper interface { 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) diff --git a/x/validator-vesting/internal/types/genesis.go b/x/validator-vesting/internal/types/genesis.go index 5133271e..69007b44 100644 --- a/x/validator-vesting/internal/types/genesis.go +++ b/x/validator-vesting/internal/types/genesis.go @@ -2,25 +2,27 @@ package types import ( "bytes" + "fmt" + "time" - "github.com/cosmos/cosmos-sdk/x/auth/exported" + tmtime "github.com/tendermint/tendermint/types/time" ) // GenesisState - all auth state that must be provided at genesis type GenesisState struct { - Accounts exported.GenesisAccounts `json:"accounts" yaml:"accounts"` + PreviousBlockTime time.Time } // NewGenesisState - Create a new genesis state -func NewGenesisState(accounts exported.GenesisAccounts) GenesisState { +func NewGenesisState(prevBlockTime time.Time) GenesisState { return GenesisState{ - Accounts: accounts, + PreviousBlockTime: prevBlockTime, } } // DefaultGenesisState - Return a default genesis state func DefaultGenesisState() GenesisState { - return NewGenesisState(exported.GenesisAccounts{}) + return NewGenesisState(tmtime.Canonical(time.Unix(0, 0))) } // Equal checks whether two gov GenesisState structs are equivalent @@ -37,5 +39,8 @@ func (data GenesisState) IsEmpty() bool { // 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 } diff --git a/x/validator-vesting/module.go b/x/validator-vesting/module.go index 2855b02f..19c8f220 100644 --- a/x/validator-vesting/module.go +++ b/x/validator-vesting/module.go @@ -3,6 +3,7 @@ package validatorvesting import ( "encoding/json" "math/rand" + "os" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -13,8 +14,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" sim "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" - "github.com/cosmos/cosmos-sdk/x/validator-vesting/simulation" + "github.com/kava-labs/kava/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/simulation" ) var ( @@ -39,6 +40,7 @@ func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { // DefaultGenesis returns default genesis state as raw bytes for the validator-vesting // module. func (AppModuleBasic) DefaultGenesis() json.RawMessage { + types.ModuleCdc.PrintTypes(os.Stdout) return types.ModuleCdc.MustMarshalJSON(types.DefaultGenesisState()) } @@ -82,15 +84,17 @@ func (AppModuleSimulation) RandomizedParams(_ *rand.Rand) []sim.ParamChange { type AppModule struct { AppModuleBasic AppModuleSimulation - keeper Keeper + keeper Keeper + accountKeeper types.AccountKeeper } // NewAppModule creates a new AppModule object -func NewAppModule(keeper Keeper) AppModule { +func NewAppModule(keeper Keeper, ak types.AccountKeeper) AppModule { return AppModule{ AppModuleBasic: AppModuleBasic{}, AppModuleSimulation: AppModuleSimulation{}, keeper: keeper, + accountKeeper: ak, } } @@ -123,7 +127,7 @@ func (am AppModule) NewQuerierHandler() sdk.Querier { func (am AppModule) InitGenesis(ctx sdk.Context, data json.RawMessage) []abci.ValidatorUpdate { var genesisState GenesisState types.ModuleCdc.MustUnmarshalJSON(data, &genesisState) - InitGenesis(ctx, am.keeper, genesisState) + InitGenesis(ctx, am.keeper, am.accountKeeper, genesisState) return []abci.ValidatorUpdate{} } diff --git a/x/validator-vesting/simulation/decoder.go b/x/validator-vesting/simulation/decoder.go index 3171b200..f6c052f5 100644 --- a/x/validator-vesting/simulation/decoder.go +++ b/x/validator-vesting/simulation/decoder.go @@ -9,7 +9,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth/exported" - "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/internal/types" ) // DecodeStore unmarshals the KVPair's Value to the corresponding auth type diff --git a/x/validator-vesting/simulation/genesis.go b/x/validator-vesting/simulation/genesis.go index 99f6c9e0..86c20a68 100644 --- a/x/validator-vesting/simulation/genesis.go +++ b/x/validator-vesting/simulation/genesis.go @@ -11,7 +11,7 @@ import ( vestexported "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" "github.com/cosmos/cosmos-sdk/x/simulation" - "github.com/cosmos/cosmos-sdk/x/validator-vesting/internal/types" + "github.com/kava-labs/kava/x/validator-vesting/internal/types" ) // RandomizedGenState generates a random GenesisState for validator-vesting diff --git a/x/validator-vesting/test_common.go b/x/validator-vesting/test_common.go index af0c3783..909ee0bb 100644 --- a/x/validator-vesting/test_common.go +++ b/x/validator-vesting/test_common.go @@ -12,14 +12,14 @@ import ( "github.com/tendermint/tendermint/crypto" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/auth" + authexported "github.com/cosmos/cosmos-sdk/x/auth/exported" "github.com/cosmos/cosmos-sdk/x/bank" "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" + "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" + "github.com/kava-labs/kava/x/validator-vesting/internal/types" ) var ( @@ -38,7 +38,7 @@ type testInput struct { privKeys []crypto.PrivKey } -func getMockApp(t *testing.T, numGenAccs int, genState types.GenesisState, genAccs []auth.Account) testInput { +func getMockApp(t *testing.T, numGenAccs int, genState types.GenesisState, genAccs []authexported.Account) testInput { mApp := mock.NewApp() staking.RegisterCodec(mApp.Cdc) @@ -105,7 +105,7 @@ func getBeginBlocker(keeper Keeper) sdk.BeginBlocker { } // gov and staking initchainer -func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, supplyKeeper supply.Keeper, accs []auth.Account, genState GenesisState, +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) @@ -122,9 +122,9 @@ func getInitChainer(mapp *mock.App, keeper Keeper, stakingKeeper staking.Keeper, validators := staking.InitGenesis(ctx, stakingKeeper, mapp.AccountKeeper, supplyKeeper, stakingGenesis) if genState.IsEmpty() { - InitGenesis(ctx, keeper, types.DefaultGenesisState()) + InitGenesis(ctx, keeper, mapp.AccountKeeper, types.DefaultGenesisState()) } else { - InitGenesis(ctx, keeper, genState) + InitGenesis(ctx, keeper, mapp.AccountKeeper, genState) } return abci.ResponseInitChain{ Validators: validators, From 54b9cf167f762a785d1a442289f09499dc0c59dc Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Thu, 3 Oct 2019 15:13:38 -0400 Subject: [PATCH 09/13] rebase, add account command --- cmd/kvd/add_account.go | 102 ++++++++++++++++-- go.mod | 4 +- go.sum | 15 +++ x/validator-vesting/genesis.go | 2 +- x/validator-vesting/internal/keeper/keeper.go | 1 + .../internal/keeper/keeper_test.go | 1 - .../types/validator_vesting_account.go | 19 ++-- .../types/validator_vesting_account_test.go | 11 -- 8 files changed, 117 insertions(+), 38 deletions(-) diff --git a/cmd/kvd/add_account.go b/cmd/kvd/add_account.go index 7a4f4fb2..2982a309 100644 --- a/cmd/kvd/add_account.go +++ b/cmd/kvd/add_account.go @@ -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 --vesting-amount --vesting-end-time --vesting-start-time --vesting-periods `, version.ClientName), + ), Args: cobra.ExactArgs(2), RunE: func(_ *cobra.Command, args []string) error { config := ctx.Config @@ -70,22 +83,43 @@ 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 := vesting.NewBaseVestingAccount( + baseAccount, vestingAmt.Sort(), vestingEnd, ) 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 +170,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 +} diff --git a/go.mod b/go.mod index 6e688a90..b0f6d7a8 100644 --- a/go.mod +++ b/go.mod @@ -9,9 +9,9 @@ require ( 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.2 + gopkg.in/yaml.v2 v2.2.3 ) replace github.com/cosmos/cosmos-sdk => ../../cosmos/cosmos-sdk diff --git a/go.sum b/go.sum index 5ddef612..0154b17c 100644 --- a/go.sum +++ b/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= @@ -73,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= @@ -143,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= @@ -189,6 +196,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= @@ -237,6 +246,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= @@ -310,6 +321,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= @@ -323,5 +336,7 @@ 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= 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= diff --git a/x/validator-vesting/genesis.go b/x/validator-vesting/genesis.go index 7ee5b460..17762ec3 100644 --- a/x/validator-vesting/genesis.go +++ b/x/validator-vesting/genesis.go @@ -11,7 +11,7 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, accountKeeper types.AccountKeep accounts := accountKeeper.GetAllAccounts(ctx) for _, a := range accounts { - vv, ok := a.(ValidatorVestingAccount) + vv, ok := a.(*ValidatorVestingAccount) if ok { keeper.SetValidatorVestingAccountKey(ctx, vv.Address) } diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/internal/keeper/keeper.go index 96fc2ecd..f1cf48b1 100644 --- a/x/validator-vesting/internal/keeper/keeper.go +++ b/x/validator-vesting/internal/keeper/keeper.go @@ -163,6 +163,7 @@ func (k Keeper) HandleVestingDebt(ctx sdk.Context, addr sdk.AccAddress, blockTim panic(err) } } + vv = k.GetAccountFromAuthKeeper(ctx, addr) vv.DebtAfterFailedVesting = sdk.NewCoins() k.ak.SetAccount(ctx, vv) } else { diff --git a/x/validator-vesting/internal/keeper/keeper_test.go b/x/validator-vesting/internal/keeper/keeper_test.go index 8299226a..792d7a70 100644 --- a/x/validator-vesting/internal/keeper/keeper_test.go +++ b/x/validator-vesting/internal/keeper/keeper_test.go @@ -203,7 +203,6 @@ func TestHandleVestingDebtNoDebt(t *testing.T) { vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address) require.Equal(t, origCoins, vva.DelegatedVesting) require.Nil(t, vva.DelegatedFree) - require.Nil(t, vva.GetCoins()) } diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/internal/types/validator_vesting_account.go index 35092f55..fb39111f 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account.go +++ b/x/validator-vesting/internal/types/validator_vesting_account.go @@ -43,13 +43,11 @@ type ValidatorVestingAccount struct { // 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 { - cva := &vestingtypes.ContinuousVestingAccount{ - StartTime: startTime, - BaseVestingAccount: bva, - } + pva := &vestingtypes.PeriodicVestingAccount{ - ContinuousVestingAccount: cva, - VestingPeriods: periods, + BaseVestingAccount: bva, + StartTime: startTime, + VestingPeriods: periods, } var vestingPeriodProgress = make([][]int, len(periods)) for i := range vestingPeriodProgress { @@ -79,13 +77,10 @@ func NewValidatorVestingAccount(baseAcc *authtypes.BaseAccount, startTime int64, OriginalVesting: baseAcc.Coins, EndTime: endTime, } - cva := &vestingtypes.ContinuousVestingAccount{ - StartTime: startTime, - BaseVestingAccount: baseVestingAcc, - } pva := &vestingtypes.PeriodicVestingAccount{ - ContinuousVestingAccount: cva, - VestingPeriods: periods, + BaseVestingAccount: baseVestingAcc, + StartTime: startTime, + VestingPeriods: periods, } var vestingPeriodProgress = make([][]int, len(periods)) for i := range vestingPeriodProgress { diff --git a/x/validator-vesting/internal/types/validator_vesting_account_test.go b/x/validator-vesting/internal/types/validator_vesting_account_test.go index b830cd0a..b5e7db4a 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account_test.go +++ b/x/validator-vesting/internal/types/validator_vesting_account_test.go @@ -286,8 +286,6 @@ func TestTrackDelegationValidatorVestingAcc(t *testing.T) { vva.TrackDelegation(now, origCoins) 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) @@ -299,8 +297,6 @@ func TestTrackDelegationValidatorVestingAcc(t *testing.T) { // 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) @@ -313,7 +309,6 @@ func TestTrackDelegationValidatorVestingAcc(t *testing.T) { 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.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000)}, vva.GetCoins()) // require no modifications when delegation amount is zero or not enough funds bacc.SetCoins(origCoins) @@ -323,7 +318,6 @@ func TestTrackDelegationValidatorVestingAcc(t *testing.T) { }) require.Nil(t, vva.DelegatedVesting) require.Nil(t, vva.DelegatedFree) - require.Equal(t, origCoins, vva.GetCoins()) } func TestTrackUndelegationPeriodicVestingAcc(t *testing.T) { @@ -347,7 +341,6 @@ func TestTrackUndelegationPeriodicVestingAcc(t *testing.T) { vva.TrackUndelegation(origCoins) require.Nil(t, vva.DelegatedFree) require.Nil(t, vva.DelegatedVesting) - require.Equal(t, origCoins, vva.GetCoins()) // require the ability to delegate all coins after they have successfully vested bacc.SetCoins(origCoins) @@ -359,7 +352,6 @@ func TestTrackUndelegationPeriodicVestingAcc(t *testing.T) { vva.TrackUndelegation(origCoins) require.Nil(t, vva.DelegatedFree) require.Nil(t, vva.DelegatedVesting) - require.Equal(t, origCoins, vva.GetCoins()) // require panic and no modifications when attempting to undelegate zero coins bacc.SetCoins(origCoins) @@ -369,7 +361,6 @@ func TestTrackUndelegationPeriodicVestingAcc(t *testing.T) { }) require.Nil(t, vva.DelegatedFree) require.Nil(t, vva.DelegatedVesting) - require.Equal(t, origCoins, vva.GetCoins()) // successfuly vest period 1 and delegate to two validators vva = NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90) @@ -381,13 +372,11 @@ func TestTrackUndelegationPeriodicVestingAcc(t *testing.T) { 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) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 25)}, vva.GetCoins()) // 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) - require.Equal(t, sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 75)}, vva.GetCoins()) } func TestGenesisAccountValidate(t *testing.T) { From ad82e971aec716c15cc6866bf4b74fd115b58516 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Fri, 4 Oct 2019 13:55:49 -0400 Subject: [PATCH 10/13] address review comments --- app/app.go | 2 + x/validator-vesting/abci.go | 43 ++++------ x/validator-vesting/abci_test.go | 52 ++++++++++-- x/validator-vesting/internal/keeper/keeper.go | 24 ++---- .../internal/keeper/keeper_test.go | 55 +++++++------ .../internal/keeper/test_common.go | 4 +- .../types/validator_vesting_account.go | 70 ++++++++++------ .../types/validator_vesting_account_test.go | 82 +++++++++---------- x/validator-vesting/spec/03_begin_block.md | 8 +- 9 files changed, 191 insertions(+), 149 deletions(-) diff --git a/app/app.go b/app/app.go index 1826378c..be60f667 100644 --- a/app/app.go +++ b/app/app.go @@ -17,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" @@ -293,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) diff --git a/x/validator-vesting/abci.go b/x/validator-vesting/abci.go index 0300354f..b959d4ef 100644 --- a/x/validator-vesting/abci.go +++ b/x/validator-vesting/abci.go @@ -23,30 +23,25 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) voteInfos = req.LastCommitInfo.GetVotes() validatorVestingKeys := k.GetAllAccountKeys(ctx) for _, key := range validatorVestingKeys { - acc := k.GetAccountFromAuthKeeper(ctx, key[1:]) - 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 + acc := k.GetAccountFromAuthKeeper(ctx, key) + vote, found := voteInfos.FilterByValidatorAddress(acc.ValidatorAddress) + if !found || !vote.SignedLastBlock { + // 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[1:]) + endTimes := k.GetPeriodEndTimes(ctx, key) for i, t := range endTimes { if currentBlockTime.Unix() >= t && previousBlockTime.Unix() < t { - k.UpdateVestedCoinsProgress(ctx, key[1:], i) + k.UpdateVestedCoinsProgress(ctx, key, i) } } // handle any new/remaining debt on the account - k.HandleVestingDebt(ctx, key[1:], currentBlockTime) + k.HandleVestingDebt(ctx, key, currentBlockTime) } k.SetPreviousBlockTime(ctx, currentBlockTime) } @@ -54,24 +49,14 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) // VoteInfos an array of abci.VoteInfo type VoteInfos []abci.VoteInfo -// ContainsValidatorAddress returns true if the input validator address is found in the VoteInfos array -func (vis VoteInfos) ContainsValidatorAddress(consAddress sdk.ConsAddress) bool { - for _, vi := range vis { - votingAddress := sdk.ConsAddress(vi.Validator.Address) - if bytes.Equal(consAddress, votingAddress) { - return true - } - } - return false -} - -// MustFilterByValidatorAddress returns the VoteInfo that has a validator address matching the input validator address -func (vis VoteInfos) MustFilterByValidatorAddress(consAddress sdk.ConsAddress) 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] + return vis[i], true } } - panic("validator address not found") + return abci.VoteInfo{}, false } diff --git a/x/validator-vesting/abci_test.go b/x/validator-vesting/abci_test.go index 918d2566..f63f4c13 100644 --- a/x/validator-vesting/abci_test.go +++ b/x/validator-vesting/abci_test.go @@ -12,6 +12,7 @@ import ( "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 TestBeginBlockerSignedBlock(t *testing.T) { @@ -72,7 +73,7 @@ func TestBeginBlockerSignedBlock(t *testing.T) { height++ blockTime = addHour(blockTime) vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) - require.Equal(t, []int64{0, 1}, vva.MissingSignCount) + require.Equal(t, types.CurrentPeriodProgress{0, 1}, vva.CurrentPeriodProgress) header = abci.Header{Height: height, Time: addHour(blockTime)} @@ -91,7 +92,7 @@ func TestBeginBlockerSignedBlock(t *testing.T) { height++ blockTime = addHour(blockTime) vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) - require.Equal(t, []int64{1, 2}, vva.MissingSignCount) + require.Equal(t, types.CurrentPeriodProgress{1, 2}, vva.CurrentPeriodProgress) // mark the validator as being absent req = abci.RequestBeginBlock{ @@ -108,7 +109,7 @@ func TestBeginBlockerSignedBlock(t *testing.T) { height++ blockTime = addHour(blockTime) vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) - require.Equal(t, []int64{2, 3}, vva.MissingSignCount) + require.Equal(t, types.CurrentPeriodProgress{2, 3}, vva.CurrentPeriodProgress) } func TestBeginBlockerSuccessfulPeriod(t *testing.T) { @@ -158,14 +159,14 @@ func TestBeginBlockerSuccessfulPeriod(t *testing.T) { 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) + require.Equal(t, types.CurrentPeriodProgress{0, 0}, vva.CurrentPeriodProgress) } } 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) + require.Equal(t, []types.VestingProgress{types.VestingProgress{true, true}, types.VestingProgress{false, false}, types.VestingProgress{false, false}}, vva.VestingPeriodProgress) } func TestBeginBlockerUnsuccessfulPeriod(t *testing.T) { @@ -175,12 +176,15 @@ func TestBeginBlockerUnsuccessfulPeriod(t *testing.T) { 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) + 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) @@ -223,7 +227,7 @@ func TestBeginBlockerUnsuccessfulPeriod(t *testing.T) { 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) + 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) @@ -234,4 +238,38 @@ func TestBeginBlockerUnsuccessfulPeriod(t *testing.T) { }) // 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()) } diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/internal/keeper/keeper.go index f1cf48b1..dd7e380c 100644 --- a/x/validator-vesting/internal/keeper/keeper.go +++ b/x/validator-vesting/internal/keeper/keeper.go @@ -83,7 +83,7 @@ func (k Keeper) IterateAccountKeys(ctx sdk.Context, cb func(accountKey []byte) ( func (k Keeper) GetAllAccountKeys(ctx sdk.Context) (keys [][]byte) { k.IterateAccountKeys(ctx, func(key []byte) (stop bool) { - keys = append(keys, key) + keys = append(keys, key[1:]) return false }) return keys @@ -103,39 +103,33 @@ func (k Keeper) GetAccountFromAuthKeeper(ctx sdk.Context, addr sdk.AccAddress) * func (k Keeper) UpdateMissingSignCount(ctx sdk.Context, addr sdk.AccAddress, missedBlock bool) { vv := k.GetAccountFromAuthKeeper(ctx, addr) if missedBlock { - vv.MissingSignCount[0]++ + vv.CurrentPeriodProgress.MissedBlocks++ } - vv.MissingSignCount[1]++ + 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) - - threshold := sdk.NewDec(vv.SigningThreshold) - blocksMissed := sdk.NewDec(vv.MissingSignCount[0]) - blockCount := sdk.NewDec(vv.MissingSignCount[1]) var successfulVest bool - if blockCount.IsZero() { + if sdk.NewDec(vv.CurrentPeriodProgress.TotalBlocks).IsZero() { successfulVest = true } else { - blocksSigned := blockCount.Sub(blocksMissed) - percentageBlocksSigned := blocksSigned.Quo(blockCount).Mul(sdk.NewDec(100)) - successfulVest = percentageBlocksSigned.GTE(threshold) + successfulVest = vv.CurrentPeriodProgress.SignedPercetageIsOverThreshold(vv.SigningThreshold) } if successfulVest { - vv.VestingPeriodProgress[period][1] = 1 + vv.VestingPeriodProgress[period].VestingSuccessful = true } else { - vv.VestingPeriodProgress[period][1] = 0 + vv.VestingPeriodProgress[period].VestingSuccessful = false notVestedTokens := vv.VestingPeriods[period].Amount // add the tokens that did not vest to DebtAfterFailedVesting vv.DebtAfterFailedVesting = vv.DebtAfterFailedVesting.Add(notVestedTokens) } - vv.VestingPeriodProgress[period][0] = 1 + vv.VestingPeriodProgress[period].PeriodComplete = true // reset the number of missed blocks and total number of blocks in the period to zero - vv.MissingSignCount = []int64{0, 0} + vv.CurrentPeriodProgress = types.CurrentPeriodProgress{0, 0} k.ak.SetAccount(ctx, vv) } diff --git a/x/validator-vesting/internal/keeper/keeper_test.go b/x/validator-vesting/internal/keeper/keeper_test.go index 792d7a70..19d11010 100644 --- a/x/validator-vesting/internal/keeper/keeper_test.go +++ b/x/validator-vesting/internal/keeper/keeper_test.go @@ -11,6 +11,7 @@ import ( 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) { @@ -36,7 +37,7 @@ func TestGetSetValidatorVestingAccounts(t *testing.T) { keys := keeper.GetAllAccountKeys(ctx) require.Equal(t, 1, len(keys)) for _, k := range keys { - require.NotPanics(t, func() { keeper.GetAccountFromAuthKeeper(ctx, k[1:]) }) + require.NotPanics(t, func() { keeper.GetAccountFromAuthKeeper(ctx, k) }) } vvAccounts := ValidatorVestingTestAccounts(10) @@ -50,7 +51,7 @@ func TestGetSetValidatorVestingAccounts(t *testing.T) { var ikeys [][]byte keeper.IterateAccountKeys(ctx, func(accountKey []byte) bool { - if bytes.Equal(accountKey, keys[0]) { + if bytes.Equal(accountKey[1:], keys[0]) { ikeys = append(ikeys, accountKey) return true } @@ -109,17 +110,17 @@ func TestSetMissingSignCount(t *testing.T) { ak.SetAccount(ctx, vva) // require empty array after ValidatorVestingAccount is initialized - require.Equal(t, []int64{0, 0}, vva.MissingSignCount) + 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, []int64{0, 1}, vva.MissingSignCount) + 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, []int64{1, 2}, vva.MissingSignCount) + require.Equal(t, types.CurrentPeriodProgress{1, 2}, vva.CurrentPeriodProgress) } @@ -132,56 +133,56 @@ 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, 0}, {0, 0}}, vva.VestingPeriodProgress) + 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.MissingSignCount[0] = 0 - vva.MissingSignCount[1] = 100 + 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 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 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, []int64{0, 0}, vva.MissingSignCount) + 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.MissingSignCount[0] = 0 - vva.MissingSignCount[1] = 0 + 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 1 - require.Equal(t, [][]int{{1, 1}, {0, 0}, {0, 0}}, vva.VestingPeriodProgress) + // 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, []int64{0, 0}, vva.MissingSignCount) + 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.MissingSignCount[0] = 50 - vva.MissingSignCount[1] = 100 + 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 {1,0} - require.Equal(t, [][]int{{1, 0}, {0, 0}, {0, 0}}, vva.VestingPeriodProgress) + // 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, []int64{0, 0}, vva.MissingSignCount) + require.Equal(t, types.CurrentPeriodProgress{0, 0}, vva.CurrentPeriodProgress) } func TestHandleVestingDebtNoDebt(t *testing.T) { @@ -240,8 +241,8 @@ func TestHandleVestingDebtForcedUnbond(t *testing.T) { require.Equal(t, 1, delegations) // period 0 passes and the threshold is not met - vva.MissingSignCount[0] = 50 - vva.MissingSignCount[1] = 100 + vva.CurrentPeriodProgress.MissedBlocks = 50 + vva.CurrentPeriodProgress.TotalBlocks = 100 ak.SetAccount(ctx, vva) keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0) vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address) @@ -277,8 +278,8 @@ func TestHandleVestingDebtBurn(t *testing.T) { _ = staking.EndBlocker(ctx, stakingKeeper) // period 0 passes and the threshold is not met - vva.MissingSignCount[0] = 50 - vva.MissingSignCount[1] = 100 + vva.CurrentPeriodProgress.MissedBlocks = 50 + vva.CurrentPeriodProgress.TotalBlocks = 100 ak.SetAccount(ctx, vva) keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0) vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address) @@ -320,8 +321,8 @@ func TestHandleVestingDebtReturn(t *testing.T) { _ = staking.EndBlocker(ctx, stakingKeeper) // period 0 passes and the threshold is not met - vva.MissingSignCount[0] = 50 - vva.MissingSignCount[1] = 100 + vva.CurrentPeriodProgress.MissedBlocks = 50 + vva.CurrentPeriodProgress.TotalBlocks = 100 ak.SetAccount(ctx, vva) keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0) vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address) diff --git a/x/validator-vesting/internal/keeper/test_common.go b/x/validator-vesting/internal/keeper/test_common.go index 5ae88d3c..8c630329 100644 --- a/x/validator-vesting/internal/keeper/test_common.go +++ b/x/validator-vesting/internal/keeper/test_common.go @@ -122,6 +122,8 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context 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{ @@ -133,7 +135,7 @@ func CreateTestInput(t *testing.T, isCheckTx bool, initPower int64) (sdk.Context supplyKeeper := supply.NewKeeper(cdc, keySupply, accountKeeper, bankKeeper, maccPerms) stakingKeeper := staking.NewKeeper(cdc, keyStaking, supplyKeeper, pk.Subspace(staking.DefaultParamspace), staking.DefaultCodespace) - stakingKeeper.SetParams(ctx, staking.DefaultParams()) + stakingKeeper.SetParams(ctx, stakingParams) keeper := NewKeeper(cdc, keyValidatorVesting, accountKeeper, bankKeeper, supplyKeeper, stakingKeeper) diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/internal/types/validator_vesting_account.go index fb39111f..bf7cce24 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account.go +++ b/x/validator-vesting/internal/types/validator_vesting_account.go @@ -23,6 +23,30 @@ func init() { authtypes.RegisterAccountTypeCodec(&ValidatorVestingAccount{}, "cosmos-sdk/ValidatorVestingAccount") } +type VestingProgress struct { + PeriodComplete bool `json:"period_complete" yaml:"period_complete"` + VestingSuccessful bool `json:"vesting_successful" yaml:"vesting_successful"` +} + +type CurrentPeriodProgress struct { + MissedBlocks int64 `json:"missed_blocks" yaml:"missed_blocks"` + TotalBlocks int64 `json:"total_blocks" yaml:"total_blocks` +} + +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 +} + +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 @@ -32,12 +56,12 @@ func init() { // 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"` - MissingSignCount []int64 `json:"missing_sign_count" yaml:"missing_sign_count"` - VestingPeriodProgress [][]int `json:"vesting_period_progress" yaml:"vesting_period_progress"` - DebtAfterFailedVesting sdk.Coins `json:"debt_after_failed_vesting" yaml:"debt_after_failed_vesting"` + 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:"missing_sign_count" yaml:"missing_sign_count"` + 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 @@ -49,9 +73,9 @@ func NewValidatorVestingAccountRaw(bva *vestingtypes.BaseVestingAccount, StartTime: startTime, VestingPeriods: periods, } - var vestingPeriodProgress = make([][]int, len(periods)) - for i := range vestingPeriodProgress { - vestingPeriodProgress[i] = make([]int, 2) + var vestingPeriodProgress []VestingProgress + for i := 0; i < len(periods); i++ { + vestingPeriodProgress = append(vestingPeriodProgress, VestingProgress{false, false}) } return &ValidatorVestingAccount{ @@ -59,7 +83,7 @@ func NewValidatorVestingAccountRaw(bva *vestingtypes.BaseVestingAccount, ValidatorAddress: validatorAddress, ReturnAddress: returnAddress, SigningThreshold: signingThreshold, - MissingSignCount: []int64{0, 0}, + CurrentPeriodProgress: CurrentPeriodProgress{0, 0}, VestingPeriodProgress: vestingPeriodProgress, DebtAfterFailedVesting: sdk.NewCoins(), } @@ -82,21 +106,19 @@ func NewValidatorVestingAccount(baseAcc *authtypes.BaseAccount, startTime int64, StartTime: startTime, VestingPeriods: periods, } - var vestingPeriodProgress = make([][]int, len(periods)) - for i := range vestingPeriodProgress { - vestingPeriodProgress[i] = make([]int, 2) + var vestingPeriodProgress []VestingProgress + for i := 0; i < len(periods); i++ { + vestingPeriodProgress = append(vestingPeriodProgress, VestingProgress{false, false}) } - debt := sdk.NewCoins() - return &ValidatorVestingAccount{ PeriodicVestingAccount: pva, ValidatorAddress: validatorAddress, ReturnAddress: returnAddress, SigningThreshold: signingThreshold, - MissingSignCount: []int64{0, 0}, + CurrentPeriodProgress: CurrentPeriodProgress{0, 0}, VestingPeriodProgress: vestingPeriodProgress, - DebtAfterFailedVesting: debt, + DebtAfterFailedVesting: sdk.NewCoins(), } } @@ -111,8 +133,7 @@ func (vva ValidatorVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins for i := 0; i < numberPeriods; i++ { x := blockTime.Unix() - currentPeriodStartTime if x >= vva.VestingPeriods[i].Length { - vestingComplete := vva.VestingPeriodProgress[i][0] == 1 - if vestingComplete { + if vva.VestingPeriodProgress[i].PeriodComplete { vestedCoins = vestedCoins.Add(vva.VestingPeriods[i].Amount) } currentPeriodStartTime += vva.VestingPeriods[i].Length @@ -129,9 +150,8 @@ func (vva ValidatorVestingAccount) GetFailedVestedCoins() sdk.Coins { var failedVestedCoins sdk.Coins numberPeriods := len(vva.VestingPeriods) for i := 0; i < numberPeriods; i++ { - if vva.VestingPeriodProgress[i][0] == 1 { - vestedFailure := vva.VestingPeriodProgress[i][1] == 0 - if vestedFailure { + if vva.VestingPeriodProgress[i].PeriodComplete { + if !vva.VestingPeriodProgress[i].VestingSuccessful { failedVestedCoins = failedVestedCoins.Add(vva.VestingPeriods[i].Amount) } } else { @@ -198,8 +218,8 @@ func (vva ValidatorVestingAccount) MarshalYAML() (interface{}, error) { ValidatorAddress sdk.ConsAddress ReturnAddress sdk.AccAddress SigningThreshold int64 - MissingSignCount []int64 - VestingPeriodProgress [][]int + CurrentPeriodProgress CurrentPeriodProgress + VestingPeriodProgress []VestingProgress DebtAfterFailedVesting sdk.Coins }{ Address: vva.Address, @@ -216,7 +236,7 @@ func (vva ValidatorVestingAccount) MarshalYAML() (interface{}, error) { ValidatorAddress: vva.ValidatorAddress, ReturnAddress: vva.ReturnAddress, SigningThreshold: vva.SigningThreshold, - MissingSignCount: vva.MissingSignCount, + CurrentPeriodProgress: vva.CurrentPeriodProgress, VestingPeriodProgress: vva.VestingPeriodProgress, DebtAfterFailedVesting: vva.DebtAfterFailedVesting, }) diff --git a/x/validator-vesting/internal/types/validator_vesting_account_test.go b/x/validator-vesting/internal/types/validator_vesting_account_test.go index b5e7db4a..2d5a1f51 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account_test.go +++ b/x/validator-vesting/internal/types/validator_vesting_account_test.go @@ -68,51 +68,51 @@ func TestGetVestedCoinsValidatorVestingAcc(t *testing.T) { require.Nil(t, vestedCoins) // require 50% of coins vested after successful period 1 vesting - vva.VestingPeriodProgress[0] = []int{1, 1} + 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] = []int{1, 0} + 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] = []int{1, 1} + 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] = []int{1, 1} + 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] = []int{1, 1} - vva.VestingPeriodProgress[1] = []int{1, 1} + 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] = []int{1, 1} - vva.VestingPeriodProgress[1] = []int{1, 0} + 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] = []int{1, 1} - vva.VestingPeriodProgress[1] = []int{1, 1} - vva.VestingPeriodProgress[2] = []int{1, 1} + 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] = []int{1, 0} - vva.VestingPeriodProgress[1] = []int{1, 0} - vva.VestingPeriodProgress[2] = []int{1, 0} + 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) @@ -143,48 +143,48 @@ func TestGetVestingCoinsValidatorVestingAcc(t *testing.T) { require.Equal(t, origCoins, vestingCoins) // require 50% of coins vesting after successful period 1 vesting - vva.VestingPeriodProgress[0] = []int{1, 1} + 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] = []int{1, 0} + 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] = []int{1, 1} + vva.VestingPeriodProgress[0] = VestingProgress{true, true} // should never happen, but still won't affect vesting balance - vva.VestingPeriodProgress[1] = []int{1, 1} + 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] = []int{1, 1} - vva.VestingPeriodProgress[1] = []int{1, 1} + 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] = []int{1, 1} - vva.VestingPeriodProgress[1] = []int{1, 1} + 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] = []int{1, 1} - vva.VestingPeriodProgress[1] = []int{1, 1} - vva.VestingPeriodProgress[2] = []int{1, 1} + 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] = []int{1, 0} - vva.VestingPeriodProgress[1] = []int{1, 0} - vva.VestingPeriodProgress[2] = []int{1, 0} + 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) @@ -211,12 +211,12 @@ 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] = []int{1, 1} + 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] = []int{1, 0} + 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) @@ -225,7 +225,7 @@ func TestSpendableCoinsValidatorVestingAccount(t *testing.T) { 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] = []int{1, 1} + 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) @@ -253,14 +253,14 @@ func TestGetFailedVestedCoins(t *testing.T) { bacc.SetCoins(origCoins) vva := NewValidatorVestingAccount(&bacc, now.Unix(), periods, testConsAddr, nil, 90) - vva.VestingPeriodProgress[0] = []int{1, 0} + 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] = []int{1, 1} + vva.VestingPeriodProgress[0] = VestingProgress{true, true} require.Equal(t, sdk.Coins(nil), vva.GetFailedVestedCoins(), @@ -290,9 +290,9 @@ func TestTrackDelegationValidatorVestingAcc(t *testing.T) { // 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.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) @@ -305,7 +305,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] = []int{1, 1} + 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) @@ -345,9 +345,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] = []int{1, 1} - vva.VestingPeriodProgress[1] = []int{1, 1} - vva.VestingPeriodProgress[2] = []int{1, 1} + 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) @@ -364,7 +364,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] = []int{1, 1} + 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)}) diff --git a/x/validator-vesting/spec/03_begin_block.md b/x/validator-vesting/spec/03_begin_block.md index b9528c51..967bb1ca 100644 --- a/x/validator-vesting/spec/03_begin_block.md +++ b/x/validator-vesting/spec/03_begin_block.md @@ -14,7 +14,7 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) voteInfos = req.LastCommitInfo.GetVotes() validatorVestingKeys := k.GetAllAccountKeys(ctx) for _, key := range validatorVestingKeys { - acc := k.GetAccountFromAuthKeeper(ctx, key[1:]) + acc := k.GetAccountFromAuthKeeper(ctx, key) if voteInfos.ContainsValidatorAddress(acc.ValidatorAddress) { vote := voteInfos.MustFilterByValidatorAddress(acc.ValidatorAddress) if !vote.SignedLastBlock { @@ -29,15 +29,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[1:]) + endTimes := k.GetPeriodEndTimes(ctx, key) for i, t := range endTimes { if currentBlockTime.Unix() >= t && previousBlockTime.Unix() < t { - k.UpdateVestedCoinsProgress(ctx, key[1:], i) + k.UpdateVestedCoinsProgress(ctx, key, i) } } // handle any new/remaining debt on the account - k.HandleVestingDebt(ctx, key[1:], currentBlockTime) + k.HandleVestingDebt(ctx, key, currentBlockTime) } k.SetPreviousBlockTime(ctx, currentBlockTime) } From 8058f4b13a61b42a8b699d653cbe49cdf6157e1c Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Fri, 4 Oct 2019 15:46:41 -0400 Subject: [PATCH 11/13] fix simulation initialization --- x/validator-vesting/simulation/genesis.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/x/validator-vesting/simulation/genesis.go b/x/validator-vesting/simulation/genesis.go index 86c20a68..e8bbeab3 100644 --- a/x/validator-vesting/simulation/genesis.go +++ b/x/validator-vesting/simulation/genesis.go @@ -45,7 +45,13 @@ func RandomizedGenState(simState *module.SimulationState) { panic(err) } } else { - consAdd := getRandomValidatorConsAddr(simState, simulation.RandIntBetween(simState.Rand, 0, int(simState.NumBonded)-1)) + 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( From 49279a3ce5699645d983b2162a6c8e1b315ac4d8 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Mon, 7 Oct 2019 16:21:24 -0400 Subject: [PATCH 12/13] follow getter setter pattern on keeper --- go.mod | 2 +- go.sum | 4 ++ x/validator-vesting/internal/keeper/keeper.go | 45 +++++++++++++------ .../types/validator_vesting_account.go | 6 ++- 4 files changed, 42 insertions(+), 15 deletions(-) diff --git a/go.mod b/go.mod index b0f6d7a8..5e39708d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/tendermint/go-amino v0.15.0 github.com/tendermint/tendermint v0.32.5 github.com/tendermint/tm-db v0.2.0 - gopkg.in/yaml.v2 v2.2.3 + gopkg.in/yaml.v2 v2.2.4 ) replace github.com/cosmos/cosmos-sdk => ../../cosmos/cosmos-sdk diff --git a/go.sum b/go.sum index 0154b17c..2b1c40e9 100644 --- a/go.sum +++ b/go.sum @@ -170,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= @@ -338,5 +340,7 @@ 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= diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/internal/keeper/keeper.go index dd7e380c..bce5603b 100644 --- a/x/validator-vesting/internal/keeper/keeper.go +++ b/x/validator-vesting/internal/keeper/keeper.go @@ -120,16 +120,30 @@ func (k Keeper) UpdateVestedCoinsProgress(ctx sdk.Context, addr sdk.AccAddress, } if successfulVest { - vv.VestingPeriodProgress[period].VestingSuccessful = true + k.SetVestingProgress(ctx, addr, period, true) } else { - vv.VestingPeriodProgress[period].VestingSuccessful = false - notVestedTokens := vv.VestingPeriods[period].Amount - // add the tokens that did not vest to DebtAfterFailedVesting - vv.DebtAfterFailedVesting = vv.DebtAfterFailedVesting.Add(notVestedTokens) + k.SetVestingProgress(ctx, addr, period, false) + k.AddDebt(ctx, addr, vv.VestingPeriods[period].Amount) } - vv.VestingPeriodProgress[period].PeriodComplete = true - // reset the number of missed blocks and total number of blocks in the period to zero - vv.CurrentPeriodProgress = types.CurrentPeriodProgress{0, 0} + 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) +} + +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) +} + +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) } @@ -138,8 +152,8 @@ func (k Keeper) UpdateVestedCoinsProgress(ctx sdk.Context, addr sdk.AccAddress, // 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 { + + if !vv.DebtAfterFailedVesting.IsZero() { spendableCoins := vv.SpendableCoins(blockTime) if spendableCoins.IsAllGTE(vv.DebtAfterFailedVesting) { if vv.ReturnAddress != nil { @@ -157,9 +171,7 @@ func (k Keeper) HandleVestingDebt(ctx sdk.Context, addr sdk.AccAddress, blockTim panic(err) } } - vv = k.GetAccountFromAuthKeeper(ctx, addr) - vv.DebtAfterFailedVesting = sdk.NewCoins() - k.ak.SetAccount(ctx, vv) + 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, @@ -172,6 +184,13 @@ func (k Keeper) HandleVestingDebt(ctx sdk.Context, addr sdk.AccAddress, blockTim } } +// 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 diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/internal/types/validator_vesting_account.go index bf7cce24..27d7bde1 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account.go +++ b/x/validator-vesting/internal/types/validator_vesting_account.go @@ -23,16 +23,19 @@ 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 @@ -42,6 +45,7 @@ func (cpp CurrentPeriodProgress) GetSignedPercentage() sdk.Dec { 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)) @@ -169,7 +173,7 @@ func (vva ValidatorVestingAccount) GetVestingCoins(blockTime time.Time) sdk.Coin // 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)) + return vva.BaseVestingAccount.SpendableCoinsVestingAccount(vva.GetVestingCoins(blockTime)) } // TrackDelegation tracks a desired delegation amount by setting the appropriate From 3908870761588015ecc937053565813a053b0647 Mon Sep 17 00:00:00 2001 From: Kevin Davis Date: Wed, 9 Oct 2019 18:42:23 -0400 Subject: [PATCH 13/13] rebase, handle first block --- cmd/kvd/add_account.go | 5 +- x/validator-vesting/abci.go | 39 +++--- x/validator-vesting/abci_test.go | 114 ++++++++++++++++-- x/validator-vesting/alias.go | 55 +++++++-- x/validator-vesting/internal/keeper/keeper.go | 16 +++ .../internal/keeper/keeper_test.go | 21 +++- .../types/validator_vesting_account.go | 4 +- .../types/validator_vesting_account_test.go | 2 +- x/validator-vesting/module.go | 2 - x/validator-vesting/simulation/genesis.go | 2 +- 10 files changed, 215 insertions(+), 45 deletions(-) diff --git a/cmd/kvd/add_account.go b/cmd/kvd/add_account.go index 2982a309..95ae9b0e 100644 --- a/cmd/kvd/add_account.go +++ b/cmd/kvd/add_account.go @@ -94,9 +94,12 @@ func AddGenesisAccountCmd( baseAccount := auth.NewBaseAccount(addr, coins.Sort(), nil, 0, 0) if !vestingAmt.IsZero() { - baseVestingAccount := vesting.NewBaseVestingAccount( + 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 != "": diff --git a/x/validator-vesting/abci.go b/x/validator-vesting/abci.go index b959d4ef..43a4f0b8 100644 --- a/x/validator-vesting/abci.go +++ b/x/validator-vesting/abci.go @@ -24,24 +24,31 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) validatorVestingKeys := k.GetAllAccountKeys(ctx) for _, key := range validatorVestingKeys { acc := k.GetAccountFromAuthKeeper(ctx, key) - vote, found := voteInfos.FilterByValidatorAddress(acc.ValidatorAddress) - if !found || !vote.SignedLastBlock { - // 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) + 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) } - // handle any new/remaining debt on the account - k.HandleVestingDebt(ctx, key, currentBlockTime) } k.SetPreviousBlockTime(ctx, currentBlockTime) } diff --git a/x/validator-vesting/abci_test.go b/x/validator-vesting/abci_test.go index f63f4c13..b2e65667 100644 --- a/x/validator-vesting/abci_test.go +++ b/x/validator-vesting/abci_test.go @@ -15,6 +15,81 @@ import ( "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() @@ -51,13 +126,13 @@ func TestBeginBlockerSignedBlock(t *testing.T) { vva.ValidatorAddress = val1.ConsAddress() ak.SetAccount(ctx, vva) - height := int64(0) + 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, @@ -76,7 +151,26 @@ func TestBeginBlockerSignedBlock(t *testing.T) { 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, @@ -92,8 +186,10 @@ func TestBeginBlockerSignedBlock(t *testing.T) { height++ blockTime = addHour(blockTime) vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) - require.Equal(t, types.CurrentPeriodProgress{1, 2}, vva.CurrentPeriodProgress) + 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, @@ -106,14 +202,12 @@ func TestBeginBlockerSignedBlock(t *testing.T) { } BeginBlocker(ctx, req, vvk) - height++ - blockTime = addHour(blockTime) vva = vvk.GetAccountFromAuthKeeper(ctx, vva.Address) - require.Equal(t, types.CurrentPeriodProgress{2, 3}, vva.CurrentPeriodProgress) + require.Equal(t, types.CurrentPeriodProgress{2, 4}, vva.CurrentPeriodProgress) } func TestBeginBlockerSuccessfulPeriod(t *testing.T) { - height := int64(0) + height := int64(1) now := tmtime.Now() blockTime := now numBlocks := int64(14) @@ -156,7 +250,7 @@ func TestBeginBlockerSuccessfulPeriod(t *testing.T) { BeginBlocker(ctx, req, vvk) blockTime = addHour(blockTime) - if height == 11 { + 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) @@ -170,10 +264,10 @@ func TestBeginBlockerSuccessfulPeriod(t *testing.T) { } func TestBeginBlockerUnsuccessfulPeriod(t *testing.T) { - height := int64(0) + height := int64(1) now := tmtime.Now() blockTime := now - numBlocks := int64(12) + 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) diff --git a/x/validator-vesting/alias.go b/x/validator-vesting/alias.go index 9f9914e9..dfd168bb 100644 --- a/x/validator-vesting/alias.go +++ b/x/validator-vesting/alias.go @@ -1,7 +1,10 @@ +// 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 -// nolint -// DONTCOVER import ( "github.com/kava-labs/kava/x/validator-vesting/internal/keeper" "github.com/kava-labs/kava/x/validator-vesting/internal/types" @@ -13,19 +16,49 @@ const ( ) var ( - NewValidatorVestingAccountRaw = types.NewValidatorVestingAccountRaw - NewValidatorVestingAccount = types.NewValidatorVestingAccount - NewGenesisState = types.NewGenesisState - DefaultGenesisState = types.DefaultGenesisState - RegisterCodec = types.RegisterCodec - ValidatorVestingAccountPrefix = types.ValidatorVestingAccountPrefix + // 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 - ValidatorVestingAccountKey = types.ValidatorVestingAccountKey - NewKeeper = keeper.NewKeeper + 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 - Keeper = keeper.Keeper + VestingProgress = types.VestingProgress + CurrentPeriodProgress = types.CurrentPeriodProgress ValidatorVestingAccount = types.ValidatorVestingAccount + Keeper = keeper.Keeper ) diff --git a/x/validator-vesting/internal/keeper/keeper.go b/x/validator-vesting/internal/keeper/keeper.go index bce5603b..bb0ed89e 100644 --- a/x/validator-vesting/internal/keeper/keeper.go +++ b/x/validator-vesting/internal/keeper/keeper.go @@ -135,12 +135,14 @@ func (k Keeper) SetVestingProgress(ctx sdk.Context, addr sdk.AccAddress, period 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} @@ -202,3 +204,17 @@ func (k Keeper) GetPeriodEndTimes(ctx sdk.Context, addr sdk.AccAddress) []int64 } 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 +} diff --git a/x/validator-vesting/internal/keeper/keeper_test.go b/x/validator-vesting/internal/keeper/keeper_test.go index 19d11010..f304ddbd 100644 --- a/x/validator-vesting/internal/keeper/keeper_test.go +++ b/x/validator-vesting/internal/keeper/keeper_test.go @@ -102,6 +102,25 @@ func TestGetEndTImes(t *testing.T) { 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) @@ -142,7 +161,7 @@ func TestUpdateVestedCoinsProgress(t *testing.T) { vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address) keeper.UpdateVestedCoinsProgress(ctx, vva.Address, 0) vva = keeper.GetAccountFromAuthKeeper(ctx, vva.Address) - // require that debt is zero + // 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) diff --git a/x/validator-vesting/internal/types/validator_vesting_account.go b/x/validator-vesting/internal/types/validator_vesting_account.go index 27d7bde1..19a414fb 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account.go +++ b/x/validator-vesting/internal/types/validator_vesting_account.go @@ -32,7 +32,7 @@ type VestingProgress struct { // 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` + TotalBlocks int64 `json:"total_blocks" yaml:"total_blocks"` } // GetSignedPercentage returns the percentage of blocks signed for the current vesting period @@ -63,7 +63,7 @@ type ValidatorVestingAccount struct { 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:"missing_sign_count" yaml:"missing_sign_count"` + 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"` } diff --git a/x/validator-vesting/internal/types/validator_vesting_account_test.go b/x/validator-vesting/internal/types/validator_vesting_account_test.go index 2d5a1f51..4c19c68e 100644 --- a/x/validator-vesting/internal/types/validator_vesting_account_test.go +++ b/x/validator-vesting/internal/types/validator_vesting_account_test.go @@ -35,7 +35,7 @@ func TestNewAccount(t *testing.T) { origCoins := sdk.Coins{sdk.NewInt64Coin(feeDenom, 1000), sdk.NewInt64Coin(stakeDenom, 100)} bacc := auth.NewBaseAccountWithAddress(testAddr) bacc.SetCoins(origCoins) - bva := vesting.NewBaseVestingAccount(&bacc, origCoins, endTime) + 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 diff --git a/x/validator-vesting/module.go b/x/validator-vesting/module.go index 19c8f220..075811e9 100644 --- a/x/validator-vesting/module.go +++ b/x/validator-vesting/module.go @@ -3,7 +3,6 @@ package validatorvesting import ( "encoding/json" "math/rand" - "os" "github.com/gorilla/mux" "github.com/spf13/cobra" @@ -40,7 +39,6 @@ func (AppModuleBasic) RegisterCodec(cdc *codec.Codec) { // DefaultGenesis returns default genesis state as raw bytes for the validator-vesting // module. func (AppModuleBasic) DefaultGenesis() json.RawMessage { - types.ModuleCdc.PrintTypes(os.Stdout) return types.ModuleCdc.MustMarshalJSON(types.DefaultGenesisState()) } diff --git a/x/validator-vesting/simulation/genesis.go b/x/validator-vesting/simulation/genesis.go index e8bbeab3..cccdc358 100644 --- a/x/validator-vesting/simulation/genesis.go +++ b/x/validator-vesting/simulation/genesis.go @@ -35,7 +35,7 @@ func RandomizedGenState(simState *module.SimulationState) { duration := va.GetEndTime() - va.GetStartTime() vestingPeriods := getRandomVestingPeriods(duration, simState.Rand, va.GetCoins()) vestingCoins := getVestingCoins(vestingPeriods) - bva := vestingtypes.NewBaseVestingAccount(&bacc, vestingCoins, va.GetEndTime()) + bva, _ := vestingtypes.NewBaseVestingAccount(&bacc, vestingCoins, va.GetEndTime()) var gacc authexported.GenesisAccount if simState.Rand.Intn(100) < 50 { // convert to periodic vesting account 50%