diff --git a/proto/cosmos/staking/v1beta1/authz.proto b/proto/cosmos/staking/v1beta1/authz.proto index 055d1b645288..24582a5f226c 100644 --- a/proto/cosmos/staking/v1beta1/authz.proto +++ b/proto/cosmos/staking/v1beta1/authz.proto @@ -46,4 +46,6 @@ enum AuthorizationType { AUTHORIZATION_TYPE_UNDELEGATE = 2; // AUTHORIZATION_TYPE_REDELEGATE defines an authorization type for Msg/BeginRedelegate AUTHORIZATION_TYPE_REDELEGATE = 3; + // AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION defines an authorization type for Msg/MsgCancelUnbondingDelegation + AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION = 4; } diff --git a/x/staking/types/authz.go b/x/staking/types/authz.go index 1f9016cdbb6e..ab3d93979d65 100644 --- a/x/staking/types/authz.go +++ b/x/staking/types/authz.go @@ -21,14 +21,23 @@ func NewStakeAuthorization(allowed []sdk.ValAddress, denied []sdk.ValAddress, au a := StakeAuthorization{} if allowedValidators != nil { - a.Validators = &StakeAuthorization_AllowList{AllowList: &StakeAuthorization_Validators{Address: allowedValidators}} + a.Validators = &StakeAuthorization_AllowList{ + AllowList: &StakeAuthorization_Validators{ + Address: allowedValidators, + }, + } } else { - a.Validators = &StakeAuthorization_DenyList{DenyList: &StakeAuthorization_Validators{Address: deniedValidators}} + a.Validators = &StakeAuthorization_DenyList{ + DenyList: &StakeAuthorization_Validators{ + Address: deniedValidators, + }, + } } if amount != nil { a.MaxTokens = amount } + a.AuthorizationType = authzType return &a, nil @@ -43,10 +52,14 @@ func (a StakeAuthorization) MsgTypeURL() string { return authzType } +// ValidateBasic performs a stateless validation of the fields. +// It fails if MaxTokens is either undefined or negative or if the authorization +// is unspecified. func (a StakeAuthorization) ValidateBasic() error { if a.MaxTokens != nil && a.MaxTokens.IsNegative() { return sdkerrors.Wrapf(authz.ErrNegativeMaxTokens, "negative coin amount: %v", a.MaxTokens) } + if a.AuthorizationType == AuthorizationType_AUTHORIZATION_TYPE_UNSPECIFIED { return authz.ErrUnknownAuthorizationType } @@ -54,10 +67,15 @@ func (a StakeAuthorization) ValidateBasic() error { return nil } -// Accept implements Authorization.Accept. +// Accept implements Authorization.Accept. It checks, that the validator is not in the denied list, +// and, should the allowed list not be empty, if the validator is in the allowed list. +// If these conditions are met, the authorization amount is validated and if successful, the +// corresponding AcceptResponse is returned. func (a StakeAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptResponse, error) { - var validatorAddress string - var amount sdk.Coin + var ( + validatorAddress string + amount sdk.Coin + ) switch msg := msg.(type) { case *MsgDelegate: @@ -69,6 +87,9 @@ func (a StakeAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptRe case *MsgBeginRedelegate: validatorAddress = msg.ValidatorDstAddress amount = msg.Amount + case *MsgCancelUnbondingDelegation: + validatorAddress = msg.ValidatorAddress + amount = msg.Amount default: return authz.AcceptResponse{}, sdkerrors.ErrInvalidRequest.Wrap("unknown msg type") } @@ -97,8 +118,12 @@ func (a StakeAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptRe if a.MaxTokens == nil { return authz.AcceptResponse{ - Accept: true, Delete: false, - Updated: &StakeAuthorization{Validators: a.GetValidators(), AuthorizationType: a.GetAuthorizationType()}, + Accept: true, + Delete: false, + Updated: &StakeAuthorization{ + Validators: a.GetValidators(), + AuthorizationType: a.GetAuthorizationType(), + }, }, nil } @@ -106,12 +131,19 @@ func (a StakeAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptRe if err != nil { return authz.AcceptResponse{}, err } + if limitLeft.IsZero() { return authz.AcceptResponse{Accept: true, Delete: true}, nil } + return authz.AcceptResponse{ - Accept: true, Delete: false, - Updated: &StakeAuthorization{Validators: a.GetValidators(), AuthorizationType: a.GetAuthorizationType(), MaxTokens: &limitLeft}, + Accept: true, + Delete: false, + Updated: &StakeAuthorization{ + Validators: a.GetValidators(), + AuthorizationType: a.GetAuthorizationType(), + MaxTokens: &limitLeft, + }, }, nil } @@ -149,6 +181,8 @@ func normalizeAuthzType(authzType AuthorizationType) (string, error) { return sdk.MsgTypeURL(&MsgUndelegate{}), nil case AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE: return sdk.MsgTypeURL(&MsgBeginRedelegate{}), nil + case AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION: + return sdk.MsgTypeURL(&MsgCancelUnbondingDelegation{}), nil default: return "", sdkerrors.Wrapf(authz.ErrUnknownAuthorizationType, "cannot normalize authz type with %T", authzType) } diff --git a/x/staking/types/authz.pb.go b/x/staking/types/authz.pb.go index 7f790975a108..ec363b733d59 100644 --- a/x/staking/types/authz.pb.go +++ b/x/staking/types/authz.pb.go @@ -40,6 +40,8 @@ const ( AuthorizationType_AUTHORIZATION_TYPE_UNDELEGATE AuthorizationType = 2 // AUTHORIZATION_TYPE_REDELEGATE defines an authorization type for Msg/BeginRedelegate AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE AuthorizationType = 3 + // AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION defines an authorization type for Msg/MsgCancelUnbondingDelegation + AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION AuthorizationType = 4 ) var AuthorizationType_name = map[int32]string{ @@ -47,13 +49,15 @@ var AuthorizationType_name = map[int32]string{ 1: "AUTHORIZATION_TYPE_DELEGATE", 2: "AUTHORIZATION_TYPE_UNDELEGATE", 3: "AUTHORIZATION_TYPE_REDELEGATE", + 4: "AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION", } var AuthorizationType_value = map[string]int32{ - "AUTHORIZATION_TYPE_UNSPECIFIED": 0, - "AUTHORIZATION_TYPE_DELEGATE": 1, - "AUTHORIZATION_TYPE_UNDELEGATE": 2, - "AUTHORIZATION_TYPE_REDELEGATE": 3, + "AUTHORIZATION_TYPE_UNSPECIFIED": 0, + "AUTHORIZATION_TYPE_DELEGATE": 1, + "AUTHORIZATION_TYPE_UNDELEGATE": 2, + "AUTHORIZATION_TYPE_REDELEGATE": 3, + "AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION": 4, } func (x AuthorizationType) String() string { @@ -229,6 +233,7 @@ func init() { } var fileDescriptor_d6d8cdbc6f4432f0 = []byte{ +<<<<<<< HEAD // 503 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x93, 0xc1, 0x6e, 0xd3, 0x30, 0x18, 0x80, 0x93, 0x75, 0x02, 0x6a, 0x10, 0x5a, 0xad, 0x09, 0x75, 0x45, 0xcb, 0x46, 0x2f, 0x94, @@ -262,6 +267,41 @@ var fileDescriptor_d6d8cdbc6f4432f0 = []byte{ 0x53, 0x43, 0xbb, 0x9c, 0x1a, 0xda, 0xb7, 0xa9, 0xa1, 0x9d, 0x3e, 0xf9, 0xe7, 0x40, 0x8e, 0xb2, 0x2b, 0x29, 0x46, 0xb3, 0x7d, 0x43, 0xdc, 0x87, 0xa7, 0xbf, 0x02, 0x00, 0x00, 0xff, 0xff, 0x21, 0xe8, 0xb9, 0x05, 0xb1, 0x03, 0x00, 0x00, +======= + // 498 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x93, 0x4d, 0x6f, 0xd3, 0x30, + 0x1c, 0xc6, 0x93, 0x75, 0x02, 0x6a, 0x5e, 0xd4, 0x5a, 0x13, 0xea, 0x8a, 0xc8, 0x46, 0x2f, 0x94, + 0x97, 0x3a, 0x5a, 0x11, 0x17, 0x4e, 0x24, 0x6d, 0xb6, 0x45, 0xaa, 0xd2, 0x29, 0x4d, 0x27, 0xd8, + 0x25, 0x72, 0x1b, 0x2b, 0xb5, 0xda, 0xc6, 0x55, 0xec, 0x8e, 0x76, 0x9f, 0x82, 0xcf, 0xc1, 0x79, + 0x1f, 0x02, 0x71, 0x9a, 0x76, 0xe2, 0x06, 0x6a, 0x25, 0x3e, 0x07, 0xca, 0x4b, 0x03, 0x63, 0x1d, + 0x97, 0x9d, 0x12, 0xe5, 0xff, 0xcb, 0xef, 0x79, 0x6c, 0xd9, 0xa0, 0xd2, 0x67, 0x7c, 0xcc, 0xb8, + 0xca, 0x05, 0x1e, 0xd2, 0xc0, 0x57, 0x4f, 0xf7, 0x7a, 0x44, 0xe0, 0x3d, 0x15, 0x4f, 0xc5, 0xe0, + 0x0c, 0x4d, 0x42, 0x26, 0x18, 0x7c, 0x9c, 0x30, 0x28, 0x65, 0x50, 0xca, 0x94, 0xb7, 0x7c, 0xe6, + 0xb3, 0x18, 0x51, 0xa3, 0xb7, 0x84, 0x2e, 0x6f, 0x27, 0xb4, 0x9b, 0x0c, 0xd2, 0x5f, 0x93, 0x91, + 0x92, 0x86, 0xf5, 0x30, 0x27, 0x59, 0x52, 0x9f, 0xd1, 0x20, 0x99, 0x57, 0x7e, 0xe5, 0x00, 0xec, + 0x08, 0x3c, 0x24, 0xda, 0x54, 0x0c, 0x58, 0x48, 0xcf, 0xb0, 0xa0, 0x2c, 0x80, 0x04, 0x80, 0x31, + 0x9e, 0xb9, 0x82, 0x0d, 0x49, 0xc0, 0x4b, 0xf2, 0xae, 0x5c, 0xbd, 0x5f, 0xdf, 0x46, 0xa9, 0x39, + 0x72, 0xad, 0x1a, 0xa1, 0x06, 0xa3, 0x81, 0xfe, 0xea, 0xcb, 0x8f, 0x9d, 0xe7, 0x3e, 0x15, 0x83, + 0x69, 0x0f, 0xf5, 0xd9, 0x38, 0xad, 0x90, 0x3e, 0x6a, 0xdc, 0x1b, 0xaa, 0x62, 0x3e, 0x21, 0x3c, + 0x86, 0xed, 0xfc, 0x18, 0xcf, 0x9c, 0x58, 0x0c, 0x8f, 0x01, 0xc0, 0xa3, 0x11, 0xfb, 0xe4, 0x8e, + 0x28, 0x17, 0xa5, 0x8d, 0x38, 0xe6, 0x2d, 0x5a, 0xbf, 0x76, 0x74, 0xbd, 0x26, 0x3a, 0xc6, 0x23, + 0xea, 0x61, 0xc1, 0x42, 0x7e, 0x28, 0xd9, 0xf9, 0x58, 0xd5, 0xa2, 0x5c, 0x40, 0x07, 0xe4, 0x3d, + 0x12, 0xcc, 0x13, 0x6d, 0xee, 0x76, 0xda, 0x7b, 0x91, 0x29, 0xb6, 0x7e, 0x00, 0x10, 0xff, 0xcd, + 0xb9, 0xd1, 0xa2, 0x4a, 0x9b, 0xbb, 0x72, 0xf5, 0x51, 0xfd, 0xc5, 0x4d, 0xfa, 0x2b, 0x66, 0x67, + 0x3e, 0x21, 0x76, 0x11, 0xff, 0xfb, 0xa9, 0xfc, 0x1e, 0x80, 0x3f, 0x99, 0xb0, 0x0e, 0xee, 0x62, + 0xcf, 0x0b, 0x09, 0x8f, 0x76, 0x3e, 0x57, 0xcd, 0xeb, 0xa5, 0xcb, 0xf3, 0xda, 0x56, 0xea, 0xd7, + 0x92, 0x49, 0x47, 0x84, 0x34, 0xf0, 0xed, 0x15, 0xf8, 0xae, 0xf8, 0xed, 0xbc, 0xf6, 0xf0, 0x4a, + 0x96, 0xfe, 0x00, 0x80, 0xd3, 0x4c, 0xfa, 0xf2, 0x52, 0x06, 0xc5, 0x6b, 0x5d, 0x60, 0x05, 0x28, + 0x5a, 0xd7, 0x39, 0x6c, 0xdb, 0xe6, 0x89, 0xe6, 0x98, 0x6d, 0xcb, 0x75, 0x3e, 0x1e, 0x19, 0x6e, + 0xd7, 0xea, 0x1c, 0x19, 0x0d, 0x73, 0xdf, 0x34, 0x9a, 0x05, 0x09, 0xee, 0x80, 0x27, 0x6b, 0x98, + 0xa6, 0xd1, 0x32, 0x0e, 0x34, 0xc7, 0x28, 0xc8, 0xf0, 0x19, 0x78, 0xba, 0x56, 0x92, 0x21, 0x1b, + 0x37, 0x20, 0xb6, 0x91, 0x21, 0x39, 0x58, 0x07, 0x68, 0x0d, 0xd2, 0xd0, 0xac, 0x86, 0xd1, 0x72, + 0xbb, 0x96, 0xde, 0xb6, 0x9a, 0xa6, 0x75, 0xb0, 0xca, 0x35, 0xdb, 0x56, 0x61, 0x53, 0xdf, 0xff, + 0xba, 0x50, 0xe4, 0x8b, 0x85, 0x22, 0xff, 0x5c, 0x28, 0xf2, 0xe7, 0xa5, 0x22, 0x5d, 0x2c, 0x15, + 0xe9, 0xfb, 0x52, 0x91, 0x4e, 0x5e, 0xff, 0xf7, 0x34, 0xce, 0xb2, 0xcb, 0x17, 0x9f, 0xcb, 0xde, + 0x9d, 0xf8, 0x32, 0xbc, 0xf9, 0x1d, 0x00, 0x00, 0xff, 0xff, 0x23, 0x47, 0x8f, 0xad, 0x9b, 0x03, + 0x00, 0x00, +>>>>>>> 0f17a1548 (feat: add CancelUnbondingDelegation authz (#21)) } func (m *StakeAuthorization) Marshal() (dAtA []byte, err error) { diff --git a/x/staking/types/authz_test.go b/x/staking/types/authz_test.go index 0d3e7aa1a9dc..5ef0b6131251 100644 --- a/x/staking/types/authz_test.go +++ b/x/staking/types/authz_test.go @@ -48,6 +48,10 @@ func TestAuthzAuthorizations(t *testing.T) { beginRedelAuth, _ := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_REDELEGATE, &coin100) require.Equal(t, beginRedelAuth.MsgTypeURL(), sdk.MsgTypeURL(&stakingtypes.MsgBeginRedelegate{})) + // verify MethodName for CancelUnbondingDelegation + cancelUnbondAuth, _ := stakingtypes.NewStakeAuthorization([]sdk.ValAddress{val1, val2}, []sdk.ValAddress{}, stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, &coin100) + require.Equal(t, cancelUnbondAuth.MsgTypeURL(), sdk.MsgTypeURL(&stakingtypes.MsgCancelUnbondingDelegation{})) + validators1_2 := []string{val1.String(), val2.String()} testCases := []struct { @@ -277,6 +281,73 @@ func TestAuthzAuthorizations(t *testing.T) { false, nil, }, + { + "cancel unbonding delegation: expect 0 remaining coins", + []sdk.ValAddress{val1}, + []sdk.ValAddress{}, + stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, + &coin100, + stakingtypes.NewMsgCancelUnbondingDelegation(delAddr, val1, ctx.BlockHeight(), coin100), + false, + true, + nil, + }, + { + "cancel unbonding delegation: verify remaining coins", + []sdk.ValAddress{val1}, + []sdk.ValAddress{}, + stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, + &coin100, + stakingtypes.NewMsgCancelUnbondingDelegation(delAddr, val1, ctx.BlockHeight(), coin50), + false, + false, + &stakingtypes.StakeAuthorization{ + Validators: &stakingtypes.StakeAuthorization_AllowList{ + AllowList: &stakingtypes.StakeAuthorization_Validators{Address: []string{val1.String()}}, + }, + MaxTokens: &coin50, + AuthorizationType: stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, + }, + }, + { + "cancel unbonding delegation: testing with invalid validator", + []sdk.ValAddress{val1, val2}, + []sdk.ValAddress{}, + stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, + &coin100, + stakingtypes.NewMsgCancelUnbondingDelegation(delAddr, val3, ctx.BlockHeight(), coin50), + true, + false, + nil, + }, + { + "cancel unbonding delegation: testing delegate without spent limit", + []sdk.ValAddress{val1, val2}, + []sdk.ValAddress{}, + stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, + nil, + stakingtypes.NewMsgCancelUnbondingDelegation(delAddr, val2, ctx.BlockHeight(), coin100), + false, + false, + &stakingtypes.StakeAuthorization{ + Validators: &stakingtypes.StakeAuthorization_AllowList{ + AllowList: &stakingtypes.StakeAuthorization_Validators{Address: validators1_2}, + }, + MaxTokens: nil, + AuthorizationType: stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, + }, + }, + { + "cancel unbonding delegation: fail cannot undelegate, permission denied", + []sdk.ValAddress{}, + []sdk.ValAddress{val1}, + stakingtypes.AuthorizationType_AUTHORIZATION_TYPE_CANCEL_UNBONDING_DELEGATION, + &coin100, + stakingtypes.NewMsgCancelUnbondingDelegation(delAddr, val1, ctx.BlockHeight(), coin100), + true, + false, + nil, + }, } for _, tc := range testCases {