diff --git a/packages/mana/balance.go b/packages/mana/balance.go new file mode 100644 index 0000000000000000000000000000000000000000..85ed7b4fc84f05a8a5b1c1bc64c74b9c3cd7b2a0 --- /dev/null +++ b/packages/mana/balance.go @@ -0,0 +1,49 @@ +package mana + +import ( + "fmt" +) + +type Balance struct { + calculator *Calculator + currentBalance uint64 + lastErosion uint64 + roundingErrorInLastErosion float64 +} + +func NewBalance(calculator *Calculator) *Balance { + return &Balance{ + calculator: calculator, + currentBalance: 0, + lastErosion: 0, + roundingErrorInLastErosion: 0, + } +} + +func (balance *Balance) GetValue() uint64 { + return balance.currentBalance +} + +func (balance *Balance) AddTransfer(movedCoins uint64, receivedTime uint64, spentTime uint64) { + gainedMana, lastErosion, _ := balance.calculator.ManaOfTransferDiscrete(movedCoins, receivedTime, spentTime) + + if lastErosion >= balance.lastErosion { + balance.Erode(lastErosion) + } else { + fmt.Println("empty") + // revert old actions + // apply new + // replay old + } + + balance.currentBalance += gainedMana +} + +func (balance *Balance) Erode(erosionTime uint64) { + if balance.lastErosion <= erosionTime { + balance.currentBalance, balance.lastErosion, _ = balance.calculator.ErodedManaDiscrete(balance.currentBalance, balance.lastErosion, erosionTime) + } else { + fmt.Println("empty") + // revert old erosions + } +} diff --git a/packages/mana/calculator.go b/packages/mana/calculator.go new file mode 100644 index 0000000000000000000000000000000000000000..7ee676f890085b8067529f608229f978bd64d416 --- /dev/null +++ b/packages/mana/calculator.go @@ -0,0 +1,138 @@ +package mana + +import ( + "math" +) + +type Calculator struct { + decayInterval uint64 + decayRate float64 + coinsPerMana uint64 + decayFactor float64 + manaScaleFactor float64 +} + +func NewCalculator(decayInterval uint64, decayRate float64, coinsPerMana uint64) (result *Calculator) { + result = &Calculator{ + decayInterval: decayInterval, + decayRate: decayRate, + coinsPerMana: coinsPerMana, + decayFactor: 1 - decayRate, + } + + // make mana reach exactly the token supply + result.manaScaleFactor = result.decayRate / result.decayFactor + + return +} + +func (calculator *Calculator) ManaOfTransferDiscrete(movedCoins uint64, receivedTime uint64, spentTime uint64) (result uint64, lastErosion uint64, roundingError float64) { + if spentTime <= receivedTime { + return 0, 0, 0 + } + + 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) { + relativeDecayTime := float64(heldTime) / float64(calculator.decayInterval) + + erosionFactor := (1-math.Pow(calculator.decayFactor, float64(relativeDecayTime+1)))/calculator.decayRate - 1 + + gainedMana := float64(movedCoins) * calculator.manaScaleFactor * erosionFactor + + 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 { + result = mana + roundingError = 0 + + return + } + + growthFactor := math.Log(1-calculator.decayRate) / float64(calculator.decayInterval) + erodedMana := float64(mana) * math.Pow(math.E, growthFactor*float64(erosionTime)) + + result = uint64(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/mana.go b/packages/mana/mana.go index d8f6b10a7ca4a9094cda378176772d17a2a0baed..4855d809ad887fc908c8886fccbe6dfc1563aed2 100644 --- a/packages/mana/mana.go +++ b/packages/mana/mana.go @@ -1,39 +1,11 @@ package mana -import ( - "math" -) - func CoinTimeDestroyed(transferredValue uint64, parkedTime uint64) uint64 { return transferredValue * parkedTime } -func ManaOfTransfer(value uint64, receivedTime uint64, spentTime uint64) (result uint64) { - firstIntervalDuration := DECAY_INTERVAL - receivedTime%DECAY_INTERVAL - lastIntervalDuration := spentTime % DECAY_INTERVAL - erosionCount := (spentTime - receivedTime) / DECAY_INTERVAL - - gainsInFirstInterval := CoinTimeDestroyed(value, firstIntervalDuration) - gainsInLastInterval := CoinTimeDestroyed(value, lastIntervalDuration) - gainsPerConsecutiveInterval := CoinTimeDestroyed(value, DECAY_INTERVAL) - - scaleFactor := 1 - DECAY_RATE - - erodedGainsOfFirstInterval := uint64(float64(gainsInFirstInterval) * math.Pow(scaleFactor, float64(erosionCount))) - - var erodedGainsOfConsecutiveIntervals uint64 - if erosionCount >= 1 { - erodedGainsOfConsecutiveIntervals = uint64(float64(gainsPerConsecutiveInterval) * scaleFactor * (1 - math.Pow(scaleFactor, float64(erosionCount-1))) / (1 - scaleFactor)) - } - - result += erodedGainsOfFirstInterval - result += erodedGainsOfConsecutiveIntervals - result += gainsInLastInterval - - return -} - const ( - DECAY_INTERVAL = 100 - DECAY_RATE = 0.5 + 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 5e1eee80eb502b35851df8336e355f40055b5452..bfb0589ee2d750c132c6833528428f94e273a0b3 100644 --- a/packages/mana/mana_test.go +++ b/packages/mana/mana_test.go @@ -2,15 +2,82 @@ package mana import ( "fmt" + "math" "testing" + + "github.com/magiconair/properties/assert" ) -func TestManaOfTransfer(t *testing.T) { - fmt.Println(ManaOfTransfer(50, 10, 110)) - fmt.Println(ManaOfTransfer(50, 0, 190)) - fmt.Println(ManaOfTransfer(50, 0, 200)) - fmt.Println(ManaOfTransfer(50, 0, 290)) - fmt.Println(ManaOfTransfer(50, 0, 300)) - fmt.Println(ManaOfTransfer(50, 0, 390)) - fmt.Println(ManaOfTransfer(50, 0, 400)) +func BenchmarkCalculator_ManaOfTransferContinuous(b *testing.B) { + calculator := NewCalculator(10, 0.1, 1) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + calculator.ManaOfTransferContinuous(10000000, 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) + + assert.Equal(t, balance.GetValue(), uint64(450)) + + balance.Erode(20) + + assert.Equal(t, balance.GetValue(), uint64(405)) + + balance.Erode(40) + + assert.Equal(t, balance.GetValue(), uint64(328)) +} + +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)) + + mana, lastErosion, _ = manaCalculator.ManaOfTransferDiscrete(49, 0, 1) + assert.Equal(t, mana, uint64(4)) + assert.Equal(t, lastErosion, uint64(0)) + + mana, lastErosion, _ = manaCalculator.ManaOfTransferDiscrete(49, 0, 10) + assert.Equal(t, mana, uint64(36)) + assert.Equal(t, lastErosion, uint64(10)) + + mana, lastErosion, _ = manaCalculator.ManaOfTransferDiscrete(50, 0, 31) + assert.Equal(t, mana, uint64(101)) + assert.Equal(t, lastErosion, uint64(30)) + + fmt.Println(roundingError) }