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  }