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  }