package keeper import ( "fmt" "github.com/tendermint/tendermint/libs/log" "github.com/cosmos/cosmos-sdk/codec" storetypes "github.com/cosmos/cosmos-sdk/store/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" "github.com/cosmos/cosmos-sdk/x/distribution/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" ) // Keeper of the distribution store type Keeper struct { storeKey storetypes.StoreKey cdc codec.BinaryCodec paramSpace paramtypes.Subspace authKeeper types.AccountKeeper bankKeeper types.BankKeeper stakingKeeper types.StakingKeeper feeCollectorName string // name of the FeeCollector ModuleAccount } // NewKeeper creates a new distribution Keeper instance func NewKeeper( cdc codec.BinaryCodec, key storetypes.StoreKey, paramSpace paramtypes.Subspace, ak types.AccountKeeper, bk types.BankKeeper, sk types.StakingKeeper, feeCollectorName string, ) Keeper { // ensure distribution module account is set if addr := ak.GetModuleAddress(types.ModuleName); addr == nil { panic(fmt.Sprintf("%s module account has not been set", types.ModuleName)) } // set KeyTable if it has not already been set if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) } return Keeper{ storeKey: key, cdc: cdc, paramSpace: paramSpace, authKeeper: ak, bankKeeper: bk, stakingKeeper: sk, feeCollectorName: feeCollectorName, } } // Logger returns a module-specific logger. func (k Keeper) Logger(ctx sdk.Context) log.Logger { return ctx.Logger().With("module", "x/"+types.ModuleName) } // SetWithdrawAddr sets a new address that will receive the rewards upon withdrawal func (k Keeper) SetWithdrawAddr(ctx sdk.Context, delegatorAddr sdk.AccAddress, withdrawAddr sdk.AccAddress) error { if k.bankKeeper.BlockedAddr(withdrawAddr) { return sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive external funds", withdrawAddr) } if !k.GetWithdrawAddrEnabled(ctx) { return types.ErrSetWithdrawAddrDisabled } ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeSetWithdrawAddress, sdk.NewAttribute(types.AttributeKeyWithdrawAddress, withdrawAddr.String()), ), ) k.SetDelegatorWithdrawAddr(ctx, delegatorAddr, withdrawAddr) return nil } // withdraw rewards from a delegation func (k Keeper) WithdrawDelegationRewards(ctx sdk.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) { val := k.stakingKeeper.Validator(ctx, valAddr) if val == nil { return nil, types.ErrNoValidatorDistInfo } del := k.stakingKeeper.Delegation(ctx, delAddr, valAddr) if del == nil { return nil, types.ErrEmptyDelegationDistInfo } // withdraw rewards rewards, err := k.withdrawDelegationRewards(ctx, val, del) if err != nil { return nil, err } if rewards.IsZero() { baseDenom, _ := sdk.GetBaseDenom() rewards = sdk.Coins{sdk.Coin{ Denom: baseDenom, Amount: sdk.ZeroInt(), }} } ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeWithdrawRewards, sdk.NewAttribute(sdk.AttributeKeyAmount, rewards.String()), sdk.NewAttribute(types.AttributeKeyValidator, valAddr.String()), ), ) // reinitialize the delegation k.initializeDelegation(ctx, valAddr, delAddr) return rewards, nil } // withdraw validator commission func (k Keeper) WithdrawValidatorCommission(ctx sdk.Context, valAddr sdk.ValAddress) (sdk.Coins, error) { // fetch validator accumulated commission accumCommission := k.GetValidatorAccumulatedCommission(ctx, valAddr) if accumCommission.Commission.IsZero() { return nil, types.ErrNoValidatorCommission } commission, remainder := accumCommission.Commission.TruncateDecimal() k.SetValidatorAccumulatedCommission(ctx, valAddr, types.ValidatorAccumulatedCommission{Commission: remainder}) // leave remainder to withdraw later // update outstanding outstanding := k.GetValidatorOutstandingRewards(ctx, valAddr).Rewards k.SetValidatorOutstandingRewards(ctx, valAddr, types.ValidatorOutstandingRewards{Rewards: outstanding.Sub(sdk.NewDecCoinsFromCoins(commission...))}) if !commission.IsZero() { accAddr := sdk.AccAddress(valAddr) withdrawAddr := k.GetDelegatorWithdrawAddr(ctx, accAddr) err := k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, withdrawAddr, commission) if err != nil { return nil, err } } ctx.EventManager().EmitEvent( sdk.NewEvent( types.EventTypeWithdrawCommission, sdk.NewAttribute(sdk.AttributeKeyAmount, commission.String()), ), ) return commission, nil } // GetTotalRewards returns the total amount of fee distribution rewards held in the store func (k Keeper) GetTotalRewards(ctx sdk.Context) (totalRewards sdk.DecCoins) { k.IterateValidatorOutstandingRewards(ctx, func(_ sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool) { totalRewards = totalRewards.Add(rewards.Rewards...) return false }, ) return totalRewards } // FundCommunityPool allows an account to directly fund the community fund pool. // The amount is first added to the distribution module account and then directly // added to the pool. An error is returned if the amount cannot be sent to the // module account. func (k Keeper) FundCommunityPool(ctx sdk.Context, amount sdk.Coins, sender sdk.AccAddress) error { if err := k.bankKeeper.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, amount); err != nil { return err } feePool := k.GetFeePool(ctx) feePool.CommunityPool = feePool.CommunityPool.Add(sdk.NewDecCoinsFromCoins(amount...)...) k.SetFeePool(ctx, feePool) return nil }