Write back account changes after tracking delegation/undelegation for vesting accounts (#8865)

Delegations and undelegations calculations performed during
accounting operations for vesting accounts were correct but
were not written back to the account store.

closes: #8601
closes: #8812

Co-authored-by: Alessio Treglia <alessio@tendermint.com>
Co-authored-by: Aleksandr Bezobchuk <alexanderbez@users.noreply.github.com>
Co-authored-by: Jonathan Gimeno <jgimeno@gmail.com>
Co-authored-by: Frojdi Dymylja <frojdi.dymylja@gmail.com>
Co-authored-by: Adam Bozanich <adam.boz@gmail.com>
Co-authored-by: Amaury <1293565+amaurym@users.noreply.github.com>
Co-authored-by: SaReN <sahithnarahari@gmail.com>
This commit is contained in:
Gianguido Sora 2021-04-08 17:20:29 +02:00 committed by GitHub
parent 3a5550a938
commit 93965e0bcc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1019 additions and 2 deletions

View File

@ -206,7 +206,7 @@ func (ak AccountKeeper) GetModuleAccount(ctx sdk.Context, moduleName string) typ
}
// SetModuleAccount sets the module account to the auth account store
func (ak AccountKeeper) SetModuleAccount(ctx sdk.Context, macc types.ModuleAccountI) { //nolint:interfacer
func (ak AccountKeeper) SetModuleAccount(ctx sdk.Context, macc types.ModuleAccountI) {
ak.SetAccount(ctx, macc)
}

View File

@ -0,0 +1,42 @@
package keeper
import (
v043 "github.com/cosmos/cosmos-sdk/x/auth/legacy/v043"
"github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/gogo/protobuf/grpc"
sdk "github.com/cosmos/cosmos-sdk/types"
)
// Migrator is a struct for handling in-place store migrations.
type Migrator struct {
keeper AccountKeeper
queryServer grpc.Server
}
// NewMigrator returns a new Migrator.
func NewMigrator(keeper AccountKeeper, queryServer grpc.Server) Migrator {
return Migrator{keeper: keeper, queryServer: queryServer}
}
// Migrate1to2 migrates from version 1 to 2.
func (m Migrator) Migrate1to2(ctx sdk.Context) error {
var iterErr error
m.keeper.IterateAccounts(ctx, func(account types.AccountI) (stop bool) {
wb, err := v043.MigrateAccount(ctx, account, m.queryServer)
if err != nil {
iterErr = err
return true
}
if wb == nil {
return false
}
m.keeper.SetAccount(ctx, wb)
return false
})
return iterErr
}

268
x/auth/legacy/v043/store.go Normal file
View File

@ -0,0 +1,268 @@
package v043
import (
"errors"
"fmt"
"github.com/cosmos/cosmos-sdk/baseapp"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/gogo/protobuf/grpc"
"github.com/gogo/protobuf/proto"
abci "github.com/tendermint/tendermint/abci/types"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
const (
delegatorDelegationPath = "/cosmos.staking.v1beta1.Query/DelegatorDelegations"
stakingParamsPath = "/cosmos.staking.v1beta1.Query/Params"
delegatorUnbondingDelegationsPath = "/cosmos.staking.v1beta1.Query/DelegatorUnbondingDelegations"
balancesPath = "/cosmos.bank.v1beta1.Query/AllBalances"
)
func migrateVestingAccounts(ctx sdk.Context, account types.AccountI, queryServer grpc.Server) (types.AccountI, error) {
bondDenom, err := getBondDenom(ctx, queryServer)
if err != nil {
return nil, err
}
asVesting, ok := account.(exported.VestingAccount)
if !ok {
return nil, nil
}
addr := account.GetAddress().String()
balance, err := getBalance(
ctx,
addr,
queryServer,
)
if err != nil {
return nil, err
}
delegations, err := getDelegatorDelegationsSum(
ctx,
addr,
queryServer,
)
if err != nil {
return nil, err
}
unbondingDelegations, err := getDelegatorUnbondingDelegationsSum(
ctx,
addr,
bondDenom,
queryServer,
)
if err != nil {
return nil, err
}
delegations = delegations.Add(unbondingDelegations...)
asVesting, ok = resetVestingDelegatedBalances(asVesting)
if !ok {
return nil, nil
}
// balance before any delegation includes balance of delegation
for _, coin := range delegations {
balance = balance.Add(coin)
}
asVesting.TrackDelegation(ctx.BlockTime(), balance, delegations)
return asVesting.(types.AccountI), nil
}
func resetVestingDelegatedBalances(evacct exported.VestingAccount) (exported.VestingAccount, bool) {
// reset `DelegatedVesting` and `DelegatedFree` to zero
df := sdk.NewCoins()
dv := sdk.NewCoins()
switch vacct := evacct.(type) {
case *vestingtypes.ContinuousVestingAccount:
vacct.DelegatedVesting = dv
vacct.DelegatedFree = df
return vacct, true
case *vestingtypes.DelayedVestingAccount:
vacct.DelegatedVesting = dv
vacct.DelegatedFree = df
return vacct, true
case *vestingtypes.PeriodicVestingAccount:
vacct.DelegatedVesting = dv
vacct.DelegatedFree = df
return vacct, true
default:
return nil, false
}
}
func getDelegatorDelegationsSum(ctx sdk.Context, address string, queryServer grpc.Server) (sdk.Coins, error) {
querier, ok := queryServer.(*baseapp.GRPCQueryRouter)
if !ok {
return nil, fmt.Errorf("unexpected type: %T wanted *baseapp.GRPCQueryRouter", queryServer)
}
queryFn := querier.Route(delegatorDelegationPath)
q := &stakingtypes.QueryDelegatorDelegationsRequest{
DelegatorAddr: address,
}
b, err := proto.Marshal(q)
if err != nil {
return nil, fmt.Errorf("cannot marshal staking type query request, %w", err)
}
req := abci.RequestQuery{
Data: b,
Path: delegatorDelegationPath,
}
resp, err := queryFn(ctx, req)
if err != nil {
e, ok := status.FromError(err)
if ok && e.Code() == codes.NotFound {
return nil, nil
}
return nil, fmt.Errorf("staking query error, %w", err)
}
balance := new(stakingtypes.QueryDelegatorDelegationsResponse)
if err := proto.Unmarshal(resp.Value, balance); err != nil {
return nil, fmt.Errorf("unable to unmarshal delegator query delegations: %w", err)
}
res := sdk.NewCoins()
for _, i := range balance.DelegationResponses {
res = res.Add(i.Balance)
}
return res, nil
}
func getDelegatorUnbondingDelegationsSum(ctx sdk.Context, address, bondDenom string, queryServer grpc.Server) (sdk.Coins, error) {
querier, ok := queryServer.(*baseapp.GRPCQueryRouter)
if !ok {
return nil, fmt.Errorf("unexpected type: %T wanted *baseapp.GRPCQueryRouter", queryServer)
}
queryFn := querier.Route(delegatorUnbondingDelegationsPath)
q := &stakingtypes.QueryDelegatorUnbondingDelegationsRequest{
DelegatorAddr: address,
}
b, err := proto.Marshal(q)
if err != nil {
return nil, fmt.Errorf("cannot marshal staking type query request, %w", err)
}
req := abci.RequestQuery{
Data: b,
Path: delegatorUnbondingDelegationsPath,
}
resp, err := queryFn(ctx, req)
if err != nil && !errors.Is(err, sdkerrors.ErrNotFound) {
e, ok := status.FromError(err)
if ok && e.Code() == codes.NotFound {
return nil, nil
}
return nil, fmt.Errorf("staking query error, %w", err)
}
balance := new(stakingtypes.QueryDelegatorUnbondingDelegationsResponse)
if err := proto.Unmarshal(resp.Value, balance); err != nil {
return nil, fmt.Errorf("unable to unmarshal delegator query delegations: %w", err)
}
res := sdk.NewCoins()
for _, i := range balance.UnbondingResponses {
for _, r := range i.Entries {
res = res.Add(sdk.NewCoin(bondDenom, r.Balance))
}
}
return res, nil
}
func getBalance(ctx sdk.Context, address string, queryServer grpc.Server) (sdk.Coins, error) {
querier, ok := queryServer.(*baseapp.GRPCQueryRouter)
if !ok {
return nil, fmt.Errorf("unexpected type: %T wanted *baseapp.GRPCQueryRouter", queryServer)
}
queryFn := querier.Route(balancesPath)
q := &banktypes.QueryAllBalancesRequest{
Address: address,
Pagination: nil,
}
b, err := proto.Marshal(q)
if err != nil {
return nil, fmt.Errorf("cannot marshal bank type query request, %w", err)
}
req := abci.RequestQuery{
Data: b,
Path: balancesPath,
}
resp, err := queryFn(ctx, req)
if err != nil {
return nil, fmt.Errorf("bank query error, %w", err)
}
balance := new(banktypes.QueryAllBalancesResponse)
if err := proto.Unmarshal(resp.Value, balance); err != nil {
return nil, fmt.Errorf("unable to unmarshal bank balance response: %w", err)
}
return balance.Balances, nil
}
func getBondDenom(ctx sdk.Context, queryServer grpc.Server) (string, error) {
querier, ok := queryServer.(*baseapp.GRPCQueryRouter)
if !ok {
return "", fmt.Errorf("unexpected type: %T wanted *baseapp.GRPCQueryRouter", queryServer)
}
queryFn := querier.Route(stakingParamsPath)
q := &stakingtypes.QueryParamsRequest{}
b, err := proto.Marshal(q)
if err != nil {
return "", fmt.Errorf("cannot marshal staking params query request, %w", err)
}
req := abci.RequestQuery{
Data: b,
Path: stakingParamsPath,
}
resp, err := queryFn(ctx, req)
if err != nil {
return "", fmt.Errorf("staking query error, %w", err)
}
params := new(stakingtypes.QueryParamsResponse)
if err := proto.Unmarshal(resp.Value, params); err != nil {
return "", fmt.Errorf("unable to unmarshal delegator query delegations: %w", err)
}
return params.Params.BondDenom, nil
}
// MigrateAccount migrates vesting account to make the DelegatedVesting and DelegatedFree fields correctly
// track delegations.
// References: https://github.com/cosmos/cosmos-sdk/issues/8601, https://github.com/cosmos/cosmos-sdk/issues/8812
func MigrateAccount(ctx sdk.Context, account types.AccountI, queryServer grpc.Server) (types.AccountI, error) {
return migrateVestingAccounts(ctx, account, queryServer)
}

View File

@ -0,0 +1,687 @@
package v043_test
import (
"fmt"
"testing"
"time"
"github.com/stretchr/testify/require"
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
authkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
"github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
"github.com/cosmos/cosmos-sdk/x/auth/vesting/types"
"github.com/cosmos/cosmos-sdk/x/staking"
stakingkeeper "github.com/cosmos/cosmos-sdk/x/staking/keeper"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
)
func TestMigrateVestingAccounts(t *testing.T) {
testCases := []struct {
name string
prepareFunc func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress)
garbageFunc func(ctx sdk.Context, vesting exported.VestingAccount, app *simapp.SimApp) error
tokenAmount int64
expVested int64
expFree int64
blockTime int64
}{
{
"delayed vesting has vested, multiple delegations less than the total account balance",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(200)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().Unix())
ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(1, 0, 0))
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
_, err = app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
_, err = app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
0,
300,
0,
},
{
"delayed vesting has vested, single delegations which exceed the vested amount",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(200)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().Unix())
ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(1, 0, 0))
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(300), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
0,
300,
0,
},
{
"delayed vesting has vested, multiple delegations which exceed the vested amount",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(200)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().Unix())
ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(1, 0, 0))
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
_, err = app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
_, err = app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
0,
300,
0,
},
{
"delayed vesting has not vested, single delegations which exceed the vested amount",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(200)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().AddDate(1, 0, 0).Unix())
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(300), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
200,
100,
0,
},
{
"delayed vesting has not vested, multiple delegations which exceed the vested amount",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(200)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().AddDate(1, 0, 0).Unix())
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
_, err = app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
_, err = app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
200,
100,
0,
},
{
"not end time",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(300)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().AddDate(1, 0, 0).Unix())
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
_, err = app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
_, err = app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(100), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
300,
0,
0,
},
{
"delayed vesting has not vested, single delegation greater than the total account balance",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(300)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().AddDate(1, 0, 0).Unix())
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(300), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
300,
0,
0,
},
{
"delayed vesting has vested, single delegation greater than the total account balance",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(300)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().Unix())
ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(1, 0, 0))
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(300), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
0,
300,
0,
},
{
"continuous vesting, start time after blocktime",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
startTime := ctx.BlockTime().AddDate(1, 0, 0).Unix()
endTime := ctx.BlockTime().AddDate(2, 0, 0).Unix()
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(300)))
delayedAccount := types.NewContinuousVestingAccount(baseAccount, vestedCoins, startTime, endTime)
ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(1, 0, 0))
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(300), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
300,
0,
0,
},
{
"continuous vesting, start time passed but not ended",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
startTime := ctx.BlockTime().AddDate(-1, 0, 0).Unix()
endTime := ctx.BlockTime().AddDate(2, 0, 0).Unix()
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(300)))
delayedAccount := types.NewContinuousVestingAccount(baseAccount, vestedCoins, startTime, endTime)
ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(1, 0, 0))
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(300), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
200,
100,
0,
},
{
"continuous vesting, start time and endtime passed",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
startTime := ctx.BlockTime().AddDate(-2, 0, 0).Unix()
endTime := ctx.BlockTime().AddDate(-1, 0, 0).Unix()
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(300)))
delayedAccount := types.NewContinuousVestingAccount(baseAccount, vestedCoins, startTime, endTime)
ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(1, 0, 0))
app.AccountKeeper.SetAccount(ctx, delayedAccount)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(300), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
0,
300,
0,
},
{
"periodic vesting account, yet to be vested, some rewards delegated",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(100)))
start := ctx.BlockTime().Unix() + int64(time.Hour/time.Second)
periods := []types.Period{
{
Length: int64((24 * time.Hour) / time.Second),
Amount: vestedCoins,
},
}
account := types.NewPeriodicVestingAccount(baseAccount, vestedCoins, start, periods)
app.AccountKeeper.SetAccount(ctx, account)
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(150), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
300,
100,
50,
0,
},
{
"periodic vesting account, nothing has vested yet",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
/*
Test case:
- periodic vesting account starts at time 1601042400
- account balance and original vesting: 3666666670000
- nothing has vested, we put the block time slightly after start time
- expected vested: original vesting amount
- expected free: zero
- we're delegating the full original vesting
*/
startTime := int64(1601042400)
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(3666666670000)))
periods := []types.Period{
{
Length: 31536000,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(1833333335000))),
},
{
Length: 15638400,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(916666667500))),
},
{
Length: 15897600,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(916666667500))),
},
}
delayedAccount := types.NewPeriodicVestingAccount(baseAccount, vestedCoins, startTime, periods)
app.AccountKeeper.SetAccount(ctx, delayedAccount)
// delegation of the original vesting
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(3666666670000), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
3666666670000,
3666666670000,
0,
1601042400 + 1,
},
{
"periodic vesting account, all has vested",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
/*
Test case:
- periodic vesting account starts at time 1601042400
- account balance and original vesting: 3666666670000
- all has vested, so we set the block time at initial time + sum of all periods times + 1 => 1601042400 + 31536000 + 15897600 + 15897600 + 1
- expected vested: zero
- expected free: original vesting amount
- we're delegating the full original vesting
*/
startTime := int64(1601042400)
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(3666666670000)))
periods := []types.Period{
{
Length: 31536000,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(1833333335000))),
},
{
Length: 15638400,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(916666667500))),
},
{
Length: 15897600,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(916666667500))),
},
}
delayedAccount := types.NewPeriodicVestingAccount(baseAccount, vestedCoins, startTime, periods)
ctx = ctx.WithBlockTime(time.Unix(1601042400+31536000+15897600+15897600+1, 0))
app.AccountKeeper.SetAccount(ctx, delayedAccount)
// delegation of the original vesting
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(3666666670000), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
3666666670000,
0,
3666666670000,
1601042400 + 31536000 + 15897600 + 15897600 + 1,
},
{
"periodic vesting account, first period has vested",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
/*
Test case:
- periodic vesting account starts at time 1601042400
- account balance and original vesting: 3666666670000
- first period have vested, so we set the block time at initial time + time of the first periods + 1 => 1601042400 + 31536000 + 1
- expected vested: original vesting - first period amount
- expected free: first period amount
- we're delegating the full original vesting
*/
startTime := int64(1601042400)
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(3666666670000)))
periods := []types.Period{
{
Length: 31536000,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(1833333335000))),
},
{
Length: 15638400,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(916666667500))),
},
{
Length: 15897600,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(916666667500))),
},
}
delayedAccount := types.NewPeriodicVestingAccount(baseAccount, vestedCoins, startTime, periods)
ctx = ctx.WithBlockTime(time.Unix(1601042400+31536000+1, 0))
app.AccountKeeper.SetAccount(ctx, delayedAccount)
// delegation of the original vesting
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(3666666670000), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
3666666670000,
3666666670000 - 1833333335000,
1833333335000,
1601042400 + 31536000 + 1,
},
{
"periodic vesting account, first 2 period has vested",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
/*
Test case:
- periodic vesting account starts at time 1601042400
- account balance and original vesting: 3666666670000
- first 2 periods have vested, so we set the block time at initial time + time of the two periods + 1 => 1601042400 + 31536000 + 15638400 + 1
- expected vested: original vesting - (sum of the first two periods amounts)
- expected free: sum of the first two periods
- we're delegating the full original vesting
*/
startTime := int64(1601042400)
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(3666666670000)))
periods := []types.Period{
{
Length: 31536000,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(1833333335000))),
},
{
Length: 15638400,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(916666667500))),
},
{
Length: 15897600,
Amount: sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(916666667500))),
},
}
delayedAccount := types.NewPeriodicVestingAccount(baseAccount, vestedCoins, startTime, periods)
ctx = ctx.WithBlockTime(time.Unix(1601042400+31536000+15638400+1, 0))
app.AccountKeeper.SetAccount(ctx, delayedAccount)
// delegation of the original vesting
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(3666666670000), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
},
cleartTrackingFields,
3666666670000,
3666666670000 - 1833333335000 - 916666667500,
1833333335000 + 916666667500,
1601042400 + 31536000 + 15638400 + 1,
},
{
"vesting account has unbonding delegations in place",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(300)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().AddDate(10, 0, 0).Unix())
app.AccountKeeper.SetAccount(ctx, delayedAccount)
// delegation of the original vesting
_, err := app.StakingKeeper.Delegate(ctx, delegatorAddr, sdk.NewInt(300), stakingtypes.Unbonded, validator, true)
require.NoError(t, err)
ctx = ctx.WithBlockTime(ctx.BlockTime().AddDate(1, 0, 0))
valAddr, err := sdk.ValAddressFromBech32(validator.OperatorAddress)
require.NoError(t, err)
// un-delegation of the original vesting
_, err = app.StakingKeeper.Undelegate(ctx, delegatorAddr, valAddr, sdk.NewDecFromInt(sdk.NewInt(300)))
require.NoError(t, err)
},
cleartTrackingFields,
450,
300,
0,
0,
},
{
"vesting account has never delegated anything",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(300)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().AddDate(10, 0, 0).Unix())
app.AccountKeeper.SetAccount(ctx, delayedAccount)
},
cleartTrackingFields,
450,
0,
0,
0,
},
{
"vesting account has no delegation but dirty DelegatedFree and DelegatedVesting fields",
func(app *simapp.SimApp, ctx sdk.Context, validator stakingtypes.Validator, delegatorAddr sdk.AccAddress) {
baseAccount := authtypes.NewBaseAccountWithAddress(delegatorAddr)
vestedCoins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(300)))
delayedAccount := types.NewDelayedVestingAccount(baseAccount, vestedCoins, ctx.BlockTime().AddDate(10, 0, 0).Unix())
app.AccountKeeper.SetAccount(ctx, delayedAccount)
},
dirtyTrackingFields,
450,
0,
0,
0,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
app := simapp.Setup(false)
ctx := app.BaseApp.NewContext(false, tmproto.Header{
Time: time.Now(),
})
addrs := simapp.AddTestAddrs(app, ctx, 1, sdk.NewInt(tc.tokenAmount))
delegatorAddr := addrs[0]
_, valAddr := createValidator(t, ctx, app, tc.tokenAmount*2)
validator, found := app.StakingKeeper.GetValidator(ctx, valAddr)
require.True(t, found)
tc.prepareFunc(app, ctx, validator, delegatorAddr)
if tc.blockTime != 0 {
ctx = ctx.WithBlockTime(time.Unix(tc.blockTime, 0))
}
// We introduce the bug
savedAccount := app.AccountKeeper.GetAccount(ctx, delegatorAddr)
vestingAccount, ok := savedAccount.(exported.VestingAccount)
require.True(t, ok)
require.NoError(t, tc.garbageFunc(ctx, vestingAccount, app))
m := authkeeper.NewMigrator(app.AccountKeeper, app.GRPCQueryRouter())
require.NoError(t, m.Migrate1to2(ctx))
var expVested sdk.Coins
var expFree sdk.Coins
if tc.expVested != 0 {
expVested = sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(tc.expVested)))
}
if tc.expFree != 0 {
expFree = sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), sdk.NewInt(tc.expFree)))
}
trackingCorrected(
ctx,
t,
app.AccountKeeper,
savedAccount.GetAddress(),
expVested,
expFree,
)
})
}
}
func trackingCorrected(ctx sdk.Context, t *testing.T, ak authkeeper.AccountKeeper, addr sdk.AccAddress, expDelVesting sdk.Coins, expDelFree sdk.Coins) {
t.Helper()
baseAccount := ak.GetAccount(ctx, addr)
vDA, ok := baseAccount.(exported.VestingAccount)
require.True(t, ok)
vestedOk := expDelVesting.IsEqual(vDA.GetDelegatedVesting())
freeOk := expDelFree.IsEqual(vDA.GetDelegatedFree())
require.True(t, vestedOk, vDA.GetDelegatedVesting().String())
require.True(t, freeOk, vDA.GetDelegatedFree().String())
}
func cleartTrackingFields(ctx sdk.Context, vesting exported.VestingAccount, app *simapp.SimApp) error {
switch t := vesting.(type) {
case *types.DelayedVestingAccount:
t.DelegatedFree = nil
t.DelegatedVesting = nil
app.AccountKeeper.SetAccount(ctx, t)
case *types.ContinuousVestingAccount:
t.DelegatedFree = nil
t.DelegatedVesting = nil
app.AccountKeeper.SetAccount(ctx, t)
case *types.PeriodicVestingAccount:
t.DelegatedFree = nil
t.DelegatedVesting = nil
app.AccountKeeper.SetAccount(ctx, t)
default:
return fmt.Errorf("expected vesting account, found %t", t)
}
return nil
}
func dirtyTrackingFields(ctx sdk.Context, vesting exported.VestingAccount, app *simapp.SimApp) error {
dirt := sdk.NewCoins(sdk.NewInt64Coin("stake", 42))
switch t := vesting.(type) {
case *types.DelayedVestingAccount:
t.DelegatedFree = dirt
t.DelegatedVesting = dirt
app.AccountKeeper.SetAccount(ctx, t)
case *types.ContinuousVestingAccount:
t.DelegatedFree = dirt
t.DelegatedVesting = dirt
app.AccountKeeper.SetAccount(ctx, t)
case *types.PeriodicVestingAccount:
t.DelegatedFree = dirt
t.DelegatedVesting = dirt
app.AccountKeeper.SetAccount(ctx, t)
default:
return fmt.Errorf("expected vesting account, found %t", t)
}
return nil
}
func createValidator(t *testing.T, ctx sdk.Context, app *simapp.SimApp, powers int64) (sdk.AccAddress, sdk.ValAddress) {
valTokens := sdk.TokensFromConsensusPower(powers)
addrs := simapp.AddTestAddrsIncremental(app, ctx, 1, valTokens)
valAddrs := simapp.ConvertAddrsToValAddrs(addrs)
pks := simapp.CreateTestPubKeys(1)
cdc := simapp.MakeTestEncodingConfig().Marshaler
app.StakingKeeper = stakingkeeper.NewKeeper(
cdc,
app.GetKey(stakingtypes.StoreKey),
app.AccountKeeper,
app.BankKeeper,
app.GetSubspace(stakingtypes.ModuleName),
)
val1, err := stakingtypes.NewValidator(valAddrs[0], pks[0], stakingtypes.Description{})
require.NoError(t, err)
app.StakingKeeper.SetValidator(ctx, val1)
require.NoError(t, app.StakingKeeper.SetValidatorByConsAddr(ctx, val1))
app.StakingKeeper.SetNewValidatorByPowerIndex(ctx, val1)
_, err = app.StakingKeeper.Delegate(ctx, addrs[0], valTokens, stakingtypes.Unbonded, val1, true)
require.NoError(t, err)
_ = staking.EndBlocker(ctx, app.StakingKeeper)
return addrs[0], valAddrs[0]
}

