code.vegaprotocol.io/vega@v0.79.0/core/rewards/reward_distribution.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  	"math"
    20  	"sort"
    21  	"time"
    22  
    23  	"code.vegaprotocol.io/vega/core/types"
    24  	"code.vegaprotocol.io/vega/libs/num"
    25  	"code.vegaprotocol.io/vega/protos/vega"
    26  
    27  	"golang.org/x/exp/rand"
    28  )
    29  
    30  func adjustScoreForNegative(partyScores []*types.PartyContributionScore) []*types.PartyContributionScore {
    31  	if len(partyScores) == 0 {
    32  		return partyScores
    33  	}
    34  	minScore := num.DecimalFromInt64(math.MaxInt64)
    35  	adjustedPartyScores := make([]*types.PartyContributionScore, 0, len(partyScores))
    36  	for _, ps := range partyScores {
    37  		if ps.Score.LessThan(minScore) {
    38  			minScore = ps.Score
    39  		}
    40  	}
    41  
    42  	if !minScore.IsNegative() {
    43  		return partyScores
    44  	}
    45  
    46  	minScore = minScore.Neg()
    47  
    48  	for _, ps := range partyScores {
    49  		adjustedScore := ps.Score.Add(minScore)
    50  		if !adjustedScore.IsZero() {
    51  			adjustedPartyScores = append(adjustedPartyScores, &types.PartyContributionScore{Party: ps.Party, Score: adjustedScore})
    52  		}
    53  	}
    54  
    55  	return adjustedPartyScores
    56  }
    57  
    58  func findRank(rankingTable []*vega.Rank, ind int) uint32 {
    59  	var lastSeen *vega.Rank
    60  	for _, rank := range rankingTable {
    61  		if int(rank.StartRank) > ind {
    62  			break
    63  		}
    64  		lastSeen = rank
    65  	}
    66  
    67  	if lastSeen == nil {
    68  		return 0
    69  	}
    70  
    71  	return lastSeen.ShareRatio
    72  }
    73  
    74  func rankingRewardCalculator(partyMetric []*types.PartyContributionScore, rankingTable []*vega.Rank, partyRewardFactor map[string]num.Decimal) []*types.PartyContributionScore {
    75  	partyScores := []*types.PartyContributionScore{}
    76  	adjustedPartyScores := adjustScoreForNegative(partyMetric)
    77  
    78  	sort.Slice(adjustedPartyScores, func(i, j int) bool {
    79  		if adjustedPartyScores[i].Score.Equal(adjustedPartyScores[j].Score) {
    80  			return adjustedPartyScores[i].Party < adjustedPartyScores[j].Party
    81  		}
    82  		return adjustedPartyScores[i].Score.GreaterThan(adjustedPartyScores[j].Score)
    83  	})
    84  	shareRatio := num.DecimalZero()
    85  	totalScores := num.DecimalZero()
    86  	for i, ps := range adjustedPartyScores {
    87  		rewardFactor, ok := partyRewardFactor[ps.Party]
    88  		if !ok {
    89  			rewardFactor = num.DecimalOne()
    90  		}
    91  		if i == 0 || !ps.Score.Equal(adjustedPartyScores[i-1].Score) {
    92  			shareRatio = num.DecimalFromInt64(int64(findRank(rankingTable, i+1)))
    93  		}
    94  		score := shareRatio.Mul(rewardFactor)
    95  		if shareRatio.IsZero() {
    96  			break
    97  		}
    98  		if score.IsZero() {
    99  			continue
   100  		}
   101  		partyScores = append(partyScores, &types.PartyContributionScore{Party: ps.Party, Score: score})
   102  		totalScores = totalScores.Add(score)
   103  	}
   104  	if totalScores.IsZero() {
   105  		return []*types.PartyContributionScore{}
   106  	}
   107  
   108  	normalise(partyScores, totalScores)
   109  	return partyScores
   110  }
   111  
   112  func selectIndex(rng *rand.Rand, probabilities []num.Decimal) int {
   113  	cumulativeProbabilities := make([]num.Decimal, len(probabilities))
   114  	cumulativeProbabilities[0] = probabilities[0]
   115  	for i := 1; i < len(probabilities); i++ {
   116  		cumulativeProbabilities[i] = cumulativeProbabilities[i-1].Add(probabilities[i])
   117  	}
   118  	totalProbability := cumulativeProbabilities[len(cumulativeProbabilities)-1]
   119  	scaleFactor := num.DecimalFromInt64(1000000)
   120  	scaledTotal := totalProbability.Mul(scaleFactor).IntPart()
   121  	randomNumber := rng.Int63n(scaledTotal)
   122  	randomDecimal := num.DecimalFromInt64(randomNumber).Div(scaleFactor)
   123  	for i, cumulativeProbability := range cumulativeProbabilities {
   124  		if randomDecimal.LessThan(cumulativeProbability) {
   125  			return i
   126  		}
   127  	}
   128  	return len(probabilities) - 1
   129  }
   130  
   131  type PartyProbability struct {
   132  	Probability num.Decimal
   133  	Party       string
   134  }
   135  
   136  func lotteryRewardScoreSorting(adjustedPartyScores []*types.PartyContributionScore, timestamp time.Time) []*types.PartyContributionScore {
   137  	source := rand.NewSource(uint64(timestamp.UnixNano()))
   138  	rng := rand.New(source)
   139  
   140  	unselectedParties := map[string]*types.PartyContributionScore{}
   141  	totalScores := num.DecimalZero()
   142  	for _, ps := range adjustedPartyScores {
   143  		totalScores = totalScores.Add(ps.Score)
   144  		unselectedParties[ps.Party] = ps
   145  	}
   146  	if totalScores.IsZero() {
   147  		return nil
   148  	}
   149  
   150  	lotteryPartyScores := make([]*types.PartyContributionScore, 0, len(adjustedPartyScores))
   151  
   152  	for {
   153  		if len(unselectedParties) < 1 {
   154  			break
   155  		}
   156  		pp := make([]PartyProbability, 0, len(unselectedParties))
   157  		for _, ps := range unselectedParties {
   158  			pp = append(pp, PartyProbability{
   159  				Probability: ps.Score.Div(totalScores),
   160  				Party:       ps.Party,
   161  			})
   162  		}
   163  		sort.Slice(pp, func(i, j int) bool {
   164  			if pp[i].Probability.Equal(pp[j].Probability) {
   165  				return pp[i].Party < pp[j].Party
   166  			}
   167  			return pp[i].Probability.LessThan(pp[j].Probability)
   168  		})
   169  
   170  		probabilities := make([]num.Decimal, len(pp))
   171  		parties := make([]string, len(pp))
   172  
   173  		for i, partyProb := range pp {
   174  			probabilities[i] = partyProb.Probability
   175  			parties[i] = partyProb.Party
   176  		}
   177  
   178  		selected := selectIndex(rng, probabilities)
   179  		selectedParty := unselectedParties[parties[selected]]
   180  		delete(unselectedParties, selectedParty.Party)
   181  		totalScores = totalScores.Sub(selectedParty.Score)
   182  		lotteryPartyScores = append(lotteryPartyScores, selectedParty)
   183  	}
   184  	return lotteryPartyScores
   185  }
   186  
   187  func rankingLotteryRewardCalculator(partyMetric []*types.PartyContributionScore, rankingTable []*vega.Rank, partyRewardFactor map[string]num.Decimal, timestamp time.Time) []*types.PartyContributionScore {
   188  	partyScores := []*types.PartyContributionScore{}
   189  	lotteryPartyScores := lotteryRewardScoreSorting(adjustScoreForNegative(partyMetric), timestamp)
   190  	if lotteryPartyScores == nil {
   191  		return nil
   192  	}
   193  
   194  	shareRatio := num.DecimalZero()
   195  	totalScores := num.DecimalZero()
   196  	for i, ps := range lotteryPartyScores {
   197  		rewardFactor, ok := partyRewardFactor[ps.Party]
   198  		if !ok {
   199  			rewardFactor = num.DecimalOne()
   200  		}
   201  		if i == 0 || !ps.Score.Equal(lotteryPartyScores[i-1].Score) {
   202  			shareRatio = num.DecimalFromInt64(int64(findRank(rankingTable, i+1)))
   203  		}
   204  		score := shareRatio.Mul(rewardFactor)
   205  		if shareRatio.IsZero() {
   206  			break
   207  		}
   208  		if score.IsZero() {
   209  			continue
   210  		}
   211  		partyScores = append(partyScores, &types.PartyContributionScore{Party: ps.Party, Score: score})
   212  		totalScores = totalScores.Add(score)
   213  	}
   214  	if totalScores.IsZero() {
   215  		return []*types.PartyContributionScore{}
   216  	}
   217  
   218  	normalise(partyScores, totalScores)
   219  	return partyScores
   220  }
   221  
   222  func proRataRewardCalculator(partyContribution []*types.PartyContributionScore, partyRewardFactor map[string]num.Decimal) []*types.PartyContributionScore {
   223  	total := num.DecimalZero()
   224  	adjustedPartyScores := adjustScoreForNegative(partyContribution)
   225  	partiesWithScore := []*types.PartyContributionScore{}
   226  	for _, metric := range adjustedPartyScores {
   227  		factor, ok := partyRewardFactor[metric.Party]
   228  		if !ok {
   229  			factor = num.DecimalOne()
   230  		}
   231  		score := factor.Mul(metric.Score)
   232  		if score.IsZero() {
   233  			continue
   234  		}
   235  		total = total.Add(score)
   236  		partiesWithScore = append(partiesWithScore, &types.PartyContributionScore{Party: metric.Party, Score: score})
   237  	}
   238  	if total.IsZero() {
   239  		return []*types.PartyContributionScore{}
   240  	}
   241  
   242  	normalise(partiesWithScore, total)
   243  	return partiesWithScore
   244  }
   245  
   246  func normalise(partyRewardScores []*types.PartyContributionScore, total num.Decimal) {
   247  	normalisedTotal := num.DecimalZero()
   248  	for _, p := range partyRewardScores {
   249  		p.Score = p.Score.Div(total)
   250  		normalisedTotal = normalisedTotal.Add(p.Score)
   251  	}
   252  	if normalisedTotal.LessThanOrEqual(num.DecimalOne()) {
   253  		return
   254  	}
   255  
   256  	capAtOne(partyRewardScores, normalisedTotal)
   257  }
   258  
   259  func capAtOne(partyRewardScores []*types.PartyContributionScore, total num.Decimal) {
   260  	if total.LessThanOrEqual(num.DecimalOne()) {
   261  		return
   262  	}
   263  
   264  	sort.SliceStable(partyRewardScores, func(i, j int) bool { return partyRewardScores[i].Score.GreaterThan(partyRewardScores[j].Score) })
   265  	delta := total.Sub(num.DecimalFromInt64(1))
   266  	partyRewardScores[0].Score = num.MaxD(num.DecimalZero(), partyRewardScores[0].Score.Sub(delta))
   267  }