diff --git a/PENDING.md b/PENDING.md index 44ee631d6..376cd2daf 100644 --- a/PENDING.md +++ b/PENDING.md @@ -71,6 +71,8 @@ BREAKING CHANGES * [x/stake] \#2394 Split up UpdateValidator into distinct state transitions applied only in EndBlock * [x/slashing] \#2480 Fix signing info handling bugs & faulty slashing * [x/stake] \#2412 Added an unbonding validator queue to EndBlock to automatically update validator.Status when finished Unbonding + * [x/stake] \#2500 Block conflicting redelegations until we add an index + * [x/params] Global Paramstore refactored * Tendermint * Update tendermint version from v0.23.0 to v0.25.0, notable changes 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/docs/spec/params/README.md b/docs/spec/params/README.md new file mode 100644 index 000000000..d9c8b9221 --- /dev/null +++ b/docs/spec/params/README.md @@ -0,0 +1,20 @@ +# Params module specification + +## Abstract + +Package params provides a globally available parameter store. + +There are two main types, Keeper and Subspace. Subspace is an isolated namespace for a +paramstore, where keys are prefixed by preconfigured spacename. Keeper has a +permission to access all existing spaces. + +Subspace can be used by the individual keepers, who needs a private parameter store +that the other keeper cannot modify. Keeper can be used by the Governance keeper, +who need to modify any parameter in case of the proposal passes. + +The following contents explains how to use params module for master and user modules. + +## Contents + +1. [Keeper](keeper.md) +1. [Subspace](subspace.md) diff --git a/docs/spec/params/keeper.md b/docs/spec/params/keeper.md new file mode 100644 index 000000000..fe376911e --- /dev/null +++ b/docs/spec/params/keeper.md @@ -0,0 +1,17 @@ +# Keeper + +In the app initialization stage, `Keeper.Subspace(Paramspace)` is passed to the user modules, and the subspaces are stored in `Keeper.spaces`. Later it can be retrieved with `Keeper.GetSubspace`, so the keepers holding `Keeper` can access to any subspace. For example, Gov module can take `Keeper` as its argument and modify parameter of any subspace when a `ParameterChangeProposal` is accepted. + +```go +type MasterKeeper struct { + pk params.Keeper +} + +func (k MasterKeeper) SetParam(ctx sdk.Context, space string, key string, param interface{}) { + space, ok := k.ps.GetSubspace(space) + if !ok { + return + } + space.Set(ctx, key, param) +} +``` diff --git a/docs/spec/params/subspace.md b/docs/spec/params/subspace.md new file mode 100644 index 000000000..378177898 --- /dev/null +++ b/docs/spec/params/subspace.md @@ -0,0 +1,76 @@ +# Subspace + +## Basic Usage + +First, declare parameter space and parameter keys for the module. Then include params.Subspace in the keeper. Since we prefix the keys with the spacename, it is recommended to use the same name with the module's. + +```go +const ( + DefaultParamspace = "mymodule" +) + +const ( + KeyParameter1 = "myparameter1" + KeyParameter2 = "myparameter2" +) + +type Keeper struct { + cdc *wire.Codec + key sdk.StoreKey + + ps params.Subspace +} +``` + +Pass a params.Subspace to NewKeeper with DefaultParamSubspace (or another) + +```go +app.myKeeper = mymodule.NewKeeper(cdc, key, app.paramStore.SubStore(mymodule.DefaultParamspace)) +``` + +`NewKeeper` should register a `TypeTable`, which defines a map from parameter keys from types. + +```go +func NewKeeper(cdc *codec.Codec, key sdk.StoreKey, space params.Subspace) Keeper { + return Keeper { + cdc: cdc, + key: key, + ps: space.WithTypeTable(ParamTypeTable()), + } +} +``` + +Now we can access to the paramstore using Paramstore Keys + +```go +var param MyStruct +k.ps.Get(KeyParameter1, ¶m) +k.ps.Set(KeyParameter2, param) +``` + +# Genesis Usage + +Declare a struct for parameters and make it implement params.ParamSet. It will then be able to be passed to SetParamSet. + +```go +type MyParams struct { + Parameter1 uint64 + Parameter2 string +} + +// Implements params.ParamSet +// KeyValuePairs must return the list of (ParamKey, PointerToTheField) +func (p *MyParams) KeyValuePairs() params.KeyValuePairs { + return params.KeyFieldPairs { + {KeyParameter1, &p.Parameter1}, + {KeyParameter2, &p.Parameter2}, + } +} + +func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { + k.ps.SetParamSet(ctx, &data.params) +} +``` + +The method is pointer receiver because there could be a case that we read from the store and set the result to the struct. + 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/store/prefixstore_test.go b/store/prefixstore_test.go index 8445991b6..4e555292c 100644 --- a/store/prefixstore_test.go +++ b/store/prefixstore_test.go @@ -119,10 +119,6 @@ func TestPrefixStoreIterate(t *testing.T) { } func incFirstByte(bz []byte) { - if bz[0] == byte(255) { - bz[0] = byte(0) - return - } bz[0]++ } 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/params/doc.go b/x/params/doc.go index 06d6620b2..bee45e79e 100644 --- a/x/params/doc.go +++ b/x/params/doc.go @@ -3,18 +3,18 @@ package params /* Package params provides a globally available parameter store. -There are two main types, Keeper and Space. Space is an isolated namespace for a +There are two main types, Keeper and Subspace. Subspace is an isolated namespace for a paramstore, where keys are prefixed by preconfigured spacename. Keeper has a -permission to access all existing spaces and create new space. +permission to access all existing spaces. -Space can be used by the individual keepers, who needs a private parameter store -that the other keeper are not able to modify. Keeper can be used by the Governance -keeper, who need to modify any parameter in case of the proposal passes. +Subspace can be used by the individual keepers, who needs a private parameter store +that the other keeper cannot modify. Keeper can be used by the Governance keeper, +who need to modify any parameter in case of the proposal passes. Basic Usage: First, declare parameter space and parameter keys for the module. Then include -params.Store in the keeper. Since we prefix the keys with the spacename, it is +params.Subspace in the keeper. Since we prefix the keys with the spacename, it is recommended to use the same name with the module's. const ( @@ -33,26 +33,29 @@ recommended to use the same name with the module's. ps params.Subspace } -Pass a params.Store to NewKeeper with DefaultParamSpace (or another) +Pass a params.Subspace to NewKeeper with DefaultParamspace (or another) app.myKeeper = mymodule.NewKeeper(app.paramStore.SubStore(mymodule.DefaultParamspace)) Now we can access to the paramstore using Paramstore Keys + var param MyStruct k.ps.Get(KeyParameter1, ¶m) k.ps.Set(KeyParameter2, param) Genesis Usage: -Declare a struct for parameters and make it implement ParamStruct. It will then -be able to be passed to SetFromParamStruct. +Declare a struct for parameters and make it implement params.ParamSet. It will then +be able to be passed to SetParamSet. type MyParams struct { Parameter1 uint64 Parameter2 string } - func (p *MyParams) KeyFieldPairs() params.KeyFieldPairs { + // Implements params.ParamSet + // KeyValuePairs must return the list of (ParamKey, PointerToTheField) + func (p *MyParams) KeyValuePairs() params.KeyValuePairs { return params.KeyFieldPairs { {KeyParameter1, &p.Parameter1}, {KeyParameter2, &p.Parameter2}, @@ -60,26 +63,26 @@ be able to be passed to SetFromParamStruct. } func InitGenesis(ctx sdk.Context, k Keeper, data GenesisState) { - k.ps.SetFromParamStruct(ctx, &data.params) + k.ps.SetParamSet(ctx, &data.params) } The method is pointer receiver because there could be a case that we read from the store and set the result to the struct. -Master Permission Usage: +Master Keeper Usage: Keepers that require master permission to the paramstore, such as gov, can take -params.Keeper itself to access all substores(using GetSubstore) +params.Keeper itself to access all subspace(using GetSubspace) type MasterKeeper struct { - ps params.Store + pk params.Keeper } func (k MasterKeeper) SetParam(ctx sdk.Context, space string, key string, param interface{}) { - store, ok := k.ps.GetSubstore(space) + space, ok := k.pk.GetSubspace(space) if !ok { return } - store.Set(ctx, key, param) + space.Set(ctx, key, param) } */ diff --git a/x/params/keeper_test.go b/x/params/keeper_test.go index 640661a24..585db3c41 100644 --- a/x/params/keeper_test.go +++ b/x/params/keeper_test.go @@ -65,45 +65,82 @@ func TestKeeper(t *testing.T) { []byte("extra2"), string(""), ) + cdc := codec.New() skey := sdk.NewKVStoreKey("test") tkey := sdk.NewTransientStoreKey("transient_test") ctx := defaultContext(skey, tkey) - store := NewKeeper(codec.New(), skey, tkey).Subspace("test").WithTypeTable(table) + keeper := NewKeeper(cdc, skey, tkey) + space := keeper.Subspace("test").WithTypeTable(table) + store := ctx.KVStore(skey).Prefix([]byte("test/")) + // Set params for i, kv := range kvs { - require.NotPanics(t, func() { store.Set(ctx, []byte(kv.key), kv.param) }, "store.Set panics, tc #%d", i) + require.NotPanics(t, func() { space.Set(ctx, []byte(kv.key), kv.param) }, "space.Set panics, tc #%d", i) } + // Test space.Get for i, kv := range kvs { var param int64 - require.NotPanics(t, func() { store.Get(ctx, []byte(kv.key), ¶m) }, "store.Get panics, tc #%d", i) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }, "space.Get panics, tc #%d", i) require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) } - cdc := codec.New() + // Test space.GetRaw for i, kv := range kvs { var param int64 - bz := store.GetRaw(ctx, []byte(kv.key)) + bz := space.GetRaw(ctx, []byte(kv.key)) err := cdc.UnmarshalJSON(bz, ¶m) require.Nil(t, err, "err is not nil, tc #%d", i) require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) } + // Test store.Get equals space.Get for i, kv := range kvs { - var param bool - require.Panics(t, func() { store.Get(ctx, []byte(kv.key), ¶m) }, "invalid store.Get not panics, tc #%d", i) + var param int64 + bz := store.Get([]byte(kv.key)) + require.NotNil(t, bz, "KVStore.Get returns nil, tc #%d", i) + err := cdc.UnmarshalJSON(bz, ¶m) + require.NoError(t, err, "UnmarshalJSON returns error, tc #%d", i) + require.Equal(t, kv.param, param, "stored param not equal, tc #%d", i) } + // Test invalid space.Get for i, kv := range kvs { - require.Panics(t, func() { store.Set(ctx, []byte(kv.key), true) }, "invalid store.Set not panics, tc #%d", i) + var param bool + require.Panics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }, "invalid space.Get not panics, tc #%d", i) + } + + // Test invalid space.Set + for i, kv := range kvs { + require.Panics(t, func() { space.Set(ctx, []byte(kv.key), true) }, "invalid space.Set not panics, tc #%d", i) + } + + // Test GetSubspace + for i, kv := range kvs { + var gparam, param int64 + gspace, ok := keeper.GetSubspace("test") + require.True(t, ok, "cannot retrieve subspace, tc #%d", i) + + require.NotPanics(t, func() { gspace.Get(ctx, []byte(kv.key), &gparam) }) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }) + require.Equal(t, gparam, param, "GetSubspace().Get not equal with space.Get, tc #%d", i) + + require.NotPanics(t, func() { gspace.Set(ctx, []byte(kv.key), int64(i)) }) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), ¶m) }) + require.Equal(t, int64(i), param, "GetSubspace().Set not equal with space.Get, tc #%d", i) } } -func TestGet(t *testing.T) { +func indirect(ptr interface{}) interface{} { + return reflect.ValueOf(ptr).Elem().Interface() +} + +func TestSubspace(t *testing.T) { + cdc := createTestCodec() key := sdk.NewKVStoreKey("test") tkey := sdk.NewTransientStoreKey("transient_test") ctx := defaultContext(key, tkey) - keeper := NewKeeper(createTestCodec(), key, tkey) + keeper := NewKeeper(cdc, key, tkey) kvs := []struct { key string @@ -140,29 +177,41 @@ func TestGet(t *testing.T) { []byte("struct"), s{}, ) - store := keeper.Subspace("test").WithTypeTable(table) + store := ctx.KVStore(key).Prefix([]byte("test/")) + space := keeper.Subspace("test").WithTypeTable(table) + // Test space.Set, space.Modified for i, kv := range kvs { - require.False(t, store.Modified(ctx, []byte(kv.key)), "store.Modified returns true before setting, tc #%d", i) - require.NotPanics(t, func() { store.Set(ctx, []byte(kv.key), kv.param) }, "store.Set panics, tc #%d", i) - require.True(t, store.Modified(ctx, []byte(kv.key)), "store.Modified returns false after setting, tc #%d", i) + require.False(t, space.Modified(ctx, []byte(kv.key)), "space.Modified returns true before setting, tc #%d", i) + require.NotPanics(t, func() { space.Set(ctx, []byte(kv.key), kv.param) }, "space.Set panics, tc #%d", i) + require.True(t, space.Modified(ctx, []byte(kv.key)), "space.Modified returns false after setting, tc #%d", i) } + // Test space.Get, space.GetIfExists for i, kv := range kvs { - require.NotPanics(t, func() { store.GetIfExists(ctx, []byte("invalid"), kv.ptr) }, "store.GetIfExists panics when no value exists, tc #%d", i) - require.Equal(t, kv.zero, reflect.ValueOf(kv.ptr).Elem().Interface(), "store.GetIfExists unmarshalls when no value exists, tc #%d", i) - require.Panics(t, func() { store.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid store.Get not panics when no value exists, tc #%d", i) - require.Equal(t, kv.zero, reflect.ValueOf(kv.ptr).Elem().Interface(), "invalid store.Get unmarshalls when no value exists, tc #%d", i) + require.NotPanics(t, func() { space.GetIfExists(ctx, []byte("invalid"), kv.ptr) }, "space.GetIfExists panics when no value exists, tc #%d", i) + require.Equal(t, kv.zero, indirect(kv.ptr), "space.GetIfExists unmarshalls when no value exists, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid space.Get not panics when no value exists, tc #%d", i) + require.Equal(t, kv.zero, indirect(kv.ptr), "invalid space.Get unmarshalls when no value exists, tc #%d", i) - require.NotPanics(t, func() { store.GetIfExists(ctx, []byte(kv.key), kv.ptr) }, "store.GetIfExists panics, tc #%d", i) - require.Equal(t, kv.param, reflect.ValueOf(kv.ptr).Elem().Interface(), "stored param not equal, tc #%d", i) - require.NotPanics(t, func() { store.Get(ctx, []byte(kv.key), kv.ptr) }, "store.Get panics, tc #%d", i) - require.Equal(t, kv.param, reflect.ValueOf(kv.ptr).Elem().Interface(), "stored param not equal, tc #%d", i) + require.NotPanics(t, func() { space.GetIfExists(ctx, []byte(kv.key), kv.ptr) }, "space.GetIfExists panics, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) + require.NotPanics(t, func() { space.Get(ctx, []byte(kv.key), kv.ptr) }, "space.Get panics, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) - require.Panics(t, func() { store.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid store.Get not panics when no value exists, tc #%d", i) - require.Equal(t, kv.param, reflect.ValueOf(kv.ptr).Elem().Interface(), "invalid store.Get unmarshalls when no value existt, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte("invalid"), kv.ptr) }, "invalid space.Get not panics when no value exists, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "invalid space.Get unmarshalls when no value existt, tc #%d", i) - require.Panics(t, func() { store.Get(ctx, []byte(kv.key), nil) }, "invalid store.Get not panics when the pointer is nil, tc #%d", i) - require.Panics(t, func() { store.Get(ctx, []byte(kv.key), new(invalid)) }, "invalid store.Get not panics when the pointer is different type, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte(kv.key), nil) }, "invalid space.Get not panics when the pointer is nil, tc #%d", i) + require.Panics(t, func() { space.Get(ctx, []byte(kv.key), new(invalid)) }, "invalid space.Get not panics when the pointer is different type, tc #%d", i) + } + + // Test store.Get equals space.Get + for i, kv := range kvs { + bz := store.Get([]byte(kv.key)) + require.NotNil(t, bz, "store.Get() returns nil, tc #%d", i) + err := cdc.UnmarshalJSON(bz, kv.ptr) + require.NoError(t, err, "cdc.UnmarshalJSON() returns error, tc #%d", i) + require.Equal(t, kv.param, indirect(kv.ptr), "stored param not equal, tc #%d", i) } } diff --git a/x/params/subspace/subspace.go b/x/params/subspace/subspace.go index 7c9ca5fda..fe5889a90 100644 --- a/x/params/subspace/subspace.go +++ b/x/params/subspace/subspace.go @@ -7,10 +7,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -// Additional capicity to be allocated for Subspace.name -// So we don't have to allocate extra space each time appending to the key -const extraKeyCap = 20 - // Individual parameter store for each keeper // Transient store persists for a block, so we use it for // recording whether the parameter has been changed or not @@ -30,32 +26,35 @@ func NewSubspace(cdc *codec.Codec, key sdk.StoreKey, tkey sdk.StoreKey, name str cdc: cdc, key: key, tkey: tkey, + name: []byte(name), + table: TypeTable{ + m: make(map[string]reflect.Type), + }, } - namebz := []byte(name) - res.name = make([]byte, len(namebz), len(namebz)+extraKeyCap) - copy(res.name, namebz) return } // WithTypeTable initializes TypeTable and returns modified Subspace -func (s Subspace) WithTypeTable(table TypeTable) (res Subspace) { - if table == nil { +func (s Subspace) WithTypeTable(table TypeTable) Subspace { + if table.m == nil { panic("SetTypeTable() called with nil TypeTable") } - if s.table != nil { - panic("SetTypeTable() called on initialized Subspace") + if len(s.table.m) != 0 { + panic("SetTypeTable() called on already initialized Subspace") } - res = Subspace{ - cdc: s.cdc, - key: s.key, - tkey: s.tkey, - name: s.name, - table: table, + for k, v := range table.m { + s.table.m[k] = v } - return + // Allocate additional capicity for Subspace.name + // So we don't have to allocate extra space each time appending to the key + name := s.name + s.name = make([]byte, len(name), len(name)+table.maxKeyLength()) + copy(s.name, name) + + return s } // Returns a KVStore identical with ctx.KVStore(s.key).Prefix() @@ -118,7 +117,7 @@ func (s Subspace) Modified(ctx sdk.Context, key []byte) bool { func (s Subspace) Set(ctx sdk.Context, key []byte, param interface{}) { store := s.kvStore(ctx) - ty, ok := s.table[string(key)] + ty, ok := s.table.m[string(key)] if !ok { panic("Parameter not registered") } diff --git a/x/params/subspace/table.go b/x/params/subspace/table.go index 363d0243d..19e41da09 100644 --- a/x/params/subspace/table.go +++ b/x/params/subspace/table.go @@ -5,7 +5,9 @@ import ( ) // TypeTable subspaces appropriate type for each parameter key -type TypeTable map[string]reflect.Type +type TypeTable struct { + m map[string]reflect.Type +} // Constructs new table func NewTypeTable(keytypes ...interface{}) (res TypeTable) { @@ -13,7 +15,9 @@ func NewTypeTable(keytypes ...interface{}) (res TypeTable) { panic("odd number arguments in NewTypeTypeTable") } - res = make(map[string]reflect.Type) + res = TypeTable{ + m: make(map[string]reflect.Type), + } for i := 0; i < len(keytypes); i += 2 { res = res.RegisterType(keytypes[i].([]byte), keytypes[i+1]) @@ -22,10 +26,27 @@ func NewTypeTable(keytypes ...interface{}) (res TypeTable) { return } +func isAlphaNumeric(key []byte) bool { + for _, b := range key { + if !((48 <= b && b <= 57) || // numeric + (65 <= b && b <= 90) || // upper case + (97 <= b && b <= 122)) { // lower case + return false + } + } + return true +} + // Register single key-type pair func (t TypeTable) RegisterType(key []byte, ty interface{}) TypeTable { + if len(key) == 0 { + panic("cannot register empty key") + } + if !isAlphaNumeric(key) { + panic("non alphanumeric parameter key") + } keystr := string(key) - if _, ok := t[keystr]; ok { + if _, ok := t.m[keystr]; ok { panic("duplicate parameter key") } @@ -36,7 +57,7 @@ func (t TypeTable) RegisterType(key []byte, ty interface{}) TypeTable { rty = rty.Elem() } - t[keystr] = rty + t.m[keystr] = rty return t } @@ -48,3 +69,13 @@ func (t TypeTable) RegisterParamSet(ps ParamSet) TypeTable { } return t } + +func (t TypeTable) maxKeyLength() (res int) { + for k := range t.m { + l := len(k) + if l > res { + res = l + } + } + return +} diff --git a/x/params/subspace/table_test.go b/x/params/subspace/table_test.go new file mode 100644 index 000000000..8f9142e1e --- /dev/null +++ b/x/params/subspace/table_test.go @@ -0,0 +1,35 @@ +package subspace + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +type testparams struct { + i int64 + b bool +} + +func (tp *testparams) KeyValuePairs() KeyValuePairs { + return KeyValuePairs{ + {[]byte("i"), &tp.i}, + {[]byte("b"), &tp.b}, + } +} + +func TestTypeTable(t *testing.T) { + table := NewTypeTable() + + require.Panics(t, func() { table.RegisterType([]byte(""), nil) }) + require.Panics(t, func() { table.RegisterType([]byte("!@#$%"), nil) }) + require.Panics(t, func() { table.RegisterType([]byte("hello,"), nil) }) + require.Panics(t, func() { table.RegisterType([]byte("hello"), nil) }) + + require.NotPanics(t, func() { table.RegisterType([]byte("hello"), bool(false)) }) + require.NotPanics(t, func() { table.RegisterType([]byte("world"), int64(0)) }) + require.Panics(t, func() { table.RegisterType([]byte("hello"), bool(false)) }) + + require.NotPanics(t, func() { table.RegisterParamSet(&testparams{}) }) + require.Panics(t, func() { table.RegisterParamSet(&testparams{}) }) +} diff --git a/x/slashing/handler_test.go b/x/slashing/handler_test.go index d993f8c20..ef307547c 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 6ad5c3792..77d0ebcf8 100644 --- a/x/slashing/keeper_test.go +++ b/x/slashing/keeper_test.go @@ -32,7 +32,7 @@ func TestHandleDoubleSign(t *testing.T) { ctx = ctx.WithBlockHeight(-1) 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) @@ -73,7 +73,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) @@ -139,7 +139,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) @@ -331,7 +331,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 81fc0e63d..a648bfe85 100644 --- a/x/slashing/test_common.go +++ b/x/slashing/test_common.go @@ -112,7 +112,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 a13e8c618..a2a2d9f0f 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 de7aee90f..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") @@ -863,6 +836,48 @@ func TestTransitiveRedelegation(t *testing.T) { require.True(t, got.IsOK(), "expected no error") } +func TestConflictingRedelegation(t *testing.T) { + ctx, _, keeper := keep.CreateTestInput(t, false, 1000) + validatorAddr := sdk.ValAddress(keep.Addrs[0]) + validatorAddr2 := sdk.ValAddress(keep.Addrs[1]) + + // set the unbonding time + params := keeper.GetParams(ctx) + params.UnbondingTime = 1 + keeper.SetParams(ctx, params) + + // create the validators + 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) + got = handleMsgCreateValidator(ctx, msgCreateValidator, keeper) + require.True(t, got.IsOK(), "expected no error on runMsgCreateValidator") + + // end block to bond them + EndBlocker(ctx, keeper) + + // begin redelegate + msgBeginRedelegate := NewMsgBeginRedelegate(sdk.AccAddress(validatorAddr), validatorAddr, validatorAddr2, sdk.NewDec(5)) + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error, %v", got) + + // cannot redelegate again while first redelegation still exists + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, !got.IsOK(), "expected an error, msg: %v", msgBeginRedelegate) + + // progress forward in time + ctx = ctx.WithBlockTime(ctx.BlockHeader().Time.Add(10 * time.Second)) + + // complete first redelegation + EndBlocker(ctx, keeper) + + // now should be able to redelegate again + got = handleMsgBeginRedelegate(ctx, msgBeginRedelegate, keeper) + require.True(t, got.IsOK(), "expected no error") +} + func TestUnbondingWhenExcessValidators(t *testing.T) { ctx, _, keeper := keep.CreateTestInput(t, false, 1000) validatorAddr1 := sdk.ValAddress(keep.Addrs[0]) @@ -876,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 @@ -920,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 6f5a44ac0..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 } @@ -526,6 +525,13 @@ func (k Keeper) CompleteUnbonding(ctx sdk.Context, delAddr sdk.AccAddress, valAd func (k Keeper) BeginRedelegation(ctx sdk.Context, delAddr sdk.AccAddress, valSrcAddr, valDstAddr sdk.ValAddress, sharesAmount sdk.Dec) (types.Redelegation, sdk.Error) { + // check if there is already a redelgation in progress from src to dst + // TODO quick fix, instead we should use an index, see https://github.com/cosmos/cosmos-sdk/issues/1402 + _, found := k.GetRedelegation(ctx, delAddr, valSrcAddr, valDstAddr) + if found { + return types.Redelegation{}, types.ErrConflictingRedelegation(k.Codespace()) + } + // check if this is a transitive redelegation if k.HasReceivingRedelegation(ctx, delAddr, valSrcAddr) { return types.Redelegation{}, types.ErrTransitiveRedelegation(k.Codespace()) 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)), + } +} diff --git a/x/stake/types/errors.go b/x/stake/types/errors.go index 84a7e5ae6..e8c85800f 100644 --- a/x/stake/types/errors.go +++ b/x/stake/types/errors.go @@ -164,6 +164,11 @@ func ErrTransitiveRedelegation(codespace sdk.CodespaceType) sdk.Error { "redelegation to this validator already in progress, first redelegation to this validator must complete before next redelegation") } +func ErrConflictingRedelegation(codespace sdk.CodespaceType) sdk.Error { + return sdk.NewError(codespace, CodeInvalidDelegation, + "conflicting redelegation from this source validator to this dest validator already exists, you must wait for it to finish") +} + func ErrBothShareMsgsGiven(codespace sdk.CodespaceType) sdk.Error { return sdk.NewError(codespace, CodeInvalidInput, "both shares amount and shares percent provided") }