View File

@ -129,6 +129,11 @@ func (am AppModule) LegacyQuerierHandler(legacyQuerierCdc *codec.LegacyAmino) sd
// module-specific GRPC queries.
func (am AppModule) RegisterServices(cfg module.Configurator) {
types.RegisterQueryServer(cfg.QueryServer(), am.accountKeeper)
m := keeper.NewMigrator(am.accountKeeper, cfg.QueryServer())
err := cfg.RegisterMigration(types.ModuleName, 1, m.Migrate1to2)
if err != nil {
panic(err)
}
}
// InitGenesis performs genesis initialization for the auth module. It returns
@ -148,7 +153,7 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONMarshaler) json
}
// ConsensusVersion implements AppModule/ConsensusVersion.
func (AppModule) ConsensusVersion() uint64 { return 1 }
func (AppModule) ConsensusVersion() uint64 { return 2 }
// BeginBlock returns the begin blocker for the auth module.
func (AppModule) BeginBlock(_ sdk.Context, _ abci.RequestBeginBlock) {}

View File

@ -446,6 +446,7 @@ func (k BaseKeeper) trackDelegation(ctx sdk.Context, addr sdk.AccAddress, balanc
if ok {
// TODO: return error on account.TrackDelegation
vacc.TrackDelegation(ctx.BlockHeader().Time, balance, amt)
k.ak.SetAccount(ctx, acc)
}
return nil
@ -461,6 +462,7 @@ func (k BaseKeeper) trackUndelegation(ctx sdk.Context, addr sdk.AccAddress, amt
if ok {
// TODO: return error on account.TrackUndelegation
vacc.TrackUndelegation(amt)
k.ak.SetAccount(ctx, acc)
}
return nil

View File

@ -5,6 +5,7 @@ import (
"testing"
"time"
"github.com/cosmos/cosmos-sdk/x/auth/vesting/exported"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
"github.com/stretchr/testify/suite"
@ -904,6 +905,12 @@ func (suite *IntegrationTestSuite) TestDelegateCoins() {
// require the ability for a vesting account to delegate
suite.Require().NoError(app.BankKeeper.DelegateCoins(ctx, addr1, addrModule, delCoins))
suite.Require().Equal(delCoins, app.BankKeeper.GetAllBalances(ctx, addr1))
// require that delegated vesting amount is equal to what was delegated with DelegateCoins
acc = app.AccountKeeper.GetAccount(ctx, addr1)
vestingAcc, ok := acc.(exported.VestingAccount)
suite.Require().True(ok)
suite.Require().Equal(delCoins, vestingAcc.GetDelegatedVesting())
}
func (suite *IntegrationTestSuite) TestDelegateCoins_Invalid() {
@ -978,6 +985,12 @@ func (suite *IntegrationTestSuite) TestUndelegateCoins() {
suite.Require().Equal(origCoins, app.BankKeeper.GetAllBalances(ctx, addr1))
suite.Require().True(app.BankKeeper.GetAllBalances(ctx, addrModule).Empty())
// require that delegated vesting amount is completely empty, since they were completely undelegated
acc = app.AccountKeeper.GetAccount(ctx, addr1)
vestingAcc, ok := acc.(exported.VestingAccount)
suite.Require().True(ok)
suite.Require().Empty(vestingAcc.GetDelegatedVesting())
}
func (suite *IntegrationTestSuite) TestUndelegateCoins_Invalid() {