-
Notifications
You must be signed in to change notification settings - Fork 179
/
calc.go
189 lines (164 loc) · 7.29 KB
/
calc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
package keeper
import (
sdk "github.com/okex/exchain/libs/cosmos-sdk/types"
"github.com/okex/exchain/x/farm/types"
)
// CalculateAmountYieldedBetween is used for calculating how many tokens haven been yielded from
// startBlockHeight to endBlockHeight. And return the amount.
func (k Keeper) CalculateAmountYieldedBetween(ctx sdk.Context, pool types.FarmPool) (types.FarmPool, sdk.SysCoins) {
currentPeriod := k.GetPoolCurrentRewards(ctx, pool.Name)
endBlockHeight := ctx.BlockHeight()
totalYieldedTokens := sdk.SysCoins{}
for i := 0; i < len(pool.YieldedTokenInfos); i++ {
startBlockHeightToYield := pool.YieldedTokenInfos[i].StartBlockHeightToYield
var startBlockHeight int64
if currentPeriod.StartBlockHeight <= startBlockHeightToYield {
startBlockHeight = startBlockHeightToYield
} else {
startBlockHeight = currentPeriod.StartBlockHeight
}
// no tokens to yield
if startBlockHeightToYield == 0 || startBlockHeight >= endBlockHeight {
continue
}
yieldedTokens := sdk.SysCoins{}
// calculate how many tokens to be yielded between startBlockHeight and endBlockHeight
blockInterval := sdk.NewDec(endBlockHeight - startBlockHeight)
amount := blockInterval.MulTruncate(pool.YieldedTokenInfos[i].AmountYieldedPerBlock)
remaining := pool.YieldedTokenInfos[i].RemainingAmount
if amount.LT(remaining.Amount) {
pool.YieldedTokenInfos[i].RemainingAmount.Amount = remaining.Amount.Sub(amount)
yieldedTokens = sdk.NewDecCoinsFromDec(remaining.Denom, amount)
} else {
pool.YieldedTokenInfos[i] = types.NewYieldedTokenInfo(
sdk.NewDecCoin(remaining.Denom, sdk.ZeroInt()), 0, sdk.ZeroDec(),
)
yieldedTokens = sdk.NewDecCoinsFromDec(remaining.Denom, remaining.Amount)
}
pool.TotalAccumulatedRewards = pool.TotalAccumulatedRewards.Add2(yieldedTokens)
totalYieldedTokens = totalYieldedTokens.Add2(yieldedTokens)
}
return pool, totalYieldedTokens
}
func (k Keeper) WithdrawRewards(
ctx sdk.Context, poolName string, totalValueLocked sdk.SysCoin, yieldedTokens sdk.SysCoins, addr sdk.AccAddress,
) (sdk.SysCoins, sdk.Error) {
// 0. check existence of lock info
lockInfo, found := k.GetLockInfo(ctx, addr, poolName)
if !found {
return nil, types.ErrNoLockInfoFound(addr.String(), poolName)
}
// 1. end current period and calculate rewards
endingPeriod := k.IncrementPoolPeriod(ctx, poolName, totalValueLocked, yieldedTokens)
rewards := k.calculateRewards(ctx, poolName, addr, endingPeriod, lockInfo)
// 2. transfer rewards to user account
if !rewards.IsZero() {
err := k.supplyKeeper.SendCoinsFromModuleToAccount(ctx, types.YieldFarmingAccount, addr, rewards)
if err != nil {
return nil, err
}
}
// 3. decrement reference count of lock info
k.decrementReferenceCount(ctx, poolName, lockInfo.ReferencePeriod)
return rewards, nil
}
// IncrementPoolPeriod increments pool period, returning the period just ended
func (k Keeper) IncrementPoolPeriod(
ctx sdk.Context, poolName string, totalValueLocked sdk.SysCoin, yieldedTokens sdk.SysCoins,
) uint64 {
// 1. fetch current period rewards
rewards := k.GetPoolCurrentRewards(ctx, poolName)
// 2. calculate current reward ratio
rewards.Rewards = rewards.Rewards.Add2(yieldedTokens)
var currentRatio sdk.SysCoins
if totalValueLocked.IsZero() {
currentRatio = sdk.SysCoins{}
} else {
currentRatio = rewards.Rewards.QuoDecTruncate(totalValueLocked.Amount)
}
// 3.1 get the previous pool historical rewards
historical := k.GetPoolHistoricalRewards(ctx, poolName, rewards.Period-1).CumulativeRewardRatio
// 3.2 decrement reference count
k.decrementReferenceCount(ctx, poolName, rewards.Period-1)
// 3.3 create new pool historical rewards with reference count of 1, then set it into store
newHistoricalRewards := types.NewPoolHistoricalRewards(historical.Add2(currentRatio), 1)
k.SetPoolHistoricalRewards(ctx, poolName, rewards.Period, newHistoricalRewards)
// 4. set new current rewards into store, incrementing period by 1
newCurRewards := types.NewPoolCurrentRewards(ctx.BlockHeight(), rewards.Period+1, sdk.SysCoins{})
k.SetPoolCurrentRewards(ctx, poolName, newCurRewards)
return rewards.Period
}
// incrementReferenceCount increments the reference count for a historical rewards value
func (k Keeper) incrementReferenceCount(ctx sdk.Context, poolName string, period uint64) {
historical := k.GetPoolHistoricalRewards(ctx, poolName, period)
if historical.ReferenceCount > 2 {
panic("reference count should never exceed 2")
}
historical.ReferenceCount++
k.SetPoolHistoricalRewards(ctx, poolName, period, historical)
}
// decrementReferenceCount decrements the reference count for a historical rewards value,
// and delete if zero references remain.
func (k Keeper) decrementReferenceCount(ctx sdk.Context, poolName string, period uint64) {
historical := k.GetPoolHistoricalRewards(ctx, poolName, period)
if historical.ReferenceCount == 0 {
panic("cannot set negative reference count")
}
historical.ReferenceCount--
if historical.ReferenceCount == 0 {
k.DeletePoolHistoricalReward(ctx, poolName, period)
} else {
k.SetPoolHistoricalRewards(ctx, poolName, period, historical)
}
}
func (k Keeper) calculateRewards(
ctx sdk.Context, poolName string, addr sdk.AccAddress, endingPeriod uint64, lockInfo types.LockInfo,
) (rewards sdk.SysCoins) {
if lockInfo.StartBlockHeight == ctx.BlockHeight() {
// started this height, no rewards yet
return
}
startingPeriod := lockInfo.ReferencePeriod
// calculate rewards for final period
return k.calculateLockRewardsBetween(ctx, poolName, startingPeriod, endingPeriod, lockInfo.Amount)
}
// calculateLockRewardsBetween calculate the rewards accrued by a pool between two periods
func (k Keeper) calculateLockRewardsBetween(ctx sdk.Context, poolName string, startingPeriod, endingPeriod uint64,
amount sdk.SysCoin) (rewards sdk.SysCoins) {
// sanity check
if startingPeriod > endingPeriod {
panic("startingPeriod cannot be greater than endingPeriod")
}
if amount.Amount.LT(sdk.ZeroDec()) {
panic("amount should not be negative")
}
// return amount * (ending - starting)
starting := k.GetPoolHistoricalRewards(ctx, poolName, startingPeriod)
ending := k.GetPoolHistoricalRewards(ctx, poolName, endingPeriod)
difference := ending.CumulativeRewardRatio.Sub(starting.CumulativeRewardRatio)
rewards = difference.MulDecTruncate(amount.Amount)
return
}
// UpdateLockInfo updates lock info for the modified lock info
func (k Keeper) UpdateLockInfo(ctx sdk.Context, addr sdk.AccAddress, poolName string, changedAmount sdk.Dec) {
// period has already been incremented - we want to store the period ended by this lock action
previousPeriod := k.GetPoolCurrentRewards(ctx, poolName).Period - 1
// get lock info, then set it into store
lockInfo, found := k.GetLockInfo(ctx, addr, poolName)
if !found {
panic("the lock info can't be found")
}
lockInfo.StartBlockHeight = ctx.BlockHeight()
lockInfo.ReferencePeriod = previousPeriod
lockInfo.Amount.Amount = lockInfo.Amount.Amount.Add(changedAmount)
if lockInfo.Amount.IsZero() {
k.DeleteLockInfo(ctx, lockInfo.Owner, lockInfo.PoolName)
k.DeleteAddressInFarmPool(ctx, lockInfo.PoolName, lockInfo.Owner)
} else {
// increment reference count for the period we're going to track
k.incrementReferenceCount(ctx, poolName, previousPeriod)
// set the updated lock info
k.SetLockInfo(ctx, lockInfo)
k.SetAddressInFarmPool(ctx, lockInfo.PoolName, lockInfo.Owner)
}
}