github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/reward/calculator_test.go (about) 1 // Copyright (C) 2019-2024, Ava Labs, Inc. All rights reserved. 2 // See the file LICENSE for licensing terms. 3 4 package reward 5 6 import ( 7 "fmt" 8 "math" 9 "testing" 10 "time" 11 12 "github.com/stretchr/testify/require" 13 14 "github.com/MetalBlockchain/metalgo/utils/units" 15 ) 16 17 const ( 18 defaultMinStakingDuration = 24 * time.Hour 19 defaultMaxStakingDuration = 365 * 24 * time.Hour 20 21 defaultMinValidatorStake = 5 * units.MilliAvax 22 ) 23 24 var defaultConfig = Config{ 25 MaxConsumptionRate: .12 * PercentDenominator, 26 MinConsumptionRate: .10 * PercentDenominator, 27 MintingPeriod: 365 * 24 * time.Hour, 28 SupplyCap: 720 * units.MegaAvax, 29 } 30 31 func TestLongerDurationBonus(t *testing.T) { 32 c := NewCalculator(defaultConfig) 33 shortDuration := 24 * time.Hour 34 totalDuration := 365 * 24 * time.Hour 35 shortBalance := units.KiloAvax 36 for i := 0; i < int(totalDuration/shortDuration); i++ { 37 r := c.Calculate(shortDuration, shortBalance, 359*units.MegaAvax+shortBalance) 38 shortBalance += r 39 } 40 reward := c.Calculate(totalDuration%shortDuration, shortBalance, 359*units.MegaAvax+shortBalance) 41 shortBalance += reward 42 43 longBalance := units.KiloAvax 44 longBalance += c.Calculate(totalDuration, longBalance, 359*units.MegaAvax+longBalance) 45 require.Less(t, shortBalance, longBalance, "should promote stakers to stake longer") 46 } 47 48 func TestRewards(t *testing.T) { 49 c := NewCalculator(defaultConfig) 50 tests := []struct { 51 duration time.Duration 52 stakeAmount uint64 53 existingAmount uint64 54 expectedReward uint64 55 }{ 56 // Max duration: 57 { // (720M - 360M) * (1M / 360M) * 12% 58 duration: defaultMaxStakingDuration, 59 stakeAmount: units.MegaAvax, 60 existingAmount: 360 * units.MegaAvax, 61 expectedReward: 120 * units.KiloAvax, 62 }, 63 { // (720M - 400M) * (1M / 400M) * 12% 64 duration: defaultMaxStakingDuration, 65 stakeAmount: units.MegaAvax, 66 existingAmount: 400 * units.MegaAvax, 67 expectedReward: 96 * units.KiloAvax, 68 }, 69 { // (720M - 400M) * (2M / 400M) * 12% 70 duration: defaultMaxStakingDuration, 71 stakeAmount: 2 * units.MegaAvax, 72 existingAmount: 400 * units.MegaAvax, 73 expectedReward: 192 * units.KiloAvax, 74 }, 75 { // (720M - 720M) * (1M / 720M) * 12% 76 duration: defaultMaxStakingDuration, 77 stakeAmount: units.MegaAvax, 78 existingAmount: defaultConfig.SupplyCap, 79 expectedReward: 0, 80 }, 81 // Min duration: 82 // (720M - 360M) * (1M / 360M) * (10% + 2% * MinimumStakingDuration / MaximumStakingDuration) * MinimumStakingDuration / MaximumStakingDuration 83 { 84 duration: defaultMinStakingDuration, 85 stakeAmount: units.MegaAvax, 86 existingAmount: 360 * units.MegaAvax, 87 expectedReward: 274122724713, 88 }, 89 // (720M - 360M) * (.005 / 360M) * (10% + 2% * MinimumStakingDuration / MaximumStakingDuration) * MinimumStakingDuration / MaximumStakingDuration 90 { 91 duration: defaultMinStakingDuration, 92 stakeAmount: defaultMinValidatorStake, 93 existingAmount: 360 * units.MegaAvax, 94 expectedReward: 1370, 95 }, 96 // (720M - 400M) * (1M / 400M) * (10% + 2% * MinimumStakingDuration / MaximumStakingDuration) * MinimumStakingDuration / MaximumStakingDuration 97 { 98 duration: defaultMinStakingDuration, 99 stakeAmount: units.MegaAvax, 100 existingAmount: 400 * units.MegaAvax, 101 expectedReward: 219298179771, 102 }, 103 // (720M - 400M) * (2M / 400M) * (10% + 2% * MinimumStakingDuration / MaximumStakingDuration) * MinimumStakingDuration / MaximumStakingDuration 104 { 105 duration: defaultMinStakingDuration, 106 stakeAmount: 2 * units.MegaAvax, 107 existingAmount: 400 * units.MegaAvax, 108 expectedReward: 438596359542, 109 }, 110 // (720M - 720M) * (1M / 720M) * (10% + 2% * MinimumStakingDuration / MaximumStakingDuration) * MinimumStakingDuration / MaximumStakingDuration 111 { 112 duration: defaultMinStakingDuration, 113 stakeAmount: units.MegaAvax, 114 existingAmount: defaultConfig.SupplyCap, 115 expectedReward: 0, 116 }, 117 } 118 for _, test := range tests { 119 name := fmt.Sprintf("reward(%s,%d,%d)==%d", 120 test.duration, 121 test.stakeAmount, 122 test.existingAmount, 123 test.expectedReward, 124 ) 125 t.Run(name, func(t *testing.T) { 126 reward := c.Calculate( 127 test.duration, 128 test.stakeAmount, 129 test.existingAmount, 130 ) 131 require.Equal(t, test.expectedReward, reward) 132 }) 133 } 134 } 135 136 func TestRewardsOverflow(t *testing.T) { 137 var ( 138 maxSupply uint64 = math.MaxUint64 139 initialSupply uint64 = 1 140 ) 141 c := NewCalculator(Config{ 142 MaxConsumptionRate: PercentDenominator, 143 MinConsumptionRate: PercentDenominator, 144 MintingPeriod: defaultMinStakingDuration, 145 SupplyCap: maxSupply, 146 }) 147 reward := c.Calculate( 148 defaultMinStakingDuration, 149 maxSupply, // The staked amount is larger than the current supply 150 initialSupply, 151 ) 152 require.Equal(t, maxSupply-initialSupply, reward) 153 } 154 155 func TestRewardsMint(t *testing.T) { 156 var ( 157 maxSupply uint64 = 1000 158 initialSupply uint64 = 1 159 ) 160 c := NewCalculator(Config{ 161 MaxConsumptionRate: PercentDenominator, 162 MinConsumptionRate: PercentDenominator, 163 MintingPeriod: defaultMinStakingDuration, 164 SupplyCap: maxSupply, 165 }) 166 rewards := c.Calculate( 167 defaultMinStakingDuration, 168 maxSupply, // The staked amount is larger than the current supply 169 initialSupply, 170 ) 171 require.Equal(t, maxSupply-initialSupply, rewards) 172 } 173 174 func TestSplit(t *testing.T) { 175 tests := []struct { 176 amount uint64 177 shares uint32 178 expectedSplit uint64 179 }{ 180 { 181 amount: 1000, 182 shares: PercentDenominator / 2, 183 expectedSplit: 500, 184 }, 185 { 186 amount: 1, 187 shares: PercentDenominator, 188 expectedSplit: 1, 189 }, 190 { 191 amount: 1, 192 shares: PercentDenominator - 1, 193 expectedSplit: 1, 194 }, 195 { 196 amount: 1, 197 shares: 1, 198 expectedSplit: 1, 199 }, 200 { 201 amount: 1, 202 shares: 0, 203 expectedSplit: 0, 204 }, 205 { 206 amount: 9223374036974675809, 207 shares: 2, 208 expectedSplit: 18446748749757, 209 }, 210 { 211 amount: 9223374036974675809, 212 shares: PercentDenominator, 213 expectedSplit: 9223374036974675809, 214 }, 215 { 216 amount: 9223372036855275808, 217 shares: PercentDenominator - 2, 218 expectedSplit: 9223353590111202098, 219 }, 220 { 221 amount: 9223372036855275808, 222 shares: 2, 223 expectedSplit: 18446744349518, 224 }, 225 } 226 for _, test := range tests { 227 t.Run(fmt.Sprintf("%d_%d", test.amount, test.shares), func(t *testing.T) { 228 require := require.New(t) 229 230 split, remainder := Split(test.amount, test.shares) 231 require.Equal(test.expectedSplit, split) 232 require.Equal(test.amount-test.expectedSplit, remainder) 233 }) 234 } 235 }