github.com/MetalBlockchain/metalgo@v1.11.9/vms/platformvm/reward/calculator.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  	"math/big"
     8  	"time"
     9  
    10  	"github.com/MetalBlockchain/metalgo/utils/math"
    11  )
    12  
    13  var _ Calculator = (*calculator)(nil)
    14  
    15  type Calculator interface {
    16  	Calculate(stakedDuration time.Duration, stakedAmount, currentSupply uint64) uint64
    17  }
    18  
    19  type calculator struct {
    20  	maxSubMinConsumptionRate *big.Int
    21  	minConsumptionRate       *big.Int
    22  	mintingPeriod            *big.Int
    23  	supplyCap                uint64
    24  }
    25  
    26  func NewCalculator(c Config) Calculator {
    27  	return &calculator{
    28  		maxSubMinConsumptionRate: new(big.Int).SetUint64(c.MaxConsumptionRate - c.MinConsumptionRate),
    29  		minConsumptionRate:       new(big.Int).SetUint64(c.MinConsumptionRate),
    30  		mintingPeriod:            new(big.Int).SetUint64(uint64(c.MintingPeriod)),
    31  		supplyCap:                c.SupplyCap,
    32  	}
    33  }
    34  
    35  // Reward returns the amount of tokens to reward the staker with.
    36  //
    37  // RemainingSupply = SupplyCap - ExistingSupply
    38  // PortionOfExistingSupply = StakedAmount / ExistingSupply
    39  // PortionOfStakingDuration = StakingDuration / MaximumStakingDuration
    40  // MintingRate = MinMintingRate + MaxSubMinMintingRate * PortionOfStakingDuration
    41  // Reward = RemainingSupply * PortionOfExistingSupply * MintingRate * PortionOfStakingDuration
    42  func (c *calculator) Calculate(stakedDuration time.Duration, stakedAmount, currentSupply uint64) uint64 {
    43  	bigStakedDuration := new(big.Int).SetUint64(uint64(stakedDuration))
    44  	bigStakedAmount := new(big.Int).SetUint64(stakedAmount)
    45  	bigCurrentSupply := new(big.Int).SetUint64(currentSupply)
    46  
    47  	adjustedConsumptionRateNumerator := new(big.Int).Mul(c.maxSubMinConsumptionRate, bigStakedDuration)
    48  	adjustedMinConsumptionRateNumerator := new(big.Int).Mul(c.minConsumptionRate, c.mintingPeriod)
    49  	adjustedConsumptionRateNumerator.Add(adjustedConsumptionRateNumerator, adjustedMinConsumptionRateNumerator)
    50  	adjustedConsumptionRateDenominator := new(big.Int).Mul(c.mintingPeriod, consumptionRateDenominator)
    51  
    52  	remainingSupply := c.supplyCap - currentSupply
    53  	reward := new(big.Int).SetUint64(remainingSupply)
    54  	reward.Mul(reward, adjustedConsumptionRateNumerator)
    55  	reward.Mul(reward, bigStakedAmount)
    56  	reward.Mul(reward, bigStakedDuration)
    57  	reward.Div(reward, adjustedConsumptionRateDenominator)
    58  	reward.Div(reward, bigCurrentSupply)
    59  	reward.Div(reward, c.mintingPeriod)
    60  
    61  	if !reward.IsUint64() {
    62  		return remainingSupply
    63  	}
    64  
    65  	finalReward := reward.Uint64()
    66  	if finalReward > remainingSupply {
    67  		return remainingSupply
    68  	}
    69  
    70  	return finalReward
    71  }
    72  
    73  // Split [totalAmount] into [totalAmount * shares percentage] and the remainder.
    74  //
    75  // Invariant: [shares] <= [PercentDenominator]
    76  func Split(totalAmount uint64, shares uint32) (uint64, uint64) {
    77  	remainderShares := PercentDenominator - uint64(shares)
    78  	remainderAmount := remainderShares * (totalAmount / PercentDenominator)
    79  
    80  	// Delay rounding as long as possible for small numbers
    81  	if optimisticReward, err := math.Mul64(remainderShares, totalAmount); err == nil {
    82  		remainderAmount = optimisticReward / PercentDenominator
    83  	}
    84  
    85  	amountFromShares := totalAmount - remainderAmount
    86  	return amountFromShares, remainderAmount
    87  }