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 }