diff --git a/CHANGELOG.md b/CHANGELOG.md index 92c89bcf01..476d34445d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### Features +* (x/authz) [#12648](https://github.com/cosmos/cosmos-sdk/pull/12648) Add an allow list, an optional list of addresses allowed to receive bank assests via authz MsgSend grant. * (cli) [#12028](https://github.com/cosmos/cosmos-sdk/pull/12028) Add the `tendermint key-migrate` to perform Tendermint v0.35 DB key migration. * (sdk.Coins) [#12627](https://github.com/cosmos/cosmos-sdk/pull/12627) Make a Denoms method on sdk.Coins. @@ -63,6 +64,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ ### API Breaking Changes +* (x/bank) [#12648](https://github.com/cosmos/cosmos-sdk/pull/12648) `NewSendAuthorization` takes a new argument of an optional list of addresses allowed to receive bank assests via authz MsgSend grant. You can pass `nil` for the same behavior as before, i.e. any recipient is allowed. * (x/bank) [\#12593](https://github.com/cosmos/cosmos-sdk/pull/12593) Add `SpendableCoin` method to `BaseViewKeeper` * (x/slashing) [#12581](https://github.com/cosmos/cosmos-sdk/pull/12581) Remove `x/slashing` legacy querier. * (types) [\#12355](https://github.com/cosmos/cosmos-sdk/pull/12355) Remove the compile-time `types.DBbackend` variable. Removes usage of the same in server/util.go diff --git a/api/cosmos/bank/v1beta1/authz.pulsar.go b/api/cosmos/bank/v1beta1/authz.pulsar.go index f00963c145..1443714203 100644 --- a/api/cosmos/bank/v1beta1/authz.pulsar.go +++ b/api/cosmos/bank/v1beta1/authz.pulsar.go @@ -66,15 +66,63 @@ func (x *_SendAuthorization_1_list) IsValid() bool { return x.list != nil } +var _ protoreflect.List = (*_SendAuthorization_2_list)(nil) + +type _SendAuthorization_2_list struct { + list *[]string +} + +func (x *_SendAuthorization_2_list) Len() int { + if x.list == nil { + return 0 + } + return len(*x.list) +} + +func (x *_SendAuthorization_2_list) Get(i int) protoreflect.Value { + return protoreflect.ValueOfString((*x.list)[i]) +} + +func (x *_SendAuthorization_2_list) Set(i int, value protoreflect.Value) { + valueUnwrapped := value.String() + concreteValue := valueUnwrapped + (*x.list)[i] = concreteValue +} + +func (x *_SendAuthorization_2_list) Append(value protoreflect.Value) { + valueUnwrapped := value.String() + concreteValue := valueUnwrapped + *x.list = append(*x.list, concreteValue) +} + +func (x *_SendAuthorization_2_list) AppendMutable() protoreflect.Value { + panic(fmt.Errorf("AppendMutable can not be called on message SendAuthorization at list field AllowList as it is not of Message kind")) +} + +func (x *_SendAuthorization_2_list) Truncate(n int) { + *x.list = (*x.list)[:n] +} + +func (x *_SendAuthorization_2_list) NewElement() protoreflect.Value { + v := "" + return protoreflect.ValueOfString(v) +} + +func (x *_SendAuthorization_2_list) IsValid() bool { + return x.list != nil +} + var ( md_SendAuthorization protoreflect.MessageDescriptor fd_SendAuthorization_spend_limit protoreflect.FieldDescriptor + fd_SendAuthorization_allow_list protoreflect.FieldDescriptor ) func init() { file_cosmos_bank_v1beta1_authz_proto_init() md_SendAuthorization = File_cosmos_bank_v1beta1_authz_proto.Messages().ByName("SendAuthorization") fd_SendAuthorization_spend_limit = md_SendAuthorization.Fields().ByName("spend_limit") + fd_SendAuthorization_allow_list = md_SendAuthorization.Fields().ByName("allow_list") } var _ protoreflect.Message = (*fastReflection_SendAuthorization)(nil) @@ -148,6 +196,12 @@ func (x *fastReflection_SendAuthorization) Range(f func(protoreflect.FieldDescri return } } + if len(x.AllowList) != 0 { + value := protoreflect.ValueOfList(&_SendAuthorization_2_list{list: &x.AllowList}) + if !f(fd_SendAuthorization_allow_list, value) { + return + } + } } // Has reports whether a field is populated. @@ -165,6 +219,8 @@ func (x *fastReflection_SendAuthorization) Has(fd protoreflect.FieldDescriptor) switch fd.FullName() { case "cosmos.bank.v1beta1.SendAuthorization.spend_limit": return len(x.SpendLimit) != 0 + case "cosmos.bank.v1beta1.SendAuthorization.allow_list": + return len(x.AllowList) != 0 default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.v1beta1.SendAuthorization")) @@ -183,6 +239,8 @@ func (x *fastReflection_SendAuthorization) Clear(fd protoreflect.FieldDescriptor switch fd.FullName() { case "cosmos.bank.v1beta1.SendAuthorization.spend_limit": x.SpendLimit = nil + case "cosmos.bank.v1beta1.SendAuthorization.allow_list": + x.AllowList = nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.v1beta1.SendAuthorization")) @@ -205,6 +263,12 @@ func (x *fastReflection_SendAuthorization) Get(descriptor protoreflect.FieldDesc } listValue := &_SendAuthorization_1_list{list: &x.SpendLimit} return protoreflect.ValueOfList(listValue) + case "cosmos.bank.v1beta1.SendAuthorization.allow_list": + if len(x.AllowList) == 0 { + return protoreflect.ValueOfList(&_SendAuthorization_2_list{}) + } + listValue := &_SendAuthorization_2_list{list: &x.AllowList} + return protoreflect.ValueOfList(listValue) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.v1beta1.SendAuthorization")) @@ -229,6 +293,10 @@ func (x *fastReflection_SendAuthorization) Set(fd protoreflect.FieldDescriptor, lv := value.List() clv := lv.(*_SendAuthorization_1_list) x.SpendLimit = *clv.list + case "cosmos.bank.v1beta1.SendAuthorization.allow_list": + lv := value.List() + clv := lv.(*_SendAuthorization_2_list) + x.AllowList = *clv.list default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.v1beta1.SendAuthorization")) @@ -255,6 +323,12 @@ func (x *fastReflection_SendAuthorization) Mutable(fd protoreflect.FieldDescript } value := &_SendAuthorization_1_list{list: &x.SpendLimit} return protoreflect.ValueOfList(value) + case "cosmos.bank.v1beta1.SendAuthorization.allow_list": + if x.AllowList == nil { + x.AllowList = []string{} + } + value := &_SendAuthorization_2_list{list: &x.AllowList} + return protoreflect.ValueOfList(value) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.v1beta1.SendAuthorization")) @@ -271,6 +345,9 @@ func (x *fastReflection_SendAuthorization) NewField(fd protoreflect.FieldDescrip case "cosmos.bank.v1beta1.SendAuthorization.spend_limit": list := []*v1beta1.Coin{} return protoreflect.ValueOfList(&_SendAuthorization_1_list{list: &list}) + case "cosmos.bank.v1beta1.SendAuthorization.allow_list": + list := []string{} + return protoreflect.ValueOfList(&_SendAuthorization_2_list{list: &list}) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.v1beta1.SendAuthorization")) @@ -346,6 +423,12 @@ func (x *fastReflection_SendAuthorization) ProtoMethods() *protoiface.Methods { n += 1 + l + runtime.Sov(uint64(l)) } } + if len(x.AllowList) > 0 { + for _, s := range x.AllowList { + l = len(s) + n += 1 + l + runtime.Sov(uint64(l)) + } + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -375,6 +458,15 @@ func (x *fastReflection_SendAuthorization) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if len(x.AllowList) > 0 { + for iNdEx := len(x.AllowList) - 1; iNdEx >= 0; iNdEx-- { + i -= len(x.AllowList[iNdEx]) + copy(dAtA[i:], x.AllowList[iNdEx]) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.AllowList[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } if len(x.SpendLimit) > 0 { for iNdEx := len(x.SpendLimit) - 1; iNdEx >= 0; iNdEx-- { encoded, err := options.Marshal(x.SpendLimit[iNdEx]) @@ -474,6 +566,38 @@ func (x *fastReflection_SendAuthorization) ProtoMethods() *protoiface.Methods { return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err } iNdEx = postIndex + case 2: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field AllowList", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrIntOverflow + } + if iNdEx >= l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if postIndex > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + x.AllowList = append(x.AllowList, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -532,6 +656,11 @@ type SendAuthorization struct { unknownFields protoimpl.UnknownFields SpendLimit []*v1beta1.Coin `protobuf:"bytes,1,rep,name=spend_limit,json=spendLimit,proto3" json:"spend_limit,omitempty"` + // allow_list specifies an optional list of addresses to whom the grantee can send tokens on behalf of the + // granter. If omitted, any recipient is allowed. + // + // Since: cosmos-sdk 0.47 + AllowList []string `protobuf:"bytes,2,rep,name=allow_list,json=allowList,proto3" json:"allow_list,omitempty"` } func (x *SendAuthorization) Reset() { @@ -561,6 +690,13 @@ func (x *SendAuthorization) GetSpendLimit() []*v1beta1.Coin { return nil } +func (x *SendAuthorization) GetAllowList() []string { + if x != nil { + return x.AllowList + } + return nil +} + var File_cosmos_bank_v1beta1_authz_proto protoreflect.FileDescriptor var file_cosmos_bank_v1beta1_authz_proto_rawDesc = []byte{ @@ -572,7 +708,7 @@ var file_cosmos_bank_v1beta1_authz_proto_rawDesc = []byte{ 0x73, 0x6d, 0x6f, 0x73, 0x5f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x1e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x62, 0x61, 0x73, 0x65, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x2f, 0x63, 0x6f, 0x69, - 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x94, 0x01, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, + 0x6e, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xb3, 0x01, 0x0a, 0x11, 0x53, 0x65, 0x6e, 0x64, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x6c, 0x0a, 0x0b, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x5f, 0x6c, 0x69, 0x6d, 0x69, 0x74, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x19, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x73, 0x65, @@ -580,21 +716,23 @@ var file_cosmos_bank_v1beta1_authz_proto_rawDesc = []byte{ 0xde, 0x1f, 0x00, 0xaa, 0xdf, 0x1f, 0x28, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2d, 0x73, 0x64, 0x6b, 0x2f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x2e, 0x43, 0x6f, 0x69, 0x6e, 0x73, 0x52, - 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x3a, 0x11, 0xca, 0xb4, 0x2d, - 0x0d, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0xc5, - 0x01, 0x0a, 0x17, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, - 0x6e, 0x6b, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0a, 0x41, 0x75, 0x74, 0x68, - 0x7a, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, - 0x73, 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, - 0x73, 0x2f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x62, - 0x61, 0x6e, 0x6b, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x42, 0x58, - 0xaa, 0x02, 0x13, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x6e, 0x6b, 0x2e, 0x56, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x13, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, - 0x42, 0x61, 0x6e, 0x6b, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x1f, 0x43, - 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x42, 0x61, 0x6e, 0x6b, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, - 0x61, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, - 0x15, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x42, 0x61, 0x6e, 0x6b, 0x3a, 0x3a, 0x56, - 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x0a, 0x73, 0x70, 0x65, 0x6e, 0x64, 0x4c, 0x69, 0x6d, 0x69, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x61, + 0x6c, 0x6c, 0x6f, 0x77, 0x5f, 0x6c, 0x69, 0x73, 0x74, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x09, 0x61, 0x6c, 0x6c, 0x6f, 0x77, 0x4c, 0x69, 0x73, 0x74, 0x3a, 0x11, 0xca, 0xb4, 0x2d, 0x0d, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x42, 0xc5, 0x01, + 0x0a, 0x17, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x6e, + 0x6b, 0x2e, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x42, 0x0a, 0x41, 0x75, 0x74, 0x68, 0x7a, + 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x30, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x73, + 0x64, 0x6b, 0x2e, 0x69, 0x6f, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, + 0x2f, 0x62, 0x61, 0x6e, 0x6b, 0x2f, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0x3b, 0x62, 0x61, + 0x6e, 0x6b, 0x76, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x42, 0x58, 0xaa, + 0x02, 0x13, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x6e, 0x6b, 0x2e, 0x56, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0xca, 0x02, 0x13, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x42, + 0x61, 0x6e, 0x6b, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, 0x31, 0xe2, 0x02, 0x1f, 0x43, 0x6f, + 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x42, 0x61, 0x6e, 0x6b, 0x5c, 0x56, 0x31, 0x62, 0x65, 0x74, 0x61, + 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x15, + 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x42, 0x61, 0x6e, 0x6b, 0x3a, 0x3a, 0x56, 0x31, + 0x62, 0x65, 0x74, 0x61, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/proto/cosmos/bank/v1beta1/authz.proto b/proto/cosmos/bank/v1beta1/authz.proto index 4f58b15e49..36f695f4c9 100644 --- a/proto/cosmos/bank/v1beta1/authz.proto +++ b/proto/cosmos/bank/v1beta1/authz.proto @@ -16,4 +16,10 @@ message SendAuthorization { repeated cosmos.base.v1beta1.Coin spend_limit = 1 [(gogoproto.nullable) = false, (gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"]; + + // allow_list specifies an optional list of addresses to whom the grantee can send tokens on behalf of the + // granter. If omitted, any recipient is allowed. + // + // Since: cosmos-sdk 0.47 + repeated string allow_list = 2; } diff --git a/x/authz/client/cli/tx.go b/x/authz/client/cli/tx.go index 8ebf8550d7..0e35e65923 100644 --- a/x/authz/client/cli/tx.go +++ b/x/authz/client/cli/tx.go @@ -26,6 +26,7 @@ const ( FlagExpiration = "expiration" FlagAllowedValidators = "allowed-validators" FlagDenyValidators = "deny-validators" + FlagAllowList = "allow-list" delegate = "delegate" redelegate = "redelegate" unbond = "unbond" @@ -93,7 +94,18 @@ Examples: return fmt.Errorf("spend-limit should be greater than zero") } - authorization = bank.NewSendAuthorization(spendLimit) + allowList, err := cmd.Flags().GetStringSlice(FlagAllowList) + if err != nil { + return err + } + + allowed, err := bech32toAccAddresses(allowList) + if err != nil { + return err + } + + authorization = bank.NewSendAuthorization(spendLimit, allowed) + case "generic": msgType, err := cmd.Flags().GetString(FlagMsgType) if err != nil { @@ -140,12 +152,12 @@ Examples: delegateLimit = &spendLimit } - allowed, err := bech32toValidatorAddresses(allowValidators) + allowed, err := bech32toValAddresses(allowValidators) if err != nil { return err } - denied, err := bech32toValidatorAddresses(denyValidators) + denied, err := bech32toValAddresses(denyValidators) if err != nil { return err } @@ -184,6 +196,7 @@ Examples: cmd.Flags().String(FlagSpendLimit, "", "SpendLimit for Send Authorization, an array of Coins allowed spend") cmd.Flags().StringSlice(FlagAllowedValidators, []string{}, "Allowed validators addresses separated by ,") cmd.Flags().StringSlice(FlagDenyValidators, []string{}, "Deny validators addresses separated by ,") + cmd.Flags().StringSlice(FlagAllowList, []string{}, "Allowed addresses grantee is allowed to send funds separated by ,") cmd.Flags().Int64(FlagExpiration, 0, "Expire time as Unix timestamp. Set zero (0) for no expiry. Default is 0.") return cmd } @@ -273,7 +286,8 @@ Example: return cmd } -func bech32toValidatorAddresses(validators []string) ([]sdk.ValAddress, error) { +// bech32toValAddresses returns []ValAddress from a list of Bech32 string addresses. +func bech32toValAddresses(validators []string) ([]sdk.ValAddress, error) { vals := make([]sdk.ValAddress, len(validators)) for i, validator := range validators { addr, err := sdk.ValAddressFromBech32(validator) @@ -284,3 +298,16 @@ func bech32toValidatorAddresses(validators []string) ([]sdk.ValAddress, error) { } return vals, nil } + +// bech32toAccAddresses returns []AccAddress from a list of Bech32 string addresses. +func bech32toAccAddresses(accAddrs []string) ([]sdk.AccAddress, error) { + addrs := make([]sdk.AccAddress, len(accAddrs)) + for i, addr := range accAddrs { + accAddr, err := sdk.AccAddressFromBech32(addr) + if err != nil { + return nil, err + } + addrs[i] = accAddr + } + return addrs, nil +} diff --git a/x/authz/client/testutil/grpc.go b/x/authz/client/testutil/grpc.go index 20a0f7c903..f29f858cb2 100644 --- a/x/authz/client/testutil/grpc.go +++ b/x/authz/client/testutil/grpc.go @@ -195,7 +195,7 @@ func (s *IntegrationTestSuite) TestQueryGranterGrantsGRPC() { fmt.Sprintf("%s/cosmos/authz/v1beta1/grants/granter/%s", val.APIAddress, val.Address.String()), false, "", - 7, + 8, }, } for _, tc := range testCases { diff --git a/x/authz/client/testutil/query.go b/x/authz/client/testutil/query.go index 45921a054d..f4326ad417 100644 --- a/x/authz/client/testutil/query.go +++ b/x/authz/client/testutil/query.go @@ -161,7 +161,18 @@ func (s *IntegrationTestSuite) TestQueryAuthorization() { fmt.Sprintf("--%s=json", tmcli.OutputFlag), }, false, - `{"@type":"/cosmos.bank.v1beta1.SendAuthorization","spend_limit":[{"denom":"stake","amount":"100"}]}`, + `{"@type":"/cosmos.bank.v1beta1.SendAuthorization","spend_limit":[{"denom":"stake","amount":"100"}],"allow_list":[]}`, + }, + { + "Valid txn with allowed list (json)", + []string{ + val.Address.String(), + s.grantee[3].String(), + typeMsgSend, + fmt.Sprintf("--%s=json", tmcli.OutputFlag), + }, + false, + fmt.Sprintf(`{"@type":"/cosmos.bank.v1beta1.SendAuthorization","spend_limit":[{"denom":"stake","amount":"88"}],"allow_list":["%s"]}`, s.grantee[4]), }, } for _, tc := range testCases { @@ -221,7 +232,7 @@ func (s *IntegrationTestSuite) TestQueryGranterGrants() { }, false, "", - 7, + 8, }, { "valid case with pagination", diff --git a/x/authz/client/testutil/tx.go b/x/authz/client/testutil/tx.go index f6a6b0a714..537ac8319a 100644 --- a/x/authz/client/testutil/tx.go +++ b/x/authz/client/testutil/tx.go @@ -45,7 +45,7 @@ func (s *IntegrationTestSuite) SetupSuite() { s.Require().NoError(err) val := s.network.Validators[0] - s.grantee = make([]sdk.AccAddress, 3) + s.grantee = make([]sdk.AccAddress, 6) // Send some funds to the new account. // Create new account in the keyring. @@ -86,7 +86,7 @@ func (s *IntegrationTestSuite) SetupSuite() { s.grantee[2] = s.createAccount("grantee3") // grant send authorization to grantee3 - out, err = CreateGrant(val, []string{ + _, err = CreateGrant(val, []string{ s.grantee[2].String(), "send", fmt.Sprintf("--%s=100stake", cli.FlagSpendLimit), @@ -98,6 +98,30 @@ func (s *IntegrationTestSuite) SetupSuite() { }) s.Require().NoError(err) + // Create new accounts in the keyring. + s.grantee[3] = s.createAccount("grantee4") + s.msgSendExec(s.grantee[3]) + + s.grantee[4] = s.createAccount("grantee5") + s.grantee[5] = s.createAccount("grantee6") + + // grant send authorization with allow list to grantee4 + out, err = CreateGrant( + val, + []string{ + s.grantee[3].String(), + "send", + fmt.Sprintf("--%s=100stake", cli.FlagSpendLimit), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%d", cli.FlagExpiration, time.Now().Add(time.Minute*time.Duration(120)).Unix()), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=%s", cli.FlagAllowList, s.grantee[4]), + }, + ) + s.Require().NoError(err) + err = s.network.WaitForNextBlock() s.Require().NoError(err) @@ -404,6 +428,40 @@ func (s *IntegrationTestSuite) TestCLITxGrantAuthorization() { false, "", }, + { + "Valid tx send authorization with allow list", + []string{ + grantee.String(), + "send", + fmt.Sprintf("--%s=100stake", cli.FlagSpendLimit), + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=%s", cli.FlagAllowList, s.grantee[1]), + }, + 0, + false, + "", + }, + { + "Invalid tx send authorization with duplicate allow list", + []string{ + grantee.String(), + "send", + fmt.Sprintf("--%s=100stake", cli.FlagSpendLimit), + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=%s", cli.FlagAllowList, fmt.Sprintf("%s,%s", s.grantee[1], s.grantee[1])), + }, + 0, + true, + "duplicate entry", + }, { "Valid tx generic authorization", []string{ @@ -877,6 +935,86 @@ func (s *IntegrationTestSuite) TestNewExecGrantAuthorized() { } } +func (s *IntegrationTestSuite) TestExecSendAuthzWithAllowList() { + val := s.network.Validators[0] + grantee := s.grantee[3] + allowedAddr := s.grantee[4] + notAllowedAddr := s.grantee[5] + twoHours := time.Now().Add(time.Minute * time.Duration(120)).Unix() + + _, err := CreateGrant( + val, + []string{ + grantee.String(), + "send", + fmt.Sprintf("--%s=100stake", cli.FlagSpendLimit), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagFrom, val.Address.String()), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%d", cli.FlagExpiration, twoHours), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=%s", cli.FlagAllowList, allowedAddr), + }, + ) + s.Require().NoError(err) + + tokens := sdk.NewCoins( + sdk.NewCoin("stake", sdk.NewInt(12)), + ) + + validGeneratedTx, err := banktestutil.MsgSendExec( + val.ClientCtx, + val.Address, + allowedAddr, + tokens, + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), + ) + s.Require().NoError(err) + execMsg := testutil.WriteToNewTempFile(s.T(), validGeneratedTx.String()) + + invalidGeneratedTx, err := banktestutil.MsgSendExec( + val.ClientCtx, + val.Address, + notAllowedAddr, + tokens, + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=true", flags.FlagGenerateOnly), + ) + s.Require().NoError(err) + execMsg1 := testutil.WriteToNewTempFile(s.T(), invalidGeneratedTx.String()) + + // test sending to allowed address + args := []string{ + execMsg.Name(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, grantee.String()), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + } + var response sdk.TxResponse + cmd := cli.NewCmdExecAuthorization() + out, err := clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) + s.Require().NoError(err) + s.Require().NoError(val.ClientCtx.Codec.UnmarshalJSON(out.Bytes(), &response), out.String()) + + // test sending to not allowed address + args = []string{ + execMsg1.Name(), + fmt.Sprintf("--%s=%s", flags.FlagFrom, grantee.String()), + fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastBlock), + fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), + fmt.Sprintf("--%s=true", flags.FlagSkipConfirmation), + } + out, err = clitestutil.ExecTestCLICmd(val.ClientCtx, cmd, args) + s.Require().NoError(err) + s.Contains(out.String(), fmt.Sprintf("cannot send to %s address", notAllowedAddr)) +} + func (s *IntegrationTestSuite) TestExecDelegateAuthorization() { val := s.network.Validators[0] grantee := s.grantee[0] diff --git a/x/authz/keeper/keeper_test.go b/x/authz/keeper/keeper_test.go index 9acde8aa01..4083888440 100644 --- a/x/authz/keeper/keeper_test.go +++ b/x/authz/keeper/keeper_test.go @@ -125,9 +125,10 @@ func (s *TestSuite) TestKeeperIter() { granteeAddr := addrs[1] granter2Addr := addrs[2] e := ctx.BlockTime().AddDate(1, 0, 0) + sendAuthz := banktypes.NewSendAuthorization(coins100, nil) - s.authzKeeper.SaveGrant(ctx, granteeAddr, granterAddr, banktypes.NewSendAuthorization(coins100), &e) - s.authzKeeper.SaveGrant(ctx, granteeAddr, granter2Addr, banktypes.NewSendAuthorization(coins100), &e) + s.authzKeeper.SaveGrant(ctx, granteeAddr, granterAddr, sendAuthz, &e) + s.authzKeeper.SaveGrant(ctx, granteeAddr, granter2Addr, sendAuthz, &e) s.authzKeeper.IterateGrants(ctx, func(granter, grantee sdk.AccAddress, grant authz.Grant) bool { s.Require().Equal(granteeAddr, grantee) @@ -144,7 +145,7 @@ func (s *TestSuite) TestDispatchAction() { granterAddr := addrs[0] granteeAddr := addrs[1] recipientAddr := addrs[2] - a := banktypes.NewSendAuthorization(coins100) + a := banktypes.NewSendAuthorization(coins100, nil) require.NoError(banktestutil.FundAccount(s.bankKeeper, s.ctx, granterAddr, coins1000)) diff --git a/x/authz/migrations/v046/store_test.go b/x/authz/migrations/v046/store_test.go index 0cefd65cf7..5d3241f452 100644 --- a/x/authz/migrations/v046/store_test.go +++ b/x/authz/migrations/v046/store_test.go @@ -35,6 +35,7 @@ func TestMigration(t *testing.T) { blockTime := ctx.BlockTime() oneDay := blockTime.AddDate(0, 0, 1) oneYear := blockTime.AddDate(1, 0, 0) + sendAuthz := banktypes.NewSendAuthorization(coins100, nil) grants := []struct { granter sdk.AccAddress @@ -47,7 +48,7 @@ func TestMigration(t *testing.T) { grantee1, sendMsgType, func() authz.Grant { - any, err := codectypes.NewAnyWithValue(banktypes.NewSendAuthorization(coins100)) + any, err := codectypes.NewAnyWithValue(sendAuthz) require.NoError(t, err) return authz.Grant{ Authorization: any, @@ -60,7 +61,7 @@ func TestMigration(t *testing.T) { grantee2, sendMsgType, func() authz.Grant { - any, err := codectypes.NewAnyWithValue(banktypes.NewSendAuthorization(coins100)) + any, err := codectypes.NewAnyWithValue(sendAuthz) require.NoError(t, err) return authz.Grant{ Authorization: any, diff --git a/x/authz/module/abci_test.go b/x/authz/module/abci_test.go index f1c447739a..cc6deae50a 100644 --- a/x/authz/module/abci_test.go +++ b/x/authz/module/abci_test.go @@ -44,9 +44,10 @@ func TestExpiredGrantsQueue(t *testing.T) { expiration := ctx.BlockTime().AddDate(0, 1, 0) expiration2 := expiration.AddDate(1, 0, 0) smallCoins := sdk.NewCoins(sdk.NewInt64Coin("stake", 10)) + sendAuthz := banktypes.NewSendAuthorization(smallCoins, nil) save := func(grantee sdk.AccAddress, exp *time.Time) { - err := authzKeeper.SaveGrant(ctx, grantee, granter, banktypes.NewSendAuthorization(smallCoins), exp) + err := authzKeeper.SaveGrant(ctx, grantee, granter, sendAuthz, exp) require.NoError(t, err, "Grant from %s", grantee.String()) } save(grantee1, &expiration) diff --git a/x/authz/msgs_test.go b/x/authz/msgs_test.go index f1b357a2b9..5d7eb3c39f 100644 --- a/x/authz/msgs_test.go +++ b/x/authz/msgs_test.go @@ -174,7 +174,8 @@ func TestAminoJSON(t *testing.T) { require.NoError(t, err) grant, err := authz.NewGrant(blockTime, authz.NewGenericAuthorization(typeURL), &expiresAt) require.NoError(t, err) - sendGrant, err := authz.NewGrant(blockTime, banktypes.NewSendAuthorization(sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000)))), &expiresAt) + sendAuthz := banktypes.NewSendAuthorization(sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000))), nil) + sendGrant, err := authz.NewGrant(blockTime, sendAuthz, &expiresAt) require.NoError(t, err) valAddr, err := sdk.ValAddressFromBech32("cosmosvaloper1xcy3els9ua75kdm783c3qu0rfa2eples6eavqq") require.NoError(t, err) diff --git a/x/authz/simulation/decoder_test.go b/x/authz/simulation/decoder_test.go index a5737ace5e..f197ac8c37 100644 --- a/x/authz/simulation/decoder_test.go +++ b/x/authz/simulation/decoder_test.go @@ -26,7 +26,8 @@ func TestDecodeStore(t *testing.T) { now := time.Now().UTC() e := now.Add(1) - grant, _ := authz.NewGrant(now, banktypes.NewSendAuthorization(sdk.NewCoins(sdk.NewInt64Coin("foo", 123))), &e) + sendAuthz := banktypes.NewSendAuthorization(sdk.NewCoins(sdk.NewInt64Coin("foo", 123)), nil) + grant, _ := authz.NewGrant(now, sendAuthz, &e) grantBz, err := cdc.Marshal(&grant) require.NoError(t, err) kvPairs := kv.Pairs{ diff --git a/x/authz/simulation/genesis.go b/x/authz/simulation/genesis.go index 8c4609b7bb..e1443683bb 100644 --- a/x/authz/simulation/genesis.go +++ b/x/authz/simulation/genesis.go @@ -37,7 +37,8 @@ func genGrant(r *rand.Rand, accounts []simtypes.Account, genT time.Time) []authz func generateRandomGrant(r *rand.Rand) *codectypes.Any { authorizations := make([]*codectypes.Any, 2) - authorizations[0] = newAnyAuthorization(banktypes.NewSendAuthorization(sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000))))) + sendAuthz := banktypes.NewSendAuthorization(sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000))), nil) + authorizations[0] = newAnyAuthorization(sendAuthz) authorizations[1] = newAnyAuthorization(authz.NewGenericAuthorization(sdk.MsgTypeURL(&v1.MsgSubmitProposal{}))) return authorizations[r.Intn(len(authorizations))] @@ -57,7 +58,9 @@ func RandomizedGenState(simState *module.SimulationState) { var grants []authz.GrantAuthorization simState.AppParams.GetOrGenerate( simState.Cdc, "authz", &grants, simState.Rand, - func(r *rand.Rand) { grants = genGrant(r, simState.Accounts, simState.GenTimestamp) }, + func(r *rand.Rand) { + grants = genGrant(r, simState.Accounts, simState.GenTimestamp) + }, ) authzGrantsGenesis := authz.NewGenesisState(grants) diff --git a/x/authz/simulation/operations.go b/x/authz/simulation/operations.go index fdea8f349e..5418d6468d 100644 --- a/x/authz/simulation/operations.go +++ b/x/authz/simulation/operations.go @@ -118,7 +118,9 @@ func SimulateMsgGrant(cdc *codec.ProtoCodec, ak authz.AccountKeeper, bk authz.Ba if !t1.Before(ctx.BlockTime()) { expiration = &t1 } - msg, err := authz.NewMsgGrant(granter.Address, grantee.Address, generateRandomAuthorization(r, spendLimit), expiration) + randomAuthz := generateRandomAuthorization(r, spendLimit) + + msg, err := authz.NewMsgGrant(granter.Address, grantee.Address, randomAuthz, expiration) if err != nil { return simtypes.NoOpMsg(authz.ModuleName, TypeMsgGrant, err.Error()), nil, err } @@ -148,7 +150,8 @@ func SimulateMsgGrant(cdc *codec.ProtoCodec, ak authz.AccountKeeper, bk authz.Ba func generateRandomAuthorization(r *rand.Rand, spendLimit sdk.Coins) authz.Authorization { authorizations := make([]authz.Authorization, 2) - authorizations[0] = banktype.NewSendAuthorization(spendLimit) + sendAuthz := banktype.NewSendAuthorization(spendLimit, nil) + authorizations[0] = sendAuthz authorizations[1] = authz.NewGenericAuthorization(sdk.MsgTypeURL(&banktype.MsgSend{})) return authorizations[r.Intn(len(authorizations))] diff --git a/x/authz/simulation/operations_test.go b/x/authz/simulation/operations_test.go index b599a7d880..f2e1ce6dc5 100644 --- a/x/authz/simulation/operations_test.go +++ b/x/authz/simulation/operations_test.go @@ -161,7 +161,7 @@ func (suite *SimTestSuite) TestSimulateRevoke() { granter := accounts[0] grantee := accounts[1] - a := banktypes.NewSendAuthorization(initCoins) + a := banktypes.NewSendAuthorization(initCoins, nil) expire := time.Now().Add(30 * time.Hour) err := suite.authzKeeper.SaveGrant(suite.ctx, grantee.Address, granter.Address, a, &expire) @@ -196,7 +196,7 @@ func (suite *SimTestSuite) TestSimulateExec() { granter := accounts[0] grantee := accounts[1] - a := banktypes.NewSendAuthorization(initCoins) + a := banktypes.NewSendAuthorization(initCoins, nil) expire := suite.ctx.BlockTime().Add(1 * time.Hour) err := suite.authzKeeper.SaveGrant(suite.ctx, grantee.Address, granter.Address, a, &expire) diff --git a/x/bank/types/authz.pb.go b/x/bank/types/authz.pb.go index 904a7f9ce7..8a2f6ae3a8 100644 --- a/x/bank/types/authz.pb.go +++ b/x/bank/types/authz.pb.go @@ -32,6 +32,11 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Since: cosmos-sdk 0.43 type SendAuthorization struct { SpendLimit github_com_cosmos_cosmos_sdk_types.Coins `protobuf:"bytes,1,rep,name=spend_limit,json=spendLimit,proto3,castrepeated=github.com/cosmos/cosmos-sdk/types.Coins" json:"spend_limit"` + // allow_list specifies an optional list of addresses to whom the grantee can send tokens on behalf of the + // granter. If omitted, any recipient is allowed. + // + // Since: cosmos-sdk 0.47 + AllowList []string `protobuf:"bytes,2,rep,name=allow_list,json=allowList,proto3" json:"allow_list,omitempty"` } func (m *SendAuthorization) Reset() { *m = SendAuthorization{} } @@ -74,6 +79,13 @@ func (m *SendAuthorization) GetSpendLimit() github_com_cosmos_cosmos_sdk_types.C return nil } +func (m *SendAuthorization) GetAllowList() []string { + if m != nil { + return m.AllowList + } + return nil +} + func init() { proto.RegisterType((*SendAuthorization)(nil), "cosmos.bank.v1beta1.SendAuthorization") } @@ -81,24 +93,25 @@ func init() { func init() { proto.RegisterFile("cosmos/bank/v1beta1/authz.proto", fileDescriptor_a4d2a37888ea779f) } var fileDescriptor_a4d2a37888ea779f = []byte{ - // 257 bytes of a gzipped FileDescriptorProto + // 285 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x92, 0x4f, 0xce, 0x2f, 0xce, 0xcd, 0x2f, 0xd6, 0x4f, 0x4a, 0xcc, 0xcb, 0xd6, 0x2f, 0x33, 0x4c, 0x4a, 0x2d, 0x49, 0x34, 0xd4, 0x4f, 0x2c, 0x2d, 0xc9, 0xa8, 0xd2, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x12, 0x86, 0x28, 0xd0, 0x03, 0x29, 0xd0, 0x83, 0x2a, 0x90, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xeb, 0x83, 0x58, 0x10, 0xa5, 0x52, 0x92, 0x10, 0xa5, 0xf1, 0x10, 0x09, 0xa8, 0x3e, 0x88, 0x94, 0x1c, 0xdc, 0x9a, - 0xe2, 0x54, 0xb8, 0x35, 0xc9, 0xf9, 0x99, 0x79, 0x10, 0x79, 0xa5, 0x29, 0x8c, 0x5c, 0x82, 0xc1, + 0xe2, 0x54, 0xb8, 0x35, 0xc9, 0xf9, 0x99, 0x79, 0x10, 0x79, 0xa5, 0xcd, 0x8c, 0x5c, 0x82, 0xc1, 0xa9, 0x79, 0x29, 0x8e, 0xa5, 0x25, 0x19, 0xf9, 0x45, 0x99, 0x55, 0x89, 0x25, 0x99, 0xf9, 0x79, 0x42, 0x39, 0x5c, 0xdc, 0xc5, 0x05, 0xa9, 0x79, 0x29, 0xf1, 0x39, 0x99, 0xb9, 0x99, 0x25, 0x12, 0x8c, 0x0a, 0xcc, 0x1a, 0xdc, 0x46, 0x92, 0x7a, 0x70, 0x17, 0x15, 0xa7, 0xc2, 0x5c, 0xa4, 0xe7, 0x9c, 0x9f, 0x99, 0xe7, 0x64, 0x70, 0xe2, 0x9e, 0x3c, 0xc3, 0xaa, 0xfb, 0xf2, 0x1a, 0xe9, 0x99, 0x25, 0x19, 0xa5, 0x49, 0x7a, 0xc9, 0xf9, 0xb9, 0x50, 0x67, 0x40, 0x29, 0xdd, 0xe2, 0x94, 0x6c, 0xfd, 0x92, 0xca, 0x82, 0xd4, 0x62, 0xb0, 0x86, 0xe2, 0x20, 0x2e, 0xb0, 0xf9, 0x3e, 0x20, 0xe3, - 0xad, 0x04, 0x4f, 0x6d, 0xd1, 0xe5, 0x45, 0x71, 0x80, 0x93, 0xf3, 0x89, 0x47, 0x72, 0x8c, 0x17, - 0x1e, 0xc9, 0x31, 0x3e, 0x78, 0x24, 0xc7, 0x38, 0xe1, 0xb1, 0x1c, 0xc3, 0x85, 0xc7, 0x72, 0x0c, - 0x37, 0x1e, 0xcb, 0x31, 0x44, 0x69, 0xe2, 0xb5, 0xa2, 0x02, 0x12, 0x9e, 0x60, 0x9b, 0x92, 0xd8, - 0xc0, 0x5e, 0x34, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x8c, 0x86, 0xc3, 0x2e, 0x6b, 0x01, 0x00, - 0x00, + 0x85, 0x64, 0xb9, 0xb8, 0x12, 0x73, 0x72, 0xf2, 0xcb, 0xe3, 0x73, 0x32, 0x8b, 0x4b, 0x24, 0x98, + 0x14, 0x98, 0x35, 0x38, 0x83, 0x38, 0xc1, 0x22, 0x3e, 0x99, 0xc5, 0x25, 0x56, 0x82, 0xa7, 0xb6, + 0xe8, 0xf2, 0xa2, 0xb8, 0xcf, 0xc9, 0xf9, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x1f, + 0x3c, 0x92, 0x63, 0x9c, 0xf0, 0x58, 0x8e, 0xe1, 0xc2, 0x63, 0x39, 0x86, 0x1b, 0x8f, 0xe5, 0x18, + 0xa2, 0x34, 0xf1, 0xba, 0xa0, 0x02, 0x12, 0xdc, 0x60, 0x87, 0x24, 0xb1, 0x81, 0x43, 0xc0, 0x18, + 0x10, 0x00, 0x00, 0xff, 0xff, 0xbd, 0x94, 0xb9, 0xfa, 0x8a, 0x01, 0x00, 0x00, } func (m *SendAuthorization) Marshal() (dAtA []byte, err error) { @@ -121,6 +134,15 @@ func (m *SendAuthorization) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.AllowList) > 0 { + for iNdEx := len(m.AllowList) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.AllowList[iNdEx]) + copy(dAtA[i:], m.AllowList[iNdEx]) + i = encodeVarintAuthz(dAtA, i, uint64(len(m.AllowList[iNdEx]))) + i-- + dAtA[i] = 0x12 + } + } if len(m.SpendLimit) > 0 { for iNdEx := len(m.SpendLimit) - 1; iNdEx >= 0; iNdEx-- { { @@ -161,6 +183,12 @@ func (m *SendAuthorization) Size() (n int) { n += 1 + l + sovAuthz(uint64(l)) } } + if len(m.AllowList) > 0 { + for _, s := range m.AllowList { + l = len(s) + n += 1 + l + sovAuthz(uint64(l)) + } + } return n } @@ -233,6 +261,38 @@ func (m *SendAuthorization) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AllowList", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowAuthz + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthAuthz + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthAuthz + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.AllowList = append(m.AllowList, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipAuthz(dAtA[iNdEx:]) diff --git a/x/bank/types/errors.go b/x/bank/types/errors.go index 3fca352de3..13af3dc4c7 100644 --- a/x/bank/types/errors.go +++ b/x/bank/types/errors.go @@ -12,5 +12,6 @@ var ( ErrSendDisabled = sdkerrors.Register(ModuleName, 5, "send transactions are disabled") ErrDenomMetadataNotFound = sdkerrors.Register(ModuleName, 6, "client denom metadata not found") ErrInvalidKey = sdkerrors.Register(ModuleName, 7, "invalid key") - ErrMultipleSenders = sdkerrors.Register(ModuleName, 8, "multiple senders not allowed") + ErrDuplicateEntry = sdkerrors.Register(ModuleName, 8, "duplicate entry") + ErrMultipleSenders = sdkerrors.Register(ModuleName, 9, "multiple senders not allowed") ) diff --git a/x/bank/types/send_authorization.go b/x/bank/types/send_authorization.go index 7eb90b5949..91b37def1c 100644 --- a/x/bank/types/send_authorization.go +++ b/x/bank/types/send_authorization.go @@ -6,13 +6,21 @@ import ( "github.com/cosmos/cosmos-sdk/x/authz" ) +// TODO: Revisit this once we have propoer gas fee framework. +// Tracking issues https://github.com/cosmos/cosmos-sdk/issues/9054, https://github.com/cosmos/cosmos-sdk/discussions/9072 +const gasCostPerIteration = uint64(10) + var _ authz.Authorization = &SendAuthorization{} // NewSendAuthorization creates a new SendAuthorization object. -func NewSendAuthorization(spendLimit sdk.Coins) *SendAuthorization { - return &SendAuthorization{ - SpendLimit: spendLimit, - } +func NewSendAuthorization(spendLimit sdk.Coins, allowed []sdk.AccAddress) *SendAuthorization { + allowedAddrs := toBech32Addresses(allowed) + + a := SendAuthorization{} + a.AllowList = allowedAddrs + a.SpendLimit = spendLimit + + return &a } // MsgTypeURL implements Authorization.MsgTypeURL. @@ -26,6 +34,7 @@ func (a SendAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptRes if !ok { return authz.AcceptResponse{}, sdkerrors.ErrInvalidType.Wrap("type mismatch") } + toAddr := mSend.ToAddress limitLeft, isNegative := a.SpendLimit.SafeSub(mSend.Amount...) if isNegative { return authz.AcceptResponse{}, sdkerrors.ErrInsufficientFunds.Wrapf("requested amount is more than spend limit") @@ -34,7 +43,22 @@ func (a SendAuthorization) Accept(ctx sdk.Context, msg sdk.Msg) (authz.AcceptRes return authz.AcceptResponse{Accept: true, Delete: true}, nil } - return authz.AcceptResponse{Accept: true, Delete: false, Updated: &SendAuthorization{SpendLimit: limitLeft}}, nil + isAddrExists := false + allowedList := a.GetAllowList() + + for _, addr := range allowedList { + ctx.GasMeter().ConsumeGas(gasCostPerIteration, "send authorization") + if addr == toAddr { + isAddrExists = true + break + } + } + + if len(allowedList) > 0 && !isAddrExists { + return authz.AcceptResponse{}, sdkerrors.ErrUnauthorized.Wrapf("cannot send to %s address", toAddr) + } + + return authz.AcceptResponse{Accept: true, Delete: false, Updated: &SendAuthorization{SpendLimit: limitLeft, AllowList: allowedList}}, nil } // ValidateBasic implements Authorization.ValidateBasic. @@ -45,5 +69,25 @@ func (a SendAuthorization) ValidateBasic() error { if !a.SpendLimit.IsAllPositive() { return sdkerrors.ErrInvalidCoins.Wrapf("spend limit must be positive") } + + found := make(map[string]bool, 0) + for i := 0; i < len(a.AllowList); i++ { + if found[a.AllowList[i]] { + return ErrDuplicateEntry + } + found[a.AllowList[i]] = true + } return nil } + +func toBech32Addresses(allowed []sdk.AccAddress) []string { + if len(allowed) == 0 { + return nil + } + + allowedAddrs := make([]string, len(allowed)) + for i, addr := range allowed { + allowedAddrs[i] = addr.String() + } + return allowedAddrs +} diff --git a/x/bank/types/send_authorization_test.go b/x/bank/types/send_authorization_test.go index 5e058317f1..0a2c6caa4e 100644 --- a/x/bank/types/send_authorization_test.go +++ b/x/bank/types/send_authorization_test.go @@ -1,6 +1,7 @@ package types_test import ( + fmt "fmt" "testing" "github.com/stretchr/testify/require" @@ -12,16 +13,19 @@ import ( ) var ( - coins1000 = sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000))) - coins500 = sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(500))) - fromAddr = sdk.AccAddress("_____from _____") - toAddr = sdk.AccAddress("_______to________") + coins1000 = sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1000))) + coins500 = sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(500))) + fromAddr = sdk.AccAddress("_____from _____") + toAddr = sdk.AccAddress("_______to________") + unknownAddr = sdk.AccAddress("_____unknown_____") ) func TestSendAuthorization(t *testing.T) { app := simapp.Setup(t, false) ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - authorization := types.NewSendAuthorization(coins1000) + allowList := make([]sdk.AccAddress, 1) + allowList[0] = toAddr + authorization := types.NewSendAuthorization(coins1000, nil) t.Log("verify authorization returns valid method name") require.Equal(t, authorization.MsgTypeURL(), "/cosmos.bank.v1beta1.MsgSend") @@ -36,7 +40,7 @@ func TestSendAuthorization(t *testing.T) { require.True(t, resp.Delete) require.Nil(t, resp.Updated) - authorization = types.NewSendAuthorization(coins1000) + authorization = types.NewSendAuthorization(coins1000, nil) require.Equal(t, authorization.MsgTypeURL(), "/cosmos.bank.v1beta1.MsgSend") require.NoError(t, authorization.ValidateBasic()) send = types.NewMsgSend(fromAddr, toAddr, coins500) @@ -47,7 +51,7 @@ func TestSendAuthorization(t *testing.T) { require.NoError(t, err) require.False(t, resp.Delete) require.NotNil(t, resp.Updated) - sendAuth := types.NewSendAuthorization(coins500) + sendAuth := types.NewSendAuthorization(coins500, nil) require.Equal(t, sendAuth.String(), resp.Updated.String()) t.Log("expect updated authorization nil after spending remaining amount") @@ -55,4 +59,17 @@ func TestSendAuthorization(t *testing.T) { require.NoError(t, err) require.True(t, resp.Delete) require.Nil(t, resp.Updated) + + t.Log("allow list and no address") + authzWithAllowList := types.NewSendAuthorization(coins1000, allowList) + require.Equal(t, authzWithAllowList.MsgTypeURL(), "/cosmos.bank.v1beta1.MsgSend") + require.NoError(t, authorization.ValidateBasic()) + send = types.NewMsgSend(fromAddr, unknownAddr, coins500) + require.NoError(t, authzWithAllowList.ValidateBasic()) + resp, err = authzWithAllowList.Accept(ctx, send) + require.False(t, resp.Accept) + require.False(t, resp.Delete) + require.Nil(t, resp.Updated) + require.Error(t, err) + require.Contains(t, err.Error(), fmt.Sprintf("cannot send to %s address", unknownAddr)) }