package keeper import ( "context" abci "git.cw.tr/mukan-network/mukan-consensus/abci/types" "cosmossdk.io/math" sdk "git.cw.tr/mukan-network/mukan-sdk/types" "git.cw.tr/mukan-network/mukan-sdk/x/distribution/types" stakingtypes "git.cw.tr/mukan-network/mukan-sdk/x/staking/types" ) // AllocateTokens performs reward and fee distribution to all validators based // on the F1 fee distribution specification. func (k Keeper) AllocateTokens(ctx context.Context, totalPreviousPower int64, bondedVotes []abci.VoteInfo) error { // 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 { return err } // temporary workaround to keep CanWithdrawInvariant happy // general discussions here: https://git.cw.tr/mukan-network/mukan-sdk/issues/2906#issuecomment-441867634 feePool, err := k.FeePool.Get(ctx) if err != nil { return err } if totalPreviousPower == 0 { feePool.CommunityPool = feePool.CommunityPool.Add(feesCollected...) return k.FeePool.Set(ctx, feePool) } // calculate fraction allocated to validators remaining := feesCollected communityTax, err := k.GetCommunityTax(ctx) if err != nil { return err } voteMultiplier := math.LegacyOneDec().Sub(communityTax) feeMultiplier := feesCollected.MulDecTruncate(voteMultiplier) // allocate tokens proportionally to voting power // // TODO: Consider parallelizing later // // Ref: https://git.cw.tr/mukan-network/mukan-sdk/pull/3099#discussion_r246276376 for _, vote := range bondedVotes { validator, err := k.stakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address) if err != nil { return err } // TODO: Consider micro-slashing for missing votes. // // Ref: https://git.cw.tr/mukan-network/mukan-sdk/issues/2525#issuecomment-430838701 powerFraction := math.LegacyNewDec(vote.Validator.Power).QuoTruncate(math.LegacyNewDec(totalPreviousPower)) reward := feeMultiplier.MulDecTruncate(powerFraction) err = k.AllocateTokensToValidator(ctx, validator, reward) if err != nil { return err } remaining = remaining.Sub(reward) } // allocate community funding feePool.CommunityPool = feePool.CommunityPool.Add(remaining...) return k.FeePool.Set(ctx, feePool) } // AllocateTokensToValidator allocate tokens to a particular validator, // splitting according to commission. func (k Keeper) AllocateTokensToValidator(ctx context.Context, val stakingtypes.ValidatorI, tokens sdk.DecCoins) error { // split tokens between validator and delegators according to commission commission := tokens.MulDec(val.GetCommission()) shared := tokens.Sub(commission) valBz, err := k.stakingKeeper.ValidatorAddressCodec().StringToBytes(val.GetOperator()) if err != nil { return err } // update current commission sdkCtx := sdk.UnwrapSDKContext(ctx) sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeCommission, sdk.NewAttribute(sdk.AttributeKeyAmount, commission.String()), sdk.NewAttribute(types.AttributeKeyValidator, val.GetOperator()), ), ) currentCommission, err := k.GetValidatorAccumulatedCommission(ctx, valBz) if err != nil { return err } currentCommission.Commission = currentCommission.Commission.Add(commission...) err = k.SetValidatorAccumulatedCommission(ctx, valBz, currentCommission) if err != nil { return err } // update current rewards currentRewards, err := k.GetValidatorCurrentRewards(ctx, valBz) if err != nil { return err } currentRewards.Rewards = currentRewards.Rewards.Add(shared...) err = k.SetValidatorCurrentRewards(ctx, valBz, currentRewards) if err != nil { return err } // update outstanding rewards sdkCtx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeRewards, sdk.NewAttribute(sdk.AttributeKeyAmount, tokens.String()), sdk.NewAttribute(types.AttributeKeyValidator, val.GetOperator()), ), ) outstanding, err := k.GetValidatorOutstandingRewards(ctx, valBz) if err != nil { return err } outstanding.Rewards = outstanding.Rewards.Add(tokens...) return k.SetValidatorOutstandingRewards(ctx, valBz, outstanding) } // sendCommunityPoolToExternalPool does the following: // // truncate the community pool value (DecCoins) to sdk.Coins // distribute from the distribution module account to the external community pool account // update the bookkept value in x/distribution func (k Keeper) sendCommunityPoolToExternalPool(ctx sdk.Context) error { feePool, err := k.FeePool.Get(ctx) if err != nil { return err } if feePool.CommunityPool.IsZero() { return nil } amt, remaining := feePool.CommunityPool.TruncateDecimal() ctx.Logger().Debug( "sending distribution community pool amount to external pool pool", "pool", k.externalCommunityPool.GetCommunityPoolModule(), "amount", amt.String(), "remaining", remaining.String(), ) if err := k.bankKeeper.SendCoinsFromModuleToModule(ctx, types.ModuleName, k.externalCommunityPool.GetCommunityPoolModule(), amt); err != nil { return err } return k.FeePool.Set(ctx, types.FeePool{CommunityPool: remaining}) }