package staking
import (
"context"
"strconv"
"time"
"github.com/armon/go-metrics"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
"google.golang.org/grpc/codes"
cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/telemetry"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper"
"github.com/cosmos/cosmos-sdk/x/staking/keeper"
"github.com/cosmos/cosmos-sdk/x/staking/types"
"google.golang.org/grpc/status"
ctypes "github.com/cybercongress/go-cyber/v7/types"
resourcestypes "github.com/cybercongress/go-cyber/v7/x/resources/types"
)
type msgServer struct {
*keeper.Keeper
bk bankkeeper.Keeper
}
func NewMsgServerImpl(keeper *keeper.Keeper, bk bankkeeper.Keeper) types.MsgServer {
return &msgServer{
Keeper: keeper,
bk: bk,
}
}
var _ types.MsgServer = msgServer{}
func (k msgServer) CreateValidator(goCtx context.Context, msg *types.MsgCreateValidator) (*types.MsgCreateValidatorResponse, error) {
result, err := WrapCreateValidator(goCtx, k.bk, msg)
if err != nil {
return nil, err
}
if !result {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "unauthorized message: %T", msg)
}
ctx := sdk.UnwrapSDKContext(goCtx)
valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
}
if msg.Commission.Rate.LT(k.MinCommissionRate(ctx)) {
return nil, sdkerrors.Wrapf(types.ErrCommissionLTMinRate, "cannot set validator commission to less than minimum rate of %s", k.MinCommissionRate(ctx))
}
if _, found := k.GetValidator(ctx, valAddr); found {
return nil, types.ErrValidatorOwnerExists
}
pk, ok := msg.Pubkey.GetCachedValue().(cryptotypes.PubKey)
if !ok {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "Expecting cryptotypes.PubKey, got %T", pk)
}
if _, found := k.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)); found {
return nil, types.ErrValidatorPubKeyExists
}
bondDenom := k.BondDenom(ctx)
if msg.Value.Denom != bondDenom {
return nil, sdkerrors.Wrapf(
sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Value.Denom, bondDenom,
)
}
if _, err := msg.Description.EnsureLength(); err != nil {
return nil, err
}
cp := ctx.ConsensusParams()
if cp != nil && cp.Validator != nil {
pkType := pk.Type()
hasKeyType := false
for _, keyType := range cp.Validator.PubKeyTypes {
if pkType == keyType {
hasKeyType = true
break
}
}
if !hasKeyType {
return nil, sdkerrors.Wrapf(
types.ErrValidatorPubKeyTypeNotSupported,
"got: %s, expected: %s", pk.Type(), cp.Validator.PubKeyTypes,
)
}
}
validator, err := types.NewValidator(valAddr, pk, msg.Description)
if err != nil {
return nil, err
}
commission := types.NewCommissionWithTime(
msg.Commission.Rate, msg.Commission.MaxRate,
msg.Commission.MaxChangeRate, ctx.BlockHeader().Time,
)
validator, err = validator.SetInitialCommission(commission)
if err != nil {
return nil, err
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
validator.MinSelfDelegation = msg.MinSelfDelegation
k.SetValidator(ctx, validator)
k.SetValidatorByConsAddr(ctx, validator)
k.SetNewValidatorByPowerIndex(ctx, validator)
if err := k.Hooks().AfterValidatorCreated(ctx, validator.GetOperator()); err != nil {
return nil, err
}
_, err = k.Keeper.Delegate(ctx, delegatorAddress, msg.Value.Amount, types.Unbonded, validator, true)
if err != nil {
return nil, err
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeCreateValidator,
sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Value.String()),
),
})
return &types.MsgCreateValidatorResponse{}, nil
}
func (k msgServer) EditValidator(goCtx context.Context, msg *types.MsgEditValidator) (*types.MsgEditValidatorResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
}
validator, found := k.GetValidator(ctx, valAddr)
if !found {
return nil, types.ErrNoValidatorFound
}
description, err := validator.Description.UpdateDescription(msg.Description)
if err != nil {
return nil, err
}
validator.Description = description
if msg.CommissionRate != nil {
commission, err := k.UpdateValidatorCommission(ctx, validator, *msg.CommissionRate)
if err != nil {
return nil, err
}
if err := k.Hooks().BeforeValidatorModified(ctx, valAddr); err != nil {
return nil, err
}
validator.Commission = commission
}
if msg.MinSelfDelegation != nil {
if !msg.MinSelfDelegation.GT(validator.MinSelfDelegation) {
return nil, types.ErrMinSelfDelegationDecreased
}
if msg.MinSelfDelegation.GT(validator.Tokens) {
return nil, types.ErrSelfDelegationBelowMinimum
}
validator.MinSelfDelegation = *msg.MinSelfDelegation
}
k.SetValidator(ctx, validator)
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeEditValidator,
sdk.NewAttribute(types.AttributeKeyCommissionRate, validator.Commission.String()),
sdk.NewAttribute(types.AttributeKeyMinSelfDelegation, validator.MinSelfDelegation.String()),
),
})
return &types.MsgEditValidatorResponse{}, nil
}
func (k msgServer) Delegate(goCtx context.Context, msg *types.MsgDelegate) (*types.MsgDelegateResponse, error) {
result, err := WrapDelegate(goCtx, k.bk, msg)
if err != nil {
return nil, err
}
if !result {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "unauthorized message: %T", msg)
}
ctx := sdk.UnwrapSDKContext(goCtx)
valAddr, valErr := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if valErr != nil {
return nil, valErr
}
validator, found := k.GetValidator(ctx, valAddr)
if !found {
return nil, types.ErrNoValidatorFound
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
bondDenom := k.BondDenom(ctx)
if msg.Amount.Denom != bondDenom {
return nil, sdkerrors.Wrapf(
sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom,
)
}
newShares, err := k.Keeper.Delegate(ctx, delegatorAddress, msg.Amount.Amount, types.Unbonded, validator, true)
if err != nil {
return nil, err
}
if msg.Amount.Amount.IsInt64() {
defer func() {
telemetry.IncrCounter(1, types.ModuleName, "delegate")
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", msg.Type()},
float32(msg.Amount.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
)
}()
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeDelegate,
sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
sdk.NewAttribute(types.AttributeKeyDelegator, msg.DelegatorAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()),
sdk.NewAttribute(types.AttributeKeyNewShares, newShares.String()),
),
})
return &types.MsgDelegateResponse{}, nil
}
func (k msgServer) BeginRedelegate(goCtx context.Context, msg *types.MsgBeginRedelegate) (*types.MsgBeginRedelegateResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddress)
if err != nil {
return nil, err
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
shares, err := k.ValidateUnbondAmount(
ctx, delegatorAddress, valSrcAddr, msg.Amount.Amount,
)
if err != nil {
return nil, err
}
bondDenom := k.BondDenom(ctx)
if msg.Amount.Denom != bondDenom {
return nil, sdkerrors.Wrapf(
sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom,
)
}
valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddress)
if err != nil {
return nil, err
}
completionTime, err := k.BeginRedelegation(
ctx, delegatorAddress, valSrcAddr, valDstAddr, shares,
)
if err != nil {
return nil, err
}
if msg.Amount.Amount.IsInt64() {
defer func() {
telemetry.IncrCounter(1, types.ModuleName, "redelegate")
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", msg.Type()},
float32(msg.Amount.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
)
}()
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeRedelegate,
sdk.NewAttribute(types.AttributeKeySrcValidator, msg.ValidatorSrcAddress),
sdk.NewAttribute(types.AttributeKeyDstValidator, msg.ValidatorDstAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()),
sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
),
})
return &types.MsgBeginRedelegateResponse{
CompletionTime: completionTime,
}, nil
}
func (k msgServer) Undelegate(goCtx context.Context, msg *types.MsgUndelegate) (*types.MsgUndelegateResponse, error) {
result, err := WrapUndelegate(goCtx, k.bk, msg)
if err != nil {
return nil, err
}
if !result {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "unauthorized message: %T", msg)
}
ctx := sdk.UnwrapSDKContext(goCtx)
addr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
shares, err := k.ValidateUnbondAmount(
ctx, delegatorAddress, addr, msg.Amount.Amount,
)
if err != nil {
return nil, err
}
bondDenom := k.BondDenom(ctx)
if msg.Amount.Denom != bondDenom {
return nil, sdkerrors.Wrapf(
sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom,
)
}
completionTime, err := k.Keeper.Undelegate(ctx, delegatorAddress, addr, shares)
if err != nil {
return nil, err
}
if msg.Amount.Amount.IsInt64() {
defer func() {
telemetry.IncrCounter(1, types.ModuleName, "undelegate")
telemetry.SetGaugeWithLabels(
[]string{"tx", "msg", msg.Type()},
float32(msg.Amount.Amount.Int64()),
[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
)
}()
}
ctx.EventManager().EmitEvents(sdk.Events{
sdk.NewEvent(
types.EventTypeUnbond,
sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()),
sdk.NewAttribute(types.AttributeKeyDelegator, msg.DelegatorAddress),
sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
),
})
return &types.MsgUndelegateResponse{
CompletionTime: completionTime,
}, nil
}
func (k msgServer) CancelUnbondingDelegation(goCtx context.Context, msg *types.MsgCancelUnbondingDelegation) (*types.MsgCancelUnbondingDelegationResponse, error) {
result, err := WrapCancelUnbondingDelegation(goCtx, k.bk, msg)
if err != nil {
return nil, err
}
if !result {
return nil, sdkerrors.Wrapf(sdkerrors.ErrUnauthorized, "unauthorized message: %T", msg)
}
ctx := sdk.UnwrapSDKContext(goCtx)
valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
if err != nil {
return nil, err
}
delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return nil, err
}
bondDenom := k.BondDenom(ctx)
if msg.Amount.Denom != bondDenom {
return nil, sdkerrors.Wrapf(
sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom,
)
}
validator, found := k.GetValidator(ctx, valAddr)
if !found {
return nil, types.ErrNoValidatorFound
}
if validator.InvalidExRate() {
return nil, types.ErrDelegatorShareExRateInvalid
}
if validator.IsJailed() {
return nil, types.ErrValidatorJailed
}
ubd, found := k.GetUnbondingDelegation(ctx, delegatorAddress, valAddr)
if !found {
return nil, status.Errorf(
codes.NotFound,
"unbonding delegation with delegator %s not found for validator %s",
msg.DelegatorAddress, msg.ValidatorAddress,
)
}
var (
unbondEntry types.UnbondingDelegationEntry
unbondEntryIndex int64 = -1
)
for i, entry := range ubd.Entries {
if entry.CreationHeight == msg.CreationHeight {
unbondEntry = entry
unbondEntryIndex = int64(i)
break
}
}
if unbondEntryIndex == -1 {
return nil, sdkerrors.ErrNotFound.Wrapf("unbonding delegation entry is not found at block height %d", msg.CreationHeight)
}
if unbondEntry.Balance.LT(msg.Amount.Amount) {
return nil, sdkerrors.ErrInvalidRequest.Wrap("amount is greater than the unbonding delegation entry balance")
}
if unbondEntry.CompletionTime.Before(ctx.BlockTime()) {
return nil, sdkerrors.ErrInvalidRequest.Wrap("unbonding delegation is already processed")
}
_, err = k.Keeper.Delegate(ctx, delegatorAddress, msg.Amount.Amount, types.Unbonding, validator, false)
if err != nil {
return nil, err
}
amount := unbondEntry.Balance.Sub(msg.Amount.Amount)
if amount.IsZero() {
ubd.RemoveEntry(unbondEntryIndex)
} else {
unbondEntry.Balance = amount
unbondEntry.InitialBalance = unbondEntry.InitialBalance.Sub(msg.Amount.Amount)
ubd.Entries[unbondEntryIndex] = unbondEntry
}
if len(ubd.Entries) == 0 {
k.RemoveUnbondingDelegation(ctx, ubd)
} else {
k.SetUnbondingDelegation(ctx, ubd)
}
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeCancelUnbondingDelegation,
sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()),
sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
sdk.NewAttribute(types.AttributeKeyDelegator, msg.DelegatorAddress),
sdk.NewAttribute(types.AttributeKeyCreationHeight, strconv.FormatInt(msg.CreationHeight, 10)),
),
)
return &types.MsgCancelUnbondingDelegationResponse{}, nil
}
func (ms msgServer) UpdateParams(goCtx context.Context, msg *types.MsgUpdateParams) (*types.MsgUpdateParamsResponse, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
if ms.GetAuthority() != msg.Authority {
return nil, sdkerrors.Wrapf(govtypes.ErrInvalidSigner, "invalid authority; expected %s, got %s", ms.GetAuthority(), msg.Authority)
}
if err := ms.SetParams(ctx, msg.Params); err != nil {
return nil, err
}
return &types.MsgUpdateParamsResponse{}, nil
}
func WrapDelegate(
goCtx context.Context,
bk bankkeeper.Keeper,
msg *types.MsgDelegate,
) (bool, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
delegator, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return false, err
}
toMint := sdk.NewCoin(ctypes.SCYB, msg.Amount.Amount)
err = bk.MintCoins(ctx, resourcestypes.ResourcesName, sdk.NewCoins(toMint))
if err != nil {
return false, err
}
err = bk.SendCoinsFromModuleToAccount(ctx, resourcestypes.ResourcesName, delegator, sdk.NewCoins(toMint))
if err != nil {
return false, err
}
return true, nil
}
func WrapUndelegate(
goCtx context.Context,
bk bankkeeper.Keeper,
msg *types.MsgUndelegate,
) (bool, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
delegator, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return false, err
}
toBurn := sdk.NewCoin(ctypes.SCYB, msg.Amount.Amount)
err = bk.SendCoinsFromAccountToModule(ctx, delegator, resourcestypes.ResourcesName, sdk.NewCoins(toBurn))
if err != nil {
return false, err
}
err = bk.BurnCoins(ctx, resourcestypes.ResourcesName, sdk.NewCoins(toBurn))
if err != nil {
return false, err
}
return true, nil
}
func WrapCreateValidator(
goCtx context.Context,
bk bankkeeper.Keeper,
msg *types.MsgCreateValidator,
) (bool, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
delegator, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return false, err
}
toMint := sdk.NewCoin(ctypes.SCYB, msg.Value.Amount)
err = bk.MintCoins(ctx, resourcestypes.ResourcesName, sdk.NewCoins(toMint))
if err != nil {
return false, err
}
err = bk.SendCoinsFromModuleToAccount(ctx, resourcestypes.ResourcesName, delegator, sdk.NewCoins(toMint))
if err != nil {
return false, err
}
return true, nil
}
func WrapCancelUnbondingDelegation(
goCtx context.Context,
bk bankkeeper.Keeper,
msg *types.MsgCancelUnbondingDelegation,
) (bool, error) {
ctx := sdk.UnwrapSDKContext(goCtx)
delegator, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
if err != nil {
return false, err
}
toMint := sdk.NewCoin(ctypes.SCYB, msg.Amount.Amount)
err = bk.MintCoins(ctx, resourcestypes.ResourcesName, sdk.NewCoins(toMint))
if err != nil {
return false, err
}
err = bk.SendCoinsFromModuleToAccount(ctx, resourcestypes.ResourcesName, delegator, sdk.NewCoins(toMint))
if err != nil {
return false, err
}
return true, nil
}