Files
zeekay f90e6bb5bb refactor(p,x): generic math.Add/Mul, Codec-free sort, LockOut.Bytes
- safemath.Add64/Mul64 → Add/Mul (Go generics; same wire/bounds
  behaviour, fewer per-width copies). Sweep across p/{reward,utxo,txs}
  and the x-side test helpers.
- lux.SortTransferableOutputs / SyntacticVerifier UTXO/Output helpers:
  drop the threaded Codec arg; sort key is now Bytes()+assetID, no
  codec roundtrip per compare. Callers in p/utxo/handler.go,
  p/txs/{add_*,base_tx,builder,executor/*}, p/state, and
  x/txs/executor/syntactic_verifier follow.
- stakeable.LockOut: implement Bytes() returning
  0xFF || BE(Locktime) || inner.Bytes(). 0xFF marker keeps locked
  outputs sorted strictly AFTER unlocked fxs primitives, preserving
  the legacy ordering invariant the SyntacticVerify expects.
- Update test helpers in p/block/{builder,executor}, p/network, p/txs
  to construct outputs without the codec arg and rely on the new sort.
2026-06-04 17:05:03 -07:00

84 lines
3.0 KiB
Go

// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
// See the file LICENSE for licensing terms.
package reward
import (
"math/big"
"time"
"github.com/luxfi/math"
)
var _ Calculator = (*calculator)(nil)
type Calculator interface {
Calculate(stakedDuration time.Duration, stakedAmount, currentSupply uint64) uint64
}
type calculator struct {
maxSubMinConsumptionRate *big.Int
minConsumptionRate *big.Int
mintingPeriod *big.Int
supplyCap uint64
}
func NewCalculator(c Config) Calculator {
return &calculator{
maxSubMinConsumptionRate: new(big.Int).SetUint64(c.MaxConsumptionRate - c.MinConsumptionRate),
minConsumptionRate: new(big.Int).SetUint64(c.MinConsumptionRate),
mintingPeriod: new(big.Int).SetUint64(uint64(c.MintingPeriod)),
supplyCap: c.SupplyCap,
}
}
// Reward returns the amount of tokens to reward the staker with.
//
// RemainingSupply = SupplyCap - ExistingSupply
// PortionOfExistingSupply = StakedAmount / ExistingSupply
// PortionOfStakingDuration = StakingDuration / MaximumStakingDuration
// MintingRate = MinMintingRate + MaxSubMinMintingRate * PortionOfStakingDuration
// Reward = RemainingSupply * PortionOfExistingSupply * MintingRate * PortionOfStakingDuration
func (c *calculator) Calculate(stakedDuration time.Duration, stakedAmount, currentSupply uint64) uint64 {
bigStakedDuration := new(big.Int).SetUint64(uint64(stakedDuration))
bigStakedAmount := new(big.Int).SetUint64(stakedAmount)
bigCurrentSupply := new(big.Int).SetUint64(currentSupply)
adjustedConsumptionRateNumerator := new(big.Int).Mul(c.maxSubMinConsumptionRate, bigStakedDuration)
adjustedMinConsumptionRateNumerator := new(big.Int).Mul(c.minConsumptionRate, c.mintingPeriod)
adjustedConsumptionRateNumerator.Add(adjustedConsumptionRateNumerator, adjustedMinConsumptionRateNumerator)
adjustedConsumptionRateDenominator := new(big.Int).Mul(c.mintingPeriod, consumptionRateDenominator)
remainingSupply := c.supplyCap - currentSupply
reward := new(big.Int).SetUint64(remainingSupply)
reward.Mul(reward, adjustedConsumptionRateNumerator)
reward.Mul(reward, bigStakedAmount)
reward.Mul(reward, bigStakedDuration)
reward.Div(reward, adjustedConsumptionRateDenominator)
reward.Div(reward, bigCurrentSupply)
reward.Div(reward, c.mintingPeriod)
if !reward.IsUint64() {
return remainingSupply
}
finalReward := reward.Uint64()
return min(remainingSupply, finalReward)
}
// Split [totalAmount] into [totalAmount * shares percentage] and the remainder.
//
// Invariant: [shares] <= [PercentDenominator]
func Split(totalAmount uint64, shares uint32) (uint64, uint64) {
remainderShares := PercentDenominator - uint64(shares)
remainderAmount := remainderShares * (totalAmount / PercentDenominator)
// Delay rounding as long as possible for small numbers
if optimisticReward, err := math.Mul(remainderShares, totalAmount); err == nil {
remainderAmount = optimisticReward / PercentDenominator
}
amountFromShares := totalAmount - remainderAmount
return amountFromShares, remainderAmount
}