diff --git a/x/emissions/module/rewards/reputer_rewards_test.go b/x/emissions/module/rewards/reputer_rewards_test.go index 2b497f4dc..56d03010d 100644 --- a/x/emissions/module/rewards/reputer_rewards_test.go +++ b/x/emissions/module/rewards/reputer_rewards_test.go @@ -2,6 +2,7 @@ package rewards_test import ( cosmosMath "cosmossdk.io/math" + "github.com/allora-network/allora-chain/app/params" alloraMath "github.com/allora-network/allora-chain/math" "github.com/allora-network/allora-chain/x/emissions/module/rewards" "github.com/allora-network/allora-chain/x/emissions/types" @@ -63,6 +64,63 @@ func (s *RewardsTestSuite) TestGetReputersRewards() { } } +func (s *RewardsTestSuite) TestGetReputersRewardsShouldGenerateRewardsForDelegators() { + topidId := uint64(1) + block := int64(1003) + + reputerAddrs := []sdk.AccAddress{ + s.addrs[0], + s.addrs[1], + s.addrs[2], + s.addrs[3], + s.addrs[4], + } + + // Generate reputers data for tests + _, err := mockReputersData(s, topidId, block, reputerAddrs) + s.Require().NoError(err) + + // Add balance to the reward account + rewardToDistribute := sdk.NewCoins(sdk.NewCoin(params.DefaultBondDenom, cosmosMath.NewInt(100000000))) + s.bankKeeper.MintCoins(s.ctx, types.AlloraRewardsAccountName, rewardToDistribute) + + // Check delegator rewards account balance + moduleAccAddr := s.accountKeeper.GetModuleAddress(types.AlloraPendingRewardForDelegatorAccountName) + inicialBalance := s.bankKeeper.GetBalance(s.ctx, moduleAccAddr, params.DefaultBondDenom) + + // Add delegator for the reputer 1 + err = s.emissionsKeeper.AddDelegateStake(s.ctx, topidId, s.addrs[5], reputerAddrs[0], cosmosMath.NewUint(10000000000)) + s.Require().NoError(err) + + // Reputers fractions of total reward + reputerFractions := []alloraMath.Dec{ + alloraMath.MustNewDecFromString("0.4486159141384113784159014720544400"), + alloraMath.MustNewDecFromString("0.1697311778065889569890976063020783"), + alloraMath.MustNewDecFromString("0.2082807308270030931355055033750957"), + alloraMath.MustNewDecFromString("0.05141234465321950512077936581311225"), + alloraMath.MustNewDecFromString("0.1219598325747770663387160524552737"), + } + + // Get reputer rewards + reputerRewards, err := rewards.GetRewardPerReputer( + s.ctx, + s.emissionsKeeper, + topidId, + alloraMath.MustNewDecFromString("1017.5559072418691"), + reputerAddrs, + reputerFractions, + ) + s.Require().NoError(err) + s.Require().Equal(5, len(reputerRewards)) + + finalBalance := s.bankKeeper.GetBalance(s.ctx, moduleAccAddr, params.DefaultBondDenom) + + // Check that the delegator has received rewards + s.Require().True( + finalBalance.Amount.GT(inicialBalance.Amount), + ) +} + // After removing the number of reputers, the rewards should increase for the remaining reputers func (s *RewardsTestSuite) TestGetReputersRewardsShouldIncreaseRewardsAfterRemovingReputer() { topicId := uint64(1) diff --git a/x/emissions/module/rewards/rewards.go b/x/emissions/module/rewards/rewards.go index 2c7aa0213..a51694811 100644 --- a/x/emissions/module/rewards/rewards.go +++ b/x/emissions/module/rewards/rewards.go @@ -239,8 +239,9 @@ func FilterAndInactivateTopicsUpdatingSums( if err != nil { return nil, alloraMath.Dec{}, cosmosMath.Int{}, errors.Wrapf(err, filterOutErrorMessage) } + } else { + weightsOfActiveTopics[topicId] = weight } - weightsOfActiveTopics[topicId] = weight } return weightsOfActiveTopics, sumWeight, sumRevenue, nil } diff --git a/x/emissions/module/rewards/rewards_test.go b/x/emissions/module/rewards/rewards_test.go index 44ecaf28b..95f3cee4a 100644 --- a/x/emissions/module/rewards/rewards_test.go +++ b/x/emissions/module/rewards/rewards_test.go @@ -119,7 +119,7 @@ func (s *RewardsTestSuite) SetupTest() { pubkey := senderPrivKey.PubKey().Address() // Add coins to account module - s.FaucetAddress(10000000000, sdk.AccAddress(pubkey)) + s.FundAccount(10000000000, sdk.AccAddress(pubkey)) addrs = append(addrs, sdk.AccAddress(pubkey)) addrsStr = append(addrsStr, addrs[i].String()) privKeys[addrsStr[i]] = senderPrivKey @@ -134,7 +134,7 @@ func (s *RewardsTestSuite) SetupTest() { } } -func (s *RewardsTestSuite) FaucetAddress(amount int64, accAddress sdk.AccAddress) { +func (s *RewardsTestSuite) FundAccount(amount int64, accAddress sdk.AccAddress) { initialStakeCoins := sdk.NewCoins(sdk.NewCoin(params.DefaultBondDenom, cosmosMath.NewInt(amount))) s.bankKeeper.MintCoins(s.ctx, types.AlloraStakingAccountName, initialStakeCoins) s.bankKeeper.SendCoinsFromModuleToAccount(s.ctx, types.AlloraStakingAccountName, accAddress, initialStakeCoins) @@ -295,6 +295,273 @@ func (s *RewardsTestSuite) TestStandardRewardEmission() { s.Require().NoError(err) } +func (s *RewardsTestSuite) TestStandardRewardEmissionShouldRewardTopicsWithFulfilledNonces() { + block := int64(600) + s.ctx = s.ctx.WithBlockHeight(block) + + // Reputer Addresses + reputerAddrs := []sdk.AccAddress{ + s.addrs[0], + s.addrs[1], + s.addrs[2], + s.addrs[3], + s.addrs[4], + } + + // Worker Addresses + workerAddrs := []sdk.AccAddress{ + s.addrs[5], + s.addrs[6], + s.addrs[7], + s.addrs[8], + s.addrs[9], + } + + // Create topic + newTopicMsg := &types.MsgCreateNewTopic{ + Creator: reputerAddrs[0].String(), + Metadata: "test", + LossLogic: "logic", + LossMethod: "method", + EpochLength: 10800, + InferenceLogic: "Ilogic", + InferenceMethod: "Imethod", + DefaultArg: "ETH", + AlphaRegret: alloraMath.NewDecFromInt64(10), + PrewardReputer: alloraMath.NewDecFromInt64(11), + PrewardInference: alloraMath.NewDecFromInt64(12), + PrewardForecast: alloraMath.NewDecFromInt64(13), + FTolerance: alloraMath.NewDecFromInt64(14), + } + res, err := s.msgServer.CreateNewTopic(s.ctx, newTopicMsg) + s.Require().NoError(err) + + // Get Topic Id + topicId := res.TopicId + + // Register 5 workers + for _, addr := range workerAddrs { + workerRegMsg := &types.MsgRegister{ + Sender: addr.String(), + LibP2PKey: "test", + MultiAddress: "test", + TopicId: topicId, + IsReputer: false, + Owner: addr.String(), + } + _, err := s.msgServer.Register(s.ctx, workerRegMsg) + s.Require().NoError(err) + } + + // Register 5 reputers + for _, addr := range reputerAddrs { + reputerRegMsg := &types.MsgRegister{ + Sender: addr.String(), + LibP2PKey: "test", + MultiAddress: "test", + TopicId: topicId, + IsReputer: true, + Owner: addr.String(), + } + _, err := s.msgServer.Register(s.ctx, reputerRegMsg) + s.Require().NoError(err) + } + + cosmosOneE18 := inference_synthesis.CosmosUintOneE18() + + // Add Stake for reputers + var stakes = []cosmosMath.Uint{ + cosmosMath.NewUint(1176644).Mul(cosmosOneE18), + cosmosMath.NewUint(384623).Mul(cosmosOneE18), + cosmosMath.NewUint(394676).Mul(cosmosOneE18), + cosmosMath.NewUint(207999).Mul(cosmosOneE18), + cosmosMath.NewUint(368582).Mul(cosmosOneE18), + } + for i, addr := range reputerAddrs { + s.MintTokensToAddress(addr, cosmosMath.NewIntFromBigInt(stakes[i].BigInt())) + _, err := s.msgServer.AddStake(s.ctx, &types.MsgAddStake{ + Sender: addr.String(), + Amount: stakes[i], + TopicId: topicId, + }) + s.Require().NoError(err) + } + + initialStake := cosmosMath.NewInt(1000) + s.MintTokensToAddress(reputerAddrs[0], initialStake) + fundTopicMessage := types.MsgFundTopic{ + Sender: reputerAddrs[0].String(), + TopicId: topicId, + Amount: initialStake, + } + _, err = s.msgServer.FundTopic(s.ctx, &fundTopicMessage) + s.Require().NoError(err) + + // Insert unfullfiled nonces + err = s.emissionsKeeper.AddWorkerNonce(s.ctx, topicId, &types.Nonce{ + BlockHeight: block, + }) + s.Require().NoError(err) + err = s.emissionsKeeper.AddReputerNonce(s.ctx, topicId, &types.Nonce{ + BlockHeight: block, + }, &types.Nonce{ + BlockHeight: block, + }) + s.Require().NoError(err) + + // Insert inference from workers + inferenceBundles := GenerateWorkerDataBundles(s, block, topicId) + _, err = s.msgServer.InsertBulkWorkerPayload(s.ctx, &types.MsgInsertBulkWorkerPayload{ + Sender: workerAddrs[0].String(), + Nonce: &types.Nonce{BlockHeight: block}, + TopicId: topicId, + WorkerDataBundles: inferenceBundles, + }) + s.Require().NoError(err) + + // Insert loss bundle from reputers + lossBundles := GenerateLossBundles(s, block, topicId, reputerAddrs) + _, err = s.msgServer.InsertBulkReputerPayload(s.ctx, &types.MsgInsertBulkReputerPayload{ + Sender: reputerAddrs[0].String(), + TopicId: topicId, + ReputerRequestNonce: &types.ReputerRequestNonce{ + ReputerNonce: &types.Nonce{ + BlockHeight: block, + }, + WorkerNonce: &types.Nonce{ + BlockHeight: block, + }, + }, + ReputerValueBundles: lossBundles.ReputerValueBundles, + }) + s.Require().NoError(err) + + // Create topic 2 + // Reputer Addresses + reputerAddrs = []sdk.AccAddress{ + s.addrs[10], + s.addrs[11], + s.addrs[12], + s.addrs[13], + s.addrs[14], + } + + // Worker Addresses + workerAddrs = []sdk.AccAddress{ + s.addrs[15], + s.addrs[16], + s.addrs[17], + s.addrs[18], + s.addrs[19], + } + + // Create topic + newTopicMsg = &types.MsgCreateNewTopic{ + Creator: reputerAddrs[0].String(), + Metadata: "test", + LossLogic: "logic", + LossMethod: "method", + EpochLength: 10800, + InferenceLogic: "Ilogic", + InferenceMethod: "Imethod", + DefaultArg: "ETH", + AlphaRegret: alloraMath.NewDecFromInt64(10), + PrewardReputer: alloraMath.NewDecFromInt64(11), + PrewardInference: alloraMath.NewDecFromInt64(12), + PrewardForecast: alloraMath.NewDecFromInt64(13), + FTolerance: alloraMath.NewDecFromInt64(14), + } + res, err = s.msgServer.CreateNewTopic(s.ctx, newTopicMsg) + s.Require().NoError(err) + + // Get Topic Id + topicId2 := res.TopicId + + // Register 5 workers + for _, addr := range workerAddrs { + workerRegMsg := &types.MsgRegister{ + Sender: addr.String(), + LibP2PKey: "test", + MultiAddress: "test", + TopicId: topicId2, + IsReputer: false, + Owner: addr.String(), + } + _, err := s.msgServer.Register(s.ctx, workerRegMsg) + s.Require().NoError(err) + } + + // Register 5 reputers + for _, addr := range reputerAddrs { + reputerRegMsg := &types.MsgRegister{ + Sender: addr.String(), + LibP2PKey: "test", + MultiAddress: "test", + TopicId: topicId2, + IsReputer: true, + Owner: addr.String(), + } + _, err := s.msgServer.Register(s.ctx, reputerRegMsg) + s.Require().NoError(err) + } + + for i, addr := range reputerAddrs { + s.MintTokensToAddress(addr, cosmosMath.NewIntFromBigInt(stakes[i].BigInt())) + _, err := s.msgServer.AddStake(s.ctx, &types.MsgAddStake{ + Sender: addr.String(), + Amount: stakes[i], + TopicId: topicId2, + }) + s.Require().NoError(err) + } + + initialStake = cosmosMath.NewInt(1000) + s.MintTokensToAddress(reputerAddrs[0], initialStake) + fundTopicMessage = types.MsgFundTopic{ + Sender: reputerAddrs[0].String(), + TopicId: topicId2, + Amount: initialStake, + } + _, err = s.msgServer.FundTopic(s.ctx, &fundTopicMessage) + s.Require().NoError(err) + + // Insert unfullfiled nonces + err = s.emissionsKeeper.AddWorkerNonce(s.ctx, topicId2, &types.Nonce{ + BlockHeight: block, + }) + s.Require().NoError(err) + err = s.emissionsKeeper.AddReputerNonce(s.ctx, topicId2, &types.Nonce{ + BlockHeight: block, + }, &types.Nonce{ + BlockHeight: block, + }) + s.Require().NoError(err) + + // Do not send bundles for topic 2 yet + + beforeRewardsTopic1FeeRevenue, err := s.emissionsKeeper.GetTopicFeeRevenue(s.ctx, topicId) + s.Require().NoError(err) + beforeRewardsTopic2FeeRevenue, err := s.emissionsKeeper.GetTopicFeeRevenue(s.ctx, topicId2) + s.Require().NoError(err) + + block += 1 + s.ctx = s.ctx.WithBlockHeight(block) + + // Trigger end block - rewards distribution + err = s.appModule.EndBlock(s.ctx) + s.Require().NoError(err) + + afterRewardsTopic1FeeRevenue, err := s.emissionsKeeper.GetTopicFeeRevenue(s.ctx, topicId) + s.Require().NoError(err) + afterRewardsTopic2FeeRevenue, err := s.emissionsKeeper.GetTopicFeeRevenue(s.ctx, topicId2) + s.Require().NoError(err) + + // Topic 1 should have less revenue after rewards distribution -> rewards distributed + s.Require().True(beforeRewardsTopic1FeeRevenue.Revenue.GT(afterRewardsTopic1FeeRevenue.Revenue)) + // Topic 2 should have the same revenue after rewards distribution -> no rewards distributed + s.Require().Equal(beforeRewardsTopic2FeeRevenue.Revenue, afterRewardsTopic2FeeRevenue.Revenue) +} + func (s *RewardsTestSuite) TestGenerateTasksRewardsShouldIncreaseRewardShareIfMoreParticipants() { block := int64(100) s.ctx = s.ctx.WithBlockHeight(block) @@ -382,7 +649,7 @@ func (s *RewardsTestSuite) TestGenerateTasksRewardsShouldIncreaseRewardShareIfMo } var initialStake int64 = 1000 - s.FaucetAddress(initialStake, reputerAddrs[0]) + s.FundAccount(initialStake, reputerAddrs[0]) fundTopicMessage := types.MsgFundTopic{ Sender: reputerAddrs[0].String(), TopicId: topicId, @@ -521,7 +788,7 @@ func (s *RewardsTestSuite) TestGenerateTasksRewardsShouldIncreaseRewardShareIfMo s.Require().NoError(err) } - s.FaucetAddress(initialStake, reputerAddrs[0]) + s.FundAccount(initialStake, reputerAddrs[0]) fundTopicMessage = types.MsgFundTopic{ Sender: reputerAddrs[0].String(), @@ -1229,7 +1496,7 @@ func (s *RewardsTestSuite) TestOnlyFewTopActorsGetReward() { } var initialStake int64 = 1000 - s.FaucetAddress(initialStake, reputerAddrs[0]) + s.FundAccount(initialStake, reputerAddrs[0]) fundTopicMessage := types.MsgFundTopic{ Sender: reputerAddrs[0].String(), @@ -1305,5 +1572,90 @@ func (s *RewardsTestSuite) TestOnlyFewTopActorsGetReward() { s.Require().Equal(len(infererScores), int(params.GetMaxTopWorkersToReward()), "Only few Top workers can get reward") s.Require().Equal(len(forecasterScores), int(params.GetMaxTopWorkersToReward()), "Only few Top workers can get reward") +} +func (s *RewardsTestSuite) TestGenerateRewardsDistributionByTopic() { + topic1Weight := alloraMath.NewDecFromInt64(100) + topic2Weight := alloraMath.NewDecFromInt64(10) + topic3Weight := alloraMath.NewDecFromInt64(500) + topic4Weight := alloraMath.NewDecFromInt64(600) + + // Define topics with varying weights + weights := map[uint64]*alloraMath.Dec{ + 1: &topic1Weight, // Above the minimum weight + 2: &topic2Weight, // Below the minimum weight, should be inactivated + 3: &topic3Weight, // Above the minimum weight + 4: &topic4Weight, // Above the minimum weight, but no reward nonce + } + sumWeight := alloraMath.NewDecFromInt64(1210) + sumRevenue := cosmosMath.NewInt(3000) + totalReward := alloraMath.NewDecFromInt64(1000) + + // Send revenue to requests account + sumRevenueCoins := sdk.NewCoins(sdk.NewCoin(params.DefaultBondDenom, sumRevenue)) + s.bankKeeper.MintCoins(s.ctx, types.AlloraRequestsAccountName, sumRevenueCoins) + + err := s.emissionsKeeper.SetTopicRewardNonce(s.ctx, 1, 1) + s.Require().NoError(err) + err = s.emissionsKeeper.SetTopicRewardNonce(s.ctx, 2, 1) + s.Require().NoError(err) + err = s.emissionsKeeper.SetTopicRewardNonce(s.ctx, 3, 1) + s.Require().NoError(err) + + topicRewards, err := rewards.GenerateRewardsDistributionByTopic( + s.ctx, + s.emissionsKeeper, + 1, // assuming 1 topic per block + 1, + totalReward, + weights, + sumWeight, + sumRevenue, + ) + s.Require().NoError(err) + s.Require().NotNil(topicRewards) + s.Require().Equal(1, len(topicRewards)) +} + +func (s *RewardsTestSuite) TestFilterAndInactivateTopicsUpdatingSums() { + topic1Weight := alloraMath.NewDecFromInt64(100) + topic2Weight := alloraMath.NewDecFromInt64(10) + topic3Weight := alloraMath.NewDecFromInt64(500) + topic4Weight := alloraMath.NewDecFromInt64(600) + + // Define topics with varying weights + weights := map[uint64]*alloraMath.Dec{ + 1: &topic1Weight, // Above the minimum weight + 2: &topic2Weight, // Below the minimum weight, should be inactivated + 3: &topic3Weight, // Above the minimum weight + 4: &topic4Weight, // Above the minimum weight, but no reward nonce + } + sumWeight := alloraMath.NewDecFromInt64(1210) + sumRevenue := cosmosMath.NewInt(3000) + totalReward := alloraMath.NewDecFromInt64(1000) + + err := s.emissionsKeeper.SetTopicRewardNonce(s.ctx, 1, 1) + s.Require().NoError(err) + err = s.emissionsKeeper.SetTopicRewardNonce(s.ctx, 2, 1) + s.Require().NoError(err) + err = s.emissionsKeeper.SetTopicRewardNonce(s.ctx, 3, 1) + s.Require().NoError(err) + + // Test execution + filteredWeights, _, _, err := rewards.FilterAndInactivateTopicsUpdatingSums( + s.ctx, + s.emissionsKeeper, + weights, + sumWeight, + sumRevenue, + totalReward, + 1) + + s.Require().NoError(err) + s.Require().NotNil(filteredWeights) + + s.Require().Equal(len(filteredWeights), 2) + for topicId := range filteredWeights { + s.Require().NotEqual(topicId, uint64(2)) + } }