code.vegaprotocol.io/vega@v0.79.0/core/rewards/contribution_reward_calculator_test.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 "testing" 20 "time" 21 22 "code.vegaprotocol.io/vega/core/types" 23 "code.vegaprotocol.io/vega/libs/num" 24 "code.vegaprotocol.io/vega/protos/vega" 25 26 "github.com/stretchr/testify/require" 27 ) 28 29 func TestCalculateRewardsByContributionIndividualProRata(t *testing.T) { 30 partyContribution := []*types.PartyContributionScore{ 31 {Party: "p1", Score: num.DecimalFromFloat(0.6)}, 32 {Party: "p2", Score: num.DecimalFromFloat(0.5)}, 33 {Party: "p3", Score: num.DecimalFromFloat(0.1)}, 34 {Party: "p4", Score: num.DecimalFromFloat(0.6)}, 35 {Party: "p5", Score: num.DecimalFromFloat(0.05)}, 36 } 37 rewardMultipliers := map[string]num.Decimal{"p2": num.DecimalFromFloat(2.5), "p3": num.DecimalFromInt64(5), "p4": num.DecimalFromFloat(2.5), "p5": num.DecimalFromInt64(3)} 38 39 now := time.Now() 40 ds := &vega.DispatchStrategy{ 41 DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA, 42 LockPeriod: 2, 43 } 44 po := calculateRewardsByContributionIndividual("1", "asset", "accountID", num.NewUint(10000), partyContribution, rewardMultipliers, now, ds, nil) 45 46 require.Equal(t, "1500", po.partyToAmount["p1"].String()) 47 require.Equal(t, "3125", po.partyToAmount["p2"].String()) 48 require.Equal(t, "1250", po.partyToAmount["p3"].String()) 49 require.Equal(t, "3750", po.partyToAmount["p4"].String()) 50 require.Equal(t, "375", po.partyToAmount["p5"].String()) 51 require.Equal(t, "asset", po.asset) 52 require.Equal(t, "1", po.epochSeq) 53 require.Equal(t, "accountID", po.fromAccount) 54 require.Equal(t, uint64(2), po.lockedForEpochs) 55 require.Equal(t, now.Unix(), po.timestamp) 56 require.Equal(t, "10000", po.totalReward.String()) 57 } 58 59 func TestCalculateRewardsByContributionIndividualProRataWithCap(t *testing.T) { 60 partyContribution := []*types.PartyContributionScore{ 61 {Party: "p1", Score: num.DecimalFromFloat(0.6)}, 62 {Party: "p2", Score: num.DecimalFromFloat(0.5)}, 63 {Party: "p3", Score: num.DecimalFromFloat(0.1)}, 64 {Party: "p4", Score: num.DecimalFromFloat(0.6)}, 65 {Party: "p5", Score: num.DecimalFromFloat(0.05)}, 66 } 67 rewardMultipliers := map[string]num.Decimal{"p2": num.DecimalFromFloat(2.5), "p3": num.DecimalFromInt64(5), "p4": num.DecimalFromFloat(2.5), "p5": num.DecimalFromInt64(3)} 68 69 now := time.Now() 70 smallCap := "0.5" 71 smallerCap := "0.25" 72 largeCap := "2" 73 ds := &vega.DispatchStrategy{ 74 DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA, 75 LockPeriod: 2, 76 CapRewardFeeMultiple: &smallCap, 77 } 78 79 takerFeeContribution := map[string]*num.Uint{ 80 "p1": num.MustUintFromString("3000", 10), 81 "p2": num.MustUintFromString("6250", 10), 82 "p3": num.MustUintFromString("2500", 10), 83 "p4": num.MustUintFromString("7500", 10), 84 "p5": num.MustUintFromString("750", 10), 85 } 86 87 // we allow each to get up to 0.5 of the cap, all are within their limits so no real capping takes place 88 po := calculateRewardsByContributionIndividual("1", "asset", "accountID", num.NewUint(10000), partyContribution, rewardMultipliers, now, ds, takerFeeContribution) 89 90 require.Equal(t, "1500", po.partyToAmount["p1"].String()) 91 require.Equal(t, "3125", po.partyToAmount["p2"].String()) 92 require.Equal(t, "1250", po.partyToAmount["p3"].String()) 93 require.Equal(t, "3750", po.partyToAmount["p4"].String()) 94 require.Equal(t, "375", po.partyToAmount["p5"].String()) 95 require.Equal(t, "asset", po.asset) 96 require.Equal(t, "1", po.epochSeq) 97 require.Equal(t, "accountID", po.fromAccount) 98 require.Equal(t, uint64(2), po.lockedForEpochs) 99 require.Equal(t, now.Unix(), po.timestamp) 100 require.Equal(t, "10000", po.totalReward.String()) 101 102 // we allow each to get up to 0.25 of the cap, all are capped at their maximum, no one gets additional amount 103 // only 0.5 of the reward is paid 104 ds.CapRewardFeeMultiple = &smallerCap 105 po = calculateRewardsByContributionIndividual("1", "asset", "accountID", num.NewUint(10000), partyContribution, rewardMultipliers, now, ds, takerFeeContribution) 106 107 require.Equal(t, "750", po.partyToAmount["p1"].String()) 108 require.Equal(t, "1562", po.partyToAmount["p2"].String()) 109 require.Equal(t, "625", po.partyToAmount["p3"].String()) 110 require.Equal(t, "1875", po.partyToAmount["p4"].String()) 111 require.Equal(t, "187", po.partyToAmount["p5"].String()) 112 require.Equal(t, "asset", po.asset) 113 require.Equal(t, "1", po.epochSeq) 114 require.Equal(t, "accountID", po.fromAccount) 115 require.Equal(t, uint64(2), po.lockedForEpochs) 116 require.Equal(t, now.Unix(), po.timestamp) 117 require.Equal(t, "4999", po.totalReward.String()) 118 119 // p1 and p2 do not contribute anything to taker fees 120 takerFeeContribution = map[string]*num.Uint{ 121 "p3": num.MustUintFromString("2000", 10), 122 "p4": num.MustUintFromString("1000", 10), 123 "p5": num.MustUintFromString("750", 10), 124 } 125 126 ds.CapRewardFeeMultiple = &largeCap 127 po = calculateRewardsByContributionIndividual("1", "asset", "accountID", num.NewUint(10000), partyContribution, rewardMultipliers, now, ds, takerFeeContribution) 128 129 // given that the uncapped breakdown is 130 // uncapped p3=1250 score=0.05405405405 capped=4000 131 // uncapped p4=3750 score=0.3243243243 capped=2000 132 // uncapped p5=375 score=0.02702702703 capped=1500 133 // we expect the following to happen: 134 // after the first round: 135 // p3=1250 136 // p4=2000 137 // p5=375 138 // we have 10000-1250-2000-375=6375 remaining 139 // after the second round: 140 // p3=2046 141 // p4=2000 142 // p5=614 143 // after the third round: 144 // p3=2713 145 // p4=2000 146 // p5=814 147 // after the fourth round: 148 // p3=3272 149 // p4=2000 150 // p5=981 151 // after the fifth round: 152 // p3=3740 153 // p4=2000 154 // p5=1121 155 // after the sixth round: 156 // p3=4000 157 // p4=2000 158 // p5=1238 159 // after the seventh round: 160 // p3=4000 161 // p4=2000 162 // p5=1341 163 // after the eighth round: 164 // p3=4000 165 // p4=2000 166 // p5=1440 167 // after the ninth round: 168 // p3=4000 169 // p4=2000 170 // p5=1500 171 require.Equal(t, "4000", po.partyToAmount["p3"].String()) 172 require.Equal(t, "2000", po.partyToAmount["p4"].String()) 173 require.Equal(t, "1500", po.partyToAmount["p5"].String()) 174 require.Equal(t, "asset", po.asset) 175 require.Equal(t, "1", po.epochSeq) 176 require.Equal(t, "accountID", po.fromAccount) 177 require.Equal(t, uint64(2), po.lockedForEpochs) 178 require.Equal(t, now.Unix(), po.timestamp) 179 require.Equal(t, "7500", po.totalReward.String()) 180 } 181 182 func TestCalculateRewardsByContributionIndividualRanking(t *testing.T) { 183 partyContribution := []*types.PartyContributionScore{ 184 {Party: "p1", Score: num.DecimalFromFloat(0.6)}, 185 {Party: "p2", Score: num.DecimalFromFloat(0.5)}, 186 {Party: "p3", Score: num.DecimalFromFloat(0.1)}, 187 {Party: "p4", Score: num.DecimalFromFloat(0.6)}, 188 {Party: "p5", Score: num.DecimalFromFloat(0.05)}, 189 } 190 191 rewardMultipliers := map[string]num.Decimal{"p1": num.DecimalFromInt64(2), "p2": num.DecimalFromInt64(4)} 192 193 now := time.Now() 194 ds := &vega.DispatchStrategy{ 195 DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK, 196 LockPeriod: 2, 197 RankTable: []*vega.Rank{ 198 {StartRank: 1, ShareRatio: 10}, 199 {StartRank: 2, ShareRatio: 5}, 200 {StartRank: 4, ShareRatio: 0}, 201 }, 202 } 203 po := calculateRewardsByContributionIndividual("1", "asset", "accountID", num.NewUint(10000), partyContribution, rewardMultipliers, now, ds, nil) 204 205 require.Equal(t, 3, len(po.partyToAmount)) 206 require.Equal(t, "4000", po.partyToAmount["p1"].String()) 207 require.Equal(t, "4000", po.partyToAmount["p2"].String()) 208 require.Equal(t, "2000", po.partyToAmount["p4"].String()) 209 require.Equal(t, "asset", po.asset) 210 require.Equal(t, "1", po.epochSeq) 211 require.Equal(t, "accountID", po.fromAccount) 212 require.Equal(t, uint64(2), po.lockedForEpochs) 213 require.Equal(t, now.Unix(), po.timestamp) 214 require.Equal(t, "10000", po.totalReward.String()) 215 } 216 217 func TestCalculateRewardsByContributionTeamsRank(t *testing.T) { 218 teamContribution := []*types.PartyContributionScore{ 219 {Party: "t1", Score: num.DecimalFromFloat(0.6)}, 220 {Party: "t2", Score: num.DecimalFromFloat(0.5)}, 221 {Party: "t3", Score: num.DecimalFromFloat(0.1)}, 222 {Party: "t4", Score: num.DecimalFromFloat(0.6)}, 223 {Party: "t5", Score: num.DecimalFromFloat(0.2)}, 224 } 225 226 t1PartyContribution := []*types.PartyContributionScore{ 227 {Party: "p11", Score: num.DecimalFromFloat(0.2)}, 228 {Party: "p12", Score: num.DecimalFromFloat(0.5)}, 229 } 230 231 t2PartyContribution := []*types.PartyContributionScore{ 232 {Party: "p21", Score: num.DecimalFromFloat(0.05)}, 233 {Party: "p22", Score: num.DecimalFromFloat(0.3)}, 234 } 235 236 t3PartyContribution := []*types.PartyContributionScore{ 237 {Party: "p31", Score: num.DecimalFromFloat(0.2)}, 238 {Party: "p32", Score: num.DecimalFromFloat(0.3)}, 239 {Party: "p33", Score: num.DecimalFromFloat(0.6)}, 240 } 241 t4PartyContribution := []*types.PartyContributionScore{ 242 {Party: "p41", Score: num.DecimalFromFloat(0.2)}, 243 } 244 t5PartyContribution := []*types.PartyContributionScore{ 245 {Party: "p51", Score: num.DecimalFromFloat(0.2)}, 246 {Party: "p52", Score: num.DecimalFromFloat(0.8)}, 247 } 248 249 teamToPartyContribution := map[string][]*types.PartyContributionScore{ 250 "t1": t1PartyContribution, 251 "t2": t2PartyContribution, 252 "t3": t3PartyContribution, 253 "t4": t4PartyContribution, 254 "t5": t5PartyContribution, 255 } 256 257 rewardMultipliers := map[string]num.Decimal{"p11": num.DecimalFromFloat(2), "p12": num.DecimalFromFloat(3), "p22": num.DecimalFromFloat(1.5), "p32": num.DecimalFromInt64(4), "p41": num.DecimalFromFloat(2.5), "p51": num.DecimalFromInt64(6)} 258 259 now := time.Now() 260 ds := &vega.DispatchStrategy{ 261 DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_RANK, 262 LockPeriod: 2, 263 RankTable: []*vega.Rank{ 264 {StartRank: 1, ShareRatio: 10}, 265 {StartRank: 2, ShareRatio: 5}, 266 {StartRank: 4, ShareRatio: 0}, 267 }, 268 } 269 po := calculateRewardsByContributionTeam("1", "asset", "accountID", num.NewUint(10000), teamContribution, teamToPartyContribution, rewardMultipliers, now, ds, nil) 270 271 // t1: 0.4 272 // t2: 0.2 273 // t4: 0.4 274 275 // r11 = 2 276 // r12 = 3 277 // ===================== 278 // s11 = 0.4 * 0.4 = 0.24 * 10000 = 1600 279 // s12 = 0.4 * 0.6 = 0.16 * 10000 = 2400 280 281 // r21 = 1 282 // r22 = 1.5 283 // ===================== 284 // s21 = 0.2 * 0.4 = 0.08 * 10000 = 800 285 // s22 = 0.2 * 0.6 = 0.12 * 10000 = 1200 286 287 // p41 = 1 288 // ===================== 289 // s41 = 0.4 * 10000 = 4000 290 require.Equal(t, "asset", po.asset) 291 require.Equal(t, "1", po.epochSeq) 292 require.Equal(t, "accountID", po.fromAccount) 293 require.Equal(t, uint64(2), po.lockedForEpochs) 294 require.Equal(t, now.Unix(), po.timestamp) 295 require.Equal(t, "1600", po.partyToAmount["p11"].String()) 296 require.Equal(t, "2400", po.partyToAmount["p12"].String()) 297 require.Equal(t, "800", po.partyToAmount["p21"].String()) 298 require.Equal(t, "1200", po.partyToAmount["p22"].String()) 299 require.Equal(t, "4000", po.partyToAmount["p41"].String()) 300 require.Equal(t, "10000", po.totalReward.String()) 301 } 302 303 func TestCalculateRewardsByContributionTeamsProRata(t *testing.T) { 304 teamContribution := []*types.PartyContributionScore{ 305 {Party: "t1", Score: num.DecimalFromFloat(0.6)}, 306 {Party: "t2", Score: num.DecimalFromFloat(0.5)}, 307 {Party: "t3", Score: num.DecimalFromFloat(0.1)}, 308 {Party: "t4", Score: num.DecimalFromFloat(0.6)}, 309 {Party: "t5", Score: num.DecimalFromFloat(0.2)}, 310 } 311 312 t1PartyContribution := []*types.PartyContributionScore{ 313 {Party: "p11", Score: num.DecimalFromFloat(0.2)}, 314 {Party: "p12", Score: num.DecimalFromFloat(0.5)}, 315 } 316 317 t2PartyContribution := []*types.PartyContributionScore{ 318 {Party: "p21", Score: num.DecimalFromFloat(0.05)}, 319 {Party: "p22", Score: num.DecimalFromFloat(0.3)}, 320 } 321 322 t3PartyContribution := []*types.PartyContributionScore{ 323 {Party: "p31", Score: num.DecimalFromFloat(0.2)}, 324 {Party: "p32", Score: num.DecimalFromFloat(0.3)}, 325 {Party: "p33", Score: num.DecimalFromFloat(0.6)}, 326 } 327 t4PartyContribution := []*types.PartyContributionScore{ 328 {Party: "p41", Score: num.DecimalFromFloat(0.2)}, 329 } 330 t5PartyContribution := []*types.PartyContributionScore{ 331 {Party: "p51", Score: num.DecimalFromFloat(0.2)}, 332 {Party: "p52", Score: num.DecimalFromFloat(0.8)}, 333 } 334 335 teamToPartyContribution := map[string][]*types.PartyContributionScore{ 336 "t1": t1PartyContribution, 337 "t2": t2PartyContribution, 338 "t3": t3PartyContribution, 339 "t4": t4PartyContribution, 340 "t5": t5PartyContribution, 341 } 342 343 rewardMultipliers := map[string]num.Decimal{"p11": num.DecimalFromFloat(2), "p12": num.DecimalFromFloat(3), "p22": num.DecimalFromFloat(1.5), "p32": num.DecimalFromInt64(3), "p41": num.DecimalFromFloat(2.5), "p51": num.DecimalFromInt64(7)} 344 345 now := time.Now() 346 ds := &vega.DispatchStrategy{ 347 DistributionStrategy: vega.DistributionStrategy_DISTRIBUTION_STRATEGY_PRO_RATA, 348 LockPeriod: 2, 349 } 350 351 po := calculateRewardsByContributionTeam("1", "asset", "accountID", num.NewUint(10000), teamContribution, teamToPartyContribution, rewardMultipliers, now, ds, nil) 352 353 // t1: 0.6/2 = 0.3 354 // t2: 0.5/2 = 0.25 355 // t3: 0.1/2 = 0.05 356 // t4: 0.6/2 = 0.3 357 // t5: 0.2/2 = 0.1 358 359 // r11 = 2 = 0.4 360 // r12 = 3 = 0.6 361 // ===================== 362 // s11 = 0.3 * 0.4 = 0.12 * 10000 = 1200 363 // s12 = 0.3 * 0.6 = 0.18 * 10000 = 1800 364 365 // r21 = 1 366 // r22 = 1.5 367 // ===================== 368 // s21 = 0.25 * 0.4 = 0.1 * 10000 = 1000 369 // s22 = 0.25 * 0.5 = 0.15 * 10000 = 1500 370 371 // r31 = 1 372 // r32 = 3 373 // r33 = 1 374 // ===================== 375 // s31 = 0.05 * 0.2 = 0.01 * 10000 = 100 376 // s32 = 0.05 * 0.6 = 0.03 * 10000 = 300 377 // s32 = 0.05 * 0.2 = 0.01 * 10000 = 100 378 379 // r41 = 2.5 380 // ===================== 381 // s41 = 0.3 * 10000 = 3000 382 383 // r51 = 6 384 // r52 = 1 385 // ===================== 386 // s51 = 0.1 * 0.875 = 0.0875 * 10000 = 875 387 // s52 = 0.1 * 0.125 = 0.0125 * 10000 = 125 388 389 require.Equal(t, "asset", po.asset) 390 require.Equal(t, "1", po.epochSeq) 391 require.Equal(t, "accountID", po.fromAccount) 392 require.Equal(t, uint64(2), po.lockedForEpochs) 393 require.Equal(t, now.Unix(), po.timestamp) 394 require.Equal(t, "1200", po.partyToAmount["p11"].String()) 395 require.Equal(t, "1800", po.partyToAmount["p12"].String()) 396 require.Equal(t, "1000", po.partyToAmount["p21"].String()) 397 require.Equal(t, "1500", po.partyToAmount["p22"].String()) 398 require.Equal(t, "100", po.partyToAmount["p31"].String()) 399 require.Equal(t, "300", po.partyToAmount["p32"].String()) 400 require.Equal(t, "100", po.partyToAmount["p33"].String()) 401 require.Equal(t, "3000", po.partyToAmount["p41"].String()) 402 require.Equal(t, "875", po.partyToAmount["p51"].String()) 403 require.Equal(t, "125", po.partyToAmount["p52"].String()) 404 require.Equal(t, "10000", po.totalReward.String()) 405 }