From 83c3766ebba1ced46efe8ed40ae4013ffd9d8206 Mon Sep 17 00:00:00 2001 From: Redouane Lakrache Date: Thu, 15 Aug 2024 09:51:48 +0200 Subject: [PATCH] [Supplier] Implement supplier revenue share (#729) ## Summary This PR implements rev share feature for supplier rewards. * Adds a `Supplier.RevShare` slice property. * Adds the corresponding supplier staking configuration parser along with its tests. * Updates the tests to include the mandatory `Supplier.RevShare` property. * Includes revshare testing in the tokenomics test suite. * Updates the supplier staking config documentation _Note: ~1100LOC are protobuf autogenerated code._ ## Issue - #496 ## Type of change Select one or more: - [x] New feature, functionality or library - [ ] Bug fix - [ ] Code health or cleanup - [ ] Documentation - [ ] Other (specify) ## Testing **Documentation changes** (only if making doc changes) - [x] `make docusaurus_start`; only needed if you make doc changes **Local Testing** (only if making code changes) - [x] **Unit Tests**: `make go_develop_and_test` - [x] **LocalNet E2E Tests**: `make test_e2e` - See [quickstart guide](https://dev.poktroll.com/developer_guide/quickstart) for instructions **PR Testing** (only if making code changes) - [ ] **DevNet E2E Tests**: Add the `devnet-test-e2e` label to the PR. - **THIS IS VERY EXPENSIVE**, so only do it after all the reviews are complete. - Optionally run `make trigger_ci` if you want to re-trigger tests without any code changes - If tests fail, try re-running failed tests only using the GitHub UI as shown [here](https://github.com/pokt-network/poktroll/assets/1892194/607984e9-0615-4569-9452-4c730190c1d2) ## Sanity Checklist - [x] I have tested my changes using the available tooling - [x] I have commented my code - [x] I have performed a self-review of my own code; both comments & source code - [ ] I create and reference any new tickets, if applicable - [ ] I have left TODOs throughout the codebase, if applicable ## Summary by CodeRabbit ## Summary by CodeRabbit - **New Features** - Introduced flexible revenue sharing configurations for suppliers, allowing for detailed management of service revenue shares. - Added new fields to configuration files and structures to support default and service-specific revenue share percentages. - Implemented stricter validation for revenue share entries to ensure compliance with defined criteria. - **Bug Fixes** - Enhanced error handling related to invalid revenue shares and owner addresses in configurations. - **Tests** - Expanded test coverage to include revenue sharing logic, ensuring robust validation of configurations and expected behaviors. - Added new test cases for various scenarios, including configurations with default and service-specific revenue shares. --------- Co-authored-by: Daniel Olshansky --- api/poktroll/shared/service.pulsar.go | 733 +++++++++++++++++- config.yml | 6 + .../configs/supplier_staking_config.md | 80 ++ .../config/supplier1_stake_config.yaml | 14 + proto/poktroll/shared/service.proto | 7 + testutil/integration/app.go | 6 + testutil/keeper/tokenomics.go | 16 +- x/shared/helpers/service_configs.go | 56 ++ x/shared/types/errors.go | 1 + x/shared/types/service.pb.go | 342 +++++++- x/supplier/config/supplier_configs_reader.go | 46 +- .../config/supplier_configs_reader_test.go | 243 +++++- .../keeper/msg_server_stake_supplier_test.go | 6 + .../msg_server_unstake_supplier_test.go | 6 + x/supplier/keeper/unbond_suppliers.go | 4 +- x/supplier/types/genesis_test.go | 24 + .../types/message_stake_supplier_test.go | 111 ++- x/supplier/types/message_unstake_supplier.go | 4 +- .../keeper_settle_pending_claims_test.go | 8 +- x/tokenomics/keeper/token_logic_modules.go | 174 ++++- .../keeper/token_logic_modules_test.go | 72 +- x/tokenomics/types/errors.go | 3 +- 22 files changed, 1828 insertions(+), 134 deletions(-) diff --git a/api/poktroll/shared/service.pulsar.go b/api/poktroll/shared/service.pulsar.go index 6c54446e8..9ca7b82a1 100644 --- a/api/poktroll/shared/service.pulsar.go +++ b/api/poktroll/shared/service.pulsar.go @@ -2,6 +2,7 @@ package shared import ( + binary "encoding/binary" fmt "fmt" _ "github.com/cosmos/cosmos-proto" runtime "github.com/cosmos/cosmos-proto/runtime" @@ -9,6 +10,7 @@ import ( protoiface "google.golang.org/protobuf/runtime/protoiface" protoimpl "google.golang.org/protobuf/runtime/protoimpl" io "io" + math "math" reflect "reflect" sync "sync" ) @@ -1095,10 +1097,62 @@ func (x *_SupplierServiceConfig_2_list) IsValid() bool { return x.list != nil } +var _ protoreflect.List = (*_SupplierServiceConfig_3_list)(nil) + +type _SupplierServiceConfig_3_list struct { + list *[]*ServiceRevenueShare +} + +func (x *_SupplierServiceConfig_3_list) Len() int { + if x.list == nil { + return 0 + } + return len(*x.list) +} + +func (x *_SupplierServiceConfig_3_list) Get(i int) protoreflect.Value { + return protoreflect.ValueOfMessage((*x.list)[i].ProtoReflect()) +} + +func (x *_SupplierServiceConfig_3_list) Set(i int, value protoreflect.Value) { + valueUnwrapped := value.Message() + concreteValue := valueUnwrapped.Interface().(*ServiceRevenueShare) + (*x.list)[i] = concreteValue +} + +func (x *_SupplierServiceConfig_3_list) Append(value protoreflect.Value) { + valueUnwrapped := value.Message() + concreteValue := valueUnwrapped.Interface().(*ServiceRevenueShare) + *x.list = append(*x.list, concreteValue) +} + +func (x *_SupplierServiceConfig_3_list) AppendMutable() protoreflect.Value { + v := new(ServiceRevenueShare) + *x.list = append(*x.list, v) + return protoreflect.ValueOfMessage(v.ProtoReflect()) +} + +func (x *_SupplierServiceConfig_3_list) Truncate(n int) { + for i := n; i < len(*x.list); i++ { + (*x.list)[i] = nil + } + *x.list = (*x.list)[:n] +} + +func (x *_SupplierServiceConfig_3_list) NewElement() protoreflect.Value { + v := new(ServiceRevenueShare) + return protoreflect.ValueOfMessage(v.ProtoReflect()) +} + +func (x *_SupplierServiceConfig_3_list) IsValid() bool { + return x.list != nil +} + var ( md_SupplierServiceConfig protoreflect.MessageDescriptor fd_SupplierServiceConfig_service protoreflect.FieldDescriptor fd_SupplierServiceConfig_endpoints protoreflect.FieldDescriptor + fd_SupplierServiceConfig_rev_share protoreflect.FieldDescriptor ) func init() { @@ -1106,6 +1160,7 @@ func init() { md_SupplierServiceConfig = File_poktroll_shared_service_proto.Messages().ByName("SupplierServiceConfig") fd_SupplierServiceConfig_service = md_SupplierServiceConfig.Fields().ByName("service") fd_SupplierServiceConfig_endpoints = md_SupplierServiceConfig.Fields().ByName("endpoints") + fd_SupplierServiceConfig_rev_share = md_SupplierServiceConfig.Fields().ByName("rev_share") } var _ protoreflect.Message = (*fastReflection_SupplierServiceConfig)(nil) @@ -1185,6 +1240,12 @@ func (x *fastReflection_SupplierServiceConfig) Range(f func(protoreflect.FieldDe return } } + if len(x.RevShare) != 0 { + value := protoreflect.ValueOfList(&_SupplierServiceConfig_3_list{list: &x.RevShare}) + if !f(fd_SupplierServiceConfig_rev_share, value) { + return + } + } } // Has reports whether a field is populated. @@ -1204,6 +1265,8 @@ func (x *fastReflection_SupplierServiceConfig) Has(fd protoreflect.FieldDescript return x.Service != nil case "poktroll.shared.SupplierServiceConfig.endpoints": return len(x.Endpoints) != 0 + case "poktroll.shared.SupplierServiceConfig.rev_share": + return len(x.RevShare) != 0 default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.SupplierServiceConfig")) @@ -1224,6 +1287,8 @@ func (x *fastReflection_SupplierServiceConfig) Clear(fd protoreflect.FieldDescri x.Service = nil case "poktroll.shared.SupplierServiceConfig.endpoints": x.Endpoints = nil + case "poktroll.shared.SupplierServiceConfig.rev_share": + x.RevShare = nil default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.SupplierServiceConfig")) @@ -1249,6 +1314,12 @@ func (x *fastReflection_SupplierServiceConfig) Get(descriptor protoreflect.Field } listValue := &_SupplierServiceConfig_2_list{list: &x.Endpoints} return protoreflect.ValueOfList(listValue) + case "poktroll.shared.SupplierServiceConfig.rev_share": + if len(x.RevShare) == 0 { + return protoreflect.ValueOfList(&_SupplierServiceConfig_3_list{}) + } + listValue := &_SupplierServiceConfig_3_list{list: &x.RevShare} + return protoreflect.ValueOfList(listValue) default: if descriptor.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.SupplierServiceConfig")) @@ -1275,6 +1346,10 @@ func (x *fastReflection_SupplierServiceConfig) Set(fd protoreflect.FieldDescript lv := value.List() clv := lv.(*_SupplierServiceConfig_2_list) x.Endpoints = *clv.list + case "poktroll.shared.SupplierServiceConfig.rev_share": + lv := value.List() + clv := lv.(*_SupplierServiceConfig_3_list) + x.RevShare = *clv.list default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.SupplierServiceConfig")) @@ -1306,6 +1381,12 @@ func (x *fastReflection_SupplierServiceConfig) Mutable(fd protoreflect.FieldDesc } value := &_SupplierServiceConfig_2_list{list: &x.Endpoints} return protoreflect.ValueOfList(value) + case "poktroll.shared.SupplierServiceConfig.rev_share": + if x.RevShare == nil { + x.RevShare = []*ServiceRevenueShare{} + } + value := &_SupplierServiceConfig_3_list{list: &x.RevShare} + return protoreflect.ValueOfList(value) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.SupplierServiceConfig")) @@ -1325,6 +1406,9 @@ func (x *fastReflection_SupplierServiceConfig) NewField(fd protoreflect.FieldDes case "poktroll.shared.SupplierServiceConfig.endpoints": list := []*SupplierEndpoint{} return protoreflect.ValueOfList(&_SupplierServiceConfig_2_list{list: &list}) + case "poktroll.shared.SupplierServiceConfig.rev_share": + list := []*ServiceRevenueShare{} + return protoreflect.ValueOfList(&_SupplierServiceConfig_3_list{list: &list}) default: if fd.IsExtension() { panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.SupplierServiceConfig")) @@ -1404,6 +1488,12 @@ func (x *fastReflection_SupplierServiceConfig) ProtoMethods() *protoiface.Method n += 1 + l + runtime.Sov(uint64(l)) } } + if len(x.RevShare) > 0 { + for _, e := range x.RevShare { + l = options.Size(e) + n += 1 + l + runtime.Sov(uint64(l)) + } + } if x.unknownFields != nil { n += len(x.unknownFields) } @@ -1433,6 +1523,22 @@ func (x *fastReflection_SupplierServiceConfig) ProtoMethods() *protoiface.Method i -= len(x.unknownFields) copy(dAtA[i:], x.unknownFields) } + if len(x.RevShare) > 0 { + for iNdEx := len(x.RevShare) - 1; iNdEx >= 0; iNdEx-- { + encoded, err := options.Marshal(x.RevShare[iNdEx]) + if err != nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, err + } + i -= len(encoded) + copy(dAtA[i:], encoded) + i = runtime.EncodeVarint(dAtA, i, uint64(len(encoded))) + i-- + dAtA[i] = 0x1a + } + } if len(x.Endpoints) > 0 { for iNdEx := len(x.Endpoints) - 1; iNdEx >= 0; iNdEx-- { encoded, err := options.Marshal(x.Endpoints[iNdEx]) @@ -1582,6 +1688,40 @@ func (x *fastReflection_SupplierServiceConfig) ProtoMethods() *protoiface.Method return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err } iNdEx = postIndex + case 3: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field RevShare", wireType) + } + var msglen int + 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++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + postIndex := iNdEx + msglen + 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.RevShare = append(x.RevShare, &ServiceRevenueShare{}) + if err := options.Unmarshal(dAtA[iNdEx:postIndex], x.RevShare[len(x.RevShare)-1]); err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := runtime.Skip(dAtA[iNdEx:]) @@ -2223,6 +2363,467 @@ func (x *fastReflection_SupplierEndpoint) ProtoMethods() *protoiface.Methods { } } +var ( + md_ServiceRevenueShare protoreflect.MessageDescriptor + fd_ServiceRevenueShare_address protoreflect.FieldDescriptor + fd_ServiceRevenueShare_rev_share_percentage protoreflect.FieldDescriptor +) + +func init() { + file_poktroll_shared_service_proto_init() + md_ServiceRevenueShare = File_poktroll_shared_service_proto.Messages().ByName("ServiceRevenueShare") + fd_ServiceRevenueShare_address = md_ServiceRevenueShare.Fields().ByName("address") + fd_ServiceRevenueShare_rev_share_percentage = md_ServiceRevenueShare.Fields().ByName("rev_share_percentage") +} + +var _ protoreflect.Message = (*fastReflection_ServiceRevenueShare)(nil) + +type fastReflection_ServiceRevenueShare ServiceRevenueShare + +func (x *ServiceRevenueShare) ProtoReflect() protoreflect.Message { + return (*fastReflection_ServiceRevenueShare)(x) +} + +func (x *ServiceRevenueShare) slowProtoReflect() protoreflect.Message { + mi := &file_poktroll_shared_service_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +var _fastReflection_ServiceRevenueShare_messageType fastReflection_ServiceRevenueShare_messageType +var _ protoreflect.MessageType = fastReflection_ServiceRevenueShare_messageType{} + +type fastReflection_ServiceRevenueShare_messageType struct{} + +func (x fastReflection_ServiceRevenueShare_messageType) Zero() protoreflect.Message { + return (*fastReflection_ServiceRevenueShare)(nil) +} +func (x fastReflection_ServiceRevenueShare_messageType) New() protoreflect.Message { + return new(fastReflection_ServiceRevenueShare) +} +func (x fastReflection_ServiceRevenueShare_messageType) Descriptor() protoreflect.MessageDescriptor { + return md_ServiceRevenueShare +} + +// Descriptor returns message descriptor, which contains only the protobuf +// type information for the message. +func (x *fastReflection_ServiceRevenueShare) Descriptor() protoreflect.MessageDescriptor { + return md_ServiceRevenueShare +} + +// Type returns the message type, which encapsulates both Go and protobuf +// type information. If the Go type information is not needed, +// it is recommended that the message descriptor be used instead. +func (x *fastReflection_ServiceRevenueShare) Type() protoreflect.MessageType { + return _fastReflection_ServiceRevenueShare_messageType +} + +// New returns a newly allocated and mutable empty message. +func (x *fastReflection_ServiceRevenueShare) New() protoreflect.Message { + return new(fastReflection_ServiceRevenueShare) +} + +// Interface unwraps the message reflection interface and +// returns the underlying ProtoMessage interface. +func (x *fastReflection_ServiceRevenueShare) Interface() protoreflect.ProtoMessage { + return (*ServiceRevenueShare)(x) +} + +// Range iterates over every populated field in an undefined order, +// calling f for each field descriptor and value encountered. +// Range returns immediately if f returns false. +// While iterating, mutating operations may only be performed +// on the current field descriptor. +func (x *fastReflection_ServiceRevenueShare) Range(f func(protoreflect.FieldDescriptor, protoreflect.Value) bool) { + if x.Address != "" { + value := protoreflect.ValueOfString(x.Address) + if !f(fd_ServiceRevenueShare_address, value) { + return + } + } + if x.RevSharePercentage != float32(0) || math.Signbit(float64(x.RevSharePercentage)) { + value := protoreflect.ValueOfFloat32(x.RevSharePercentage) + if !f(fd_ServiceRevenueShare_rev_share_percentage, value) { + return + } + } +} + +// Has reports whether a field is populated. +// +// Some fields have the property of nullability where it is possible to +// distinguish between the default value of a field and whether the field +// was explicitly populated with the default value. Singular message fields, +// member fields of a oneof, and proto2 scalar fields are nullable. Such +// fields are populated only if explicitly set. +// +// In other cases (aside from the nullable cases above), +// a proto3 scalar field is populated if it contains a non-zero value, and +// a repeated field is populated if it is non-empty. +func (x *fastReflection_ServiceRevenueShare) Has(fd protoreflect.FieldDescriptor) bool { + switch fd.FullName() { + case "poktroll.shared.ServiceRevenueShare.address": + return x.Address != "" + case "poktroll.shared.ServiceRevenueShare.rev_share_percentage": + return x.RevSharePercentage != float32(0) || math.Signbit(float64(x.RevSharePercentage)) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.ServiceRevenueShare")) + } + panic(fmt.Errorf("message poktroll.shared.ServiceRevenueShare does not contain field %s", fd.FullName())) + } +} + +// Clear clears the field such that a subsequent Has call reports false. +// +// Clearing an extension field clears both the extension type and value +// associated with the given field number. +// +// Clear is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_ServiceRevenueShare) Clear(fd protoreflect.FieldDescriptor) { + switch fd.FullName() { + case "poktroll.shared.ServiceRevenueShare.address": + x.Address = "" + case "poktroll.shared.ServiceRevenueShare.rev_share_percentage": + x.RevSharePercentage = float32(0) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.ServiceRevenueShare")) + } + panic(fmt.Errorf("message poktroll.shared.ServiceRevenueShare does not contain field %s", fd.FullName())) + } +} + +// Get retrieves the value for a field. +// +// For unpopulated scalars, it returns the default value, where +// the default value of a bytes scalar is guaranteed to be a copy. +// For unpopulated composite types, it returns an empty, read-only view +// of the value; to obtain a mutable reference, use Mutable. +func (x *fastReflection_ServiceRevenueShare) Get(descriptor protoreflect.FieldDescriptor) protoreflect.Value { + switch descriptor.FullName() { + case "poktroll.shared.ServiceRevenueShare.address": + value := x.Address + return protoreflect.ValueOfString(value) + case "poktroll.shared.ServiceRevenueShare.rev_share_percentage": + value := x.RevSharePercentage + return protoreflect.ValueOfFloat32(value) + default: + if descriptor.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.ServiceRevenueShare")) + } + panic(fmt.Errorf("message poktroll.shared.ServiceRevenueShare does not contain field %s", descriptor.FullName())) + } +} + +// Set stores the value for a field. +// +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType. +// When setting a composite type, it is unspecified whether the stored value +// aliases the source's memory in any way. If the composite value is an +// empty, read-only value, then it panics. +// +// Set is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_ServiceRevenueShare) Set(fd protoreflect.FieldDescriptor, value protoreflect.Value) { + switch fd.FullName() { + case "poktroll.shared.ServiceRevenueShare.address": + x.Address = value.Interface().(string) + case "poktroll.shared.ServiceRevenueShare.rev_share_percentage": + x.RevSharePercentage = float32(value.Float()) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.ServiceRevenueShare")) + } + panic(fmt.Errorf("message poktroll.shared.ServiceRevenueShare does not contain field %s", fd.FullName())) + } +} + +// Mutable returns a mutable reference to a composite type. +// +// If the field is unpopulated, it may allocate a composite value. +// For a field belonging to a oneof, it implicitly clears any other field +// that may be currently set within the same oneof. +// For extension fields, it implicitly stores the provided ExtensionType +// if not already stored. +// It panics if the field does not contain a composite type. +// +// Mutable is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_ServiceRevenueShare) Mutable(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "poktroll.shared.ServiceRevenueShare.address": + panic(fmt.Errorf("field address of message poktroll.shared.ServiceRevenueShare is not mutable")) + case "poktroll.shared.ServiceRevenueShare.rev_share_percentage": + panic(fmt.Errorf("field rev_share_percentage of message poktroll.shared.ServiceRevenueShare is not mutable")) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.ServiceRevenueShare")) + } + panic(fmt.Errorf("message poktroll.shared.ServiceRevenueShare does not contain field %s", fd.FullName())) + } +} + +// NewField returns a new value that is assignable to the field +// for the given descriptor. For scalars, this returns the default value. +// For lists, maps, and messages, this returns a new, empty, mutable value. +func (x *fastReflection_ServiceRevenueShare) NewField(fd protoreflect.FieldDescriptor) protoreflect.Value { + switch fd.FullName() { + case "poktroll.shared.ServiceRevenueShare.address": + return protoreflect.ValueOfString("") + case "poktroll.shared.ServiceRevenueShare.rev_share_percentage": + return protoreflect.ValueOfFloat32(float32(0)) + default: + if fd.IsExtension() { + panic(fmt.Errorf("proto3 declared messages do not support extensions: poktroll.shared.ServiceRevenueShare")) + } + panic(fmt.Errorf("message poktroll.shared.ServiceRevenueShare does not contain field %s", fd.FullName())) + } +} + +// WhichOneof reports which field within the oneof is populated, +// returning nil if none are populated. +// It panics if the oneof descriptor does not belong to this message. +func (x *fastReflection_ServiceRevenueShare) WhichOneof(d protoreflect.OneofDescriptor) protoreflect.FieldDescriptor { + switch d.FullName() { + default: + panic(fmt.Errorf("%s is not a oneof field in poktroll.shared.ServiceRevenueShare", d.FullName())) + } + panic("unreachable") +} + +// GetUnknown retrieves the entire list of unknown fields. +// The caller may only mutate the contents of the RawFields +// if the mutated bytes are stored back into the message with SetUnknown. +func (x *fastReflection_ServiceRevenueShare) GetUnknown() protoreflect.RawFields { + return x.unknownFields +} + +// SetUnknown stores an entire list of unknown fields. +// The raw fields must be syntactically valid according to the wire format. +// An implementation may panic if this is not the case. +// Once stored, the caller must not mutate the content of the RawFields. +// An empty RawFields may be passed to clear the fields. +// +// SetUnknown is a mutating operation and unsafe for concurrent use. +func (x *fastReflection_ServiceRevenueShare) SetUnknown(fields protoreflect.RawFields) { + x.unknownFields = fields +} + +// IsValid reports whether the message is valid. +// +// An invalid message is an empty, read-only value. +// +// An invalid message often corresponds to a nil pointer of the concrete +// message type, but the details are implementation dependent. +// Validity is not part of the protobuf data model, and may not +// be preserved in marshaling or other operations. +func (x *fastReflection_ServiceRevenueShare) IsValid() bool { + return x != nil +} + +// ProtoMethods returns optional fastReflectionFeature-path implementations of various operations. +// This method may return nil. +// +// The returned methods type is identical to +// "google.golang.org/protobuf/runtime/protoiface".Methods. +// Consult the protoiface package documentation for details. +func (x *fastReflection_ServiceRevenueShare) ProtoMethods() *protoiface.Methods { + size := func(input protoiface.SizeInput) protoiface.SizeOutput { + x := input.Message.Interface().(*ServiceRevenueShare) + if x == nil { + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: 0, + } + } + options := runtime.SizeInputToOptions(input) + _ = options + var n int + var l int + _ = l + l = len(x.Address) + if l > 0 { + n += 1 + l + runtime.Sov(uint64(l)) + } + if x.RevSharePercentage != 0 || math.Signbit(float64(x.RevSharePercentage)) { + n += 5 + } + if x.unknownFields != nil { + n += len(x.unknownFields) + } + return protoiface.SizeOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Size: n, + } + } + + marshal := func(input protoiface.MarshalInput) (protoiface.MarshalOutput, error) { + x := input.Message.Interface().(*ServiceRevenueShare) + if x == nil { + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + options := runtime.MarshalInputToOptions(input) + _ = options + size := options.Size(x) + dAtA := make([]byte, size) + i := len(dAtA) + _ = i + var l int + _ = l + if x.unknownFields != nil { + i -= len(x.unknownFields) + copy(dAtA[i:], x.unknownFields) + } + if x.RevSharePercentage != 0 || math.Signbit(float64(x.RevSharePercentage)) { + i -= 4 + binary.LittleEndian.PutUint32(dAtA[i:], uint32(math.Float32bits(float32(x.RevSharePercentage)))) + i-- + dAtA[i] = 0x15 + } + if len(x.Address) > 0 { + i -= len(x.Address) + copy(dAtA[i:], x.Address) + i = runtime.EncodeVarint(dAtA, i, uint64(len(x.Address))) + i-- + dAtA[i] = 0xa + } + if input.Buf != nil { + input.Buf = append(input.Buf, dAtA...) + } else { + input.Buf = dAtA + } + return protoiface.MarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Buf: input.Buf, + }, nil + } + unmarshal := func(input protoiface.UnmarshalInput) (protoiface.UnmarshalOutput, error) { + x := input.Message.Interface().(*ServiceRevenueShare) + if x == nil { + return protoiface.UnmarshalOutput{ + NoUnkeyedLiterals: input.NoUnkeyedLiterals, + Flags: input.Flags, + }, nil + } + options := runtime.UnmarshalInputToOptions(input) + _ = options + dAtA := input.Buf + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire 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++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: ServiceRevenueShare: wiretype end group for non-group") + } + if fieldNum <= 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: ServiceRevenueShare: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field Address", 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.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 5 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, fmt.Errorf("proto: wrong wireType = %d for field RevSharePercentage", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + v = uint32(binary.LittleEndian.Uint32(dAtA[iNdEx:])) + iNdEx += 4 + x.RevSharePercentage = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := runtime.Skip(dAtA[iNdEx:]) + if err != nil { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, runtime.ErrInvalidLength + } + if (iNdEx + skippy) > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + if !options.DiscardUnknown { + x.unknownFields = append(x.unknownFields, dAtA[iNdEx:iNdEx+skippy]...) + } + iNdEx += skippy + } + } + + if iNdEx > l { + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, io.ErrUnexpectedEOF + } + return protoiface.UnmarshalOutput{NoUnkeyedLiterals: input.NoUnkeyedLiterals, Flags: input.Flags}, nil + } + return &protoiface.Methods{ + NoUnkeyedLiterals: struct{}{}, + Flags: protoiface.SupportMarshalDeterministic | protoiface.SupportUnmarshalDiscardUnknown, + Size: size, + Marshal: marshal, + Unmarshal: unmarshal, + Merge: nil, + CheckInitialized: nil, + } +} + var ( md_ConfigOption protoreflect.MessageDescriptor fd_ConfigOption_key protoreflect.FieldDescriptor @@ -2245,7 +2846,7 @@ func (x *ConfigOption) ProtoReflect() protoreflect.Message { } func (x *ConfigOption) slowProtoReflect() protoreflect.Message { - mi := &file_poktroll_shared_service_proto_msgTypes[4] + mi := &file_poktroll_shared_service_proto_msgTypes[5] if protoimpl.UnsafeEnabled && x != nil { ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) if ms.LoadMessageInfo() == nil { @@ -2924,8 +3525,9 @@ type SupplierServiceConfig struct { unknownFields protoimpl.UnknownFields // TODO_MAINNET: Avoid embedding the full Service because we just need the ID. - Service *Service `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` // The Service for which the supplier is configured - Endpoints []*SupplierEndpoint `protobuf:"bytes,2,rep,name=endpoints,proto3" json:"endpoints,omitempty"` // List of endpoints for the service + Service *Service `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` // The Service for which the supplier is configured + Endpoints []*SupplierEndpoint `protobuf:"bytes,2,rep,name=endpoints,proto3" json:"endpoints,omitempty"` // List of endpoints for the service + RevShare []*ServiceRevenueShare `protobuf:"bytes,3,rep,name=rev_share,json=revShare,proto3" json:"rev_share,omitempty"` // List of revenue share configurations for the service } func (x *SupplierServiceConfig) Reset() { @@ -2962,6 +3564,13 @@ func (x *SupplierServiceConfig) GetEndpoints() []*SupplierEndpoint { return nil } +func (x *SupplierServiceConfig) GetRevShare() []*ServiceRevenueShare { + if x != nil { + return x.RevShare + } + return nil +} + // SupplierEndpoint message to hold service configuration details type SupplierEndpoint struct { state protoimpl.MessageState @@ -3014,6 +3623,50 @@ func (x *SupplierEndpoint) GetConfigs() []*ConfigOption { return nil } +// ServiceRevenueShare message to hold revenue share configuration details +type ServiceRevenueShare struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` // The Bech32 address of the revenue share recipient + RevSharePercentage float32 `protobuf:"fixed32,2,opt,name=rev_share_percentage,json=revSharePercentage,proto3" json:"rev_share_percentage,omitempty"` // The percentage of revenue share the recipient will receive +} + +func (x *ServiceRevenueShare) Reset() { + *x = ServiceRevenueShare{} + if protoimpl.UnsafeEnabled { + mi := &file_poktroll_shared_service_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ServiceRevenueShare) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ServiceRevenueShare) ProtoMessage() {} + +// Deprecated: Use ServiceRevenueShare.ProtoReflect.Descriptor instead. +func (*ServiceRevenueShare) Descriptor() ([]byte, []int) { + return file_poktroll_shared_service_proto_rawDescGZIP(), []int{4} +} + +func (x *ServiceRevenueShare) GetAddress() string { + if x != nil { + return x.Address + } + return "" +} + +func (x *ServiceRevenueShare) GetRevSharePercentage() float32 { + if x != nil { + return x.RevSharePercentage + } + return 0 +} + // Key-value wrapper for config options, as proto maps can't be keyed by enums type ConfigOption struct { state protoimpl.MessageState @@ -3027,7 +3680,7 @@ type ConfigOption struct { func (x *ConfigOption) Reset() { *x = ConfigOption{} if protoimpl.UnsafeEnabled { - mi := &file_poktroll_shared_service_proto_msgTypes[4] + mi := &file_poktroll_shared_service_proto_msgTypes[5] ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) ms.StoreMessageInfo(mi) } @@ -3041,7 +3694,7 @@ func (*ConfigOption) ProtoMessage() {} // Deprecated: Use ConfigOption.ProtoReflect.Descriptor instead. func (*ConfigOption) Descriptor() ([]byte, []int) { - return file_poktroll_shared_service_proto_rawDescGZIP(), []int{4} + return file_poktroll_shared_service_proto_rawDescGZIP(), []int{5} } func (x *ConfigOption) GetKey() ConfigOptions { @@ -3081,7 +3734,7 @@ var file_poktroll_shared_service_proto_rawDesc = []byte{ 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, - 0x65, 0x22, 0x8c, 0x01, 0x0a, 0x15, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x53, 0x65, + 0x65, 0x22, 0xcf, 0x01, 0x0a, 0x15, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x32, 0x0a, 0x07, 0x73, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x53, @@ -3090,16 +3743,28 @@ var file_poktroll_shared_service_proto_rawDesc = []byte{ 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x52, 0x09, 0x65, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x73, - 0x22, 0x92, 0x01, 0x0a, 0x10, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x72, 0x45, 0x6e, 0x64, - 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x01, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x33, 0x0a, 0x08, 0x72, 0x70, 0x63, 0x5f, 0x74, - 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, 0x6f, 0x6b, 0x74, - 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x52, 0x50, 0x43, 0x54, - 0x79, 0x70, 0x65, 0x52, 0x07, 0x72, 0x70, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, 0x37, 0x0a, 0x07, - 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, - 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, - 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x07, 0x63, 0x6f, - 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0x56, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4f, + 0x12, 0x41, 0x0a, 0x09, 0x72, 0x65, 0x76, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x24, 0x2e, 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, + 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x52, 0x65, 0x76, + 0x65, 0x6e, 0x75, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x52, 0x08, 0x72, 0x65, 0x76, 0x53, 0x68, + 0x61, 0x72, 0x65, 0x22, 0x92, 0x01, 0x0a, 0x10, 0x53, 0x75, 0x70, 0x70, 0x6c, 0x69, 0x65, 0x72, + 0x45, 0x6e, 0x64, 0x70, 0x6f, 0x69, 0x6e, 0x74, 0x12, 0x10, 0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c, 0x12, 0x33, 0x0a, 0x08, 0x72, 0x70, + 0x63, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x18, 0x2e, 0x70, + 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x52, + 0x50, 0x43, 0x54, 0x79, 0x70, 0x65, 0x52, 0x07, 0x72, 0x70, 0x63, 0x54, 0x79, 0x70, 0x65, 0x12, + 0x37, 0x0a, 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, + 0x32, 0x1d, 0x2e, 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, + 0x65, 0x64, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x52, + 0x07, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x73, 0x22, 0x7b, 0x0a, 0x13, 0x53, 0x65, 0x72, 0x76, + 0x69, 0x63, 0x65, 0x52, 0x65, 0x76, 0x65, 0x6e, 0x75, 0x65, 0x53, 0x68, 0x61, 0x72, 0x65, 0x12, + 0x32, 0x0a, 0x07, 0x61, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, + 0x42, 0x18, 0xd2, 0xb4, 0x2d, 0x14, 0x63, 0x6f, 0x73, 0x6d, 0x6f, 0x73, 0x2e, 0x41, 0x64, 0x64, + 0x72, 0x65, 0x73, 0x73, 0x53, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x61, 0x64, 0x64, 0x72, + 0x65, 0x73, 0x73, 0x12, 0x30, 0x0a, 0x14, 0x72, 0x65, 0x76, 0x5f, 0x73, 0x68, 0x61, 0x72, 0x65, + 0x5f, 0x70, 0x65, 0x72, 0x63, 0x65, 0x6e, 0x74, 0x61, 0x67, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, + 0x02, 0x52, 0x12, 0x72, 0x65, 0x76, 0x53, 0x68, 0x61, 0x72, 0x65, 0x50, 0x65, 0x72, 0x63, 0x65, + 0x6e, 0x74, 0x61, 0x67, 0x65, 0x22, 0x56, 0x0a, 0x0c, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x30, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x1e, 0x2e, 0x70, 0x6f, 0x6b, 0x74, 0x72, 0x6f, 0x6c, 0x6c, 0x2e, 0x73, 0x68, 0x61, 0x72, 0x65, 0x64, 0x2e, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x4f, 0x70, 0x74, 0x69, 0x6f, @@ -3139,7 +3804,7 @@ func file_poktroll_shared_service_proto_rawDescGZIP() []byte { } var file_poktroll_shared_service_proto_enumTypes = make([]protoimpl.EnumInfo, 2) -var file_poktroll_shared_service_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_poktroll_shared_service_proto_msgTypes = make([]protoimpl.MessageInfo, 6) var file_poktroll_shared_service_proto_goTypes = []interface{}{ (RPCType)(0), // 0: poktroll.shared.RPCType (ConfigOptions)(0), // 1: poktroll.shared.ConfigOptions @@ -3147,20 +3812,22 @@ var file_poktroll_shared_service_proto_goTypes = []interface{}{ (*ApplicationServiceConfig)(nil), // 3: poktroll.shared.ApplicationServiceConfig (*SupplierServiceConfig)(nil), // 4: poktroll.shared.SupplierServiceConfig (*SupplierEndpoint)(nil), // 5: poktroll.shared.SupplierEndpoint - (*ConfigOption)(nil), // 6: poktroll.shared.ConfigOption + (*ServiceRevenueShare)(nil), // 6: poktroll.shared.ServiceRevenueShare + (*ConfigOption)(nil), // 7: poktroll.shared.ConfigOption } var file_poktroll_shared_service_proto_depIdxs = []int32{ 2, // 0: poktroll.shared.ApplicationServiceConfig.service:type_name -> poktroll.shared.Service 2, // 1: poktroll.shared.SupplierServiceConfig.service:type_name -> poktroll.shared.Service 5, // 2: poktroll.shared.SupplierServiceConfig.endpoints:type_name -> poktroll.shared.SupplierEndpoint - 0, // 3: poktroll.shared.SupplierEndpoint.rpc_type:type_name -> poktroll.shared.RPCType - 6, // 4: poktroll.shared.SupplierEndpoint.configs:type_name -> poktroll.shared.ConfigOption - 1, // 5: poktroll.shared.ConfigOption.key:type_name -> poktroll.shared.ConfigOptions - 6, // [6:6] is the sub-list for method output_type - 6, // [6:6] is the sub-list for method input_type - 6, // [6:6] is the sub-list for extension type_name - 6, // [6:6] is the sub-list for extension extendee - 0, // [0:6] is the sub-list for field type_name + 6, // 3: poktroll.shared.SupplierServiceConfig.rev_share:type_name -> poktroll.shared.ServiceRevenueShare + 0, // 4: poktroll.shared.SupplierEndpoint.rpc_type:type_name -> poktroll.shared.RPCType + 7, // 5: poktroll.shared.SupplierEndpoint.configs:type_name -> poktroll.shared.ConfigOption + 1, // 6: poktroll.shared.ConfigOption.key:type_name -> poktroll.shared.ConfigOptions + 7, // [7:7] is the sub-list for method output_type + 7, // [7:7] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name } func init() { file_poktroll_shared_service_proto_init() } @@ -3218,6 +3885,18 @@ func file_poktroll_shared_service_proto_init() { } } file_poktroll_shared_service_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ServiceRevenueShare); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_poktroll_shared_service_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { switch v := v.(*ConfigOption); i { case 0: return &v.state @@ -3236,7 +3915,7 @@ func file_poktroll_shared_service_proto_init() { GoPackagePath: reflect.TypeOf(x{}).PkgPath(), RawDescriptor: file_poktroll_shared_service_proto_rawDesc, NumEnums: 2, - NumMessages: 5, + NumMessages: 6, NumExtensions: 0, NumServices: 0, }, diff --git a/config.yml b/config.yml index ed65e4ae4..765970617 100644 --- a/config.yml +++ b/config.yml @@ -178,12 +178,18 @@ genesis: - configs: [] rpc_type: JSON_RPC url: http://relayminer1:8545 + rev_share: + - address: pokt19a3t4yunp0dlpfjrp7qwnzwlrzd5fzs2gjaaaj + rev_share_percentage: "100" - service: id: ollama endpoints: - configs: [] rpc_type: REST url: http://relayminer1:8545 + rev_share: + - address: pokt19a3t4yunp0dlpfjrp7qwnzwlrzd5fzs2gjaaaj + rev_share_percentage: "100" stake: # NB: This value should be exactly 1upokt smaller than the value in # `application1_stake_config.yaml` so that the stake command causes a state change. diff --git a/docusaurus/docs/operate/configs/supplier_staking_config.md b/docusaurus/docs/operate/configs/supplier_staking_config.md index 1bd9afde0..b83b7529a 100644 --- a/docusaurus/docs/operate/configs/supplier_staking_config.md +++ b/docusaurus/docs/operate/configs/supplier_staking_config.md @@ -11,12 +11,15 @@ a stake transaction required to provide RPC services on Pocket Network._ - [Reference Example](#reference-example) - [Usage](#usage) - [Configuration](#configuration) + - [`owner_address`](#owner_address) - [`stake_amount`](#stake_amount) + - [`default_rev_share_percent`](#default_rev_share_percent) - [`services`](#services) - [`service_id`](#service_id) - [`endpoints`](#endpoints) - [`publicly_exposed_url`](#publicly_exposed_url) - [`rpc_type`](#rpc_type) + - [`rev_share_percent`](#rev_share_percent) ## Reference Example @@ -43,6 +46,18 @@ poktrolld tx supplier stake-supplier \ ## Configuration +### `owner_address` + +_`Required`_, _`Non-empty`_ + +```yaml +owner_address:
+``` + +The `owner_address` is used as the unique shareholder address for the `Supplier` +if none of `default_rev_share_percent` or `rev_share_percent` is defined in the +configuration file. + ### `stake_amount` _`Required`_, _`Non-empty`_ @@ -72,6 +87,43 @@ sybil or flooding attacks on the network. ::: +### `default_rev_share_percent` + +_`Optional`_, _`Non-empty`_ + +```yaml +default_rev_share_percent: + : +``` + +`default_rev_share_percent` is an optional map that defines the default the revenue +share percentage for all the `service`s that do not have their specific `rev_share_percent` +entry defined. + +This field is useful if the `Supplier` owner wants to set a default revenue share +for all the `service`s entries that do not provide one. This way, the operator +does not have to repeat the same values for each `service` in the `services` section. + +This map cannot be empty but can be omitted, in which case the default revenue +share falls back to `100%` of the rewards allocated to the `Supplier`'s `owner_address`. + +:::note + +The `shareholder_address`s MUST be valid Pocket addresses. + +The revenue share values MUST be strictly positive floats with a maximum value of +100 and a total sum of 100 across all the `shareholder_address`es. + +::: + +:::warning + +If `default_rev_share_percent` is defined, then the `owner_address` of the `Supplier` +MUST be **explicitly** defined in the map if they are to receive a share on the +`service`s that fall back to the default. + +::: + ### `services` _`Required`_, _`Non-empty`_ @@ -82,6 +134,8 @@ services: endpoints: - publicly_exposed_url: ://: rpc_type: + rev_share_percent: + : ``` `services` define the list of services that the `Supplier` wants to provide. @@ -154,3 +208,29 @@ endpoints: ::: The `rpc_type` MUST be one of the [supported types found here](https://github.com/pokt-network/poktroll/tree/main/pkg/relayer/config/types.go#L8). + +#### `rev_share_percent` + +`rev_share_percent` is an optional map that defines the `service`'s specific revenue +share percentage. + +It overrides the `default_rev_share_percent` if defined for the `service`. + +This map cannot be empty but can be omitted, in which case it falls back to the +`default_rev_share_percent` top-level configuration entry. + +:::note + +The `shareholder_address`s MUST be valid Pocket addresses. + +The revenue share values MUST be strictly positive decimals with a maximum value +of 100 and a total sum of 100 across all the `shareholder_address`es. + +::: + +:::warning + +If `rev_share_percent` is defined for a `service`, then the `owner_address` of the +`Supplier` MUST be **explicitly** defined in the map if they are to receive a share. + +::: \ No newline at end of file diff --git a/localnet/poktrolld/config/supplier1_stake_config.yaml b/localnet/poktrolld/config/supplier1_stake_config.yaml index 91978a6e7..48c5c3351 100644 --- a/localnet/poktrolld/config/supplier1_stake_config.yaml +++ b/localnet/poktrolld/config/supplier1_stake_config.yaml @@ -1,6 +1,14 @@ +owner_address: pokt1mrqt5f7qh8uxs27cjm9t7v9e74a9vvdnq5jva4 # NB: The stake amount is exactly 1upokt greater than the value in genesis.json # so that the stake command causes a state change. stake_amount: 1000069upokt +# If default_rev_share_percent is omitted, the owner receives 100% of the rewards. +# default_rev_share_percent cannot be empty - it must either be omitted completely +# or include at least one item. +default_rev_share_percent: + # The sum of all shares must equal 100%. Staking will fail otherwise. + - pokt1mrqt5f7qh8uxs27cjm9t7v9e74a9vvdnq5jva4: 80.5 + - pokt1eeeksh2tvkh7wzmfrljnhw4wrhs55lcuvmekkw: 19.5 services: # The endpoint URL for the Anvil service is provided via the RelayMiner. # The RelayMiner acts as a proxy, forwarding requests to the actual Anvil data node behind it. @@ -10,6 +18,12 @@ services: - publicly_exposed_url: http://relayminer1:8545 rpc_type: JSON_RPC - service_id: ollama + # Service specific rev share, if rev_share_percent is omitted for a specific + # service, default_rev_share_percent is used. + # The sum of all shares must equal 100%. Staking will fail otherwise. + rev_share_percent: + - pokt1mrqt5f7qh8uxs27cjm9t7v9e74a9vvdnq5jva4: 50 + - pokt1eeeksh2tvkh7wzmfrljnhw4wrhs55lcuvmekkw: 50 endpoints: - publicly_exposed_url: http://relayminer1:8545 rpc_type: REST diff --git a/proto/poktroll/shared/service.proto b/proto/poktroll/shared/service.proto index cd07c6086..4777b0ccb 100644 --- a/proto/poktroll/shared/service.proto +++ b/proto/poktroll/shared/service.proto @@ -43,6 +43,7 @@ message SupplierServiceConfig { // TODO_MAINNET: Avoid embedding the full Service because we just need the ID. Service service = 1; // The Service for which the supplier is configured repeated SupplierEndpoint endpoints = 2; // List of endpoints for the service + repeated ServiceRevenueShare rev_share = 3; // List of revenue share configurations for the service // TODO_MAINNET: There is an opportunity for supplier to advertise the min // they're willing to earn for a certain configuration/price, but this is outside of scope. } @@ -54,6 +55,12 @@ message SupplierEndpoint { repeated ConfigOption configs = 3; // Additional configuration options for the endpoint } +// ServiceRevenueShare message to hold revenue share configuration details +message ServiceRevenueShare { + string address = 1 [(cosmos_proto.scalar) = "cosmos.AddressString"]; // The Bech32 address of the revenue share recipient + float rev_share_percentage = 2; // The percentage of revenue share the recipient will receive +} + // Enum to define RPC types enum RPCType { UNKNOWN_RPC = 0; // Undefined RPC type diff --git a/testutil/integration/app.go b/testutil/integration/app.go index 9bb861c60..ccd56beba 100644 --- a/testutil/integration/app.go +++ b/testutil/integration/app.go @@ -534,6 +534,12 @@ func NewCompleteIntegrationApp(t *testing.T) *App { Stake: &supplierStake, Services: []*sharedtypes.SupplierServiceConfig{ { + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: sample.AccAddress(), + RevSharePercentage: 100, + }, + }, Service: &defaultService, }, }, diff --git a/testutil/keeper/tokenomics.go b/testutil/keeper/tokenomics.go index b7504f0a6..089913a36 100644 --- a/testutil/keeper/tokenomics.go +++ b/testutil/keeper/tokenomics.go @@ -122,10 +122,22 @@ func TokenomicsKeeperWithActorAddrs(t testing.TB) ( } // Prepare the test supplier. + supplierOwnerAddr := sample.AccAddress() supplier := sharedtypes.Supplier{ - OwnerAddress: sample.AccAddress(), - OperatorAddress: sample.AccAddress(), + OwnerAddress: supplierOwnerAddr, + OperatorAddress: supplierOwnerAddr, Stake: &sdk.Coin{Denom: "upokt", Amount: math.NewInt(100000)}, + Services: []*sharedtypes.SupplierServiceConfig{ + { + Service: service, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: supplierOwnerAddr, + RevSharePercentage: 100, + }, + }, + }, + }, } ctrl := gomock.NewController(t) diff --git a/x/shared/helpers/service_configs.go b/x/shared/helpers/service_configs.go index a7ac0ecf5..5462ca1fa 100644 --- a/x/shared/helpers/service_configs.go +++ b/x/shared/helpers/service_configs.go @@ -3,9 +3,15 @@ package helpers import ( "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) +const ( + requiredRevSharePercentageSum = 100 +) + // ValidateAppServiceConfigs returns an error if any of the application service configs are invalid func ValidateAppServiceConfigs(services []*sharedtypes.ApplicationServiceConfig) error { if len(services) == 0 { @@ -78,6 +84,56 @@ func ValidateSupplierServiceConfigs(services []*sharedtypes.SupplierServiceConfi // return fmt.Errorf("endpoint.Configs must have at least one entry: %v", serviceConfig) // } } + + if err := ValidateServiceRevShare(serviceConfig.RevShare); err != nil { + return err + } + } + + return nil +} + +// ValidateServiceRevShare validates the supplier's service revenue share, +// ensuring that the sum of the revenue share percentages is 100. +// NB: This function is unit tested via the supplier staking config tests. +func ValidateServiceRevShare(revShareList []*sharedtypes.ServiceRevenueShare) error { + revSharePercentageSum := float32(0) + + if len(revShareList) == 0 { + return sharedtypes.ErrSharedInvalidRevShare.Wrap("no rev share configurations") + } + + for _, revShare := range revShareList { + if revShare == nil { + return sharedtypes.ErrSharedInvalidRevShare.Wrap("rev share cannot be nil") + } + + // Validate the revshare address + if revShare.Address == "" { + return sharedtypes.ErrSharedInvalidRevShare.Wrapf("rev share address cannot be empty: %v", revShare) + } + + if _, err := sdk.AccAddressFromBech32(revShare.Address); err != nil { + return sharedtypes.ErrSharedInvalidRevShare.Wrapf("invalid rev share address %s; (%v)", revShare.Address, err) + } + + if revShare.RevSharePercentage <= 0 || revShare.RevSharePercentage > 100 { + return sharedtypes.ErrSharedInvalidRevShare.Wrapf( + "invalid rev share value %v; must be between 0 and 100", + revShare.RevSharePercentage, + ) + } + + revSharePercentageSum += revShare.RevSharePercentage } + + if revSharePercentageSum != requiredRevSharePercentageSum { + return sharedtypes.ErrSharedInvalidRevShare.Wrapf( + "invalid rev share percentage sum %v; must be equal to %v", + revSharePercentageSum, + requiredRevSharePercentageSum, + ) + } + return nil } diff --git a/x/shared/types/errors.go b/x/shared/types/errors.go index c4e129a1c..f823d6893 100644 --- a/x/shared/types/errors.go +++ b/x/shared/types/errors.go @@ -14,4 +14,5 @@ var ( ErrSharedParamInvalid = sdkerrors.Register(ModuleName, 1103, "the provided param is invalid") ErrSharedEmitEvent = sdkerrors.Register(ModuleName, 1104, "failed to emit event") ErrSharedUnauthorizedSupplierUpdate = sdkerrors.Register(ModuleName, 1105, "unauthorized supplier update") + ErrSharedInvalidRevShare = sdkerrors.Register(ModuleName, 1106, "invalid revenue share configuration") ) diff --git a/x/shared/types/service.pb.go b/x/shared/types/service.pb.go index acfd8144a..3bbb2d31d 100644 --- a/x/shared/types/service.pb.go +++ b/x/shared/types/service.pb.go @@ -7,6 +7,7 @@ package types import ( + encoding_binary "encoding/binary" fmt "fmt" _ "github.com/cosmos/cosmos-proto" proto "github.com/cosmos/gogoproto/proto" @@ -215,8 +216,9 @@ func (m *ApplicationServiceConfig) GetService() *Service { // SupplierServiceConfig holds the service configuration the supplier stakes for type SupplierServiceConfig struct { // TODO_MAINNET: Avoid embedding the full Service because we just need the ID. - Service *Service `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` - Endpoints []*SupplierEndpoint `protobuf:"bytes,2,rep,name=endpoints,proto3" json:"endpoints,omitempty"` + Service *Service `protobuf:"bytes,1,opt,name=service,proto3" json:"service,omitempty"` + Endpoints []*SupplierEndpoint `protobuf:"bytes,2,rep,name=endpoints,proto3" json:"endpoints,omitempty"` + RevShare []*ServiceRevenueShare `protobuf:"bytes,3,rep,name=rev_share,json=revShare,proto3" json:"rev_share,omitempty"` } func (m *SupplierServiceConfig) Reset() { *m = SupplierServiceConfig{} } @@ -266,6 +268,13 @@ func (m *SupplierServiceConfig) GetEndpoints() []*SupplierEndpoint { return nil } +func (m *SupplierServiceConfig) GetRevShare() []*ServiceRevenueShare { + if m != nil { + return m.RevShare + } + return nil +} + // SupplierEndpoint message to hold service configuration details type SupplierEndpoint struct { Url string `protobuf:"bytes,1,opt,name=url,proto3" json:"url,omitempty"` @@ -327,6 +336,59 @@ func (m *SupplierEndpoint) GetConfigs() []*ConfigOption { return nil } +// ServiceRevenueShare message to hold revenue share configuration details +type ServiceRevenueShare struct { + Address string `protobuf:"bytes,1,opt,name=address,proto3" json:"address,omitempty"` + RevSharePercentage float32 `protobuf:"fixed32,2,opt,name=rev_share_percentage,json=revSharePercentage,proto3" json:"rev_share_percentage,omitempty"` +} + +func (m *ServiceRevenueShare) Reset() { *m = ServiceRevenueShare{} } +func (m *ServiceRevenueShare) String() string { return proto.CompactTextString(m) } +func (*ServiceRevenueShare) ProtoMessage() {} +func (*ServiceRevenueShare) Descriptor() ([]byte, []int) { + return fileDescriptor_302c2f793a11ae1e, []int{4} +} +func (m *ServiceRevenueShare) XXX_Unmarshal(b []byte) error { + return m.Unmarshal(b) +} +func (m *ServiceRevenueShare) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) { + if deterministic { + return xxx_messageInfo_ServiceRevenueShare.Marshal(b, m, deterministic) + } else { + b = b[:cap(b)] + n, err := m.MarshalToSizedBuffer(b) + if err != nil { + return nil, err + } + return b[:n], nil + } +} +func (m *ServiceRevenueShare) XXX_Merge(src proto.Message) { + xxx_messageInfo_ServiceRevenueShare.Merge(m, src) +} +func (m *ServiceRevenueShare) XXX_Size() int { + return m.Size() +} +func (m *ServiceRevenueShare) XXX_DiscardUnknown() { + xxx_messageInfo_ServiceRevenueShare.DiscardUnknown(m) +} + +var xxx_messageInfo_ServiceRevenueShare proto.InternalMessageInfo + +func (m *ServiceRevenueShare) GetAddress() string { + if m != nil { + return m.Address + } + return "" +} + +func (m *ServiceRevenueShare) GetRevSharePercentage() float32 { + if m != nil { + return m.RevSharePercentage + } + return 0 +} + // Key-value wrapper for config options, as proto maps can't be keyed by enums type ConfigOption struct { Key ConfigOptions `protobuf:"varint,1,opt,name=key,proto3,enum=poktroll.shared.ConfigOptions" json:"key,omitempty"` @@ -337,7 +399,7 @@ func (m *ConfigOption) Reset() { *m = ConfigOption{} } func (m *ConfigOption) String() string { return proto.CompactTextString(m) } func (*ConfigOption) ProtoMessage() {} func (*ConfigOption) Descriptor() ([]byte, []int) { - return fileDescriptor_302c2f793a11ae1e, []int{4} + return fileDescriptor_302c2f793a11ae1e, []int{5} } func (m *ConfigOption) XXX_Unmarshal(b []byte) error { return m.Unmarshal(b) @@ -387,47 +449,52 @@ func init() { proto.RegisterType((*ApplicationServiceConfig)(nil), "poktroll.shared.ApplicationServiceConfig") proto.RegisterType((*SupplierServiceConfig)(nil), "poktroll.shared.SupplierServiceConfig") proto.RegisterType((*SupplierEndpoint)(nil), "poktroll.shared.SupplierEndpoint") + proto.RegisterType((*ServiceRevenueShare)(nil), "poktroll.shared.ServiceRevenueShare") proto.RegisterType((*ConfigOption)(nil), "poktroll.shared.ConfigOption") } func init() { proto.RegisterFile("poktroll/shared/service.proto", fileDescriptor_302c2f793a11ae1e) } var fileDescriptor_302c2f793a11ae1e = []byte{ - // 542 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x53, 0xcd, 0x6e, 0xd3, 0x4c, - 0x14, 0x8d, 0xe3, 0x7c, 0x9f, 0x9b, 0x9b, 0x9f, 0x5a, 0xa3, 0x20, 0x0c, 0x52, 0xad, 0x90, 0x55, - 0x54, 0xa9, 0x71, 0x95, 0x0a, 0xb1, 0x42, 0xa8, 0x89, 0x42, 0x15, 0x22, 0xe2, 0x68, 0x9c, 0x50, - 0x89, 0x8d, 0xe5, 0xda, 0x43, 0x6a, 0xc5, 0xf1, 0x8c, 0xc6, 0x76, 0x4b, 0xde, 0x81, 0x05, 0xe2, - 0x15, 0x78, 0x05, 0x1e, 0x82, 0x65, 0xc5, 0x8a, 0x25, 0x4a, 0x5e, 0x04, 0x8d, 0x7f, 0xf8, 0x69, - 0x11, 0x1b, 0x76, 0x77, 0xe6, 0x9c, 0x7b, 0xe6, 0x1c, 0x5f, 0x5f, 0x38, 0x60, 0x74, 0x15, 0x73, - 0x1a, 0x04, 0x46, 0x74, 0xe9, 0x70, 0xe2, 0x19, 0x11, 0xe1, 0x57, 0xbe, 0x4b, 0x7a, 0x8c, 0xd3, - 0x98, 0xa2, 0xfd, 0x02, 0xee, 0x65, 0xf0, 0xc3, 0x07, 0x2e, 0x8d, 0xd6, 0x34, 0xb2, 0x53, 0xd8, - 0xc8, 0x0e, 0x19, 0xb7, 0xf3, 0x51, 0x02, 0xc5, 0xca, 0xba, 0x51, 0x13, 0xca, 0xbe, 0xa7, 0x49, - 0x6d, 0xa9, 0x5b, 0xc5, 0x65, 0xdf, 0x43, 0x08, 0x2a, 0xa1, 0xb3, 0x26, 0x5a, 0x39, 0xbd, 0x49, - 0x6b, 0xf4, 0x18, 0xee, 0xbb, 0x74, 0xcd, 0x92, 0x98, 0xd8, 0x49, 0xe8, 0xc7, 0x91, 0xcd, 0x08, - 0xb7, 0x39, 0x09, 0x9c, 0x8d, 0x26, 0xb7, 0xa5, 0x6e, 0x05, 0xb7, 0x72, 0x78, 0x21, 0xd0, 0x19, - 0xe1, 0x58, 0x60, 0xe8, 0x29, 0x34, 0xe8, 0x75, 0x48, 0xb8, 0xed, 0x78, 0x1e, 0x27, 0x51, 0xa4, - 0x55, 0x84, 0xe6, 0x40, 0xfb, 0xf2, 0xe9, 0xa8, 0x95, 0xfb, 0x39, 0xcd, 0x10, 0x2b, 0xe6, 0x7e, - 0xb8, 0xc4, 0xf5, 0x94, 0x9e, 0xdf, 0x75, 0xa6, 0xa0, 0x9d, 0x32, 0x16, 0xf8, 0xae, 0x13, 0xfb, - 0x34, 0xcc, 0xfd, 0x0e, 0x69, 0xf8, 0xc6, 0x5f, 0xa2, 0x3e, 0x28, 0x79, 0xfc, 0xd4, 0x7a, 0xad, - 0xaf, 0xf5, 0x6e, 0xe5, 0xef, 0xe5, 0x0d, 0xb8, 0x20, 0x76, 0xde, 0x49, 0x70, 0xcf, 0x4a, 0x84, - 0x22, 0xe1, 0xff, 0xac, 0x86, 0x9e, 0x41, 0x95, 0x84, 0x1e, 0xa3, 0x7e, 0x18, 0x47, 0x5a, 0xb9, - 0x2d, 0x77, 0x6b, 0xfd, 0x47, 0x77, 0xbb, 0xf2, 0xe7, 0x46, 0x39, 0x13, 0xff, 0xec, 0xe9, 0x7c, - 0x90, 0x40, 0xbd, 0x8d, 0x23, 0x15, 0xe4, 0x84, 0x07, 0xf9, 0x38, 0x44, 0x89, 0x4e, 0x60, 0x8f, - 0x33, 0xd7, 0x8e, 0x37, 0x2c, 0x9b, 0x49, 0xf3, 0x0f, 0xe6, 0xf0, 0x6c, 0x38, 0xdf, 0x30, 0x82, - 0x15, 0xce, 0x5c, 0x51, 0xa0, 0x27, 0xa0, 0xb8, 0x69, 0xb4, 0x48, 0x93, 0x53, 0x6b, 0x07, 0x77, - 0x7a, 0xb2, 0xe8, 0x26, 0x13, 0xdf, 0x16, 0x17, 0xec, 0xce, 0x2b, 0xa8, 0xff, 0x0a, 0xa0, 0x63, - 0x90, 0x57, 0x64, 0x93, 0xfa, 0x69, 0xf6, 0xf5, 0xbf, 0x8a, 0x44, 0x58, 0x50, 0x51, 0x0b, 0xfe, - 0xbb, 0x72, 0x82, 0xa4, 0xf8, 0x81, 0xb2, 0xc3, 0xe1, 0x04, 0x94, 0xdc, 0x24, 0xda, 0x87, 0xda, - 0x62, 0x3a, 0x99, 0x9a, 0xe7, 0x53, 0x1b, 0xcf, 0x86, 0x6a, 0x09, 0xed, 0x41, 0xe5, 0x4c, 0x54, - 0x12, 0x6a, 0x40, 0xf5, 0x7c, 0x34, 0xb0, 0xcc, 0xe1, 0x64, 0x34, 0x57, 0xcb, 0xa8, 0x0e, 0x7b, - 0x2f, 0x2c, 0x33, 0xa3, 0xc9, 0x82, 0x86, 0x47, 0xd6, 0x5c, 0xad, 0x1c, 0x1e, 0x43, 0xe3, 0xb7, - 0x87, 0x11, 0x82, 0x66, 0x21, 0x39, 0x34, 0xa7, 0xcf, 0xc7, 0x67, 0x6a, 0x09, 0xd5, 0x40, 0x99, - 0x8f, 0x5f, 0x8e, 0xcc, 0xc5, 0x5c, 0x95, 0x06, 0xe3, 0xcf, 0x5b, 0x5d, 0xba, 0xd9, 0xea, 0xd2, - 0xb7, 0xad, 0x2e, 0xbd, 0xdf, 0xe9, 0xa5, 0x9b, 0x9d, 0x5e, 0xfa, 0xba, 0xd3, 0x4b, 0xaf, 0x8d, - 0xa5, 0x1f, 0x5f, 0x26, 0x17, 0x3d, 0x97, 0xae, 0x0d, 0x91, 0xee, 0x28, 0x24, 0xf1, 0x35, 0xe5, - 0x2b, 0xe3, 0xc7, 0xb6, 0xbd, 0x2d, 0xf6, 0x4d, 0x8c, 0x20, 0xba, 0xf8, 0x3f, 0x5d, 0xa1, 0x93, - 0xef, 0x01, 0x00, 0x00, 0xff, 0xff, 0x03, 0x0b, 0xf3, 0xdc, 0x8f, 0x03, 0x00, 0x00, + // 606 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xa4, 0x54, 0xcb, 0x6e, 0xd3, 0x40, + 0x14, 0xcd, 0x24, 0x01, 0x27, 0x37, 0x6d, 0x6a, 0x0d, 0x41, 0x18, 0xa4, 0x5a, 0xc5, 0x62, 0x51, + 0x55, 0x6a, 0x5c, 0xa5, 0x42, 0xac, 0x10, 0x6a, 0xa3, 0x50, 0x95, 0x8a, 0x38, 0x1a, 0xa7, 0x54, + 0x62, 0x63, 0xb9, 0xf6, 0x90, 0x5a, 0x75, 0x3c, 0xa3, 0xb1, 0x9d, 0x12, 0xf1, 0x13, 0x88, 0x5f, + 0xe0, 0x17, 0xf8, 0x08, 0x76, 0x54, 0xac, 0x58, 0xa2, 0xf6, 0x47, 0xd0, 0xf8, 0x11, 0xa0, 0x2d, + 0xb0, 0x60, 0x77, 0x3d, 0xe7, 0xdc, 0xc7, 0x39, 0x77, 0x3c, 0xb0, 0xca, 0xd9, 0x69, 0x22, 0x58, + 0x18, 0x9a, 0xf1, 0x89, 0x2b, 0xa8, 0x6f, 0xc6, 0x54, 0xcc, 0x02, 0x8f, 0x76, 0xb9, 0x60, 0x09, + 0xc3, 0x2b, 0x25, 0xdc, 0xcd, 0xe1, 0x07, 0xf7, 0x3d, 0x16, 0x4f, 0x59, 0xec, 0x64, 0xb0, 0x99, + 0x7f, 0xe4, 0x5c, 0xe3, 0x23, 0x02, 0xc5, 0xce, 0xb3, 0x71, 0x1b, 0xaa, 0x81, 0xaf, 0xa1, 0x35, + 0xb4, 0xde, 0x24, 0xd5, 0xc0, 0xc7, 0x18, 0xea, 0x91, 0x3b, 0xa5, 0x5a, 0x35, 0x3b, 0xc9, 0x62, + 0xfc, 0x18, 0xee, 0x79, 0x6c, 0xca, 0xd3, 0x84, 0x3a, 0x69, 0x14, 0x24, 0xb1, 0xc3, 0xa9, 0x70, + 0x04, 0x0d, 0xdd, 0xb9, 0x56, 0x5b, 0x43, 0xeb, 0x75, 0xd2, 0x29, 0xe0, 0x43, 0x89, 0x8e, 0xa8, + 0x20, 0x12, 0xc3, 0x4f, 0x61, 0x99, 0x9d, 0x45, 0x54, 0x38, 0xae, 0xef, 0x0b, 0x1a, 0xc7, 0x5a, + 0x5d, 0xd6, 0xdc, 0xd5, 0xbe, 0x7e, 0xda, 0xec, 0x14, 0xf3, 0xec, 0xe4, 0x88, 0x9d, 0x88, 0x20, + 0x9a, 0x90, 0xa5, 0x8c, 0x5e, 0x9c, 0x19, 0x43, 0xd0, 0x76, 0x38, 0x0f, 0x03, 0xcf, 0x4d, 0x02, + 0x16, 0x15, 0xf3, 0xf6, 0x59, 0xf4, 0x26, 0x98, 0xe0, 0x1e, 0x28, 0x85, 0xfc, 0x6c, 0xf4, 0x56, + 0x4f, 0xeb, 0x5e, 0xd1, 0xdf, 0x2d, 0x12, 0x48, 0x49, 0x34, 0xbe, 0x20, 0xb8, 0x6b, 0xa7, 0xb2, + 0x22, 0x15, 0xff, 0x5d, 0x0d, 0x3f, 0x83, 0x26, 0x8d, 0x7c, 0xce, 0x82, 0x28, 0x89, 0xb5, 0xea, + 0x5a, 0x6d, 0xbd, 0xd5, 0x7b, 0x78, 0x3d, 0xab, 0x68, 0x37, 0x28, 0x98, 0xe4, 0x67, 0x0e, 0xde, + 0x81, 0xa6, 0xa0, 0x33, 0x27, 0x63, 0x6a, 0xb5, 0xac, 0xc0, 0xa3, 0x3f, 0xb6, 0xa5, 0x33, 0x1a, + 0xa5, 0xd4, 0x96, 0x87, 0xa4, 0x21, 0xe8, 0x2c, 0x8b, 0x8c, 0x0f, 0x08, 0xd4, 0xab, 0x2d, 0xb0, + 0x0a, 0xb5, 0x54, 0x84, 0xc5, 0x46, 0x65, 0x88, 0xb7, 0xa1, 0x21, 0xb8, 0xe7, 0x24, 0x73, 0x9e, + 0xaf, 0xb5, 0x7d, 0x83, 0x3e, 0x32, 0xea, 0x8f, 0xe7, 0x9c, 0x12, 0x45, 0x70, 0x4f, 0x06, 0xf8, + 0x09, 0x28, 0x5e, 0xe6, 0x4e, 0x5c, 0x0c, 0xb7, 0x7a, 0x2d, 0x27, 0x77, 0xcf, 0xe2, 0x72, 0x3d, + 0xa4, 0x64, 0x1b, 0xef, 0xe0, 0xce, 0x0d, 0x53, 0x4b, 0x8f, 0xcb, 0x6b, 0x80, 0xfe, 0x71, 0x0d, + 0x4a, 0x22, 0xde, 0x82, 0xce, 0xc2, 0x22, 0x79, 0xe7, 0x3c, 0x1a, 0x25, 0xee, 0x24, 0x17, 0x51, + 0x25, 0xb8, 0xf4, 0x61, 0xb4, 0x40, 0x8c, 0x57, 0xb0, 0xf4, 0xeb, 0x54, 0x78, 0x0b, 0x6a, 0xa7, + 0x74, 0x9e, 0x75, 0x6c, 0xf7, 0xf4, 0xbf, 0x2a, 0x88, 0x89, 0xa4, 0xe2, 0x0e, 0xdc, 0x9a, 0xb9, + 0x61, 0x5a, 0xfe, 0x00, 0xf9, 0xc7, 0xc6, 0x01, 0x28, 0x85, 0x43, 0x78, 0x05, 0x5a, 0x87, 0xc3, + 0x83, 0xa1, 0x75, 0x34, 0x74, 0xc8, 0xa8, 0xaf, 0x56, 0x70, 0x03, 0xea, 0x7b, 0x32, 0x42, 0x78, + 0x19, 0x9a, 0x47, 0x83, 0x5d, 0xdb, 0xea, 0x1f, 0x0c, 0xc6, 0x6a, 0x15, 0x2f, 0x41, 0xe3, 0x85, + 0x6d, 0xe5, 0xb4, 0x9a, 0xa4, 0x91, 0x81, 0x3d, 0x56, 0xeb, 0x1b, 0x5b, 0xb0, 0xfc, 0x5b, 0x63, + 0x8c, 0xa1, 0x5d, 0x96, 0xec, 0x5b, 0xc3, 0xe7, 0xfb, 0x7b, 0x6a, 0x05, 0xb7, 0x40, 0x19, 0xef, + 0xbf, 0x1c, 0x58, 0x87, 0x63, 0x15, 0xed, 0xee, 0x7f, 0xbe, 0xd0, 0xd1, 0xf9, 0x85, 0x8e, 0xbe, + 0x5f, 0xe8, 0xe8, 0xfd, 0xa5, 0x5e, 0x39, 0xbf, 0xd4, 0x2b, 0xdf, 0x2e, 0xf5, 0xca, 0x6b, 0x73, + 0x12, 0x24, 0x27, 0xe9, 0x71, 0xd7, 0x63, 0x53, 0x53, 0xaa, 0xdb, 0x8c, 0x68, 0x72, 0xc6, 0xc4, + 0xa9, 0xb9, 0x78, 0x2d, 0xde, 0x96, 0xef, 0x85, 0xdc, 0x7f, 0x7c, 0x7c, 0x3b, 0x7b, 0x02, 0xb6, + 0x7f, 0x04, 0x00, 0x00, 0xff, 0xff, 0xe0, 0x38, 0x4c, 0xac, 0x4f, 0x04, 0x00, 0x00, } func (m *Service) Marshal() (dAtA []byte, err error) { @@ -534,6 +601,20 @@ func (m *SupplierServiceConfig) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if len(m.RevShare) > 0 { + for iNdEx := len(m.RevShare) - 1; iNdEx >= 0; iNdEx-- { + { + size, err := m.RevShare[iNdEx].MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintService(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a + } + } if len(m.Endpoints) > 0 { for iNdEx := len(m.Endpoints) - 1; iNdEx >= 0; iNdEx-- { { @@ -612,6 +693,42 @@ func (m *SupplierEndpoint) MarshalToSizedBuffer(dAtA []byte) (int, error) { return len(dAtA) - i, nil } +func (m *ServiceRevenueShare) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalToSizedBuffer(dAtA[:size]) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ServiceRevenueShare) MarshalTo(dAtA []byte) (int, error) { + size := m.Size() + return m.MarshalToSizedBuffer(dAtA[:size]) +} + +func (m *ServiceRevenueShare) MarshalToSizedBuffer(dAtA []byte) (int, error) { + i := len(dAtA) + _ = i + var l int + _ = l + if m.RevSharePercentage != 0 { + i -= 4 + encoding_binary.LittleEndian.PutUint32(dAtA[i:], uint32(math.Float32bits(float32(m.RevSharePercentage)))) + i-- + dAtA[i] = 0x15 + } + if len(m.Address) > 0 { + i -= len(m.Address) + copy(dAtA[i:], m.Address) + i = encodeVarintService(dAtA, i, uint64(len(m.Address))) + i-- + dAtA[i] = 0xa + } + return len(dAtA) - i, nil +} + func (m *ConfigOption) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -711,6 +828,12 @@ func (m *SupplierServiceConfig) Size() (n int) { n += 1 + l + sovService(uint64(l)) } } + if len(m.RevShare) > 0 { + for _, e := range m.RevShare { + l = e.Size() + n += 1 + l + sovService(uint64(l)) + } + } return n } @@ -736,6 +859,22 @@ func (m *SupplierEndpoint) Size() (n int) { return n } +func (m *ServiceRevenueShare) Size() (n int) { + if m == nil { + return 0 + } + var l int + _ = l + l = len(m.Address) + if l > 0 { + n += 1 + l + sovService(uint64(l)) + } + if m.RevSharePercentage != 0 { + n += 5 + } + return n +} + func (m *ConfigOption) Size() (n int) { if m == nil { return 0 @@ -1108,6 +1247,40 @@ func (m *SupplierServiceConfig) Unmarshal(dAtA []byte) error { return err } iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field RevShare", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthService + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthService + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.RevShare = append(m.RevShare, &ServiceRevenueShare{}) + if err := m.RevShare[len(m.RevShare)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipService(dAtA[iNdEx:]) @@ -1264,6 +1437,99 @@ func (m *SupplierEndpoint) Unmarshal(dAtA []byte) error { } return nil } +func (m *ServiceRevenueShare) Unmarshal(dAtA []byte) error { + l := len(dAtA) + iNdEx := 0 + for iNdEx < l { + preIndex := iNdEx + var wire uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowService + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + wire |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + fieldNum := int32(wire >> 3) + wireType := int(wire & 0x7) + if wireType == 4 { + return fmt.Errorf("proto: ServiceRevenueShare: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ServiceRevenueShare: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Address", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowService + } + 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 ErrInvalidLengthService + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthService + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Address = string(dAtA[iNdEx:postIndex]) + iNdEx = postIndex + case 2: + if wireType != 5 { + return fmt.Errorf("proto: wrong wireType = %d for field RevSharePercentage", wireType) + } + var v uint32 + if (iNdEx + 4) > l { + return io.ErrUnexpectedEOF + } + v = uint32(encoding_binary.LittleEndian.Uint32(dAtA[iNdEx:])) + iNdEx += 4 + m.RevSharePercentage = float32(math.Float32frombits(v)) + default: + iNdEx = preIndex + skippy, err := skipService(dAtA[iNdEx:]) + if err != nil { + return err + } + if (skippy < 0) || (iNdEx+skippy) < 0 { + return ErrInvalidLengthService + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *ConfigOption) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 diff --git a/x/supplier/config/supplier_configs_reader.go b/x/supplier/config/supplier_configs_reader.go index 7f18fb035..d8adfb227 100644 --- a/x/supplier/config/supplier_configs_reader.go +++ b/x/supplier/config/supplier_configs_reader.go @@ -10,23 +10,26 @@ import ( "github.com/pokt-network/poktroll/pkg/polylog" _ "github.com/pokt-network/poktroll/pkg/polylog/polyzero" + "github.com/pokt-network/poktroll/x/shared/helpers" sharedhelpers "github.com/pokt-network/poktroll/x/shared/helpers" sharedtypes "github.com/pokt-network/poktroll/x/shared/types" ) // YAMLStakeConfig is the structure describing the supplier stake config file. type YAMLStakeConfig struct { - OwnerAddress string `yaml:"owner_address"` - OperatorAddress string `yaml:"operator_address"` - StakeAmount string `yaml:"stake_amount"` - Services []*YAMLStakeService `yaml:"services"` + OwnerAddress string `yaml:"owner_address"` + OperatorAddress string `yaml:"operator_address"` + StakeAmount string `yaml:"stake_amount"` + Services []*YAMLStakeService `yaml:"services"` + DefaultRevSharePercent map[string]float32 `yaml:"default_rev_share_percent"` } // YAMLStakeService is the structure describing a single service entry in the // stake config file. type YAMLStakeService struct { - ServiceId string `yaml:"service_id"` - Endpoints []YAMLServiceEndpoint `yaml:"endpoints"` + ServiceId string `yaml:"service_id"` + RevSharePercent map[string]float32 `yaml:"rev_share_percent"` + Endpoints []YAMLServiceEndpoint `yaml:"endpoints"` } // YAMLServiceEndpoint is the structure describing a single service endpoint in @@ -101,6 +104,18 @@ func ParseSupplierConfigs(ctx context.Context, configContent []byte) (*SupplierS ) } + defaultRevSharePercent := map[string]float32{} + if stakeConfig.DefaultRevSharePercent == nil || len(stakeConfig.DefaultRevSharePercent) == 0 { + // Ensure that if no default rev share is provided, the owner address is set + // to 100% rev share. + if stakeConfig.OwnerAddress == "" { + return nil, ErrSupplierConfigInvalidOwnerAddress.Wrap("owner address cannot be empty") + } + defaultRevSharePercent[stakeConfig.OwnerAddress] = 100 + } else { + defaultRevSharePercent = stakeConfig.DefaultRevSharePercent + } + // Validate the services if stakeConfig.Services == nil || len(stakeConfig.Services) == 0 { return nil, ErrSupplierConfigInvalidServiceId.Wrap("serviceIds cannot be empty") @@ -123,6 +138,7 @@ func ParseSupplierConfigs(ctx context.Context, configContent []byte) (*SupplierS // Create a supplied service config with the serviceId service := &sharedtypes.SupplierServiceConfig{ Service: &sharedtypes.Service{Id: svc.ServiceId}, + RevShare: []*sharedtypes.ServiceRevenueShare{}, Endpoints: []*sharedtypes.SupplierEndpoint{}, } @@ -134,6 +150,24 @@ func ParseSupplierConfigs(ctx context.Context, configContent []byte) (*SupplierS } service.Endpoints = append(service.Endpoints, parsedEndpointEntry) } + + serviceConfigRevShare := svc.RevSharePercent + // If the service does not have a rev share, use the default one. + if serviceConfigRevShare == nil { + serviceConfigRevShare = defaultRevSharePercent + } + + for address, revSharePercent := range serviceConfigRevShare { + service.RevShare = append(service.RevShare, &sharedtypes.ServiceRevenueShare{ + Address: address, + RevSharePercentage: revSharePercent, + }) + } + + if err := helpers.ValidateServiceRevShare(service.RevShare); err != nil { + return nil, err + } + supplierServiceConfig = append(supplierServiceConfig, service) } diff --git a/x/supplier/config/supplier_configs_reader_test.go b/x/supplier/config/supplier_configs_reader_test.go index 356b7844c..c2d711553 100644 --- a/x/supplier/config/supplier_configs_reader_test.go +++ b/x/supplier/config/supplier_configs_reader_test.go @@ -3,6 +3,7 @@ package config_test import ( "context" "fmt" + "slices" "testing" sdkerrors "cosmossdk.io/errors" @@ -15,12 +16,16 @@ import ( "github.com/pokt-network/poktroll/testutil/sample" "github.com/pokt-network/poktroll/testutil/yaml" "github.com/pokt-network/poktroll/x/shared/types" + sharedtypes "github.com/pokt-network/poktroll/x/shared/types" "github.com/pokt-network/poktroll/x/supplier/config" ) func Test_ParseSupplierConfigs_Services(t *testing.T) { operatorAddress := sample.AccAddress() ownerAddress := sample.AccAddress() + firstShareHolderAddress := sample.AccAddress() + secondShareHolderAddress := sample.AccAddress() + tests := []struct { desc string inputConfig string @@ -63,6 +68,12 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { }, }, }, + RevShare: []*types.ServiceRevenueShare{ + { + Address: ownerAddress, + RevSharePercentage: 100, + }, + }, }, }, }, @@ -93,6 +104,12 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { RpcType: types.RPCType_JSON_RPC, }, }, + RevShare: []*types.ServiceRevenueShare{ + { + Address: ownerAddress, + RevSharePercentage: 100, + }, + }, }, }, }, @@ -125,6 +142,12 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { Configs: []*types.ConfigOption{}, }, }, + RevShare: []*types.ServiceRevenueShare{ + { + Address: ownerAddress, + RevSharePercentage: 100, + }, + }, }, }, }, @@ -177,6 +200,12 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { }, }, }, + RevShare: []*types.ServiceRevenueShare{ + { + Address: ownerAddress, + RevSharePercentage: 100, + }, + }, }, }, }, @@ -221,6 +250,12 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { }, }, }, + RevShare: []*types.ServiceRevenueShare{ + { + Address: ownerAddress, + RevSharePercentage: 100, + }, + }, }, { Service: &types.Service{Id: "svc2"}, @@ -236,6 +271,83 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { }, }, }, + RevShare: []*types.ServiceRevenueShare{ + { + Address: ownerAddress, + RevSharePercentage: 100, + }, + }, + }, + }, + }, + }, + { + desc: "valid full service config with both default and service specific rev share", + inputConfig: fmt.Sprintf(` + owner_address: %s + operator_address: %s + default_rev_share_percent: + %s: 50.5 + %s: 49.5 + stake_amount: 1000upokt + services: + # Service with default rev share + - service_id: svc + endpoints: + - publicly_exposed_url: http://pokt.network:8081 + rpc_type: json_rpc + # Service with custom rev share + - service_id: svc2 + endpoints: + - publicly_exposed_url: http://pokt.network:8082 + rpc_type: json_rpc + rev_share_percent: + %s: 60 + %s: 40 + `, ownerAddress, operatorAddress, firstShareHolderAddress, secondShareHolderAddress, ownerAddress, firstShareHolderAddress), + expectedError: nil, + expectedConfig: &config.SupplierStakeConfig{ + OwnerAddress: ownerAddress, + OperatorAddress: operatorAddress, + StakeAmount: sdk.NewCoin("upokt", math.NewInt(1000)), + Services: []*types.SupplierServiceConfig{ + { + Service: &types.Service{Id: "svc"}, + Endpoints: []*types.SupplierEndpoint{ + { + Url: "http://pokt.network:8081", + RpcType: types.RPCType_JSON_RPC, + }, + }, + RevShare: []*types.ServiceRevenueShare{ + { + Address: firstShareHolderAddress, + RevSharePercentage: 50.5, + }, + { + Address: secondShareHolderAddress, + RevSharePercentage: 49.5, + }, + }, + }, + { + Service: &types.Service{Id: "svc2"}, + Endpoints: []*types.SupplierEndpoint{ + { + Url: "http://pokt.network:8082", + RpcType: types.RPCType_JSON_RPC, + }, + }, + RevShare: []*types.ServiceRevenueShare{ + { + Address: ownerAddress, + RevSharePercentage: 60, + }, + { + Address: firstShareHolderAddress, + RevSharePercentage: 40, + }, + }, }, }, }, @@ -274,12 +386,18 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { }, }, }, + RevShare: []*types.ServiceRevenueShare{ + { + Address: ownerAddress, + RevSharePercentage: 100, + }, + }, }, }, }, }, { - desc: "valid full service config", + desc: "valid missing default rev share config", inputConfig: fmt.Sprintf(` owner_address: %s operator_address: %s @@ -312,6 +430,12 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { }, }, }, + RevShare: []*types.ServiceRevenueShare{ + { + Address: ownerAddress, + RevSharePercentage: 100, + }, + }, }, }, }, @@ -490,7 +614,7 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { { desc: "missing owner address", inputConfig: fmt.Sprintf(` - # explictly omitted owner address + # explicitly omitted owner address operator_address: %s stake_amount: 1000upokt services: @@ -535,6 +659,110 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { `, ownerAddress), expectedError: config.ErrSupplierConfigInvalidOperatorAddress, }, + { + desc: "default rev share does not sum to 100", + inputConfig: fmt.Sprintf(` + owner_address: %s + operator_address: %s + default_rev_share_percent: + %s: 50 + %s: 49 + stake_amount: 1000upokt + services: + # Service with default rev share + - service_id: svc + endpoints: + - publicly_exposed_url: http://pokt.network:8081 + rpc_type: json_rpc + `, ownerAddress, operatorAddress, firstShareHolderAddress, secondShareHolderAddress), + expectedError: sharedtypes.ErrSharedInvalidRevShare, + }, + { + desc: "service specific rev share does not sum up to 100", + inputConfig: fmt.Sprintf(` + owner_address: %s + operator_address: %s + stake_amount: 1000upokt + services: + - service_id: svc + endpoints: + - publicly_exposed_url: http://pokt.network:8081 + rpc_type: json_rpc + rev_share_percent: + %s: 50 + %s: 49 + `, ownerAddress, operatorAddress, firstShareHolderAddress, secondShareHolderAddress), + expectedError: sharedtypes.ErrSharedInvalidRevShare, + }, + { + desc: "invalid revenue share address", + inputConfig: fmt.Sprintf(` + owner_address: %s + operator_address: %s + stake_amount: 1000upokt + services: + - service_id: svc + endpoints: + - publicly_exposed_url: http://pokt.network:8081 + rpc_type: json_rpc + rev_share_percent: + %s: 50 + %s: 49 + `, ownerAddress, operatorAddress, firstShareHolderAddress, "invalid_address"), + expectedError: sharedtypes.ErrSharedInvalidRevShare, + }, + { + desc: "empty revenue share address", + inputConfig: fmt.Sprintf(` + owner_address: %s + operator_address: %s + stake_amount: 1000upokt + services: + - service_id: svc + endpoints: + - publicly_exposed_url: http://pokt.network:8081 + rpc_type: json_rpc + rev_share_percent: + %s: 50 + %s: 49 + `, ownerAddress, operatorAddress, firstShareHolderAddress, ""), + expectedError: config.ErrSupplierConfigUnmarshalYAML, + }, + { + desc: "negative revenue share allocation is disallowed", + inputConfig: fmt.Sprintf(` + owner_address: %s + operator_address: %s + stake_amount: 1000upokt + services: + - service_id: svc + endpoints: + - publicly_exposed_url: http://pokt.network:8081 + rpc_type: json_rpc + rev_share_percent: + %s: 90 + %s: 11 + %s: -1 + `, ownerAddress, operatorAddress, ownerAddress, firstShareHolderAddress, secondShareHolderAddress), + expectedError: sharedtypes.ErrSharedInvalidRevShare, + }, + { + desc: "errors when the rev share config is empty", + inputConfig: fmt.Sprintf(` + owner_address: %s + operator_address: %s + stake_amount: 1000upokt + services: + - service_id: svc + endpoints: + - publicly_exposed_url: http://pokt.network:8081 + rpc_type: json_rpc + config: + timeout: 10 + rev_share_percent: {} + `, ownerAddress, ownerAddress), + expectedError: sharedtypes.ErrSharedInvalidRevShare, + }, } ctx := context.Background() @@ -579,6 +807,17 @@ func Test_ParseSupplierConfigs_Services(t *testing.T) { require.Equal(t, expectedConfig.Value, config.Value) } } + + require.Equal(t, len(expectedService.RevShare), len(service.RevShare)) + for _, expectedRevShare := range expectedService.RevShare { + revShareIdx := slices.IndexFunc(service.RevShare, func(revShare *sharedtypes.ServiceRevenueShare) bool { + return revShare.Address == expectedRevShare.Address + }) + require.NotEqual(t, -1, revShareIdx) + + require.Equal(t, expectedRevShare.Address, service.RevShare[revShareIdx].Address) + require.Equal(t, expectedRevShare.RevSharePercentage, service.RevShare[revShareIdx].RevSharePercentage) + } } }) } diff --git a/x/supplier/keeper/msg_server_stake_supplier_test.go b/x/supplier/keeper/msg_server_stake_supplier_test.go index 8664c1787..743ee23ec 100644 --- a/x/supplier/keeper/msg_server_stake_supplier_test.go +++ b/x/supplier/keeper/msg_server_stake_supplier_test.go @@ -346,6 +346,12 @@ func stakeSupplierForServicesMsg( Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: ownerAddr, + RevSharePercentage: 100, + }, + }, }) } diff --git a/x/supplier/keeper/msg_server_unstake_supplier_test.go b/x/supplier/keeper/msg_server_unstake_supplier_test.go index 4bdd414c1..73eb8e1c8 100644 --- a/x/supplier/keeper/msg_server_unstake_supplier_test.go +++ b/x/supplier/keeper/msg_server_unstake_supplier_test.go @@ -257,6 +257,12 @@ func createStakeMsg(supplierOwnerAddr string, stakeAmount int64) *types.MsgStake Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: supplierOwnerAddr, + RevSharePercentage: 100, + }, + }, }, }, } diff --git a/x/supplier/keeper/unbond_suppliers.go b/x/supplier/keeper/unbond_suppliers.go index ab6f9d0b1..ba8242e88 100644 --- a/x/supplier/keeper/unbond_suppliers.go +++ b/x/supplier/keeper/unbond_suppliers.go @@ -58,8 +58,8 @@ func (k Keeper) EndBlockerUnbondSuppliers(ctx context.Context) error { ctx, types.ModuleName, ownerAddress, []cosmostypes.Coin{*supplier.Stake}, ); err != nil { logger.Error(fmt.Sprintf( - "could not send %s coins from %s module to %s account due to %s", - supplier.Stake.String(), ownerAddress, types.ModuleName, err, + "could not send %s coins from module %s to account %s due to %s", + supplier.Stake.String(), types.ModuleName, ownerAddress, err, )) return err } diff --git a/x/supplier/types/genesis_test.go b/x/supplier/types/genesis_test.go index b1a78f68d..5d089f20c 100644 --- a/x/supplier/types/genesis_test.go +++ b/x/supplier/types/genesis_test.go @@ -26,6 +26,12 @@ func TestGenesisState_Validate(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: addr1, + RevSharePercentage: 100, + }, + }, } serviceList1 := []*sharedtypes.SupplierServiceConfig{serviceConfig1} @@ -42,6 +48,12 @@ func TestGenesisState_Validate(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: addr2, + RevSharePercentage: 100, + }, + }, } serviceList2 := []*sharedtypes.SupplierServiceConfig{serviceConfig2} @@ -282,6 +294,12 @@ func TestGenesisState_Validate(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: addr2, + RevSharePercentage: 100, + }, + }, }, }, }, @@ -315,6 +333,12 @@ func TestGenesisState_Validate(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: addr2, + RevSharePercentage: 100, + }, + }, }, }, }, diff --git a/x/supplier/types/message_stake_supplier_test.go b/x/supplier/types/message_stake_supplier_test.go index 980b91f6e..e3ea40af4 100644 --- a/x/supplier/types/message_stake_supplier_test.go +++ b/x/supplier/types/message_stake_supplier_test.go @@ -28,6 +28,12 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: sample.AccAddress(), + RevSharePercentage: 100, + }, + }, }, } @@ -116,7 +122,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { desc: "missing owner address", msg: MsgStakeSupplier{ Signer: ownerAddress, - // OwnerAddress: ownerAddress, + // OwnerAddress: ownerAddress, // intentionally commented out. OperatorAddress: operatorAddress, Stake: &sdk.Coin{Denom: volatile.DenomuPOKT, Amount: math.NewInt(100)}, Services: defaultServicesList, @@ -128,7 +134,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { msg: MsgStakeSupplier{ Signer: ownerAddress, OwnerAddress: ownerAddress, - // OperatorAddress: operatorAddress, + // OperatorAddress: operatorAddress, // intentionally commented out. Stake: &sdk.Coin{Denom: volatile.DenomuPOKT, Amount: math.NewInt(0)}, Services: defaultServicesList, }, @@ -137,7 +143,7 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { { desc: "missing signer address", msg: MsgStakeSupplier{ - // Signer: ownerAddress, + // Signer: ownerAddress, // intentionally commented out. OwnerAddress: ownerAddress, OperatorAddress: operatorAddress, Stake: &sdk.Coin{Denom: volatile.DenomuPOKT, Amount: math.NewInt(0)}, @@ -233,6 +239,12 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: sample.AccAddress(), + RevSharePercentage: 100, + }, + }, }, { Service: &sharedtypes.Service{ @@ -245,6 +257,12 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: sample.AccAddress(), + RevSharePercentage: 100, + }, + }, }, }, }, @@ -290,6 +308,12 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: sample.AccAddress(), + RevSharePercentage: 100, + }, + }, }, }, }, @@ -315,6 +339,12 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: sample.AccAddress(), + RevSharePercentage: 100, + }, + }, }, }, }, @@ -339,6 +369,12 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: sample.AccAddress(), + RevSharePercentage: 100, + }, + }, }, }, }, @@ -364,6 +400,12 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: sample.AccAddress(), + RevSharePercentage: 100, + }, + }, }, }, }, @@ -389,6 +431,12 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { Configs: make([]*sharedtypes.ConfigOption, 0), }, }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: sample.AccAddress(), + RevSharePercentage: 100, + }, + }, }, }, }, @@ -396,6 +444,63 @@ func TestMsgStakeSupplier_ValidateBasic(t *testing.T) { }, { desc: "invalid service configs - missing rpc type", + msg: MsgStakeSupplier{ + Signer: ownerAddress, + OwnerAddress: ownerAddress, + OperatorAddress: operatorAddress, + Stake: &sdk.Coin{Denom: volatile.DenomuPOKT, Amount: math.NewInt(100)}, + Services: []*sharedtypes.SupplierServiceConfig{ + { + Service: &sharedtypes.Service{ + Id: "svcId", + Name: "name", + }, + Endpoints: []*sharedtypes.SupplierEndpoint{ + { + Url: "http://localhost:8080", + // RpcType explicitly omitted, + Configs: make([]*sharedtypes.ConfigOption, 0), + }, + }, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: sample.AccAddress(), + RevSharePercentage: 100, + }, + }, + }, + }, + }, + expectedErr: ErrSupplierInvalidServiceConfig, + }, + { + desc: "invalid service configs - empty revenue share config", + msg: MsgStakeSupplier{ + Signer: ownerAddress, + OwnerAddress: ownerAddress, + OperatorAddress: operatorAddress, + Stake: &sdk.Coin{Denom: volatile.DenomuPOKT, Amount: math.NewInt(100)}, + Services: []*sharedtypes.SupplierServiceConfig{ + { + Service: &sharedtypes.Service{ + Id: "svcId", + Name: "name", + }, + Endpoints: []*sharedtypes.SupplierEndpoint{ + { + Url: "http://localhost:8080", + // RpcType explicitly omitted, + Configs: make([]*sharedtypes.ConfigOption, 0), + }, + }, + RevShare: []*sharedtypes.ServiceRevenueShare{}, + }, + }, + }, + expectedErr: ErrSupplierInvalidServiceConfig, + }, + { + desc: "invalid service configs - missing revenue share config", msg: MsgStakeSupplier{ Signer: ownerAddress, OwnerAddress: ownerAddress, diff --git a/x/supplier/types/message_unstake_supplier.go b/x/supplier/types/message_unstake_supplier.go index a35d5e191..772b9687e 100644 --- a/x/supplier/types/message_unstake_supplier.go +++ b/x/supplier/types/message_unstake_supplier.go @@ -15,11 +15,11 @@ func NewMsgUnstakeSupplier(signerAddress, operatorAddress string) *MsgUnstakeSup func (msg *MsgUnstakeSupplier) ValidateBasic() error { if _, err := sdk.AccAddressFromBech32(msg.Signer); err != nil { - return ErrSupplierInvalidAddress.Wrapf("invalid signer address address (%s)", err) + return ErrSupplierInvalidAddress.Wrapf("invalid signer address (%s)", err) } if _, err := sdk.AccAddressFromBech32(msg.OperatorAddress); err != nil { - return ErrSupplierInvalidAddress.Wrapf("invalid operator address address (%s)", err) + return ErrSupplierInvalidAddress.Wrapf("invalid operator address (%s)", err) } return nil diff --git a/x/tokenomics/keeper/keeper_settle_pending_claims_test.go b/x/tokenomics/keeper/keeper_settle_pending_claims_test.go index 680460ea4..cdb9d8e98 100644 --- a/x/tokenomics/keeper/keeper_settle_pending_claims_test.go +++ b/x/tokenomics/keeper/keeper_settle_pending_claims_test.go @@ -101,7 +101,13 @@ func (s *TestSuite) SetupTest() { OwnerAddress: supplierOwnerAddr, OperatorAddress: supplierOwnerAddr, Stake: &supplierStake, - Services: []*sharedtypes.SupplierServiceConfig{{Service: &service}}, + Services: []*sharedtypes.SupplierServiceConfig{{ + Service: &service, + RevShare: []*sharedtypes.ServiceRevenueShare{{ + Address: supplierOwnerAddr, + RevSharePercentage: 100, + }}, + }}, } s.keepers.SetSupplier(s.ctx, supplier) diff --git a/x/tokenomics/keeper/token_logic_modules.go b/x/tokenomics/keeper/token_logic_modules.go index fc88c3bd9..74e6d66c3 100644 --- a/x/tokenomics/keeper/token_logic_modules.go +++ b/x/tokenomics/keeper/token_logic_modules.go @@ -247,7 +247,7 @@ func (k Keeper) TokenLogicModuleRelayBurnEqualsMint( service *sharedtypes.Service, application *apptypes.Application, supplier *sharedtypes.Supplier, - settlementCoins cosmostypes.Coin, + settlementCoin cosmostypes.Coin, relayMiningDifficulty *tokenomictypes.RelayMiningDifficulty, ) error { logger := k.Logger().With("method", "TokenLogicModuleRelayBurnEqualsMint") @@ -265,29 +265,25 @@ func (k Keeper) TokenLogicModuleRelayBurnEqualsMint( // Mint new uPOKT to the supplier module account. // These funds will be transferred to the supplier below. if err = k.bankKeeper.MintCoins( - ctx, suppliertypes.ModuleName, sdk.NewCoins(settlementCoins), + ctx, suppliertypes.ModuleName, sdk.NewCoins(settlementCoin), ); err != nil { return tokenomicstypes.ErrTokenomicsSupplierModuleSendFailed.Wrapf( "minting %s to the supplier module account: %v", - settlementCoins, + settlementCoin, err, ) } - logger.Info(fmt.Sprintf("minted (%v) coins in the supplier module", settlementCoins)) + logger.Info(fmt.Sprintf("minted (%v) coins in the supplier module", settlementCoin)) - // Send the newley minted uPOKT from the supplier module account - // to the supplier's account. - if err = k.bankKeeper.SendCoinsFromModuleToAccount( - ctx, suppliertypes.ModuleName, ownerAddr, sdk.NewCoins(settlementCoins), - ); err != nil { - return tokenomicstypes.ErrTokenomicsSupplierModuleSendFailed.Wrapf( - "sending (%s) to supplier with operator address %s: %v", - settlementCoins, + amount := settlementCoin.Amount.Uint64() + if err = k.distributeSupplierRewardsToShareHolders(ctx, ownerAddr.String(), service.Id, amount); err != nil { + return tokenomicstypes.ErrTokenomicsSupplierModuleMintFailed.Wrapf( + "distributing rewards to supplier with operator address %s shareholders: %v", supplier.OperatorAddress, err, ) } - logger.Info(fmt.Sprintf("sent (%v) from the supplier module to the supplier account with operator address %q", settlementCoins, supplier.OperatorAddress)) + logger.Info(fmt.Sprintf("sent (%v) from the supplier module to the supplier account with address %q", settlementCoin, supplier.OperatorAddress)) // TODO_MAINNET: Decide on the behaviour here when an app is over serviced. // If an app has 10 POKT staked, but the supplier earned 20 POKT. We still @@ -295,8 +291,8 @@ func (k Keeper) TokenLogicModuleRelayBurnEqualsMint( // questions and nuance here that needs to be addressed. // Verify that the application has enough uPOKT to pay for the services it consumed - if application.GetStake().IsLT(settlementCoins) { - settlementCoins, err = k.handleOverservicedApplication(ctx, application, settlementCoins) + if application.GetStake().IsLT(settlementCoin) { + settlementCoin, err = k.handleOverservicedApplication(ctx, application, settlementCoin) if err != nil { return err } @@ -305,14 +301,14 @@ func (k Keeper) TokenLogicModuleRelayBurnEqualsMint( // Burn uPOKT from the application module account which was held in escrow // on behalf of the application account. if err = k.bankKeeper.BurnCoins( - ctx, apptypes.ModuleName, sdk.NewCoins(settlementCoins), + ctx, apptypes.ModuleName, sdk.NewCoins(settlementCoin), ); err != nil { - return tokenomicstypes.ErrTokenomicsApplicationModuleBurn.Wrapf("burning %s from the application module account: %v", settlementCoins, err) + return tokenomicstypes.ErrTokenomicsApplicationModuleBurn.Wrapf("burning %s from the application module account: %v", settlementCoin, err) } - logger.Info(fmt.Sprintf("burned (%v) from the application module account", settlementCoins)) + logger.Info(fmt.Sprintf("burned (%v) from the application module account", settlementCoin)) // Update the application's on-chain stake - newAppStake, err := application.Stake.SafeSub(settlementCoins) + newAppStake, err := application.Stake.SafeSub(settlementCoin) if err != nil { return tokenomicstypes.ErrTokenomicsApplicationNewStakeInvalid.Wrapf("application %q stake cannot be reduced to a negative amount %v", application.Address, newAppStake) } @@ -350,43 +346,48 @@ func (k Keeper) TokenLogicModuleGlobalMint( logger.Info(fmt.Sprintf("minted (%v) coins in the tokenomics module", newMintCoins)) // Send a portion of the rewards to the application - appCoins, err := k.sendRewardsToAccount(ctx, application.Address, newMintAmtFloat, MintAllocationApplication) + appCoin, err := k.sendRewardsToAccount(ctx, application.Address, newMintAmtFloat, MintAllocationApplication) if err != nil { - return tokenomictypes.ErrTokenomicsSendingMindRewards.Wrapf("sending rewards to application: %v", err) + return tokenomictypes.ErrTokenomicsSendingMintRewards.Wrapf("sending rewards to application: %v", err) } - logger.Debug(fmt.Sprintf("sent (%v) newley minted coins from the tokenomics module to the application with address %q", appCoins, application.Address)) + logger.Debug(fmt.Sprintf("sent (%v) newley minted coins from the tokenomics module to the application with address %q", appCoin, application.Address)) - // Send a portion of the rewards to the supplier - supplierCoins, err := k.sendRewardsToAccount(ctx, supplier.OwnerAddress, newMintAmtFloat, MintAllocationSupplier) - if err != nil { - return tokenomictypes.ErrTokenomicsSendingMindRewards.Wrapf("sending rewards to supplier: %v", err) + // Send a portion of the rewards to the supplier shareholders. + coinsToShareAmt := calculateGlobalMintAllocationFromSettlementAmount(newMintAmtFloat, MintAllocationSupplier) + if err = k.distributeSupplierRewardsToShareHolders(ctx, supplier.OperatorAddress, service.Id, uint64(coinsToShareAmt)); err != nil { + return tokenomicstypes.ErrTokenomicsSupplierModuleMintFailed.Wrapf( + "distributing rewards to supplier with operator address %s shareholders: %v", + supplier.OperatorAddress, + err, + ) } - logger.Debug(fmt.Sprintf("sent (%v) newley minted coins from the tokenomics module to the supplier with operator address %q", supplierCoins, supplier.OperatorAddress)) + supplierCoin := cosmostypes.NewCoin(volatile.DenomuPOKT, math.NewInt(newMintAmtInt)) + logger.Debug(fmt.Sprintf("sent (%v) newley minted coins from the tokenomics module to the supplier with address %q", supplierCoin, supplier.OperatorAddress)) // Send a portion of the rewards to the DAO - daoCoins, err := k.sendRewardsToAccount(ctx, k.GetAuthority(), newMintAmtFloat, MintAllocationDAO) + daoCoin, err := k.sendRewardsToAccount(ctx, k.GetAuthority(), newMintAmtFloat, MintAllocationDAO) if err != nil { - return tokenomictypes.ErrTokenomicsSendingMindRewards.Wrapf("sending rewards to DAO: %v", err) + return tokenomictypes.ErrTokenomicsSendingMintRewards.Wrapf("sending rewards to DAO: %v", err) } - logger.Debug(fmt.Sprintf("sent (%v) newley minted coins from the tokenomics module to the DAO with address %q", daoCoins, k.GetAuthority())) + logger.Debug(fmt.Sprintf("sent (%v) newley minted coins from the tokenomics module to the DAO with address %q", daoCoin, k.GetAuthority())) // Send a portion of the rewards to the source owner - serviceCoins, err := k.sendRewardsToAccount(ctx, service.OwnerAddress, newMintAmtFloat, MintAllocationSourceOwner) + serviceCoin, err := k.sendRewardsToAccount(ctx, service.OwnerAddress, newMintAmtFloat, MintAllocationSourceOwner) if err != nil { - return tokenomictypes.ErrTokenomicsSendingMindRewards.Wrapf("sending rewards to source owner: %v", err) + return tokenomictypes.ErrTokenomicsSendingMintRewards.Wrapf("sending rewards to source owner: %v", err) } - logger.Debug(fmt.Sprintf("sent (%v) newley minted coins from the tokenomics module to the source owner with address %q", serviceCoins, service.OwnerAddress)) + logger.Debug(fmt.Sprintf("sent (%v) newley minted coins from the tokenomics module to the source owner with address %q", serviceCoin, service.OwnerAddress)) // Send a portion of the rewards to the block proposer proposerAddr := cosmostypes.AccAddress(sdk.UnwrapSDKContext(ctx).BlockHeader().ProposerAddress).String() - proposerCoins, err := k.sendRewardsToAccount(ctx, proposerAddr, newMintAmtFloat, MintAllocationProposer) + proposerCoin, err := k.sendRewardsToAccount(ctx, proposerAddr, newMintAmtFloat, MintAllocationProposer) if err != nil { - return tokenomictypes.ErrTokenomicsSendingMindRewards.Wrapf("sending rewards to proposer: %v", err) + return tokenomictypes.ErrTokenomicsSendingMintRewards.Wrapf("sending rewards to proposer: %v", err) } - logger.Debug(fmt.Sprintf("sent (%v) newley minted coins from the tokenomics module to the proposer with address %q", proposerCoins, proposerAddr)) + logger.Debug(fmt.Sprintf("sent (%v) newley minted coins from the tokenomics module to the proposer with address %q", proposerCoin, proposerAddr)) // TODO_MAINNET: Verify that the total distributed coins equals the settlement coins which could happen due to float rounding - totalDistributedCoins := appCoins.Add(*supplierCoins).Add(*daoCoins).Add(*serviceCoins).Add(*proposerCoins) + totalDistributedCoins := appCoin.Add(supplierCoin).Add(*daoCoin).Add(*serviceCoin).Add(*proposerCoin) if totalDistributedCoins.Amount.BigInt().Cmp(settlementCoins.Amount.BigInt()) != 0 { logger.Error(fmt.Sprintf("TODO_MAINNET: The total distributed coins (%v) does not equal the settlement coins (%v)", totalDistributedCoins, settlementCoins.Amount.BigInt())) } @@ -410,7 +411,7 @@ func (k Keeper) sendRewardsToAccount( return nil, err } - coinsToAccAmt, _ := new(big.Float).Mul(settlementAmtFloat, big.NewFloat(allocation)).Int64() + coinsToAccAmt := calculateGlobalMintAllocationFromSettlementAmount(settlementAmtFloat, allocation) coinToAcc := cosmostypes.NewCoin(volatile.DenomuPOKT, math.NewInt(coinsToAccAmt)) if err := k.bankKeeper.SendCoinsFromModuleToAccount( ctx, suppliertypes.ModuleName, accountAddr, sdk.NewCoins(coinToAcc), @@ -480,3 +481,100 @@ func (k Keeper) numRelaysToCoin( return cosmostypes.NewCoin(volatile.DenomuPOKT, upoktAmount), nil } + +// distributeSupplierRewardsToShareHolders distributes the supplier rewards to its +// shareholders based on the rev share percentage of the supplier service config. +func (k Keeper) distributeSupplierRewardsToShareHolders( + ctx context.Context, + supplierAddr string, + serviceId string, + amountToDistribute uint64, +) error { + logger := k.Logger().With("method", "distributeSupplierRewardsToShareHolders") + + supplier, supplierFound := k.supplierKeeper.GetSupplier(ctx, supplierAddr) + if !supplierFound { + return tokenomicstypes.ErrTokenomicsSupplierRevShareFailed.Wrapf( + "supplier with address %q not found", + supplierAddr, + ) + } + + var serviceRevShare []*sharedtypes.ServiceRevenueShare + for _, svc := range supplier.Services { + if svc.Service.Id == serviceId { + serviceRevShare = svc.RevShare + break + } + } + + if serviceRevShare == nil { + return tokenomicstypes.ErrTokenomicsSupplierRevShareFailed.Wrapf( + "service %q not found for supplier %v", + serviceId, + supplier, + ) + } + + shareAmountMap := GetShareAmountMap(serviceRevShare, amountToDistribute) + for shareHolderAddress, shareAmount := range shareAmountMap { + shareAmountCoin := cosmostypes.NewCoin(volatile.DenomuPOKT, math.NewInt(int64(shareAmount))) + shareAmountCoins := cosmostypes.NewCoins(shareAmountCoin) + shareHolderAccAddress, err := sdk.AccAddressFromBech32(shareHolderAddress) + if err != nil { + return err + } + + // Send the newley minted uPOKT from the supplier module account + // to the supplier's shareholders. + if err := k.bankKeeper.SendCoinsFromModuleToAccount( + ctx, suppliertypes.ModuleName, shareHolderAccAddress, shareAmountCoins, + ); err != nil { + return err + } + + logger.Info(fmt.Sprintf("sent %s from the supplier module to the supplier shareholder with address %q", shareAmountCoin, supplierAddr)) + } + + logger.Info(fmt.Sprintf("distributed %d uPOKT to supplier %q shareholders", amountToDistribute, supplierAddr)) + + return nil +} + +// calculateGlobalMintAllocationFromSettlementAmount calculates the global mint +// allocation resulting from the GlobalMint TLM given the settlement amount and +// the allocation percentage. +func calculateGlobalMintAllocationFromSettlementAmount( + settlementAmtFloat *big.Float, + allocation float64, +) int64 { + coinsToAccAmt, _ := big.NewFloat(0).Mul(settlementAmtFloat, big.NewFloat(allocation)).Int64() + return coinsToAccAmt +} + +// GetShareAmountMap calculates the amount of uPOKT to distribute to each revenue +// shareholder based on the rev share percentage of the service. +// It returns a map of the shareholder address to the amount of uPOKT to distribute. +// The first shareholder gets any remainder due to floating point arithmetic. +// NB: It is publically exposed to be used in the tests. +func GetShareAmountMap( + serviceRevShare []*sharedtypes.ServiceRevenueShare, + amountToDistribute uint64, +) (shareAmountMap map[string]uint64) { + totalDistributed := uint64(0) + shareAmountMap = make(map[string]uint64, len(serviceRevShare)) + for _, revshare := range serviceRevShare { + // TODO_MAINNET: Consider using fixed point arithmetic for deterministic results. + sharePercentageFloat := big.NewFloat(float64(revshare.RevSharePercentage) / 100) + amountToDistributeFloat := big.NewFloat(float64(amountToDistribute)) + shareAmount, _ := big.NewFloat(0).Mul(amountToDistributeFloat, sharePercentageFloat).Uint64() + shareAmountMap[revshare.Address] = shareAmount + totalDistributed += shareAmount + } + + // Add any remainder due to floating point arithmetic to the first shareholder. + remainder := amountToDistribute - totalDistributed + shareAmountMap[serviceRevShare[0].Address] += remainder + + return shareAmountMap +} diff --git a/x/tokenomics/keeper/token_logic_modules_test.go b/x/tokenomics/keeper/token_logic_modules_test.go index d80f825b5..eb2bea113 100644 --- a/x/tokenomics/keeper/token_logic_modules_test.go +++ b/x/tokenomics/keeper/token_logic_modules_test.go @@ -25,6 +25,7 @@ import ( sessiontypes "github.com/pokt-network/poktroll/x/session/types" sharedtypes "github.com/pokt-network/poktroll/x/shared/types" suppliertypes "github.com/pokt-network/poktroll/x/supplier/types" + "github.com/pokt-network/poktroll/x/tokenomics/keeper" tokenomicstypes "github.com/pokt-network/poktroll/x/tokenomics/types" ) @@ -54,11 +55,23 @@ func TestProcessTokenLogicModules_HandleAppGoingIntoDebt(t *testing.T) { keepers.SetApplication(ctx, app) // Add a new supplier + supplierOwnerAddress := sample.AccAddress() supplierStake := cosmostypes.NewCoin("upokt", math.NewInt(1000000)) supplier := sharedtypes.Supplier{ - OwnerAddress: sample.AccAddress(), - OperatorAddress: sample.AccAddress(), + OwnerAddress: supplierOwnerAddress, + OperatorAddress: supplierOwnerAddress, Stake: &supplierStake, + Services: []*sharedtypes.SupplierServiceConfig{ + { + Service: service, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: supplierOwnerAddress, + RevSharePercentage: 100, + }, + }, + }, + }, } keepers.SetSupplier(ctx, supplier) @@ -113,12 +126,29 @@ func TestProcessTokenLogicModules_ValidAccounting(t *testing.T) { } keepers.SetApplication(ctx, app) + shareRatios := []float32{12.5, 37.5, 50} + revShares := make([]*sharedtypes.ServiceRevenueShare, len(shareRatios)) + for i := range revShares { + shareHolderAddress := sample.AccAddress() + revShares[i] = &sharedtypes.ServiceRevenueShare{ + Address: shareHolderAddress, + RevSharePercentage: shareRatios[i], + } + } + // Add a new supplier. supplierStake := cosmostypes.NewCoin("upokt", math.NewInt(1000000)) supplier := sharedtypes.Supplier{ - OwnerAddress: sample.AccAddress(), - OperatorAddress: sample.AccAddress(), + // Make the first shareholder the supplier itself. + OwnerAddress: revShares[0].Address, + OperatorAddress: revShares[0].Address, Stake: &supplierStake, + Services: []*sharedtypes.SupplierServiceConfig{ + { + Service: service, + RevShare: revShares, + }, + }, } keepers.SetSupplier(ctx, supplier) @@ -127,8 +157,6 @@ func TestProcessTokenLogicModules_ValidAccounting(t *testing.T) { // Query application module balance prior to the accounting. appModuleStartBalance := getBalance(t, ctx, keepers, appModuleAddress) - // Query supplier balance prior to the accounting. - supplierStartBalance := getBalance(t, ctx, keepers, supplier.GetOwnerAddress()) // Query supplier module balance prior to the accounting. supplierModuleStartBalance := getBalance(t, ctx, keepers, supplierModuleAddress) @@ -169,10 +197,18 @@ func TestProcessTokenLogicModules_ValidAccounting(t *testing.T) { require.NotNil(t, appModuleEndBalance) require.EqualValues(t, &expectedAppModuleEndBalance, appModuleEndBalance) - // Assert that `supplierOwnerAddress` account balance has *increased* by the appropriate amount - supplierOwnerEndBalance := getBalance(t, ctx, keepers, supplier.GetOwnerAddress()) - expectedSupplierBalance := supplierStartBalance.Add(expectedAppBurn) - require.EqualValues(t, &expectedSupplierBalance, supplierOwnerEndBalance) + // Assert that the supplier shareholders account balances have *increased* by + // the appropriate amount. + mintAmountInt := expectedAppBurn.Amount.Uint64() + shareAmounts := keeper.GetShareAmountMap(supplier.Services[0].RevShare, mintAmountInt) + for shareHolder, expectedShareAmount := range shareAmounts { + shareHolderBalance := getBalance(t, ctx, keepers, shareHolder) + + require.Equal(t, + int64(expectedShareAmount), + shareHolderBalance.Amount.Int64(), + ) + } // Assert that `supplierOperatorAddress` staked balance is *unchanged* supplier, supplierIsFound := keepers.GetSupplier(ctx, supplier.GetOperatorAddress()) @@ -224,11 +260,23 @@ func TestProcessTokenLogicModules_AppStakeTooLow(t *testing.T) { appModuleStartBalance := getBalance(t, ctx, keepers, appModuleAddress) // Add a new supplier. + supplierOwnerAddress := sample.AccAddress() supplierStake := cosmostypes.NewCoin("upokt", math.NewInt(1000000)) supplier := sharedtypes.Supplier{ - OwnerAddress: sample.AccAddress(), - OperatorAddress: sample.AccAddress(), + OwnerAddress: supplierOwnerAddress, + OperatorAddress: supplierOwnerAddress, Stake: &supplierStake, + Services: []*sharedtypes.SupplierServiceConfig{ + { + Service: service, + RevShare: []*sharedtypes.ServiceRevenueShare{ + { + Address: supplierOwnerAddress, + RevSharePercentage: 100, + }, + }, + }, + }, } keepers.SetSupplier(ctx, supplier) diff --git a/x/tokenomics/types/errors.go b/x/tokenomics/types/errors.go index 1c4f6443d..3ff72fc5a 100644 --- a/x/tokenomics/types/errors.go +++ b/x/tokenomics/types/errors.go @@ -28,7 +28,8 @@ var ( ErrTokenomicsApplicationOverserviced = sdkerrors.Register(ModuleName, 1119, "application was overserviced") ErrTokenomicsServiceNotFound = sdkerrors.Register(ModuleName, 1120, "service not found") ErrTokenomicsModuleMintFailed = sdkerrors.Register(ModuleName, 1121, "failed to mint uPOKT to tokenomics module account") - ErrTokenomicsSendingMindRewards = sdkerrors.Register(ModuleName, 1122, "failed to send minted rewards") + ErrTokenomicsSendingMintRewards = sdkerrors.Register(ModuleName, 1122, "failed to send minted rewards") ErrTokenomicsSupplierModuleMintFailed = sdkerrors.Register(ModuleName, 1123, "failed to mint uPOKT to supplier module account") ErrTokenomicsSupplierOwnerAddressInvalid = sdkerrors.Register(ModuleName, 1124, "the supplier owner address in the claim is not a valid bech32 address") + ErrTokenomicsSupplierRevShareFailed = sdkerrors.Register(ModuleName, 1125, "failed to send rev share to supplier shareholders") )