diff --git a/packages/mana/balance.go b/packages/mana/balance.go index 85ed7b4fc84f05a8a5b1c1bc64c74b9c3cd7b2a0..ebb9c4e5d42ad8afd3bd01bf9ff8234f91c3642f 100644 --- a/packages/mana/balance.go +++ b/packages/mana/balance.go @@ -25,10 +25,10 @@ func (balance *Balance) GetValue() uint64 { } func (balance *Balance) AddTransfer(movedCoins uint64, receivedTime uint64, spentTime uint64) { - gainedMana, lastErosion, _ := balance.calculator.ManaOfTransferDiscrete(movedCoins, receivedTime, spentTime) + gainedMana, _ := balance.calculator.GenerateMana(movedCoins, spentTime-receivedTime) - if lastErosion >= balance.lastErosion { - balance.Erode(lastErosion) + if spentTime >= balance.lastErosion { + balance.Erode(spentTime) } else { fmt.Println("empty") // revert old actions @@ -41,7 +41,7 @@ func (balance *Balance) AddTransfer(movedCoins uint64, receivedTime uint64, spen func (balance *Balance) Erode(erosionTime uint64) { if balance.lastErosion <= erosionTime { - balance.currentBalance, balance.lastErosion, _ = balance.calculator.ErodedManaDiscrete(balance.currentBalance, balance.lastErosion, erosionTime) + balance.currentBalance, _ = balance.calculator.ErodeMana(balance.currentBalance, erosionTime-balance.lastErosion) } else { fmt.Println("empty") // revert old erosions diff --git a/packages/mana/calculator.go b/packages/mana/calculator.go index 7ee676f890085b8067529f608229f978bd64d416..d1ff397f51616f1101e2b1ce2ebd37fde8ec6084 100644 --- a/packages/mana/calculator.go +++ b/packages/mana/calculator.go @@ -4,135 +4,60 @@ import ( "math" ) +// A calculator that can be used to calculate the changes of mana due to erosion or mana generation. type Calculator struct { - decayInterval uint64 - decayRate float64 - coinsPerMana uint64 - decayFactor float64 - manaScaleFactor float64 + decayInterval float64 + decayRate float64 + options *CalculatorOptions + tokenSupplyScalefactor float64 } -func NewCalculator(decayInterval uint64, decayRate float64, coinsPerMana uint64) (result *Calculator) { - result = &Calculator{ +// Creates a new calculator that can be used to calculate the changes of mana due to erosion or mana generation. +func NewCalculator(decayInterval float64, decayRate float64, optionalOptions ...CalculatorOption) *Calculator { + return &Calculator{ + // store key settings decayInterval: decayInterval, decayRate: decayRate, - coinsPerMana: coinsPerMana, - decayFactor: 1 - decayRate, - } - // make mana reach exactly the token supply - result.manaScaleFactor = result.decayRate / result.decayFactor + // configure optional parameters + options: DEFAULT_OPTIONS.Override(optionalOptions...), - return -} - -func (calculator *Calculator) ManaOfTransferDiscrete(movedCoins uint64, receivedTime uint64, spentTime uint64) (result uint64, lastErosion uint64, roundingError float64) { - if spentTime <= receivedTime { - return 0, 0, 0 + // derive important factors ... + // ... make mana reach exactly the token supply as it's max value (n coins => n mana) + tokenSupplyScalefactor: decayRate / (1 - decayRate), } - - baseMana := movedCoins / calculator.coinsPerMana - scaleFactor := 1 - calculator.decayRate - erosionIntervals := spentTime/calculator.decayInterval - receivedTime/calculator.decayInterval - - var totalManaReceived float64 - - switch true { - // no decay intervals - case erosionIntervals == 0: - lastIntervalDuration := spentTime - receivedTime - if lastIntervalDuration != 0 { - totalManaReceived += float64(CoinTimeDestroyed(baseMana, lastIntervalDuration)) - } - - lastErosion = 0 - - // only 1 decay interval - case erosionIntervals == 1: - firstIntervalDuration := calculator.decayInterval - receivedTime%calculator.decayInterval - gainsInFirstInterval := float64(CoinTimeDestroyed(baseMana, firstIntervalDuration)) * math.Pow(scaleFactor, float64(erosionIntervals)) - totalManaReceived += gainsInFirstInterval - - lastIntervalDuration := spentTime % calculator.decayInterval - if lastIntervalDuration != 0 { - totalManaReceived += float64(CoinTimeDestroyed(baseMana, lastIntervalDuration)) - } - - lastErosion = spentTime - lastIntervalDuration - - // multiple decay intervals - default: - firstIntervalDuration := calculator.decayInterval - receivedTime%calculator.decayInterval - gainsInFirstInterval := float64(CoinTimeDestroyed(baseMana, firstIntervalDuration)) * math.Pow(scaleFactor, float64(erosionIntervals)) - totalManaReceived += gainsInFirstInterval - - gainsInConsecutiveIntervals := float64(CoinTimeDestroyed(baseMana, calculator.decayInterval)) * scaleFactor * (1 - math.Pow(scaleFactor, float64(erosionIntervals-1))) / (1 - scaleFactor) - totalManaReceived += gainsInConsecutiveIntervals - - lastIntervalDuration := spentTime % calculator.decayInterval - if lastIntervalDuration != 0 { - totalManaReceived += float64(CoinTimeDestroyed(baseMana, lastIntervalDuration)) - } - - lastErosion = spentTime - lastIntervalDuration - } - - result = uint64(totalManaReceived) - roundingError = totalManaReceived - float64(result) - - return } -func (calculator *Calculator) ManaOfTransferContinuous(movedCoins uint64, heldTime uint64) (result uint64, roundingError float64) { +// Returns the amount of mana that was generated by holding the given amount of coins for the given time. +func (calculator *Calculator) GenerateMana(coins uint64, heldTime uint64) (result uint64, roundingError float64) { + // calculate results relativeDecayTime := float64(heldTime) / float64(calculator.decayInterval) + erosionFactor := (1 - math.Pow(1-calculator.decayRate, float64(relativeDecayTime+1)) - calculator.decayRate) / calculator.decayRate + gainedMana := float64(coins) * calculator.options.ManaScaleFactor * calculator.tokenSupplyScalefactor * erosionFactor - erosionFactor := (1-math.Pow(calculator.decayFactor, float64(relativeDecayTime+1)))/calculator.decayRate - 1 - - gainedMana := float64(movedCoins) * calculator.manaScaleFactor * erosionFactor - + // assign rounded results & determine roundingErrors result = uint64(math.Round(gainedMana)) roundingError = gainedMana - float64(result) return } -func (calculator *Calculator) ManaOfTransferContinuous1(movedCoins uint64, heldTime uint64) (result uint64, roundingError float64) { - gainsInConsecutiveIntervals := (float64(movedCoins) / calculator.decayRate) * (1 - math.Pow(math.E, -calculator.decayRate*float64(heldTime))) - - result = uint64(gainsInConsecutiveIntervals) - roundingError = gainsInConsecutiveIntervals - float64(result) - - return -} - -func (calculator *Calculator) ErodedManaContinuous(mana uint64, erosionTime uint64) (result uint64, roundingError float64) { - if erosionTime == 0 { +// Returns the amount of mana that is left after the erosion of the given amount for the given time. +func (calculator *Calculator) ErodeMana(mana uint64, decayTime uint64) (result uint64, roundingError float64) { + // if no time has passed -> return unchanged values + if decayTime == 0 { result = mana - roundingError = 0 return } + // calculate results growthFactor := math.Log(1-calculator.decayRate) / float64(calculator.decayInterval) - erodedMana := float64(mana) * math.Pow(math.E, growthFactor*float64(erosionTime)) + erodedMana := float64(mana) * math.Pow(math.E, growthFactor*float64(decayTime)) - result = uint64(erodedMana) + // assign rounded results & determine roundingErrors + result = uint64(math.Round(erodedMana)) roundingError = erodedMana - float64(result) return } - -func (calculator *Calculator) ErodedManaDiscrete(mana uint64, erosionStartTime uint64, erosionEndTime uint64) (result uint64, lastErosion uint64, roundingError float64) { - if erosionStartTime > erosionEndTime { - panic("negative erosion duration") - } - - erosionIntervals := erosionEndTime/calculator.decayInterval - erosionStartTime/calculator.decayInterval - erodedValue := math.Pow(float64(1-calculator.decayRate), float64(erosionIntervals)) * float64(mana) - - result = uint64(erodedValue) - lastErosion = erosionEndTime - roundingError = erodedValue - float64(result) - - return -} diff --git a/packages/mana/calculator_options.go b/packages/mana/calculator_options.go new file mode 100644 index 0000000000000000000000000000000000000000..833bf59d44b83d88215d6c11f2bd38eb5be75eb3 --- /dev/null +++ b/packages/mana/calculator_options.go @@ -0,0 +1,38 @@ +package mana + +// region default options ////////////////////////////////////////////////////////////////////////////////////////////// + +var DEFAULT_OPTIONS = &CalculatorOptions{ + ManaScaleFactor: 1, +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region calculator options /////////////////////////////////////////////////////////////////////////////////////////// + +func ManaScaleFactor(manaScaleFactor float64) CalculatorOption { + return func(args *CalculatorOptions) { + args.ManaScaleFactor = manaScaleFactor + } +} + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + +// region type definitions ///////////////////////////////////////////////////////////////////////////////////////////// + +type CalculatorOptions struct { + ManaScaleFactor float64 +} + +func (options CalculatorOptions) Override(optionalOptions ...CalculatorOption) *CalculatorOptions { + result := &options + for _, option := range optionalOptions { + option(result) + } + + return result +} + +type CalculatorOption func(*CalculatorOptions) + +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/packages/mana/mana.go b/packages/mana/mana.go deleted file mode 100644 index 4855d809ad887fc908c8886fccbe6dfc1563aed2..0000000000000000000000000000000000000000 --- a/packages/mana/mana.go +++ /dev/null @@ -1,11 +0,0 @@ -package mana - -func CoinTimeDestroyed(transferredValue uint64, parkedTime uint64) uint64 { - return transferredValue * parkedTime -} - -const ( - DECAY_INTERVAL = 10 - DECAY_RATE = 0.01 - COINS_PER_MANA = 10 -) diff --git a/packages/mana/mana_test.go b/packages/mana/mana_test.go index bfb0589ee2d750c132c6833528428f94e273a0b3..01d7b50c380ecca138537838e52987a4fa59b91c 100644 --- a/packages/mana/mana_test.go +++ b/packages/mana/mana_test.go @@ -1,83 +1,47 @@ package mana import ( - "fmt" - "math" "testing" "github.com/magiconair/properties/assert" ) -func BenchmarkCalculator_ManaOfTransferContinuous(b *testing.B) { - calculator := NewCalculator(10, 0.1, 1) - - b.ResetTimer() +func BenchmarkCalculator_GenerateMana(b *testing.B) { + calculator := NewCalculator(10, 0.1) for i := 0; i < b.N; i++ { - calculator.ManaOfTransferContinuous(10000000, 100000000000) + calculator.GenerateMana(1000000, 100000000000) } } -func TestBalance_Erode(t *testing.T) { - calculator := NewCalculator(50, 0.1, 9) - balance := NewBalance(calculator) - - calculator1 := NewCalculator(5000, 0.2, 1) - - fmt.Println("===") - fmt.Println(calculator1.ManaOfTransferContinuous(1000, 5000000)) - fmt.Println(calculator.ManaOfTransferDiscrete(1000, 0, 50)) - - balance.AddTransfer(500, 0, 10) +func TestCalculator_GenerateMana(t *testing.T) { + calculator := NewCalculator(500, 0.1, ManaScaleFactor(2)) - assert.Equal(t, balance.GetValue(), uint64(450)) + generatedMana, _ := calculator.GenerateMana(1000, 0) + assert.Equal(t, generatedMana, uint64(0)) - balance.Erode(20) + generatedMana, _ = calculator.GenerateMana(1000, 500) + assert.Equal(t, generatedMana, uint64(200)) - assert.Equal(t, balance.GetValue(), uint64(405)) - - balance.Erode(40) - - assert.Equal(t, balance.GetValue(), uint64(328)) + generatedMana, _ = calculator.GenerateMana(1000, 5000000) + assert.Equal(t, generatedMana, uint64(2000)) } -func calcManaContinuous(calculator *Calculator, coins uint64, timeHeld uint64) (result uint64, roundingError float64) { - scaleFactor := 1 - math.Pow(1-calculator.decayRate, float64(timeHeld)/float64(calculator.decayInterval)) - fmt.Println(scaleFactor) - erodedGains := float64(coins) * float64(calculator.decayInterval) * scaleFactor - - result = uint64(erodedGains) - roundingError = erodedGains - float64(result) - - return -} - -func TestCalculator_ManaOfTransfer(t *testing.T) { - manaCalculator := NewCalculator(10, 0.1, 10) - - var mana, lastErosion uint64 - var roundingError float64 - - mana, lastErosion, _ = manaCalculator.ManaOfTransferDiscrete(49, 0, 0) - assert.Equal(t, mana, uint64(0)) - assert.Equal(t, lastErosion, uint64(0)) - - fmt.Println(calcManaContinuous(manaCalculator, 50, 10)) - fmt.Println(manaCalculator.ManaOfTransferDiscrete(50, 0, 10)) - fmt.Println(calcManaContinuous(manaCalculator, 50, 20)) - fmt.Println(manaCalculator.ManaOfTransferDiscrete(50, 0, 20)) +func TestCalculator_ManaSymmetry(t *testing.T) { + calculator := NewCalculator(500, 0.1, ManaScaleFactor(2)) - mana, lastErosion, _ = manaCalculator.ManaOfTransferDiscrete(49, 0, 1) - assert.Equal(t, mana, uint64(4)) - assert.Equal(t, lastErosion, uint64(0)) + // 1st case: generate mana by spending two times + generatedManaStep1, _ := calculator.GenerateMana(1000, 500) + generatedManaStep2, _ := calculator.GenerateMana(1000, 500) + generatedManaStep3, _ := calculator.GenerateMana(1000, 500) - mana, lastErosion, _ = manaCalculator.ManaOfTransferDiscrete(49, 0, 10) - assert.Equal(t, mana, uint64(36)) - assert.Equal(t, lastErosion, uint64(10)) + // the first "realized" mana part starts decaying while the coins of the 2nd spend are gaining weight again + erodedMana1, _ := calculator.ErodeMana(generatedManaStep1, 1000) + erodedMana2, _ := calculator.ErodeMana(generatedManaStep2, 500) - mana, lastErosion, _ = manaCalculator.ManaOfTransferDiscrete(50, 0, 31) - assert.Equal(t, mana, uint64(101)) - assert.Equal(t, lastErosion, uint64(30)) + // 2nd case: generate mana by spending only once + generatedManaWithoutSpends, _ := calculator.GenerateMana(1000, 1500) - fmt.Println(roundingError) + // the two mana values should be equal + assert.Equal(t, generatedManaWithoutSpends, erodedMana1+erodedMana2+generatedManaStep3) }