diff --git a/api/cosmos/bank/module/v1/module.pulsar.go b/api/cosmos/bank/module/v1/module.pulsar.go index 64e252a4af2..c7bbf4d1a93 100644 --- a/api/cosmos/bank/module/v1/module.pulsar.go +++ b/api/cosmos/bank/module/v1/module.pulsar.go @@ -59,10 +59,57 @@ func (x *_Module_1_list) IsValid() bool { return x.list != nil } +var _ protoreflect.List = (*_Module_3_list)(nil) + +type _Module_3_list struct { + list *[]string +} + +func (x *_Module_3_list) Len() int { + if x.list == nil { + return 0 + } + return len(*x.list) +} + +func (x *_Module_3_list) Get(i int) protoreflect.Value { + return protoreflect.ValueOfString((*x.list)[i]) +} + +func (x *_Module_3_list) Set(i int, value protoreflect.Value) { + valueUnwrapped := value.String() + concreteValue := valueUnwrapped + (*x.list)[i] = concreteValue +} + +func (x *_Module_3_list) Append(value protoreflect.Value) { + valueUnwrapped := value.String() + concreteValue := valueUnwrapped + *x.list = append(*x.list, concreteValue) +} + +func (x *_Module_3_list) AppendMutable() protoreflect.Value { + panic(fmt.Errorf("AppendMutable can not be called on message Module at list field SendHooksOrder as it is not of Message kind")) +} + +func (x *_Module_3_list) Truncate(n int) { + *x.list = (*x.list)[:n] +} + +func (x *_Module_3_list) NewElement() protoreflect.Value { + v := "" + return protoreflect.ValueOfString(v) +} + +func (x *_Module_3_list) IsValid() bool { + return x.list != nil +} + var ( md_Module protoreflect.MessageDescriptor fd_Module_blocked_module_accounts_override protoreflect.FieldDescriptor fd_Module_authority protoreflect.FieldDescriptor + fd_Module_send_hooks_order protoreflect.FieldDescriptor ) func init() { @@ -70,6 +117,7 @@ func init() { md_Module = File_cosmos_bank_module_v1_module_proto.Messages().ByName("Module") fd_Module_blocked_module_accounts_override = md_Module.Fields().ByName("blocked_module_accounts_override") fd_Module_authority = md_Module.Fields().ByName("authority") + fd_Module_send_hooks_order = md_Module.Fields().ByName("send_hooks_order") } var _ protoreflect.Message = (*fastReflection_Module)(nil) @@ -149,6 +197,12 @@ func (x *fastReflection_Module) Range(f func(protoreflect.FieldDescriptor, proto return } } + if len(x.SendHooksOrder) != 0 { + value := protoreflect.ValueOfList(&_Module_3_list{list: &x.SendHooksOrder}) + if !f(fd_Module_send_hooks_order, value) { + return + } + } } // Has reports whether a field is populated. @@ -168,6 +222,8 @@ func (x *fastReflection_Module) Has(fd protoreflect.FieldDescriptor) bool { return len(x.BlockedModuleAccountsOverride) != 0 case "cosmos.bank.module.v1.Module.authority": return x.Authority != "" + case "cosmos.bank.module.v1.Module.send_hooks_order": + return len(x.SendHooksOrder) != 0 default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.module.v1.Module")) @@ -188,6 +244,8 @@ func (x *fastReflection_Module) Clear(fd protoreflect.FieldDescriptor) { x.BlockedModuleAccountsOverride = nil case "cosmos.bank.module.v1.Module.authority": x.Authority = "" + case "cosmos.bank.module.v1.Module.send_hooks_order": + x.SendHooksOrder = nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.module.v1.Module")) @@ -213,6 +271,12 @@ func (x *fastReflection_Module) Get(descriptor protoreflect.FieldDescriptor) pro case "cosmos.bank.module.v1.Module.authority": value := x.Authority return protoreflect.ValueOfString(value) + case "cosmos.bank.module.v1.Module.send_hooks_order": + if len(x.SendHooksOrder) == 0 { + return protoreflect.ValueOfList(&_Module_3_list{}) + } + listValue := &_Module_3_list{list: &x.SendHooksOrder} + return protoreflect.ValueOfList(listValue) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.module.v1.Module")) @@ -239,6 +303,10 @@ func (x *fastReflection_Module) Set(fd protoreflect.FieldDescriptor, value proto x.BlockedModuleAccountsOverride = *clv.list case "cosmos.bank.module.v1.Module.authority": x.Authority = value.Interface().(string) + case "cosmos.bank.module.v1.Module.send_hooks_order": + lv := value.List() + clv := lv.(*_Module_3_list) + x.SendHooksOrder = *clv.list default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.module.v1.Module")) @@ -265,6 +333,12 @@ func (x *fastReflection_Module) Mutable(fd protoreflect.FieldDescriptor) protore } value := &_Module_1_list{list: &x.BlockedModuleAccountsOverride} return protoreflect.ValueOfList(value) + case "cosmos.bank.module.v1.Module.send_hooks_order": + if x.SendHooksOrder == nil { + x.SendHooksOrder = []string{} + } + value := &_Module_3_list{list: &x.SendHooksOrder} + return protoreflect.ValueOfList(value) case "cosmos.bank.module.v1.Module.authority": panic(fmt.Errorf("field authority of message cosmos.bank.module.v1.Module is not mutable")) default: @@ -285,6 +359,9 @@ func (x *fastReflection_Module) NewField(fd protoreflect.FieldDescriptor) protor return protoreflect.ValueOfList(&_Module_1_list{list: &list}) case "cosmos.bank.module.v1.Module.authority": return protoreflect.ValueOfString("") + case "cosmos.bank.module.v1.Module.send_hooks_order": + list := []string{} + return protoreflect.ValueOfList(&_Module_3_list{list: &list}) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: cosmos.bank.module.v1.Module")) @@ -364,6 +441,12 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods { if l > 0 { n += 1 + l + runtime.Sov(uint64(l)) } + if len(x.SendHooksOrder) > 0 { + for _, s := range x.SendHooksOrder { + l = len(s) + n += 1 + l + runtime.Sov(uint64(l)) + } + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -393,6 +476,15 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods { i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if len(x.SendHooksOrder) > 0 { + for iNdEx := len(x.SendHooksOrder) - 1; iNdEx >= 0; iNdEx-- { + i -= len(x.SendHooksOrder[iNdEx]) + copy(dAtA[i:], x.SendHooksOrder[iNdEx]) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.SendHooksOrder[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } if len(x.Authority) > 0 { i -= len(x.Authority) copy(dAtA[i:], x.Authority) @@ -522,6 +614,38 @@ func (x *fastReflection_Module) ProtoMethods() *protoiface.Methods { } x.Authority = string(dAtA[iNdEx:postIndex]) iNdEx = postIndex + case 3: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field SendHooksOrder", 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.SendHooksOrder = append(x.SendHooksOrder, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -582,6 +706,10 @@ type Module struct { BlockedModuleAccountsOverride []string `protobuf:"bytes,1,rep,name=blocked_module_accounts_override,json=blockedModuleAccountsOverride,proto3" json:"blocked_module_accounts_override,omitempty"` // authority defines the custom module authority. If not set, defaults to the governance module. Authority string `protobuf:"bytes,2,opt,name=authority,proto3" json:"authority,omitempty"` + // hooks_order specifies the order of bank hooks and should be a list + // of module names which provide a send hooks instance. If no order is + // provided, then hooks will be applied in alphabetical order of module names. + SendHooksOrder []string `protobuf:"bytes,3,rep,name=send_hooks_order,json=sendHooksOrder,proto3" json:"send_hooks_order,omitempty"` } func (x *Module) Reset() { @@ -618,6 +746,13 @@ func (x *Module) GetAuthority() string { return "" } +func (x *Module) GetSendHooksOrder() []string { + if x != nil { + return x.SendHooksOrder + } + return nil +} + var File_cosmos_bank_module_v1_module_proto protoreflect.FileDescriptor var file_cosmos_bank_module_v1_module_proto_rawDesc = []byte{ @@ -626,31 +761,34 @@ var file_cosmos_bank_module_v1_module_proto_rawDesc = []byte{ 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x15, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x6e, 0x6b, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x1a, 0x20, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2f, 0x61, 0x70, 0x70, 0x2f, 0x76, 0x31, 0x61, 0x6c, 0x70, 0x68, 0x61, 0x31, - 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x9c, 0x01, + 0x2f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0xc6, 0x01, 0x0a, 0x06, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x12, 0x47, 0x0a, 0x20, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x5f, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5f, 0x61, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x5f, 0x6f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x1d, 0x62, 0x6c, 0x6f, 0x63, 0x6b, 0x65, 0x64, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x41, 0x63, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x73, 0x4f, 0x76, 0x65, 0x72, 0x72, 0x69, 0x64, 0x65, 0x12, 0x1c, 0x0a, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x18, 0x02, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x3a, - 0x2b, 0xba, 0xc0, 0x96, 0xda, 0x01, 0x25, 0x0a, 0x23, 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, 0x78, 0x2f, 0x62, 0x61, 0x6e, 0x6b, 0x42, 0xd0, 0x01, 0x0a, - 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x6e, 0x6b, - 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x4d, 0x6f, 0x64, 0x75, - 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, 0x2f, 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, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2f, 0x76, - 0x31, 0x3b, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x42, 0x4d, - 0xaa, 0x02, 0x15, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x6e, 0x6b, 0x2e, 0x4d, - 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x15, 0x43, 0x6f, 0x73, 0x6d, 0x6f, - 0x73, 0x5c, 0x42, 0x61, 0x6e, 0x6b, 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5c, 0x56, 0x31, - 0xe2, 0x02, 0x21, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x42, 0x61, 0x6e, 0x6b, 0x5c, 0x4d, - 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5c, 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, - 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x18, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x42, - 0x61, 0x6e, 0x6b, 0x3a, 0x3a, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, + 0x28, 0x0a, 0x10, 0x73, 0x65, 0x6e, 0x64, 0x5f, 0x68, 0x6f, 0x6f, 0x6b, 0x73, 0x5f, 0x6f, 0x72, + 0x64, 0x65, 0x72, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0e, 0x73, 0x65, 0x6e, 0x64, 0x48, + 0x6f, 0x6f, 0x6b, 0x73, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x3a, 0x2b, 0xba, 0xc0, 0x96, 0xda, 0x01, + 0x25, 0x0a, 0x23, 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, + 0x78, 0x2f, 0x62, 0x61, 0x6e, 0x6b, 0x42, 0xd0, 0x01, 0x0a, 0x19, 0x63, 0x6f, 0x6d, 0x2e, 0x63, + 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x62, 0x61, 0x6e, 0x6b, 0x2e, 0x6d, 0x6f, 0x64, 0x75, 0x6c, + 0x65, 0x2e, 0x76, 0x31, 0x42, 0x0b, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x50, 0x72, 0x6f, 0x74, + 0x6f, 0x50, 0x01, 0x5a, 0x2f, 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, 0x6d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2f, 0x76, 0x31, 0x3b, 0x6d, 0x6f, 0x64, 0x75, + 0x6c, 0x65, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x43, 0x42, 0x4d, 0xaa, 0x02, 0x15, 0x43, 0x6f, 0x73, + 0x6d, 0x6f, 0x73, 0x2e, 0x42, 0x61, 0x6e, 0x6b, 0x2e, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x2e, + 0x56, 0x31, 0xca, 0x02, 0x15, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x5c, 0x42, 0x61, 0x6e, 0x6b, + 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5c, 0x56, 0x31, 0xe2, 0x02, 0x21, 0x43, 0x6f, 0x73, + 0x6d, 0x6f, 0x73, 0x5c, 0x42, 0x61, 0x6e, 0x6b, 0x5c, 0x4d, 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x5c, + 0x56, 0x31, 0x5c, 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, + 0x18, 0x43, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x3a, 0x3a, 0x42, 0x61, 0x6e, 0x6b, 0x3a, 0x3a, 0x4d, + 0x6f, 0x64, 0x75, 0x6c, 0x65, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/go.work.example b/go.work.example index 973f20df4fe..6974add9a38 100644 --- a/go.work.example +++ b/go.work.example @@ -18,4 +18,4 @@ use ( ./tools/confix ./x/nft ./x/circuit -) +) \ No newline at end of file diff --git a/proto/cosmos/bank/module/v1/module.proto b/proto/cosmos/bank/module/v1/module.proto index 51e3158b686..fa19623362b 100644 --- a/proto/cosmos/bank/module/v1/module.proto +++ b/proto/cosmos/bank/module/v1/module.proto @@ -17,4 +17,9 @@ message Module { // authority defines the custom module authority. If not set, defaults to the governance module. string authority = 2; + + // hooks_order specifies the order of bank hooks and should be a list + // of module names which provide a send hooks instance. If no order is + // provided, then hooks will be applied in alphabetical order of module names. + repeated string send_hooks_order = 3; } \ No newline at end of file diff --git a/simapp/app.go b/simapp/app.go index 8a8819a84ce..353f573b5fc 100644 --- a/simapp/app.go +++ b/simapp/app.go @@ -301,6 +301,11 @@ func NewSimApp( BlockedAddresses(), authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) + app.BankKeeper.SetHooks( + banktypes.NewMultiSendHooks( + // register the send hooks here + ), + ) app.StakingKeeper = stakingkeeper.NewKeeper( appCodec, keys[stakingtypes.StoreKey], app.AccountKeeper, app.BankKeeper, authtypes.NewModuleAddress(govtypes.ModuleName).String(), ) diff --git a/x/bank/keeper/send.go b/x/bank/keeper/send.go index 28263d7af32..3cdd819274c 100644 --- a/x/bank/keeper/send.go +++ b/x/bank/keeper/send.go @@ -60,6 +60,7 @@ type BaseSendKeeper struct { // the address capable of executing a MsgUpdateParams message. Typically, this // should be the x/gov module account. authority string + hooks types.SendCoinsHooks } func NewBaseSendKeeper( @@ -79,6 +80,7 @@ func NewBaseSendKeeper( ak: ak, storeKey: storeKey, blockedAddrs: blockedAddrs, + hooks: nil, authority: authority, } } @@ -125,6 +127,26 @@ func (k BaseSendKeeper) SetParams(ctx sdk.Context, params types.Params) error { return nil } +// Hooks gets the hooks for the base send keeper. +func (k *BaseSendKeeper) Hooks() types.SendCoinsHooks { + if k.hooks == nil { + // return a no-op implementation if no hooks are set + return types.MultiSendCoinsHooks{} + } + + return k.hooks +} + +// SetHooks Set the send coins hooks. In contrast to other receivers, this method must take a pointer due to nature +// of the hooks interface and SDK start up sequence. +func (k *BaseSendKeeper) SetHooks(sh types.SendCoinsHooks) { + if k.hooks != nil { + panic("cannot set base send keeper hooks twice") + } + + k.hooks = sh +} + // InputOutputCoins performs multi-send functionality. It accepts a series of // inputs that correspond to a series of outputs. It returns an error if the // inputs and outputs don't line up or if any single transfer of tokens fails. @@ -141,6 +163,11 @@ func (k BaseSendKeeper) InputOutputCoins(ctx sdk.Context, inputs []types.Input, return err } + // Run send hooks if present. + if err := k.Hooks().BeforeSendCoins(ctx, inAddress, inAddress, in.Coins); err != nil { + return err + } + err = k.subUnlockedCoins(ctx, inAddress, in.Coins) if err != nil { return err @@ -160,6 +187,11 @@ func (k BaseSendKeeper) InputOutputCoins(ctx sdk.Context, inputs []types.Input, return err } + // Run After Send hooks if present. + if err := k.Hooks().AfterSendCoins(ctx, outAddress, outAddress, out.Coins); err != nil { + return err + } + if err := k.addCoins(ctx, outAddress, out.Coins); err != nil { return err } @@ -189,6 +221,11 @@ func (k BaseSendKeeper) InputOutputCoins(ctx sdk.Context, inputs []types.Input, // SendCoins transfers amt coins from a sending account to a receiving account. // An error is returned upon failure. func (k BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amt sdk.Coins) error { + // Run send hooks if present. + if err := k.Hooks().BeforeSendCoins(ctx, fromAddr, toAddr, amt); err != nil { + return err + } + err := k.subUnlockedCoins(ctx, fromAddr, amt) if err != nil { return err @@ -209,6 +246,11 @@ func (k BaseSendKeeper) SendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAd k.ak.SetAccount(ctx, k.ak.NewAccountWithAddress(ctx, toAddr)) } + // Run After Send hooks if present. + if err := k.Hooks().BeforeSendCoins(ctx, fromAddr, toAddr, amt); err != nil { + return err + } + // bech32 encoding is expensive! Only do it once for fromAddr fromAddrString := fromAddr.String() ctx.EventManager().EmitEvents(sdk.Events{ diff --git a/x/bank/module.go b/x/bank/module.go index 0b732929642..803a2bbd23c 100644 --- a/x/bank/module.go +++ b/x/bank/module.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "sort" "time" modulev1 "cosmossdk.io/api/cosmos/bank/module/v1" @@ -12,6 +13,7 @@ import ( gwruntime "github.com/grpc-ecosystem/grpc-gateway/runtime" "github.com/spf13/cobra" abci "github.com/tendermint/tendermint/abci/types" + "golang.org/x/exp/maps" "github.com/cosmos/cosmos-sdk/client" "github.com/cosmos/cosmos-sdk/codec" @@ -202,6 +204,7 @@ func (am AppModule) WeightedOperations(simState module.SimulationState) []simtyp func init() { appmodule.Register(&modulev1.Module{}, appmodule.Provide(ProvideModule), + appmodule.Invoke(InvokeSetStakingHooks), ) } @@ -260,3 +263,43 @@ func ProvideModule(in BankInputs) BankOutputs { return BankOutputs{BankKeeper: bankKeeper, Module: m} } + +func InvokeSetStakingHooks( + config *modulev1.Module, + keeper *keeper.BaseSendKeeper, + sendCoinsHooks map[string]types.SendCoinsHooksWrapper, +) error { + // all arguments to invokers are optional, so we need to check if they are nil + if keeper == nil || config == nil { + return nil + } + + modNames := maps.Keys(sendCoinsHooks) + order := config.SendHooksOrder + // if order is empty, use default order + if len(order) == 0 { + order = modNames + sort.Strings(order) + } + + if len(order) != len(modNames) { + return fmt.Errorf("len(hooks_order: %v) != len(hooks modules: %v)", order, modNames) + } + + if len(modNames) == 0 { + return nil + } + + var multiHooks types.MultiSendCoinsHooks + for _, modName := range order { + hook, ok := sendCoinsHooks[modName] + if !ok { + return fmt.Errorf("can't find staking hooks for module %s", modName) + } + + multiHooks = append(multiHooks, hook) + } + + keeper.SetHooks(multiHooks) + return nil +} diff --git a/x/bank/types/expected_keepers.go b/x/bank/types/expected_keepers.go index a56aeace3b5..6f985783caa 100644 --- a/x/bank/types/expected_keepers.go +++ b/x/bank/types/expected_keepers.go @@ -27,3 +27,12 @@ type AccountKeeper interface { SetModuleAccount(ctx sdk.Context, macc sdk.ModuleAccountI) GetModulePermissions() map[string]types.PermissionsForAddress } + +type SendCoinsHooks interface { + BeforeSendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amount sdk.Coins) error + AfterSendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amount sdk.Coins) error +} + +type SendCoinsHooksWrapper struct{ SendCoinsHooks } + +func (SendCoinsHooksWrapper) IsOnePerModuleType() {} diff --git a/x/bank/types/hooks.go b/x/bank/types/hooks.go new file mode 100644 index 00000000000..744f886d00d --- /dev/null +++ b/x/bank/types/hooks.go @@ -0,0 +1,31 @@ +package types + +import sdk "github.com/cosmos/cosmos-sdk/types" + +var _ SendCoinsHooks = &MultiSendCoinsHooks{} + +type MultiSendCoinsHooks []SendCoinsHooks + +func NewMultiSendCoinsHooks(hooks ...SendCoinsHooks) MultiSendCoinsHooks { + return hooks +} + +func (h MultiSendCoinsHooks) BeforeSendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amount sdk.Coins) error { + for i := range h { + if err := h[i].BeforeSendCoins(ctx, fromAddr, toAddr, amount); err != nil { + return err + } + } + + return nil +} + +func (h MultiSendCoinsHooks) AfterSendCoins(ctx sdk.Context, fromAddr sdk.AccAddress, toAddr sdk.AccAddress, amount sdk.Coins) error { + for i := range h { + if err := h[i].AfterSendCoins(ctx, fromAddr, toAddr, amount); err != nil { + return err + } + } + + return nil +}