github.com/Finschia/finschia-sdk@v0.48.1/x/staking/keeper/msg_server.go (about)

     1  package keeper
     2  
     3  import (
     4  	"context"
     5  	"time"
     6  
     7  	oststrings "github.com/Finschia/ostracon/libs/strings"
     8  	metrics "github.com/armon/go-metrics"
     9  
    10  	cryptotypes "github.com/Finschia/finschia-sdk/crypto/types"
    11  	"github.com/Finschia/finschia-sdk/telemetry"
    12  	sdk "github.com/Finschia/finschia-sdk/types"
    13  	sdkerrors "github.com/Finschia/finschia-sdk/types/errors"
    14  	"github.com/Finschia/finschia-sdk/x/staking/types"
    15  )
    16  
    17  type msgServer struct {
    18  	Keeper
    19  }
    20  
    21  // NewMsgServerImpl returns an implementation of the bank MsgServer interface
    22  // for the provided Keeper.
    23  func NewMsgServerImpl(keeper Keeper) types.MsgServer {
    24  	return &msgServer{Keeper: keeper}
    25  }
    26  
    27  var _ types.MsgServer = msgServer{}
    28  
    29  // CreateValidator defines a method for creating a new validator
    30  func (k msgServer) CreateValidator(goCtx context.Context, msg *types.MsgCreateValidator) (*types.MsgCreateValidatorResponse, error) {
    31  	ctx := sdk.UnwrapSDKContext(goCtx)
    32  
    33  	valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
    34  	if err != nil {
    35  		return nil, err
    36  	}
    37  
    38  	// check to see if the pubkey or sender has been registered before
    39  	if _, found := k.GetValidator(ctx, valAddr); found {
    40  		return nil, types.ErrValidatorOwnerExists
    41  	}
    42  
    43  	pk, ok := msg.Pubkey.GetCachedValue().(cryptotypes.PubKey)
    44  	if !ok {
    45  		return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidType, "Expecting cryptotypes.PubKey, got %T", pk)
    46  	}
    47  
    48  	if _, found := k.GetValidatorByConsAddr(ctx, sdk.GetConsAddress(pk)); found {
    49  		return nil, types.ErrValidatorPubKeyExists
    50  	}
    51  
    52  	bondDenom := k.BondDenom(ctx)
    53  	if msg.Value.Denom != bondDenom {
    54  		return nil, sdkerrors.Wrapf(
    55  			sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Value.Denom, bondDenom,
    56  		)
    57  	}
    58  
    59  	if _, err := msg.Description.EnsureLength(); err != nil {
    60  		return nil, err
    61  	}
    62  
    63  	cp := ctx.ConsensusParams()
    64  	if cp != nil && cp.Validator != nil {
    65  		if !oststrings.StringInSlice(pk.Type(), cp.Validator.PubKeyTypes) {
    66  			return nil, sdkerrors.Wrapf(
    67  				types.ErrValidatorPubKeyTypeNotSupported,
    68  				"got: %s, expected: %s", pk.Type(), cp.Validator.PubKeyTypes,
    69  			)
    70  		}
    71  	}
    72  
    73  	validator, err := types.NewValidator(valAddr, pk, msg.Description)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  	commission := types.NewCommissionWithTime(
    78  		msg.Commission.Rate, msg.Commission.MaxRate,
    79  		msg.Commission.MaxChangeRate, ctx.BlockHeader().Time,
    80  	)
    81  
    82  	validator, err = validator.SetInitialCommission(commission)
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  
    87  	delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	validator.MinSelfDelegation = msg.MinSelfDelegation
    93  
    94  	k.SetValidator(ctx, validator)
    95  	if err = k.SetValidatorByConsAddr(ctx, validator); err != nil {
    96  		return nil, err
    97  	}
    98  	k.SetNewValidatorByPowerIndex(ctx, validator)
    99  
   100  	// call the after-creation hook
   101  	k.AfterValidatorCreated(ctx, validator.GetOperator())
   102  
   103  	// move coins from the msg.Address account to a (self-delegation) delegator account
   104  	// the validator account and global shares are updated within here
   105  	// NOTE source will always be from a wallet which are unbonded
   106  	_, err = k.Keeper.Delegate(ctx, delegatorAddress, msg.Value.Amount, types.Unbonded, validator, true)
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	ctx.EventManager().EmitEvents(sdk.Events{
   112  		sdk.NewEvent(
   113  			types.EventTypeCreateValidator,
   114  			sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
   115  			sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Value.String()),
   116  		),
   117  	})
   118  
   119  	return &types.MsgCreateValidatorResponse{}, nil
   120  }
   121  
   122  // EditValidator defines a method for editing an existing validator
   123  func (k msgServer) EditValidator(goCtx context.Context, msg *types.MsgEditValidator) (*types.MsgEditValidatorResponse, error) {
   124  	ctx := sdk.UnwrapSDKContext(goCtx)
   125  	valAddr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  	// validator must already be registered
   130  	validator, found := k.GetValidator(ctx, valAddr)
   131  	if !found {
   132  		return nil, types.ErrNoValidatorFound
   133  	}
   134  
   135  	// replace all editable fields (clients should autofill existing values)
   136  	description, err := validator.Description.UpdateDescription(msg.Description)
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	validator.Description = description
   142  
   143  	if msg.CommissionRate != nil {
   144  		commission, err := k.UpdateValidatorCommission(ctx, validator, *msg.CommissionRate)
   145  		if err != nil {
   146  			return nil, err
   147  		}
   148  
   149  		// call the before-modification hook since we're about to update the commission
   150  		k.BeforeValidatorModified(ctx, valAddr)
   151  
   152  		validator.Commission = commission
   153  	}
   154  
   155  	if msg.MinSelfDelegation != nil {
   156  		if !msg.MinSelfDelegation.GT(validator.MinSelfDelegation) {
   157  			return nil, types.ErrMinSelfDelegationDecreased
   158  		}
   159  
   160  		if msg.MinSelfDelegation.GT(validator.Tokens) {
   161  			return nil, types.ErrSelfDelegationBelowMinimum
   162  		}
   163  
   164  		validator.MinSelfDelegation = (*msg.MinSelfDelegation)
   165  	}
   166  
   167  	k.SetValidator(ctx, validator)
   168  
   169  	ctx.EventManager().EmitEvents(sdk.Events{
   170  		sdk.NewEvent(
   171  			types.EventTypeEditValidator,
   172  			sdk.NewAttribute(types.AttributeKeyCommissionRate, validator.Commission.String()),
   173  			sdk.NewAttribute(types.AttributeKeyMinSelfDelegation, validator.MinSelfDelegation.String()),
   174  		),
   175  	})
   176  
   177  	return &types.MsgEditValidatorResponse{}, nil
   178  }
   179  
   180  // Delegate defines a method for performing a delegation of coins from a delegator to a validator
   181  func (k msgServer) Delegate(goCtx context.Context, msg *types.MsgDelegate) (*types.MsgDelegateResponse, error) {
   182  	ctx := sdk.UnwrapSDKContext(goCtx)
   183  	valAddr, valErr := sdk.ValAddressFromBech32(msg.ValidatorAddress)
   184  	if valErr != nil {
   185  		return nil, valErr
   186  	}
   187  
   188  	validator, found := k.GetValidator(ctx, valAddr)
   189  	if !found {
   190  		return nil, types.ErrNoValidatorFound
   191  	}
   192  
   193  	delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
   194  	if err != nil {
   195  		return nil, err
   196  	}
   197  
   198  	bondDenom := k.BondDenom(ctx)
   199  	if msg.Amount.Denom != bondDenom {
   200  		return nil, sdkerrors.Wrapf(
   201  			sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom,
   202  		)
   203  	}
   204  
   205  	// NOTE: source funds are always unbonded
   206  	newShares, err := k.Keeper.Delegate(ctx, delegatorAddress, msg.Amount.Amount, types.Unbonded, validator, true)
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  
   211  	if msg.Amount.Amount.IsInt64() {
   212  		defer func() {
   213  			telemetry.IncrCounter(1, types.ModuleName, "delegate")
   214  			telemetry.SetGaugeWithLabels(
   215  				[]string{"tx", "msg", msg.Type()},
   216  				float32(msg.Amount.Amount.Int64()),
   217  				[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
   218  			)
   219  		}()
   220  	}
   221  
   222  	ctx.EventManager().EmitEvents(sdk.Events{
   223  		sdk.NewEvent(
   224  			types.EventTypeDelegate,
   225  			sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
   226  			sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()),
   227  			sdk.NewAttribute(types.AttributeKeyNewShares, newShares.String()),
   228  		),
   229  	})
   230  
   231  	return &types.MsgDelegateResponse{}, nil
   232  }
   233  
   234  // BeginRedelegate defines a method for performing a redelegation of coins from a delegator and source validator to a destination validator
   235  func (k msgServer) BeginRedelegate(goCtx context.Context, msg *types.MsgBeginRedelegate) (*types.MsgBeginRedelegateResponse, error) {
   236  	ctx := sdk.UnwrapSDKContext(goCtx)
   237  	valSrcAddr, err := sdk.ValAddressFromBech32(msg.ValidatorSrcAddress)
   238  	if err != nil {
   239  		return nil, err
   240  	}
   241  	delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
   242  	if err != nil {
   243  		return nil, err
   244  	}
   245  	shares, err := k.ValidateUnbondAmount(
   246  		ctx, delegatorAddress, valSrcAddr, msg.Amount.Amount,
   247  	)
   248  	if err != nil {
   249  		return nil, err
   250  	}
   251  
   252  	bondDenom := k.BondDenom(ctx)
   253  	if msg.Amount.Denom != bondDenom {
   254  		return nil, sdkerrors.Wrapf(
   255  			sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom,
   256  		)
   257  	}
   258  
   259  	valDstAddr, err := sdk.ValAddressFromBech32(msg.ValidatorDstAddress)
   260  	if err != nil {
   261  		return nil, err
   262  	}
   263  
   264  	completionTime, err := k.BeginRedelegation(
   265  		ctx, delegatorAddress, valSrcAddr, valDstAddr, shares,
   266  	)
   267  	if err != nil {
   268  		return nil, err
   269  	}
   270  
   271  	if msg.Amount.Amount.IsInt64() {
   272  		defer func() {
   273  			telemetry.IncrCounter(1, types.ModuleName, "redelegate")
   274  			telemetry.SetGaugeWithLabels(
   275  				[]string{"tx", "msg", msg.Type()},
   276  				float32(msg.Amount.Amount.Int64()),
   277  				[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
   278  			)
   279  		}()
   280  	}
   281  
   282  	ctx.EventManager().EmitEvents(sdk.Events{
   283  		sdk.NewEvent(
   284  			types.EventTypeRedelegate,
   285  			sdk.NewAttribute(types.AttributeKeySrcValidator, msg.ValidatorSrcAddress),
   286  			sdk.NewAttribute(types.AttributeKeyDstValidator, msg.ValidatorDstAddress),
   287  			sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()),
   288  			sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
   289  		),
   290  	})
   291  
   292  	return &types.MsgBeginRedelegateResponse{
   293  		CompletionTime: completionTime,
   294  	}, nil
   295  }
   296  
   297  // Undelegate defines a method for performing an undelegation from a delegate and a validator
   298  func (k msgServer) Undelegate(goCtx context.Context, msg *types.MsgUndelegate) (*types.MsgUndelegateResponse, error) {
   299  	ctx := sdk.UnwrapSDKContext(goCtx)
   300  
   301  	addr, err := sdk.ValAddressFromBech32(msg.ValidatorAddress)
   302  	if err != nil {
   303  		return nil, err
   304  	}
   305  	delegatorAddress, err := sdk.AccAddressFromBech32(msg.DelegatorAddress)
   306  	if err != nil {
   307  		return nil, err
   308  	}
   309  	shares, err := k.ValidateUnbondAmount(
   310  		ctx, delegatorAddress, addr, msg.Amount.Amount,
   311  	)
   312  	if err != nil {
   313  		return nil, err
   314  	}
   315  
   316  	bondDenom := k.BondDenom(ctx)
   317  	if msg.Amount.Denom != bondDenom {
   318  		return nil, sdkerrors.Wrapf(
   319  			sdkerrors.ErrInvalidRequest, "invalid coin denomination: got %s, expected %s", msg.Amount.Denom, bondDenom,
   320  		)
   321  	}
   322  
   323  	completionTime, err := k.Keeper.Undelegate(ctx, delegatorAddress, addr, shares)
   324  	if err != nil {
   325  		return nil, err
   326  	}
   327  
   328  	if msg.Amount.Amount.IsInt64() {
   329  		defer func() {
   330  			telemetry.IncrCounter(1, types.ModuleName, "undelegate")
   331  			telemetry.SetGaugeWithLabels(
   332  				[]string{"tx", "msg", msg.Type()},
   333  				float32(msg.Amount.Amount.Int64()),
   334  				[]metrics.Label{telemetry.NewLabel("denom", msg.Amount.Denom)},
   335  			)
   336  		}()
   337  	}
   338  
   339  	ctx.EventManager().EmitEvents(sdk.Events{
   340  		sdk.NewEvent(
   341  			types.EventTypeUnbond,
   342  			sdk.NewAttribute(types.AttributeKeyValidator, msg.ValidatorAddress),
   343  			sdk.NewAttribute(sdk.AttributeKeyAmount, msg.Amount.String()),
   344  			sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)),
   345  		),
   346  	})
   347  
   348  	return &types.MsgUndelegateResponse{
   349  		CompletionTime: completionTime,
   350  	}, nil
   351  }