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 }