cosmos-sdk/x/distribution/keeper/keeper.go
mergify[bot] 072a38d556
feat(x/protocolpool,x/distribution)!: remove dependency + fix continuous fund bug (backport #20790) (#21060)
Co-authored-by: Facundo Medica <14063057+facundomedica@users.noreply.github.com>
2024-07-24 18:19:12 +02:00

280 lines
9.6 KiB
Go

package keeper
import (
"context"
"errors"
"fmt"
"cosmossdk.io/collections"
collcodec "cosmossdk.io/collections/codec"
"cosmossdk.io/core/appmodule"
"cosmossdk.io/core/comet"
"cosmossdk.io/core/event"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/x/distribution/types"
"github.com/cosmos/cosmos-sdk/codec"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
)
// Keeper of the distribution store
type Keeper struct {
appmodule.Environment
cometService comet.Service
cdc codec.BinaryCodec
authKeeper types.AccountKeeper
bankKeeper types.BankKeeper
stakingKeeper types.StakingKeeper
// the address capable of executing a MsgUpdateParams message. Typically, this
// should be the x/gov module account.
authority string
Schema collections.Schema
Params collections.Item[types.Params]
// FeePool stores decimal tokens that cannot be yet distributed.
// In the past it held the community pool, but it has been replaced by x/protocolpool.
FeePool collections.Item[types.FeePool]
// DelegatorsWithdrawAddress key: delAddr | value: withdrawAddr
DelegatorsWithdrawAddress collections.Map[sdk.AccAddress, sdk.AccAddress]
// ValidatorCurrentRewards key: valAddr | value: ValidatorCurrentRewards
ValidatorCurrentRewards collections.Map[sdk.ValAddress, types.ValidatorCurrentRewards]
// DelegatorStartingInfo key: valAddr+delAccAddr | value: DelegatorStartingInfo
DelegatorStartingInfo collections.Map[collections.Pair[sdk.ValAddress, sdk.AccAddress], types.DelegatorStartingInfo]
// ValidatorsAccumulatedCommission key: valAddr | value: ValidatorAccumulatedCommission
ValidatorsAccumulatedCommission collections.Map[sdk.ValAddress, types.ValidatorAccumulatedCommission]
// ValidatorOutstandingRewards key: valAddr | value: ValidatorOustandingRewards
ValidatorOutstandingRewards collections.Map[sdk.ValAddress, types.ValidatorOutstandingRewards]
// ValidatorHistoricalRewards key: valAddr+period | value: ValidatorHistoricalRewards
ValidatorHistoricalRewards collections.Map[collections.Pair[sdk.ValAddress, uint64], types.ValidatorHistoricalRewards]
// ValidatorSlashEvents key: valAddr+height+period | value: ValidatorSlashEvent
ValidatorSlashEvents collections.Map[collections.Triple[sdk.ValAddress, uint64, uint64], types.ValidatorSlashEvent]
feeCollectorName string // name of the FeeCollector ModuleAccount
}
// NewKeeper creates a new distribution Keeper instance
func NewKeeper(
cdc codec.BinaryCodec,
env appmodule.Environment,
ak types.AccountKeeper,
bk types.BankKeeper,
sk types.StakingKeeper,
cometService comet.Service,
feeCollectorName, authority 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))
}
sb := collections.NewSchemaBuilder(env.KVStoreService)
k := Keeper{
Environment: env,
cometService: cometService,
cdc: cdc,
authKeeper: ak,
bankKeeper: bk,
stakingKeeper: sk,
feeCollectorName: feeCollectorName,
authority: authority,
Params: collections.NewItem(sb, types.ParamsKey, "params", codec.CollValue[types.Params](cdc)),
FeePool: collections.NewItem(sb, types.FeePoolKey, "fee_pool", codec.CollValue[types.FeePool](cdc)),
DelegatorsWithdrawAddress: collections.NewMap(
sb,
types.DelegatorWithdrawAddrPrefix,
"delegators_withdraw_address",
sdk.LengthPrefixedAddressKey(sdk.AccAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility
collcodec.KeyToValueCodec(sdk.AccAddressKey),
),
ValidatorCurrentRewards: collections.NewMap(
sb,
types.ValidatorCurrentRewardsPrefix,
"validators_current_rewards",
sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility
codec.CollValue[types.ValidatorCurrentRewards](cdc),
),
DelegatorStartingInfo: collections.NewMap(
sb,
types.DelegatorStartingInfoPrefix,
"delegators_starting_info",
collections.PairKeyCodec(sdk.ValAddressKey, sdk.LengthPrefixedAddressKey(sdk.AccAddressKey)), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility
codec.CollValue[types.DelegatorStartingInfo](cdc),
),
ValidatorsAccumulatedCommission: collections.NewMap(
sb,
types.ValidatorAccumulatedCommissionPrefix,
"validators_accumulated_commission",
sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility
codec.CollValue[types.ValidatorAccumulatedCommission](cdc),
),
ValidatorOutstandingRewards: collections.NewMap(
sb,
types.ValidatorOutstandingRewardsPrefix,
"validator_outstanding_rewards",
sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility
codec.CollValue[types.ValidatorOutstandingRewards](cdc),
),
ValidatorHistoricalRewards: collections.NewMap(
sb,
types.ValidatorHistoricalRewardsPrefix,
"validator_historical_rewards",
collections.PairKeyCodec(sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), sdk.LEUint64Key), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility
codec.CollValue[types.ValidatorHistoricalRewards](cdc),
),
ValidatorSlashEvents: collections.NewMap(
sb,
types.ValidatorSlashEventPrefix,
"validator_slash_events",
collections.TripleKeyCodec(sdk.LengthPrefixedAddressKey(sdk.ValAddressKey), collections.Uint64Key, collections.Uint64Key), //nolint: staticcheck // sdk.LengthPrefixedAddressKey is needed to retain state compatibility
codec.CollValue[types.ValidatorSlashEvent](cdc),
),
}
schema, err := sb.Build()
if err != nil {
panic(err)
}
k.Schema = schema
return k
}
// GetAuthority returns the x/distribution module's authority.
func (k Keeper) GetAuthority() string {
return k.authority
}
// SetWithdrawAddr sets a new address that will receive the rewards upon withdrawal
func (k Keeper) SetWithdrawAddr(ctx context.Context, delegatorAddr, withdrawAddr sdk.AccAddress) error {
if k.bankKeeper.BlockedAddr(withdrawAddr) {
return errorsmod.Wrapf(sdkerrors.ErrUnauthorized, "%s is not allowed to receive external funds", withdrawAddr)
}
withdrawAddrEnabled, err := k.GetWithdrawAddrEnabled(ctx)
if err != nil {
return err
}
if !withdrawAddrEnabled {
return types.ErrSetWithdrawAddrDisabled
}
addr, err := k.authKeeper.AddressCodec().BytesToString(withdrawAddr)
if err != nil {
return err
}
if err = k.EventService.EventManager(ctx).EmitKV(
types.EventTypeSetWithdrawAddress,
event.NewAttribute(types.AttributeKeyWithdrawAddress, addr),
); err != nil {
return err
}
return k.DelegatorsWithdrawAddress.Set(ctx, delegatorAddr, withdrawAddr)
}
// withdraw rewards from a delegation
func (k Keeper) WithdrawDelegationRewards(ctx context.Context, delAddr sdk.AccAddress, valAddr sdk.ValAddress) (sdk.Coins, error) {
val, err := k.stakingKeeper.Validator(ctx, valAddr)
if err != nil {
return nil, err
}
if val == nil {
return nil, types.ErrNoValidatorDistInfo
}
del, err := k.stakingKeeper.Delegation(ctx, delAddr, valAddr)
if err != nil {
return nil, err
}
if del == nil {
return nil, types.ErrEmptyDelegationDistInfo
}
// withdraw rewards
rewards, err := k.withdrawDelegationRewards(ctx, val, del)
if err != nil {
return nil, err
}
// reinitialize the delegation
err = k.initializeDelegation(ctx, valAddr, delAddr)
if err != nil {
return nil, err
}
return rewards, nil
}
// withdraw validator commission
func (k Keeper) WithdrawValidatorCommission(ctx context.Context, valAddr sdk.ValAddress) (sdk.Coins, error) {
// fetch validator accumulated commission
accumCommission, err := k.ValidatorsAccumulatedCommission.Get(ctx, valAddr)
if err != nil && !errors.Is(err, collections.ErrNotFound) {
return nil, err
}
if accumCommission.Commission.IsZero() {
return nil, types.ErrNoValidatorCommission
}
commission, remainder := accumCommission.Commission.TruncateDecimal()
err = k.ValidatorsAccumulatedCommission.Set(ctx, valAddr, types.ValidatorAccumulatedCommission{Commission: remainder}) // leave remainder to withdraw later
if err != nil {
return nil, err
}
// update outstanding
outstanding, err := k.ValidatorOutstandingRewards.Get(ctx, valAddr)
if err != nil {
return nil, err
}
err = k.ValidatorOutstandingRewards.Set(ctx, valAddr, types.ValidatorOutstandingRewards{Rewards: outstanding.Rewards.Sub(sdk.NewDecCoinsFromCoins(commission...))})
if err != nil {
return nil, err
}
if !commission.IsZero() {
accAddr := sdk.AccAddress(valAddr)
withdrawAddr, err := k.GetDelegatorWithdrawAddr(ctx, accAddr)
if err != nil {
return nil, err
}
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, withdrawAddr, commission)
if err != nil {
return nil, err
}
}
err = k.EventService.EventManager(ctx).EmitKV(
types.EventTypeWithdrawCommission,
event.NewAttribute(sdk.AttributeKeyAmount, commission.String()),
)
if err != nil {
return nil, err
}
return commission, nil
}
// GetTotalRewards returns the total amount of fee distribution rewards held in the store
func (k Keeper) GetTotalRewards(ctx context.Context) (totalRewards sdk.DecCoins) {
err := k.ValidatorOutstandingRewards.Walk(ctx, nil, func(_ sdk.ValAddress, rewards types.ValidatorOutstandingRewards) (stop bool, err error) {
totalRewards = totalRewards.Add(rewards.Rewards...)
return false, nil
},
)
if err != nil {
panic(err)
}
return totalRewards
}