From 440334a4e9be86a68f622774bd96f737e80e57d4 Mon Sep 17 00:00:00 2001 From: Collin Brittain Date: Fri, 11 Oct 2024 10:00:27 -0500 Subject: [PATCH 1/8] Add validator distribution fields to incentives Params --- proto/incentives/v1/genesis.proto | 4 + x/incentives/types/genesis.pb.go | 134 +++++++++++++++++++++++++----- 2 files changed, 117 insertions(+), 21 deletions(-) diff --git a/proto/incentives/v1/genesis.proto b/proto/incentives/v1/genesis.proto index 66a332344..1781d1faa 100644 --- a/proto/incentives/v1/genesis.proto +++ b/proto/incentives/v1/genesis.proto @@ -18,4 +18,8 @@ message Params { // IncentivesCutoffHeight defines the block height after which the incentives module will stop sending coins to the distribution module from // the community pool uint64 incentives_cutoff_height = 2; + // ValidatorDistributionPerBlock defines the coin to be sent to the distribution module from the community pool every block + cosmos.base.v1beta1.Coin validator_distribution_per_block = 3 [(gogoproto.nullable) = false]; + // ValidatorIncentivesCutoffHeight defines the block height after which the validator incentives will be stopped + uint64 validator_incentives_cutoff_height = 4; } diff --git a/x/incentives/types/genesis.pb.go b/x/incentives/types/genesis.pb.go index 42b1b63c0..f288f9d30 100644 --- a/x/incentives/types/genesis.pb.go +++ b/x/incentives/types/genesis.pb.go @@ -75,6 +75,10 @@ type Params struct { // IncentivesCutoffHeight defines the block height after which the incentives module will stop sending coins to the distribution module from // the community pool IncentivesCutoffHeight uint64 `protobuf:"varint,2,opt,name=incentives_cutoff_height,json=incentivesCutoffHeight,proto3" json:"incentives_cutoff_height,omitempty"` + // ValidatorDistributionPerBlock defines the coin to be sent to the distribution module from the community pool every block + ValidatorDistributionPerBlock types.Coin `protobuf:"bytes,3,opt,name=validator_distribution_per_block,json=validatorDistributionPerBlock,proto3" json:"validator_distribution_per_block"` + // ValidatorIncentivesCutoffHeight defines the block height after which the validator incentives will be stopped + ValidatorIncentivesCutoffHeight uint64 `protobuf:"varint,4,opt,name=validator_incentives_cutoff_height,json=validatorIncentivesCutoffHeight,proto3" json:"validator_incentives_cutoff_height,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -124,6 +128,20 @@ func (m *Params) GetIncentivesCutoffHeight() uint64 { return 0 } +func (m *Params) GetValidatorDistributionPerBlock() types.Coin { + if m != nil { + return m.ValidatorDistributionPerBlock + } + return types.Coin{} +} + +func (m *Params) GetValidatorIncentivesCutoffHeight() uint64 { + if m != nil { + return m.ValidatorIncentivesCutoffHeight + } + return 0 +} + func init() { proto.RegisterType((*GenesisState)(nil), "incentives.v1.GenesisState") proto.RegisterType((*Params)(nil), "incentives.v1.Params") @@ -132,27 +150,29 @@ func init() { func init() { proto.RegisterFile("incentives/v1/genesis.proto", fileDescriptor_179cfb82d3e2b395) } var fileDescriptor_179cfb82d3e2b395 = []byte{ - // 309 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x54, 0xd0, 0xb1, 0x4f, 0x3a, 0x31, - 0x14, 0x07, 0xf0, 0xeb, 0x2f, 0x84, 0xe1, 0x7e, 0xba, 0x5c, 0x90, 0x20, 0x26, 0x95, 0x30, 0x31, - 0xb5, 0x39, 0x18, 0x74, 0x86, 0x41, 0x07, 0x07, 0x82, 0x71, 0x71, 0xb9, 0xdc, 0xd5, 0x47, 0xa9, - 0x72, 0x7d, 0x97, 0xb6, 0x34, 0xf2, 0x5f, 0xb8, 0xfa, 0x1f, 0x31, 0x32, 0x3a, 0x19, 0x03, 0xff, - 0x88, 0xe1, 0xee, 0x12, 0x70, 0x6b, 0xf2, 0x79, 0xef, 0xdb, 0x6f, 0x5e, 0x78, 0xa5, 0xb4, 0x00, - 0xed, 0x94, 0x07, 0xcb, 0x7d, 0xcc, 0x25, 0x68, 0xb0, 0xca, 0xb2, 0xc2, 0xa0, 0xc3, 0xe8, 0xfc, - 0x88, 0xcc, 0xc7, 0xdd, 0x96, 0x44, 0x89, 0xa5, 0xf0, 0xc3, 0xab, 0x1a, 0xea, 0x52, 0x81, 0x36, - 0x47, 0xcb, 0xb3, 0xd4, 0x02, 0xf7, 0x71, 0x06, 0x2e, 0x8d, 0xb9, 0x40, 0xa5, 0x2b, 0xef, 0x4f, - 0xc2, 0xb3, 0xbb, 0x2a, 0xf5, 0xd1, 0xa5, 0x0e, 0xa2, 0x51, 0xd8, 0x2c, 0x52, 0x93, 0xe6, 0xb6, - 0x43, 0x7a, 0x64, 0xf0, 0x7f, 0x78, 0xc1, 0xfe, 0xfc, 0xc2, 0xa6, 0x25, 0x8e, 0x1b, 0x9b, 0xef, - 0xeb, 0x60, 0x56, 0x8f, 0xf6, 0x3f, 0x49, 0xd8, 0xac, 0x20, 0x7a, 0x0a, 0xdb, 0x2f, 0xca, 0x3a, - 0xa3, 0xb2, 0x95, 0x53, 0xa8, 0x93, 0x02, 0x4c, 0x92, 0x2d, 0x51, 0xbc, 0xd5, 0x79, 0x97, 0xac, - 0x2a, 0xc4, 0x0e, 0x85, 0x58, 0x5d, 0x88, 0x4d, 0x50, 0xe9, 0x3a, 0xb3, 0x75, 0xba, 0x3e, 0x05, - 0x33, 0x3e, 0x2c, 0x47, 0xb7, 0x61, 0xe7, 0xd8, 0x23, 0x11, 0x2b, 0x87, 0xf3, 0x79, 0xb2, 0x00, - 0x25, 0x17, 0xae, 0xf3, 0xaf, 0x47, 0x06, 0x8d, 0x59, 0xfb, 0xe8, 0x93, 0x92, 0xef, 0x4b, 0x1d, - 0x3f, 0x6c, 0x76, 0x94, 0x6c, 0x77, 0x94, 0xfc, 0xec, 0x28, 0xf9, 0xd8, 0xd3, 0x60, 0xbb, 0xa7, - 0xc1, 0xd7, 0x9e, 0x06, 0xcf, 0x43, 0xa9, 0xdc, 0x62, 0x95, 0x31, 0x81, 0x39, 0x2f, 0x40, 0xca, - 0xf5, 0xab, 0xe7, 0x16, 0xf3, 0x1c, 0x96, 0x0a, 0x0c, 0xf7, 0x37, 0xfc, 0x9d, 0x9f, 0x9c, 0xdf, - 0xad, 0x0b, 0xb0, 0x59, 0xb3, 0xbc, 0xda, 0xe8, 0x37, 0x00, 0x00, 0xff, 0xff, 0xa1, 0x68, 0x83, - 0x05, 0x99, 0x01, 0x00, 0x00, + // 352 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xc1, 0x6a, 0xea, 0x40, + 0x14, 0x86, 0x13, 0xaf, 0xb8, 0x98, 0x7b, 0xef, 0x26, 0x78, 0xc5, 0x6b, 0x69, 0x14, 0x57, 0xae, + 0x66, 0x88, 0x2e, 0xda, 0xb5, 0x16, 0xda, 0xd2, 0x2e, 0xc4, 0xd2, 0x4d, 0x37, 0x21, 0x89, 0xc7, + 0x64, 0x5a, 0x93, 0x13, 0x66, 0xc6, 0xa1, 0xbe, 0x45, 0x1f, 0xcb, 0xa5, 0xcb, 0xae, 0x4a, 0xd1, + 0x17, 0x29, 0x26, 0xc1, 0x58, 0x50, 0xe8, 0x6e, 0xe0, 0xfb, 0xcf, 0x7f, 0x3e, 0x98, 0x43, 0xce, + 0x78, 0x12, 0x40, 0xa2, 0xb8, 0x06, 0xc9, 0xb4, 0xc3, 0x42, 0x48, 0x40, 0x72, 0x49, 0x53, 0x81, + 0x0a, 0xad, 0xbf, 0x25, 0xa4, 0xda, 0x69, 0xd5, 0x43, 0x0c, 0x31, 0x23, 0x6c, 0xf7, 0xca, 0x43, + 0x2d, 0x3b, 0x40, 0x19, 0xa3, 0x64, 0xbe, 0x27, 0x81, 0x69, 0xc7, 0x07, 0xe5, 0x39, 0x2c, 0x40, + 0x9e, 0xe4, 0xbc, 0x3b, 0x22, 0x7f, 0xae, 0xf3, 0xd6, 0x07, 0xe5, 0x29, 0xb0, 0x06, 0xa4, 0x96, + 0x7a, 0xc2, 0x8b, 0x65, 0xd3, 0xec, 0x98, 0xbd, 0xdf, 0xfd, 0x7f, 0xf4, 0xdb, 0x16, 0x3a, 0xce, + 0xe0, 0xb0, 0xba, 0xfa, 0x68, 0x1b, 0x93, 0x22, 0xda, 0x5d, 0x57, 0x48, 0x2d, 0x07, 0xd6, 0x23, + 0x69, 0x4c, 0xb9, 0x54, 0x82, 0xfb, 0x0b, 0xc5, 0x31, 0x71, 0x53, 0x10, 0xae, 0x3f, 0xc7, 0xe0, + 0xa5, 0xe8, 0xfb, 0x4f, 0x73, 0x21, 0xba, 0x13, 0xa2, 0x85, 0x10, 0x1d, 0x21, 0x4f, 0x8a, 0xce, + 0xfa, 0xe1, 0xf8, 0x18, 0xc4, 0x70, 0x37, 0x6c, 0x5d, 0x92, 0x66, 0xe9, 0xe1, 0x06, 0x0b, 0x85, + 0xb3, 0x99, 0x1b, 0x01, 0x0f, 0x23, 0xd5, 0xac, 0x74, 0xcc, 0x5e, 0x75, 0xd2, 0x28, 0xf9, 0x28, + 0xc3, 0x37, 0x19, 0xb5, 0x22, 0xd2, 0xd1, 0xde, 0x9c, 0x4f, 0x3d, 0x85, 0xc2, 0x3d, 0xa1, 0xf6, + 0xeb, 0x67, 0x6a, 0xe7, 0xfb, 0xa2, 0xab, 0x63, 0x8e, 0x77, 0xa4, 0x5b, 0x6e, 0x3a, 0x69, 0x5b, + 0xcd, 0x6c, 0xdb, 0xfb, 0xe4, 0xed, 0x51, 0xed, 0xe1, 0xfd, 0x6a, 0x63, 0x9b, 0xeb, 0x8d, 0x6d, + 0x7e, 0x6e, 0x6c, 0xf3, 0x6d, 0x6b, 0x1b, 0xeb, 0xad, 0x6d, 0xbc, 0x6f, 0x6d, 0xe3, 0xa9, 0x1f, + 0x72, 0x15, 0x2d, 0x7c, 0x1a, 0x60, 0xcc, 0x52, 0x08, 0xc3, 0xe5, 0xb3, 0x66, 0x12, 0xe3, 0x18, + 0xe6, 0x1c, 0x04, 0xd3, 0x17, 0xec, 0x95, 0x1d, 0x5c, 0x8d, 0x5a, 0xa6, 0x20, 0xfd, 0x5a, 0xf6, + 0xd9, 0x83, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x16, 0xce, 0x6a, 0xe2, 0x50, 0x02, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -208,6 +228,21 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.ValidatorIncentivesCutoffHeight != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.ValidatorIncentivesCutoffHeight)) + i-- + dAtA[i] = 0x20 + } + { + size, err := m.ValidatorDistributionPerBlock.MarshalToSizedBuffer(dAtA[:i]) + if err != nil { + return 0, err + } + i -= size + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1a if m.IncentivesCutoffHeight != 0 { i = encodeVarintGenesis(dAtA, i, uint64(m.IncentivesCutoffHeight)) i-- @@ -259,6 +294,11 @@ func (m *Params) Size() (n int) { if m.IncentivesCutoffHeight != 0 { n += 1 + sovGenesis(uint64(m.IncentivesCutoffHeight)) } + l = m.ValidatorDistributionPerBlock.Size() + n += 1 + l + sovGenesis(uint64(l)) + if m.ValidatorIncentivesCutoffHeight != 0 { + n += 1 + sovGenesis(uint64(m.ValidatorIncentivesCutoffHeight)) + } return n } @@ -432,6 +472,58 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorDistributionPerBlock", wireType) + } + var msglen int + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + msglen |= int(b&0x7F) << shift + if b < 0x80 { + break + } + } + if msglen < 0 { + return ErrInvalidLengthGenesis + } + postIndex := iNdEx + msglen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ValidatorDistributionPerBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorIncentivesCutoffHeight", wireType) + } + m.ValidatorIncentivesCutoffHeight = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ValidatorIncentivesCutoffHeight |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) From 88e2bac5f33f2151b2d3910212d97108029b45ce Mon Sep 17 00:00:00 2001 From: Collin Brittain Date: Tue, 15 Oct 2024 11:30:51 -0500 Subject: [PATCH 2/8] Fix help for a cork-result commands --- x/axelarcork/client/cli/query.go | 2 +- x/cork/client/cli/query.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/x/axelarcork/client/cli/query.go b/x/axelarcork/client/cli/query.go index 94e8781f9..cb0c33fff 100644 --- a/x/axelarcork/client/cli/query.go +++ b/x/axelarcork/client/cli/query.go @@ -297,7 +297,7 @@ func queryScheduledCorksByID() *cobra.Command { func queryCorkResult() *cobra.Command { cmd := &cobra.Command{ - Use: "cork-result [chain-id] [cork-id]", + Use: "cork-result [cork-id] [chain-id]", Aliases: []string{"cr"}, Args: cobra.ExactArgs(2), Short: "query cork result from the chain", diff --git a/x/cork/client/cli/query.go b/x/cork/client/cli/query.go index 34b8bdaed..8710f5f97 100644 --- a/x/cork/client/cli/query.go +++ b/x/cork/client/cli/query.go @@ -227,7 +227,7 @@ func queryScheduledCorksByID() *cobra.Command { func queryCorkResult() *cobra.Command { cmd := &cobra.Command{ - Use: "cork-result", + Use: "cork-result [cork-id]", Aliases: []string{"cr"}, Args: cobra.ExactArgs(1), Short: "query cork result from the chain", From 7611c45a31ee68d87654495d7a395fc96b70efe7 Mon Sep 17 00:00:00 2001 From: Collin Brittain Date: Tue, 15 Oct 2024 11:31:04 -0500 Subject: [PATCH 3/8] WIP - First attempt at validator incentives allocation --- proto/incentives/v1/genesis.proto | 4 + x/incentives/keeper/abci.go | 55 +++++- x/incentives/keeper/abci_test.go | 9 +- x/incentives/keeper/incentives.go | 69 ++++++++ x/incentives/keeper/incentives_test.go | 235 +++++++++++++++++++++++++ x/incentives/module.go | 6 +- x/incentives/types/errors.go | 5 +- x/incentives/types/genesis.pb.go | 137 +++++++++++--- x/incentives/types/params.go | 56 +++++- 9 files changed, 541 insertions(+), 35 deletions(-) create mode 100644 x/incentives/keeper/incentives.go create mode 100644 x/incentives/keeper/incentives_test.go diff --git a/proto/incentives/v1/genesis.proto b/proto/incentives/v1/genesis.proto index 1781d1faa..707a0863f 100644 --- a/proto/incentives/v1/genesis.proto +++ b/proto/incentives/v1/genesis.proto @@ -22,4 +22,8 @@ message Params { cosmos.base.v1beta1.Coin validator_distribution_per_block = 3 [(gogoproto.nullable) = false]; // ValidatorIncentivesCutoffHeight defines the block height after which the validator incentives will be stopped uint64 validator_incentives_cutoff_height = 4; + // ValidatorIncentivesMaxFraction defines the maximum fraction of the validator distribution per block that can be sent to a single validator + string validator_incentives_max_fraction = 5 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false]; + // ValidatorIncentivesSetSizeLimit defines the max number of validators to apportion the validator distribution per block to + uint64 validator_incentives_set_size_limit = 6; } diff --git a/x/incentives/keeper/abci.go b/x/incentives/keeper/abci.go index 345259bc2..b34283a9a 100644 --- a/x/incentives/keeper/abci.go +++ b/x/incentives/keeper/abci.go @@ -1,13 +1,66 @@ package keeper import ( + abci "github.com/cometbft/cometbft/abci/types" authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" sdk "github.com/cosmos/cosmos-sdk/types" ) -func (k Keeper) BeginBlocker(ctx sdk.Context) {} +func (k Keeper) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) { + incentivesParams := k.GetParamSet(ctx) + cutoffHeight := incentivesParams.ValidatorIncentivesCutoffHeight + distPerBlock := incentivesParams.ValidatorDistributionPerBlock + if uint64(ctx.BlockHeight()) >= cutoffHeight || distPerBlock.IsZero() { + return + } + + voterInfos := GetSortedVoterInfosByPower(req.LastCommitInfo.GetVotes()) + totalPower := int64(0) + for _, voterInfo := range voterInfos { + totalPower += voterInfo.Validator.Power + } + + // Limit the number of voter info + // TODO: Make this a function that can be unit tested + setSizeLimit := incentivesParams.ValidatorIncentivesSetSizeLimit + if uint64(len(voterInfos)) > setSizeLimit { + voterInfos = voterInfos[:setSizeLimit] + } + + distPerBlockDec := sdk.NewDec(distPerBlock.Amount.Int64()) + validatorMaxPortionFraction := sdk.MustNewDecFromStr("0.1") + apportionments, remaining, err := getApportionments(setSizeLimit, distPerBlockDec, validatorMaxPortionFraction) + if err != nil { + ctx.Logger().Error("Error getting apportionments. Are params properly validated?", "error", err) + return + } + + // Distribute rewards to each validator + feePool := k.DistributionKeeper.GetFeePool(ctx) + newPool, negative := feePool.CommunityPool.SafeSub(sdk.NewDecCoinsFromCoins(distPerBlock)) + if negative { + k.Logger(ctx).Error("Insufficient coins in community to distribute", "community pool", feePool.CommunityPool) + return + } + + for i, voterInfo := range voterInfos { + recipient := sdk.AccAddress(voterInfo.Validator.Address) + amount := apportionments[i].TruncateInt() + if amount.IsZero() { + continue + } + + err := k.BankKeeper.SendCoinsFromModuleToAccount(ctx, distributiontypes.ModuleName, recipient, sdk.NewCoins(sdk.NewCoin(distPerBlock.Denom, amount))) + if err != nil { + panic(err) + } + } + + feePool.CommunityPool = newPool + k.DistributionKeeper.SetFeePool(ctx, feePool) +} // EndBlocker defines Distribution of incentives to stakers // diff --git a/x/incentives/keeper/abci_test.go b/x/incentives/keeper/abci_test.go index 39d1745f3..c3667cd24 100644 --- a/x/incentives/keeper/abci_test.go +++ b/x/incentives/keeper/abci_test.go @@ -1,6 +1,7 @@ package keeper import ( + abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" distributionTypes "github.com/cosmos/cosmos-sdk/x/distribution/types" "github.com/peggyjv/sommelier/v7/app/params" @@ -17,20 +18,20 @@ func (suite *KeeperTestSuite) TestEndBlockerIncentivesDisabledDoesNothing() { // By not mocking any other calls, the test will panic and fail if an unmocked keeper function is called, // implying that the function isn't exiting early as designed. - require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx) }) + require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, abci.RequestBeginBlock{}) }) require.NotPanics(func() { incentivesKeeper.EndBlocker(ctx) }) incentivesParams.DistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.OneInt()) incentivesKeeper.SetParams(ctx, incentivesParams) - require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx) }) + require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, abci.RequestBeginBlock{}) }) require.NotPanics(func() { incentivesKeeper.EndBlocker(ctx) }) incentivesParams.DistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.ZeroInt()) incentivesParams.IncentivesCutoffHeight = 1500 incentivesKeeper.SetParams(ctx, incentivesParams) - require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx) }) + require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, abci.RequestBeginBlock{}) }) require.NotPanics(func() { incentivesKeeper.EndBlocker(ctx) }) } @@ -53,6 +54,6 @@ func (suite *KeeperTestSuite) TestEndBlockerInsufficientCommunityPoolBalance() { // By not mocking the bank SendModuleToModule call, the test will panic and fail if the community pool balance // check branch isn't taken as intended. - require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx) }) + require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, abci.RequestBeginBlock{}) }) require.NotPanics(func() { incentivesKeeper.EndBlocker(ctx) }) } diff --git a/x/incentives/keeper/incentives.go b/x/incentives/keeper/incentives.go new file mode 100644 index 000000000..347f9f025 --- /dev/null +++ b/x/incentives/keeper/incentives.go @@ -0,0 +1,69 @@ +package keeper + +import ( + "fmt" + "sort" + + abci "github.com/cometbft/cometbft/abci/types" + sdk "github.com/cosmos/cosmos-sdk/types" +) + +type VoterInfo struct { + Validator *abci.Validator +} + +// GetSortedVoterInfosByPower returns the previous block's voter information by validator power in descending order +func GetSortedVoterInfosByPower(votes []abci.VoteInfo) []VoterInfo { + voterInfos := []VoterInfo{} + for i := range votes { + if !votes[i].SignedLastBlock { + continue + } + + voterInfos = append(voterInfos, VoterInfo{ + Validator: &votes[i].Validator, + }) + } + + // Sort voteInfos by descending Power + sort.Slice(voterInfos, func(i, j int) bool { + return voterInfos[i].Validator.Power > voterInfos[j].Validator.Power + }) + + return voterInfos +} + +// GetApportionments returns a slice of fractions of the passed in value that sums to the value approximately, and +// a remaining value. The sum of the returned slice plus the remaining value will equal the original value. +func getApportionments(numPortions uint64, value sdk.Dec, maxPortionFraction sdk.Dec) ([]sdk.Dec, sdk.Dec, error) { + // We error check for sanity, even though the arguments should only be coming from validated Param values + if numPortions == 0 { + return make([]sdk.Dec, 0), value, nil + } + + if value.IsNegative() { + value = sdk.ZeroDec() + } + + if maxPortionFraction.IsNegative() { + return nil, sdk.ZeroDec(), fmt.Errorf("max portion cannot be negative") + } else if maxPortionFraction.GT(sdk.OneDec()) { + return nil, sdk.ZeroDec(), fmt.Errorf("max portion must be less than or equal to one") + } + + remainingValue := value + apportionments := make([]sdk.Dec, numPortions) + + for i := 0; i < len(apportionments); i++ { + if remainingValue.IsZero() || maxPortionFraction.IsZero() { + apportionments[i] = sdk.ZeroDec() + continue + } + + portion := remainingValue.Mul(maxPortionFraction) + apportionments[i] = portion + remainingValue = remainingValue.Sub(portion) + } + + return apportionments, remainingValue, nil +} diff --git a/x/incentives/keeper/incentives_test.go b/x/incentives/keeper/incentives_test.go new file mode 100644 index 000000000..91ba7877b --- /dev/null +++ b/x/incentives/keeper/incentives_test.go @@ -0,0 +1,235 @@ +package keeper + +import ( + "testing" + + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/stretchr/testify/require" +) + +func TestGetApportionments(t *testing.T) { + tests := []struct { + name string + numPortions uint64 + value sdk.Dec + maxPortion sdk.Dec + want []sdk.Dec + wantErr bool + }{ + { + name: "Negative value", + numPortions: 10, + value: sdk.NewDec(-100), + maxPortion: sdk.MustNewDecFromStr("0.1"), + wantErr: true, + }, + { + name: "Zero length slice", + numPortions: 0, + value: sdk.NewDec(100), + maxPortion: sdk.MustNewDecFromStr("0.1"), + want: make([]sdk.Dec, 0), + }, + { + name: "Zero value", + numPortions: 10, + value: sdk.NewDec(0), + maxPortion: sdk.MustNewDecFromStr("0.1"), + want: []sdk.Dec{sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()}, + }, + { + name: "Max portion", + numPortions: 10, + value: sdk.NewDec(100), + maxPortion: sdk.OneDec(), + want: []sdk.Dec{sdk.NewDec(100), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()}, + }, + { + name: "Max portion is zero", + numPortions: 10, + value: sdk.NewDec(100), + maxPortion: sdk.ZeroDec(), + want: []sdk.Dec{sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()}, + }, + { + name: "Max portion is negative", + numPortions: 10, + value: sdk.NewDec(100), + maxPortion: sdk.MustNewDecFromStr("-0.1"), + wantErr: true, + }, + { + name: "Max portion greater than 1", + numPortions: 10, + value: sdk.NewDec(100), + maxPortion: sdk.NewDec(2), + wantErr: true, + }, + { + name: "Specific distribution with 50 portions with 5% max portion", + numPortions: 50, + value: sdk.NewDec(100), + maxPortion: sdk.MustNewDecFromStr("0.05"), + want: []sdk.Dec{ + sdk.MustNewDecFromStr("5"), + sdk.MustNewDecFromStr("4.75"), + sdk.MustNewDecFromStr("4.5125"), + sdk.MustNewDecFromStr("4.286875"), + sdk.MustNewDecFromStr("4.07253125"), + sdk.MustNewDecFromStr("3.868904688"), + sdk.MustNewDecFromStr("3.675459453"), + sdk.MustNewDecFromStr("3.49168648"), + sdk.MustNewDecFromStr("3.317102156"), + sdk.MustNewDecFromStr("3.151247049"), + sdk.MustNewDecFromStr("2.993684696"), + sdk.MustNewDecFromStr("2.844000461"), + sdk.MustNewDecFromStr("2.701800438"), + sdk.MustNewDecFromStr("2.566710416"), + sdk.MustNewDecFromStr("2.438374896"), + sdk.MustNewDecFromStr("2.316456151"), + sdk.MustNewDecFromStr("2.200633343"), + sdk.MustNewDecFromStr("2.090601676"), + sdk.MustNewDecFromStr("1.986071592"), + sdk.MustNewDecFromStr("1.886768013"), + sdk.MustNewDecFromStr("1.792429612"), + sdk.MustNewDecFromStr("1.702808131"), + sdk.MustNewDecFromStr("1.617667725"), + sdk.MustNewDecFromStr("1.536784339"), + sdk.MustNewDecFromStr("1.459945122"), + sdk.MustNewDecFromStr("1.386947866"), + sdk.MustNewDecFromStr("1.317600472"), + sdk.MustNewDecFromStr("1.251720449"), + sdk.MustNewDecFromStr("1.189134426"), + sdk.MustNewDecFromStr("1.129677705"), + sdk.MustNewDecFromStr("1.07319382"), + sdk.MustNewDecFromStr("1.019534129"), + sdk.MustNewDecFromStr("0.9685574223"), + sdk.MustNewDecFromStr("0.9201295512"), + sdk.MustNewDecFromStr("0.8741230736"), + sdk.MustNewDecFromStr("0.8304169199"), + sdk.MustNewDecFromStr("0.7888960739"), + sdk.MustNewDecFromStr("0.7494512702"), + sdk.MustNewDecFromStr("0.7119787067"), + sdk.MustNewDecFromStr("0.6763797714"), + sdk.MustNewDecFromStr("0.6425607828"), + sdk.MustNewDecFromStr("0.6104327437"), + sdk.MustNewDecFromStr("0.5799111065"), + sdk.MustNewDecFromStr("0.5509155512"), + sdk.MustNewDecFromStr("0.5233697736"), + sdk.MustNewDecFromStr("0.4972012849"), + sdk.MustNewDecFromStr("0.4723412207"), + sdk.MustNewDecFromStr("0.4487241597"), + sdk.MustNewDecFromStr("0.4262879517"), + sdk.MustNewDecFromStr("0.4049735541"), + }, + }, + { + name: "Specific distribution with 50 portions with 10% max portion", + numPortions: 50, + value: sdk.NewDec(100), + maxPortion: sdk.MustNewDecFromStr("0.1"), + want: []sdk.Dec{ + sdk.MustNewDecFromStr("10"), + sdk.MustNewDecFromStr("9"), + sdk.MustNewDecFromStr("8.1"), + sdk.MustNewDecFromStr("7.29"), + sdk.MustNewDecFromStr("6.561"), + sdk.MustNewDecFromStr("5.9049"), + sdk.MustNewDecFromStr("5.31441"), + sdk.MustNewDecFromStr("4.782969"), + sdk.MustNewDecFromStr("4.3046721"), + sdk.MustNewDecFromStr("3.87420489"), + sdk.MustNewDecFromStr("3.486784401"), + sdk.MustNewDecFromStr("3.138105961"), + sdk.MustNewDecFromStr("2.824295365"), + sdk.MustNewDecFromStr("2.541865828"), + sdk.MustNewDecFromStr("2.287679245"), + sdk.MustNewDecFromStr("2.058911321"), + sdk.MustNewDecFromStr("1.853020189"), + sdk.MustNewDecFromStr("1.66771817"), + sdk.MustNewDecFromStr("1.500946353"), + sdk.MustNewDecFromStr("1.350851718"), + sdk.MustNewDecFromStr("1.215766546"), + sdk.MustNewDecFromStr("1.094189891"), + sdk.MustNewDecFromStr("0.9847709022"), + sdk.MustNewDecFromStr("0.886293812"), + sdk.MustNewDecFromStr("0.7976644308"), + sdk.MustNewDecFromStr("0.7178979877"), + sdk.MustNewDecFromStr("0.6461081889"), + sdk.MustNewDecFromStr("0.58149737"), + sdk.MustNewDecFromStr("0.523347633"), + sdk.MustNewDecFromStr("0.4710128697"), + sdk.MustNewDecFromStr("0.4239115828"), + sdk.MustNewDecFromStr("0.3815204245"), + sdk.MustNewDecFromStr("0.343368382"), + sdk.MustNewDecFromStr("0.3090315438"), + sdk.MustNewDecFromStr("0.2781283894"), + sdk.MustNewDecFromStr("0.2503155505"), + sdk.MustNewDecFromStr("0.2252839954"), + sdk.MustNewDecFromStr("0.2027555959"), + sdk.MustNewDecFromStr("0.1824800363"), + sdk.MustNewDecFromStr("0.1642320327"), + sdk.MustNewDecFromStr("0.1478088294"), + sdk.MustNewDecFromStr("0.1330279465"), + sdk.MustNewDecFromStr("0.1197251518"), + sdk.MustNewDecFromStr("0.1077526366"), + sdk.MustNewDecFromStr("0.09697737298"), + sdk.MustNewDecFromStr("0.08727963568"), + sdk.MustNewDecFromStr("0.07855167211"), + sdk.MustNewDecFromStr("0.0706965049"), + sdk.MustNewDecFromStr("0.06362685441"), + sdk.MustNewDecFromStr("0.05726416897"), + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt := tt + got, remaining, err := getApportionments(tt.numPortions, tt.value, tt.maxPortion) + if tt.wantErr { + require.Error(t, err) + } else { + require.NoError(t, err) + + // Correct number of portions + require.Equal(t, tt.numPortions, uint(len(got))) + + // We round before comparing to avoid floating point precision issues. + // Check that the values returned in the slice line up with the expected values. + for i, value := range got { + // Correct apportionment values + require.Equal(t, roundToPrecision(tt.want[i], 6), roundToPrecision(value, 6)) + + // No negative apportionments + require.True(t, value.GTE(sdk.ZeroDec())) + } + + // Non-negative remaining value + require.True(t, remaining.GTE(sdk.ZeroDec())) + + // Sum of apportionments plus remaining equals the original value + gotSum := sdk.ZeroDec() + for _, g := range got { + gotSum = gotSum.Add(g) + } + require.True(t, gotSum.LTE(tt.value)) + require.Equal(t, tt.value, gotSum.Add(remaining)) + + // Test that each value is greater than or equal to the next + for i := 0; i < len(got)-1; i++ { + if i == len(got)-1 { + break + } + + require.True(t, got[i].GTE(got[i+1]), "Value at index %d should be greater than or equal to value at index %d", i, i+1) + } + } + }) + } +} + +func roundToPrecision(d sdk.Dec, precision uint64) sdk.Dec { + multiplier := sdk.NewDec(10).Power(precision) + return d.Mul(multiplier).RoundInt().ToLegacyDec().Quo(multiplier) +} diff --git a/x/incentives/module.go b/x/incentives/module.go index bc102a26c..234c128c2 100644 --- a/x/incentives/module.go +++ b/x/incentives/module.go @@ -106,7 +106,7 @@ func (AppModule) QuerierRoute() string { return types.QuerierRoute } // ConsensusVersion implements AppModule/ConsensusVersion. func (AppModule) ConsensusVersion() uint64 { - return 1 + return 2 } func (am AppModule) WeightedOperations(simState module.SimulationState) []sim.WeightedOperation { @@ -135,8 +135,8 @@ func (am AppModule) ExportGenesis(ctx sdk.Context, cdc codec.JSONCodec) json.Raw } // BeginBlock returns the begin blocker for the incentives module. -func (am AppModule) BeginBlock(ctx sdk.Context, _ abci.RequestBeginBlock) { - am.keeper.BeginBlocker(ctx) +func (am AppModule) BeginBlock(ctx sdk.Context, req abci.RequestBeginBlock) { + am.keeper.BeginBlocker(ctx, req) } // EndBlock returns the end blocker for the incentives module. diff --git a/x/incentives/types/errors.go b/x/incentives/types/errors.go index c59b21c9d..661ec7a0f 100644 --- a/x/incentives/types/errors.go +++ b/x/incentives/types/errors.go @@ -5,5 +5,8 @@ import ( ) var ( - ErrInvalidDistributionPerBlock = errorsmod.Register(ModuleName, 1, "invalid distribution per block") + ErrInvalidDistributionPerBlock = errorsmod.Register(ModuleName, 1, "invalid distribution per block") + ErrInvalidValidatorDistributionPerBlock = errorsmod.Register(ModuleName, 2, "invalid validator distribution per block") + ErrInvalidValidatorIncentivesMaxFraction = errorsmod.Register(ModuleName, 3, "invalid validator incentives max fraction") + ErrInvalidValidatorIncentivesSetSizeLimit = errorsmod.Register(ModuleName, 4, "invalid validator incentives set size limit") ) diff --git a/x/incentives/types/genesis.pb.go b/x/incentives/types/genesis.pb.go index f288f9d30..a4f56505f 100644 --- a/x/incentives/types/genesis.pb.go +++ b/x/incentives/types/genesis.pb.go @@ -5,6 +5,7 @@ package types import ( fmt "fmt" + github_com_cosmos_cosmos_sdk_types "github.com/cosmos/cosmos-sdk/types" types "github.com/cosmos/cosmos-sdk/types" _ "github.com/cosmos/gogoproto/gogoproto" proto "github.com/cosmos/gogoproto/proto" @@ -79,6 +80,10 @@ type Params struct { ValidatorDistributionPerBlock types.Coin `protobuf:"bytes,3,opt,name=validator_distribution_per_block,json=validatorDistributionPerBlock,proto3" json:"validator_distribution_per_block"` // ValidatorIncentivesCutoffHeight defines the block height after which the validator incentives will be stopped ValidatorIncentivesCutoffHeight uint64 `protobuf:"varint,4,opt,name=validator_incentives_cutoff_height,json=validatorIncentivesCutoffHeight,proto3" json:"validator_incentives_cutoff_height,omitempty"` + // ValidatorIncentivesMaxFraction defines the maximum fraction of the validator distribution per block that can be sent to a single validator + ValidatorIncentivesMaxFraction github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=validator_incentives_max_fraction,json=validatorIncentivesMaxFraction,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"validator_incentives_max_fraction"` + // ValidatorIncentivesSetSizeLimit defines the max number of validators to apportion the validator distribution per block to + ValidatorIncentivesSetSizeLimit uint64 `protobuf:"varint,6,opt,name=validator_incentives_set_size_limit,json=validatorIncentivesSetSizeLimit,proto3" json:"validator_incentives_set_size_limit,omitempty"` } func (m *Params) Reset() { *m = Params{} } @@ -142,6 +147,13 @@ func (m *Params) GetValidatorIncentivesCutoffHeight() uint64 { return 0 } +func (m *Params) GetValidatorIncentivesSetSizeLimit() uint64 { + if m != nil { + return m.ValidatorIncentivesSetSizeLimit + } + return 0 +} + func init() { proto.RegisterType((*GenesisState)(nil), "incentives.v1.GenesisState") proto.RegisterType((*Params)(nil), "incentives.v1.Params") @@ -150,29 +162,35 @@ func init() { func init() { proto.RegisterFile("incentives/v1/genesis.proto", fileDescriptor_179cfb82d3e2b395) } var fileDescriptor_179cfb82d3e2b395 = []byte{ - // 352 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0xc1, 0x6a, 0xea, 0x40, - 0x14, 0x86, 0x13, 0xaf, 0xb8, 0x98, 0x7b, 0xef, 0x26, 0x78, 0xc5, 0x6b, 0x69, 0x14, 0x57, 0xae, - 0x66, 0x88, 0x2e, 0xda, 0xb5, 0x16, 0xda, 0xd2, 0x2e, 0xc4, 0xd2, 0x4d, 0x37, 0x21, 0x89, 0xc7, - 0x64, 0x5a, 0x93, 0x13, 0x66, 0xc6, 0xa1, 0xbe, 0x45, 0x1f, 0xcb, 0xa5, 0xcb, 0xae, 0x4a, 0xd1, - 0x17, 0x29, 0x26, 0xc1, 0x58, 0x50, 0xe8, 0x6e, 0xe0, 0xfb, 0xcf, 0x7f, 0x3e, 0x98, 0x43, 0xce, - 0x78, 0x12, 0x40, 0xa2, 0xb8, 0x06, 0xc9, 0xb4, 0xc3, 0x42, 0x48, 0x40, 0x72, 0x49, 0x53, 0x81, - 0x0a, 0xad, 0xbf, 0x25, 0xa4, 0xda, 0x69, 0xd5, 0x43, 0x0c, 0x31, 0x23, 0x6c, 0xf7, 0xca, 0x43, - 0x2d, 0x3b, 0x40, 0x19, 0xa3, 0x64, 0xbe, 0x27, 0x81, 0x69, 0xc7, 0x07, 0xe5, 0x39, 0x2c, 0x40, - 0x9e, 0xe4, 0xbc, 0x3b, 0x22, 0x7f, 0xae, 0xf3, 0xd6, 0x07, 0xe5, 0x29, 0xb0, 0x06, 0xa4, 0x96, - 0x7a, 0xc2, 0x8b, 0x65, 0xd3, 0xec, 0x98, 0xbd, 0xdf, 0xfd, 0x7f, 0xf4, 0xdb, 0x16, 0x3a, 0xce, - 0xe0, 0xb0, 0xba, 0xfa, 0x68, 0x1b, 0x93, 0x22, 0xda, 0x5d, 0x57, 0x48, 0x2d, 0x07, 0xd6, 0x23, - 0x69, 0x4c, 0xb9, 0x54, 0x82, 0xfb, 0x0b, 0xc5, 0x31, 0x71, 0x53, 0x10, 0xae, 0x3f, 0xc7, 0xe0, - 0xa5, 0xe8, 0xfb, 0x4f, 0x73, 0x21, 0xba, 0x13, 0xa2, 0x85, 0x10, 0x1d, 0x21, 0x4f, 0x8a, 0xce, - 0xfa, 0xe1, 0xf8, 0x18, 0xc4, 0x70, 0x37, 0x6c, 0x5d, 0x92, 0x66, 0xe9, 0xe1, 0x06, 0x0b, 0x85, - 0xb3, 0x99, 0x1b, 0x01, 0x0f, 0x23, 0xd5, 0xac, 0x74, 0xcc, 0x5e, 0x75, 0xd2, 0x28, 0xf9, 0x28, - 0xc3, 0x37, 0x19, 0xb5, 0x22, 0xd2, 0xd1, 0xde, 0x9c, 0x4f, 0x3d, 0x85, 0xc2, 0x3d, 0xa1, 0xf6, - 0xeb, 0x67, 0x6a, 0xe7, 0xfb, 0xa2, 0xab, 0x63, 0x8e, 0x77, 0xa4, 0x5b, 0x6e, 0x3a, 0x69, 0x5b, - 0xcd, 0x6c, 0xdb, 0xfb, 0xe4, 0xed, 0x51, 0xed, 0xe1, 0xfd, 0x6a, 0x63, 0x9b, 0xeb, 0x8d, 0x6d, - 0x7e, 0x6e, 0x6c, 0xf3, 0x6d, 0x6b, 0x1b, 0xeb, 0xad, 0x6d, 0xbc, 0x6f, 0x6d, 0xe3, 0xa9, 0x1f, - 0x72, 0x15, 0x2d, 0x7c, 0x1a, 0x60, 0xcc, 0x52, 0x08, 0xc3, 0xe5, 0xb3, 0x66, 0x12, 0xe3, 0x18, - 0xe6, 0x1c, 0x04, 0xd3, 0x17, 0xec, 0x95, 0x1d, 0x5c, 0x8d, 0x5a, 0xa6, 0x20, 0xfd, 0x5a, 0xf6, - 0xd9, 0x83, 0xaf, 0x00, 0x00, 0x00, 0xff, 0xff, 0x16, 0xce, 0x6a, 0xe2, 0x50, 0x02, 0x00, 0x00, + // 435 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x4d, 0x8b, 0xd3, 0x40, + 0x1c, 0xc6, 0x1b, 0xad, 0x05, 0x47, 0xbd, 0x84, 0x75, 0x89, 0x2b, 0x4e, 0x6b, 0x05, 0xe9, 0xc5, + 0x19, 0xba, 0x7b, 0xd0, 0x73, 0xbb, 0xf8, 0x82, 0x15, 0x96, 0x16, 0x2f, 0x5e, 0x86, 0xc9, 0xf4, + 0xdf, 0xf4, 0xef, 0x36, 0x99, 0x30, 0x33, 0x0d, 0xed, 0x7e, 0x0a, 0xaf, 0x7e, 0xa3, 0x3d, 0xee, + 0x51, 0x3c, 0x2c, 0xd2, 0x7e, 0x11, 0xc9, 0x0b, 0xdb, 0x0a, 0x29, 0x78, 0x4a, 0xe0, 0xf7, 0xcc, + 0xf3, 0xfc, 0x42, 0x86, 0x3c, 0xc7, 0x44, 0x41, 0xe2, 0x30, 0x03, 0xcb, 0xb3, 0x3e, 0x8f, 0x20, + 0x01, 0x8b, 0x96, 0xa5, 0x46, 0x3b, 0xed, 0x3f, 0xd9, 0x41, 0x96, 0xf5, 0x4f, 0x8e, 0x22, 0x1d, + 0xe9, 0x82, 0xf0, 0xfc, 0xad, 0x0c, 0x9d, 0x50, 0xa5, 0x6d, 0xac, 0x2d, 0x0f, 0xa5, 0x05, 0x9e, + 0xf5, 0x43, 0x70, 0xb2, 0xcf, 0x95, 0xc6, 0xa4, 0xe4, 0xdd, 0x21, 0x79, 0xfc, 0xa1, 0x6c, 0x9d, + 0x38, 0xe9, 0xc0, 0x3f, 0x23, 0xad, 0x54, 0x1a, 0x19, 0xdb, 0xc0, 0xeb, 0x78, 0xbd, 0x47, 0xa7, + 0x4f, 0xd9, 0x3f, 0x2b, 0xec, 0xa2, 0x80, 0x83, 0xe6, 0xf5, 0x6d, 0xbb, 0x31, 0xae, 0xa2, 0xdd, + 0x9f, 0x4d, 0xd2, 0x2a, 0x81, 0xff, 0x95, 0x1c, 0x4f, 0xd1, 0x3a, 0x83, 0xe1, 0xd2, 0xa1, 0x4e, + 0x44, 0x0a, 0x46, 0x84, 0x0b, 0xad, 0x2e, 0xab, 0xbe, 0x67, 0xac, 0x14, 0x62, 0xb9, 0x10, 0xab, + 0x84, 0xd8, 0x50, 0x63, 0x52, 0x75, 0x1e, 0xed, 0x1f, 0xbf, 0x00, 0x33, 0xc8, 0x0f, 0xfb, 0xef, + 0x48, 0xb0, 0xf3, 0x10, 0x6a, 0xe9, 0xf4, 0x6c, 0x26, 0xe6, 0x80, 0xd1, 0xdc, 0x05, 0xf7, 0x3a, + 0x5e, 0xaf, 0x39, 0x3e, 0xde, 0xf1, 0x61, 0x81, 0x3f, 0x16, 0xd4, 0x9f, 0x93, 0x4e, 0x26, 0x17, + 0x38, 0x95, 0x4e, 0x1b, 0x71, 0x40, 0xed, 0xfe, 0xff, 0xa9, 0xbd, 0xb8, 0x2b, 0x3a, 0xaf, 0x73, + 0xfc, 0x4c, 0xba, 0xbb, 0xa5, 0x83, 0xb6, 0xcd, 0xc2, 0xb6, 0x7d, 0x97, 0xfc, 0x54, 0xaf, 0xbd, + 0x26, 0x2f, 0x6b, 0xcb, 0x62, 0xb9, 0x12, 0x33, 0x23, 0x55, 0xbe, 0x1c, 0x3c, 0xe8, 0x78, 0xbd, + 0x87, 0x03, 0x96, 0xcb, 0xfd, 0xbe, 0x6d, 0xbf, 0x8e, 0xd0, 0xcd, 0x97, 0x21, 0x53, 0x3a, 0xe6, + 0xd5, 0x5f, 0x2f, 0x1f, 0x6f, 0xec, 0xf4, 0x92, 0xbb, 0x75, 0x0a, 0x96, 0x9d, 0x83, 0x1a, 0xd3, + 0x9a, 0xed, 0x2f, 0x72, 0xf5, 0xbe, 0x6a, 0xf5, 0x47, 0xe4, 0x55, 0xed, 0xb4, 0x05, 0x27, 0x2c, + 0x5e, 0x81, 0x58, 0x60, 0x8c, 0x2e, 0x68, 0x1d, 0xfc, 0x90, 0x09, 0xb8, 0x09, 0x5e, 0xc1, 0x28, + 0x8f, 0x0d, 0x46, 0xd7, 0x1b, 0xea, 0xdd, 0x6c, 0xa8, 0xf7, 0x67, 0x43, 0xbd, 0x1f, 0x5b, 0xda, + 0xb8, 0xd9, 0xd2, 0xc6, 0xaf, 0x2d, 0x6d, 0x7c, 0x3b, 0xdd, 0xf3, 0x4d, 0x21, 0x8a, 0xd6, 0xdf, + 0x33, 0x6e, 0x75, 0x1c, 0xc3, 0x02, 0xc1, 0xf0, 0xec, 0x2d, 0x5f, 0xf1, 0xbd, 0xeb, 0x5f, 0xf8, + 0x87, 0xad, 0xe2, 0xd6, 0x9e, 0xfd, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x21, 0x51, 0x24, 0xd4, 0x19, + 0x03, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -228,6 +246,21 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l + if m.ValidatorIncentivesSetSizeLimit != 0 { + i = encodeVarintGenesis(dAtA, i, uint64(m.ValidatorIncentivesSetSizeLimit)) + i-- + dAtA[i] = 0x30 + } + { + size := m.ValidatorIncentivesMaxFraction.Size() + i -= size + if _, err := m.ValidatorIncentivesMaxFraction.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintGenesis(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x2a if m.ValidatorIncentivesCutoffHeight != 0 { i = encodeVarintGenesis(dAtA, i, uint64(m.ValidatorIncentivesCutoffHeight)) i-- @@ -299,6 +332,11 @@ func (m *Params) Size() (n int) { if m.ValidatorIncentivesCutoffHeight != 0 { n += 1 + sovGenesis(uint64(m.ValidatorIncentivesCutoffHeight)) } + l = m.ValidatorIncentivesMaxFraction.Size() + n += 1 + l + sovGenesis(uint64(l)) + if m.ValidatorIncentivesSetSizeLimit != 0 { + n += 1 + sovGenesis(uint64(m.ValidatorIncentivesSetSizeLimit)) + } return n } @@ -524,6 +562,59 @@ func (m *Params) Unmarshal(dAtA []byte) error { break } } + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorIncentivesMaxFraction", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + 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 ErrInvalidLengthGenesis + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthGenesis + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.ValidatorIncentivesMaxFraction.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorIncentivesSetSizeLimit", wireType) + } + m.ValidatorIncentivesSetSizeLimit = 0 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenesis + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + m.ValidatorIncentivesSetSizeLimit |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } default: iNdEx = preIndex skippy, err := skipGenesis(dAtA[iNdEx:]) diff --git a/x/incentives/types/params.go b/x/incentives/types/params.go index f434d5744..aaa63676d 100644 --- a/x/incentives/types/params.go +++ b/x/incentives/types/params.go @@ -9,8 +9,12 @@ import ( // Parameter keys var ( - KeyDistributionPerBlock = []byte("DistributionPerBlock") - KeyIncentivesCutoffHeight = []byte("IncentivesCutoffHeight") + KeyDistributionPerBlock = []byte("DistributionPerBlock") + KeyIncentivesCutoffHeight = []byte("IncentivesCutoffHeight") + KeyValidatorDistributionPerBlock = []byte("ValidatorDistributionPerBlock") + KeyValidatorIncentivesCutoffHeight = []byte("ValidatorIncentivesCutoffHeight") + KeyValidatorIncentivesMaxFraction = []byte("ValidatorIncentivesMaxFraction") + KeyValidatorIncentivesSetSizeLimit = []byte("ValidatorIncentivesSetSizeLimit") ) var _ paramtypes.ParamSet = &Params{} @@ -25,7 +29,11 @@ func DefaultParams() Params { return Params{ DistributionPerBlock: sdk.NewCoin(params.BaseCoinUnit, sdk.ZeroInt()), // Anything lower than or equal to current height is "off" - IncentivesCutoffHeight: 0, + IncentivesCutoffHeight: 0, + ValidatorDistributionPerBlock: sdk.NewCoin(params.BaseCoinUnit, sdk.ZeroInt()), + ValidatorIncentivesCutoffHeight: 0, + ValidatorIncentivesMaxFraction: sdk.MustNewDecFromStr("0.1"), + ValidatorIncentivesSetSizeLimit: 50, } } @@ -34,6 +42,10 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{ paramtypes.NewParamSetPair(KeyDistributionPerBlock, &p.DistributionPerBlock, validateDistributionPerBlock), paramtypes.NewParamSetPair(KeyIncentivesCutoffHeight, &p.IncentivesCutoffHeight, validateIncentivesCutoffHeight), + paramtypes.NewParamSetPair(KeyValidatorDistributionPerBlock, &p.ValidatorDistributionPerBlock, validateValidatorDistributionPerBlock), + paramtypes.NewParamSetPair(KeyValidatorIncentivesCutoffHeight, &p.ValidatorIncentivesCutoffHeight, validateValidatorIncentivesCutoffHeight), + paramtypes.NewParamSetPair(KeyValidatorIncentivesMaxFraction, &p.ValidatorIncentivesMaxFraction, validateValidatorIncentivesMaxFraction), + paramtypes.NewParamSetPair(KeyValidatorIncentivesSetSizeLimit, &p.ValidatorIncentivesSetSizeLimit, validateValidatorIncentivesSetSizeLimit), } } @@ -69,3 +81,41 @@ func validateDistributionPerBlock(i interface{}) error { func validateIncentivesCutoffHeight(_ interface{}) error { return nil } + +func validateValidatorDistributionPerBlock(i interface{}) error { + if err := validateDistributionPerBlock(i); err != nil { + return errorsmod.Wrapf(ErrInvalidValidatorDistributionPerBlock, "invalid parameter type: %T", i) + } + + return nil +} + +func validateValidatorIncentivesCutoffHeight(i interface{}) error { + return nil +} + +func validateValidatorIncentivesMaxFraction(i interface{}) error { + dec, ok := i.(sdk.Dec) + if !ok { + return errorsmod.Wrapf(ErrInvalidValidatorIncentivesMaxFraction, "invalid parameter type: %T", i) + } + + if dec.IsNegative() { + return errorsmod.Wrapf(ErrInvalidValidatorIncentivesMaxFraction, "validator incentives max fraction cannot be negative") + } + + if dec.GT(sdk.OneDec()) { + return errorsmod.Wrapf(ErrInvalidValidatorIncentivesMaxFraction, "validator incentives max fraction cannot be greater than one") + } + + return nil +} + +func validateValidatorIncentivesSetSizeLimit(i interface{}) error { + _, ok := i.(uint64) + if !ok { + return errorsmod.Wrapf(ErrInvalidValidatorIncentivesSetSizeLimit, "invalid parameter type: %T", i) + } + + return nil +} From 1b30447eb95d43cf3d3477dcd9316f4c1ec5d42b Mon Sep 17 00:00:00 2001 From: Collin Brittain Date: Wed, 16 Oct 2024 13:32:26 -0500 Subject: [PATCH 4/8] Implement validator rewards in BeginBlocker --- app/app.go | 6 +- x/incentives/keeper/abci.go | 58 +++++-------- x/incentives/keeper/incentives.go | 113 ++++++++++++++++--------- x/incentives/keeper/keeper.go | 3 + x/incentives/types/events.go | 7 ++ x/incentives/types/expected_keepers.go | 11 +++ 6 files changed, 113 insertions(+), 85 deletions(-) create mode 100644 x/incentives/types/events.go diff --git a/app/app.go b/app/app.go index e98fc33a4..4baeea642 100644 --- a/app/app.go +++ b/app/app.go @@ -520,11 +520,7 @@ func NewSommelierApp( ) app.IncentivesKeeper = incentiveskeeper.NewKeeper( - appCodec, keys[incentivestypes.StoreKey], app.GetSubspace(incentivestypes.ModuleName), app.DistrKeeper, app.BankKeeper, app.MintKeeper, - ) - - app.IncentivesKeeper = incentiveskeeper.NewKeeper( - appCodec, keys[incentivestypes.StoreKey], app.GetSubspace(incentivestypes.ModuleName), app.DistrKeeper, app.BankKeeper, app.MintKeeper, + appCodec, keys[incentivestypes.StoreKey], app.GetSubspace(incentivestypes.ModuleName), app.DistrKeeper, app.BankKeeper, app.MintKeeper, app.StakingKeeper, ) app.GravityKeeper = *app.GravityKeeper.SetHooks( diff --git a/x/incentives/keeper/abci.go b/x/incentives/keeper/abci.go index b34283a9a..ece025ce5 100644 --- a/x/incentives/keeper/abci.go +++ b/x/incentives/keeper/abci.go @@ -8,56 +8,38 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) +// BeginBlocker defines distribution rewards for validators +// +// 1) Subtract the total distribution from the community pool +// 2) Get a list of qualifying validators sorted by descending power +// 3) Allocate tokens to qualifying validators proportionally to their power with a cap +// 4) Add the remaining coins back to the community pool func (k Keeper) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) { incentivesParams := k.GetParamSet(ctx) - cutoffHeight := incentivesParams.ValidatorIncentivesCutoffHeight - distPerBlock := incentivesParams.ValidatorDistributionPerBlock - if uint64(ctx.BlockHeight()) >= cutoffHeight || distPerBlock.IsZero() { + if uint64(ctx.BlockHeight()) >= incentivesParams.ValidatorIncentivesCutoffHeight || incentivesParams.ValidatorDistributionPerBlock.IsZero() { return } - voterInfos := GetSortedVoterInfosByPower(req.LastCommitInfo.GetVotes()) - totalPower := int64(0) - for _, voterInfo := range voterInfos { - totalPower += voterInfo.Validator.Power - } - - // Limit the number of voter info - // TODO: Make this a function that can be unit tested - setSizeLimit := incentivesParams.ValidatorIncentivesSetSizeLimit - if uint64(len(voterInfos)) > setSizeLimit { - voterInfos = voterInfos[:setSizeLimit] - } - - distPerBlockDec := sdk.NewDec(distPerBlock.Amount.Int64()) - validatorMaxPortionFraction := sdk.MustNewDecFromStr("0.1") - apportionments, remaining, err := getApportionments(setSizeLimit, distPerBlockDec, validatorMaxPortionFraction) - if err != nil { - ctx.Logger().Error("Error getting apportionments. Are params properly validated?", "error", err) - return - } - - // Distribute rewards to each validator + // Rewards come from the community pool + totalDistribution := sdk.NewDecCoinsFromCoins(incentivesParams.ValidatorDistributionPerBlock) feePool := k.DistributionKeeper.GetFeePool(ctx) - newPool, negative := feePool.CommunityPool.SafeSub(sdk.NewDecCoinsFromCoins(distPerBlock)) + newPool, negative := feePool.CommunityPool.SafeSub(totalDistribution) if negative { - k.Logger(ctx).Error("Insufficient coins in community to distribute", "community pool", feePool.CommunityPool) + k.Logger(ctx).Error("Insufficient coins in community to distribute to validators", "community pool", feePool.CommunityPool) return } - for i, voterInfo := range voterInfos { - recipient := sdk.AccAddress(voterInfo.Validator.Address) - amount := apportionments[i].TruncateInt() - if amount.IsZero() { - continue - } + // Get a list of qualifying validators sorted by descending power + valInfos := k.getValidatorInfos(ctx, req) + sortedValInfos := sortValidatorInfosByPower(valInfos) + qualifyingVoters := sortedValInfos[:incentivesParams.ValidatorIncentivesSetSizeLimit] - err := k.BankKeeper.SendCoinsFromModuleToAccount(ctx, distributiontypes.ModuleName, recipient, sdk.NewCoins(sdk.NewCoin(distPerBlock.Denom, amount))) - if err != nil { - panic(err) - } - } + // Allocate tokens to qualifying validators proportionally to their power with a cap + totalPower := getTotalPower(&qualifyingVoters) + remaining := k.AllocateTokens(ctx, totalPower, totalDistribution, qualifyingVoters, incentivesParams.ValidatorIncentivesMaxFraction) + // Add the remaining coins back to the community pool + newPool = newPool.Add(remaining...) feePool.CommunityPool = newPool k.DistributionKeeper.SetFeePool(ctx, feePool) } diff --git a/x/incentives/keeper/incentives.go b/x/incentives/keeper/incentives.go index 347f9f025..adf4ca132 100644 --- a/x/incentives/keeper/incentives.go +++ b/x/incentives/keeper/incentives.go @@ -1,69 +1,98 @@ package keeper import ( - "fmt" "sort" + "cosmossdk.io/math" + abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/peggyjv/sommelier/v7/x/incentives/types" ) -type VoterInfo struct { - Validator *abci.Validator +type ValidatorInfo struct { + Validator stakingtypes.ValidatorI + Power int64 +} + +// sortValidatorInfosByPower sorts the validator information by power in descending order +func sortValidatorInfosByPower(valInfos []ValidatorInfo) []ValidatorInfo { + sort.Slice(valInfos, func(i, j int) bool { + return valInfos[i].Power > valInfos[j].Power + }) + + return valInfos } -// GetSortedVoterInfosByPower returns the previous block's voter information by validator power in descending order -func GetSortedVoterInfosByPower(votes []abci.VoteInfo) []VoterInfo { - voterInfos := []VoterInfo{} - for i := range votes { - if !votes[i].SignedLastBlock { +// GetTotalPower returns the total power of the passed in validatorInfos +func getTotalPower(valInfos *[]ValidatorInfo) int64 { + totalPower := int64(0) + for _, valInfo := range *valInfos { + totalPower += valInfo.Power + } + + return totalPower +} + +// getValidatorInfos returns the validator information for the voters in the last block +func (k Keeper) getValidatorInfos(ctx sdk.Context, req abci.RequestBeginBlock) []ValidatorInfo { + validatorInfos := []ValidatorInfo{} + for _, vote := range req.LastCommitInfo.GetVotes() { + if !vote.SignedLastBlock { continue } - voterInfos = append(voterInfos, VoterInfo{ - Validator: &votes[i].Validator, + validator := k.StakingKeeper.ValidatorByConsAddr(ctx, vote.Validator.Address) + validatorInfos = append(validatorInfos, ValidatorInfo{ + Validator: validator, + Power: vote.Validator.Power, }) } + return validatorInfos +} - // Sort voteInfos by descending Power - sort.Slice(voterInfos, func(i, j int) bool { - return voterInfos[i].Validator.Power > voterInfos[j].Validator.Power - }) +// AllocateTokens performs reward distribution to the provided validators proportionally to their power with a cap +func (k Keeper) AllocateTokens(ctx sdk.Context, totalPreviousPower int64, totalDistribution sdk.DecCoins, qualifyingVoters []ValidatorInfo, maxFraction sdk.Dec) sdk.DecCoins { + remaining := totalDistribution - return voterInfos -} + for _, valInfo := range qualifyingVoters { + validator := valInfo.Validator + powerFraction := math.LegacyNewDec(valInfo.Power).QuoInt64(totalPreviousPower) -// GetApportionments returns a slice of fractions of the passed in value that sums to the value approximately, and -// a remaining value. The sum of the returned slice plus the remaining value will equal the original value. -func getApportionments(numPortions uint64, value sdk.Dec, maxPortionFraction sdk.Dec) ([]sdk.Dec, sdk.Dec, error) { - // We error check for sanity, even though the arguments should only be coming from validated Param values - if numPortions == 0 { - return make([]sdk.Dec, 0), value, nil - } + // Cap at the max fraction + if powerFraction.GT(maxFraction) { + powerFraction = maxFraction + } - if value.IsNegative() { - value = sdk.ZeroDec() - } + reward := totalDistribution.MulDecTruncate(powerFraction) - if maxPortionFraction.IsNegative() { - return nil, sdk.ZeroDec(), fmt.Errorf("max portion cannot be negative") - } else if maxPortionFraction.GT(sdk.OneDec()) { - return nil, sdk.ZeroDec(), fmt.Errorf("max portion must be less than or equal to one") + k.AllocateTokensToValidator(ctx, validator, reward) + remaining = remaining.Sub(reward) } - remainingValue := value - apportionments := make([]sdk.Dec, numPortions) + return remaining +} - for i := 0; i < len(apportionments); i++ { - if remainingValue.IsZero() || maxPortionFraction.IsZero() { - apportionments[i] = sdk.ZeroDec() - continue - } +// AllocateTokensToValidator allocates tokens to a particular validator. +// All tokens go to the validator. +func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val stakingtypes.ValidatorI, tokens sdk.DecCoins) { + // Update validator rewards + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeIncentivesRewards, + sdk.NewAttribute(sdk.AttributeKeyAmount, tokens.String()), + sdk.NewAttribute(types.AttributeKeyValidator, val.GetOperator().String()), + ), + ) - portion := remainingValue.Mul(maxPortionFraction) - apportionments[i] = portion - remainingValue = remainingValue.Sub(portion) - } + // Update current rewards + currentRewards := k.DistributionKeeper.GetValidatorCurrentRewards(ctx, val.GetOperator()) + currentRewards.Rewards = currentRewards.Rewards.Add(tokens...) + k.DistributionKeeper.SetValidatorCurrentRewards(ctx, val.GetOperator(), currentRewards) - return apportionments, remainingValue, nil + // Update outstanding rewards + outstanding := k.DistributionKeeper.GetValidatorOutstandingRewards(ctx, val.GetOperator()) + outstanding.Rewards = outstanding.Rewards.Add(tokens...) + k.DistributionKeeper.SetValidatorOutstandingRewards(ctx, val.GetOperator(), outstanding) } diff --git a/x/incentives/keeper/keeper.go b/x/incentives/keeper/keeper.go index dd812b80d..d3d5690f2 100644 --- a/x/incentives/keeper/keeper.go +++ b/x/incentives/keeper/keeper.go @@ -17,6 +17,7 @@ type Keeper struct { DistributionKeeper types.DistributionKeeper BankKeeper types.BankKeeper MintKeeper types.MintKeeper + StakingKeeper types.StakingKeeper } func NewKeeper( @@ -26,6 +27,7 @@ func NewKeeper( distributionKeeper types.DistributionKeeper, bankKeeper types.BankKeeper, mintKeeper types.MintKeeper, + stakingKeeper types.StakingKeeper, ) Keeper { if !paramSpace.HasKeyTable() { paramSpace = paramSpace.WithKeyTable(types.ParamKeyTable()) @@ -38,6 +40,7 @@ func NewKeeper( DistributionKeeper: distributionKeeper, BankKeeper: bankKeeper, MintKeeper: mintKeeper, + StakingKeeper: stakingKeeper, } } diff --git a/x/incentives/types/events.go b/x/incentives/types/events.go new file mode 100644 index 000000000..94ea9909b --- /dev/null +++ b/x/incentives/types/events.go @@ -0,0 +1,7 @@ +package types + +const ( + EventTypeIncentivesRewards = "incentives_rewards" + + AttributeKeyValidator = "validator" +) diff --git a/x/incentives/types/expected_keepers.go b/x/incentives/types/expected_keepers.go index b270094f7..3d89312e3 100644 --- a/x/incentives/types/expected_keepers.go +++ b/x/incentives/types/expected_keepers.go @@ -7,12 +7,19 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) // DistributionKeeper defines the expected distribution keeper methods type DistributionKeeper interface { GetFeePool(ctx sdk.Context) (feePool distributiontypes.FeePool) SetFeePool(ctx sdk.Context, feePool distributiontypes.FeePool) + GetValidatorOutstandingRewards(ctx sdk.Context, valAddr sdk.ValAddress) (rewards distributiontypes.ValidatorOutstandingRewards) + SetValidatorOutstandingRewards(ctx sdk.Context, valAddr sdk.ValAddress, rewards distributiontypes.ValidatorOutstandingRewards) + GetValidatorCurrentRewards(ctx sdk.Context, valAddr sdk.ValAddress) (rewards distributiontypes.ValidatorCurrentRewards) + SetValidatorCurrentRewards(ctx sdk.Context, valAddr sdk.ValAddress, rewards distributiontypes.ValidatorCurrentRewards) + GetValidatorAccumulatedCommission(ctx sdk.Context, valAddr sdk.ValAddress) (commission distributiontypes.ValidatorAccumulatedCommission) + SetValidatorAccumulatedCommission(ctx sdk.Context, valAddr sdk.ValAddress, commission distributiontypes.ValidatorAccumulatedCommission) } // BankKeeper defines the expected interface needed to retrieve account balances. @@ -35,3 +42,7 @@ type MintKeeper interface { StakingTokenSupply(ctx sdk.Context) math.Int BondedRatio(ctx sdk.Context) sdk.Dec } + +type StakingKeeper interface { + ValidatorByConsAddr(ctx sdk.Context, consAddr sdk.ConsAddress) stakingtypes.ValidatorI +} From 03e7fe6a24600218188f7f5569193bb5c7d0939a Mon Sep 17 00:00:00 2001 From: Collin Brittain Date: Thu, 17 Oct 2024 13:50:47 -0500 Subject: [PATCH 5/8] Fix validator incentives bug revealed by tests --- x/incentives/keeper/abci.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/x/incentives/keeper/abci.go b/x/incentives/keeper/abci.go index ece025ce5..deffc20e4 100644 --- a/x/incentives/keeper/abci.go +++ b/x/incentives/keeper/abci.go @@ -32,7 +32,7 @@ func (k Keeper) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) { // Get a list of qualifying validators sorted by descending power valInfos := k.getValidatorInfos(ctx, req) sortedValInfos := sortValidatorInfosByPower(valInfos) - qualifyingVoters := sortedValInfos[:incentivesParams.ValidatorIncentivesSetSizeLimit] + qualifyingVoters := truncateVoters(sortedValInfos, incentivesParams.ValidatorIncentivesSetSizeLimit) // Allocate tokens to qualifying validators proportionally to their power with a cap totalPower := getTotalPower(&qualifyingVoters) From 55939055ef49abbd95554a595883f57a8def7eb6 Mon Sep 17 00:00:00 2001 From: Collin Brittain Date: Thu, 17 Oct 2024 13:50:54 -0500 Subject: [PATCH 6/8] Unit and integration tests --- .github/workflows/integration-tests.yml | 1 + Makefile | 3 + integration_tests/genesis.go | 16 + integration_tests/incentives_test.go | 246 ++++++++ integration_tests/setup_test.go | 7 +- x/cellarfees/migrations/v1/types/errors.go | 5 - x/cellarfees/migrations/v1/types/genesis.go | 3 +- x/cellarfees/migrations/v1/types/params.go | 20 +- x/cellarfees/types/errors.go | 4 +- x/incentives/keeper/abci_test.go | 108 ++++ x/incentives/keeper/incentives.go | 9 + x/incentives/keeper/incentives_test.go | 564 +++++++++++------- x/incentives/keeper/keeper_test.go | 11 +- .../testutil/expected_keepers_mocks.go | 121 +++- 14 files changed, 871 insertions(+), 247 deletions(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 13523f806..ef35eed21 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -125,6 +125,7 @@ jobs: "Auction", "CellarFees", "Incentives", + "ValidatorIncentives", "Pubsub", "Addresses", ] diff --git a/Makefile b/Makefile index 314f45e1e..da6b73e79 100644 --- a/Makefile +++ b/Makefile @@ -388,6 +388,9 @@ e2e_cellarfees_test: e2e_clean_slate e2e_incentives_test: e2e_clean_slate @E2E_SKIP_CLEANUP=true integration_tests/integration_tests.test -test.failfast -test.v -test.run IntegrationTestSuite -testify.m TestIncentives || make -s fail +e2e_validator_incentives_test: e2e_clean_slate + @E2E_SKIP_CLEANUP=true integration_tests/integration_tests.test -test.failfast -test.v -test.run IntegrationTestSuite -testify.m TestValidatorIncentives || make -s fail + e2e_pubsub_test: e2e_clean_slate @E2E_SKIP_CLEANUP=true integration_tests/integration_tests.test -test.failfast -test.v -test.run IntegrationTestSuite -testify.m TestPubsub || make -s fail diff --git a/integration_tests/genesis.go b/integration_tests/genesis.go index 421ecbcf8..698259b46 100644 --- a/integration_tests/genesis.go +++ b/integration_tests/genesis.go @@ -12,6 +12,8 @@ import ( banktypes "github.com/cosmos/cosmos-sdk/x/bank/types" "github.com/cosmos/cosmos-sdk/x/genutil" genutiltypes "github.com/cosmos/cosmos-sdk/x/genutil/types" + "github.com/peggyjv/sommelier/v7/app/params" + incentivestypes "github.com/peggyjv/sommelier/v7/x/incentives/types" ) func getGenDoc(path string) (*tmtypes.GenesisDoc, error) { @@ -108,3 +110,17 @@ func addGenesisAccount(path, moniker, amountStr string, accAddr sdk.AccAddress) genDoc.AppState = appStateJSON return genutil.ExportGenesisFile(genDoc, genFile) } + +func (s *IntegrationTestSuite) setIncentivesGenState(appGenState map[string]json.RawMessage) error { + incentivesGenState := incentivestypes.DefaultGenesisState() + err := cdc.UnmarshalJSON(appGenState[incentivestypes.ModuleName], &incentivesGenState) + if err != nil { + return fmt.Errorf("failed to unmarshal incentives genesis state: %w", err) + } + + incentivesGenState.Params.ValidatorIncentivesCutoffHeight = 0 + incentivesGenState.Params.ValidatorDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(0)) + + appGenState[incentivestypes.ModuleName] = cdc.MustMarshalJSON(&incentivesGenState) + return nil +} diff --git a/integration_tests/incentives_test.go b/integration_tests/incentives_test.go index e15c84725..83b08a716 100644 --- a/integration_tests/incentives_test.go +++ b/integration_tests/incentives_test.go @@ -241,3 +241,249 @@ func (s *IntegrationTestSuite) getRewardAmountAndHeight(ctx context.Context, dis return amount, height } + +func (s *IntegrationTestSuite) TestValidatorIncentives() { + validator := s.chain.validators[0] + proposer := s.chain.proposer + proposerCtx, err := s.chain.clientContext("tcp://localhost:26657", proposer.keyring, "proposer", proposer.address()) + s.Require().NoError(err) + orch := s.chain.orchestrators[0] + orchClientCtx, err := s.chain.clientContext("tcp://localhost:26657", orch.keyring, "orch", orch.address()) + s.Require().NoError(err) + ctx := context.Background() + + s.T().Log("Getting the initial community pool balance") + queryClient := disttypes.NewQueryClient(proposerCtx) + queryRes, err := queryClient.CommunityPool(ctx, &disttypes.QueryCommunityPoolRequest{}) + s.Require().NoError(err) + s.T().Logf("Initial community pool balance: %s", queryRes.Pool.String()) + initialCommunityPool := queryRes.Pool + + // Wait for outstanding rewards to equal 4000000usomm. Current theory is these initial rewards come from + // the genesis delegation tx fees. + s.T().Log("Waiting for outstanding rewards to equal 4000000usomm") + initialRewards := disttypes.ValidatorOutstandingRewards{ + Rewards: sdk.DecCoins{ + sdk.DecCoin{ + Denom: params.BaseCoinUnit, + Amount: sdk.NewDec(4000000), + }, + }, + } + s.Require().Eventually(func() bool { + rewards, err := s.getValidatorOutstandingRewards(validator) + s.Require().NoError(err) + return rewards.Rewards.AmountOf(params.BaseCoinUnit).Equal(initialRewards.Rewards.AmountOf(params.BaseCoinUnit)) + }, time.Second*30, time.Second*1, "outstanding rewards did not reach 4000000usomm") + + // Submit proposal to enable validator incentives + s.T().Log("Submitting proposal to enable validator incentives") + cutoffHeight := 100 + proposal := paramsproposal.ParameterChangeProposal{ + Title: "Enable validator incentives", + Description: "Enable validator incentives", + Changes: []paramsproposal.ParamChange{ + { + Subspace: "incentives", + Key: "ValidatorIncentivesCutoffHeight", + Value: fmt.Sprintf("\"%d\"", cutoffHeight), + }, + { + Subspace: "incentives", + Key: "ValidatorDistributionPerBlock", + Value: fmt.Sprintf("{\"denom\":\"%s\",\"amount\":\"%d\"}", params.BaseCoinUnit, 1000000), + }, + }, + } + + proposalMsg, err := govtypesv1beta1.NewMsgSubmitProposal( + &proposal, + sdk.Coins{ + { + Denom: testDenom, + Amount: stakeAmount.Quo(sdk.NewInt(2)), + }, + }, + proposer.address(), + ) + s.Require().NoError(err) + submitProposalResponse, err := s.chain.sendMsgs(*proposerCtx, proposalMsg) + s.Require().NoError(err) + s.Require().Zero(submitProposalResponse.Code, "raw log: %s", submitProposalResponse.RawLog) + + s.T().Log("Checking proposal was submitted correctly") + govQueryClient := govtypesv1beta1.NewQueryClient(orchClientCtx) + s.Require().Eventually(func() bool { + proposalsQueryResponse, err := govQueryClient.Proposals(context.Background(), &govtypesv1beta1.QueryProposalsRequest{}) + if err != nil { + s.T().Logf("error querying proposals: %e", err) + return false + } + + s.Require().NotEmpty(proposalsQueryResponse.Proposals) + s.Require().Equal(uint64(1), proposalsQueryResponse.Proposals[0].ProposalId, "not proposal id 1") + s.Require().Equal(govtypesv1beta1.StatusVotingPeriod, proposalsQueryResponse.Proposals[0].Status, "proposal not in voting period") + + return true + }, time.Second*30, time.Second*5, "proposal submission was never found") + + s.T().Log("Vote for proposal") + for _, val := range s.chain.validators { + kr, err := val.keyring() + s.Require().NoError(err) + localClientCtx, err := s.chain.clientContext("tcp://localhost:26657", &kr, "val", val.address()) + s.Require().NoError(err) + + voteMsg := govtypesv1beta1.NewMsgVote(val.address(), 1, govtypesv1beta1.OptionYes) + voteResponse, err := s.chain.sendMsgs(*localClientCtx, voteMsg) + s.Require().NoError(err) + s.Require().Zero(voteResponse.Code, "Vote error: %s", voteResponse.RawLog) + } + + // Wait for proposal to be approved + s.T().Log("Waiting for proposal to be approved") + s.Require().Eventually(func() bool { + proposalQueryResponse, _ := govQueryClient.Proposal(context.Background(), &govtypesv1beta1.QueryProposalRequest{ProposalId: 1}) + return govtypesv1beta1.StatusPassed == proposalQueryResponse.Proposal.Status + }, time.Second*30, time.Second*5, "proposal was never accepted") + s.T().Log("proposal approved!") + + // Wait for a few blocks to pass to allow the validator rewards to increase + s.T().Log("Waiting for a few blocks to pass") + s.waitForBlocks(10) + + // Get the updated outstanding rewards for the validator + s.T().Log("Getting updated validator rewards") + currentRewards2, err := s.getValidatorOutstandingRewards(validator) + s.Require().NoError(err) + + // Check if the validator's outstanding rewards have increased + s.T().Logf("Initial rewards: %s, updated rewards: %s", initialRewards.Rewards, currentRewards2.Rewards) + s.Require().True(currentRewards2.Rewards.AmountOf(params.BaseCoinUnit).GT(initialRewards.Rewards.AmountOf(params.BaseCoinUnit)), + "Expected validator rewards to increase, got initial: %s, updated: %s", initialRewards.Rewards, currentRewards2.Rewards) + + s.T().Logf("Validator rewards increased from %s to %s", initialRewards.Rewards, currentRewards2.Rewards) + + s.T().Logf("Waiting to see validator rewards cut off at height %d", cutoffHeight) + s.waitUntilHeight(int64(cutoffHeight)) + + s.T().Log("Getting current validator rewards") + currentRewards, err := s.getValidatorOutstandingRewards(validator) + s.Require().NoError(err) + + s.T().Logf("Current rewards: %s", currentRewards.Rewards) + + s.T().Log("Waiting for a few blocks to pass") + s.waitForBlocks(10) + + s.T().Log("Getting updated validator rewards") + currentRewards2, err = s.getValidatorOutstandingRewards(validator) + s.Require().NoError(err) + + s.T().Logf("Current rewards: %s", currentRewards2.Rewards) + s.Require().Equal(currentRewards.Rewards, currentRewards2.Rewards, "Expected validator rewards to remain constant after cutoff height") + s.T().Log("Validator rewards ended!") + + s.T().Log("Getting sum of all validator rewards") + totalRewards := sdk.DecCoins{} + for _, val := range s.chain.validators { + rewards, err := s.getValidatorOutstandingRewards(val) + s.Require().NoError(err) + totalRewards = totalRewards.Add(rewards.Rewards...) + } + s.T().Logf("Total rewards: %s", totalRewards) + + s.T().Log("Getting community pool balance") + queryRes, err = queryClient.CommunityPool(ctx, &disttypes.QueryCommunityPoolRequest{}) + s.Require().NoError(err) + s.T().Logf("Community pool balance: %s", queryRes.Pool.String()) + + // Subtract the initial rewards and the tx fees from the total rewards to get the incentive rewards + s.T().Log("Total incentive rewards is current rewards minus initial rewards minus tx fees from the proposal submission and votes") + totalIncentiveRewards := totalRewards.Sub(initialRewards.Rewards.MulDec(sdk.NewDec(4))).Sub(sdk.DecCoins{ + { + Denom: testDenom, + Amount: sdk.NewDec(246913560), + }, + }.MulDec(sdk.NewDec(5))) + s.T().Logf("Total incentive rewards: %s", totalIncentiveRewards) + + s.T().Log("Checking that the total incentive rewards are equal to the community pool balance") + s.T().Logf("Initial community pool: %s, updated community pool: %s", initialCommunityPool, queryRes.Pool) + s.Require().Equal(totalIncentiveRewards, initialCommunityPool.Sub(queryRes.Pool), "Expected sum of all validator rewards to be equal to the change in community pool balance") +} + +func (s *IntegrationTestSuite) getValidatorOutstandingRewards(val *validator) (disttypes.ValidatorOutstandingRewards, error) { + ctx := context.Background() + kb, err := val.keyring() + s.Require().NoError(err) + clientCtx, err := s.chain.clientContext("tcp://localhost:26657", &kb, "val", val.address()) + s.Require().NoError(err) + queryClient := disttypes.NewQueryClient(clientCtx) + resp, err := queryClient.ValidatorOutstandingRewards( + ctx, + &disttypes.QueryValidatorOutstandingRewardsRequest{ + ValidatorAddress: val.validatorAddress().String(), + }, + ) + if err != nil { + return disttypes.ValidatorOutstandingRewards{}, err + } + return resp.Rewards, nil +} + +func (s *IntegrationTestSuite) waitForBlocks(numBlocks int64) error { + validator := s.chain.validators[0] + kb, err := validator.keyring() + s.Require().NoError(err) + clientCtx, err := s.chain.clientContext("tcp://localhost:26657", &kb, "val", validator.address()) + s.Require().NoError(err) + + initialHeight, err := s.getCurrentHeight(clientCtx) + s.Require().NoError(err) + targetHeight := initialHeight + numBlocks + + for { + height, err := s.getCurrentHeight(clientCtx) + if err != nil { + return err + } + + if height >= targetHeight { + break + } + + time.Sleep(time.Second) + } + + return nil +} + +func (s *IntegrationTestSuite) waitUntilHeight(height int64) error { + validator := s.chain.validators[0] + kb, err := validator.keyring() + s.Require().NoError(err) + clientCtx, err := s.chain.clientContext("tcp://localhost:26657", &kb, "val", validator.address()) + s.Require().NoError(err) + + errorsTotal := 0 + for { + if errorsTotal > 5 { + return fmt.Errorf("failed to get to height %d: too many errors", height) + } + + currentHeight, err := s.getCurrentHeight(clientCtx) + if err != nil { + errorsTotal++ + continue + } + + if currentHeight >= height { + break + } + + time.Sleep(time.Second * 3) + } + + return nil +} diff --git a/integration_tests/setup_test.go b/integration_tests/setup_test.go index a0321c955..c2b43949c 100644 --- a/integration_tests/setup_test.go +++ b/integration_tests/setup_test.go @@ -534,11 +534,8 @@ func (s *IntegrationTestSuite) initGenesis() { s.Require().NoError(err) appGenState[gravitytypes.ModuleName] = bz - // incentivesGenState := incentivestypes.DefaultGenesisState() - // s.Require().NoError(cdc.UnmarshalJSON(appGenState[gravitytypes.ModuleName], &gravityGenState)) - // bz, err = cdc.MarshalJSON(&incentivesGenState) - // s.Require().NoError(err) - // appGenState[incentivestypes.ModuleName] = bz + // set incentives gen state + s.Require().NoError(s.setIncentivesGenState(appGenState)) // serialize genesis state bz, err = json.MarshalIndent(appGenState, "", " ") diff --git a/x/cellarfees/migrations/v1/types/errors.go b/x/cellarfees/migrations/v1/types/errors.go index 8e55258b6..a2fa9199e 100644 --- a/x/cellarfees/migrations/v1/types/errors.go +++ b/x/cellarfees/migrations/v1/types/errors.go @@ -7,10 +7,5 @@ import ( // x/cellarfees module sentinel errors var ( ErrInvalidFeeAccrualAuctionThreshold = errorsmod.Register(ModuleName, 2, "invalid fee accrual auction threshold") - ErrInvalidRewardEmissionPeriod = errorsmod.Register(ModuleName, 3, "invalid reward emission period") - ErrInvalidInitialPriceDecreaseRate = errorsmod.Register(ModuleName, 4, "invalid initial price decrease rate") - ErrInvalidPriceDecreaseBlockInterval = errorsmod.Register(ModuleName, 5, "invalid price decrease block interval") ErrInvalidFeeAccrualCounters = errorsmod.Register(ModuleName, 6, "invalid fee accrual counters") - ErrInvalidLastRewardSupplyPeak = errorsmod.Register(ModuleName, 7, "invalid last reward supply peak") - ErrInvalidAuctionInterval = errorsmod.Register(ModuleName, 8, "invalid interval blocks between auctions") ) diff --git a/x/cellarfees/migrations/v1/types/genesis.go b/x/cellarfees/migrations/v1/types/genesis.go index 774a3274f..2fae372c2 100644 --- a/x/cellarfees/migrations/v1/types/genesis.go +++ b/x/cellarfees/migrations/v1/types/genesis.go @@ -4,6 +4,7 @@ import ( "sort" sdk "github.com/cosmos/cosmos-sdk/types" + types "github.com/peggyjv/sommelier/v7/x/cellarfees/types" ) const DefaultParamspace = ModuleName @@ -37,7 +38,7 @@ func (gs GenesisState) Validate() error { } if gs.LastRewardSupplyPeak.LT(sdk.ZeroInt()) { - return ErrInvalidLastRewardSupplyPeak.Wrap("last reward supply peak cannot be less than zero!") + return types.ErrInvalidLastRewardSupplyPeak.Wrap("last reward supply peak cannot be less than zero!") } return nil diff --git a/x/cellarfees/migrations/v1/types/params.go b/x/cellarfees/migrations/v1/types/params.go index 0d81222a0..a1a7b6747 100644 --- a/x/cellarfees/migrations/v1/types/params.go +++ b/x/cellarfees/migrations/v1/types/params.go @@ -5,6 +5,8 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" "gopkg.in/yaml.v2" + + types "github.com/peggyjv/sommelier/v7/x/cellarfees/types" ) const ( @@ -92,11 +94,11 @@ func validateFeeAccrualAuctionThreshold(i interface{}) error { func validateRewardEmissionPeriod(i interface{}) error { emissionPeriod, ok := i.(uint64) if !ok { - return errorsmod.Wrapf(ErrInvalidRewardEmissionPeriod, "reward emission period: %T", i) + return errorsmod.Wrapf(types.ErrInvalidRewardEmissionPeriod, "reward emission period: %T", i) } if emissionPeriod == 0 { - return errorsmod.Wrapf(ErrInvalidRewardEmissionPeriod, "reward emission period cannot be zero") + return errorsmod.Wrapf(types.ErrInvalidRewardEmissionPeriod, "reward emission period cannot be zero") } return nil @@ -105,15 +107,15 @@ func validateRewardEmissionPeriod(i interface{}) error { func validateInitialPriceDecreaseRate(i interface{}) error { rate, ok := i.(sdk.Dec) if !ok { - return errorsmod.Wrapf(ErrInvalidInitialPriceDecreaseRate, "initial price decrease rate: %T", i) + return errorsmod.Wrapf(types.ErrInvalidInitialPriceDecreaseRate, "initial price decrease rate: %T", i) } if rate == sdk.ZeroDec() { - return errorsmod.Wrapf(ErrInvalidInitialPriceDecreaseRate, "initial price decrease rate cannot be zero, must be 0 < x < 1") + return errorsmod.Wrapf(types.ErrInvalidInitialPriceDecreaseRate, "initial price decrease rate cannot be zero, must be 0 < x < 1") } if rate == sdk.OneDec() { - return errorsmod.Wrapf(ErrInvalidInitialPriceDecreaseRate, "initial price decrease rate cannot be one, must be 0 < x < 1") + return errorsmod.Wrapf(types.ErrInvalidInitialPriceDecreaseRate, "initial price decrease rate cannot be one, must be 0 < x < 1") } return nil @@ -122,11 +124,11 @@ func validateInitialPriceDecreaseRate(i interface{}) error { func validatePriceDecreaseBlockInterval(i interface{}) error { interval, ok := i.(uint64) if !ok { - return errorsmod.Wrapf(ErrInvalidPriceDecreaseBlockInterval, "price decrease block interval: %T", i) + return errorsmod.Wrapf(types.ErrInvalidPriceDecreaseBlockInterval, "price decrease block interval: %T", i) } if interval == 0 { - return errorsmod.Wrapf(ErrInvalidPriceDecreaseBlockInterval, "price decrease block interval cannot be zero") + return errorsmod.Wrapf(types.ErrInvalidPriceDecreaseBlockInterval, "price decrease block interval cannot be zero") } return nil @@ -135,11 +137,11 @@ func validatePriceDecreaseBlockInterval(i interface{}) error { func validateAuctionInterval(i interface{}) error { interval, ok := i.(uint64) if !ok { - return errorsmod.Wrapf(ErrInvalidAuctionInterval, "auction interval: %T", i) + return errorsmod.Wrapf(types.ErrInvalidAuctionInterval, "auction interval: %T", i) } if interval == 0 { - return errorsmod.Wrapf(ErrInvalidAuctionInterval, "auction interval cannot be zero") + return errorsmod.Wrapf(types.ErrInvalidAuctionInterval, "auction interval cannot be zero") } return nil diff --git a/x/cellarfees/types/errors.go b/x/cellarfees/types/errors.go index 8e587e9b1..2a1910f0d 100644 --- a/x/cellarfees/types/errors.go +++ b/x/cellarfees/types/errors.go @@ -6,11 +6,11 @@ import ( // x/cellarfees module sentinel errors var ( - ErrInvalidFeeAccrualAuctionThreshold = errorsmod.Register(ModuleName, 2, "invalid fee accrual auction threshold") + // Codes 2 and 6 were deleted during v2 module upgrade + ErrInvalidRewardEmissionPeriod = errorsmod.Register(ModuleName, 3, "invalid reward emission period") ErrInvalidInitialPriceDecreaseRate = errorsmod.Register(ModuleName, 4, "invalid initial price decrease rate") ErrInvalidPriceDecreaseBlockInterval = errorsmod.Register(ModuleName, 5, "invalid price decrease block interval") - ErrInvalidFeeAccrualCounters = errorsmod.Register(ModuleName, 6, "invalid fee accrual counters") ErrInvalidLastRewardSupplyPeak = errorsmod.Register(ModuleName, 7, "invalid last reward supply peak") ErrInvalidAuctionInterval = errorsmod.Register(ModuleName, 8, "invalid interval blocks between auctions") ErrInvalidAuctionThresholdUsdValue = errorsmod.Register(ModuleName, 9, "invalid auction threshold USD value") diff --git a/x/incentives/keeper/abci_test.go b/x/incentives/keeper/abci_test.go index c3667cd24..e137b3f08 100644 --- a/x/incentives/keeper/abci_test.go +++ b/x/incentives/keeper/abci_test.go @@ -4,6 +4,7 @@ import ( abci "github.com/cometbft/cometbft/abci/types" sdk "github.com/cosmos/cosmos-sdk/types" distributionTypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + "github.com/golang/mock/gomock" "github.com/peggyjv/sommelier/v7/app/params" incentivesTypes "github.com/peggyjv/sommelier/v7/x/incentives/types" ) @@ -57,3 +58,110 @@ func (suite *KeeperTestSuite) TestEndBlockerInsufficientCommunityPoolBalance() { require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, abci.RequestBeginBlock{}) }) require.NotPanics(func() { incentivesKeeper.EndBlocker(ctx) }) } + +func (suite *KeeperTestSuite) TestBeginBlockerIncentivesDisabled() { + ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper + require := suite.Require() + + incentivesParams := incentivesTypes.DefaultParams() + incentivesParams.ValidatorIncentivesCutoffHeight = 100 + incentivesParams.ValidatorDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(1000)) + incentivesKeeper.SetParams(ctx, incentivesParams) + + // Set block height above cutoff + ctx = ctx.WithBlockHeight(101) + + // By not mocking any other calls, the test will panic and fail if an unmocked keeper function is called, + // implying that the function isn't exiting early as designed. + require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, abci.RequestBeginBlock{}) }) + + incentivesParams.ValidatorIncentivesCutoffHeight = 200 + incentivesParams.ValidatorDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.ZeroInt()) + incentivesKeeper.SetParams(ctx, incentivesParams) + + require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, abci.RequestBeginBlock{}) }) +} + +func (suite *KeeperTestSuite) TestBeginBlockerInsufficientCommunityPoolBalance() { + ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper + require := suite.Require() + + incentivesParams := incentivesTypes.DefaultParams() + incentivesParams.ValidatorIncentivesCutoffHeight = 100 + incentivesParams.ValidatorDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(1000)) + incentivesKeeper.SetParams(ctx, incentivesParams) + + // Set block height below cutoff + ctx = ctx.WithBlockHeight(99) + + // Mock insufficient community pool balance + pool := distributionTypes.FeePool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(params.BaseCoinUnit, sdk.NewInt(999))), + } + suite.distributionKeeper.EXPECT().GetFeePool(ctx).Return(pool) + + // By not mocking any further calls, the test will panic and fail if the community pool balance + // check branch isn't taken as intended. + require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, abci.RequestBeginBlock{}) }) +} + +func (suite *KeeperTestSuite) TestBeginBlockerSuccess() { + ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper + require := suite.Require() + + incentivesParams := incentivesTypes.DefaultParams() + incentivesParams.ValidatorIncentivesCutoffHeight = 100 + incentivesParams.ValidatorDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(1000)) + incentivesKeeper.SetParams(ctx, incentivesParams) + + // Set block height below cutoff + ctx = ctx.WithBlockHeight(99) + + // Mock sufficient community pool balance + pool := distributionTypes.FeePool{ + CommunityPool: sdk.NewDecCoins(sdk.NewDecCoin(params.BaseCoinUnit, sdk.NewInt(2000))), + } + suite.distributionKeeper.EXPECT().GetFeePool(ctx).Return(pool) + + // Mock validators + validators, err := getMockValidators(suite) + require.NoError(err) + validator1, validator2 := validators[0], validators[1] + + consAddr1, err := validator1.GetConsAddr() + require.NoError(err) + consAddr2, err := validator2.GetConsAddr() + require.NoError(err) + + // Mock RequestBeginBlock + req := abci.RequestBeginBlock{ + LastCommitInfo: abci.CommitInfo{ + Votes: []abci.VoteInfo{ + {Validator: abci.Validator{Address: consAddr1, Power: 10}, SignedLastBlock: true}, + {Validator: abci.Validator{Address: consAddr2, Power: 20}, SignedLastBlock: true}, + }, + }, + } + + // Mock StakingKeeper expectations + suite.stakingKeeper.EXPECT().ValidatorByConsAddr(ctx, consAddr1).Return(validator1) + suite.stakingKeeper.EXPECT().ValidatorByConsAddr(ctx, consAddr2).Return(validator2) + + // Mock DistributionKeeper expectations for AllocateTokens + suite.distributionKeeper.EXPECT().GetValidatorCurrentRewards(ctx, validator1.GetOperator()).Return(distributionTypes.ValidatorCurrentRewards{Rewards: sdk.DecCoins{}}) + suite.distributionKeeper.EXPECT().SetValidatorCurrentRewards(ctx, validator1.GetOperator(), gomock.Any()) + suite.distributionKeeper.EXPECT().GetValidatorOutstandingRewards(ctx, validator1.GetOperator()).Return(distributionTypes.ValidatorOutstandingRewards{Rewards: sdk.DecCoins{}}) + suite.distributionKeeper.EXPECT().SetValidatorOutstandingRewards(ctx, validator1.GetOperator(), gomock.Any()) + + suite.distributionKeeper.EXPECT().GetValidatorCurrentRewards(ctx, validator2.GetOperator()).Return(distributionTypes.ValidatorCurrentRewards{Rewards: sdk.DecCoins{}}) + suite.distributionKeeper.EXPECT().SetValidatorCurrentRewards(ctx, validator2.GetOperator(), gomock.Any()) + suite.distributionKeeper.EXPECT().GetValidatorOutstandingRewards(ctx, validator2.GetOperator()).Return(distributionTypes.ValidatorOutstandingRewards{Rewards: sdk.DecCoins{}}) + suite.distributionKeeper.EXPECT().SetValidatorOutstandingRewards(ctx, validator2.GetOperator(), gomock.Any()) + + // Mock setting the updated fee pool + suite.distributionKeeper.EXPECT().SetFeePool(ctx, gomock.Any()) + + require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, req) }) + + // You can add more specific assertions here if needed, such as checking emitted events +} diff --git a/x/incentives/keeper/incentives.go b/x/incentives/keeper/incentives.go index adf4ca132..30d75ee3e 100644 --- a/x/incentives/keeper/incentives.go +++ b/x/incentives/keeper/incentives.go @@ -52,6 +52,15 @@ func (k Keeper) getValidatorInfos(ctx sdk.Context, req abci.RequestBeginBlock) [ return validatorInfos } +// truncateVoters returns the first maxSize validatorInfos +func truncateVoters(validatorInfos []ValidatorInfo, maxSize uint64) []ValidatorInfo { + if len(validatorInfos) > int(maxSize) { + return validatorInfos[:maxSize] + } + + return validatorInfos +} + // AllocateTokens performs reward distribution to the provided validators proportionally to their power with a cap func (k Keeper) AllocateTokens(ctx sdk.Context, totalPreviousPower int64, totalDistribution sdk.DecCoins, qualifyingVoters []ValidatorInfo, maxFraction sdk.Dec) sdk.DecCoins { remaining := totalDistribution diff --git a/x/incentives/keeper/incentives_test.go b/x/incentives/keeper/incentives_test.go index 91ba7877b..301c1c702 100644 --- a/x/incentives/keeper/incentives_test.go +++ b/x/incentives/keeper/incentives_test.go @@ -1,235 +1,361 @@ package keeper import ( - "testing" - + abci "github.com/cometbft/cometbft/abci/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" + ccrypto "github.com/cosmos/cosmos-sdk/crypto/types" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/stretchr/testify/require" + distributiontypes "github.com/cosmos/cosmos-sdk/x/distribution/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/peggyjv/sommelier/v7/x/incentives/types" ) -func TestGetApportionments(t *testing.T) { - tests := []struct { - name string - numPortions uint64 - value sdk.Dec - maxPortion sdk.Dec - want []sdk.Dec - wantErr bool - }{ - { - name: "Negative value", - numPortions: 10, - value: sdk.NewDec(-100), - maxPortion: sdk.MustNewDecFromStr("0.1"), - wantErr: true, - }, - { - name: "Zero length slice", - numPortions: 0, - value: sdk.NewDec(100), - maxPortion: sdk.MustNewDecFromStr("0.1"), - want: make([]sdk.Dec, 0), - }, - { - name: "Zero value", - numPortions: 10, - value: sdk.NewDec(0), - maxPortion: sdk.MustNewDecFromStr("0.1"), - want: []sdk.Dec{sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()}, - }, - { - name: "Max portion", - numPortions: 10, - value: sdk.NewDec(100), - maxPortion: sdk.OneDec(), - want: []sdk.Dec{sdk.NewDec(100), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()}, - }, - { - name: "Max portion is zero", - numPortions: 10, - value: sdk.NewDec(100), - maxPortion: sdk.ZeroDec(), - want: []sdk.Dec{sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec()}, - }, - { - name: "Max portion is negative", - numPortions: 10, - value: sdk.NewDec(100), - maxPortion: sdk.MustNewDecFromStr("-0.1"), - wantErr: true, - }, - { - name: "Max portion greater than 1", - numPortions: 10, - value: sdk.NewDec(100), - maxPortion: sdk.NewDec(2), - wantErr: true, - }, - { - name: "Specific distribution with 50 portions with 5% max portion", - numPortions: 50, - value: sdk.NewDec(100), - maxPortion: sdk.MustNewDecFromStr("0.05"), - want: []sdk.Dec{ - sdk.MustNewDecFromStr("5"), - sdk.MustNewDecFromStr("4.75"), - sdk.MustNewDecFromStr("4.5125"), - sdk.MustNewDecFromStr("4.286875"), - sdk.MustNewDecFromStr("4.07253125"), - sdk.MustNewDecFromStr("3.868904688"), - sdk.MustNewDecFromStr("3.675459453"), - sdk.MustNewDecFromStr("3.49168648"), - sdk.MustNewDecFromStr("3.317102156"), - sdk.MustNewDecFromStr("3.151247049"), - sdk.MustNewDecFromStr("2.993684696"), - sdk.MustNewDecFromStr("2.844000461"), - sdk.MustNewDecFromStr("2.701800438"), - sdk.MustNewDecFromStr("2.566710416"), - sdk.MustNewDecFromStr("2.438374896"), - sdk.MustNewDecFromStr("2.316456151"), - sdk.MustNewDecFromStr("2.200633343"), - sdk.MustNewDecFromStr("2.090601676"), - sdk.MustNewDecFromStr("1.986071592"), - sdk.MustNewDecFromStr("1.886768013"), - sdk.MustNewDecFromStr("1.792429612"), - sdk.MustNewDecFromStr("1.702808131"), - sdk.MustNewDecFromStr("1.617667725"), - sdk.MustNewDecFromStr("1.536784339"), - sdk.MustNewDecFromStr("1.459945122"), - sdk.MustNewDecFromStr("1.386947866"), - sdk.MustNewDecFromStr("1.317600472"), - sdk.MustNewDecFromStr("1.251720449"), - sdk.MustNewDecFromStr("1.189134426"), - sdk.MustNewDecFromStr("1.129677705"), - sdk.MustNewDecFromStr("1.07319382"), - sdk.MustNewDecFromStr("1.019534129"), - sdk.MustNewDecFromStr("0.9685574223"), - sdk.MustNewDecFromStr("0.9201295512"), - sdk.MustNewDecFromStr("0.8741230736"), - sdk.MustNewDecFromStr("0.8304169199"), - sdk.MustNewDecFromStr("0.7888960739"), - sdk.MustNewDecFromStr("0.7494512702"), - sdk.MustNewDecFromStr("0.7119787067"), - sdk.MustNewDecFromStr("0.6763797714"), - sdk.MustNewDecFromStr("0.6425607828"), - sdk.MustNewDecFromStr("0.6104327437"), - sdk.MustNewDecFromStr("0.5799111065"), - sdk.MustNewDecFromStr("0.5509155512"), - sdk.MustNewDecFromStr("0.5233697736"), - sdk.MustNewDecFromStr("0.4972012849"), - sdk.MustNewDecFromStr("0.4723412207"), - sdk.MustNewDecFromStr("0.4487241597"), - sdk.MustNewDecFromStr("0.4262879517"), - sdk.MustNewDecFromStr("0.4049735541"), +var ( + // ConsPrivKeys generate ed25519 ConsPrivKeys to be used for validator operator keys + ConsPrivKeys = []ccrypto.PrivKey{ + ed25519.GenPrivKey(), + ed25519.GenPrivKey(), + ed25519.GenPrivKey(), + ed25519.GenPrivKey(), + ed25519.GenPrivKey(), + } + + // ConsPubKeys holds the consensus public keys to be used for validator operator keys + ConsPubKeys = []ccrypto.PubKey{ + ConsPrivKeys[0].PubKey(), + ConsPrivKeys[1].PubKey(), + ConsPrivKeys[2].PubKey(), + ConsPrivKeys[3].PubKey(), + ConsPrivKeys[4].PubKey(), + } +) + +func getMockValidators(suite *KeeperTestSuite) ([]*stakingtypes.Validator, error) { + validator1, err := stakingtypes.NewValidator(sdk.ValAddress([]byte("val1val1val1val1val1")), ConsPubKeys[0], stakingtypes.Description{}) + suite.Require().NoError(err) + validator2, err := stakingtypes.NewValidator(sdk.ValAddress([]byte("val2val2val2val2val2")), ConsPubKeys[1], stakingtypes.Description{}) + suite.Require().NoError(err) + validator3, err := stakingtypes.NewValidator(sdk.ValAddress([]byte("val3val3val3val3val3")), ConsPubKeys[2], stakingtypes.Description{}) + suite.Require().NoError(err) + return []*stakingtypes.Validator{&validator1, &validator2, &validator3}, nil +} + +func (suite *KeeperTestSuite) TestGetValidatorInfos() { + ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper + + // Create mock validators + validators, err := getMockValidators(suite) + suite.Require().NoError(err) + validator1, validator2, validator3 := validators[0], validators[1], validators[2] + + consAddr1, err := validator1.GetConsAddr() + suite.Require().NoError(err) + consAddr2, err := validator2.GetConsAddr() + suite.Require().NoError(err) + consAddr3, err := validator3.GetConsAddr() + suite.Require().NoError(err) + + // Create mock RequestBeginBlock + req := abci.RequestBeginBlock{ + LastCommitInfo: abci.CommitInfo{ + Votes: []abci.VoteInfo{ + { + Validator: abci.Validator{Address: consAddr1, Power: 10}, + SignedLastBlock: true, + }, + { + Validator: abci.Validator{Address: consAddr2, Power: 20}, + SignedLastBlock: true, + }, + { + Validator: abci.Validator{Address: consAddr3, Power: 30}, + SignedLastBlock: false, + }, }, }, - { - name: "Specific distribution with 50 portions with 10% max portion", - numPortions: 50, - value: sdk.NewDec(100), - maxPortion: sdk.MustNewDecFromStr("0.1"), - want: []sdk.Dec{ - sdk.MustNewDecFromStr("10"), - sdk.MustNewDecFromStr("9"), - sdk.MustNewDecFromStr("8.1"), - sdk.MustNewDecFromStr("7.29"), - sdk.MustNewDecFromStr("6.561"), - sdk.MustNewDecFromStr("5.9049"), - sdk.MustNewDecFromStr("5.31441"), - sdk.MustNewDecFromStr("4.782969"), - sdk.MustNewDecFromStr("4.3046721"), - sdk.MustNewDecFromStr("3.87420489"), - sdk.MustNewDecFromStr("3.486784401"), - sdk.MustNewDecFromStr("3.138105961"), - sdk.MustNewDecFromStr("2.824295365"), - sdk.MustNewDecFromStr("2.541865828"), - sdk.MustNewDecFromStr("2.287679245"), - sdk.MustNewDecFromStr("2.058911321"), - sdk.MustNewDecFromStr("1.853020189"), - sdk.MustNewDecFromStr("1.66771817"), - sdk.MustNewDecFromStr("1.500946353"), - sdk.MustNewDecFromStr("1.350851718"), - sdk.MustNewDecFromStr("1.215766546"), - sdk.MustNewDecFromStr("1.094189891"), - sdk.MustNewDecFromStr("0.9847709022"), - sdk.MustNewDecFromStr("0.886293812"), - sdk.MustNewDecFromStr("0.7976644308"), - sdk.MustNewDecFromStr("0.7178979877"), - sdk.MustNewDecFromStr("0.6461081889"), - sdk.MustNewDecFromStr("0.58149737"), - sdk.MustNewDecFromStr("0.523347633"), - sdk.MustNewDecFromStr("0.4710128697"), - sdk.MustNewDecFromStr("0.4239115828"), - sdk.MustNewDecFromStr("0.3815204245"), - sdk.MustNewDecFromStr("0.343368382"), - sdk.MustNewDecFromStr("0.3090315438"), - sdk.MustNewDecFromStr("0.2781283894"), - sdk.MustNewDecFromStr("0.2503155505"), - sdk.MustNewDecFromStr("0.2252839954"), - sdk.MustNewDecFromStr("0.2027555959"), - sdk.MustNewDecFromStr("0.1824800363"), - sdk.MustNewDecFromStr("0.1642320327"), - sdk.MustNewDecFromStr("0.1478088294"), - sdk.MustNewDecFromStr("0.1330279465"), - sdk.MustNewDecFromStr("0.1197251518"), - sdk.MustNewDecFromStr("0.1077526366"), - sdk.MustNewDecFromStr("0.09697737298"), - sdk.MustNewDecFromStr("0.08727963568"), - sdk.MustNewDecFromStr("0.07855167211"), - sdk.MustNewDecFromStr("0.0706965049"), - sdk.MustNewDecFromStr("0.06362685441"), - sdk.MustNewDecFromStr("0.05726416897"), + } + + // Set up expectations for the mock StakingKeeper + suite.stakingKeeper.EXPECT().ValidatorByConsAddr(ctx, consAddr1).Return(validator1) + suite.stakingKeeper.EXPECT().ValidatorByConsAddr(ctx, consAddr2).Return(validator2) + + // Call the function being tested + validatorInfos := incentivesKeeper.getValidatorInfos(ctx, req) + + // Assert the results + suite.Require().Len(validatorInfos, 2) + suite.Require().Equal(validator1, validatorInfos[0].Validator) + suite.Require().Equal(int64(10), validatorInfos[0].Power) + suite.Require().Equal(validator2, validatorInfos[1].Validator) + suite.Require().Equal(int64(20), validatorInfos[1].Power) +} + +func (suite *KeeperTestSuite) TestGetValidatorInfosNoSigners() { + ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper + + // Create mock RequestBeginBlock with no signers + req := abci.RequestBeginBlock{ + LastCommitInfo: abci.CommitInfo{ + Votes: []abci.VoteInfo{ + { + Validator: abci.Validator{Address: []byte("val1"), Power: 10}, + SignedLastBlock: false, + }, + { + Validator: abci.Validator{Address: []byte("val2"), Power: 20}, + SignedLastBlock: false, + }, }, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - tt := tt - got, remaining, err := getApportionments(tt.numPortions, tt.value, tt.maxPortion) - if tt.wantErr { - require.Error(t, err) - } else { - require.NoError(t, err) - - // Correct number of portions - require.Equal(t, tt.numPortions, uint(len(got))) - - // We round before comparing to avoid floating point precision issues. - // Check that the values returned in the slice line up with the expected values. - for i, value := range got { - // Correct apportionment values - require.Equal(t, roundToPrecision(tt.want[i], 6), roundToPrecision(value, 6)) - - // No negative apportionments - require.True(t, value.GTE(sdk.ZeroDec())) - } - - // Non-negative remaining value - require.True(t, remaining.GTE(sdk.ZeroDec())) - - // Sum of apportionments plus remaining equals the original value - gotSum := sdk.ZeroDec() - for _, g := range got { - gotSum = gotSum.Add(g) - } - require.True(t, gotSum.LTE(tt.value)) - require.Equal(t, tt.value, gotSum.Add(remaining)) - - // Test that each value is greater than or equal to the next - for i := 0; i < len(got)-1; i++ { - if i == len(got)-1 { - break - } - - require.True(t, got[i].GTE(got[i+1]), "Value at index %d should be greater than or equal to value at index %d", i, i+1) - } - } - }) + // Call the function being tested + validatorInfos := incentivesKeeper.getValidatorInfos(ctx, req) + + // Assert the results + suite.Require().Len(validatorInfos, 0) +} + +func (suite *KeeperTestSuite) TestSortValidatorInfosByPower() { + // Create a slice of ValidatorInfo with unsorted power + valInfos := []ValidatorInfo{ + {Power: 30}, + {Power: 10}, + {Power: 50}, + {Power: 20}, + {Power: 40}, + } + + // Sort the validator infos + sortedValInfos := sortValidatorInfosByPower(valInfos) + + // Assert the results + suite.Require().Len(sortedValInfos, 5) + suite.Require().Equal(int64(50), sortedValInfos[0].Power) + suite.Require().Equal(int64(40), sortedValInfos[1].Power) + suite.Require().Equal(int64(30), sortedValInfos[2].Power) + suite.Require().Equal(int64(20), sortedValInfos[3].Power) + suite.Require().Equal(int64(10), sortedValInfos[4].Power) +} + +func (suite *KeeperTestSuite) TestTruncateVoters() { + // Create a slice of ValidatorInfo + valInfos := []ValidatorInfo{ + {Power: 30}, + {Power: 10}, + {Power: 50}, + {Power: 20}, + {Power: 40}, + } + + // Get the truncated voters + truncatedVoters := truncateVoters(valInfos, 3) + + // Assert the results + suite.Require().Len(truncatedVoters, 3) + suite.Require().Equal(int64(30), truncatedVoters[0].Power) + suite.Require().Equal(int64(10), truncatedVoters[1].Power) + suite.Require().Equal(int64(50), truncatedVoters[2].Power) +} + +func (suite *KeeperTestSuite) TestSortValidatorInfosByPowerEmptySlice() { + // Create an empty slice of ValidatorInfo + var valInfos []ValidatorInfo + + // Sort the validator infos + sortedValInfos := sortValidatorInfosByPower(valInfos) + + // Assert the results + suite.Require().Len(sortedValInfos, 0) +} + +func (suite *KeeperTestSuite) TestGetTotalPower() { + // Create a slice of ValidatorInfo + valInfos := []ValidatorInfo{ + {Power: 30}, + {Power: 10}, + {Power: 50}, + {Power: 20}, + {Power: 40}, + } + + // Get the total power + totalPower := getTotalPower(&valInfos) + + // Assert the result + suite.Require().Equal(int64(150), totalPower) +} + +func (suite *KeeperTestSuite) TestGetTotalPowerEmptySlice() { + // Create an empty slice of ValidatorInfo + var valInfos []ValidatorInfo + + // Get the total power + totalPower := getTotalPower(&valInfos) + + // Assert the result + suite.Require().Equal(int64(0), totalPower) +} + +func (suite *KeeperTestSuite) TestAllocateTokensToValidator() { + ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper + + // Create a mock validator + valAddr := sdk.ValAddress([]byte("validatorvalidatorva")) + validator, err := stakingtypes.NewValidator(valAddr, ConsPubKeys[0], stakingtypes.Description{}) + suite.Require().NoError(err) + + // Create mock tokens to allocate + tokens := sdk.NewDecCoins(sdk.NewDecCoin("usom", sdk.NewInt(100))) + + // Set up expectations for the mock DistributionKeeper + currentRewards := distributiontypes.ValidatorCurrentRewards{Rewards: sdk.DecCoins{}} + outstandingRewards := distributiontypes.ValidatorOutstandingRewards{Rewards: sdk.DecCoins{}} + + suite.distributionKeeper.EXPECT(). + GetValidatorCurrentRewards(ctx, valAddr). + Return(currentRewards) + suite.distributionKeeper.EXPECT(). + SetValidatorCurrentRewards(ctx, valAddr, distributiontypes.ValidatorCurrentRewards{Rewards: tokens}) + suite.distributionKeeper.EXPECT(). + GetValidatorOutstandingRewards(ctx, valAddr). + Return(outstandingRewards) + suite.distributionKeeper.EXPECT(). + SetValidatorOutstandingRewards(ctx, valAddr, distributiontypes.ValidatorOutstandingRewards{Rewards: tokens}) + + // Call the function being tested + incentivesKeeper.AllocateTokensToValidator(ctx, validator, tokens) + + // Verify that the event was emitted + events := ctx.EventManager().Events() + suite.Require().Len(events, 1) + event := events[0] + suite.Require().Equal(types.EventTypeIncentivesRewards, event.Type) + suite.Require().Len(event.Attributes, 2) + suite.Require().Equal(sdk.AttributeKeyAmount, string(event.Attributes[0].Key)) + suite.Require().Equal(tokens.String(), string(event.Attributes[0].Value)) + suite.Require().Equal(types.AttributeKeyValidator, string(event.Attributes[1].Key)) + suite.Require().Equal(valAddr.String(), string(event.Attributes[1].Value)) +} + +func (suite *KeeperTestSuite) TestAllocateTokensToValidatorWithExistingRewards() { + ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper + + // Create a mock validator + valAddr := sdk.ValAddress([]byte("validatorvalidatorva")) + validator, err := stakingtypes.NewValidator(valAddr, ConsPubKeys[0], stakingtypes.Description{}) + suite.Require().NoError(err) + + // Create mock tokens to allocate + existingRewards := sdk.NewDecCoins(sdk.NewDecCoin("usom", sdk.NewInt(50))) + newTokens := sdk.NewDecCoins(sdk.NewDecCoin("usom", sdk.NewInt(100))) + expectedTotalRewards := existingRewards.Add(newTokens...) + + // Set up expectations for the mock DistributionKeeper + currentRewards := distributiontypes.ValidatorCurrentRewards{Rewards: existingRewards} + outstandingRewards := distributiontypes.ValidatorOutstandingRewards{Rewards: existingRewards} + + suite.distributionKeeper.EXPECT(). + GetValidatorCurrentRewards(ctx, valAddr). + Return(currentRewards) + suite.distributionKeeper.EXPECT(). + SetValidatorCurrentRewards(ctx, valAddr, distributiontypes.ValidatorCurrentRewards{Rewards: expectedTotalRewards}) + suite.distributionKeeper.EXPECT(). + GetValidatorOutstandingRewards(ctx, valAddr). + Return(outstandingRewards) + suite.distributionKeeper.EXPECT(). + SetValidatorOutstandingRewards(ctx, valAddr, distributiontypes.ValidatorOutstandingRewards{Rewards: expectedTotalRewards}) + + // Call the function being tested + incentivesKeeper.AllocateTokensToValidator(ctx, validator, newTokens) + + // Verify that the event was emitted + events := ctx.EventManager().Events() + suite.Require().Len(events, 1) + event := events[0] + suite.Require().Equal(types.EventTypeIncentivesRewards, event.Type) + suite.Require().Len(event.Attributes, 2) + suite.Require().Equal(sdk.AttributeKeyAmount, string(event.Attributes[0].Key)) + suite.Require().Equal(newTokens.String(), string(event.Attributes[0].Value)) + suite.Require().Equal(types.AttributeKeyValidator, string(event.Attributes[1].Key)) + suite.Require().Equal(valAddr.String(), string(event.Attributes[1].Value)) +} + +func (suite *KeeperTestSuite) TestAllocateTokens() { + ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper + + // Create mock validators + validators, err := getMockValidators(suite) + suite.Require().NoError(err) + validator1, validator2, validator3 := validators[0], validators[1], validators[2] + + // Set up qualifying voters + qualifyingVoters := []ValidatorInfo{ + {Validator: validator1, Power: 30}, + {Validator: validator2, Power: 20}, + {Validator: validator3, Power: 10}, + } + + totalPreviousPower := int64(60) + totalDistribution := sdk.NewDecCoins(sdk.NewDecCoin("usom", sdk.NewInt(100))) + maxFraction := sdk.NewDecWithPrec(5, 1) // 0.5 + + // Set up expectations for the mock DistributionKeeper + totalExpectedRewards := sdk.NewDecCoins() + for _, voter := range qualifyingVoters { + powerFraction := sdk.NewDecFromInt(sdk.NewInt(voter.Power)).QuoInt64(totalPreviousPower) + expectedReward := totalDistribution.MulDecTruncate(powerFraction) + if powerFraction.GT(maxFraction) { + expectedReward = totalDistribution.MulDecTruncate(maxFraction) + } + + totalExpectedRewards = totalExpectedRewards.Add(expectedReward...) + + suite.distributionKeeper.EXPECT(). + GetValidatorCurrentRewards(ctx, voter.Validator.GetOperator()). + Return(distributiontypes.ValidatorCurrentRewards{Rewards: sdk.DecCoins{}}) + suite.distributionKeeper.EXPECT(). + SetValidatorCurrentRewards(ctx, voter.Validator.GetOperator(), distributiontypes.ValidatorCurrentRewards{Rewards: expectedReward}) + suite.distributionKeeper.EXPECT(). + GetValidatorOutstandingRewards(ctx, voter.Validator.GetOperator()). + Return(distributiontypes.ValidatorOutstandingRewards{Rewards: sdk.DecCoins{}}) + suite.distributionKeeper.EXPECT(). + SetValidatorOutstandingRewards(ctx, voter.Validator.GetOperator(), distributiontypes.ValidatorOutstandingRewards{Rewards: expectedReward}) + } + + // Call the function being tested + remaining := incentivesKeeper.AllocateTokens(ctx, totalPreviousPower, totalDistribution, qualifyingVoters, maxFraction) + + // Verify that the sum of remaining and distributed rewards equals totalDistribution + totalAllocated := remaining.Add(totalExpectedRewards...) + suite.Require().Equal(totalDistribution, totalAllocated, "Sum of remaining and distributed rewards should equal total distribution") + + // Verify that events were emitted + events := ctx.EventManager().Events() + suite.Require().Len(events, 3) // One event for each validator + for i, event := range events { + suite.Require().Equal(types.EventTypeIncentivesRewards, event.Type) + suite.Require().Len(event.Attributes, 2) + suite.Require().Equal(sdk.AttributeKeyAmount, string(event.Attributes[0].Key)) + suite.Require().Equal(types.AttributeKeyValidator, string(event.Attributes[1].Key)) + suite.Require().Equal(qualifyingVoters[i].Validator.GetOperator().String(), string(event.Attributes[1].Value)) } } -func roundToPrecision(d sdk.Dec, precision uint64) sdk.Dec { - multiplier := sdk.NewDec(10).Power(precision) - return d.Mul(multiplier).RoundInt().ToLegacyDec().Quo(multiplier) +func (suite *KeeperTestSuite) TestAllocateTokensNoQualifyingVoters() { + ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper + + totalPreviousPower := int64(100) + totalDistribution := sdk.NewDecCoins(sdk.NewDecCoin("usom", sdk.NewInt(100))) + maxFraction := sdk.NewDecWithPrec(5, 1) // 0.5 + + // Call the function being tested with empty qualifyingVoters + remaining := incentivesKeeper.AllocateTokens(ctx, totalPreviousPower, totalDistribution, []ValidatorInfo{}, maxFraction) + + // Verify that all tokens remain unallocated + suite.Require().Equal(totalDistribution, remaining, "All tokens should remain unallocated when there are no qualifying voters") + + // Verify that no events were emitted + events := ctx.EventManager().Events() + suite.Require().Len(events, 0, "No events should be emitted when there are no qualifying voters") } diff --git a/x/incentives/keeper/keeper_test.go b/x/incentives/keeper/keeper_test.go index c04092516..e690c9ef0 100644 --- a/x/incentives/keeper/keeper_test.go +++ b/x/incentives/keeper/keeper_test.go @@ -26,6 +26,7 @@ type KeeperTestSuite struct { distributionKeeper *incentivestestutil.MockDistributionKeeper bankKeeper *incentivestestutil.MockBankKeeper mintKeeper *incentivestestutil.MockMintKeeper + stakingKeeper *incentivestestutil.MockStakingKeeper encCfg moduletestutil.TestEncodingConfig } @@ -44,6 +45,7 @@ func (suite *KeeperTestSuite) SetupTest() { suite.distributionKeeper = incentivestestutil.NewMockDistributionKeeper(ctrl) suite.bankKeeper = incentivestestutil.NewMockBankKeeper(ctrl) suite.mintKeeper = incentivestestutil.NewMockMintKeeper(ctrl) + suite.stakingKeeper = incentivestestutil.NewMockStakingKeeper(ctrl) suite.ctx = ctx params := paramskeeper.NewKeeper( @@ -64,6 +66,7 @@ func (suite *KeeperTestSuite) SetupTest() { suite.distributionKeeper, suite.bankKeeper, suite.mintKeeper, + suite.stakingKeeper, ) suite.encCfg = encCfg @@ -93,10 +96,10 @@ func (suite *KeeperTestSuite) TestGetAPY() { require.Equal(sdk.ZeroDec(), incentivesKeeper.GetAPY(ctx)) // incentives enabled - incentivesKeeper.SetParams(ctx, incentivesTypes.Params{ - DistributionPerBlock: distributionPerBlock, - IncentivesCutoffHeight: 1000, - }) + params := incentivesKeeper.GetParamSet(ctx) + params.DistributionPerBlock = distributionPerBlock + params.IncentivesCutoffHeight = 1000 + incentivesKeeper.SetParams(ctx, params) expected := sdk.NewDecFromInt(distributionPerBlock.Amount.Mul(sdk.NewInt(int64(blocksPerYear)))).Quo(sdk.NewDecFromInt(stakingTotalSupply).Mul(bondedRatio)) require.Equal(expected, incentivesKeeper.GetAPY(ctx)) } diff --git a/x/incentives/testutil/expected_keepers_mocks.go b/x/incentives/testutil/expected_keepers_mocks.go index 4f6cad0ad..c87ea1611 100644 --- a/x/incentives/testutil/expected_keepers_mocks.go +++ b/x/incentives/testutil/expected_keepers_mocks.go @@ -7,9 +7,11 @@ package mock_types import ( reflect "reflect" + math "cosmossdk.io/math" types "github.com/cosmos/cosmos-sdk/types" types0 "github.com/cosmos/cosmos-sdk/x/distribution/types" types1 "github.com/cosmos/cosmos-sdk/x/mint/types" + types2 "github.com/cosmos/cosmos-sdk/x/staking/types" gomock "github.com/golang/mock/gomock" ) @@ -50,6 +52,48 @@ func (mr *MockDistributionKeeperMockRecorder) GetFeePool(ctx interface{}) *gomoc return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetFeePool", reflect.TypeOf((*MockDistributionKeeper)(nil).GetFeePool), ctx) } +// GetValidatorAccumulatedCommission mocks base method. +func (m *MockDistributionKeeper) GetValidatorAccumulatedCommission(ctx types.Context, valAddr types.ValAddress) types0.ValidatorAccumulatedCommission { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValidatorAccumulatedCommission", ctx, valAddr) + ret0, _ := ret[0].(types0.ValidatorAccumulatedCommission) + return ret0 +} + +// GetValidatorAccumulatedCommission indicates an expected call of GetValidatorAccumulatedCommission. +func (mr *MockDistributionKeeperMockRecorder) GetValidatorAccumulatedCommission(ctx, valAddr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorAccumulatedCommission", reflect.TypeOf((*MockDistributionKeeper)(nil).GetValidatorAccumulatedCommission), ctx, valAddr) +} + +// GetValidatorCurrentRewards mocks base method. +func (m *MockDistributionKeeper) GetValidatorCurrentRewards(ctx types.Context, valAddr types.ValAddress) types0.ValidatorCurrentRewards { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValidatorCurrentRewards", ctx, valAddr) + ret0, _ := ret[0].(types0.ValidatorCurrentRewards) + return ret0 +} + +// GetValidatorCurrentRewards indicates an expected call of GetValidatorCurrentRewards. +func (mr *MockDistributionKeeperMockRecorder) GetValidatorCurrentRewards(ctx, valAddr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorCurrentRewards", reflect.TypeOf((*MockDistributionKeeper)(nil).GetValidatorCurrentRewards), ctx, valAddr) +} + +// GetValidatorOutstandingRewards mocks base method. +func (m *MockDistributionKeeper) GetValidatorOutstandingRewards(ctx types.Context, valAddr types.ValAddress) types0.ValidatorOutstandingRewards { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "GetValidatorOutstandingRewards", ctx, valAddr) + ret0, _ := ret[0].(types0.ValidatorOutstandingRewards) + return ret0 +} + +// GetValidatorOutstandingRewards indicates an expected call of GetValidatorOutstandingRewards. +func (mr *MockDistributionKeeperMockRecorder) GetValidatorOutstandingRewards(ctx, valAddr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetValidatorOutstandingRewards", reflect.TypeOf((*MockDistributionKeeper)(nil).GetValidatorOutstandingRewards), ctx, valAddr) +} + // SetFeePool mocks base method. func (m *MockDistributionKeeper) SetFeePool(ctx types.Context, feePool types0.FeePool) { m.ctrl.T.Helper() @@ -62,6 +106,42 @@ func (mr *MockDistributionKeeperMockRecorder) SetFeePool(ctx, feePool interface{ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetFeePool", reflect.TypeOf((*MockDistributionKeeper)(nil).SetFeePool), ctx, feePool) } +// SetValidatorAccumulatedCommission mocks base method. +func (m *MockDistributionKeeper) SetValidatorAccumulatedCommission(ctx types.Context, valAddr types.ValAddress, commission types0.ValidatorAccumulatedCommission) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetValidatorAccumulatedCommission", ctx, valAddr, commission) +} + +// SetValidatorAccumulatedCommission indicates an expected call of SetValidatorAccumulatedCommission. +func (mr *MockDistributionKeeperMockRecorder) SetValidatorAccumulatedCommission(ctx, valAddr, commission interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetValidatorAccumulatedCommission", reflect.TypeOf((*MockDistributionKeeper)(nil).SetValidatorAccumulatedCommission), ctx, valAddr, commission) +} + +// SetValidatorCurrentRewards mocks base method. +func (m *MockDistributionKeeper) SetValidatorCurrentRewards(ctx types.Context, valAddr types.ValAddress, rewards types0.ValidatorCurrentRewards) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetValidatorCurrentRewards", ctx, valAddr, rewards) +} + +// SetValidatorCurrentRewards indicates an expected call of SetValidatorCurrentRewards. +func (mr *MockDistributionKeeperMockRecorder) SetValidatorCurrentRewards(ctx, valAddr, rewards interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetValidatorCurrentRewards", reflect.TypeOf((*MockDistributionKeeper)(nil).SetValidatorCurrentRewards), ctx, valAddr, rewards) +} + +// SetValidatorOutstandingRewards mocks base method. +func (m *MockDistributionKeeper) SetValidatorOutstandingRewards(ctx types.Context, valAddr types.ValAddress, rewards types0.ValidatorOutstandingRewards) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SetValidatorOutstandingRewards", ctx, valAddr, rewards) +} + +// SetValidatorOutstandingRewards indicates an expected call of SetValidatorOutstandingRewards. +func (mr *MockDistributionKeeperMockRecorder) SetValidatorOutstandingRewards(ctx, valAddr, rewards interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetValidatorOutstandingRewards", reflect.TypeOf((*MockDistributionKeeper)(nil).SetValidatorOutstandingRewards), ctx, valAddr, rewards) +} + // MockBankKeeper is a mock of BankKeeper interface. type MockBankKeeper struct { ctrl *gomock.Controller @@ -249,10 +329,10 @@ func (mr *MockMintKeeperMockRecorder) GetParams(ctx interface{}) *gomock.Call { } // StakingTokenSupply mocks base method. -func (m *MockMintKeeper) StakingTokenSupply(ctx types.Context) types.Int { +func (m *MockMintKeeper) StakingTokenSupply(ctx types.Context) math.Int { m.ctrl.T.Helper() ret := m.ctrl.Call(m, "StakingTokenSupply", ctx) - ret0, _ := ret[0].(types.Int) + ret0, _ := ret[0].(math.Int) return ret0 } @@ -261,3 +341,40 @@ func (mr *MockMintKeeperMockRecorder) StakingTokenSupply(ctx interface{}) *gomoc mr.mock.ctrl.T.Helper() return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StakingTokenSupply", reflect.TypeOf((*MockMintKeeper)(nil).StakingTokenSupply), ctx) } + +// MockStakingKeeper is a mock of StakingKeeper interface. +type MockStakingKeeper struct { + ctrl *gomock.Controller + recorder *MockStakingKeeperMockRecorder +} + +// MockStakingKeeperMockRecorder is the mock recorder for MockStakingKeeper. +type MockStakingKeeperMockRecorder struct { + mock *MockStakingKeeper +} + +// NewMockStakingKeeper creates a new mock instance. +func NewMockStakingKeeper(ctrl *gomock.Controller) *MockStakingKeeper { + mock := &MockStakingKeeper{ctrl: ctrl} + mock.recorder = &MockStakingKeeperMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use. +func (m *MockStakingKeeper) EXPECT() *MockStakingKeeperMockRecorder { + return m.recorder +} + +// ValidatorByConsAddr mocks base method. +func (m *MockStakingKeeper) ValidatorByConsAddr(ctx types.Context, consAddr types.ConsAddress) types2.ValidatorI { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ValidatorByConsAddr", ctx, consAddr) + ret0, _ := ret[0].(types2.ValidatorI) + return ret0 +} + +// ValidatorByConsAddr indicates an expected call of ValidatorByConsAddr. +func (mr *MockStakingKeeperMockRecorder) ValidatorByConsAddr(ctx, consAddr interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValidatorByConsAddr", reflect.TypeOf((*MockStakingKeeper)(nil).ValidatorByConsAddr), ctx, consAddr) +} From e69dd1867552c77b0abe8750339ed38e9b2b14bd Mon Sep 17 00:00:00 2001 From: Collin Brittain Date: Thu, 17 Oct 2024 16:03:11 -0500 Subject: [PATCH 7/8] Fix linter issues --- .../migrations/v1/keeper/query_server.go | 21 ++++++------ x/incentives/keeper/abci_test.go | 3 +- x/incentives/keeper/incentives_test.go | 32 +++++++++---------- 3 files changed, 26 insertions(+), 30 deletions(-) diff --git a/x/cellarfees/migrations/v1/keeper/query_server.go b/x/cellarfees/migrations/v1/keeper/query_server.go index ac9a70663..569976445 100644 --- a/x/cellarfees/migrations/v1/keeper/query_server.go +++ b/x/cellarfees/migrations/v1/keeper/query_server.go @@ -5,51 +5,50 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/peggyjv/sommelier/v7/x/cellarfees/migrations/v1/types" - v1types "github.com/peggyjv/sommelier/v7/x/cellarfees/migrations/v1/types" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) var _ types.QueryServer = Keeper{} -func (k Keeper) QueryParams(c context.Context, req *v1types.QueryParamsRequest) (*v1types.QueryParamsResponse, error) { +func (k Keeper) QueryParams(c context.Context, req *types.QueryParamsRequest) (*types.QueryParamsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - return &v1types.QueryParamsResponse{ + return &types.QueryParamsResponse{ Params: k.GetParams(sdk.UnwrapSDKContext(c)), }, nil } -func (k Keeper) QueryModuleAccounts(c context.Context, req *v1types.QueryModuleAccountsRequest) (*v1types.QueryModuleAccountsResponse, error) { +func (k Keeper) QueryModuleAccounts(c context.Context, req *types.QueryModuleAccountsRequest) (*types.QueryModuleAccountsResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - return &v1types.QueryModuleAccountsResponse{ + return &types.QueryModuleAccountsResponse{ FeesAddress: k.GetFeesAccount(sdk.UnwrapSDKContext(c)).GetAddress().String(), }, nil } -func (k Keeper) QueryLastRewardSupplyPeak(c context.Context, req *v1types.QueryLastRewardSupplyPeakRequest) (*v1types.QueryLastRewardSupplyPeakResponse, error) { +func (k Keeper) QueryLastRewardSupplyPeak(c context.Context, req *types.QueryLastRewardSupplyPeakRequest) (*types.QueryLastRewardSupplyPeakResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - return &v1types.QueryLastRewardSupplyPeakResponse{LastRewardSupplyPeak: k.GetLastRewardSupplyPeak(sdk.UnwrapSDKContext(c))}, nil + return &types.QueryLastRewardSupplyPeakResponse{LastRewardSupplyPeak: k.GetLastRewardSupplyPeak(sdk.UnwrapSDKContext(c))}, nil } -func (k Keeper) QueryFeeAccrualCounters(c context.Context, req *v1types.QueryFeeAccrualCountersRequest) (*v1types.QueryFeeAccrualCountersResponse, error) { +func (k Keeper) QueryFeeAccrualCounters(c context.Context, req *types.QueryFeeAccrualCountersRequest) (*types.QueryFeeAccrualCountersResponse, error) { if req == nil { return nil, status.Error(codes.InvalidArgument, "empty request") } - return &v1types.QueryFeeAccrualCountersResponse{FeeAccrualCounters: k.GetFeeAccrualCounters(sdk.UnwrapSDKContext(c))}, nil + return &types.QueryFeeAccrualCountersResponse{FeeAccrualCounters: k.GetFeeAccrualCounters(sdk.UnwrapSDKContext(c))}, nil } -func (k Keeper) QueryAPY(c context.Context, _ *v1types.QueryAPYRequest) (*v1types.QueryAPYResponse, error) { - return &v1types.QueryAPYResponse{ +func (k Keeper) QueryAPY(c context.Context, _ *types.QueryAPYRequest) (*types.QueryAPYResponse, error) { + return &types.QueryAPYResponse{ Apy: k.GetAPY(sdk.UnwrapSDKContext(c)).String(), }, nil } diff --git a/x/incentives/keeper/abci_test.go b/x/incentives/keeper/abci_test.go index e137b3f08..1bf7edb18 100644 --- a/x/incentives/keeper/abci_test.go +++ b/x/incentives/keeper/abci_test.go @@ -124,8 +124,7 @@ func (suite *KeeperTestSuite) TestBeginBlockerSuccess() { suite.distributionKeeper.EXPECT().GetFeePool(ctx).Return(pool) // Mock validators - validators, err := getMockValidators(suite) - require.NoError(err) + validators := suite.getMockValidators() validator1, validator2 := validators[0], validators[1] consAddr1, err := validator1.GetConsAddr() diff --git a/x/incentives/keeper/incentives_test.go b/x/incentives/keeper/incentives_test.go index 301c1c702..fdef40030 100644 --- a/x/incentives/keeper/incentives_test.go +++ b/x/incentives/keeper/incentives_test.go @@ -30,22 +30,21 @@ var ( } ) -func getMockValidators(suite *KeeperTestSuite) ([]*stakingtypes.Validator, error) { +func (suite *KeeperTestSuite) getMockValidators() []*stakingtypes.Validator { validator1, err := stakingtypes.NewValidator(sdk.ValAddress([]byte("val1val1val1val1val1")), ConsPubKeys[0], stakingtypes.Description{}) suite.Require().NoError(err) validator2, err := stakingtypes.NewValidator(sdk.ValAddress([]byte("val2val2val2val2val2")), ConsPubKeys[1], stakingtypes.Description{}) suite.Require().NoError(err) validator3, err := stakingtypes.NewValidator(sdk.ValAddress([]byte("val3val3val3val3val3")), ConsPubKeys[2], stakingtypes.Description{}) suite.Require().NoError(err) - return []*stakingtypes.Validator{&validator1, &validator2, &validator3}, nil + return []*stakingtypes.Validator{&validator1, &validator2, &validator3} } func (suite *KeeperTestSuite) TestGetValidatorInfos() { ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper // Create mock validators - validators, err := getMockValidators(suite) - suite.Require().NoError(err) + validators := suite.getMockValidators() validator1, validator2, validator3 := validators[0], validators[1], validators[2] consAddr1, err := validator1.GetConsAddr() @@ -232,10 +231,10 @@ func (suite *KeeperTestSuite) TestAllocateTokensToValidator() { event := events[0] suite.Require().Equal(types.EventTypeIncentivesRewards, event.Type) suite.Require().Len(event.Attributes, 2) - suite.Require().Equal(sdk.AttributeKeyAmount, string(event.Attributes[0].Key)) - suite.Require().Equal(tokens.String(), string(event.Attributes[0].Value)) - suite.Require().Equal(types.AttributeKeyValidator, string(event.Attributes[1].Key)) - suite.Require().Equal(valAddr.String(), string(event.Attributes[1].Value)) + suite.Require().Equal(sdk.AttributeKeyAmount, event.Attributes[0].Key) + suite.Require().Equal(tokens.String(), event.Attributes[0].Value) + suite.Require().Equal(types.AttributeKeyValidator, event.Attributes[1].Key) + suite.Require().Equal(valAddr.String(), event.Attributes[1].Value) } func (suite *KeeperTestSuite) TestAllocateTokensToValidatorWithExistingRewards() { @@ -275,18 +274,17 @@ func (suite *KeeperTestSuite) TestAllocateTokensToValidatorWithExistingRewards() event := events[0] suite.Require().Equal(types.EventTypeIncentivesRewards, event.Type) suite.Require().Len(event.Attributes, 2) - suite.Require().Equal(sdk.AttributeKeyAmount, string(event.Attributes[0].Key)) - suite.Require().Equal(newTokens.String(), string(event.Attributes[0].Value)) - suite.Require().Equal(types.AttributeKeyValidator, string(event.Attributes[1].Key)) - suite.Require().Equal(valAddr.String(), string(event.Attributes[1].Value)) + suite.Require().Equal(sdk.AttributeKeyAmount, event.Attributes[0].Key) + suite.Require().Equal(newTokens.String(), event.Attributes[0].Value) + suite.Require().Equal(types.AttributeKeyValidator, event.Attributes[1].Key) + suite.Require().Equal(valAddr.String(), event.Attributes[1].Value) } func (suite *KeeperTestSuite) TestAllocateTokens() { ctx, incentivesKeeper := suite.ctx, suite.incentivesKeeper // Create mock validators - validators, err := getMockValidators(suite) - suite.Require().NoError(err) + validators := suite.getMockValidators() validator1, validator2, validator3 := validators[0], validators[1], validators[2] // Set up qualifying voters @@ -336,9 +334,9 @@ func (suite *KeeperTestSuite) TestAllocateTokens() { for i, event := range events { suite.Require().Equal(types.EventTypeIncentivesRewards, event.Type) suite.Require().Len(event.Attributes, 2) - suite.Require().Equal(sdk.AttributeKeyAmount, string(event.Attributes[0].Key)) - suite.Require().Equal(types.AttributeKeyValidator, string(event.Attributes[1].Key)) - suite.Require().Equal(qualifyingVoters[i].Validator.GetOperator().String(), string(event.Attributes[1].Value)) + suite.Require().Equal(sdk.AttributeKeyAmount, event.Attributes[0].Key) + suite.Require().Equal(types.AttributeKeyValidator, event.Attributes[1].Key) + suite.Require().Equal(qualifyingVoters[i].Validator.GetOperator().String(), event.Attributes[1].Value) } } From ea8bb246d2ed127c225a92fef6fe204253f45de1 Mon Sep 17 00:00:00 2001 From: Collin Brittain Date: Mon, 21 Oct 2024 12:01:39 -0500 Subject: [PATCH 8/8] Param name change for clarity. Add event for total val incentive reward per block --- integration_tests/genesis.go | 2 +- integration_tests/incentives_test.go | 2 +- proto/incentives/v1/genesis.proto | 4 +- x/incentives/keeper/abci.go | 4 +- x/incentives/keeper/abci_test.go | 8 +-- x/incentives/keeper/incentives.go | 12 ++++- x/incentives/keeper/incentives_test.go | 23 +++++--- x/incentives/types/errors.go | 8 +-- x/incentives/types/events.go | 3 +- x/incentives/types/genesis.pb.go | 74 +++++++++++++------------- x/incentives/types/params.go | 28 +++++----- 11 files changed, 93 insertions(+), 75 deletions(-) diff --git a/integration_tests/genesis.go b/integration_tests/genesis.go index 698259b46..062cee700 100644 --- a/integration_tests/genesis.go +++ b/integration_tests/genesis.go @@ -119,7 +119,7 @@ func (s *IntegrationTestSuite) setIncentivesGenState(appGenState map[string]json } incentivesGenState.Params.ValidatorIncentivesCutoffHeight = 0 - incentivesGenState.Params.ValidatorDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(0)) + incentivesGenState.Params.ValidatorMaxDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(0)) appGenState[incentivestypes.ModuleName] = cdc.MustMarshalJSON(&incentivesGenState) return nil diff --git a/integration_tests/incentives_test.go b/integration_tests/incentives_test.go index 83b08a716..495c2a228 100644 --- a/integration_tests/incentives_test.go +++ b/integration_tests/incentives_test.go @@ -290,7 +290,7 @@ func (s *IntegrationTestSuite) TestValidatorIncentives() { }, { Subspace: "incentives", - Key: "ValidatorDistributionPerBlock", + Key: "ValidatorMaxDistributionPerBlock", Value: fmt.Sprintf("{\"denom\":\"%s\",\"amount\":\"%d\"}", params.BaseCoinUnit, 1000000), }, }, diff --git a/proto/incentives/v1/genesis.proto b/proto/incentives/v1/genesis.proto index 707a0863f..c422c4787 100644 --- a/proto/incentives/v1/genesis.proto +++ b/proto/incentives/v1/genesis.proto @@ -18,8 +18,8 @@ message Params { // IncentivesCutoffHeight defines the block height after which the incentives module will stop sending coins to the distribution module from // the community pool uint64 incentives_cutoff_height = 2; - // ValidatorDistributionPerBlock defines the coin to be sent to the distribution module from the community pool every block - cosmos.base.v1beta1.Coin validator_distribution_per_block = 3 [(gogoproto.nullable) = false]; + // ValidatorMaxDistributionPerBlock defines the maximum coins to be sent directly to voters in the last block from the community pool every block. Leftover coins remain in the community pool. + cosmos.base.v1beta1.Coin validator_max_distribution_per_block = 3 [(gogoproto.nullable) = false]; // ValidatorIncentivesCutoffHeight defines the block height after which the validator incentives will be stopped uint64 validator_incentives_cutoff_height = 4; // ValidatorIncentivesMaxFraction defines the maximum fraction of the validator distribution per block that can be sent to a single validator diff --git a/x/incentives/keeper/abci.go b/x/incentives/keeper/abci.go index deffc20e4..2290da606 100644 --- a/x/incentives/keeper/abci.go +++ b/x/incentives/keeper/abci.go @@ -16,12 +16,12 @@ import ( // 4) Add the remaining coins back to the community pool func (k Keeper) BeginBlocker(ctx sdk.Context, req abci.RequestBeginBlock) { incentivesParams := k.GetParamSet(ctx) - if uint64(ctx.BlockHeight()) >= incentivesParams.ValidatorIncentivesCutoffHeight || incentivesParams.ValidatorDistributionPerBlock.IsZero() { + if uint64(ctx.BlockHeight()) >= incentivesParams.ValidatorIncentivesCutoffHeight || incentivesParams.ValidatorMaxDistributionPerBlock.IsZero() { return } // Rewards come from the community pool - totalDistribution := sdk.NewDecCoinsFromCoins(incentivesParams.ValidatorDistributionPerBlock) + totalDistribution := sdk.NewDecCoinsFromCoins(incentivesParams.ValidatorMaxDistributionPerBlock) feePool := k.DistributionKeeper.GetFeePool(ctx) newPool, negative := feePool.CommunityPool.SafeSub(totalDistribution) if negative { diff --git a/x/incentives/keeper/abci_test.go b/x/incentives/keeper/abci_test.go index 1bf7edb18..0ecbbb64c 100644 --- a/x/incentives/keeper/abci_test.go +++ b/x/incentives/keeper/abci_test.go @@ -65,7 +65,7 @@ func (suite *KeeperTestSuite) TestBeginBlockerIncentivesDisabled() { incentivesParams := incentivesTypes.DefaultParams() incentivesParams.ValidatorIncentivesCutoffHeight = 100 - incentivesParams.ValidatorDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(1000)) + incentivesParams.ValidatorMaxDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(1000)) incentivesKeeper.SetParams(ctx, incentivesParams) // Set block height above cutoff @@ -76,7 +76,7 @@ func (suite *KeeperTestSuite) TestBeginBlockerIncentivesDisabled() { require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, abci.RequestBeginBlock{}) }) incentivesParams.ValidatorIncentivesCutoffHeight = 200 - incentivesParams.ValidatorDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.ZeroInt()) + incentivesParams.ValidatorMaxDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.ZeroInt()) incentivesKeeper.SetParams(ctx, incentivesParams) require.NotPanics(func() { incentivesKeeper.BeginBlocker(ctx, abci.RequestBeginBlock{}) }) @@ -88,7 +88,7 @@ func (suite *KeeperTestSuite) TestBeginBlockerInsufficientCommunityPoolBalance() incentivesParams := incentivesTypes.DefaultParams() incentivesParams.ValidatorIncentivesCutoffHeight = 100 - incentivesParams.ValidatorDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(1000)) + incentivesParams.ValidatorMaxDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(1000)) incentivesKeeper.SetParams(ctx, incentivesParams) // Set block height below cutoff @@ -111,7 +111,7 @@ func (suite *KeeperTestSuite) TestBeginBlockerSuccess() { incentivesParams := incentivesTypes.DefaultParams() incentivesParams.ValidatorIncentivesCutoffHeight = 100 - incentivesParams.ValidatorDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(1000)) + incentivesParams.ValidatorMaxDistributionPerBlock = sdk.NewCoin(params.BaseCoinUnit, sdk.NewInt(1000)) incentivesKeeper.SetParams(ctx, incentivesParams) // Set block height below cutoff diff --git a/x/incentives/keeper/incentives.go b/x/incentives/keeper/incentives.go index 30d75ee3e..f8d01f355 100644 --- a/x/incentives/keeper/incentives.go +++ b/x/incentives/keeper/incentives.go @@ -80,6 +80,16 @@ func (k Keeper) AllocateTokens(ctx sdk.Context, totalPreviousPower int64, totalD remaining = remaining.Sub(reward) } + // Only emit the total distribution reward event if there are qualifying voters + if len(qualifyingVoters) > 0 { + ctx.EventManager().EmitEvent( + sdk.NewEvent( + types.EventTypeTotalValidatorIncentivesRewards, + sdk.NewAttribute(sdk.AttributeKeyAmount, totalDistribution.Sub(remaining).String()), + ), + ) + } + return remaining } @@ -89,7 +99,7 @@ func (k Keeper) AllocateTokensToValidator(ctx sdk.Context, val stakingtypes.Vali // Update validator rewards ctx.EventManager().EmitEvent( sdk.NewEvent( - types.EventTypeIncentivesRewards, + types.EventTypeValidatorIncentivesReward, sdk.NewAttribute(sdk.AttributeKeyAmount, tokens.String()), sdk.NewAttribute(types.AttributeKeyValidator, val.GetOperator().String()), ), diff --git a/x/incentives/keeper/incentives_test.go b/x/incentives/keeper/incentives_test.go index fdef40030..480c0bbf5 100644 --- a/x/incentives/keeper/incentives_test.go +++ b/x/incentives/keeper/incentives_test.go @@ -229,7 +229,7 @@ func (suite *KeeperTestSuite) TestAllocateTokensToValidator() { events := ctx.EventManager().Events() suite.Require().Len(events, 1) event := events[0] - suite.Require().Equal(types.EventTypeIncentivesRewards, event.Type) + suite.Require().Equal(types.EventTypeValidatorIncentivesReward, event.Type) suite.Require().Len(event.Attributes, 2) suite.Require().Equal(sdk.AttributeKeyAmount, event.Attributes[0].Key) suite.Require().Equal(tokens.String(), event.Attributes[0].Value) @@ -272,7 +272,7 @@ func (suite *KeeperTestSuite) TestAllocateTokensToValidatorWithExistingRewards() events := ctx.EventManager().Events() suite.Require().Len(events, 1) event := events[0] - suite.Require().Equal(types.EventTypeIncentivesRewards, event.Type) + suite.Require().Equal(types.EventTypeValidatorIncentivesReward, event.Type) suite.Require().Len(event.Attributes, 2) suite.Require().Equal(sdk.AttributeKeyAmount, event.Attributes[0].Key) suite.Require().Equal(newTokens.String(), event.Attributes[0].Value) @@ -329,14 +329,21 @@ func (suite *KeeperTestSuite) TestAllocateTokens() { suite.Require().Equal(totalDistribution, totalAllocated, "Sum of remaining and distributed rewards should equal total distribution") // Verify that events were emitted + totalEvents := len(qualifyingVoters) + 1 // One event for each validator plus one for the total distribution events := ctx.EventManager().Events() - suite.Require().Len(events, 3) // One event for each validator + suite.Require().Len(events, totalEvents) for i, event := range events { - suite.Require().Equal(types.EventTypeIncentivesRewards, event.Type) - suite.Require().Len(event.Attributes, 2) - suite.Require().Equal(sdk.AttributeKeyAmount, event.Attributes[0].Key) - suite.Require().Equal(types.AttributeKeyValidator, event.Attributes[1].Key) - suite.Require().Equal(qualifyingVoters[i].Validator.GetOperator().String(), event.Attributes[1].Value) + if i == totalEvents-1 { + suite.Require().Equal(types.EventTypeTotalValidatorIncentivesRewards, event.Type) + suite.Require().Equal(totalDistribution.Sub(remaining).String(), event.Attributes[0].Value) + continue + } else { + suite.Require().Equal(types.EventTypeValidatorIncentivesReward, event.Type) + suite.Require().Len(event.Attributes, 2) + suite.Require().Equal(sdk.AttributeKeyAmount, event.Attributes[0].Key) + suite.Require().Equal(types.AttributeKeyValidator, event.Attributes[1].Key) + suite.Require().Equal(qualifyingVoters[i].Validator.GetOperator().String(), event.Attributes[1].Value) + } } } diff --git a/x/incentives/types/errors.go b/x/incentives/types/errors.go index 661ec7a0f..4677d4971 100644 --- a/x/incentives/types/errors.go +++ b/x/incentives/types/errors.go @@ -5,8 +5,8 @@ import ( ) var ( - ErrInvalidDistributionPerBlock = errorsmod.Register(ModuleName, 1, "invalid distribution per block") - ErrInvalidValidatorDistributionPerBlock = errorsmod.Register(ModuleName, 2, "invalid validator distribution per block") - ErrInvalidValidatorIncentivesMaxFraction = errorsmod.Register(ModuleName, 3, "invalid validator incentives max fraction") - ErrInvalidValidatorIncentivesSetSizeLimit = errorsmod.Register(ModuleName, 4, "invalid validator incentives set size limit") + ErrInvalidDistributionPerBlock = errorsmod.Register(ModuleName, 1, "invalid distribution per block") + ErrInvalidValidatorMaxDistributionPerBlock = errorsmod.Register(ModuleName, 2, "invalid validator distribution per block") + ErrInvalidValidatorIncentivesMaxFraction = errorsmod.Register(ModuleName, 3, "invalid validator incentives max fraction") + ErrInvalidValidatorIncentivesSetSizeLimit = errorsmod.Register(ModuleName, 4, "invalid validator incentives set size limit") ) diff --git a/x/incentives/types/events.go b/x/incentives/types/events.go index 94ea9909b..09cecfb71 100644 --- a/x/incentives/types/events.go +++ b/x/incentives/types/events.go @@ -1,7 +1,8 @@ package types const ( - EventTypeIncentivesRewards = "incentives_rewards" + EventTypeValidatorIncentivesReward = "validator_incentives_reward" + EventTypeTotalValidatorIncentivesRewards = "total_validator_incentives_rewards" AttributeKeyValidator = "validator" ) diff --git a/x/incentives/types/genesis.pb.go b/x/incentives/types/genesis.pb.go index a4f56505f..febfc9bfb 100644 --- a/x/incentives/types/genesis.pb.go +++ b/x/incentives/types/genesis.pb.go @@ -76,8 +76,8 @@ type Params struct { // IncentivesCutoffHeight defines the block height after which the incentives module will stop sending coins to the distribution module from // the community pool IncentivesCutoffHeight uint64 `protobuf:"varint,2,opt,name=incentives_cutoff_height,json=incentivesCutoffHeight,proto3" json:"incentives_cutoff_height,omitempty"` - // ValidatorDistributionPerBlock defines the coin to be sent to the distribution module from the community pool every block - ValidatorDistributionPerBlock types.Coin `protobuf:"bytes,3,opt,name=validator_distribution_per_block,json=validatorDistributionPerBlock,proto3" json:"validator_distribution_per_block"` + // ValidatorMaxDistributionPerBlock defines the maximum coins to be sent directly to voters in the last block from the community pool every block. Leftover coins remain in the community pool. + ValidatorMaxDistributionPerBlock types.Coin `protobuf:"bytes,3,opt,name=validator_max_distribution_per_block,json=validatorMaxDistributionPerBlock,proto3" json:"validator_max_distribution_per_block"` // ValidatorIncentivesCutoffHeight defines the block height after which the validator incentives will be stopped ValidatorIncentivesCutoffHeight uint64 `protobuf:"varint,4,opt,name=validator_incentives_cutoff_height,json=validatorIncentivesCutoffHeight,proto3" json:"validator_incentives_cutoff_height,omitempty"` // ValidatorIncentivesMaxFraction defines the maximum fraction of the validator distribution per block that can be sent to a single validator @@ -133,9 +133,9 @@ func (m *Params) GetIncentivesCutoffHeight() uint64 { return 0 } -func (m *Params) GetValidatorDistributionPerBlock() types.Coin { +func (m *Params) GetValidatorMaxDistributionPerBlock() types.Coin { if m != nil { - return m.ValidatorDistributionPerBlock + return m.ValidatorMaxDistributionPerBlock } return types.Coin{} } @@ -162,35 +162,35 @@ func init() { func init() { proto.RegisterFile("incentives/v1/genesis.proto", fileDescriptor_179cfb82d3e2b395) } var fileDescriptor_179cfb82d3e2b395 = []byte{ - // 435 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x92, 0x4d, 0x8b, 0xd3, 0x40, - 0x1c, 0xc6, 0x1b, 0xad, 0x05, 0x47, 0xbd, 0x84, 0x75, 0x89, 0x2b, 0x4e, 0x6b, 0x05, 0xe9, 0xc5, - 0x19, 0xba, 0x7b, 0xd0, 0x73, 0xbb, 0xf8, 0x82, 0x15, 0x96, 0x16, 0x2f, 0x5e, 0x86, 0xc9, 0xf4, - 0xdf, 0xf4, 0xef, 0x36, 0x99, 0x30, 0x33, 0x0d, 0xed, 0x7e, 0x0a, 0xaf, 0x7e, 0xa3, 0x3d, 0xee, - 0x51, 0x3c, 0x2c, 0xd2, 0x7e, 0x11, 0xc9, 0x0b, 0xdb, 0x0a, 0x29, 0x78, 0x4a, 0xe0, 0xf7, 0xcc, - 0xf3, 0xfc, 0x42, 0x86, 0x3c, 0xc7, 0x44, 0x41, 0xe2, 0x30, 0x03, 0xcb, 0xb3, 0x3e, 0x8f, 0x20, - 0x01, 0x8b, 0x96, 0xa5, 0x46, 0x3b, 0xed, 0x3f, 0xd9, 0x41, 0x96, 0xf5, 0x4f, 0x8e, 0x22, 0x1d, - 0xe9, 0x82, 0xf0, 0xfc, 0xad, 0x0c, 0x9d, 0x50, 0xa5, 0x6d, 0xac, 0x2d, 0x0f, 0xa5, 0x05, 0x9e, - 0xf5, 0x43, 0x70, 0xb2, 0xcf, 0x95, 0xc6, 0xa4, 0xe4, 0xdd, 0x21, 0x79, 0xfc, 0xa1, 0x6c, 0x9d, - 0x38, 0xe9, 0xc0, 0x3f, 0x23, 0xad, 0x54, 0x1a, 0x19, 0xdb, 0xc0, 0xeb, 0x78, 0xbd, 0x47, 0xa7, - 0x4f, 0xd9, 0x3f, 0x2b, 0xec, 0xa2, 0x80, 0x83, 0xe6, 0xf5, 0x6d, 0xbb, 0x31, 0xae, 0xa2, 0xdd, - 0x9f, 0x4d, 0xd2, 0x2a, 0x81, 0xff, 0x95, 0x1c, 0x4f, 0xd1, 0x3a, 0x83, 0xe1, 0xd2, 0xa1, 0x4e, - 0x44, 0x0a, 0x46, 0x84, 0x0b, 0xad, 0x2e, 0xab, 0xbe, 0x67, 0xac, 0x14, 0x62, 0xb9, 0x10, 0xab, - 0x84, 0xd8, 0x50, 0x63, 0x52, 0x75, 0x1e, 0xed, 0x1f, 0xbf, 0x00, 0x33, 0xc8, 0x0f, 0xfb, 0xef, - 0x48, 0xb0, 0xf3, 0x10, 0x6a, 0xe9, 0xf4, 0x6c, 0x26, 0xe6, 0x80, 0xd1, 0xdc, 0x05, 0xf7, 0x3a, - 0x5e, 0xaf, 0x39, 0x3e, 0xde, 0xf1, 0x61, 0x81, 0x3f, 0x16, 0xd4, 0x9f, 0x93, 0x4e, 0x26, 0x17, - 0x38, 0x95, 0x4e, 0x1b, 0x71, 0x40, 0xed, 0xfe, 0xff, 0xa9, 0xbd, 0xb8, 0x2b, 0x3a, 0xaf, 0x73, - 0xfc, 0x4c, 0xba, 0xbb, 0xa5, 0x83, 0xb6, 0xcd, 0xc2, 0xb6, 0x7d, 0x97, 0xfc, 0x54, 0xaf, 0xbd, - 0x26, 0x2f, 0x6b, 0xcb, 0x62, 0xb9, 0x12, 0x33, 0x23, 0x55, 0xbe, 0x1c, 0x3c, 0xe8, 0x78, 0xbd, - 0x87, 0x03, 0x96, 0xcb, 0xfd, 0xbe, 0x6d, 0xbf, 0x8e, 0xd0, 0xcd, 0x97, 0x21, 0x53, 0x3a, 0xe6, - 0xd5, 0x5f, 0x2f, 0x1f, 0x6f, 0xec, 0xf4, 0x92, 0xbb, 0x75, 0x0a, 0x96, 0x9d, 0x83, 0x1a, 0xd3, - 0x9a, 0xed, 0x2f, 0x72, 0xf5, 0xbe, 0x6a, 0xf5, 0x47, 0xe4, 0x55, 0xed, 0xb4, 0x05, 0x27, 0x2c, - 0x5e, 0x81, 0x58, 0x60, 0x8c, 0x2e, 0x68, 0x1d, 0xfc, 0x90, 0x09, 0xb8, 0x09, 0x5e, 0xc1, 0x28, - 0x8f, 0x0d, 0x46, 0xd7, 0x1b, 0xea, 0xdd, 0x6c, 0xa8, 0xf7, 0x67, 0x43, 0xbd, 0x1f, 0x5b, 0xda, - 0xb8, 0xd9, 0xd2, 0xc6, 0xaf, 0x2d, 0x6d, 0x7c, 0x3b, 0xdd, 0xf3, 0x4d, 0x21, 0x8a, 0xd6, 0xdf, - 0x33, 0x6e, 0x75, 0x1c, 0xc3, 0x02, 0xc1, 0xf0, 0xec, 0x2d, 0x5f, 0xf1, 0xbd, 0xeb, 0x5f, 0xf8, - 0x87, 0xad, 0xe2, 0xd6, 0x9e, 0xfd, 0x0d, 0x00, 0x00, 0xff, 0xff, 0x21, 0x51, 0x24, 0xd4, 0x19, - 0x03, 0x00, 0x00, + // 442 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x8c, 0x93, 0x4d, 0x8b, 0xd3, 0x40, + 0x18, 0xc7, 0x1b, 0xad, 0x05, 0x47, 0xbd, 0x84, 0x75, 0x89, 0x2b, 0xa4, 0xb5, 0x8a, 0xf4, 0xe2, + 0x0c, 0xdd, 0x3d, 0xe8, 0xb9, 0x5d, 0x7c, 0xc1, 0x2e, 0x2c, 0x2d, 0x5e, 0xbc, 0x0c, 0x93, 0xe9, + 0xd3, 0xf4, 0x71, 0x9b, 0x4c, 0x98, 0x99, 0x86, 0x76, 0x3f, 0x85, 0x1f, 0xc1, 0x8f, 0xb3, 0xc7, + 0x3d, 0x8a, 0x87, 0x45, 0xda, 0x2f, 0x22, 0x79, 0xa1, 0xa9, 0x90, 0xc2, 0x9e, 0x12, 0xf8, 0x3d, + 0xf3, 0xff, 0xff, 0x1e, 0x92, 0x21, 0x2f, 0x31, 0x96, 0x10, 0x5b, 0x4c, 0xc1, 0xb0, 0xb4, 0xcf, + 0x42, 0x88, 0xc1, 0xa0, 0xa1, 0x89, 0x56, 0x56, 0xb9, 0xcf, 0x2a, 0x48, 0xd3, 0xfe, 0xc9, 0x51, + 0xa8, 0x42, 0x95, 0x13, 0x96, 0xbd, 0x15, 0x43, 0x27, 0xbe, 0x54, 0x26, 0x52, 0x86, 0x05, 0xc2, + 0x00, 0x4b, 0xfb, 0x01, 0x58, 0xd1, 0x67, 0x52, 0x61, 0x5c, 0xf0, 0xee, 0x90, 0x3c, 0xfd, 0x54, + 0xa4, 0x4e, 0xac, 0xb0, 0xe0, 0x9e, 0x91, 0x56, 0x22, 0xb4, 0x88, 0x8c, 0xe7, 0x74, 0x9c, 0xde, + 0x93, 0xd3, 0xe7, 0xf4, 0xbf, 0x16, 0x7a, 0x99, 0xc3, 0x41, 0xf3, 0xe6, 0xae, 0xdd, 0x18, 0x97, + 0xa3, 0xdd, 0x5f, 0x4d, 0xd2, 0x2a, 0x80, 0xfb, 0x8d, 0x1c, 0x4f, 0xd1, 0x58, 0x8d, 0xc1, 0xd2, + 0xa2, 0x8a, 0x79, 0x02, 0x9a, 0x07, 0x0b, 0x25, 0xaf, 0xca, 0xbc, 0x17, 0xb4, 0x10, 0xa2, 0x99, + 0x10, 0x2d, 0x85, 0xe8, 0x50, 0x61, 0x5c, 0x66, 0x1e, 0xed, 0x1f, 0xbf, 0x04, 0x3d, 0xc8, 0x0e, + 0xbb, 0x1f, 0x88, 0x57, 0x79, 0x70, 0xb9, 0xb4, 0x6a, 0x36, 0xe3, 0x73, 0xc0, 0x70, 0x6e, 0xbd, + 0x07, 0x1d, 0xa7, 0xd7, 0x1c, 0x1f, 0x57, 0x7c, 0x98, 0xe3, 0xcf, 0x39, 0x75, 0x15, 0x79, 0x93, + 0x8a, 0x05, 0x4e, 0x85, 0x55, 0x9a, 0x47, 0x62, 0xc5, 0x0f, 0xe8, 0x3d, 0xbc, 0x9f, 0x5e, 0x67, + 0x17, 0x76, 0x21, 0x56, 0xe7, 0x75, 0xaa, 0x5f, 0x49, 0xb7, 0x2a, 0x3c, 0x28, 0xdd, 0xcc, 0xa5, + 0xdb, 0xbb, 0xc9, 0x2f, 0xf5, 0xf6, 0x6b, 0xf2, 0xaa, 0x36, 0x2c, 0x5b, 0x64, 0xa6, 0x85, 0xcc, + 0x9a, 0xbd, 0x47, 0x1d, 0xa7, 0xf7, 0x78, 0x40, 0x33, 0xbf, 0x3f, 0x77, 0xed, 0xb7, 0x21, 0xda, + 0xf9, 0x32, 0xa0, 0x52, 0x45, 0xac, 0xfc, 0xf8, 0xc5, 0xe3, 0x9d, 0x99, 0x5e, 0x31, 0xbb, 0x4e, + 0xc0, 0xd0, 0x73, 0x90, 0x63, 0xbf, 0xa6, 0xfb, 0x42, 0xac, 0x3e, 0x96, 0xa9, 0xee, 0x88, 0xbc, + 0xae, 0xad, 0x36, 0x60, 0xb9, 0xc1, 0x6b, 0xe0, 0x0b, 0x8c, 0xd0, 0x7a, 0xad, 0x83, 0x8b, 0x4c, + 0xc0, 0x4e, 0xf0, 0x1a, 0x46, 0xd9, 0xd8, 0x60, 0x74, 0xb3, 0xf1, 0x9d, 0xdb, 0x8d, 0xef, 0xfc, + 0xdd, 0xf8, 0xce, 0xcf, 0xad, 0xdf, 0xb8, 0xdd, 0xfa, 0x8d, 0xdf, 0x5b, 0xbf, 0xf1, 0xfd, 0x74, + 0xcf, 0x37, 0x81, 0x30, 0x5c, 0xff, 0x48, 0x99, 0x51, 0x51, 0x04, 0x0b, 0x04, 0xcd, 0xd2, 0xf7, + 0x6c, 0xc5, 0xf6, 0x6e, 0x41, 0xee, 0x1f, 0xb4, 0xf2, 0x9f, 0xf7, 0xec, 0x5f, 0x00, 0x00, 0x00, + 0xff, 0xff, 0x32, 0x0e, 0x87, 0xd4, 0x20, 0x03, 0x00, 0x00, } func (m *GenesisState) Marshal() (dAtA []byte, err error) { @@ -267,7 +267,7 @@ func (m *Params) MarshalToSizedBuffer(dAtA []byte) (int, error) { dAtA[i] = 0x20 } { - size, err := m.ValidatorDistributionPerBlock.MarshalToSizedBuffer(dAtA[:i]) + size, err := m.ValidatorMaxDistributionPerBlock.MarshalToSizedBuffer(dAtA[:i]) if err != nil { return 0, err } @@ -327,7 +327,7 @@ func (m *Params) Size() (n int) { if m.IncentivesCutoffHeight != 0 { n += 1 + sovGenesis(uint64(m.IncentivesCutoffHeight)) } - l = m.ValidatorDistributionPerBlock.Size() + l = m.ValidatorMaxDistributionPerBlock.Size() n += 1 + l + sovGenesis(uint64(l)) if m.ValidatorIncentivesCutoffHeight != 0 { n += 1 + sovGenesis(uint64(m.ValidatorIncentivesCutoffHeight)) @@ -512,7 +512,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { } case 3: if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ValidatorDistributionPerBlock", wireType) + return fmt.Errorf("proto: wrong wireType = %d for field ValidatorMaxDistributionPerBlock", wireType) } var msglen int for shift := uint(0); ; shift += 7 { @@ -539,7 +539,7 @@ func (m *Params) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - if err := m.ValidatorDistributionPerBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + if err := m.ValidatorMaxDistributionPerBlock.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } iNdEx = postIndex diff --git a/x/incentives/types/params.go b/x/incentives/types/params.go index aaa63676d..9927a1383 100644 --- a/x/incentives/types/params.go +++ b/x/incentives/types/params.go @@ -9,12 +9,12 @@ import ( // Parameter keys var ( - KeyDistributionPerBlock = []byte("DistributionPerBlock") - KeyIncentivesCutoffHeight = []byte("IncentivesCutoffHeight") - KeyValidatorDistributionPerBlock = []byte("ValidatorDistributionPerBlock") - KeyValidatorIncentivesCutoffHeight = []byte("ValidatorIncentivesCutoffHeight") - KeyValidatorIncentivesMaxFraction = []byte("ValidatorIncentivesMaxFraction") - KeyValidatorIncentivesSetSizeLimit = []byte("ValidatorIncentivesSetSizeLimit") + KeyDistributionPerBlock = []byte("DistributionPerBlock") + KeyIncentivesCutoffHeight = []byte("IncentivesCutoffHeight") + KeyValidatorMaxDistributionPerBlock = []byte("ValidatorMaxDistributionPerBlock") + KeyValidatorIncentivesCutoffHeight = []byte("ValidatorIncentivesCutoffHeight") + KeyValidatorIncentivesMaxFraction = []byte("ValidatorIncentivesMaxFraction") + KeyValidatorIncentivesSetSizeLimit = []byte("ValidatorIncentivesSetSizeLimit") ) var _ paramtypes.ParamSet = &Params{} @@ -29,11 +29,11 @@ func DefaultParams() Params { return Params{ DistributionPerBlock: sdk.NewCoin(params.BaseCoinUnit, sdk.ZeroInt()), // Anything lower than or equal to current height is "off" - IncentivesCutoffHeight: 0, - ValidatorDistributionPerBlock: sdk.NewCoin(params.BaseCoinUnit, sdk.ZeroInt()), - ValidatorIncentivesCutoffHeight: 0, - ValidatorIncentivesMaxFraction: sdk.MustNewDecFromStr("0.1"), - ValidatorIncentivesSetSizeLimit: 50, + IncentivesCutoffHeight: 0, + ValidatorMaxDistributionPerBlock: sdk.NewCoin(params.BaseCoinUnit, sdk.ZeroInt()), + ValidatorIncentivesCutoffHeight: 0, + ValidatorIncentivesMaxFraction: sdk.MustNewDecFromStr("0.1"), + ValidatorIncentivesSetSizeLimit: 50, } } @@ -42,7 +42,7 @@ func (p *Params) ParamSetPairs() paramtypes.ParamSetPairs { return paramtypes.ParamSetPairs{ paramtypes.NewParamSetPair(KeyDistributionPerBlock, &p.DistributionPerBlock, validateDistributionPerBlock), paramtypes.NewParamSetPair(KeyIncentivesCutoffHeight, &p.IncentivesCutoffHeight, validateIncentivesCutoffHeight), - paramtypes.NewParamSetPair(KeyValidatorDistributionPerBlock, &p.ValidatorDistributionPerBlock, validateValidatorDistributionPerBlock), + paramtypes.NewParamSetPair(KeyValidatorMaxDistributionPerBlock, &p.ValidatorMaxDistributionPerBlock, validateValidatorMaxDistributionPerBlock), paramtypes.NewParamSetPair(KeyValidatorIncentivesCutoffHeight, &p.ValidatorIncentivesCutoffHeight, validateValidatorIncentivesCutoffHeight), paramtypes.NewParamSetPair(KeyValidatorIncentivesMaxFraction, &p.ValidatorIncentivesMaxFraction, validateValidatorIncentivesMaxFraction), paramtypes.NewParamSetPair(KeyValidatorIncentivesSetSizeLimit, &p.ValidatorIncentivesSetSizeLimit, validateValidatorIncentivesSetSizeLimit), @@ -82,9 +82,9 @@ func validateIncentivesCutoffHeight(_ interface{}) error { return nil } -func validateValidatorDistributionPerBlock(i interface{}) error { +func validateValidatorMaxDistributionPerBlock(i interface{}) error { if err := validateDistributionPerBlock(i); err != nil { - return errorsmod.Wrapf(ErrInvalidValidatorDistributionPerBlock, "invalid parameter type: %T", i) + return errorsmod.Wrapf(ErrInvalidValidatorMaxDistributionPerBlock, "invalid parameter type: %T", i) } return nil