Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add reward cacheing logic to launchpad and governance #455

Closed
wants to merge 1 commit into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
178 changes: 178 additions & 0 deletions launchpad/reward_calculation.gno
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
package launchpad

import (
"strconv"
"strings"

"gno.land/p/demo/avl"

u256 "gno.land/p/gnoswap/uint256"
)

type Period struct {
// [start, end)
// EndHeight is set to 0 if it's not ended
StartHeight uint64
EndHeight uint64

// total accumulated reward when this distribution period is started,
// after distribution deduction
InitReward uint64
// total accumulated reward when this distribution period is ended
// set to 0 if it's not ended
FinalReward uint64
// distributed amount when this distribution period starts
// FinalReward(n-1) - Distributed(n) == InitReward(n)
Distributed uint64

// total staked amount for this distribution period
TotalStake uint64
}

func NewPeriod(startHeight uint64, initReward uint64, distributed uint64, totalStake uint64) Period {
return Period{
StartHeight: startHeight,
InitReward: initReward,
Distributed: distributed,
TotalStake: totalStake,
}
}

func (self *Period) Finalize(endHeight uint64, finalReward uint64) {
self.EndHeight = endHeight
self.FinalReward = finalReward
}

func (self *Period) IsEnded() bool {
return self.EndHeight != 0
}

// CONTRACT: must be called only after Finalize
func (self *Period) TotalReward() uint64 {
return self.FinalReward - self.InitReward
}

// CONTRACT: must be called only after Finalize
func (self *Period) Distribute(stake uint64) uint64 {

}

func EncodeUint(num uint64) string {
// Convert the value to a decimal string.
s := strconv.FormatUint(num, 10)

// Zero-pad to a total length of 20 characters.
zerosNeeded := 20 - len(s)
return strings.Repeat("0", zerosNeeded) + s
}

func DecodeUint(s string) uint64 {
num, err := strconv.ParseUint(s, 10, 64)
if err != nil {
panic(err)
}
return num
}

type Periods struct {
tree *avl.Tree
}

func NewPeriods(currentHeight uint64) *Periods {
result := &Periods{
tree: avl.NewTree(),
}
result.Set(currentHeight, NewPeriod(currentHeight, 0, 0))
return result
}

func (self *Periods) Get(key uint64) (Period, bool) {
v, ok := self.tree.Get(EncodeUint(key))
if !ok {
return Period{}, false
}
return v.(Period), true
}

func (self *Periods) Set(key uint64, value Period) {
self.tree.Set(EncodeUint(key), value)
}

func (self *Periods) Remove(key uint64) {
self.tree.Remove(EncodeUint(key))
}

func (self *Periods) Iterate(start, end uint64, fn func(key uint64, value Period)) {
self.tree.Iterate(EncodeUint(start), EncodeUint(end), func(key string, value interface{}) bool {
fn(DecodeUint(key), value.(Period))
return true
})
}

func (self *Periods) ReverseIterate(start, end uint64, fn func(key uint64, value Period)) {
self.tree.ReverseIterate(EncodeUint(start), EncodeUint(end), func(key string, value interface{}) bool {
fn(DecodeUint(key), value.(Period))
return true
})
}

func (self *Periods) Size() uint64 {
return uint64(self.tree.Size())
}

// Period that has equal or less than current height
// There MUST be at least one period
func (self *Periods) CurrentPeriod(currentHeight uint64) Period {
var period Period
self.ReverseIterate(0, currentHeight, func(key uint64, value Period) {
period = value
})
return period
}

// NOTE: finalReward may be inconsistent within a single block
// as it is calculated as the current balance of the contract.
// However, if this happens, the value
func (self *Periods) AddStake(currentHeight uint64, finalReward uint64, stake uint64) {
period := self.CurrentPeriod(currentHeight)
if period.StartHeight == currentHeight {
// Period update has been already happened in this block
// Modify instead of push new period
period.TotalStake += stake
self.Set(currentHeight, period)
return
}

period.Finalize(currentHeight, finalReward)
self.Set(period.StartHeight, period)

newPeriod := NewPeriod(currentHeight, period.FinalReward, 0, period.TotalStake+stake)
self.Set(currentHeight, newPeriod)
}

func (self *Periods) RemoveStake(currentHeight uint64, finalReward uint64, stake uint64) uint64 {
period := self.CurrentPeriod(currentHeight)
if period.StartHeight == currentHeight {
// Period update has been already happened in this block
// Modify instead of push new period
prevPeriod := self.CurrentPeriod(currentHeight - 1)
prevTotalReward := prevPeriod.TotalReward()
toDistribute := prevTotalReward - period.Distributed

reward := u256.NewUint(toDistribute)
reward = reward.Mul(reward, u256.NewUint(stake))
reward = reward.Div(reward, u256.NewUint(period.TotalStake))

reward64 := reward.Uint64()
period.Distributed += reward64
period.InitReward -= reward64
self.Set(currentHeight, period)

return reward64
}

period.Finalize(currentHeight, 0)
self.Set(period.StartHeight, period)

return 0
}
Loading