github.com/cosmos/cosmos-sdk@v0.50.10/x/staking/types/authz.go (about)

     1  package types
     2  
     3  import (
     4  	context "context"
     5  
     6  	errorsmod "cosmossdk.io/errors"
     7  
     8  	sdk "github.com/cosmos/cosmos-sdk/types"
     9  	sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
    10  	"github.com/cosmos/cosmos-sdk/x/authz"
    11  )
    12  
    13  // TODO: Revisit this once we have propoer gas fee framework.
    14  // Tracking issues https://github.com/cosmos/cosmos-sdk/issues/9054, https://github.com/cosmos/cosmos-sdk/discussions/9072
    15  const gasCostPerIteration = uint64(10)
    16  
    17  var _ authz.Authorization = &StakeAuthorization{}
    18  
    19  // NewStakeAuthorization creates a new StakeAuthorization object.
    20  func NewStakeAuthorization(allowed, denied []sdk.ValAddress, authzType AuthorizationType, amount *sdk.Coin) (*StakeAuthorization, error) {
    21  	allowedValidators, deniedValidators, err := validateAllowAndDenyValidators(allowed, denied)
    22  	if err != nil {
    23  		return nil, err
    24  	}
    25  
    26  	a := StakeAuthorization{}
    27  	if allowedValidators != nil {
    28  		a.Validators = &StakeAuthorization_AllowList{
    29  			AllowList: &StakeAuthorization_Validators{
    30  				Address: allowedValidators,
    31  			},
    32  		}
    33  	} else {
    34  		a.Validators = &StakeAuthorization_DenyList{
    35  			DenyList: &StakeAuthorization_Validators{
    36  				Address: deniedValidators,
    37  			},
    38  		}
    39  	}
    40  
    41  	if amount != nil {
    42  		a.MaxTokens = amount
    43  	}
    44  
    45  	a.AuthorizationType = authzType
    46  
    47  	return &a, nil
    48  }
    49  
    50  // MsgTypeURL implements Authorization.MsgTypeURL.
    51  func (a StakeAuthorization) MsgTypeURL() string {
    52  	authzType, err := normalizeAuthzType(a.AuthorizationType)
    53  	if err != nil {
    54  		panic(err)
    55  	}
    56  
    57  	return authzType
    58  }
    59  
    60  // ValidateBasic performs a stateless validation of the fields.
    61  // It fails if MaxTokens is either undefined or negative or if the authorization
    62  // is unspecified.
    63  func (a StakeAuthorization) ValidateBasic() error {
    64  	if a.MaxTokens != nil && a.MaxTokens.IsNegative() {
    65  		return errorsmod.Wrapf(authz.ErrNegativeMaxTokens, "negative coin amount: %v", a.MaxTokens)
    66  	}
    67  
    68  	if a.AuthorizationType == AuthorizationType_AUTHORIZATION_TYPE_UNSPECIFIED {
    69  		return authz.ErrUnknownAuthorizationType
    70  	}
    71  
    72  	return nil
    73  }
    74  
    75  // Accept implements Authorization.Accept. It checks, that the validator is not in the denied list,
    76  // and, should the allowed list not be empty, if the validator is in the allowed list.
    77  // If these conditions are met, the authorization amount is validated and if successful, the
    78  // corresponding AcceptResponse is returned.
    79  func (a StakeAuthorization) Accept(ctx context.Context, msg sdk.Msg) (authz.AcceptResponse, error) {
    80  	var (
    81  		validatorAddress string
    82  		amount           sdk.Coin
    83  	)
    84  
    85  	switch msg := msg.(type) {
    86  	case *MsgDelegate:
    87  		validatorAddress = msg.ValidatorAddress
    88  		amount = msg.Amount
    89  	case *MsgUndelegate:
    90  		validatorAddress = msg.ValidatorAddress
    91  		amount = msg.Amount
    92  	case *MsgBeginRedelegate:
    93  		validatorAddress = msg.ValidatorDstAddress
    94  		amount = msg.Amount
    95  	case *MsgCancelUnbondingDelegation:
    96  		validatorAddress = msg.ValidatorAddress
    97  		amount = msg.Amount
    98  	default:
    99  		return authz.AcceptResponse{}, sdkerrors.ErrInvalidRequest.Wrap("unknown msg type")
   100  	}
   101  
   102  	isValidatorExists := false
   103  	allowedList := a.GetAllowList().GetAddress()
   104  	sdkCtx := sdk.UnwrapSDKContext(ctx)
   105  	for _, validator := range allowedList {
   106  		sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "stake authorization")
   107  		if validator == validatorAddress {
   108  			isValidatorExists = true
   109  			break
   110  		}
   111  	}
   112  
   113  	denyList := a.GetDenyList().GetAddress()
   114  	for _, validator := range denyList {
   115  		sdkCtx.GasMeter().ConsumeGas(gasCostPerIteration, "stake authorization")
   116  		if validator == validatorAddress {
   117  			return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrapf("cannot delegate/undelegate to %s validator", validator)
   118  		}
   119  	}
   120  
   121  	if len(allowedList) > 0 && !isValidatorExists {
   122  		return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrapf("cannot delegate/undelegate to %s validator", validatorAddress)
   123  	}
   124  
   125  	if a.MaxTokens == nil {
   126  		return authz.AcceptResponse{
   127  			Accept: true,
   128  			Delete: false,
   129  			Updated: &StakeAuthorization{
   130  				Validators:        a.GetValidators(),
   131  				AuthorizationType: a.GetAuthorizationType(),
   132  			},
   133  		}, nil
   134  	}
   135  
   136  	limitLeft, err := a.MaxTokens.SafeSub(amount)
   137  	if err != nil {
   138  		return authz.AcceptResponse{}, err
   139  	}
   140  
   141  	if limitLeft.IsZero() {
   142  		return authz.AcceptResponse{Accept: true, Delete: true}, nil
   143  	}
   144  
   145  	return authz.AcceptResponse{
   146  		Accept: true,
   147  		Delete: false,
   148  		Updated: &StakeAuthorization{
   149  			Validators:        a.GetValidators(),
   150  			AuthorizationType: a.GetAuthorizationType(),
   151  			MaxTokens:         &limitLeft,
   152  		},
   153  	}, nil
   154  }
   155  
   156  func validateAllowAndDenyValidators(allowed, denied []sdk.ValAddress) ([]string, []string, error) {
   157  	if len(allowed) == 0 && len(denied) == 0 {
   158  		return nil, nil, sdkerrors.ErrInvalidRequest.Wrap("both allowed & deny list cannot be empty")
   159  	}
   160  
   161  	if len(allowed) > 0 && len(denied) > 0 {
   162  		return nil, nil, sdkerrors.ErrInvalidRequest.Wrap("cannot set both allowed & deny list")
   163  	}
   164  
   165  	allowedValidators := make([]string, len(allowed))
   166  	if len(allowed) > 0 {
   167  		for i, validator := range allowed {
   168  			allowedValidators[i] = validator.String()
   169  		}
   170  		return allowedValidators, nil, nil
   171  	}
   172  
   173  	deniedValidators := make([]string, len(denied))
   174  	for i, validator := range denied {
   175  		deniedValidators[i] = validator.String()
   176  	}
   177  
   178  	return nil, deniedValidators, nil
   179  }
   180  
   181  // Normalized Msg type URLs
   182  func normalizeAuthzType(authzType AuthorizationType) (string, error) {
   183  	switch authzType {
   184  	case AuthorizationType_AUTHORIZATION_TYPE_DELEGATE:
   185  		return sdk.MsgTypeURL(&MsgDelegate{}), nil
   186  	case AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE:
   187  		return sdk.MsgTypeURL(&MsgUndelegate{}), nil
   188  	case AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE:
   189  		return sdk.MsgTypeURL(&MsgBeginRedelegate{}), nil
   190  	case AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION:
   191  		return sdk.MsgTypeURL(&MsgCancelUnbondingDelegation{}), nil
   192  	default:
   193  		return "", errorsmod.Wrapf(authz.ErrUnknownAuthorizationType, "cannot normalize authz type with %T", authzType)
   194  	}
   195  }