From 3d2fe44d02af8c0645bad1746bd7119fd7a7b8da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Guilherme=20Brand=C3=A3o?= <37072140+guilherme-brandao@users.noreply.github.com> Date: Wed, 24 Apr 2024 16:41:06 +0100 Subject: [PATCH] Added tests for reward distribution between participants and tasks (#186) Added 2 tests: On the participants' rewards distribution level: If more reputers have the same totalReputerRewards to share, there will be fewer rewards for each reputers. On the task rewards distribution level: If there are more reputers, a higher share of the total rewards will be allocated to reputers. --- .../module/rewards/reputer_rewards_test.go | 90 ++++++- x/emissions/module/rewards/rewards.go | 227 ++++++++-------- x/emissions/module/rewards/rewards_test.go | 248 +++++++++++++++++- x/emissions/module/rewards/scores_test.go | 12 +- 4 files changed, 460 insertions(+), 117 deletions(-) diff --git a/x/emissions/module/rewards/reputer_rewards_test.go b/x/emissions/module/rewards/reputer_rewards_test.go index ddceaf053..342838faf 100644 --- a/x/emissions/module/rewards/reputer_rewards_test.go +++ b/x/emissions/module/rewards/reputer_rewards_test.go @@ -12,8 +12,16 @@ func (s *RewardsTestSuite) TestGetReputersRewards() { 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) + _, err := mockReputersData(s, topidId, block, reputerAddrs) s.Require().NoError(err) // Get reputer rewards @@ -46,8 +54,11 @@ func (s *RewardsTestSuite) TestGetReputersRewards() { } } -// mockReputersData generates reputer scores, stakes and losses -func mockReputersData(s *RewardsTestSuite, topicId uint64, block int64) (types.ReputerValueBundles, error) { +// After removing the number of reputers, the rewards should increase for the remaining reputers +func (s *RewardsTestSuite) TestGetReputersRewardsShouldIncreaseRewardsAfterRemovingReputer() { + topidId := uint64(1) + block := int64(1003) + reputerAddrs := []sdk.AccAddress{ s.addrs[0], s.addrs[1], @@ -56,6 +67,79 @@ func mockReputersData(s *RewardsTestSuite, topicId uint64, block int64) (types.R s.addrs[4], } + // Generate reputers data for tests + _, err := mockReputersData(s, topidId, block, reputerAddrs) + s.Require().NoError(err) + + // Get reputer rewards + reputerRewards, err := rewards.GetReputerRewards( + s.ctx, + s.emissionsKeeper, + topidId, + block, + alloraMath.OneDec(), + alloraMath.MustNewDecFromString("1017.5559072418691"), + ) + s.Require().NoError(err) + s.Require().Equal(5, len(reputerRewards)) + + expectedRewards := []alloraMath.Dec{ + alloraMath.MustNewDecFromString("456.49"), + alloraMath.MustNewDecFromString("172.71"), + alloraMath.MustNewDecFromString("211.93"), + alloraMath.MustNewDecFromString("52.31"), + alloraMath.MustNewDecFromString("124.10"), + } + + for i, reputerReward := range reputerRewards { + s.Require().True( + alloraMath.InDelta(expectedRewards[i], reputerReward.Reward, alloraMath.MustNewDecFromString("0.01")), + "expected: %s, got: %s", + expectedRewards[i].String(), + reputerReward.Reward.String(), + ) + } + + // New Topic, block and reputer addresses + topidId = uint64(2) + block = int64(1004) + // Reduce number of reputer addresses + reputerAddrs = []sdk.AccAddress{ + s.addrs[5], + s.addrs[6], + s.addrs[7], + s.addrs[8], + } + + // Generate reputers same loss data for less reputers + _, err = mockReputersData(s, topidId, block, reputerAddrs) + s.Require().NoError(err) + + // Get reputer rewards + newReputerRewards, err := rewards.GetReputerRewards( + s.ctx, + s.emissionsKeeper, + topidId, + block, + alloraMath.OneDec(), + alloraMath.MustNewDecFromString("1017.5559072418691"), + ) + s.Require().NoError(err) + s.Require().Equal(4, len(newReputerRewards)) + + for i, newReputerReward := range newReputerRewards { + newReputerReward.Reward.Gt(reputerRewards[i].Reward) + s.Require().True( + newReputerReward.Reward.Gt(reputerRewards[i].Reward), + "expected: %s, got: %s", + newReputerReward.Reward.String(), + reputerRewards[i].Reward.String(), + ) + } +} + +// mockReputersData generates reputer scores, stakes and losses +func mockReputersData(s *RewardsTestSuite, topicId uint64, block int64, reputerAddrs []sdk.AccAddress) (types.ReputerValueBundles, error) { var scores = []alloraMath.Dec{ alloraMath.MustNewDecFromString("17.53436"), alloraMath.MustNewDecFromString("20.29489"), diff --git a/x/emissions/module/rewards/rewards.go b/x/emissions/module/rewards/rewards.go index e143fdbf8..ed1b47421 100644 --- a/x/emissions/module/rewards/rewards.go +++ b/x/emissions/module/rewards/rewards.go @@ -66,82 +66,7 @@ func EmitRewards(ctx sdk.Context, k keeper.Keeper, activeTopics []*types.Topic) continue } - lossBundles, err := k.GetNetworkLossBundleAtBlock(ctx, topic.Id, topicRewardNonce) - if err != nil { - return err - } - - // Get Entropy for each task - reputerEntropy, reputerFractions, reputers, err := GetReputerTaskEntropy( - ctx, - k, - topic.Id, - moduleParams.TaskRewardAlpha, - moduleParams.PRewardSpread, - moduleParams.BetaEntropy, - topicRewardNonce, - ) - if err != nil { - return err - } - inferenceEntropy, inferenceFractions, workersInference, err := GetInferenceTaskEntropy( - ctx, - k, - topic.Id, - moduleParams.TaskRewardAlpha, - moduleParams.PRewardSpread, - moduleParams.BetaEntropy, - topicRewardNonce, - ) - if err != nil { - return err - } - forecastingEntropy, forecastFractions, workersForecast, err := GetForecastingTaskEntropy( - ctx, - k, - topic.Id, - moduleParams.TaskRewardAlpha, - moduleParams.PRewardSpread, - moduleParams.BetaEntropy, - topicRewardNonce, - ) - if err != nil { - return err - } - - // Get Total Rewards for Reputation task - taskReputerReward, err := GetRewardForReputerTaskInTopic( - inferenceEntropy, - forecastingEntropy, - reputerEntropy, - topicRewards, - ) - if err != nil { - return err - } - taskInferenceReward, err := GetRewardForInferenceTaskInTopic( - lossBundles.NaiveValue, - lossBundles.CombinedValue, - inferenceEntropy, - forecastingEntropy, - reputerEntropy, - topicRewards, - moduleParams.SigmoidA, - moduleParams.SigmoidB, - ) - if err != nil { - return err - } - taskForecastingReward, err := GetRewardForForecastingTaskInTopic( - lossBundles.NaiveValue, - lossBundles.CombinedValue, - inferenceEntropy, - forecastingEntropy, - reputerEntropy, - topicRewards, - moduleParams.SigmoidA, - moduleParams.SigmoidB, - ) + taskReputerReward, taskInferenceReward, taskForecastingReward, err := GenerateTasksRewards(ctx, k, topic.Id, topicRewards, topicRewardNonce, moduleParams) if err != nil { return err } @@ -195,17 +120,6 @@ func EmitRewards(ctx sdk.Context, k keeper.Keeper, activeTopics []*types.Topic) if err != nil { return err } - SetPreviousRewardFractions( - ctx, - k, - topic.Id, - reputers, - reputerFractions, - workersInference, - inferenceFractions, - workersForecast, - forecastFractions, - ) // Delete topic reward nonce err = k.DeleteTopicRewardNonce(ctx, topic.Id) @@ -218,25 +132,111 @@ func EmitRewards(ctx sdk.Context, k keeper.Keeper, activeTopics []*types.Topic) return nil } -func payoutRewards(ctx sdk.Context, k keeper.Keeper, rewards []TaskRewards) error { - for _, reward := range rewards { - address, err := sdk.AccAddressFromBech32(reward.Address.String()) - if err != nil { - return err - } +func GenerateTasksRewards( + ctx sdk.Context, + k keeper.Keeper, + topicId uint64, + topicRewards alloraMath.Dec, + block int64, + moduleParams types.Params, +) ( + alloraMath.Dec, + alloraMath.Dec, + alloraMath.Dec, + error, +) { + lossBundles, err := k.GetNetworkLossBundleAtBlock(ctx, topicId, block) + if err != nil { + return alloraMath.Dec{}, alloraMath.Dec{}, alloraMath.Dec{}, err + } - err = k.BankKeeper().SendCoinsFromModuleToAccount( - ctx, - types.AlloraRewardsAccountName, - address, - sdk.NewCoins(sdk.NewCoin(params.DefaultBondDenom, reward.Reward.SdkIntTrim())), - ) - if err != nil { - return err - } + reputerEntropy, reputerFractions, reputers, err := GetReputerTaskEntropy( + ctx, + k, + topicId, + moduleParams.TaskRewardAlpha, + moduleParams.PRewardSpread, + moduleParams.BetaEntropy, + block, + ) + if err != nil { + return alloraMath.Dec{}, alloraMath.Dec{}, alloraMath.Dec{}, err + } + inferenceEntropy, inferenceFractions, workersInference, err := GetInferenceTaskEntropy( + ctx, + k, + topicId, + moduleParams.TaskRewardAlpha, + moduleParams.PRewardSpread, + moduleParams.BetaEntropy, + block, + ) + if err != nil { + return alloraMath.Dec{}, alloraMath.Dec{}, alloraMath.Dec{}, err + } + forecastingEntropy, forecastFractions, workersForecast, err := GetForecastingTaskEntropy( + ctx, + k, + topicId, + moduleParams.TaskRewardAlpha, + moduleParams.PRewardSpread, + moduleParams.BetaEntropy, + block, + ) + if err != nil { + return alloraMath.Dec{}, alloraMath.Dec{}, alloraMath.Dec{}, err } - return nil + // Get Total Rewards for Reputation task + taskReputerReward, err := GetRewardForReputerTaskInTopic( + inferenceEntropy, + forecastingEntropy, + reputerEntropy, + topicRewards, + ) + if err != nil { + return alloraMath.Dec{}, alloraMath.Dec{}, alloraMath.Dec{}, err + } + taskInferenceReward, err := GetRewardForInferenceTaskInTopic( + lossBundles.NaiveValue, + lossBundles.CombinedValue, + inferenceEntropy, + forecastingEntropy, + reputerEntropy, + topicRewards, + moduleParams.SigmoidA, + moduleParams.SigmoidB, + ) + if err != nil { + return alloraMath.Dec{}, alloraMath.Dec{}, alloraMath.Dec{}, err + } + taskForecastingReward, err := GetRewardForForecastingTaskInTopic( + lossBundles.NaiveValue, + lossBundles.CombinedValue, + inferenceEntropy, + forecastingEntropy, + reputerEntropy, + topicRewards, + moduleParams.SigmoidA, + moduleParams.SigmoidB, + ) + if err != nil { + return alloraMath.Dec{}, alloraMath.Dec{}, alloraMath.Dec{}, err + } + + SetPreviousRewardFractions( + ctx, + k, + topicId, + reputers, + reputerFractions, + workersInference, + inferenceFractions, + workersForecast, + forecastFractions, + ) + + return taskReputerReward, taskInferenceReward, taskForecastingReward, nil } func SetPreviousRewardFractions( @@ -270,3 +270,24 @@ func SetPreviousRewardFractions( } return nil } + +func payoutRewards(ctx sdk.Context, k keeper.Keeper, rewards []TaskRewards) error { + for _, reward := range rewards { + address, err := sdk.AccAddressFromBech32(reward.Address.String()) + if err != nil { + return err + } + + err = k.BankKeeper().SendCoinsFromModuleToAccount( + ctx, + types.AlloraRewardsAccountName, + address, + sdk.NewCoins(sdk.NewCoin(params.DefaultBondDenom, reward.Reward.SdkIntTrim())), + ) + if err != nil { + return err + } + } + + return nil +} diff --git a/x/emissions/module/rewards/rewards_test.go b/x/emissions/module/rewards/rewards_test.go index 40d3777fd..4aaca046a 100644 --- a/x/emissions/module/rewards/rewards_test.go +++ b/x/emissions/module/rewards/rewards_test.go @@ -14,6 +14,7 @@ import ( "github.com/allora-network/allora-chain/x/emissions/keeper/inference_synthesis" "github.com/allora-network/allora-chain/x/emissions/keeper/msgserver" "github.com/allora-network/allora-chain/x/emissions/module" + "github.com/allora-network/allora-chain/x/emissions/module/rewards" "github.com/allora-network/allora-chain/x/emissions/types" "github.com/cometbft/cometbft/crypto/secp256k1" "github.com/cosmos/cosmos-sdk/codec/address" @@ -270,7 +271,7 @@ func (s *RewardsTestSuite) TestStandardRewardEmission() { s.Require().NoError(err) // Insert loss bundle from reputers - lossBundles := GenerateLossBundles(s, block, topicId) + lossBundles := GenerateLossBundles(s, block, topicId, reputerAddrs) _, err = s.msgServer.InsertBulkReputerPayload(s.ctx, &types.MsgInsertBulkReputerPayload{ Sender: reputerAddrs[0].String(), TopicId: topicId, @@ -293,3 +294,248 @@ func (s *RewardsTestSuite) TestStandardRewardEmission() { err = s.appModule.EndBlock(s.ctx) s.Require().NoError(err) } + +func (s *RewardsTestSuite) TestGenerateTasksRewardsShouldIncreaseRewardShareIfMoreParticipants() { + block := int64(100) + s.ctx = s.ctx.WithBlockHeight(block) + + reputerAddrs := []sdk.AccAddress{ + s.addrs[0], + s.addrs[1], + s.addrs[2], + } + + workerAddrs := []sdk.AccAddress{ + s.addrs[5], + s.addrs[6], + s.addrs[7], + s.addrs[8], + s.addrs[9], + } + + stakes := []cosmosMath.Uint{ + cosmosMath.NewUint(1000000000000000000), + cosmosMath.NewUint(1000000000000000000), + cosmosMath.NewUint(1000000000000000000), + } + + // Create topic + newTopicMsg := &types.MsgCreateNewTopic{ + Creator: reputerAddrs[0].String(), + Metadata: "test", + LossLogic: "logic", + 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 3 reputers + for _, addr := range reputerAddrs { + reputerRegMsg := &types.MsgRegister{ + Sender: addr.String(), + LibP2PKey: "test", + MultiAddress: "test", + TopicId: topicId, + IsReputer: true, + } + _, err := s.msgServer.Register(s.ctx, reputerRegMsg) + s.Require().NoError(err) + } + // Add Stake for reputers + for i, addr := range reputerAddrs { + _, err := s.msgServer.AddStake(s.ctx, &types.MsgAddStake{ + Sender: addr.String(), + Amount: stakes[i], + TopicId: topicId, + }) + s.Require().NoError(err) + } + + 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) + + topicTotalRewards := alloraMath.NewDecFromInt64(1000000) + params, err := s.emissionsKeeper.GetParams(s.ctx) + s.Require().NoError(err) + + firstTaskReputerReward, _, _, err := rewards.GenerateTasksRewards(s.ctx, s.emissionsKeeper, topicId, topicTotalRewards, block, params) + s.Require().NoError(err) + + block += 1 + s.ctx = s.ctx.WithBlockHeight(block) + + // Add new reputers and stakes + newReputerAddrs := []sdk.AccAddress{ + s.addrs[3], + s.addrs[4], + } + reputerAddrs = append(reputerAddrs, newReputerAddrs...) + + // Add Stake for new reputers + newStakes := []cosmosMath.Uint{ + cosmosMath.NewUint(1000000000000000000), + cosmosMath.NewUint(1000000000000000000), + } + stakes = append(stakes, newStakes...) + + // Create new topic + newTopicMsg = &types.MsgCreateNewTopic{ + Creator: reputerAddrs[0].String(), + Metadata: "test", + LossLogic: "logic", + 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, + } + _, err := s.msgServer.Register(s.ctx, reputerRegMsg) + s.Require().NoError(err) + } + // Add Stake for reputers + for i, addr := range reputerAddrs { + _, err := s.msgServer.AddStake(s.ctx, &types.MsgAddStake{ + Sender: addr.String(), + Amount: stakes[i], + TopicId: topicId, + }) + s.Require().NoError(err) + } + + 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) + + secondTaskReputerReward, _, _, err := rewards.GenerateTasksRewards(s.ctx, s.emissionsKeeper, topicId, topicTotalRewards, block, params) + s.Require().NoError(err) + + // Check if the reward share increased + s.Require().True(secondTaskReputerReward.Gt(firstTaskReputerReward)) +} diff --git a/x/emissions/module/rewards/scores_test.go b/x/emissions/module/rewards/scores_test.go index 06a1dd720..7622a6669 100644 --- a/x/emissions/module/rewards/scores_test.go +++ b/x/emissions/module/rewards/scores_test.go @@ -144,7 +144,7 @@ func mockReputersScoresTestData(s *RewardsTestSuite, topicId uint64, block int64 } } - reputerValueBundles := GenerateLossBundles(s, block, topicId) + reputerValueBundles := GenerateLossBundles(s, block, topicId, reputers) err := s.emissionsKeeper.InsertReputerLossBundlesAtBlock(s.ctx, topicId, block, reputerValueBundles) if err != nil { return types.ReputerValueBundles{}, err @@ -177,15 +177,7 @@ func GenerateReputerLatestScores(s *RewardsTestSuite, reputers []sdk.AccAddress, return nil } -func GenerateLossBundles(s *RewardsTestSuite, blockHeight int64, topicId uint64) types.ReputerValueBundles { - reputers := []sdk.AccAddress{ - s.addrs[0], - s.addrs[1], - s.addrs[2], - s.addrs[3], - s.addrs[4], - } - +func GenerateLossBundles(s *RewardsTestSuite, blockHeight int64, topicId uint64, reputers []sdk.AccAddress) types.ReputerValueBundles { workers := []sdk.AccAddress{ s.addrs[5], s.addrs[6],