code.vegaprotocol.io/vega@v0.79.0/core/rewards/contribution_reward_calculator.go (about)

     1  // Copyright (C) 2023 Gobalsky Labs Limited
     2  //
     3  // This program is free software: you can redistribute it and/or modify
     4  // it under the terms of the GNU Affero General Public License as
     5  // published by the Free Software Foundation, either version 3 of the
     6  // License, or (at your option) any later version.
     7  //
     8  // This program is distributed in the hope that it will be useful,
     9  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    10  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    11  // GNU Affero General Public License for more details.
    12  //
    13  // You should have received a copy of the GNU Affero General Public License
    14  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    15  
    16  package rewards
    17  
    18  import (
    19  	"time"
    20  
    21  	"code.vegaprotocol.io/vega/core/types"
    22  	"code.vegaprotocol.io/vega/libs/num"
    23  	"code.vegaprotocol.io/vega/protos/vega"
    24  )
    25  
    26  func calculateRewardsByScores(rewardBalance num.Decimal, partyScores []*types.PartyContributionScore, takerFeeContributionInRewardToken map[string]*num.Uint, cap num.Decimal) (map[string]*num.Uint, *num.Uint) {
    27  	total := num.UintZero()
    28  	partyToAmount := map[string]*num.Uint{}
    29  	remainingRounds := 10
    30  	if cap.IsZero() {
    31  		remainingRounds = 1
    32  	}
    33  	for {
    34  		totalPerRound := num.UintZero()
    35  		for _, p := range partyScores {
    36  			currentPartyReward, ok := partyToAmount[p.Party]
    37  			if !ok {
    38  				currentPartyReward = num.UintZero()
    39  			}
    40  			partyRewardD := rewardBalance.Mul(p.Score)
    41  			if !cap.IsZero() {
    42  				partyTakeFeeContributionInRewardAsset, ok := takerFeeContributionInRewardToken[p.Party]
    43  				if ok {
    44  					partyRewardD = num.MinD(currentPartyReward.ToDecimal().Add(partyRewardD), cap.Mul(partyTakeFeeContributionInRewardAsset.ToDecimal()))
    45  				} else {
    46  					partyRewardD = num.DecimalZero()
    47  				}
    48  			}
    49  			partyReward, _ := num.UintFromDecimal(partyRewardD)
    50  			if !partyReward.IsZero() {
    51  				var partyRewardDelta *num.Uint
    52  				if _, ok := partyToAmount[p.Party]; !ok {
    53  					partyRewardDelta = partyReward
    54  				} else {
    55  					partyRewardDelta = num.UintZero().Sub(partyReward, partyToAmount[p.Party])
    56  				}
    57  				partyToAmount[p.Party] = partyReward
    58  				totalPerRound.AddSum(partyRewardDelta)
    59  			}
    60  		}
    61  		rewardBalance = rewardBalance.Sub(totalPerRound.ToDecimal())
    62  		remainingRounds -= 1
    63  		total.AddSum(totalPerRound)
    64  		if rewardBalance.LessThan(num.DecimalOne()) || totalPerRound.IsZero() || remainingRounds <= 0 {
    65  			break
    66  		}
    67  	}
    68  	return partyToAmount, total
    69  }
    70  
    71  // given party contribution scores, reward multipliers and distribution strategy calculate the payout per party.
    72  func calculateRewardsByContributionIndividual(epochSeq, asset, accountID string, balance *num.Uint, partyContribution []*types.PartyContributionScore, rewardFactors map[string]num.Decimal, timestamp time.Time, ds *vega.DispatchStrategy, takerFeeContributionInRewardToken map[string]*num.Uint) *payout {
    73  	po := &payout{
    74  		asset:           asset,
    75  		fromAccount:     accountID,
    76  		epochSeq:        epochSeq,
    77  		timestamp:       timestamp.Unix(),
    78  		partyToAmount:   map[string]*num.Uint{},
    79  		lockedForEpochs: ds.LockPeriod,
    80  	}
    81  
    82  	var partyScores []*types.PartyContributionScore
    83  	if ds.DistributionStrategy == vega.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA {
    84  		partyScores = proRataRewardCalculator(partyContribution, rewardFactors)
    85  	} else if ds.DistributionStrategy == vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK {
    86  		partyScores = rankingRewardCalculator(partyContribution, ds.RankTable, rewardFactors)
    87  	} else if ds.DistributionStrategy == vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK_LOTTERY {
    88  		partyScores = rankingLotteryRewardCalculator(partyContribution, ds.RankTable, rewardFactors, timestamp)
    89  	}
    90  
    91  	cap := num.DecimalZero()
    92  	if ds.CapRewardFeeMultiple != nil {
    93  		cap = num.MustDecimalFromString(*ds.CapRewardFeeMultiple)
    94  	}
    95  	po.partyToAmount, po.totalReward = calculateRewardsByScores(balance.ToDecimal(), partyScores, takerFeeContributionInRewardToken, cap)
    96  	if po.totalReward.IsZero() {
    97  		return nil
    98  	}
    99  	return po
   100  }
   101  
   102  // given party contribution scores, reward multipliers and distribution strategy calculate the payout per party in a team.
   103  func calculateRewardsByContributionTeam(epochSeq, asset, accountID string, balance *num.Uint, teamContribution []*types.PartyContributionScore, teamPartyContribution map[string][]*types.PartyContributionScore, rewardFactors map[string]num.Decimal, timestamp time.Time, ds *vega.DispatchStrategy, takerFeeContributionInRewardToken map[string]*num.Uint) *payout {
   104  	po := &payout{
   105  		asset:           asset,
   106  		fromAccount:     accountID,
   107  		epochSeq:        epochSeq,
   108  		timestamp:       timestamp.Unix(),
   109  		partyToAmount:   map[string]*num.Uint{},
   110  		lockedForEpochs: ds.LockPeriod,
   111  	}
   112  	var teamScores []*types.PartyContributionScore
   113  	if ds.DistributionStrategy == vega.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA {
   114  		teamScores = proRataRewardCalculator(teamContribution, map[string]num.Decimal{})
   115  	} else if ds.DistributionStrategy == vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK {
   116  		teamScores = rankingRewardCalculator(teamContribution, ds.RankTable, map[string]num.Decimal{})
   117  	} else if ds.DistributionStrategy == vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK_LOTTERY {
   118  		teamScores = rankingLotteryRewardCalculator(teamContribution, ds.RankTable, map[string]num.Decimal{}, timestamp)
   119  	}
   120  
   121  	partyScores := []*types.PartyContributionScore{}
   122  	totalScore := num.DecimalZero()
   123  	for _, teamScore := range teamScores {
   124  		partyScores = append(partyScores, calcPartyInTeamRewardShare(teamScore, teamPartyContribution[teamScore.Party], rewardFactors)...)
   125  	}
   126  	for _, pcs := range partyScores {
   127  		totalScore = totalScore.Add(pcs.Score)
   128  	}
   129  
   130  	capAtOne(partyScores, totalScore)
   131  
   132  	cap := num.DecimalZero()
   133  	if ds.CapRewardFeeMultiple != nil {
   134  		cap = num.MustDecimalFromString(*ds.CapRewardFeeMultiple)
   135  	}
   136  	po.partyToAmount, po.totalReward = calculateRewardsByScores(balance.ToDecimal(), partyScores, takerFeeContributionInRewardToken, cap)
   137  
   138  	if po.totalReward.IsZero() {
   139  		return nil
   140  	}
   141  	return po
   142  }
   143  
   144  func calcPartyInTeamRewardShare(teamScore *types.PartyContributionScore, partyToMetricScore []*types.PartyContributionScore, rewardFactors map[string]num.Decimal) []*types.PartyContributionScore {
   145  	ps := make([]*types.PartyContributionScore, 0, len(partyToMetricScore))
   146  
   147  	totalScores := num.DecimalZero()
   148  	for _, pcs := range partyToMetricScore {
   149  		if pcs.Score.IsZero() {
   150  			continue
   151  		}
   152  		rewardFactor := num.DecimalOne()
   153  		if factor, ok := rewardFactors[pcs.Party]; ok {
   154  			rewardFactor = factor
   155  		}
   156  		ps = append(ps, &types.PartyContributionScore{Party: pcs.Party, Score: rewardFactor})
   157  		totalScores = totalScores.Add(rewardFactor)
   158  	}
   159  	if totalScores.IsZero() {
   160  		return []*types.PartyContributionScore{}
   161  	}
   162  
   163  	for _, pcs := range ps {
   164  		pcs.Score = pcs.Score.Mul(teamScore.Score).Div(totalScores)
   165  	}
   166  	return ps
   167  }