From f2e87ad81f8be230469d2c72e25534bd039d48b5 Mon Sep 17 00:00:00 2001 From: Alexander Bezobchuk Date: Thu, 17 Jan 2019 11:15:13 -0500 Subject: [PATCH] Merge PR #3308: Genesis Vesting Accounts & Simulation * Update vesting spec and impl time fields and constructors * Update genesis to support vesting accounts * More spec and godoc updates * Update genesis section in vesting spec * Fix bank unit tests * Add test cases to ToAccount in genesis * Update RegisterCodec to include vesting interface and types * Fix GetVestedCoins bug where block time is past vesting end time * Add vesting accounts to simulation * Update vesting genesis logic to panic on invalid accounts * Change randomness of vesting accounts in simulation --- PENDING.md | 2 + cmd/gaia/app/app.go | 3 +- cmd/gaia/app/genesis.go | 37 +++++++++++++--- cmd/gaia/app/genesis_test.go | 13 +++++- cmd/gaia/app/sim_test.go | 46 +++++++++++++++++--- docs/spec/auth/vesting.md | 77 +++++++++++++++----------------- x/auth/account.go | 59 +++++++++++++++---------- x/auth/account_test.go | 82 +++++++++++++++++++++++++---------- x/auth/codec.go | 4 ++ x/bank/keeper_test.go | 16 +++++-- x/bank/simulation/msgs.go | 2 +- x/mock/simulation/account.go | 2 + x/mock/simulation/simulate.go | 19 ++++---- 13 files changed, 247 insertions(+), 115 deletions(-) diff --git a/PENDING.md b/PENDING.md index d077aeac5dab..7fcaaa187a32 100644 --- a/PENDING.md +++ b/PENDING.md @@ -55,6 +55,8 @@ FEATURES * Gaia * [\#2182] [x/staking] Added querier for querying a single redelegation + * [\#3305](https://github.com/cosmos/cosmos-sdk/issues/3305) Add support for + vesting accounts at genesis. * [\#3198](https://github.com/cosmos/cosmos-sdk/issues/3198) [x/auth] Add multisig transactions support * [\#3198](https://github.com/cosmos/cosmos-sdk/issues/3198) `add-genesis-account` can take both account addresses and key names diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 64c78893b3bc..7b235f4cd6e5 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -231,10 +231,11 @@ func (app *GaiaApp) initFromGenesisState(ctx sdk.Context, genesisState GenesisSt sort.Slice(genesisState.Accounts, func(i, j int) bool { return genesisState.Accounts[i].AccountNumber < genesisState.Accounts[j].AccountNumber }) + // load the accounts for _, gacc := range genesisState.Accounts { acc := gacc.ToAccount() - acc.AccountNumber = app.accountKeeper.GetNextAccountNumber(ctx) + acc = app.accountKeeper.NewAccount(ctx, acc) // set account number app.accountKeeper.SetAccount(ctx, acc) } diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index 32c2555adacf..129786f3b472 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -58,12 +58,15 @@ func NewGenesisState(accounts []GenesisAccount, authData auth.GenesisState, } } -// nolint +// GenesisAccount defines an account initialized at genesis. type GenesisAccount struct { Address sdk.AccAddress `json:"address"` Coins sdk.Coins `json:"coins"` Sequence uint64 `json:"sequence_number"` AccountNumber uint64 `json:"account_number"` + Vesting bool `json:"vesting"` + StartTime int64 `json:"start_time"` + EndTime int64 `json:"end_time"` } func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { @@ -76,22 +79,43 @@ func NewGenesisAccount(acc *auth.BaseAccount) GenesisAccount { } func NewGenesisAccountI(acc auth.Account) GenesisAccount { - return GenesisAccount{ + gacc := GenesisAccount{ Address: acc.GetAddress(), Coins: acc.GetCoins(), AccountNumber: acc.GetAccountNumber(), Sequence: acc.GetSequence(), } + + vacc, ok := acc.(auth.VestingAccount) + if ok { + gacc.Vesting = true + gacc.StartTime = vacc.GetStartTime() + gacc.EndTime = vacc.GetEndTime() + } + + return gacc } // convert GenesisAccount to auth.BaseAccount -func (ga *GenesisAccount) ToAccount() (acc *auth.BaseAccount) { - return &auth.BaseAccount{ +func (ga *GenesisAccount) ToAccount() auth.Account { + bacc := &auth.BaseAccount{ Address: ga.Address, Coins: ga.Coins.Sort(), AccountNumber: ga.AccountNumber, Sequence: ga.Sequence, } + + if ga.Vesting { + if ga.StartTime != 0 && ga.EndTime != 0 { + return auth.NewContinuousVestingAccount(bacc, ga.StartTime, ga.EndTime) + } else if ga.EndTime != 0 { + return auth.NewDelayedVestingAccount(bacc, ga.EndTime) + } else { + panic(fmt.Sprintf("invalid genesis vesting account: %+v", ga)) + } + } + + return bacc } // Create the core parameters for genesis initialization for gaia @@ -114,11 +138,13 @@ func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []js if err := cdc.UnmarshalJSON(genTx, &tx); err != nil { return genesisState, err } + msgs := tx.GetMsgs() if len(msgs) != 1 { return genesisState, errors.New( "must provide genesis StdTx with exactly 1 CreateValidator message") } + if _, ok := msgs[0].(staking.MsgCreateValidator); !ok { return genesisState, fmt.Errorf( "Genesis transaction %v does not contain a MsgCreateValidator", i) @@ -126,7 +152,6 @@ func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []js } for _, acc := range genesisState.Accounts { - // create the genesis account, give'm few steaks and a buncha token with there name for _, coin := range acc.Coins { if coin.Denom == bondDenom { stakingData.Pool.LooseTokens = stakingData.Pool.LooseTokens. @@ -134,8 +159,10 @@ func GaiaAppGenState(cdc *codec.Codec, genDoc tmtypes.GenesisDoc, appGenTxs []js } } } + genesisState.StakingData = stakingData genesisState.GenTxs = appGenTxs + return genesisState, nil } diff --git a/cmd/gaia/app/genesis_test.go b/cmd/gaia/app/genesis_test.go index 90fcb86715b2..889b74188e86 100644 --- a/cmd/gaia/app/genesis_test.go +++ b/cmd/gaia/app/genesis_test.go @@ -3,6 +3,7 @@ package app import ( "encoding/json" "testing" + "time" "github.com/tendermint/tendermint/crypto/secp256k1" tmtypes "github.com/tendermint/tendermint/types" @@ -56,7 +57,17 @@ func TestToAccount(t *testing.T) { addr := sdk.AccAddress(priv.PubKey().Address()) authAcc := auth.NewBaseAccountWithAddress(addr) genAcc := NewGenesisAccount(&authAcc) - require.Equal(t, authAcc, *genAcc.ToAccount()) + acc := genAcc.ToAccount() + require.IsType(t, &auth.BaseAccount{}, acc) + require.Equal(t, &authAcc, acc.(*auth.BaseAccount)) + + vacc := auth.NewContinuousVestingAccount( + &authAcc, time.Now().Unix(), time.Now().Add(24*time.Hour).Unix(), + ) + genAcc = NewGenesisAccountI(vacc) + acc = genAcc.ToAccount() + require.IsType(t, &auth.ContinuousVestingAccount{}, acc) + require.Equal(t, vacc, acc.(*auth.ContinuousVestingAccount)) } func TestGaiaAppGenTx(t *testing.T) { diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 6f14cb6fdbed..db9402377762 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -54,7 +54,7 @@ func init() { flag.IntVar(&period, "SimulationPeriod", 1, "Run slow invariants only once every period assertions") } -func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { +func appStateFn(r *rand.Rand, accs []simulation.Account, genesisTimestamp time.Time) json.RawMessage { var genesisAccounts []GenesisAccount amount := int64(r.Intn(1e6)) @@ -67,13 +67,44 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { "\t{amount of steak per account: %v, initially bonded validators: %v}\n", amount, numInitiallyBonded) - // Randomly generate some genesis accounts - for _, acc := range accs { + // randomly generate some genesis accounts + for i, acc := range accs { coins := sdk.Coins{sdk.NewCoin(stakingTypes.DefaultBondDenom, sdk.NewInt(amount))} - genesisAccounts = append(genesisAccounts, GenesisAccount{ - Address: acc.Address, - Coins: coins, - }) + bacc := auth.NewBaseAccountWithAddress(acc.Address) + bacc.SetCoins(coins) + + var gacc GenesisAccount + + // Only consider making a vesting account once the initial bonded validator + // set is exhausted due to needing to track DelegatedVesting. + if int64(i) > numInitiallyBonded && r.Intn(100) < 50 { + var ( + vacc auth.VestingAccount + endTime int + ) + + startTime := genesisTimestamp.Unix() + + // Allow for some vesting accounts to vest very quickly while others very + // slowly. + if r.Intn(100) < 50 { + endTime = randIntBetween(r, int(startTime), int(startTime+(60*60*24*30))) + } else { + endTime = randIntBetween(r, int(startTime), int(startTime+(60*60*12))) + } + + if r.Intn(100) < 50 { + vacc = auth.NewContinuousVestingAccount(&bacc, startTime, int64(endTime)) + } else { + vacc = auth.NewDelayedVestingAccount(&bacc, int64(endTime)) + } + + gacc = NewGenesisAccountI(vacc) + } else { + gacc = NewGenesisAccount(&bacc) + } + + genesisAccounts = append(genesisAccounts, gacc) } authGenesis := auth.GenesisState{ @@ -156,6 +187,7 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { validators = append(validators, validator) delegations = append(delegations, delegation) } + stakingGenesis.Pool.LooseTokens = sdk.NewInt((amount * numAccs) + (numInitiallyBonded * amount)) stakingGenesis.Validators = validators stakingGenesis.Bonds = delegations diff --git a/docs/spec/auth/vesting.md b/docs/spec/auth/vesting.md index 12211c12eaa3..f0722e9ebfd1 100644 --- a/docs/spec/auth/vesting.md +++ b/docs/spec/auth/vesting.md @@ -14,7 +14,7 @@ - [Undelegating](#undelegating) - [Keepers/Handlers](#keepershandlers-2) - [Keepers & Handlers](#keepers--handlers) - - [Initializing at Genesis](#initializing-at-genesis) + - [Genesis Initialization](#genesis-initialization) - [Examples](#examples) - [Simple](#simple) - [Slashing](#slashing) @@ -45,13 +45,16 @@ order to make such a distinction. type VestingAccount interface { Account - GetVestedCoins(Time) Coins + GetVestedCoins(Time) Coins GetVestingCoins(Time) Coins // Delegation and undelegation accounting that returns the resulting base // coins amount. TrackDelegation(Time, Coins) TrackUndelegation(Coins) + + GetStartTime() int64 + GetEndTime() int64 } // BaseVestingAccount implements the VestingAccount interface. It contains all @@ -63,7 +66,7 @@ type BaseVestingAccount struct { DelegatedFree Coins // coins that are vested and delegated DelegatedVesting Coins // coins that vesting and delegated - EndTime Time // when the coins become unlocked + EndTime int64 // when the coins become unlocked } // ContinuousVestingAccount implements the VestingAccount interface. It @@ -71,7 +74,7 @@ type BaseVestingAccount struct { type ContinuousVestingAccount struct { BaseVestingAccount - StartTime Time // when the coins start to vest + StartTime int64 // when the coins start to vest } // DelayedVestingAccount implements the VestingAccount interface. It vests all @@ -127,11 +130,13 @@ is _vesting_. ```go func (cva ContinuousVestingAccount) GetVestedCoins(t Time) Coins { - // We must handle the case where the start time for a vesting account has - // been set into the future or when the start of the chain is not exactly - // known. - if t <= va.StartTime { + if t <= cva.StartTime { + // We must handle the case where the start time for a vesting account has + // been set into the future or when the start of the chain is not exactly + // known. return ZeroCoins + } else if t >= cva.EndTime { + return cva.OriginalVesting } x := t - cva.StartTime @@ -299,50 +304,38 @@ unlocked coin amount. See the above specification for full implementation details. -## Initializing at Genesis +## Genesis Initialization -To initialize both vesting and base accounts, the `GenesisAccount` struct will -include an `EndTime`. Accounts meant to be of type `BaseAccount` will -have `EndTime = 0`. The `initChainer` method will parse the GenesisAccount into -BaseAccounts and VestingAccounts as appropriate. +To initialize both vesting and non-vesting accounts, the `GenesisAccount` struct will +include new fields: `Vesting`, `StartTime`, and `EndTime`. Accounts meant to be +of type `BaseAccount` or any non-vesting type will have `Vesting = false`. The +genesis initialization logic (e.g. `initFromGenesisState`) will have to parse +and return the correct accounts accordingly based off of these new fields. ```go type GenesisAccount struct { - Address sdk.AccAddress - GenesisCoins sdk.Coins - EndTime int64 - StartTime int64 + // ... + + Vesting bool + EndTime int64 + StartTime int64 } -func initChainer() { - for genAcc in GenesisAccounts { - baseAccount := BaseAccount{ - Address: genAcc.Address, - Coins: genAcc.GenesisCoins, - } +func ToAccount(gacc GenesisAccount) Account { + bacc := NewBaseAccount(gacc) - if genAcc.StartTime != 0 && genAcc.EndTime != 0 { - vestingAccount := ContinuousVestingAccount{ - BaseAccount: baseAccount, - OriginalVesting: genAcc.GenesisCoins, - StartTime: RequestInitChain.Time, - StartTime: genAcc.StartTime, - EndTime: genAcc.EndTime, - } - - AddAccountToState(vestingAccount) - } else if genAcc.EndTime != 0 { - vestingAccount := DelayedVestingAccount{ - BaseAccount: baseAccount, - OriginalVesting: genAcc.GenesisCoins, - EndTime: genAcc.EndTime, - } - - AddAccountToState(vestingAccount) + if gacc.Vesting { + if ga.StartTime != 0 && ga.EndTime != 0 { + return NewContinuousVestingAccount(bacc, gacc.StartTime, gacc.EndTime) + } else if ga.EndTime != 0 { + return NewDelayedVestingAccount(bacc, gacc.EndTime) } else { - AddAccountToState(baseAccount) + // invalid genesis vesting account provided + panic() } } + + return bacc } ``` diff --git a/x/auth/account.go b/x/auth/account.go index bc279a579eb2..f04cb179a32c 100644 --- a/x/auth/account.go +++ b/x/auth/account.go @@ -48,6 +48,9 @@ type VestingAccount interface { GetVestedCoins(blockTime time.Time) sdk.Coins GetVestingCoins(blockTime time.Time) sdk.Coins + + GetStartTime() int64 + GetEndTime() int64 } // AccountDecoder unmarshals account bytes @@ -158,7 +161,7 @@ type BaseVestingAccount struct { DelegatedFree sdk.Coins // coins that are vested and delegated DelegatedVesting sdk.Coins // coins that vesting and delegated - EndTime time.Time // when the coins become unlocked + EndTime int64 // when the coins become unlocked } // spendableCoins returns all the spendable coins for a vesting account given a @@ -320,21 +323,16 @@ var _ VestingAccount = (*ContinuousVestingAccount)(nil) type ContinuousVestingAccount struct { *BaseVestingAccount - StartTime time.Time // when the coins start to vest + StartTime int64 // when the coins start to vest } func NewContinuousVestingAccount( - addr sdk.AccAddress, origCoins sdk.Coins, StartTime, EndTime time.Time, + baseAcc *BaseAccount, StartTime, EndTime int64, ) *ContinuousVestingAccount { - baseAcc := &BaseAccount{ - Address: addr, - Coins: origCoins, - } - baseVestingAcc := &BaseVestingAccount{ BaseAccount: baseAcc, - OriginalVesting: origCoins, + OriginalVesting: baseAcc.Coins, EndTime: EndTime, } @@ -352,13 +350,15 @@ func (cva ContinuousVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coin // We must handle the case where the start time for a vesting account has // been set into the future or when the start of the chain is not exactly // known. - if blockTime.Unix() <= cva.StartTime.Unix() { + if blockTime.Unix() <= cva.StartTime { return vestedCoins + } else if blockTime.Unix() >= cva.EndTime { + return cva.OriginalVesting } // calculate the vesting scalar - x := int64(blockTime.Sub(cva.StartTime).Seconds()) - y := int64(cva.EndTime.Sub(cva.StartTime).Seconds()) + x := blockTime.Unix() - cva.StartTime + y := cva.EndTime - cva.StartTime s := sdk.NewDec(x).Quo(sdk.NewDec(y)) for _, ovc := range cva.OriginalVesting { @@ -388,6 +388,17 @@ func (cva *ContinuousVestingAccount) TrackDelegation(blockTime time.Time, amount cva.trackDelegation(cva.GetVestingCoins(blockTime), amount) } +// GetStartTime returns the time when vesting starts for a continuous vesting +// account. +func (cva *ContinuousVestingAccount) GetStartTime() int64 { + return cva.StartTime +} + +// GetEndTime returns the time when vesting ends for a continuous vesting account. +func (cva *ContinuousVestingAccount) GetEndTime() int64 { + return cva.EndTime +} + //----------------------------------------------------------------------------- // Delayed Vesting Account @@ -400,18 +411,10 @@ type DelayedVestingAccount struct { *BaseVestingAccount } -func NewDelayedVestingAccount( - addr sdk.AccAddress, origCoins sdk.Coins, EndTime time.Time, -) *DelayedVestingAccount { - - baseAcc := &BaseAccount{ - Address: addr, - Coins: origCoins, - } - +func NewDelayedVestingAccount(baseAcc *BaseAccount, EndTime int64) *DelayedVestingAccount { baseVestingAcc := &BaseVestingAccount{ BaseAccount: baseAcc, - OriginalVesting: origCoins, + OriginalVesting: baseAcc.Coins, EndTime: EndTime, } @@ -421,7 +424,7 @@ func NewDelayedVestingAccount( // GetVestedCoins returns the total amount of vested coins for a delayed vesting // account. All coins are only vested once the schedule has elapsed. func (dva DelayedVestingAccount) GetVestedCoins(blockTime time.Time) sdk.Coins { - if blockTime.Unix() >= dva.EndTime.Unix() { + if blockTime.Unix() >= dva.EndTime { return dva.OriginalVesting } @@ -447,6 +450,16 @@ func (dva *DelayedVestingAccount) TrackDelegation(blockTime time.Time, amount sd dva.trackDelegation(dva.GetVestingCoins(blockTime), amount) } +// GetStartTime returns zero since a delayed vesting account has no start time. +func (dva *DelayedVestingAccount) GetStartTime() int64 { + return 0 +} + +// GetEndTime returns the time when vesting ends for a delayed vesting account. +func (dva *DelayedVestingAccount) GetEndTime() int64 { + return dva.EndTime +} + //----------------------------------------------------------------------------- // Codec diff --git a/x/auth/account_test.go b/x/auth/account_test.go index 2c6eb0a15764..6a7753a59ba1 100644 --- a/x/auth/account_test.go +++ b/x/auth/account_test.go @@ -108,7 +108,9 @@ func TestGetVestedCoinsContVestingAcc(t *testing.T) { _, _, addr := keyPubAddr() origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} - cva := NewContinuousVestingAccount(addr, origCoins, now, endTime) + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) // require no coins vested in the very beginning of the vesting schedule vestedCoins := cva.GetVestedCoins(now) @@ -121,6 +123,10 @@ func TestGetVestedCoinsContVestingAcc(t *testing.T) { // require 50% of coins vested vestedCoins = cva.GetVestedCoins(now.Add(12 * time.Hour)) require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, vestedCoins) + + // require 100% of coins vested + vestedCoins = cva.GetVestedCoins(now.Add(48 * time.Hour)) + require.Equal(t, origCoins, vestedCoins) } func TestGetVestingCoinsContVestingAcc(t *testing.T) { @@ -129,7 +135,9 @@ func TestGetVestingCoinsContVestingAcc(t *testing.T) { _, _, addr := keyPubAddr() origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} - cva := NewContinuousVestingAccount(addr, origCoins, now, endTime) + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) // require all coins vesting in the beginning of the vesting schedule vestingCoins := cva.GetVestingCoins(now) @@ -150,7 +158,9 @@ func TestSpendableCoinsContVestingAcc(t *testing.T) { _, _, addr := keyPubAddr() origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} - cva := NewContinuousVestingAccount(addr, origCoins, now, endTime) + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) + cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) // require that there exist no spendable coins in the beginning of the // vesting schedule @@ -188,23 +198,27 @@ func TestTrackDelegationContVestingAcc(t *testing.T) { _, _, addr := keyPubAddr() origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) // require the ability to delegate all vesting coins - cva := NewContinuousVestingAccount(addr, origCoins, now, endTime) + cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) cva.TrackDelegation(now, origCoins) require.Equal(t, origCoins, cva.DelegatedVesting) require.Nil(t, cva.DelegatedFree) require.Nil(t, cva.GetCoins()) // require the ability to delegate all vested coins - cva = NewContinuousVestingAccount(addr, origCoins, now, endTime) + bacc.SetCoins(origCoins) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) cva.TrackDelegation(endTime, origCoins) require.Nil(t, cva.DelegatedVesting) require.Equal(t, origCoins, cva.DelegatedFree) require.Nil(t, cva.GetCoins()) // require the ability to delegate all vesting coins (50%) and all vested coins (50%) - cva = NewContinuousVestingAccount(addr, origCoins, now, endTime) + bacc.SetCoins(origCoins) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) require.Equal(t, sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}, cva.DelegatedVesting) require.Nil(t, cva.DelegatedFree) @@ -215,7 +229,8 @@ func TestTrackDelegationContVestingAcc(t *testing.T) { require.Nil(t, cva.GetCoins()) // require no modifications when delegation amount is zero or not enough funds - cva = NewContinuousVestingAccount(addr, origCoins, now, endTime) + bacc.SetCoins(origCoins) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) require.Panics(t, func() { cva.TrackDelegation(endTime, sdk.Coins{sdk.NewInt64Coin(testDenom, 1000000)}) }) @@ -230,9 +245,11 @@ func TestTrackUndelegationContVestingAcc(t *testing.T) { _, _, addr := keyPubAddr() origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) // require the ability to undelegate all vesting coins - cva := NewContinuousVestingAccount(addr, origCoins, now, endTime) + cva := NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) cva.TrackDelegation(now, origCoins) cva.TrackUndelegation(origCoins) require.Nil(t, cva.DelegatedFree) @@ -240,7 +257,9 @@ func TestTrackUndelegationContVestingAcc(t *testing.T) { require.Equal(t, origCoins, cva.GetCoins()) // require the ability to undelegate all vested coins - cva = NewContinuousVestingAccount(addr, origCoins, now, endTime) + bacc.SetCoins(origCoins) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) + cva.TrackDelegation(endTime, origCoins) cva.TrackUndelegation(origCoins) require.Nil(t, cva.DelegatedFree) @@ -248,7 +267,8 @@ func TestTrackUndelegationContVestingAcc(t *testing.T) { require.Equal(t, origCoins, cva.GetCoins()) // require no modifications when the undelegation amount is zero - cva = NewContinuousVestingAccount(addr, origCoins, now, endTime) + bacc.SetCoins(origCoins) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) require.Panics(t, func() { cva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 0)}) @@ -258,7 +278,7 @@ func TestTrackUndelegationContVestingAcc(t *testing.T) { require.Equal(t, origCoins, cva.GetCoins()) // vest 50% and delegate to two validators - cva = NewContinuousVestingAccount(addr, origCoins, now, endTime) + cva = NewContinuousVestingAccount(&bacc, now.Unix(), endTime.Unix()) cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) cva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) @@ -281,9 +301,11 @@ func TestGetVestedCoinsDelVestingAcc(t *testing.T) { _, _, addr := keyPubAddr() origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) // require no coins are vested until schedule maturation - dva := NewDelayedVestingAccount(addr, origCoins, endTime) + dva := NewDelayedVestingAccount(&bacc, endTime.Unix()) vestedCoins := dva.GetVestedCoins(now) require.Nil(t, vestedCoins) @@ -298,9 +320,11 @@ func TestGetVestingCoinsDelVestingAcc(t *testing.T) { _, _, addr := keyPubAddr() origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) // require all coins vesting at the beginning of the schedule - dva := NewDelayedVestingAccount(addr, origCoins, endTime) + dva := NewDelayedVestingAccount(&bacc, endTime.Unix()) vestingCoins := dva.GetVestingCoins(now) require.Equal(t, origCoins, vestingCoins) @@ -315,10 +339,12 @@ func TestSpendableCoinsDelVestingAcc(t *testing.T) { _, _, addr := keyPubAddr() origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) // require that no coins are spendable in the beginning of the vesting // schedule - dva := NewDelayedVestingAccount(addr, origCoins, endTime) + dva := NewDelayedVestingAccount(&bacc, endTime.Unix()) spendableCoins := dva.SpendableCoins(now) require.Nil(t, spendableCoins) @@ -354,16 +380,20 @@ func TestTrackDelegationDelVestingAcc(t *testing.T) { _, _, addr := keyPubAddr() origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) // require the ability to delegate all vesting coins - dva := NewDelayedVestingAccount(addr, origCoins, endTime) + bacc.SetCoins(origCoins) + dva := NewDelayedVestingAccount(&bacc, endTime.Unix()) dva.TrackDelegation(now, origCoins) require.Equal(t, origCoins, dva.DelegatedVesting) require.Nil(t, dva.DelegatedFree) require.Nil(t, dva.GetCoins()) // require the ability to delegate all vested coins - dva = NewDelayedVestingAccount(addr, origCoins, endTime) + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) dva.TrackDelegation(endTime, origCoins) require.Nil(t, dva.DelegatedVesting) require.Equal(t, origCoins, dva.DelegatedFree) @@ -371,14 +401,16 @@ func TestTrackDelegationDelVestingAcc(t *testing.T) { // require the ability to delegate all coins half way through the vesting // schedule - dva = NewDelayedVestingAccount(addr, origCoins, endTime) + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) dva.TrackDelegation(now.Add(12*time.Hour), origCoins) require.Equal(t, origCoins, dva.DelegatedVesting) require.Nil(t, dva.DelegatedFree) require.Nil(t, dva.GetCoins()) // require no modifications when delegation amount is zero or not enough funds - dva = NewDelayedVestingAccount(addr, origCoins, endTime) + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) require.Panics(t, func() { dva.TrackDelegation(endTime, sdk.Coins{sdk.NewInt64Coin(testDenom, 1000000)}) @@ -394,9 +426,12 @@ func TestTrackUndelegationDelVestingAcc(t *testing.T) { _, _, addr := keyPubAddr() origCoins := sdk.Coins{sdk.NewInt64Coin(testDenom, 100)} + bacc := NewBaseAccountWithAddress(addr) + bacc.SetCoins(origCoins) // require the ability to undelegate all vesting coins - dva := NewDelayedVestingAccount(addr, origCoins, endTime) + bacc.SetCoins(origCoins) + dva := NewDelayedVestingAccount(&bacc, endTime.Unix()) dva.TrackDelegation(now, origCoins) dva.TrackUndelegation(origCoins) require.Nil(t, dva.DelegatedFree) @@ -404,7 +439,8 @@ func TestTrackUndelegationDelVestingAcc(t *testing.T) { require.Equal(t, origCoins, dva.GetCoins()) // require the ability to undelegate all vested coins - dva = NewDelayedVestingAccount(addr, origCoins, endTime) + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) dva.TrackDelegation(endTime, origCoins) dva.TrackUndelegation(origCoins) require.Nil(t, dva.DelegatedFree) @@ -412,7 +448,8 @@ func TestTrackUndelegationDelVestingAcc(t *testing.T) { require.Equal(t, origCoins, dva.GetCoins()) // require no modifications when the undelegation amount is zero - dva = NewDelayedVestingAccount(addr, origCoins, endTime) + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) require.Panics(t, func() { dva.TrackUndelegation(sdk.Coins{sdk.NewInt64Coin(testDenom, 0)}) @@ -422,7 +459,8 @@ func TestTrackUndelegationDelVestingAcc(t *testing.T) { require.Equal(t, origCoins, dva.GetCoins()) // vest 50% and delegate to two validators - dva = NewDelayedVestingAccount(addr, origCoins, endTime) + bacc.SetCoins(origCoins) + dva = NewDelayedVestingAccount(&bacc, endTime.Unix()) dva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) dva.TrackDelegation(now.Add(12*time.Hour), sdk.Coins{sdk.NewInt64Coin(testDenom, 50)}) diff --git a/x/auth/codec.go b/x/auth/codec.go index 624bdf4288db..0c8374d5ea7a 100644 --- a/x/auth/codec.go +++ b/x/auth/codec.go @@ -8,6 +8,10 @@ import ( func RegisterCodec(cdc *codec.Codec) { cdc.RegisterInterface((*Account)(nil), nil) cdc.RegisterConcrete(&BaseAccount{}, "auth/Account", nil) + cdc.RegisterInterface((*VestingAccount)(nil), nil) + cdc.RegisterConcrete(&BaseVestingAccount{}, "auth/BaseVestingAccount", nil) + cdc.RegisterConcrete(&ContinuousVestingAccount{}, "auth/ContinuousVestingAccount", nil) + cdc.RegisterConcrete(&DelayedVestingAccount{}, "auth/DelayedVestingAccount", nil) cdc.RegisterConcrete(StdTx{}, "auth/StdTx", nil) } diff --git a/x/bank/keeper_test.go b/x/bank/keeper_test.go index 0473578f47fd..8a3d4dc83882 100644 --- a/x/bank/keeper_test.go +++ b/x/bank/keeper_test.go @@ -213,7 +213,9 @@ func TestVestingAccountSend(t *testing.T) { addr1 := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) - vacc := auth.NewContinuousVestingAccount(addr1, origCoins, ctx.BlockHeader().Time, endTime) + bacc := auth.NewBaseAccountWithAddress(addr1) + bacc.SetCoins(origCoins) + vacc := auth.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) input.ak.SetAccount(ctx, vacc) // require that no coins be sendable at the beginning of the vesting schedule @@ -245,7 +247,9 @@ func TestVestingAccountReceive(t *testing.T) { addr1 := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) - vacc := auth.NewContinuousVestingAccount(addr1, origCoins, ctx.BlockHeader().Time, endTime) + bacc := auth.NewBaseAccountWithAddress(addr1) + bacc.SetCoins(origCoins) + vacc := auth.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) acc := input.ak.NewAccountWithAddress(ctx, addr2) input.ak.SetAccount(ctx, vacc) input.ak.SetAccount(ctx, acc) @@ -276,7 +280,9 @@ func TestDelegateCoins(t *testing.T) { addr1 := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) - vacc := auth.NewContinuousVestingAccount(addr1, origCoins, ctx.BlockHeader().Time, endTime) + bacc := auth.NewBaseAccountWithAddress(addr1) + bacc.SetCoins(origCoins) + vacc := auth.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) acc := input.ak.NewAccountWithAddress(ctx, addr2) input.ak.SetAccount(ctx, vacc) input.ak.SetAccount(ctx, acc) @@ -310,7 +316,9 @@ func TestUndelegateCoins(t *testing.T) { addr1 := sdk.AccAddress([]byte("addr1")) addr2 := sdk.AccAddress([]byte("addr2")) - vacc := auth.NewContinuousVestingAccount(addr1, origCoins, ctx.BlockHeader().Time, endTime) + bacc := auth.NewBaseAccountWithAddress(addr1) + bacc.SetCoins(origCoins) + vacc := auth.NewContinuousVestingAccount(&bacc, ctx.BlockHeader().Time.Unix(), endTime.Unix()) acc := input.ak.NewAccountWithAddress(ctx, addr2) input.ak.SetAccount(ctx, vacc) input.ak.SetAccount(ctx, acc) diff --git a/x/bank/simulation/msgs.go b/x/bank/simulation/msgs.go index 96df52f56f43..e7d16496b72e 100644 --- a/x/bank/simulation/msgs.go +++ b/x/bank/simulation/msgs.go @@ -64,7 +64,7 @@ func createSingleInputSendMsg(r *rand.Rand, ctx sdk.Context, accs []simulation.A toAcc = simulation.RandomAcc(r, accs) } toAddr := toAcc.Address - initFromCoins := mapper.GetAccount(ctx, fromAcc.Address).GetCoins() + initFromCoins := mapper.GetAccount(ctx, fromAcc.Address).SpendableCoins(ctx.BlockHeader().Time) if len(initFromCoins) == 0 { return fromAcc, "skipping, no coins at all", msg, true diff --git a/x/mock/simulation/account.go b/x/mock/simulation/account.go index 37dfbb2cdb1a..fb3fdb65d817 100644 --- a/x/mock/simulation/account.go +++ b/x/mock/simulation/account.go @@ -44,8 +44,10 @@ func RandomAccounts(r *rand.Rand, n int) []Account { } else { accs[i].PrivKey = ed25519.GenPrivKeyFromSecret(privkeySeed) } + accs[i].PubKey = accs[i].PrivKey.PubKey() accs[i].Address = sdk.AccAddress(accs[i].PubKey.Address()) } + return accs } diff --git a/x/mock/simulation/simulate.go b/x/mock/simulation/simulate.go index d2836bc7bd3b..81e360a1a827 100644 --- a/x/mock/simulation/simulate.go +++ b/x/mock/simulation/simulate.go @@ -19,7 +19,7 @@ import ( ) // AppStateFn returns the app state json bytes -type AppStateFn func(r *rand.Rand, accs []Account) json.RawMessage +type AppStateFn func(r *rand.Rand, accs []Account, genesisTimestamp time.Time) json.RawMessage // Simulate tests application by sending random messages. func Simulate(t *testing.T, app *baseapp.BaseApp, @@ -32,12 +32,13 @@ func Simulate(t *testing.T, app *baseapp.BaseApp, } // initialize the chain for the simulation -func initChain(r *rand.Rand, params Params, accounts []Account, - app *baseapp.BaseApp, - appStateFn AppStateFn) mockValidators { +func initChain( + r *rand.Rand, params Params, accounts []Account, + app *baseapp.BaseApp, appStateFn AppStateFn, genesisTimestamp time.Time, +) mockValidators { req := abci.RequestInitChain{ - AppStateBytes: appStateFn(r, accounts), + AppStateBytes: appStateFn(r, accounts, genesisTimestamp), } res := app.InitChain(req) validators := newMockValidators(r, res.Validators, params) @@ -62,9 +63,9 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, params := RandomParams(r) // := DefaultParams() fmt.Printf("Randomized simulation params: %+v\n", params) - timestamp := RandTimestamp(r) + genesisTimestamp := RandTimestamp(r) fmt.Printf("Starting the simulation from time %v, unixtime %v\n", - timestamp.UTC().Format(time.UnixDate), timestamp.Unix()) + genesisTimestamp.UTC().Format(time.UnixDate), genesisTimestamp.Unix()) timeDiff := maxTimePerBlock - minTimePerBlock accs := RandomAccounts(r, params.NumKeys) @@ -72,12 +73,12 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, // Second variable to keep pending validator set (delayed one block since // TM 0.24) Initially this is the same as the initial validator set - validators := initChain(r, params, accs, app, appStateFn) + validators := initChain(r, params, accs, app, appStateFn, genesisTimestamp) nextValidators := validators header := abci.Header{ Height: 1, - Time: timestamp, + Time: genesisTimestamp, ProposerAddress: validators.randomProposer(r), } opCount := 0