package keeper import ( "fmt" abci "github.com/tendermint/tendermint/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/distribution/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // AllocateTokens handles distribution of the collected fees func (k Keeper) AllocateTokens( ctx sdk.Context, sumPreviousPrecommitPower, totalPreviousPower int64, previousProposer sdk.ConsAddress, previousVotes []abci.VoteInfo, ) { logger := k.Logger(ctx) // fetch and clear the collected fees for distribution, since this is // called in BeginBlock, collected fees will be from the previous block // (and distributed to the previous proposer) feeCollector := k.authKeeper.GetModuleAccount(ctx, k.feeCollectorName) feesCollectedInt := k.bankKeeper.GetAllBalances(ctx, feeCollector.GetAddress()) feesCollected := sdk.NewDecCoinsFromCoins(feesCollectedInt...) // transfer collected fees to the distribution module account err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, k.feeCollectorName, types.ModuleName, feesCollectedInt) if err != nil { panic(err) } // temporary workaround to keep CanWithdrawInvariant happy // general discussions here: https://github.com/cosmos/cosmos-sdk/issues/2906#issuecomment-441867634 feePool := k.GetFeePool(ctx) if totalPreviousPower == 0 { feePool.CommunityPool = feePool.CommunityPool.Add(feesCollected...) k.SetFeePool(ctx, feePool) return } // calculate fraction votes previousFractionVotes := sdk.NewDec(sumPreviousPrecommitPower).Quo(sdk.NewDec(totalPreviousPower)) // calculate previous proposer reward baseProposerReward := k.GetBaseProposerReward(ctx) bonusProposerReward := k.GetBonusProposerReward(ctx) proposerMultiplier := baseProposerReward.Add(bonusProposerReward.MulTruncate(previousFractionVotes)) proposerReward := feesCollected.MulDecTruncate(proposerMultiplier) // pay previous proposer remaining := feesCollected proposerValidator := k.stakingKeeper.ValidatorByConsAddr(ctx, previousProposer) if proposerValidator != nil { ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeProposerReward, sdk.NewAttribute(sdk.AttributeKeyAmount, proposerReward.String()), sdk.NewAttribute(types.AttributeKeyValidator, proposerValidator.GetOperator().String()), ), ) k.AllocateTokensToValidator(ctx, proposerValidator, proposerReward) remaining = remaining.Sub(proposerReward) } else { // previous proposer can be unknown if say, the unbonding period is 1 block, so // e.g. a validator undelegates at block X, it's removed entirely by // block X+1's endblock, then X+2 we need to refer to the previous // proposer for X+1, but we've forgotten about them. logger.Error(fmt.Sprintf( "WARNING: Attempt to allocate proposer rewards to unknown proposer %s. "+ "This should happen only if the proposer unbonded completely within a single block, "+ "which generally should not happen except in exceptional circumstances (or fuzz testing). "+ "We recommend you investigate immediately.", previousProposer.String())) } // calculate fraction allocated to validators communityTax := k.GetCommunityTax(ctx) voteMultiplier := sdk.OneDec().Sub(proposerMultiplier).Sub(communityTax) // allocate tokens proportionally to voting power // TODO consider parallelizing later, ref https://github.com/cosmos/cosmos-sdk/pull/3099#discussion_r246276376 for _, vote := range previousVotes { validator := k.stakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address) // TODO consider microslashing for missing votes. // ref https://github.com/cosmos/cosmos-sdk/issues/2525#issuecomment-430838701 powerFraction := sdk.NewDec(vote.Validator.Power).QuoTruncate(sdk.NewDec(totalPreviousPower)) reward := feesCollected.MulDecTruncate(voteMultiplier).MulDecTruncate(powerFraction) k.AllocateTokensToValidator(ctx, validator, reward) remaining = remaining.Sub(reward) } // allocate community funding feePool.CommunityPool = feePool.CommunityPool.Add(remaining...) k.SetFeePool(ctx, feePool) } // AllocateTokensToValidator allocate tokens to a particular validator, splitting according to commission func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val stakingtypes.ValidatorI, tokens sdk.DecCoins) { // split tokens between validator and delegators according to commission commission := tokens.MulDec(val.GetCommission()) shared := tokens.Sub(commission) // update current commission ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeCommission, sdk.NewAttribute(sdk.AttributeKeyAmount, commission.String()), sdk.NewAttribute(types.AttributeKeyValidator, val.GetOperator().String()), ), ) currentCommission := k.GetValidatorAccumulatedCommission(ctx, val.GetOperator()) currentCommission.Commission = currentCommission.Commission.Add(commission...) k.SetValidatorAccumulatedCommission(ctx, val.GetOperator(), currentCommission) // update current rewards currentRewards := k.GetValidatorCurrentRewards(ctx, val.GetOperator()) currentRewards.Rewards = currentRewards.Rewards.Add(shared...) k.SetValidatorCurrentRewards(ctx, val.GetOperator(), currentRewards) // update outstanding rewards ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeRewards, sdk.NewAttribute(sdk.AttributeKeyAmount, tokens.String()), sdk.NewAttribute(types.AttributeKeyValidator, val.GetOperator().String()), ), ) outstanding := k.GetValidatorOutstandingRewards(ctx, val.GetOperator()) outstanding.Rewards = outstanding.Rewards.Add(tokens...) k.SetValidatorOutstandingRewards(ctx, val.GetOperator(), outstanding) }