diff --git a/cmd/gaia/app/app.go b/cmd/gaia/app/app.go index 40b55493e..2c72e3aaf 100644 --- a/cmd/gaia/app/app.go +++ b/cmd/gaia/app/app.go @@ -16,6 +16,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" "github.com/cosmos/cosmos-sdk/x/bank" + distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/params" "github.com/cosmos/cosmos-sdk/x/slashing" @@ -43,6 +44,8 @@ type GaiaApp struct { keyStake *sdk.KVStoreKey tkeyStake *sdk.TransientStoreKey keySlashing *sdk.KVStoreKey + keyDistr *sdk.KVStoreKey + tkeyDistr *sdk.TransientStoreKey keyGov *sdk.KVStoreKey keyFeeCollection *sdk.KVStoreKey keyParams *sdk.KVStoreKey @@ -54,6 +57,7 @@ type GaiaApp struct { bankKeeper bank.Keeper stakeKeeper stake.Keeper slashingKeeper slashing.Keeper + distrKeeper distr.Keeper govKeeper gov.Keeper paramsKeeper params.Keeper } @@ -72,6 +76,8 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio keyAccount: sdk.NewKVStoreKey("acc"), keyStake: sdk.NewKVStoreKey("stake"), tkeyStake: sdk.NewTransientStoreKey("transient_stake"), + keyDistr: sdk.NewKVStoreKey("distr"), + tkeyDistr: sdk.NewTransientStoreKey("transient_distr"), keySlashing: sdk.NewKVStoreKey("slashing"), keyGov: sdk.NewKVStoreKey("gov"), keyFeeCollection: sdk.NewKVStoreKey("fee"), @@ -88,30 +94,33 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio // add handlers app.bankKeeper = bank.NewBaseKeeper(app.accountMapper) - + app.feeCollectionKeeper = auth.NewFeeCollectionKeeper( + app.cdc, + app.keyFeeCollection, + ) app.paramsKeeper = params.NewKeeper( app.cdc, app.keyParams, app.tkeyParams, ) - app.stakeKeeper = stake.NewKeeper( app.cdc, app.keyStake, app.tkeyStake, app.bankKeeper, app.paramsKeeper.Subspace(stake.DefaultParamspace), app.RegisterCodespace(stake.DefaultCodespace), ) - + app.distrKeeper = distr.NewKeeper( + app.cdc, + app.keyDistr, + app.paramsKeeper.Subspace(distr.DefaultParamspace), + app.bankKeeper, app.stakeKeeper, app.feeCollectionKeeper, + app.RegisterCodespace(stake.DefaultCodespace), + ) app.slashingKeeper = slashing.NewKeeper( app.cdc, app.keySlashing, app.stakeKeeper, app.paramsKeeper.Subspace(slashing.DefaultParamspace), app.RegisterCodespace(slashing.DefaultCodespace), ) - - app.stakeKeeper = app.stakeKeeper.WithHooks( - app.slashingKeeper.Hooks(), - ) - app.govKeeper = gov.NewKeeper( app.cdc, app.keyGov, @@ -119,15 +128,15 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio app.RegisterCodespace(gov.DefaultCodespace), ) - app.feeCollectionKeeper = auth.NewFeeCollectionKeeper( - app.cdc, - app.keyFeeCollection, - ) + // register the staking hooks + app.stakeKeeper = app.stakeKeeper.WithHooks( + NewHooks(app.distrKeeper.Hooks(), app.slashingKeeper.Hooks())) // register message routes app.Router(). AddRoute("bank", bank.NewHandler(app.bankKeeper)). AddRoute("stake", stake.NewHandler(app.stakeKeeper)). + AddRoute("distr", distr.NewHandler(app.distrKeeper)). AddRoute("slashing", slashing.NewHandler(app.slashingKeeper)). AddRoute("gov", gov.NewHandler(app.govKeeper)) @@ -138,11 +147,12 @@ func NewGaiaApp(logger log.Logger, db dbm.DB, traceStore io.Writer, baseAppOptio // initialize BaseApp app.SetInitChainer(app.initChainer) app.SetBeginBlocker(app.BeginBlocker) - app.SetEndBlocker(app.EndBlocker) app.SetAnteHandler(auth.NewAnteHandler(app.accountMapper, app.feeCollectionKeeper)) - app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, + app.MountStoresIAVL(app.keyMain, app.keyAccount, app.keyStake, app.keyDistr, app.keySlashing, app.keyGov, app.keyFeeCollection, app.keyParams) - app.MountStoresTransient(app.tkeyParams, app.tkeyStake) + app.MountStoresTransient(app.tkeyParams, app.tkeyStake, app.tkeyDistr) + app.SetEndBlocker(app.EndBlocker) + err := app.LoadLatestVersion(app.keyMain) if err != nil { cmn.Exit(err.Error()) @@ -156,6 +166,7 @@ func MakeCodec() *codec.Codec { var cdc = codec.New() bank.RegisterCodec(cdc) stake.RegisterCodec(cdc) + distr.RegisterCodec(cdc) slashing.RegisterCodec(cdc) gov.RegisterCodec(cdc) auth.RegisterCodec(cdc) @@ -168,6 +179,9 @@ func MakeCodec() *codec.Codec { func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) abci.ResponseBeginBlock { tags := slashing.BeginBlocker(ctx, req, app.slashingKeeper) + // distribute rewards from previous block + distr.BeginBlocker(ctx, req, app.distrKeeper) + return abci.ResponseBeginBlock{ Tags: tags.ToKVPairs(), } @@ -176,10 +190,13 @@ func (app *GaiaApp) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) ab // application updates every end block // nolint: unparam func (app *GaiaApp) EndBlocker(ctx sdk.Context, req abci.RequestEndBlock) abci.ResponseEndBlock { + tags := gov.EndBlocker(ctx, app.govKeeper) validatorUpdates := stake.EndBlocker(ctx, app.stakeKeeper) + // Add these new validators to the addr -> pubkey map. app.slashingKeeper.AddValidators(ctx, validatorUpdates) + return abci.ResponseEndBlock{ ValidatorUpdates: validatorUpdates, Tags: tags, @@ -208,18 +225,17 @@ func (app *GaiaApp) initChainer(ctx sdk.Context, req abci.RequestInitChain) abci // load the initial stake information validators, err := stake.InitGenesis(ctx, app.stakeKeeper, genesisState.StakeData) if err != nil { - panic(err) // TODO https://github.com/cosmos/cosmos-sdk/issues/468 - // return sdk.ErrGenesisParse("").TraceCause(err, "") + panic(err) // TODO find a way to do this w/o panics } // load the address to pubkey map slashing.InitGenesis(ctx, app.slashingKeeper, genesisState.SlashingData, genesisState.StakeData) gov.InitGenesis(ctx, app.govKeeper, genesisState.GovData) + distr.InitGenesis(ctx, app.distrKeeper, genesisState.DistrData) err = GaiaValidateGenesisState(genesisState) if err != nil { - // TODO find a way to do this w/o panics - panic(err) + panic(err) // TODO find a way to do this w/o panics } return abci.ResponseInitChain{ @@ -243,6 +259,7 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val genState := GenesisState{ Accounts: accounts, StakeData: stake.WriteGenesis(ctx, app.stakeKeeper), + DistrData: distr.WriteGenesis(ctx, app.distrKeeper), GovData: gov.WriteGenesis(ctx, app.govKeeper), } appState, err = codec.MarshalJSONIndent(app.cdc, genState) @@ -252,3 +269,43 @@ func (app *GaiaApp) ExportAppStateAndValidators() (appState json.RawMessage, val validators = stake.WriteValidators(ctx, app.stakeKeeper) return appState, validators, nil } + +//______________________________________________________________________________________________ + +// Combined Staking Hooks +type Hooks struct { + dh distr.Hooks + sh slashing.Hooks +} + +func NewHooks(dh distr.Hooks, sh slashing.Hooks) Hooks { + return Hooks{dh, sh} +} + +var _ sdk.StakingHooks = Hooks{} + +// nolint +func (h Hooks) OnValidatorCreated(ctx sdk.Context, addr sdk.ValAddress) { + h.dh.OnValidatorCreated(ctx, addr) +} +func (h Hooks) OnValidatorCommissionChange(ctx sdk.Context, addr sdk.ValAddress) { + h.dh.OnValidatorCommissionChange(ctx, addr) +} +func (h Hooks) OnValidatorRemoved(ctx sdk.Context, addr sdk.ValAddress) { + h.dh.OnValidatorRemoved(ctx, addr) +} +func (h Hooks) OnValidatorBonded(ctx sdk.Context, addr sdk.ConsAddress) { + h.sh.OnValidatorBonded(ctx, addr) +} +func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, addr sdk.ConsAddress) { + h.sh.OnValidatorBeginUnbonding(ctx, addr) +} +func (h Hooks) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.OnDelegationCreated(ctx, delAddr, valAddr) +} +func (h Hooks) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.OnDelegationSharesModified(ctx, delAddr, valAddr) +} +func (h Hooks) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.dh.OnDelegationRemoved(ctx, delAddr, valAddr) +} diff --git a/cmd/gaia/app/app_test.go b/cmd/gaia/app/app_test.go index d16cba40e..7023eb09c 100644 --- a/cmd/gaia/app/app_test.go +++ b/cmd/gaia/app/app_test.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/x/auth" + distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" "github.com/stretchr/testify/require" @@ -24,6 +25,7 @@ func setGenesis(gapp *GaiaApp, accs ...*auth.BaseAccount) error { genesisState := GenesisState{ Accounts: genaccs, StakeData: stake.DefaultGenesisState(), + DistrData: distr.DefaultGenesisState(), SlashingData: slashing.DefaultGenesisState(), } diff --git a/cmd/gaia/app/genesis.go b/cmd/gaia/app/genesis.go index f4a5e2b41..690d92502 100644 --- a/cmd/gaia/app/genesis.go +++ b/cmd/gaia/app/genesis.go @@ -11,6 +11,7 @@ import ( "github.com/cosmos/cosmos-sdk/server/config" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/auth" + distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" @@ -34,6 +35,7 @@ var ( type GenesisState struct { Accounts []GenesisAccount `json:"accounts"` StakeData stake.GenesisState `json:"stake"` + DistrData distr.GenesisState `json:"distr"` GovData gov.GenesisState `json:"gov"` SlashingData slashing.GenesisState `json:"slashing"` } @@ -196,6 +198,7 @@ func GaiaAppGenState(cdc *codec.Codec, appGenTxs []json.RawMessage) (genesisStat genesisState = GenesisState{ Accounts: genaccs, StakeData: stakeData, + DistrData: distr.DefaultGenesisState(), GovData: gov.DefaultGenesisState(), SlashingData: slashingData, } diff --git a/cmd/gaia/app/sim_test.go b/cmd/gaia/app/sim_test.go index 2521e0788..41b3da741 100644 --- a/cmd/gaia/app/sim_test.go +++ b/cmd/gaia/app/sim_test.go @@ -15,6 +15,7 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" banksim "github.com/cosmos/cosmos-sdk/x/bank/simulation" + distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" govsim "github.com/cosmos/cosmos-sdk/x/gov/simulation" "github.com/cosmos/cosmos-sdk/x/mock/simulation" @@ -60,13 +61,18 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { slashingGenesis := slashing.DefaultGenesisState() var validators []stake.Validator var delegations []stake.Delegation + // XXX Try different numbers of initially bonded validators numInitiallyBonded := int64(50) + valAddrs := make([]sdk.ValAddress, numInitiallyBonded) for i := 0; i < int(numInitiallyBonded); i++ { - validator := stake.NewValidator(sdk.ValAddress(accs[i].Address), accs[i].PubKey, stake.Description{}) + valAddr := sdk.ValAddress(accs[i].Address) + valAddrs[i] = valAddr + + validator := stake.NewValidator(valAddr, accs[i].PubKey, stake.Description{}) validator.Tokens = sdk.NewDec(100) validator.DelegatorShares = sdk.NewDec(100) - delegation := stake.Delegation{accs[i].Address, sdk.ValAddress(accs[i].Address), sdk.NewDec(100), 0} + delegation := stake.Delegation{accs[i].Address, valAddr, sdk.NewDec(100), 0} validators = append(validators, validator) delegations = append(delegations, delegation) } @@ -76,9 +82,11 @@ func appStateFn(r *rand.Rand, accs []simulation.Account) json.RawMessage { // No inflation, for now stakeGenesis.Params.InflationMax = sdk.NewDec(0) stakeGenesis.Params.InflationMin = sdk.NewDec(0) + genesis := GenesisState{ Accounts: genesisAccounts, StakeData: stakeGenesis, + DistrData: distr.DefaultGenesisWithValidators(valAddrs), SlashingData: slashingGenesis, GovData: govGenesis, } diff --git a/cmd/gaia/app/test_utils.go b/cmd/gaia/app/test_utils.go index 32e4c70a5..18946b397 100644 --- a/cmd/gaia/app/test_utils.go +++ b/cmd/gaia/app/test_utils.go @@ -6,6 +6,7 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" + distr "github.com/cosmos/cosmos-sdk/x/distribution" "github.com/cosmos/cosmos-sdk/x/gov" "github.com/cosmos/cosmos-sdk/x/slashing" "github.com/cosmos/cosmos-sdk/x/stake" @@ -72,6 +73,7 @@ func NewTestGaiaAppGenState( return GenesisState{ Accounts: genAccs, StakeData: stakeData, + DistrData: distr.DefaultGenesisState(), SlashingData: slashing.DefaultGenesisState(), GovData: gov.DefaultGenesisState(), }, nil diff --git a/cmd/gaia/cmd/gaiacli/main.go b/cmd/gaia/cmd/gaiacli/main.go index ef64f7bef..710966478 100644 --- a/cmd/gaia/cmd/gaiacli/main.go +++ b/cmd/gaia/cmd/gaiacli/main.go @@ -1,7 +1,11 @@ package main import ( + "os" + "path" + "github.com/spf13/cobra" + "github.com/spf13/viper" "github.com/tendermint/tendermint/libs/cli" @@ -10,18 +14,17 @@ import ( "github.com/cosmos/cosmos-sdk/client/lcd" "github.com/cosmos/cosmos-sdk/client/rpc" "github.com/cosmos/cosmos-sdk/client/tx" + "github.com/cosmos/cosmos-sdk/cmd/gaia/app" "github.com/cosmos/cosmos-sdk/version" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" bankcmd "github.com/cosmos/cosmos-sdk/x/bank/client/cli" + distrcmd "github.com/cosmos/cosmos-sdk/x/distribution/client/cli" govcmd "github.com/cosmos/cosmos-sdk/x/gov/client/cli" slashingcmd "github.com/cosmos/cosmos-sdk/x/slashing/client/cli" stakecmd "github.com/cosmos/cosmos-sdk/x/stake/client/cli" _ "github.com/cosmos/cosmos-sdk/client/lcd/statik" - "github.com/cosmos/cosmos-sdk/cmd/gaia/app" - "github.com/spf13/viper" - "os" - "path" ) const ( @@ -101,11 +104,13 @@ func main() { stakecmd.GetCmdCreateValidator(cdc), stakecmd.GetCmdEditValidator(cdc), stakecmd.GetCmdDelegate(cdc), - govcmd.GetCmdDeposit(cdc), stakecmd.GetCmdRedelegate(storeStake, cdc), + stakecmd.GetCmdUnbond(storeStake, cdc), + distrcmd.GetCmdWithdrawRewards(cdc), + distrcmd.GetCmdSetWithdrawAddr(cdc), + govcmd.GetCmdDeposit(cdc), bankcmd.SendTxCmd(cdc), govcmd.GetCmdSubmitProposal(cdc), - stakecmd.GetCmdUnbond(storeStake, cdc), slashingcmd.GetCmdUnjail(cdc), govcmd.GetCmdVote(cdc), )...) diff --git a/docs/spec/distribution/end_block.md b/docs/spec/distribution/end_block.md index 952d1832a..7f54ee972 100644 --- a/docs/spec/distribution/end_block.md +++ b/docs/spec/distribution/end_block.md @@ -15,7 +15,7 @@ pool which validator holds individually (`ValidatorDistribution.ProvisionsRewardPool`). ``` -func AllocateFees(feesCollected sdk.Coins, global Global, proposer ValidatorDistribution, +func AllocateFees(feesCollected sdk.Coins, feePool FeePool, proposer ValidatorDistribution, sumPowerPrecommitValidators, totalBondedTokens, communityTax, proposerCommissionRate sdk.Dec) @@ -28,13 +28,11 @@ func AllocateFees(feesCollected sdk.Coins, global Global, proposer ValidatorDist proposer.Pool += proposerReward - commission communityFunding = feesCollectedDec * communityTax - global.CommunityFund += communityFunding + feePool.CommunityFund += communityFunding poolReceived = feesCollectedDec - proposerReward - communityFunding - global.Pool += poolReceived - global.EverReceivedPool += poolReceived - global.LastReceivedPool = poolReceived + feePool.Pool += poolReceived SetValidatorDistribution(proposer) - SetGlobal(global) + SetFeePool(feePool) ``` diff --git a/docs/spec/distribution/hooks.md b/docs/spec/distribution/hooks.md index e1a18ef59..8c2b4b91a 100644 --- a/docs/spec/distribution/hooks.md +++ b/docs/spec/distribution/hooks.md @@ -6,7 +6,7 @@ The pool of a new delegator bond will be 0 for the height at which the bond was added, or the withdrawal has taken place. This is achieved by setting -`DelegatorDistInfo.WithdrawalHeight` to the height of the triggering transaction. +`DelegationDistInfo.WithdrawalHeight` to the height of the triggering transaction. ## Commission rate change diff --git a/docs/spec/distribution/overview.md b/docs/spec/distribution/overview.md index 13a4ea3f1..28b1bbedd 100644 --- a/docs/spec/distribution/overview.md +++ b/docs/spec/distribution/overview.md @@ -40,7 +40,7 @@ to independently and lazily withdraw their rewards. As a part of the lazy computations, each delegator holds an accumulation term specific to each validator which is used to estimate what their approximate -fair portion of tokens held in the global pool is owed to them. +fair portion of tokens held in the global fee pool is owed to them. ``` entitlement = delegator-accumulation / all-delegators-accumulation diff --git a/docs/spec/distribution/state.md b/docs/spec/distribution/state.md index 3e3669789..576f5390b 100644 --- a/docs/spec/distribution/state.md +++ b/docs/spec/distribution/state.md @@ -1,9 +1,9 @@ ## State -### Global +### FeePool All globally tracked parameters for distribution are stored within -`Global`. Rewards are collected and added to the reward pool and +`FeePool`. Rewards are collected and added to the reward pool and distributed to validators/delegators from here. Note that the reward pool holds decimal coins (`DecCoins`) to allow @@ -11,7 +11,7 @@ for fractions of coins to be received from operations like inflation. When coins are distributed from the pool they are truncated back to `sdk.Coins` which are non-decimal. - - Global: `0x00 -> amino(global)` + - FeePool: `0x00 -> amino(FeePool)` ```golang // coins with decimal @@ -22,7 +22,7 @@ type DecCoin struct { Denom string } -type Global struct { +type FeePool struct { TotalValAccumUpdateHeight int64 // last height which the total validator accum was updated TotalValAccum sdk.Dec // total valdator accum held by validators Pool DecCoins // funds for all validators which have yet to be withdrawn @@ -42,7 +42,7 @@ Validator distribution information for the relevant validator is updated each ti ```golang type ValidatorDistInfo struct { - GlobalWithdrawalHeight int64 // last height this validator withdrew from the global pool + FeePoolWithdrawalHeight int64 // last height this validator withdrew from the global fee pool Pool DecCoins // rewards owed to delegators, commission has already been charged (includes proposer reward) PoolCommission DecCoins // commission collected by this validator (pending withdrawal) @@ -59,10 +59,10 @@ properties change (aka bonded tokens etc.) its properties will remain constant and the delegator's _accumulation_ factor can be calculated passively knowing only the height of the last withdrawal and its current properties. - - DelegatorDistInfo: ` 0x02 | DelegatorAddr | ValOperatorAddr -> amino(delegatorDist)` + - DelegationDistInfo: ` 0x02 | DelegatorAddr | ValOperatorAddr -> amino(delegatorDist)` ```golang -type DelegatorDistInfo struct { +type DelegationDistInfo struct { WithdrawalHeight int64 // last time this delegation withdrew rewards } ``` diff --git a/docs/spec/distribution/transactions.md b/docs/spec/distribution/transactions.md index 0b89ae44e..821c4ba35 100644 --- a/docs/spec/distribution/transactions.md +++ b/docs/spec/distribution/transactions.md @@ -1,16 +1,15 @@ # Transactions -## TxWithdrawDelegationRewardsAll +## MsgWithdrawDelegationRewardsAll When a delegator wishes to withdraw their rewards it must send -`TxWithdrawDelegationRewardsAll`. Note that parts of this transaction logic are also +`MsgWithdrawDelegationRewardsAll`. Note that parts of this transaction logic are also triggered each with any change in individual delegations, such as an unbond, redelegation, or delegation of additional tokens to a specific validator. ```golang -type TxWithdrawDelegationRewardsAll struct { - delegatorAddr sdk.AccAddress - withdrawAddr sdk.AccAddress // address to make the withdrawal to +type MsgWithdrawDelegationRewardsAll struct { + DelegatorAddr sdk.AccAddress } func WithdrawDelegationRewardsAll(delegatorAddr, withdrawAddr sdk.AccAddress) @@ -26,31 +25,30 @@ func GetDelegatorRewardsAll(delegatorAddr sdk.AccAddress, height int64) DecCoins // collect all entitled rewards withdraw = 0 pool = stake.GetPool() - global = GetGlobal() + feePool = GetFeePool() for delegation = range delegations delInfo = GetDelegationDistInfo(delegation.DelegatorAddr, delegation.ValidatorAddr) valInfo = GetValidatorDistInfo(delegation.ValidatorAddr) validator = GetValidator(delegation.ValidatorAddr) - global, diWithdraw = delInfo.WithdrawRewards(global, valInfo, height, pool.BondedTokens, + feePool, diWithdraw = delInfo.WithdrawRewards(feePool, valInfo, height, pool.BondedTokens, validator.Tokens, validator.DelegatorShares, validator.Commission) withdraw += diWithdraw - SetGlobal(global) + SetFeePool(feePool) return withdraw ``` -## TxWithdrawDelegationReward +## MsgWithdrawDelegationReward under special circumstances a delegator may wish to withdraw rewards from only a single validator. ```golang -type TxWithdrawDelegationReward struct { - delegatorAddr sdk.AccAddress - validatorAddr sdk.AccAddress - withdrawAddr sdk.AccAddress // address to make the withdrawal to +type MsgWithdrawDelegationReward struct { + DelegatorAddr sdk.AccAddress + ValidatorAddr sdk.ValAddress } func WithdrawDelegationReward(delegatorAddr, validatorAddr, withdrawAddr sdk.AccAddress) @@ -58,39 +56,38 @@ func WithdrawDelegationReward(delegatorAddr, validatorAddr, withdrawAddr sdk.Acc // get all distribution scenarios pool = stake.GetPool() - global = GetGlobal() + feePool = GetFeePool() delInfo = GetDelegationDistInfo(delegatorAddr, validatorAddr) valInfo = GetValidatorDistInfo(validatorAddr) validator = GetValidator(validatorAddr) - global, withdraw = delInfo.WithdrawRewards(global, valInfo, height, pool.BondedTokens, + feePool, withdraw = delInfo.WithdrawRewards(feePool, valInfo, height, pool.BondedTokens, validator.Tokens, validator.DelegatorShares, validator.Commission) - SetGlobal(global) + SetFeePool(feePool) AddCoins(withdrawAddr, withdraw.TruncateDecimal()) ``` -## TxWithdrawValidatorRewardsAll +## MsgWithdrawValidatorRewardsAll When a validator wishes to withdraw their rewards it must send -`TxWithdrawValidatorRewardsAll`. Note that parts of this transaction logic are also +`MsgWithdrawValidatorRewardsAll`. Note that parts of this transaction logic are also triggered each with any change in individual delegations, such as an unbond, redelegation, or delegation of additional tokens to a specific validator. This transaction withdraws the validators commission fee, as well as any rewards earning on their self-delegation. ``` -type TxWithdrawValidatorRewardsAll struct { - operatorAddr sdk.AccAddress // validator address to withdraw from - withdrawAddr sdk.AccAddress // address to make the withdrawal to +type MsgWithdrawValidatorRewardsAll struct { + OperatorAddr sdk.ValAddress // validator address to withdraw from } func WithdrawValidatorRewardsAll(operatorAddr, withdrawAddr sdk.AccAddress) height = GetHeight() - global = GetGlobal() + feePool = GetFeePool() pool = GetPool() ValInfo = GetValidatorDistInfo(delegation.ValidatorAddr) validator = GetValidator(delegation.ValidatorAddr) @@ -99,10 +96,10 @@ func WithdrawValidatorRewardsAll(operatorAddr, withdrawAddr sdk.AccAddress) withdraw = GetDelegatorRewardsAll(validator.OperatorAddr, height) // withdrawal validator commission rewards - global, commission = valInfo.WithdrawCommission(global, valInfo, height, pool.BondedTokens, + feePool, commission = valInfo.WithdrawCommission(feePool, valInfo, height, pool.BondedTokens, validator.Tokens, validator.Commission) withdraw += commission - SetGlobal(global) + SetFeePool(feePool) AddCoins(withdrawAddr, withdraw.TruncateDecimal()) ``` @@ -117,7 +114,7 @@ block. The accum is always additive to the existing accum. This term is to be updated each time rewards are withdrawn from the system. ``` -func (g Global) UpdateTotalValAccum(height int64, totalBondedTokens Dec) Global +func (g FeePool) UpdateTotalValAccum(height int64, totalBondedTokens Dec) FeePool blocks = height - g.TotalValAccumUpdateHeight g.TotalValAccum += totalDelShares * blocks g.TotalValAccumUpdateHeight = height @@ -140,7 +137,7 @@ func (vi ValidatorDistInfo) UpdateTotalDelAccum(height int64, totalDelShares Dec return vi ``` -### Global pool to validator pool +### FeePool pool to validator pool Every time a validator or delegator executes a withdrawal or the validator is the proposer and receives new tokens, the relevant validator must move tokens @@ -148,14 +145,14 @@ from the passive global pool to their own pool. It is at this point that the commission is withdrawn ``` -func (vi ValidatorDistInfo) TakeAccum(g Global, height int64, totalBonded, vdTokens, commissionRate Dec) ( - vi ValidatorDistInfo, g Global) +func (vi ValidatorDistInfo) TakeFeePoolRewards(g FeePool, height int64, totalBonded, vdTokens, commissionRate Dec) ( + vi ValidatorDistInfo, g FeePool) g.UpdateTotalValAccum(height, totalBondedShares) // update the validators pool - blocks = height - vi.GlobalWithdrawalHeight - vi.GlobalWithdrawalHeight = height + blocks = height - vi.FeePoolWithdrawalHeight + vi.FeePoolWithdrawalHeight = height accum = blocks * vdTokens withdrawalTokens := g.Pool * accum / g.TotalValAccum commission := withdrawalTokens * commissionRate @@ -175,12 +172,12 @@ For delegations (including validator's self-delegation) all rewards from reward pool have already had the validator's commission taken away. ``` -func (di DelegatorDistInfo) WithdrawRewards(g Global, vi ValidatorDistInfo, +func (di DelegationDistInfo) WithdrawRewards(g FeePool, vi ValidatorDistInfo, height int64, totalBonded, vdTokens, totalDelShares, commissionRate Dec) ( - di DelegatorDistInfo, g Global, withdrawn DecCoins) + di DelegationDistInfo, g FeePool, withdrawn DecCoins) vi.UpdateTotalDelAccum(height, totalDelShares) - g = vi.TakeAccum(g, height, totalBonded, vdTokens, commissionRate) + g = vi.TakeFeePoolRewards(g, height, totalBonded, vdTokens, commissionRate) blocks = height - di.WithdrawalHeight di.WithdrawalHeight = height @@ -200,11 +197,11 @@ func (di DelegatorDistInfo) WithdrawRewards(g Global, vi ValidatorDistInfo, Commission is calculated each time rewards enter into the validator. ``` -func (vi ValidatorDistInfo) WithdrawCommission(g Global, height int64, +func (vi ValidatorDistInfo) WithdrawCommission(g FeePool, height int64, totalBonded, vdTokens, commissionRate Dec) ( - vi ValidatorDistInfo, g Global, withdrawn DecCoins) + vi ValidatorDistInfo, g FeePool, withdrawn DecCoins) - g = vi.TakeAccum(g, height, totalBonded, vdTokens, commissionRate) + g = vi.TakeFeePoolRewards(g, height, totalBonded, vdTokens, commissionRate) withdrawalTokens := vi.PoolCommission vi.PoolCommission = 0 diff --git a/examples/democoin/mock/validator.go b/examples/democoin/mock/validator.go index a54cdfedf..fc00b79be 100644 --- a/examples/democoin/mock/validator.go +++ b/examples/democoin/mock/validator.go @@ -48,6 +48,11 @@ func (v Validator) GetDelegatorShares() sdk.Dec { return sdk.ZeroDec() } +// Implements sdk.Validator +func (v Validator) GetCommission() sdk.Dec { + return sdk.ZeroDec() +} + // Implements sdk.Validator func (v Validator) GetJailed() bool { return false diff --git a/types/context.go b/types/context.go index a3150087b..bfb4c58fe 100644 --- a/types/context.go +++ b/types/context.go @@ -187,6 +187,12 @@ func (c Context) WithBlockTime(newTime time.Time) Context { return c.WithBlockHeader(newHeader) } +func (c Context) WithProposer(addr ConsAddress) Context { + newHeader := c.BlockHeader() + newHeader.ProposerAddress = addr.Bytes() + return c.WithBlockHeader(newHeader) +} + func (c Context) WithBlockHeight(height int64) Context { newHeader := c.BlockHeader() newHeader.Height = height diff --git a/types/decimal.go b/types/decimal.go index 13a8a26c1..e9623995f 100644 --- a/types/decimal.go +++ b/types/decimal.go @@ -241,6 +241,12 @@ func (d Dec) Quo(d2 Dec) Dec { return Dec{chopped} } +// quotient +func (d Dec) QuoInt(i Int) Dec { + mul := new(big.Int).Quo(d.Int, i.i) + return Dec{mul} +} + func (d Dec) String() string { str := d.ToLeftPaddedWithDecimals(Precision) placement := len(str) - Precision @@ -462,6 +468,6 @@ func MaxDec(d1, d2 Dec) Dec { } // intended to be used with require/assert: require.True(DecEq(...)) -func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, Dec, Dec) { - return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp, got +func DecEq(t *testing.T, exp, got Dec) (*testing.T, bool, string, string, string) { + return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() } diff --git a/types/int.go b/types/int.go index 1421a934d..c2bef7a64 100644 --- a/types/int.go +++ b/types/int.go @@ -2,6 +2,7 @@ package types import ( "encoding/json" + "testing" "math/big" "math/rand" @@ -525,3 +526,10 @@ func (i *Uint) UnmarshalJSON(bz []byte) error { } return unmarshalJSON(i.i, bz) } + +//__________________________________________________________________________ + +// intended to be used with require/assert: require.True(IntEq(...)) +func IntEq(t *testing.T, exp, got Int) (*testing.T, bool, string, string, string) { + return t, exp.Equal(got), "expected:\t%v\ngot:\t\t%v", exp.String(), got.String() +} diff --git a/types/stake.go b/types/stake.go index c84ed8d05..d529fa179 100644 --- a/types/stake.go +++ b/types/stake.go @@ -44,6 +44,7 @@ type Validator interface { GetConsAddr() ConsAddress // validation consensus address GetPower() Dec // validation power GetTokens() Dec // validation tokens + GetCommission() Dec // validator commission rate GetDelegatorShares() Dec // Total out standing delegator shares GetBondHeight() int64 // height in which the validator became active } diff --git a/x/auth/ante.go b/x/auth/ante.go index 68f6f6540..b6f880254 100644 --- a/x/auth/ante.go +++ b/x/auth/ante.go @@ -302,6 +302,3 @@ func getSignBytesList(chainID string, stdTx StdTx, stdSigs []StdSignature) (sign } return } - -// BurnFeeHandler burns all fees (decreasing total supply) -func BurnFeeHandler(_ sdk.Context, _ sdk.Tx, _ sdk.Coins) {} diff --git a/x/auth/feekeeper.go b/x/auth/feekeeper.go index d2cf7ce62..45894be1b 100644 --- a/x/auth/feekeeper.go +++ b/x/auth/feekeeper.go @@ -20,7 +20,6 @@ type FeeCollectionKeeper struct { cdc *codec.Codec } -// NewFeeKeeper returns a new FeeKeeper func NewFeeCollectionKeeper(cdc *codec.Codec, key sdk.StoreKey) FeeCollectionKeeper { return FeeCollectionKeeper{ key: key, @@ -28,7 +27,7 @@ func NewFeeCollectionKeeper(cdc *codec.Codec, key sdk.StoreKey) FeeCollectionKee } } -// Adds to Collected Fee Pool +// retrieves the collected fee pool func (fck FeeCollectionKeeper) GetCollectedFees(ctx sdk.Context) sdk.Coins { store := ctx.KVStore(fck.key) bz := store.Get(collectedFeesKey) @@ -41,14 +40,12 @@ func (fck FeeCollectionKeeper) GetCollectedFees(ctx sdk.Context) sdk.Coins { return *feePool } -// Sets to Collected Fee Pool func (fck FeeCollectionKeeper) setCollectedFees(ctx sdk.Context, coins sdk.Coins) { bz := fck.cdc.MustMarshalBinary(coins) store := ctx.KVStore(fck.key) store.Set(collectedFeesKey, bz) } -// Adds to Collected Fee Pool func (fck FeeCollectionKeeper) addCollectedFees(ctx sdk.Context, coins sdk.Coins) sdk.Coins { newCoins := fck.GetCollectedFees(ctx).Plus(coins) fck.setCollectedFees(ctx, newCoins) @@ -56,7 +53,7 @@ func (fck FeeCollectionKeeper) addCollectedFees(ctx sdk.Context, coins sdk.Coins return newCoins } -// Clears the collected Fee Pool +// clear the fee pool func (fck FeeCollectionKeeper) ClearCollectedFees(ctx sdk.Context) { fck.setCollectedFees(ctx, sdk.Coins{}) } diff --git a/x/distribution/abci_app.go b/x/distribution/abci_app.go new file mode 100644 index 000000000..2bdcadb6a --- /dev/null +++ b/x/distribution/abci_app.go @@ -0,0 +1,39 @@ +package distribution + +import ( + abci "github.com/tendermint/tendermint/abci/types" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/keeper" +) + +// set the proposer for determining distribution during endblock +func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, k keeper.Keeper) { + + if ctx.BlockHeight() > 1 { + previousPercentPrecommitVotes := getPreviousPercentPrecommitVotes(req) + previousProposer := k.GetPreviousProposerConsAddr(ctx) + k.AllocateFees(ctx, previousPercentPrecommitVotes, previousProposer) + } + + consAddr := sdk.ConsAddress(req.Header.ProposerAddress) + k.SetPreviousProposerConsAddr(ctx, consAddr) +} + +// percent precommit votes for the previous block +func getPreviousPercentPrecommitVotes(req abci.RequestBeginBlock) sdk.Dec { + + // determine the total number of signed power + totalPower, sumPrecommitPower := int64(0), int64(0) + for _, voteInfo := range req.LastCommitInfo.GetVotes() { + totalPower += voteInfo.Validator.Power + if voteInfo.SignedLastBlock { + sumPrecommitPower += voteInfo.Validator.Power + } + } + + if totalPower == 0 { + return sdk.ZeroDec() + } + return sdk.NewDec(sumPrecommitPower).Quo(sdk.NewDec(totalPower)) +} diff --git a/x/distribution/alias.go b/x/distribution/alias.go new file mode 100644 index 000000000..7f14f82a4 --- /dev/null +++ b/x/distribution/alias.go @@ -0,0 +1,76 @@ +// nolint +package distribution + +import ( + "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + "github.com/cosmos/cosmos-sdk/x/distribution/tags" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +type ( + Keeper = keeper.Keeper + Hooks = keeper.Hooks + + DelegatorWithdrawInfo = types.DelegatorWithdrawInfo + DelegationDistInfo = types.DelegationDistInfo + ValidatorDistInfo = types.ValidatorDistInfo + TotalAccum = types.TotalAccum + FeePool = types.FeePool + + MsgSetWithdrawAddress = types.MsgSetWithdrawAddress + MsgWithdrawDelegatorRewardsAll = types.MsgWithdrawDelegatorRewardsAll + MsgWithdrawDelegatorReward = types.MsgWithdrawDelegatorReward + MsgWithdrawValidatorRewardsAll = types.MsgWithdrawValidatorRewardsAll + + GenesisState = types.GenesisState +) + +var ( + NewKeeper = keeper.NewKeeper + + GetValidatorDistInfoKey = keeper.GetValidatorDistInfoKey + GetDelegationDistInfoKey = keeper.GetDelegationDistInfoKey + GetDelegationDistInfosKey = keeper.GetDelegationDistInfosKey + GetDelegatorWithdrawAddrKey = keeper.GetDelegatorWithdrawAddrKey + FeePoolKey = keeper.FeePoolKey + ValidatorDistInfoKey = keeper.ValidatorDistInfoKey + DelegationDistInfoKey = keeper.DelegationDistInfoKey + DelegatorWithdrawInfoKey = keeper.DelegatorWithdrawInfoKey + ProposerKey = keeper.ProposerKey + DefaultParamspace = keeper.DefaultParamspace + + InitialFeePool = types.InitialFeePool + + NewGenesisState = types.NewGenesisState + DefaultGenesisState = types.DefaultGenesisState + DefaultGenesisWithValidators = types.DefaultGenesisWithValidators + + RegisterCodec = types.RegisterCodec + + NewMsgSetWithdrawAddress = types.NewMsgSetWithdrawAddress + NewMsgWithdrawDelegatorRewardsAll = types.NewMsgWithdrawDelegatorRewardsAll + NewMsgWithdrawDelegationReward = types.NewMsgWithdrawDelegatorReward + NewMsgWithdrawValidatorRewardsAll = types.NewMsgWithdrawValidatorRewardsAll +) + +const ( + DefaultCodespace = types.DefaultCodespace + CodeInvalidInput = types.CodeInvalidInput +) + +var ( + ErrNilDelegatorAddr = types.ErrNilDelegatorAddr + ErrNilWithdrawAddr = types.ErrNilWithdrawAddr + ErrNilValidatorAddr = types.ErrNilValidatorAddr +) + +var ( + ActionModifyWithdrawAddress = tags.ActionModifyWithdrawAddress + ActionWithdrawDelegatorRewardsAll = tags.ActionWithdrawDelegatorRewardsAll + ActionWithdrawDelegatorReward = tags.ActionWithdrawDelegatorReward + ActionWithdrawValidatorRewardsAll = tags.ActionWithdrawValidatorRewardsAll + + TagAction = tags.Action + TagValidator = tags.Validator + TagDelegator = tags.Delegator +) diff --git a/x/distribution/client/cli/tx.go b/x/distribution/client/cli/tx.go new file mode 100644 index 000000000..b88968cda --- /dev/null +++ b/x/distribution/client/cli/tx.go @@ -0,0 +1,114 @@ +// nolint +package cli + +import ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/viper" + + "github.com/cosmos/cosmos-sdk/client/context" + "github.com/cosmos/cosmos-sdk/client/utils" + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + authcmd "github.com/cosmos/cosmos-sdk/x/auth/client/cli" + authtxb "github.com/cosmos/cosmos-sdk/x/auth/client/txbuilder" + + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +var ( + flagOnlyFromValidator = "only-from-validator" + flagIsValidator = "is-validator" +) + +// command to withdraw rewards +func GetCmdWithdrawRewards(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "withdraw-rewards", + Short: "withdraw rewards for either: all-delegations, a delegation, or a validator", + Args: cobra.NoArgs, + RunE: func(cmd *cobra.Command, args []string) error { + + onlyFromVal := viper.GetString(flagOnlyFromValidator) + isVal := viper.GetBool(flagIsValidator) + + if onlyFromVal != "" && isVal { + return fmt.Errorf("cannot use --%v, and --%v flags together", + flagOnlyFromValidator, flagIsValidator) + } + + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + var msg sdk.Msg + switch { + case isVal: + addr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + valAddr := sdk.ValAddress(addr.Bytes()) + msg = types.NewMsgWithdrawValidatorRewardsAll(valAddr) + case onlyFromVal != "": + delAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + + valAddr, err := sdk.ValAddressFromBech32(onlyFromVal) + if err != nil { + return err + } + + msg = types.NewMsgWithdrawDelegatorReward(delAddr, valAddr) + default: + delAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + msg = types.NewMsgWithdrawDelegatorRewardsAll(delAddr) + } + + // build and sign the transaction, then broadcast to Tendermint + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + cmd.Flags().String(flagOnlyFromValidator, "", "only withdraw from this validator address (in bech)") + cmd.Flags().Bool(flagIsValidator, false, "also withdraw validator's commission") + return cmd +} + +// GetCmdDelegate implements the delegate command. +func GetCmdSetWithdrawAddr(cdc *codec.Codec) *cobra.Command { + cmd := &cobra.Command{ + Use: "set-withdraw-addr [withdraw-addr]", + Short: "change the default withdraw address for rewards associated with an address", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + + txBldr := authtxb.NewTxBuilderFromCLI().WithCodec(cdc) + cliCtx := context.NewCLIContext(). + WithCodec(cdc). + WithAccountDecoder(authcmd.GetAccountDecoder(cdc)) + + delAddr, err := cliCtx.GetFromAddress() + if err != nil { + return err + } + + withdrawAddr, err := sdk.AccAddressFromBech32(args[0]) + if err != nil { + return err + } + + msg := types.NewMsgSetWithdrawAddress(delAddr, withdrawAddr) + + // build and sign the transaction, then broadcast to Tendermint + return utils.CompleteAndBroadcastTxCli(txBldr, cliCtx, []sdk.Msg{msg}) + }, + } + return cmd +} diff --git a/x/distribution/genesis.go b/x/distribution/genesis.go new file mode 100644 index 000000000..4ee5651a6 --- /dev/null +++ b/x/distribution/genesis.go @@ -0,0 +1,38 @@ +package distribution + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +// InitGenesis sets distribution information for genesis +func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) { + keeper.SetFeePool(ctx, data.FeePool) + keeper.SetCommunityTax(ctx, data.CommunityTax) + keeper.SetBaseProposerReward(ctx, data.BaseProposerReward) + keeper.SetBonusProposerReward(ctx, data.BonusProposerReward) + + for _, vdi := range data.ValidatorDistInfos { + keeper.SetValidatorDistInfo(ctx, vdi) + } + for _, ddi := range data.DelegationDistInfos { + keeper.SetDelegationDistInfo(ctx, ddi) + } + for _, dw := range data.DelegatorWithdrawInfos { + keeper.SetDelegatorWithdrawAddr(ctx, dw.DelegatorAddr, dw.WithdrawAddr) + } +} + +// WriteGenesis returns a GenesisState for a given context and keeper. The +// GenesisState will contain the pool, and validator/delegator distribution info's +func WriteGenesis(ctx sdk.Context, keeper Keeper) types.GenesisState { + feePool := keeper.GetFeePool(ctx) + communityTax := keeper.GetCommunityTax(ctx) + baseProposerRewards := keeper.GetBaseProposerReward(ctx) + bonusProposerRewards := keeper.GetBonusProposerReward(ctx) + vdis := keeper.GetAllValidatorDistInfos(ctx) + ddis := keeper.GetAllDelegationDistInfos(ctx) + dwis := keeper.GetAllDelegatorWithdrawInfos(ctx) + return NewGenesisState(feePool, communityTax, baseProposerRewards, + bonusProposerRewards, vdis, ddis, dwis) +} diff --git a/x/distribution/handler.go b/x/distribution/handler.go new file mode 100644 index 000000000..661d7d32a --- /dev/null +++ b/x/distribution/handler.go @@ -0,0 +1,84 @@ +package distribution + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/keeper" + "github.com/cosmos/cosmos-sdk/x/distribution/tags" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +func NewHandler(k keeper.Keeper) sdk.Handler { + return func(ctx sdk.Context, msg sdk.Msg) sdk.Result { + // NOTE msg already has validate basic run + switch msg := msg.(type) { + case types.MsgSetWithdrawAddress: + return handleMsgModifyWithdrawAddress(ctx, msg, k) + case types.MsgWithdrawDelegatorRewardsAll: + return handleMsgWithdrawDelegatorRewardsAll(ctx, msg, k) + case types.MsgWithdrawDelegatorReward: + return handleMsgWithdrawDelegatorReward(ctx, msg, k) + case types.MsgWithdrawValidatorRewardsAll: + return handleMsgWithdrawValidatorRewardsAll(ctx, msg, k) + default: + return sdk.ErrTxDecode("invalid message parse in distribution module").Result() + } + } +} + +//_____________________________________________________________________ + +// These functions assume everything has been authenticated, +// now we just perform action and save + +func handleMsgModifyWithdrawAddress(ctx sdk.Context, msg types.MsgSetWithdrawAddress, k keeper.Keeper) sdk.Result { + + k.SetDelegatorWithdrawAddr(ctx, msg.DelegatorAddr, msg.WithdrawAddr) + + tags := sdk.NewTags( + tags.Action, tags.ActionModifyWithdrawAddress, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} + +func handleMsgWithdrawDelegatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawDelegatorRewardsAll, k keeper.Keeper) sdk.Result { + + k.WithdrawDelegationRewardsAll(ctx, msg.DelegatorAddr) + + tags := sdk.NewTags( + tags.Action, tags.ActionWithdrawDelegatorRewardsAll, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} + +func handleMsgWithdrawDelegatorReward(ctx sdk.Context, msg types.MsgWithdrawDelegatorReward, k keeper.Keeper) sdk.Result { + + k.WithdrawDelegationReward(ctx, msg.DelegatorAddr, msg.ValidatorAddr) + + tags := sdk.NewTags( + tags.Action, tags.ActionWithdrawDelegatorReward, + tags.Delegator, []byte(msg.DelegatorAddr.String()), + tags.Validator, []byte(msg.ValidatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} + +func handleMsgWithdrawValidatorRewardsAll(ctx sdk.Context, msg types.MsgWithdrawValidatorRewardsAll, k keeper.Keeper) sdk.Result { + + k.WithdrawValidatorRewardsAll(ctx, msg.ValidatorAddr) + + tags := sdk.NewTags( + tags.Action, tags.ActionWithdrawValidatorRewardsAll, + tags.Validator, []byte(msg.ValidatorAddr.String()), + ) + return sdk.Result{ + Tags: tags, + } +} diff --git a/x/distribution/keeper/allocation.go b/x/distribution/keeper/allocation.go new file mode 100644 index 000000000..e6dd1c969 --- /dev/null +++ b/x/distribution/keeper/allocation.go @@ -0,0 +1,50 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +// Allocate fees handles distribution of the collected fees +func (k Keeper) AllocateFees(ctx sdk.Context, percentVotes sdk.Dec, proposer sdk.ConsAddress) { + ctx.Logger().With("module", "x/distribution").Error(fmt.Sprintf("allocation height: %v", ctx.BlockHeight())) + + // get the proposer of this block + proposerValidator := k.stakeKeeper.ValidatorByConsAddr(ctx, proposer) + proposerDist := k.GetValidatorDistInfo(ctx, proposerValidator.GetOperator()) + + // get the fees which have been getting collected through all the + // transactions in the block + feesCollected := k.feeCollectionKeeper.GetCollectedFees(ctx) + feesCollectedDec := types.NewDecCoins(feesCollected) + + // allocated rewards to proposer + baseProposerReward := k.GetBaseProposerReward(ctx) + bonusProposerReward := k.GetBonusProposerReward(ctx) + proposerMultiplier := baseProposerReward.Add(bonusProposerReward.Mul(percentVotes)) + proposerReward := feesCollectedDec.MulDec(proposerMultiplier) + + // apply commission + commission := proposerReward.MulDec(proposerValidator.GetCommission()) + remaining := proposerReward.Minus(commission) + proposerDist.PoolCommission = proposerDist.PoolCommission.Plus(commission) + proposerDist.Pool = proposerDist.Pool.Plus(remaining) + + // allocate community funding + communityTax := k.GetCommunityTax(ctx) + communityFunding := feesCollectedDec.MulDec(communityTax) + feePool := k.GetFeePool(ctx) + feePool.CommunityPool = feePool.CommunityPool.Plus(communityFunding) + + // set the global pool within the distribution module + poolReceived := feesCollectedDec.Minus(proposerReward).Minus(communityFunding) + feePool.Pool = feePool.Pool.Plus(poolReceived) + + k.SetValidatorDistInfo(ctx, proposerDist) + k.SetFeePool(ctx, feePool) + + // clear the now distributed fees + k.feeCollectionKeeper.ClearCollectedFees(ctx) +} diff --git a/x/distribution/keeper/allocation_test.go b/x/distribution/keeper/allocation_test.go new file mode 100644 index 000000000..441739ebe --- /dev/null +++ b/x/distribution/keeper/allocation_test.go @@ -0,0 +1,110 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestAllocateFeesBasic(t *testing.T) { + + // no community tax on inputs + ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + totalPower := int64(10) + totalPowerDec := sdk.NewDec(totalPower) + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, totalPower) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // verify everything has been set in staking correctly + validator, found := sk.GetValidator(ctx, valOpAddr1) + require.True(t, found) + require.Equal(t, sdk.Bonded, validator.Status) + assert.True(sdk.DecEq(t, totalPowerDec, validator.Tokens)) + assert.True(sdk.DecEq(t, totalPowerDec, validator.DelegatorShares)) + bondedTokens := sk.TotalPower(ctx) + assert.True(sdk.DecEq(t, totalPowerDec, bondedTokens)) + + // initial fee pool should be empty + feePool := keeper.GetFeePool(ctx) + require.Nil(t, feePool.Pool) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // verify that these fees have been received by the feePool + percentProposer := sdk.NewDecWithPrec(5, 2) + percentRemaining := sdk.OneDec().Sub(percentProposer) + feePool = keeper.GetFeePool(ctx) + expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining) + require.Equal(t, 1, len(feePool.Pool)) + require.True(sdk.DecEq(t, expRes, feePool.Pool[0].Amount)) +} + +func TestAllocateFeesWithCommunityTax(t *testing.T) { + communityTax := sdk.NewDecWithPrec(1, 2) //1% + ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, communityTax) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + totalPower := int64(10) + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, totalPower) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // verify that these fees have been received by the feePool + feePool := keeper.GetFeePool(ctx) + // 5% goes to proposer, 1% community tax + percentProposer := sdk.NewDecWithPrec(5, 2) + percentRemaining := sdk.OneDec().Sub(communityTax.Add(percentProposer)) + expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining) + require.Equal(t, 1, len(feePool.Pool)) + require.True(sdk.DecEq(t, expRes, feePool.Pool[0].Amount)) +} + +func TestAllocateFeesWithPartialPrecommitPower(t *testing.T) { + communityTax := sdk.NewDecWithPrec(1, 2) + ctx, _, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, communityTax) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + totalPower := int64(100) + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, totalPower) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + percentPrecommitVotes := sdk.NewDecWithPrec(25, 2) + keeper.AllocateFees(ctx, percentPrecommitVotes, valConsAddr1) + + // verify that these fees have been received by the feePool + feePool := keeper.GetFeePool(ctx) + // 1% + 4%*0.25 to proposer + 1% community tax = 97% + percentProposer := sdk.NewDecWithPrec(1, 2).Add(sdk.NewDecWithPrec(4, 2).Mul(percentPrecommitVotes)) + percentRemaining := sdk.OneDec().Sub(communityTax.Add(percentProposer)) + expRes := sdk.NewDecFromInt(feeInputs).Mul(percentRemaining) + require.Equal(t, 1, len(feePool.Pool)) + require.True(sdk.DecEq(t, expRes, feePool.Pool[0].Amount)) +} diff --git a/x/distribution/keeper/delegation.go b/x/distribution/keeper/delegation.go new file mode 100644 index 000000000..b7443a0c1 --- /dev/null +++ b/x/distribution/keeper/delegation.go @@ -0,0 +1,130 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +// get the delegator distribution info +func (k Keeper) GetDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress, + valOperatorAddr sdk.ValAddress) (ddi types.DelegationDistInfo) { + + store := ctx.KVStore(k.storeKey) + + b := store.Get(GetDelegationDistInfoKey(delAddr, valOperatorAddr)) + if b == nil { + panic("Stored delegation-distribution info should not have been nil") + } + + k.cdc.MustUnmarshalBinary(b, &ddi) + return +} + +// set the delegator distribution info +func (k Keeper) SetDelegationDistInfo(ctx sdk.Context, ddi types.DelegationDistInfo) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(ddi) + store.Set(GetDelegationDistInfoKey(ddi.DelegatorAddr, ddi.ValOperatorAddr), b) +} + +// remove a delegator distribution info +func (k Keeper) RemoveDelegationDistInfo(ctx sdk.Context, delAddr sdk.AccAddress, + valOperatorAddr sdk.ValAddress) { + + store := ctx.KVStore(k.storeKey) + store.Delete(GetDelegationDistInfoKey(delAddr, valOperatorAddr)) +} + +//___________________________________________________________________________________________ + +// get the delegator withdraw address, return the delegator address if not set +func (k Keeper) GetDelegatorWithdrawAddr(ctx sdk.Context, delAddr sdk.AccAddress) sdk.AccAddress { + store := ctx.KVStore(k.storeKey) + + b := store.Get(GetDelegatorWithdrawAddrKey(delAddr)) + if b == nil { + return delAddr + } + return sdk.AccAddress(b) +} + +// set the delegator withdraw address +func (k Keeper) SetDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) { + store := ctx.KVStore(k.storeKey) + store.Set(GetDelegatorWithdrawAddrKey(delAddr), withdrawAddr.Bytes()) +} + +// remove a delegator withdraw info +func (k Keeper) RemoveDelegatorWithdrawAddr(ctx sdk.Context, delAddr, withdrawAddr sdk.AccAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetDelegatorWithdrawAddrKey(delAddr)) +} + +//___________________________________________________________________________________________ + +// withdraw all the rewards for a single delegation +func (k Keeper) WithdrawDelegationReward(ctx sdk.Context, delegatorAddr sdk.AccAddress, + validatorAddr sdk.ValAddress) { + + height := ctx.BlockHeight() + bondedTokens := k.stakeKeeper.TotalPower(ctx) + feePool := k.GetFeePool(ctx) + delInfo := k.GetDelegationDistInfo(ctx, delegatorAddr, validatorAddr) + valInfo := k.GetValidatorDistInfo(ctx, validatorAddr) + validator := k.stakeKeeper.Validator(ctx, validatorAddr) + delegation := k.stakeKeeper.Delegation(ctx, delegatorAddr, validatorAddr) + + delInfo, valInfo, feePool, withdraw := delInfo.WithdrawRewards(feePool, valInfo, height, bondedTokens, + validator.GetTokens(), validator.GetDelegatorShares(), delegation.GetShares(), validator.GetCommission()) + + k.SetFeePool(ctx, feePool) + k.SetValidatorDistInfo(ctx, valInfo) + k.SetDelegationDistInfo(ctx, delInfo) + withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, delegatorAddr) + _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, withdraw.TruncateDecimal()) + if err != nil { + panic(err) + } +} + +//___________________________________________________________________________________________ + +// return all rewards for all delegations of a delegator +func (k Keeper) WithdrawDelegationRewardsAll(ctx sdk.Context, delegatorAddr sdk.AccAddress) { + height := ctx.BlockHeight() + withdraw := k.getDelegatorRewardsAll(ctx, delegatorAddr, height) + withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, delegatorAddr) + _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, withdraw.TruncateDecimal()) + if err != nil { + panic(err) + } +} + +// return all rewards for all delegations of a delegator +func (k Keeper) getDelegatorRewardsAll(ctx sdk.Context, delAddr sdk.AccAddress, height int64) types.DecCoins { + + withdraw := types.DecCoins{} + bondedTokens := k.stakeKeeper.TotalPower(ctx) + feePool := k.GetFeePool(ctx) + + // iterate over all the delegations + operationAtDelegation := func(_ int64, del sdk.Delegation) (stop bool) { + valAddr := del.GetValidator() + delInfo := k.GetDelegationDistInfo(ctx, delAddr, valAddr) + valInfo := k.GetValidatorDistInfo(ctx, valAddr) + validator := k.stakeKeeper.Validator(ctx, valAddr) + delegation := k.stakeKeeper.Delegation(ctx, delAddr, valAddr) + + delInfo, valInfo, feePool, diWithdraw := delInfo.WithdrawRewards(feePool, valInfo, height, bondedTokens, + validator.GetTokens(), validator.GetDelegatorShares(), delegation.GetShares(), validator.GetCommission()) + withdraw = withdraw.Plus(diWithdraw) + k.SetFeePool(ctx, feePool) + k.SetValidatorDistInfo(ctx, valInfo) + k.SetDelegationDistInfo(ctx, delInfo) + return false + } + k.stakeKeeper.IterateDelegations(ctx, delAddr, operationAtDelegation) + + k.SetFeePool(ctx, feePool) + return withdraw +} diff --git a/x/distribution/keeper/delegation_test.go b/x/distribution/keeper/delegation_test.go new file mode 100644 index 000000000..8415c708a --- /dev/null +++ b/x/distribution/keeper/delegation_test.go @@ -0,0 +1,250 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/stretchr/testify/require" +) + +func TestWithdrawDelegationRewardBasic(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, 10) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, int64(90), amt.Int64()) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw delegation + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + expRes := sdk.NewDec(90).Add(sdk.NewDec(100).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100 tokens * 10/20 + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawDelegationRewardWithCommission(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator with 10% commission + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, int64(90), amt.Int64()) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw delegation + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + expRes := sdk.NewDec(90).Add(sdk.NewDec(90).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100*90% tokens * 10/20 + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawDelegationRewardTwoDelegators(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator with 10% commission + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, int64(90), amt.Int64()) + + msgDelegate = stake.NewTestMsgDelegate(delAddr2, valOpAddr1, 20) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) + require.Equal(t, int64(80), amt.Int64()) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // delegator 1 withdraw delegation + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + expRes := sdk.NewDec(90).Add(sdk.NewDec(90).Quo(sdk.NewDec(4))).TruncateInt() // 90 + 100*90% tokens * 10/40 + require.True(sdk.IntEq(t, expRes, amt)) +} + +// this test demonstrates how two delegators with the same power can end up +// with different rewards in the end +func TestWithdrawDelegationRewardTwoDelegatorsUneven(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator with no commission + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, 10, sdk.ZeroDec()) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, int64(90), amt.Int64()) + + msgDelegate = stake.NewTestMsgDelegate(delAddr2, valOpAddr1, 10) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) + require.Equal(t, int64(90), amt.Int64()) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(90) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + ctx = ctx.WithBlockHeight(1) + + // delegator 1 withdraw delegation early, delegator 2 just keeps it's accum + keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + expRes1 := sdk.NewDec(90).Add(sdk.NewDec(90).Quo(sdk.NewDec(3))).TruncateInt() // 90 + 100 * 10/30 + require.True(sdk.IntEq(t, expRes1, amt)) + + // allocate 200 denom of fees + feeInputs = sdk.NewInt(180) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + ctx = ctx.WithBlockHeight(2) + + // delegator 2 now withdraws everything it's entitled to + keeper.WithdrawDelegationReward(ctx, delAddr2, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) + // existingTokens + (100+200 * (10/(20+30)) + withdrawnFromVal := sdk.NewDec(60 + 180).Mul(sdk.NewDec(2)).Quo(sdk.NewDec(5)) + expRes2 := sdk.NewDec(90).Add(withdrawnFromVal).TruncateInt() + require.True(sdk.IntEq(t, expRes2, amt)) + + // finally delegator 1 withdraws the remainder of its reward + keeper.WithdrawDelegationReward(ctx, delAddr1, valOpAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + remainingInVal := sdk.NewDec(60 + 180).Sub(withdrawnFromVal) + expRes3 := sdk.NewDecFromInt(expRes1).Add(remainingInVal.Mul(sdk.NewDec(1)).Quo(sdk.NewDec(3))).TruncateInt() + require.True(sdk.IntEq(t, expRes3, amt)) + + // verify the final withdraw amounts are different + require.True(t, expRes2.GT(expRes3)) +} + +func TestWithdrawDelegationRewardsAll(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //make some validators with different commissions + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr2, valConsPk2, 50, sdk.NewDecWithPrec(2, 1)) + got = stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr3, valConsPk3, 40, sdk.NewDecWithPrec(3, 1)) + got = stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate to all the validators + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) + require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) + msgDelegate = stake.NewTestMsgDelegate(delAddr1, valOpAddr2, 20) + require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) + msgDelegate = stake.NewTestMsgDelegate(delAddr1, valOpAddr3, 30) + require.True(t, stakeHandler(ctx, msgDelegate).IsOK()) + + // 40 tokens left after delegating 60 of them + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, int64(40), amt.Int64()) + + // total power of each validator: + // validator 1: 10 (self) + 10 (delegator) = 20 + // validator 2: 50 (self) + 20 (delegator) = 70 + // validator 3: 40 (self) + 30 (delegator) = 70 + // grand total: 160 + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(1000) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw delegation + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawDelegationRewardsAll(ctx, delAddr1) + amt = accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + + // orig-amount + fees *(1-proposerReward)* (val1Portion * delegatorPotion * (1-val1Commission) ... etc) + // + fees *(proposerReward) * (delegatorPotion * (1-val1Commission)) + // 40 + 1000 *(1- 0.95)* (20/160 * 10/20 * 0.9 + 70/160 * 20/70 * 0.8 + 70/160 * 30/70 * 0.7) + // 40 + 1000 *( 0.05) * (10/20 * 0.9) + feesInNonProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(95, 2)) + feesInProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(5, 2)) + feesInVal1 := feesInNonProposer.Mul(sdk.NewDec(10).Quo(sdk.NewDec(160))).Mul(sdk.NewDecWithPrec(9, 1)) + feesInVal2 := feesInNonProposer.Mul(sdk.NewDec(20).Quo(sdk.NewDec(160))).Mul(sdk.NewDecWithPrec(8, 1)) + feesInVal3 := feesInNonProposer.Mul(sdk.NewDec(30).Quo(sdk.NewDec(160))).Mul(sdk.NewDecWithPrec(7, 1)) + feesInVal1Proposer := feesInProposer.Mul(sdk.NewDec(10).Quo(sdk.NewDec(20))).Mul(sdk.NewDecWithPrec(9, 1)) + expRes := sdk.NewDec(40).Add(feesInVal1).Add(feesInVal2).Add(feesInVal3).Add(feesInVal1Proposer).TruncateInt() + require.True(sdk.IntEq(t, expRes, amt)) +} diff --git a/x/distribution/keeper/genesis.go b/x/distribution/keeper/genesis.go new file mode 100644 index 000000000..06b153a51 --- /dev/null +++ b/x/distribution/keeper/genesis.go @@ -0,0 +1,50 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +// Get the set of all validator-distribution-info's with no limits, used during genesis dump +func (k Keeper) GetAllValidatorDistInfos(ctx sdk.Context) (vdis []types.ValidatorDistInfo) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, ValidatorDistInfoKey) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var vdi types.ValidatorDistInfo + k.cdc.MustUnmarshalBinary(iterator.Value(), &vdi) + vdis = append(vdis, vdi) + } + return vdis +} + +// Get the set of all delegator-distribution-info's with no limits, used during genesis dump +func (k Keeper) GetAllDelegationDistInfos(ctx sdk.Context) (ddis []types.DelegationDistInfo) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + var ddi types.DelegationDistInfo + k.cdc.MustUnmarshalBinary(iterator.Value(), &ddi) + ddis = append(ddis, ddi) + } + return ddis +} + +// Get the set of all delegator-withdraw addresses with no limits, used during genesis dump +func (k Keeper) GetAllDelegatorWithdrawInfos(ctx sdk.Context) (dwis []types.DelegatorWithdrawInfo) { + store := ctx.KVStore(k.storeKey) + iterator := sdk.KVStorePrefixIterator(store, DelegationDistInfoKey) + defer iterator.Close() + + for ; iterator.Valid(); iterator.Next() { + dw := types.DelegatorWithdrawInfo{ + DelegatorAddr: sdk.AccAddress(iterator.Key()), + WithdrawAddr: sdk.AccAddress(iterator.Value()), + } + dwis = append(dwis, dw) + } + return dwis +} diff --git a/x/distribution/keeper/hooks.go b/x/distribution/keeper/hooks.go new file mode 100644 index 000000000..721a26db1 --- /dev/null +++ b/x/distribution/keeper/hooks.go @@ -0,0 +1,97 @@ +package keeper + +import ( + "fmt" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +// Create a new validator distribution record +func (k Keeper) onValidatorCreated(ctx sdk.Context, addr sdk.ValAddress) { + + height := ctx.BlockHeight() + vdi := types.ValidatorDistInfo{ + OperatorAddr: addr, + FeePoolWithdrawalHeight: height, + Pool: types.DecCoins{}, + PoolCommission: types.DecCoins{}, + DelAccum: types.NewTotalAccum(height), + } + k.SetValidatorDistInfo(ctx, vdi) +} + +// Withdrawal all validator rewards +func (k Keeper) onValidatorCommissionChange(ctx sdk.Context, addr sdk.ValAddress) { + k.WithdrawValidatorRewardsAll(ctx, addr) +} + +// Withdrawal all validator distribution rewards and cleanup the distribution record +func (k Keeper) onValidatorRemoved(ctx sdk.Context, addr sdk.ValAddress) { + k.RemoveValidatorDistInfo(ctx, addr) +} + +//_________________________________________________________________________________________ + +// Create a new delegator distribution record +func (k Keeper) onDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, + valAddr sdk.ValAddress) { + + ddi := types.DelegationDistInfo{ + DelegatorAddr: delAddr, + ValOperatorAddr: valAddr, + WithdrawalHeight: ctx.BlockHeight(), + } + k.SetDelegationDistInfo(ctx, ddi) + ctx.Logger().With("module", "x/distribution").Error(fmt.Sprintf("ddi created: %v", ddi)) +} + +// Withdrawal all validator rewards +func (k Keeper) onDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, + valAddr sdk.ValAddress) { + + k.WithdrawDelegationReward(ctx, delAddr, valAddr) +} + +// Withdrawal all validator distribution rewards and cleanup the distribution record +func (k Keeper) onDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, + valAddr sdk.ValAddress) { + + k.RemoveDelegationDistInfo(ctx, delAddr, valAddr) +} + +//_________________________________________________________________________________________ + +// Wrapper struct +type Hooks struct { + k Keeper +} + +var _ sdk.StakingHooks = Hooks{} + +// New Validator Hooks +func (k Keeper) Hooks() Hooks { return Hooks{k} } + +// nolint +func (h Hooks) OnValidatorCreated(ctx sdk.Context, addr sdk.ValAddress) { + h.k.onValidatorCreated(ctx, addr) +} +func (h Hooks) OnValidatorCommissionChange(ctx sdk.Context, addr sdk.ValAddress) { + h.k.onValidatorCommissionChange(ctx, addr) +} +func (h Hooks) OnValidatorRemoved(ctx sdk.Context, addr sdk.ValAddress) { + h.k.onValidatorRemoved(ctx, addr) +} +func (h Hooks) OnDelegationCreated(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.k.onDelegationCreated(ctx, delAddr, valAddr) +} +func (h Hooks) OnDelegationSharesModified(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.k.onDelegationSharesModified(ctx, delAddr, valAddr) +} +func (h Hooks) OnDelegationRemoved(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) { + h.k.onDelegationRemoved(ctx, delAddr, valAddr) +} + +// nolint - unused hooks for interface +func (h Hooks) OnValidatorBonded(ctx sdk.Context, addr sdk.ConsAddress) {} +func (h Hooks) OnValidatorBeginUnbonding(ctx sdk.Context, addr sdk.ConsAddress) {} diff --git a/x/distribution/keeper/keeper.go b/x/distribution/keeper/keeper.go new file mode 100644 index 000000000..0ccf76ca6 --- /dev/null +++ b/x/distribution/keeper/keeper.go @@ -0,0 +1,129 @@ +package keeper + +import ( + "github.com/cosmos/cosmos-sdk/codec" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/cosmos/cosmos-sdk/x/params" +) + +// keeper of the stake store +type Keeper struct { + storeKey sdk.StoreKey + cdc *codec.Codec + paramSpace params.Subspace + bankKeeper types.BankKeeper + stakeKeeper types.StakeKeeper + feeCollectionKeeper types.FeeCollectionKeeper + + // codespace + codespace sdk.CodespaceType +} + +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, paramSpace params.Subspace, ck types.BankKeeper, + sk types.StakeKeeper, fck types.FeeCollectionKeeper, codespace sdk.CodespaceType) Keeper { + + keeper := Keeper{ + storeKey: key, + cdc: cdc, + paramSpace: paramSpace.WithTypeTable(ParamTypeTable()), + bankKeeper: ck, + stakeKeeper: sk, + feeCollectionKeeper: fck, + codespace: codespace, + } + return keeper +} + +//______________________________________________________________________ + +// get the global fee pool distribution info +func (k Keeper) GetFeePool(ctx sdk.Context) (feePool types.FeePool) { + store := ctx.KVStore(k.storeKey) + b := store.Get(FeePoolKey) + if b == nil { + panic("Stored fee pool should not have been nil") + } + k.cdc.MustUnmarshalBinary(b, &feePool) + return +} + +// set the global fee pool distribution info +func (k Keeper) SetFeePool(ctx sdk.Context, feePool types.FeePool) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(feePool) + store.Set(FeePoolKey, b) +} + +//______________________________________________________________________ + +// set the proposer public key for this block +func (k Keeper) GetPreviousProposerConsAddr(ctx sdk.Context) (consAddr sdk.ConsAddress) { + store := ctx.KVStore(k.storeKey) + + b := store.Get(ProposerKey) + if b == nil { + panic("Previous proposer not set") + } + + k.cdc.MustUnmarshalBinary(b, &consAddr) + return +} + +// get the proposer public key for this block +func (k Keeper) SetPreviousProposerConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(consAddr) + store.Set(ProposerKey, b) +} + +//______________________________________________________________________ +// PARAM STORE + +// Type declaration for parameters +func ParamTypeTable() params.TypeTable { + return params.NewTypeTable( + ParamStoreKeyCommunityTax, sdk.Dec{}, + ParamStoreKeyBaseProposerReward, sdk.Dec{}, + ParamStoreKeyBonusProposerReward, sdk.Dec{}, + ) +} + +// Returns the current CommunityTax rate from the global param store +// nolint: errcheck +func (k Keeper) GetCommunityTax(ctx sdk.Context) sdk.Dec { + var percent sdk.Dec + k.paramSpace.Get(ctx, ParamStoreKeyCommunityTax, &percent) + return percent +} + +// nolint: errcheck +func (k Keeper) SetCommunityTax(ctx sdk.Context, percent sdk.Dec) { + k.paramSpace.Set(ctx, ParamStoreKeyCommunityTax, &percent) +} + +// Returns the current BaseProposerReward rate from the global param store +// nolint: errcheck +func (k Keeper) GetBaseProposerReward(ctx sdk.Context) sdk.Dec { + var percent sdk.Dec + k.paramSpace.Get(ctx, ParamStoreKeyBaseProposerReward, &percent) + return percent +} + +// nolint: errcheck +func (k Keeper) SetBaseProposerReward(ctx sdk.Context, percent sdk.Dec) { + k.paramSpace.Set(ctx, ParamStoreKeyBaseProposerReward, &percent) +} + +// Returns the current BaseProposerReward rate from the global param store +// nolint: errcheck +func (k Keeper) GetBonusProposerReward(ctx sdk.Context) sdk.Dec { + var percent sdk.Dec + k.paramSpace.Get(ctx, ParamStoreKeyBonusProposerReward, &percent) + return percent +} + +// nolint: errcheck +func (k Keeper) SetBonusProposerReward(ctx sdk.Context, percent sdk.Dec) { + k.paramSpace.Set(ctx, ParamStoreKeyBonusProposerReward, &percent) +} diff --git a/x/distribution/keeper/keeper_test.go b/x/distribution/keeper/keeper_test.go new file mode 100644 index 000000000..824430511 --- /dev/null +++ b/x/distribution/keeper/keeper_test.go @@ -0,0 +1,37 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/stretchr/testify/require" +) + +func TestSetGetPreviousProposerConsAddr(t *testing.T) { + ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0) + + keeper.SetPreviousProposerConsAddr(ctx, valConsAddr1) + res := keeper.GetPreviousProposerConsAddr(ctx) + require.True(t, res.Equals(valConsAddr1), "expected: %v got: %v", valConsAddr1.String(), res.String()) +} + +func TestSetGetCommunityTax(t *testing.T) { + ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0) + + someDec := sdk.NewDec(333) + keeper.SetCommunityTax(ctx, someDec) + res := keeper.GetCommunityTax(ctx) + require.True(sdk.DecEq(t, someDec, res)) +} + +func TestSetGetFeePool(t *testing.T) { + ctx, _, keeper, _, _ := CreateTestInputDefault(t, false, 0) + + fp := types.InitialFeePool() + fp.ValAccum.UpdateHeight = 777 + + keeper.SetFeePool(ctx, fp) + res := keeper.GetFeePool(ctx) + require.Equal(t, fp.ValAccum, res.ValAccum) +} diff --git a/x/distribution/keeper/key.go b/x/distribution/keeper/key.go new file mode 100644 index 000000000..2e5989081 --- /dev/null +++ b/x/distribution/keeper/key.go @@ -0,0 +1,46 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +// keys/key-prefixes +var ( + FeePoolKey = []byte{0x00} // key for global distribution state + ValidatorDistInfoKey = []byte{0x01} // prefix for each key to a validator distribution + DelegationDistInfoKey = []byte{0x02} // prefix for each key to a delegation distribution + DelegatorWithdrawInfoKey = []byte{0x03} // prefix for each key to a delegator withdraw info + ProposerKey = []byte{0x04} // key for storing the proposer operator address + + // params store + ParamStoreKeyCommunityTax = []byte("community-tax") + ParamStoreKeyBaseProposerReward = []byte("base-proposer-reward") + ParamStoreKeyBonusProposerReward = []byte("bonus-proposer-reward") +) + +const ( + // default paramspace for params keeper + DefaultParamspace = "distr" +) + +// gets the key for the validator distribution info from address +// VALUE: distribution/types.ValidatorDistInfo +func GetValidatorDistInfoKey(operatorAddr sdk.ValAddress) []byte { + return append(ValidatorDistInfoKey, operatorAddr.Bytes()...) +} + +// gets the key for delegator distribution for a validator +// VALUE: distribution/types.DelegationDistInfo +func GetDelegationDistInfoKey(delAddr sdk.AccAddress, valAddr sdk.ValAddress) []byte { + return append(GetDelegationDistInfosKey(delAddr), valAddr.Bytes()...) +} + +// gets the prefix for a delegator's distributions across all validators +func GetDelegationDistInfosKey(delAddr sdk.AccAddress) []byte { + return append(DelegationDistInfoKey, delAddr.Bytes()...) +} + +// gets the prefix for a delegator's withdraw info +func GetDelegatorWithdrawAddrKey(delAddr sdk.AccAddress) []byte { + return append(DelegatorWithdrawInfoKey, delAddr.Bytes()...) +} diff --git a/x/distribution/keeper/test_common.go b/x/distribution/keeper/test_common.go new file mode 100644 index 000000000..59b615ec8 --- /dev/null +++ b/x/distribution/keeper/test_common.go @@ -0,0 +1,161 @@ +package keeper + +import ( + "testing" + + "github.com/stretchr/testify/require" + + abci "github.com/tendermint/tendermint/abci/types" + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + dbm "github.com/tendermint/tendermint/libs/db" + "github.com/tendermint/tendermint/libs/log" + + "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/bank" + "github.com/cosmos/cosmos-sdk/x/params" + "github.com/cosmos/cosmos-sdk/x/stake" + + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +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()) + + addrs = []sdk.AccAddress{ + delAddr1, delAddr2, delAddr3, + valAccAddr1, valAccAddr2, valAccAddr3, + } + + emptyDelAddr sdk.AccAddress + emptyValAddr sdk.ValAddress + emptyPubkey crypto.PubKey +) + +// create a codec used only for testing +func MakeTestCodec() *codec.Codec { + var cdc = codec.New() + bank.RegisterCodec(cdc) + stake.RegisterCodec(cdc) + auth.RegisterCodec(cdc) + sdk.RegisterCodec(cdc) + codec.RegisterCrypto(cdc) + + types.RegisterCodec(cdc) // distr + return cdc +} + +// test input with default values +func CreateTestInputDefault(t *testing.T, isCheckTx bool, initCoins int64) ( + sdk.Context, auth.AccountMapper, Keeper, stake.Keeper, DummyFeeCollectionKeeper) { + + communityTax := sdk.NewDecWithPrec(2, 2) + return CreateTestInputAdvanced(t, isCheckTx, initCoins, communityTax) +} + +// hogpodge of all sorts of input required for testing +func CreateTestInputAdvanced(t *testing.T, isCheckTx bool, initCoins int64, + communityTax sdk.Dec) ( + sdk.Context, auth.AccountMapper, Keeper, stake.Keeper, DummyFeeCollectionKeeper) { + + keyDistr := sdk.NewKVStoreKey("distr") + keyStake := sdk.NewKVStoreKey("stake") + tkeyStake := sdk.NewTransientStoreKey("transient_stake") + keyAcc := sdk.NewKVStoreKey("acc") + keyFeeCollection := sdk.NewKVStoreKey("fee") + keyParams := sdk.NewKVStoreKey("params") + tkeyParams := sdk.NewTransientStoreKey("transient_params") + + db := dbm.NewMemDB() + ms := store.NewCommitMultiStore(db) + + ms.MountStoreWithDB(keyDistr, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyStake, sdk.StoreTypeTransient, nil) + ms.MountStoreWithDB(keyStake, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyAcc, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyFeeCollection, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(keyParams, sdk.StoreTypeIAVL, db) + ms.MountStoreWithDB(tkeyParams, sdk.StoreTypeTransient, db) + + err := ms.LoadLatestVersion() + require.Nil(t, err) + + cdc := MakeTestCodec() + pk := params.NewKeeper(cdc, keyParams, tkeyParams) + + ctx := sdk.NewContext(ms, abci.Header{ChainID: "foochainid"}, isCheckTx, log.NewNopLogger()) + accountMapper := auth.NewAccountMapper(cdc, keyAcc, auth.ProtoBaseAccount) + ck := bank.NewBaseKeeper(accountMapper) + sk := stake.NewKeeper(cdc, keyStake, tkeyStake, ck, pk.Subspace(stake.DefaultParamspace), stake.DefaultCodespace) + sk.SetPool(ctx, stake.InitialPool()) + sk.SetParams(ctx, stake.DefaultParams()) + sk.InitIntraTxCounter(ctx) + + // fill all the addresses with some coins, set the loose pool tokens simultaneously + for _, addr := range addrs { + pool := sk.GetPool(ctx) + _, _, err := ck.AddCoins(ctx, addr, sdk.Coins{ + {sk.GetParams(ctx).BondDenom, sdk.NewInt(initCoins)}, + }) + require.Nil(t, err) + pool.LooseTokens = pool.LooseTokens.Add(sdk.NewDec(initCoins)) + sk.SetPool(ctx, pool) + } + + fck := DummyFeeCollectionKeeper{} + keeper := NewKeeper(cdc, keyDistr, pk.Subspace(DefaultParamspace), ck, sk, fck, types.DefaultCodespace) + + // set the distribution hooks on staking + sk = sk.WithHooks(keeper.Hooks()) + + // set genesis items required for distribution + keeper.SetFeePool(ctx, types.InitialFeePool()) + keeper.SetCommunityTax(ctx, communityTax) + keeper.SetBaseProposerReward(ctx, sdk.NewDecWithPrec(1, 2)) + keeper.SetBonusProposerReward(ctx, sdk.NewDecWithPrec(4, 2)) + + return ctx, accountMapper, keeper, sk, fck +} + +//__________________________________________________________________________________ +// fee collection keeper used only for testing +type DummyFeeCollectionKeeper struct{} + +var heldFees sdk.Coins +var _ types.FeeCollectionKeeper = DummyFeeCollectionKeeper{} + +// nolint +func (fck DummyFeeCollectionKeeper) GetCollectedFees(_ sdk.Context) sdk.Coins { + return heldFees +} +func (fck DummyFeeCollectionKeeper) SetCollectedFees(in sdk.Coins) { + heldFees = in +} +func (fck DummyFeeCollectionKeeper) ClearCollectedFees(_ sdk.Context) { + heldFees = sdk.Coins{} +} diff --git a/x/distribution/keeper/validator.go b/x/distribution/keeper/validator.go new file mode 100644 index 000000000..ae07d8b3f --- /dev/null +++ b/x/distribution/keeper/validator.go @@ -0,0 +1,60 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/distribution/types" +) + +// get the validator distribution info +func (k Keeper) GetValidatorDistInfo(ctx sdk.Context, + operatorAddr sdk.ValAddress) (vdi types.ValidatorDistInfo) { + + store := ctx.KVStore(k.storeKey) + + b := store.Get(GetValidatorDistInfoKey(operatorAddr)) + if b == nil { + panic("Stored validator-distribution info should not have been nil") + } + + k.cdc.MustUnmarshalBinary(b, &vdi) + return +} + +// set the validator distribution info +func (k Keeper) SetValidatorDistInfo(ctx sdk.Context, vdi types.ValidatorDistInfo) { + store := ctx.KVStore(k.storeKey) + b := k.cdc.MustMarshalBinary(vdi) + store.Set(GetValidatorDistInfoKey(vdi.OperatorAddr), b) +} + +// remove a validator distribution info +func (k Keeper) RemoveValidatorDistInfo(ctx sdk.Context, valAddr sdk.ValAddress) { + store := ctx.KVStore(k.storeKey) + store.Delete(GetValidatorDistInfoKey(valAddr)) +} + +// withdrawal all the validator rewards including the commission +func (k Keeper) WithdrawValidatorRewardsAll(ctx sdk.Context, operatorAddr sdk.ValAddress) { + + // withdraw self-delegation + height := ctx.BlockHeight() + validator := k.stakeKeeper.Validator(ctx, operatorAddr) + accAddr := sdk.AccAddress(operatorAddr.Bytes()) + withdraw := k.getDelegatorRewardsAll(ctx, accAddr, height) + + // withdrawal validator commission rewards + bondedTokens := k.stakeKeeper.TotalPower(ctx) + valInfo := k.GetValidatorDistInfo(ctx, operatorAddr) + feePool := k.GetFeePool(ctx) + valInfo, feePool, commission := valInfo.WithdrawCommission(feePool, height, bondedTokens, + validator.GetTokens(), validator.GetCommission()) + withdraw = withdraw.Plus(commission) + k.SetValidatorDistInfo(ctx, valInfo) + k.SetFeePool(ctx, feePool) + + withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, accAddr) + _, _, err := k.bankKeeper.AddCoins(ctx, withdrawAddr, withdraw.TruncateDecimal()) + if err != nil { + panic(err) + } +} diff --git a/x/distribution/keeper/validator_test.go b/x/distribution/keeper/validator_test.go new file mode 100644 index 000000000..57c9a8b81 --- /dev/null +++ b/x/distribution/keeper/validator_test.go @@ -0,0 +1,192 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/stake" + "github.com/stretchr/testify/require" +) + +func TestWithdrawValidatorRewardsAllNoDelegator(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, 10) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw self-delegation reward + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) + amt := accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) + expRes := sdk.NewDec(90).Add(sdk.NewDec(100)).TruncateInt() + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawValidatorRewardsAllDelegatorNoCommission(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + msgCreateValidator := stake.NewTestMsgCreateValidator(valOpAddr1, valConsPk1, 10) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, int64(90), amt.Int64()) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw self-delegation reward + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) + amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) + expRes := sdk.NewDec(90).Add(sdk.NewDec(100).Quo(sdk.NewDec(2))).TruncateInt() // 90 + 100 tokens * 10/20 + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawValidatorRewardsAllDelegatorWithCommission(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator + commissionRate := sdk.NewDecWithPrec(1, 1) + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, 10, commissionRate) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, int64(90), amt.Int64()) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw validator reward + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) + amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) + commissionTaken := sdk.NewDec(100).Mul(commissionRate) + afterCommission := sdk.NewDec(100).Sub(commissionTaken) + selfDelegationReward := afterCommission.Quo(sdk.NewDec(2)) + expRes := sdk.NewDec(90).Add(commissionTaken).Add(selfDelegationReward).TruncateInt() // 90 + 100 tokens * 10/20 + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawValidatorRewardsAllMultipleValidator(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //make some validators with different commissions + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr2, valConsPk2, 50, sdk.NewDecWithPrec(2, 1)) + got = stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + msgCreateValidator = stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr3, valConsPk3, 40, sdk.NewDecWithPrec(3, 1)) + got = stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(1000) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw validator reward + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) + amt := accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) + + feesInNonProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(95, 2)) + feesInProposer := sdk.NewDecFromInt(feeInputs).Mul(sdk.NewDecWithPrec(5, 2)) + expRes := sdk.NewDec(90). // orig tokens (100 - 10) + Add(feesInNonProposer.Quo(sdk.NewDec(10))). // validator 1 has 1/10 total power + Add(feesInProposer). + TruncateInt() + require.True(sdk.IntEq(t, expRes, amt)) +} + +func TestWithdrawValidatorRewardsAllMultipleDelegator(t *testing.T) { + ctx, accMapper, keeper, sk, fck := CreateTestInputAdvanced(t, false, 100, sdk.ZeroDec()) + stakeHandler := stake.NewHandler(sk) + denom := sk.GetParams(ctx).BondDenom + + //first make a validator with 10% commission + commissionRate := sdk.NewDecWithPrec(1, 1) + msgCreateValidator := stake.NewTestMsgCreateValidatorWithCommission( + valOpAddr1, valConsPk1, 10, sdk.NewDecWithPrec(1, 1)) + got := stakeHandler(ctx, msgCreateValidator) + require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) + _ = sk.ApplyAndReturnValidatorSetUpdates(ctx) + + // delegate + msgDelegate := stake.NewTestMsgDelegate(delAddr1, valOpAddr1, 10) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt := accMapper.GetAccount(ctx, delAddr1).GetCoins().AmountOf(denom) + require.Equal(t, int64(90), amt.Int64()) + + msgDelegate = stake.NewTestMsgDelegate(delAddr2, valOpAddr1, 20) + got = stakeHandler(ctx, msgDelegate) + require.True(t, got.IsOK()) + amt = accMapper.GetAccount(ctx, delAddr2).GetCoins().AmountOf(denom) + require.Equal(t, int64(80), amt.Int64()) + + // allocate 100 denom of fees + feeInputs := sdk.NewInt(100) + fck.SetCollectedFees(sdk.Coins{sdk.NewCoin(denom, feeInputs)}) + require.Equal(t, feeInputs, fck.GetCollectedFees(ctx).AmountOf(denom)) + keeper.AllocateFees(ctx, sdk.OneDec(), valConsAddr1) + + // withdraw validator reward + ctx = ctx.WithBlockHeight(1) + keeper.WithdrawValidatorRewardsAll(ctx, valOpAddr1) + amt = accMapper.GetAccount(ctx, valAccAddr1).GetCoins().AmountOf(denom) + + commissionTaken := sdk.NewDec(100).Mul(commissionRate) + afterCommission := sdk.NewDec(100).Sub(commissionTaken) + expRes := sdk.NewDec(90). + Add(afterCommission.Quo(sdk.NewDec(4))). + Add(commissionTaken). + TruncateInt() // 90 + 100*90% tokens * 10/40 + require.True(sdk.IntEq(t, expRes, amt)) +} diff --git a/x/distribution/tags/tags.go b/x/distribution/tags/tags.go new file mode 100644 index 000000000..dd55ba236 --- /dev/null +++ b/x/distribution/tags/tags.go @@ -0,0 +1,17 @@ +// nolint +package tags + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +var ( + ActionModifyWithdrawAddress = []byte("modify-withdraw-address") + ActionWithdrawDelegatorRewardsAll = []byte("withdraw-delegator-rewards-all") + ActionWithdrawDelegatorReward = []byte("withdraw-delegator-reward") + ActionWithdrawValidatorRewardsAll = []byte("withdraw-validator-rewards-all") + + Action = sdk.TagAction + Validator = sdk.TagSrcValidator + Delegator = sdk.TagDelegator +) diff --git a/x/distribution/types/delegator_info.go b/x/distribution/types/delegator_info.go index 4de296890..f5b8978e5 100644 --- a/x/distribution/types/delegator_info.go +++ b/x/distribution/types/delegator_info.go @@ -5,16 +5,16 @@ import ( ) // distribution info for a delegation - used to determine entitled rewards -type DelegatorDistInfo struct { +type DelegationDistInfo struct { DelegatorAddr sdk.AccAddress `json:"delegator_addr"` ValOperatorAddr sdk.ValAddress `json:"val_operator_addr"` WithdrawalHeight int64 `json:"withdrawal_height"` // last time this delegation withdrew rewards } -func NewDelegatorDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.ValAddress, - currentHeight int64) DelegatorDistInfo { +func NewDelegationDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.ValAddress, + currentHeight int64) DelegationDistInfo { - return DelegatorDistInfo{ + return DelegationDistInfo{ DelegatorAddr: delegatorAddr, ValOperatorAddr: valOperatorAddr, WithdrawalHeight: currentHeight, @@ -22,9 +22,9 @@ func NewDelegatorDistInfo(delegatorAddr sdk.AccAddress, valOperatorAddr sdk.ValA } // withdraw rewards from delegator -func (di DelegatorDistInfo) WithdrawRewards(fp FeePool, vi ValidatorDistInfo, +func (di DelegationDistInfo) WithdrawRewards(fp FeePool, vi ValidatorDistInfo, height int64, totalBonded, vdTokens, totalDelShares, delegatorShares, - commissionRate sdk.Dec) (DelegatorDistInfo, ValidatorDistInfo, FeePool, DecCoins) { + commissionRate sdk.Dec) (DelegationDistInfo, ValidatorDistInfo, FeePool, DecCoins) { vi = vi.UpdateTotalDelAccum(height, totalDelShares) diff --git a/x/distribution/types/delegator_info_test.go b/x/distribution/types/delegator_info_test.go index 2d9e9bc0f..516cbf99d 100644 --- a/x/distribution/types/delegator_info_test.go +++ b/x/distribution/types/delegator_info_test.go @@ -18,10 +18,10 @@ func TestWithdrawRewards(t *testing.T) { validatorDelShares := sdk.NewDec(10) totalBondedTokens := validatorTokens.Add(sdk.NewDec(90)) // validator-1 is 10% of total power - di1 := NewDelegatorDistInfo(delAddr1, valAddr1, height) + di1 := NewDelegationDistInfo(delAddr1, valAddr1, height) di1Shares := sdk.NewDec(5) // this delegator has half the shares in the validator - di2 := NewDelegatorDistInfo(delAddr2, valAddr1, height) + di2 := NewDelegationDistInfo(delAddr2, valAddr1, height) di2Shares := sdk.NewDec(5) // simulate adding some stake for inflation diff --git a/x/distribution/types/genesis.go b/x/distribution/types/genesis.go index f01d1b781..528295e5c 100644 --- a/x/distribution/types/genesis.go +++ b/x/distribution/types/genesis.go @@ -12,22 +12,56 @@ type DelegatorWithdrawInfo struct { // GenesisState - all distribution state that must be provided at genesis type GenesisState struct { FeePool FeePool `json:"fee_pool"` + CommunityTax sdk.Dec `json:"community_tax"` + BaseProposerReward sdk.Dec `json:"base_proposer_reward"` + BonusProposerReward sdk.Dec `json:"bonus_proposer_reward"` ValidatorDistInfos []ValidatorDistInfo `json:"validator_dist_infos"` - DelegatorDistInfos []DelegatorDistInfo `json:"delegator_dist_infos"` + DelegationDistInfos []DelegationDistInfo `json:"delegator_dist_infos"` DelegatorWithdrawInfos []DelegatorWithdrawInfo `json:"delegator_withdraw_infos"` } -func NewGenesisState(feePool FeePool, vdis []ValidatorDistInfo, ddis []DelegatorDistInfo) GenesisState { +func NewGenesisState(feePool FeePool, communityTax, baseProposerReward, bonusProposerReward sdk.Dec, + vdis []ValidatorDistInfo, ddis []DelegationDistInfo, dwis []DelegatorWithdrawInfo) GenesisState { + return GenesisState{ - FeePool: feePool, - ValidatorDistInfos: vdis, - DelegatorDistInfos: ddis, + FeePool: feePool, + CommunityTax: communityTax, + BaseProposerReward: baseProposerReward, + BonusProposerReward: bonusProposerReward, + ValidatorDistInfos: vdis, + DelegationDistInfos: ddis, + DelegatorWithdrawInfos: dwis, } } // get raw genesis raw message for testing func DefaultGenesisState() GenesisState { return GenesisState{ - FeePool: InitialFeePool(), + FeePool: InitialFeePool(), + CommunityTax: sdk.NewDecWithPrec(2, 2), // 2% + BaseProposerReward: sdk.NewDecWithPrec(1, 2), // 1% + BonusProposerReward: sdk.NewDecWithPrec(4, 2), // 4% + } +} + +// default genesis utility function, initialize for starting validator set +func DefaultGenesisWithValidators(valAddrs []sdk.ValAddress) GenesisState { + + vdis := make([]ValidatorDistInfo, len(valAddrs)) + ddis := make([]DelegationDistInfo, len(valAddrs)) + + for i, valAddr := range valAddrs { + vdis[i] = NewValidatorDistInfo(valAddr, 0) + accAddr := sdk.AccAddress(valAddr) + ddis[i] = NewDelegationDistInfo(accAddr, valAddr, 0) + } + + return GenesisState{ + FeePool: InitialFeePool(), + CommunityTax: sdk.NewDecWithPrec(2, 2), // 2% + BaseProposerReward: sdk.NewDecWithPrec(1, 2), // 1% + BonusProposerReward: sdk.NewDecWithPrec(4, 2), // 4% + ValidatorDistInfos: vdis, + DelegationDistInfos: ddis, } } diff --git a/x/distribution/types/test_utils.go b/x/distribution/types/test_common.go similarity index 82% rename from x/distribution/types/test_utils.go rename to x/distribution/types/test_common.go index 3ea78cd79..b77efd46c 100644 --- a/x/distribution/types/test_utils.go +++ b/x/distribution/types/test_common.go @@ -18,9 +18,9 @@ var ( valPk1 = ed25519.GenPrivKey().PubKey() valPk2 = ed25519.GenPrivKey().PubKey() valPk3 = ed25519.GenPrivKey().PubKey() - valAddr1 = sdk.ValAddress(delPk1.Address()) - valAddr2 = sdk.ValAddress(delPk2.Address()) - valAddr3 = sdk.ValAddress(delPk3.Address()) + valAddr1 = sdk.ValAddress(valPk1.Address()) + valAddr2 = sdk.ValAddress(valPk2.Address()) + valAddr3 = sdk.ValAddress(valPk3.Address()) emptyValAddr sdk.ValAddress emptyPubkey crypto.PubKey diff --git a/x/mock/simulation/random_simulate_blocks.go b/x/mock/simulation/random_simulate_blocks.go index 0fc9c21a9..0af6fbbac 100644 --- a/x/mock/simulation/random_simulate_blocks.go +++ b/x/mock/simulation/random_simulate_blocks.go @@ -15,6 +15,7 @@ import ( "time" abci "github.com/tendermint/tendermint/abci/types" + common "github.com/tendermint/tendermint/libs/common" tmtypes "github.com/tendermint/tendermint/types" "github.com/cosmos/cosmos-sdk/baseapp" @@ -81,7 +82,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, // Initially this is the same as the initial validator set nextValidators := validators - header := abci.Header{Height: 0, Time: timestamp} + header := abci.Header{Height: 0, Time: timestamp, ProposerAddress: randomProposer(r, validators)} opCount := 0 // Setup code to catch SIGTERM's @@ -150,6 +151,7 @@ func SimulateFromSeed(tb testing.TB, app *baseapp.BaseApp, res := app.EndBlock(abci.RequestEndBlock{}) header.Height++ header.Time = header.Time.Add(time.Duration(minTimePerBlock) * time.Second).Add(time.Duration(int64(r.Intn(int(timeDiff)))) * time.Second) + header.ProposerAddress = randomProposer(r, validators) logWriter("EndBlock") if testingMode { @@ -318,6 +320,21 @@ func getKeys(validators map[string]mockValidator) []string { return keys } +// randomProposer picks a random proposer from the current validator set +func randomProposer(r *rand.Rand, validators map[string]mockValidator) common.HexBytes { + keys := getKeys(validators) + if len(keys) == 0 { + return nil + } + key := keys[r.Intn(len(keys))] + proposer := validators[key].val + pk, err := tmtypes.PB2TM.PubKey(proposer.PubKey) + if err != nil { + panic(err) + } + return pk.Address() +} + // RandomRequestBeginBlock generates a list of signing validators according to the provided list of validators, signing fraction, and evidence fraction // nolint: unparam func RandomRequestBeginBlock(r *rand.Rand, validators map[string]mockValidator, livenessTransitions TransitionMatrix, evidenceFraction float64, diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index 20395b2e9..394a8e0b3 100644 --- a/x/slashing/handler_test.go +++ b/x/slashing/handler_test.go @@ -16,7 +16,7 @@ func TestCannotUnjailUnlessJailed(t *testing.T) { slh := NewHandler(keeper) amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) - msg := newTestMsgCreateValidator(addr, val, amt) + msg := NewTestMsgCreateValidator(addr, val, amt) got := stake.NewHandler(sk)(ctx, msg) require.True(t, got.IsOK()) stake.EndBlocker(ctx, sk) @@ -41,7 +41,7 @@ func TestJailedValidatorDelegations(t *testing.T) { valPubKey, bondAmount := pks[0], sdk.NewInt(amount) valAddr, consAddr := addrs[1], sdk.ConsAddress(addrs[0]) - msgCreateVal := newTestMsgCreateValidator(valAddr, valPubKey, bondAmount) + msgCreateVal := NewTestMsgCreateValidator(valAddr, valPubKey, bondAmount) got := stake.NewHandler(stakeKeeper)(ctx, msgCreateVal) require.True(t, got.IsOK(), "expected create validator msg to be ok, got: %v", got) diff --git a/x/slashing/keeper_test.go b/x/slashing/keeper_test.go index 627eff6f2..a0980d04d 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -33,7 +33,7 @@ func TestHandleDoubleSign(t *testing.T) { sk = sk.WithHooks(keeper.Hooks()) amtInt := int64(100) operatorAddr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) - got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(operatorAddr, val, amt)) + got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, val, amt)) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) @@ -75,7 +75,7 @@ func TestSlashingPeriodCap(t *testing.T) { amtInt := int64(100) operatorAddr, amt := addrs[0], sdk.NewInt(amtInt) valConsPubKey, valConsAddr := pks[0], pks[0].Address() - got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(operatorAddr, valConsPubKey, amt)) + got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(operatorAddr, valConsPubKey, amt)) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) ctx = ctx.WithBlockHeight(ctx.BlockHeight() + 1) @@ -142,7 +142,7 @@ func TestHandleAbsentValidator(t *testing.T) { addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) sh := stake.NewHandler(sk) slh := NewHandler(keeper) - got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) @@ -294,7 +294,7 @@ func TestHandleNewValidator(t *testing.T) { ctx, ck, sk, _, keeper := createTestInput(t, keeperTestParams()) addr, val, amt := addrs[0], pks[0], int64(100) sh := stake.NewHandler(sk) - got := sh(ctx, newTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, sdk.NewInt(amt))) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) @@ -332,7 +332,7 @@ func TestHandleAlreadyJailed(t *testing.T) { amtInt := int64(100) addr, val, amt := addrs[0], pks[0], sdk.NewInt(amtInt) sh := stake.NewHandler(sk) - got := sh(ctx, newTestMsgCreateValidator(addr, val, amt)) + got := sh(ctx, NewTestMsgCreateValidator(addr, val, amt)) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) diff --git a/x/slashing/test_common.go b/x/slashing/test_common.go index af2a3f7d8..54eb906eb 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -111,7 +111,7 @@ func testAddr(addr string) sdk.AccAddress { return res } -func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator { +func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt sdk.Int) stake.MsgCreateValidator { commission := stake.NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) return stake.MsgCreateValidator{ Description: stake.Description{}, diff --git a/x/slashing/tick.go b/x/slashing/tick.go index 007d93788..03bd094af 100644 --- a/x/slashing/tick.go +++ b/x/slashing/tick.go @@ -18,7 +18,7 @@ func BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock, sk Keeper) (tags tags = sdk.NewTags("height", heightBytes) // Iterate over all the validators which *should* have signed this block - // Store whether or not they have actually signed it and slash/unbond any + // store whether or not they have actually signed it and slash/unbond any // which have missed too many blocks in a row (downtime slashing) for _, voteInfo := range req.LastCommitInfo.GetVotes() { sk.handleValidatorSignature(ctx, voteInfo.Validator.Address, voteInfo.Validator.Power, voteInfo.SignedLastBlock) diff --git a/x/slashing/tick_test.go b/x/slashing/tick_test.go index 085ce9eba..c7617f4d7 100644 --- a/x/slashing/tick_test.go +++ b/x/slashing/tick_test.go @@ -17,7 +17,7 @@ func TestBeginBlocker(t *testing.T) { addr, pk, amt := addrs[2], pks[2], sdk.NewInt(100) // bond the validator - got := stake.NewHandler(sk)(ctx, newTestMsgCreateValidator(addr, pk, amt)) + got := stake.NewHandler(sk)(ctx, NewTestMsgCreateValidator(addr, pk, amt)) require.True(t, got.IsOK()) validatorUpdates := stake.EndBlocker(ctx, sk) keeper.AddValidators(ctx, validatorUpdates) diff --git a/x/stake/app_test.go b/x/stake/app_test.go index ac0bd8fcc..4ab210f70 100644 --- a/x/stake/app_test.go +++ b/x/stake/app_test.go @@ -10,24 +10,6 @@ import ( "github.com/cosmos/cosmos-sdk/x/params" "github.com/stretchr/testify/require" abci "github.com/tendermint/tendermint/abci/types" - "github.com/tendermint/tendermint/crypto/ed25519" -) - -var ( - priv1 = ed25519.GenPrivKey() - addr1 = sdk.AccAddress(priv1.PubKey().Address()) - priv2 = ed25519.GenPrivKey() - addr2 = sdk.AccAddress(priv2.PubKey().Address()) - addr3 = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) - priv4 = ed25519.GenPrivKey() - addr4 = sdk.AccAddress(priv4.PubKey().Address()) - coins = sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(10))} - fee = auth.NewStdFee( - 100000, - sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(0))}..., - ) - - commissionMsg = NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) ) // getMockApp returns an initialized mock application for this module. diff --git a/x/stake/genesis.go b/x/stake/genesis.go index 3cd104e66..6e1e055f2 100644 --- a/x/stake/genesis.go +++ b/x/stake/genesis.go @@ -42,10 +42,13 @@ func InitGenesis(ctx sdk.Context, keeper Keeper, data types.GenesisState) (res [ // Manually set indices for the first time keeper.SetValidatorByConsAddr(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator, data.Pool) + + keeper.OnValidatorCreated(ctx, validator.OperatorAddr) } - for _, bond := range data.Bonds { - keeper.SetDelegation(ctx, bond) + for _, delegation := range data.Bonds { + keeper.SetDelegation(ctx, delegation) + keeper.OnDelegationCreated(ctx, delegation.DelegatorAddr, delegation.ValidatorAddr) } res = keeper.ApplyAndReturnValidatorSetUpdates(ctx) diff --git a/x/stake/handler.go b/x/stake/handler.go index 57430d58f..aff38ac6e 100644 --- a/x/stake/handler.go +++ b/x/stake/handler.go @@ -106,7 +106,7 @@ func handleMsgCreateValidator(ctx sdk.Context, msg types.MsgCreateValidator, k k validator := NewValidator(msg.ValidatorAddr, msg.PubKey, msg.Description) commission := NewCommissionWithTime( - msg.Commission.Rate, msg.Commission.MaxChangeRate, + msg.Commission.Rate, msg.Commission.MaxRate, msg.Commission.MaxChangeRate, ctx.BlockHeader().Time, ) validator, err := validator.SetInitialCommission(commission) diff --git a/x/stake/handler_test.go b/x/stake/handler_test.go index f3d6ae5eb..083bb9a91 100644 --- a/x/stake/handler_test.go +++ b/x/stake/handler_test.go @@ -7,8 +7,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/tendermint/tendermint/crypto" - sdk "github.com/cosmos/cosmos-sdk/types" keep "github.com/cosmos/cosmos-sdk/x/stake/keeper" "github.com/cosmos/cosmos-sdk/x/stake/types" @@ -16,31 +14,6 @@ import ( //______________________________________________________________________ -func newTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt int64) MsgCreateValidator { - return types.NewMsgCreateValidator( - address, pubKey, sdk.NewCoin("steak", sdk.NewInt(amt)), Description{}, commissionMsg, - ) -} - -func newTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt int64) MsgDelegate { - return MsgDelegate{ - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - Delegation: sdk.NewCoin("steak", sdk.NewInt(amt)), - } -} - -func newTestMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, valPubKey crypto.PubKey, amt int64) MsgCreateValidator { - return MsgCreateValidator{ - Description: Description{}, - Commission: commissionMsg, - DelegatorAddr: delAddr, - ValidatorAddr: valAddr, - PubKey: valPubKey, - Delegation: sdk.NewCoin("steak", sdk.NewInt(amt)), - } -} - // retrieve params which are instant func setInstantUnbondPeriod(keeper keep.Keeper, ctx sdk.Context) types.Params { params := keeper.GetParams(ctx) @@ -59,7 +32,7 @@ func TestValidatorByPowerIndex(t *testing.T) { _ = setInstantUnbondPeriod(keeper, ctx) // create validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) @@ -83,7 +56,7 @@ func TestValidatorByPowerIndex(t *testing.T) { require.True(t, keep.ValidatorByPowerIndexExists(ctx, keeper, power)) // create a second validator keep it bonded - msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], int64(1000000)) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr3, keep.PKs[2], int64(1000000)) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) @@ -146,7 +119,7 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { addr1, addr2 := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]) pk1, pk2 := keep.PKs[0], keep.PKs[1] - msgCreateValidator1 := newTestMsgCreateValidator(addr1, pk1, 10) + msgCreateValidator1 := NewTestMsgCreateValidator(addr1, pk1, 10) got := handleMsgCreateValidator(ctx, msgCreateValidator1, keeper) require.True(t, got.IsOK(), "%v", got) @@ -162,17 +135,17 @@ func TestDuplicatesMsgCreateValidator(t *testing.T) { assert.Equal(t, Description{}, validator.Description) // two validators can't have the same operator address - msgCreateValidator2 := newTestMsgCreateValidator(addr1, pk2, 10) + msgCreateValidator2 := NewTestMsgCreateValidator(addr1, pk2, 10) got = handleMsgCreateValidator(ctx, msgCreateValidator2, keeper) require.False(t, got.IsOK(), "%v", got) // two validators can't have the same pubkey - msgCreateValidator3 := newTestMsgCreateValidator(addr2, pk1, 10) + msgCreateValidator3 := NewTestMsgCreateValidator(addr2, pk1, 10) got = handleMsgCreateValidator(ctx, msgCreateValidator3, keeper) require.False(t, got.IsOK(), "%v", got) // must have different pubkey and operator - msgCreateValidator4 := newTestMsgCreateValidator(addr2, pk2, 10) + msgCreateValidator4 := NewTestMsgCreateValidator(addr2, pk2, 10) got = handleMsgCreateValidator(ctx, msgCreateValidator4, keeper) require.True(t, got.IsOK(), "%v", got) @@ -197,7 +170,7 @@ func TestDuplicatesMsgCreateValidatorOnBehalfOf(t *testing.T) { validatorAddr := sdk.ValAddress(keep.Addrs[0]) delegatorAddr := keep.Addrs[1] pk := keep.PKs[0] - msgCreateValidatorOnBehalfOf := newTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, 10) + msgCreateValidatorOnBehalfOf := NewTestMsgCreateValidatorOnBehalfOf(delegatorAddr, validatorAddr, pk, 10) got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) require.True(t, got.IsOK(), "%v", got) @@ -232,7 +205,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { delAddr := keep.Addrs[1] // create validator - msgCreateVal := newTestMsgCreateValidator(valAddr, valConsPubKey, bondAmount) + msgCreateVal := NewTestMsgCreateValidator(valAddr, valConsPubKey, bondAmount) got := handleMsgCreateValidator(ctx, msgCreateVal, keeper) require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) @@ -248,7 +221,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { require.Equal(t, bondAmount, validator.BondedTokens().RoundInt64()) // delegate tokens to the validator - msgDelegate := newTestMsgDelegate(delAddr, valAddr, bondAmount) + msgDelegate := NewTestMsgDelegate(delAddr, valAddr, bondAmount) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) @@ -283,12 +256,12 @@ func TestLegacyValidatorDelegations(t *testing.T) { require.Equal(t, bondAmount, validator.DelegatorShares.RoundInt64()) // verify a delegator cannot create a new delegation to the now jailed validator - msgDelegate = newTestMsgDelegate(delAddr, valAddr, bondAmount) + msgDelegate = NewTestMsgDelegate(delAddr, valAddr, bondAmount) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.False(t, got.IsOK(), "expected delegation to not be ok, got %v", got) // verify the validator can still self-delegate - msgSelfDelegate := newTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) + msgSelfDelegate := NewTestMsgDelegate(sdk.AccAddress(valAddr), valAddr, bondAmount) got = handleMsgDelegate(ctx, msgSelfDelegate, keeper) require.True(t, got.IsOK(), "expected delegation to not be ok, got %v", got) @@ -302,7 +275,7 @@ func TestLegacyValidatorDelegations(t *testing.T) { keeper.Unjail(ctx, valConsAddr) // verify the validator can now accept delegations - msgDelegate = newTestMsgDelegate(delAddr, valAddr, bondAmount) + msgDelegate = NewTestMsgDelegate(delAddr, valAddr, bondAmount) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) @@ -328,7 +301,7 @@ func TestIncrementsMsgDelegate(t *testing.T) { validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] // first create validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], bondAmount) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], bondAmount) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create validator msg to be ok, got %v", got) @@ -354,7 +327,7 @@ func TestIncrementsMsgDelegate(t *testing.T) { require.Equal(t, bondAmount, pool.BondedTokens.RoundInt64()) // just send the same msgbond multiple times - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, bondAmount) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, bondAmount) for i := 0; i < 5; i++ { ctx = ctx.WithBlockHeight(int64(i)) @@ -402,14 +375,14 @@ func TestIncrementsMsgUnbond(t *testing.T) { // create validator, delegate validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], initBond) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected create-validator to be ok, got %v", got) // initial balance amt1 := accMapper.GetAccount(ctx, delegatorAddr).GetCoins().AmountOf(denom) - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, initBond) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, initBond) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected delegation to be ok, got %v", got) @@ -505,7 +478,7 @@ func TestMultipleMsgCreateValidator(t *testing.T) { // bond them all for i, validatorAddr := range validatorAddrs { - msgCreateValidatorOnBehalfOf := newTestMsgCreateValidatorOnBehalfOf(delegatorAddrs[i], validatorAddr, keep.PKs[i], 10) + msgCreateValidatorOnBehalfOf := NewTestMsgCreateValidatorOnBehalfOf(delegatorAddrs[i], validatorAddr, keep.PKs[i], 10) got := handleMsgCreateValidator(ctx, msgCreateValidatorOnBehalfOf, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) @@ -552,13 +525,13 @@ func TestMultipleMsgDelegate(t *testing.T) { _ = setInstantUnbondPeriod(keeper, ctx) //first make a validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected msg to be ok, got %v", got) // delegate multiple parties for i, delegatorAddr := range delegatorAddrs { - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, 10) got := handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected msg %d to be ok, got %v", i, got) @@ -590,12 +563,12 @@ func TestJailValidator(t *testing.T) { _ = setInstantUnbondPeriod(keeper, ctx) // create the validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // bond a delegator - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) @@ -639,12 +612,12 @@ func TestValidatorQueue(t *testing.T) { keeper.SetParams(ctx, params) // create the validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // bond a delegator - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) @@ -689,7 +662,7 @@ func TestUnbondingPeriod(t *testing.T) { keeper.SetParams(ctx, params) // create the validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") @@ -727,12 +700,12 @@ func TestUnbondingFromUnbondingValidator(t *testing.T) { validatorAddr, delegatorAddr := sdk.ValAddress(keep.Addrs[0]), keep.Addrs[1] // create the validator - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // bond a delegator - msgDelegate := newTestMsgDelegate(delegatorAddr, validatorAddr, 10) + msgDelegate := NewTestMsgDelegate(delegatorAddr, validatorAddr, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected ok, got %v", got) @@ -774,7 +747,7 @@ func TestRedelegationPeriod(t *testing.T) { keeper.SetParams(ctx, params) // create the validators - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) // initial balance amt1 := AccMapper.GetAccount(ctx, sdk.AccAddress(validatorAddr)).GetCoins().AmountOf(denom) @@ -786,7 +759,7 @@ func TestRedelegationPeriod(t *testing.T) { amt2 := AccMapper.GetAccount(ctx, sdk.AccAddress(validatorAddr)).GetCoins().AmountOf(denom) require.Equal(t, amt1.Sub(sdk.NewInt(10)).Int64(), amt2.Int64(), "expected coins to be subtracted") - msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") @@ -833,15 +806,15 @@ func TestTransitiveRedelegation(t *testing.T) { keeper.SetParams(ctx, params) // create the validators - msgCreateValidator := newTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") @@ -918,21 +891,21 @@ func TestUnbondingWhenExcessValidators(t *testing.T) { keeper.SetParams(ctx, params) // add three validators - msgCreateValidator := newTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) + msgCreateValidator := NewTestMsgCreateValidator(validatorAddr1, keep.PKs[0], 50) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 1, len(keeper.GetValidatorsBonded(ctx))) - msgCreateValidator = newTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr2, keep.PKs[1], 30) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // apply TM updates keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 2, len(keeper.GetValidatorsBonded(ctx))) - msgCreateValidator = newTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) + msgCreateValidator = NewTestMsgCreateValidator(validatorAddr3, keep.PKs[2], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // apply TM updates @@ -962,16 +935,16 @@ func TestBondUnbondRedelegateSlashTwice(t *testing.T) { valA, valB, del := sdk.ValAddress(keep.Addrs[0]), sdk.ValAddress(keep.Addrs[1]), keep.Addrs[2] consAddr0 := sdk.ConsAddress(keep.PKs[0].Address()) - msgCreateValidator := newTestMsgCreateValidator(valA, keep.PKs[0], 10) + msgCreateValidator := NewTestMsgCreateValidator(valA, keep.PKs[0], 10) got := handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") - msgCreateValidator = newTestMsgCreateValidator(valB, keep.PKs[1], 10) + msgCreateValidator = NewTestMsgCreateValidator(valB, keep.PKs[1], 10) got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") // delegate 10 stake - msgDelegate := newTestMsgDelegate(del, valA, 10) + msgDelegate := NewTestMsgDelegate(del, valA, 10) got = handleMsgDelegate(ctx, msgDelegate, keeper) require.True(t, got.IsOK(), "expected no error on runMsgDelegate") diff --git a/x/stake/keeper/delegation.go b/x/stake/keeper/delegation.go index 52b9d8595..25ea3d08b 100644 --- a/x/stake/keeper/delegation.go +++ b/x/stake/keeper/delegation.go @@ -380,8 +380,6 @@ func (k Keeper) Delegate(ctx sdk.Context, delAddr sdk.AccAddress, bondAmt sdk.Co func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress, shares sdk.Dec) (amount sdk.Dec, err sdk.Error) { - k.OnDelegationSharesModified(ctx, delAddr, valAddr) - // check if delegation has any shares in it unbond delegation, found := k.GetDelegation(ctx, delAddr, valAddr) if !found { @@ -389,6 +387,8 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA return } + k.OnDelegationSharesModified(ctx, delAddr, valAddr) + // retrieve the amount to remove if delegation.Shares.LT(shares) { err = types.ErrNotEnoughDelegationShares(k.Codespace(), delegation.Shares.String()) @@ -430,7 +430,6 @@ func (k Keeper) unbond(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValA k.RemoveValidator(ctx, validator.OperatorAddr) } - k.OnDelegationSharesModified(ctx, delegation.DelegatorAddr, validator.OperatorAddr) return amount, nil } diff --git a/x/stake/keeper/delegation_test.go b/x/stake/keeper/delegation_test.go index 6c42feb41..46bde785a 100644 --- a/x/stake/keeper/delegation_test.go +++ b/x/stake/keeper/delegation_test.go @@ -25,9 +25,9 @@ func TestDelegation(t *testing.T) { } keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) - validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) // first add a validators[0] to delegate too @@ -184,7 +184,7 @@ func TestUnbondDelegation(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) require.Equal(t, int64(10), pool.BondedTokens.RoundInt64()) @@ -226,7 +226,7 @@ func TestUndelegateSelfDelegation(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) selfDelegation := types.Delegation{ DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()), @@ -240,7 +240,7 @@ func TestUndelegateSelfDelegation(t *testing.T) { validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -274,7 +274,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) selfDelegation := types.Delegation{ DelegatorAddr: sdk.AccAddress(addrVals[0].Bytes()), @@ -288,7 +288,7 @@ func TestUndelegateFromUnbondingValidator(t *testing.T) { validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -350,7 +350,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -365,7 +365,7 @@ func TestUndelegateFromUnbondedValidator(t *testing.T) { validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -512,7 +512,7 @@ func TestRedelegateSelfDelegation(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -528,14 +528,14 @@ func TestRedelegateSelfDelegation(t *testing.T) { require.Equal(t, int64(10), issuedShares.RoundInt64()) pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDec(10)) keeper.SetPool(ctx, pool) - validator2 = testingUpdateValidator(keeper, ctx, validator2) + validator2 = TestingUpdateValidator(keeper, ctx, validator2) require.Equal(t, sdk.Bonded, validator2.Status) // create a second delegation to this validator validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -569,7 +569,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -584,7 +584,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { validator, pool, issuedShares = validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -599,7 +599,7 @@ func TestRedelegateFromUnbondingValidator(t *testing.T) { validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator2 = testingUpdateValidator(keeper, ctx, validator2) + validator2 = TestingUpdateValidator(keeper, ctx, validator2) header := ctx.BlockHeader() blockHeight := int64(10) @@ -653,7 +653,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator, pool, issuedShares := validator.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) val0AccAddr := sdk.AccAddress(addrVals[0].Bytes()) selfDelegation := types.Delegation{ @@ -669,7 +669,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator.BondIntraTxCounter = 1 require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) pool = keeper.GetPool(ctx) delegation := types.Delegation{ DelegatorAddr: addrDels[0], @@ -684,7 +684,7 @@ func TestRedelegateFromUnbondedValidator(t *testing.T) { validator2, pool, issuedShares = validator2.AddTokensFromDel(pool, sdk.NewInt(10)) require.Equal(t, int64(10), issuedShares.RoundInt64()) keeper.SetPool(ctx, pool) - validator2 = testingUpdateValidator(keeper, ctx, validator2) + validator2 = TestingUpdateValidator(keeper, ctx, validator2) require.Equal(t, sdk.Bonded, validator2.Status) ctx = ctx.WithBlockHeight(10) diff --git a/x/stake/keeper/slash_test.go b/x/stake/keeper/slash_test.go index edc7ff16b..aab97b811 100644 --- a/x/stake/keeper/slash_test.go +++ b/x/stake/keeper/slash_test.go @@ -29,7 +29,7 @@ func setupHelper(t *testing.T, amt int64) (sdk.Context, Keeper, types.Params) { validator.BondIntraTxCounter = int16(i) pool.BondedTokens = pool.BondedTokens.Add(sdk.NewDec(amt)) keeper.SetPool(ctx, pool) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) keeper.SetValidatorByConsAddr(ctx, validator) } pool = keeper.GetPool(ctx) diff --git a/x/stake/keeper/test_common.go b/x/stake/keeper/test_common.go index 10bf2755d..aeeaf0c0d 100644 --- a/x/stake/keeper/test_common.go +++ b/x/stake/keeper/test_common.go @@ -211,7 +211,8 @@ func ValidatorByPowerIndexExists(ctx sdk.Context, keeper Keeper, power []byte) b return store.Has(power) } -func testingUpdateValidator(keeper Keeper, ctx sdk.Context, validator types.Validator) types.Validator { +// update validator for testing +func TestingUpdateValidator(keeper Keeper, ctx sdk.Context, validator types.Validator) types.Validator { pool := keeper.GetPool(ctx) keeper.SetValidator(ctx, validator) keeper.SetValidatorByPowerIndex(ctx, validator, pool) diff --git a/x/stake/keeper/validator_test.go b/x/stake/keeper/validator_test.go index 9b4dc3c55..6f14ba7a5 100644 --- a/x/stake/keeper/validator_test.go +++ b/x/stake/keeper/validator_test.go @@ -84,7 +84,7 @@ func TestUpdateValidatorByPowerIndex(t *testing.T) { require.Equal(t, sdk.Unbonded, validator.Status) require.Equal(t, int64(100), validator.Tokens.RoundInt64()) keeper.SetPool(ctx, pool) - testingUpdateValidator(keeper, ctx, validator) + TestingUpdateValidator(keeper, ctx, validator) validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool) @@ -98,7 +98,7 @@ func TestUpdateValidatorByPowerIndex(t *testing.T) { validator, pool, burned := validator.RemoveDelShares(pool, delSharesCreated.Quo(sdk.NewDec(2))) require.Equal(t, int64(50), burned.RoundInt64()) keeper.SetPool(ctx, pool) // update the pool - testingUpdateValidator(keeper, ctx, validator) // update the validator, possibly kicking it out + TestingUpdateValidator(keeper, ctx, validator) // update the validator, possibly kicking it out require.False(t, validatorByPowerIndexExists(keeper, ctx, power)) pool = keeper.GetPool(ctx) @@ -135,7 +135,7 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { val, pool, _ = val.AddTokensFromDel(pool, sdk.NewInt(int64((i+1)*10))) keeper.SetPool(ctx, pool) - val = testingUpdateValidator(keeper, ctx, val) + val = TestingUpdateValidator(keeper, ctx, val) validators[i] = val } @@ -146,7 +146,7 @@ func TestUpdateBondedValidatorsDecreaseCliff(t *testing.T) { keeper.DeleteValidatorByPowerIndex(ctx, nextCliffVal, pool) nextCliffVal, pool, _ = nextCliffVal.RemoveDelShares(pool, sdk.NewDec(21)) keeper.SetPool(ctx, pool) - nextCliffVal = testingUpdateValidator(keeper, ctx, nextCliffVal) + nextCliffVal = TestingUpdateValidator(keeper, ctx, nextCliffVal) expectedValStatus := map[int]sdk.BondStatus{ 9: sdk.Bonded, 8: sdk.Bonded, 7: sdk.Bonded, 5: sdk.Bonded, 4: sdk.Bonded, @@ -178,7 +178,7 @@ func TestSlashToZeroPowerRemoved(t *testing.T) { require.Equal(t, int64(100), validator.Tokens.RoundInt64()) keeper.SetPool(ctx, pool) keeper.SetValidatorByConsAddr(ctx, validator) - validator = testingUpdateValidator(keeper, ctx, validator) + validator = TestingUpdateValidator(keeper, ctx, validator) require.Equal(t, int64(100), validator.Tokens.RoundInt64(), "\nvalidator %v\npool %v", validator, pool) // slash the validator by 100% @@ -223,7 +223,7 @@ func TestValidatorBasics(t *testing.T) { assert.True(sdk.DecEq(t, sdk.ZeroDec(), pool.BondedTokens)) // set and retrieve a record - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) keeper.SetValidatorByConsAddr(ctx, validators[0]) resVal, found := keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) @@ -250,7 +250,7 @@ func TestValidatorBasics(t *testing.T) { validators[0].Status = sdk.Bonded validators[0].Tokens = sdk.NewDec(10) validators[0].DelegatorShares = sdk.NewDec(10) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) resVal, found = keeper.GetValidator(ctx, addrVals[0]) require.True(t, found) assert.True(ValEq(t, validators[0], resVal)) @@ -260,8 +260,8 @@ func TestValidatorBasics(t *testing.T) { assert.True(ValEq(t, validators[0], resVals[0])) // add other validators - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) - validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) resVal, found = keeper.GetValidator(ctx, addrVals[1]) require.True(t, found) assert.True(ValEq(t, validators[1], resVal)) @@ -294,7 +294,7 @@ func GetValidatorSortingUnmixed(t *testing.T) { validators[i].Status = sdk.Bonded validators[i].Tokens = sdk.NewDec(amt) validators[i].DelegatorShares = sdk.NewDec(amt) - testingUpdateValidator(keeper, ctx, validators[i]) + TestingUpdateValidator(keeper, ctx, validators[i]) } // first make sure everything made it in to the gotValidator group @@ -313,14 +313,14 @@ func GetValidatorSortingUnmixed(t *testing.T) { // test a basic increase in voting power validators[3].Tokens = sdk.NewDec(500) - testingUpdateValidator(keeper, ctx, validators[3]) + TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) // test a decrease in voting power validators[3].Tokens = sdk.NewDec(300) - testingUpdateValidator(keeper, ctx, validators[3]) + TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) @@ -329,7 +329,7 @@ func GetValidatorSortingUnmixed(t *testing.T) { // test equal voting power, different age validators[3].Tokens = sdk.NewDec(200) ctx = ctx.WithBlockHeight(10) - testingUpdateValidator(keeper, ctx, validators[3]) + TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) @@ -339,7 +339,7 @@ func GetValidatorSortingUnmixed(t *testing.T) { // no change in voting power - no change in sort ctx = ctx.WithBlockHeight(20) - testingUpdateValidator(keeper, ctx, validators[4]) + TestingUpdateValidator(keeper, ctx, validators[4]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) assert.True(ValEq(t, validators[3], resValidators[0])) @@ -348,11 +348,11 @@ func GetValidatorSortingUnmixed(t *testing.T) { // change in voting power of both validators, both still in v-set, no age change validators[3].Tokens = sdk.NewDec(300) validators[4].Tokens = sdk.NewDec(300) - testingUpdateValidator(keeper, ctx, validators[3]) + TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n) ctx = ctx.WithBlockHeight(30) - testingUpdateValidator(keeper, ctx, validators[4]) + TestingUpdateValidator(keeper, ctx, validators[4]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, len(resValidators), n, "%v", resValidators) assert.True(ValEq(t, validators[3], resValidators[0])) @@ -390,7 +390,7 @@ func GetValidatorSortingMixed(t *testing.T) { validators[4].Tokens = sdk.NewDec(amts[4]) for i := range amts { - testingUpdateValidator(keeper, ctx, validators[i]) + TestingUpdateValidator(keeper, ctx, validators[i]) } val0, found := keeper.GetValidator(ctx, sdk.ValAddress(Addrs[0])) require.True(t, found) @@ -444,7 +444,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) validators[i].BondIntraTxCounter = int16(i) keeper.SetPool(ctx, pool) - validators[i] = testingUpdateValidator(keeper, ctx, validators[i]) + validators[i] = TestingUpdateValidator(keeper, ctx, validators[i]) } for i := range amts { @@ -460,7 +460,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { keeper.DeleteValidatorByPowerIndex(ctx, validators[0], pool) validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(500)) keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) @@ -478,7 +478,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) validators[3], pool, _ = validators[3].AddTokensFromDel(pool, sdk.NewInt(1)) keeper.SetPool(ctx, pool) - validators[3] = testingUpdateValidator(keeper, ctx, validators[3]) + validators[3] = TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) @@ -488,7 +488,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) validators[3], pool, _ = validators[3].RemoveDelShares(pool, sdk.NewDec(201)) keeper.SetPool(ctx, pool) - validators[3] = testingUpdateValidator(keeper, ctx, validators[3]) + validators[3] = TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) @@ -498,7 +498,7 @@ func TestGetValidatorsEdgeCases(t *testing.T) { keeper.DeleteValidatorByPowerIndex(ctx, validators[3], pool) validators[3], pool, _ = validators[3].AddTokensFromDel(pool, sdk.NewInt(200)) keeper.SetPool(ctx, pool) - validators[3] = testingUpdateValidator(keeper, ctx, validators[3]) + validators[3] = TestingUpdateValidator(keeper, ctx, validators[3]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, nMax, uint16(len(resValidators))) assert.True(ValEq(t, validators[0], resValidators[0])) @@ -531,13 +531,13 @@ func TestValidatorBondHeight(t *testing.T) { validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewInt(100)) keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) //////////////////////////////////////// // If two validators both increase to the same voting power in the same block, // the one with the first transaction should become bonded - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) - validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) pool = keeper.GetPool(ctx) @@ -551,10 +551,10 @@ func TestValidatorBondHeight(t *testing.T) { validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewInt(50)) validators[2], pool, _ = validators[2].AddTokensFromDel(pool, sdk.NewInt(50)) keeper.SetPool(ctx, pool) - validators[2] = testingUpdateValidator(keeper, ctx, validators[2]) + validators[2] = TestingUpdateValidator(keeper, ctx, validators[2]) resValidators = keeper.GetBondedValidatorsByPower(ctx) require.Equal(t, params.MaxValidators, uint16(len(resValidators))) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) assert.True(ValEq(t, validators[0], resValidators[0])) assert.True(ValEq(t, validators[2], resValidators[1])) } @@ -575,7 +575,7 @@ func TestFullValidatorSetPowerChange(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) validators[i].BondIntraTxCounter = int16(i) keeper.SetPool(ctx, pool) - testingUpdateValidator(keeper, ctx, validators[i]) + TestingUpdateValidator(keeper, ctx, validators[i]) } for i := range amts { var found bool @@ -596,7 +596,7 @@ func TestFullValidatorSetPowerChange(t *testing.T) { pool := keeper.GetPool(ctx) validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(600)) keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) resValidators = keeper.GetBondedValidatorsByPower(ctx) assert.Equal(t, max, len(resValidators)) assert.True(ValEq(t, validators[0], resValidators[0])) @@ -647,14 +647,14 @@ func TestApplyAndReturnValidatorSetUpdatesIdentical(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test identical, // tendermintUpdate set: {} -> {} - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) } @@ -669,15 +669,15 @@ func TestApplyAndReturnValidatorSetUpdatesSingleValueChange(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test single value change // tendermintUpdate set: {} -> {c1'} validators[0].Status = sdk.Bonded validators[0].Tokens = sdk.NewDec(600) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) @@ -696,8 +696,8 @@ func TestApplyAndReturnValidatorSetUpdatesMultipleValueChange(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test multiple value change @@ -706,8 +706,8 @@ func TestApplyAndReturnValidatorSetUpdatesMultipleValueChange(t *testing.T) { validators[0], pool, _ = validators[0].AddTokensFromDel(pool, sdk.NewInt(190)) validators[1], pool, _ = validators[1].AddTokensFromDel(pool, sdk.NewInt(80)) keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 2, len(updates)) @@ -726,8 +726,8 @@ func TestApplyAndReturnValidatorSetUpdatesInserted(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test validtor added at the beginning @@ -775,13 +775,13 @@ func TestApplyAndReturnValidatorSetUpdatesWithCliffValidator(t *testing.T) { validators[i], pool, _ = validators[i].AddTokensFromDel(pool, sdk.NewInt(amt)) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // test validator added at the end but not inserted in the valset // tendermintUpdate set: {} -> {} - testingUpdateValidator(keeper, ctx, validators[2]) + TestingUpdateValidator(keeper, ctx, validators[2]) updates := keeper.ApplyAndReturnValidatorSetUpdates(ctx) require.Equal(t, 0, len(updates)) @@ -813,8 +813,8 @@ func TestApplyAndReturnValidatorSetUpdatesPowerDecrease(t *testing.T) { validators[i].BondIntraTxCounter = int16(i) keeper.SetPool(ctx, pool) } - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) require.Equal(t, 0, len(keeper.ApplyAndReturnValidatorSetUpdates(ctx))) // check initial power @@ -827,8 +827,8 @@ func TestApplyAndReturnValidatorSetUpdatesPowerDecrease(t *testing.T) { validators[0], pool, _ = validators[0].RemoveDelShares(pool, sdk.NewDec(20)) validators[1], pool, _ = validators[1].RemoveDelShares(pool, sdk.NewDec(30)) keeper.SetPool(ctx, pool) - validators[0] = testingUpdateValidator(keeper, ctx, validators[0]) - validators[1] = testingUpdateValidator(keeper, ctx, validators[1]) + validators[0] = TestingUpdateValidator(keeper, ctx, validators[0]) + validators[1] = TestingUpdateValidator(keeper, ctx, validators[1]) // power has changed require.Equal(t, sdk.NewDec(80).RoundInt64(), validators[0].GetPower().RoundInt64()) diff --git a/x/stake/simulation/msgs.go b/x/stake/simulation/msgs.go index 9c2b1b98d..76b996363 100644 --- a/x/stake/simulation/msgs.go +++ b/x/stake/simulation/msgs.go @@ -16,7 +16,9 @@ import ( // SimulateMsgCreateValidator func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account, event func(string)) ( + action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom description := stake.Description{ @@ -71,7 +73,9 @@ func SimulateMsgCreateValidator(m auth.AccountMapper, k stake.Keeper) simulation // SimulateMsgEditValidator func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account, event func(string)) ( + action string, fOp []simulation.FutureOperation, err error) { description := stake.Description{ Moniker: simulation.RandStringOfLength(r, 10), @@ -109,7 +113,9 @@ func SimulateMsgEditValidator(k stake.Keeper) simulation.Operation { // SimulateMsgDelegate func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account, event func(string)) ( + action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom validatorAcc := simulation.RandomAcc(r, accs) @@ -145,7 +151,9 @@ func SimulateMsgDelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operat // SimulateMsgBeginUnbonding func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account, event func(string)) ( + action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom validatorAcc := simulation.RandomAcc(r, accs) @@ -181,7 +189,9 @@ func SimulateMsgBeginUnbonding(m auth.AccountMapper, k stake.Keeper) simulation. // SimulateMsgBeginRedelegate func SimulateMsgBeginRedelegate(m auth.AccountMapper, k stake.Keeper) simulation.Operation { handler := stake.NewHandler(k) - return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, accs []simulation.Account, event func(string)) (action string, fOp []simulation.FutureOperation, err error) { + return func(r *rand.Rand, app *baseapp.BaseApp, ctx sdk.Context, + accs []simulation.Account, event func(string)) ( + action string, fOp []simulation.FutureOperation, err error) { denom := k.GetParams(ctx).BondDenom sourceValidatorAcc := simulation.RandomAcc(r, accs) diff --git a/x/stake/stake.go b/x/stake/stake.go index 0baa468ba..527111d2c 100644 --- a/x/stake/stake.go +++ b/x/stake/stake.go @@ -56,6 +56,7 @@ var ( GetREDsFromValSrcIndexKey = keeper.GetREDsFromValSrcIndexKey GetREDsToValDstIndexKey = keeper.GetREDsToValDstIndexKey GetREDsByDelToValDstIndexKey = keeper.GetREDsByDelToValDstIndexKey + TestingUpdateValidator = keeper.TestingUpdateValidator DefaultParamspace = keeper.DefaultParamspace KeyInflationRateChange = types.KeyInflationRateChange diff --git a/x/stake/test_common.go b/x/stake/test_common.go new file mode 100644 index 000000000..49bac0f31 --- /dev/null +++ b/x/stake/test_common.go @@ -0,0 +1,62 @@ +package stake + +import ( + "github.com/tendermint/tendermint/crypto" + "github.com/tendermint/tendermint/crypto/ed25519" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/x/auth" + "github.com/cosmos/cosmos-sdk/x/stake/types" +) + +var ( + priv1 = ed25519.GenPrivKey() + addr1 = sdk.AccAddress(priv1.PubKey().Address()) + priv2 = ed25519.GenPrivKey() + addr2 = sdk.AccAddress(priv2.PubKey().Address()) + addr3 = sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address()) + priv4 = ed25519.GenPrivKey() + addr4 = sdk.AccAddress(priv4.PubKey().Address()) + coins = sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(10))} + fee = auth.NewStdFee( + 100000, + sdk.Coins{sdk.NewCoin("foocoin", sdk.NewInt(0))}..., + ) + + commissionMsg = NewCommissionMsg(sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()) +) + +func NewTestMsgCreateValidator(address sdk.ValAddress, pubKey crypto.PubKey, amt int64) MsgCreateValidator { + return types.NewMsgCreateValidator( + address, pubKey, sdk.NewCoin("steak", sdk.NewInt(amt)), Description{}, commissionMsg, + ) +} + +func NewTestMsgCreateValidatorWithCommission(address sdk.ValAddress, pubKey crypto.PubKey, + amt int64, commissionRate sdk.Dec) MsgCreateValidator { + + commission := NewCommissionMsg(commissionRate, sdk.OneDec(), sdk.ZeroDec()) + + return types.NewMsgCreateValidator( + address, pubKey, sdk.NewCoin("steak", sdk.NewInt(amt)), Description{}, commission, + ) +} + +func NewTestMsgDelegate(delAddr sdk.AccAddress, valAddr sdk.ValAddress, amt int64) MsgDelegate { + return MsgDelegate{ + DelegatorAddr: delAddr, + ValidatorAddr: valAddr, + Delegation: sdk.NewCoin("steak", sdk.NewInt(amt)), + } +} + +func NewTestMsgCreateValidatorOnBehalfOf(delAddr sdk.AccAddress, valAddr sdk.ValAddress, valPubKey crypto.PubKey, amt int64) MsgCreateValidator { + return MsgCreateValidator{ + Description: Description{}, + Commission: commissionMsg, + DelegatorAddr: delAddr, + ValidatorAddr: valAddr, + PubKey: valPubKey, + Delegation: sdk.NewCoin("steak", sdk.NewInt(amt)), + } +}