/
interest.go
171 lines (145 loc) · 6.4 KB
/
interest.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
package keeper
import (
"fmt"
"math"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/percosis-labs/fury/x/cdp/types"
)
var scalingFactor = 1e18
// AccumulateInterest calculates the new interest that has accrued for the input collateral type based on the total amount of principal
// that has been created with that collateral type and the amount of time that has passed since interest was last accumulated
func (k Keeper) AccumulateInterest(ctx sdk.Context, ctype string) error {
previousAccrualTime, found := k.GetPreviousAccrualTime(ctx, ctype)
if !found {
k.SetPreviousAccrualTime(ctx, ctype, ctx.BlockTime())
return nil
}
timeElapsed := int64(math.RoundToEven(
ctx.BlockTime().Sub(previousAccrualTime).Seconds(),
))
if timeElapsed == 0 {
return nil
}
totalPrincipalPrior := k.GetTotalPrincipal(ctx, ctype, types.DefaultStableDenom)
if totalPrincipalPrior.IsZero() || totalPrincipalPrior.IsNegative() {
k.SetPreviousAccrualTime(ctx, ctype, ctx.BlockTime())
return nil
}
interestFactorPrior, foundInterestFactorPrior := k.GetInterestFactor(ctx, ctype)
if !foundInterestFactorPrior {
k.SetInterestFactor(ctx, ctype, sdk.OneDec())
// set previous accrual time exit early because interest accumulated will be zero
k.SetPreviousAccrualTime(ctx, ctype, ctx.BlockTime())
return nil
}
borrowRateSpy := k.getFeeRate(ctx, ctype)
if borrowRateSpy.Equal(sdk.OneDec()) {
k.SetPreviousAccrualTime(ctx, ctype, ctx.BlockTime())
return nil
}
interestFactor := CalculateInterestFactor(borrowRateSpy, sdkmath.NewInt(timeElapsed))
interestAccumulated := (interestFactor.Mul(sdk.NewDecFromInt(totalPrincipalPrior))).RoundInt().Sub(totalPrincipalPrior)
if interestAccumulated.IsZero() {
// in the case accumulated interest rounds to zero, exit early without updating accrual time
return nil
}
err := k.MintDebtCoins(ctx, types.ModuleName, k.GetDebtDenom(ctx), sdk.NewCoin(types.DefaultStableDenom, interestAccumulated))
if err != nil {
return err
}
dp, found := k.GetDebtParam(ctx, types.DefaultStableDenom)
if !found {
panic(fmt.Sprintf("Debt parameters for %s not found", types.DefaultStableDenom))
}
newFeesSurplus := interestAccumulated
// mint surplus coins to the liquidator module account.
if newFeesSurplus.IsPositive() {
err := k.bankKeeper.MintCoins(ctx, types.LiquidatorMacc, sdk.NewCoins(sdk.NewCoin(dp.Denom, newFeesSurplus)))
if err != nil {
return err
}
}
interestFactorNew := interestFactorPrior.Mul(interestFactor)
totalPrincipalNew := totalPrincipalPrior.Add(interestAccumulated)
k.SetTotalPrincipal(ctx, ctype, types.DefaultStableDenom, totalPrincipalNew)
k.SetInterestFactor(ctx, ctype, interestFactorNew)
k.SetPreviousAccrualTime(ctx, ctype, ctx.BlockTime())
return nil
}
// CalculateInterestFactor calculates the simple interest scaling factor,
// which is equal to: (per-second interest rate ** number of seconds elapsed)
// Will return 1.000x, multiply by principal to get new principal with added interest
func CalculateInterestFactor(perSecondInterestRate sdk.Dec, secondsElapsed sdkmath.Int) sdk.Dec {
scalingFactorUint := sdk.NewUint(uint64(scalingFactor))
scalingFactorInt := sdkmath.NewInt(int64(scalingFactor))
// Convert per-second interest rate to a uint scaled by 1e18
interestMantissa := sdkmath.NewUintFromBigInt(perSecondInterestRate.MulInt(scalingFactorInt).RoundInt().BigInt())
// Convert seconds elapsed to uint (*not scaled*)
secondsElapsedUint := sdkmath.NewUintFromBigInt(secondsElapsed.BigInt())
// Calculate the interest factor as a uint scaled by 1e18
interestFactorMantissa := sdkmath.RelativePow(interestMantissa, secondsElapsedUint, scalingFactorUint)
// Convert interest factor to an unscaled sdk.Dec
return sdk.NewDecFromBigInt(interestFactorMantissa.BigInt()).QuoInt(scalingFactorInt)
}
// SynchronizeInterest updates the input cdp object to reflect the current accumulated interest, updates the cdp state in the store,
// and returns the updated cdp object
func (k Keeper) SynchronizeInterest(ctx sdk.Context, cdp types.CDP) types.CDP {
globalInterestFactor, found := k.GetInterestFactor(ctx, cdp.Type)
if !found {
k.SetInterestFactor(ctx, cdp.Type, sdk.OneDec())
cdp.InterestFactor = sdk.OneDec()
cdp.FeesUpdated = ctx.BlockTime()
if err := k.SetCDP(ctx, cdp); err != nil {
panic(err)
}
return cdp
}
accumulatedInterest := k.CalculateNewInterest(ctx, cdp)
prevAccrualTime, found := k.GetPreviousAccrualTime(ctx, cdp.Type)
if !found {
return cdp
}
if accumulatedInterest.IsZero() {
// accumulated interest is zero if apy is zero or are if the total fees for all cdps round to zero
if cdp.FeesUpdated.Equal(prevAccrualTime) {
// if all fees are rounding to zero, don't update FeesUpdated
return cdp
}
// if apy is zero, we need to update FeesUpdated
cdp.FeesUpdated = prevAccrualTime
if err := k.SetCDP(ctx, cdp); err != nil {
panic(err)
}
}
cdp.AccumulatedFees = cdp.AccumulatedFees.Add(accumulatedInterest)
cdp.FeesUpdated = prevAccrualTime
cdp.InterestFactor = globalInterestFactor
collateralToDebtRatio := k.CalculateCollateralToDebtRatio(ctx, cdp.Collateral, cdp.Type, cdp.GetTotalPrincipal())
if err := k.UpdateCdpAndCollateralRatioIndex(ctx, cdp, collateralToDebtRatio); err != nil {
panic(err)
}
return cdp
}
// CalculateNewInterest returns the amount of interest that has accrued to the cdp since its interest was last synchronized
func (k Keeper) CalculateNewInterest(ctx sdk.Context, cdp types.CDP) sdk.Coin {
globalInterestFactor, found := k.GetInterestFactor(ctx, cdp.Type)
if !found {
return sdk.NewCoin(cdp.AccumulatedFees.Denom, sdk.ZeroInt())
}
cdpInterestFactor := globalInterestFactor.Quo(cdp.InterestFactor)
if cdpInterestFactor.Equal(sdk.OneDec()) {
return sdk.NewCoin(cdp.AccumulatedFees.Denom, sdk.ZeroInt())
}
accumulatedInterest := sdk.NewDecFromInt(cdp.GetTotalPrincipal().Amount).Mul(cdpInterestFactor).RoundInt().Sub(cdp.GetTotalPrincipal().Amount)
return sdk.NewCoin(cdp.AccumulatedFees.Denom, accumulatedInterest)
}
// SynchronizeInterestForRiskyCDPs synchronizes the interest for the slice of cdps with the lowest collateral:debt ratio
func (k Keeper) SynchronizeInterestForRiskyCDPs(ctx sdk.Context, slice sdkmath.Int, targetRatio sdk.Dec, collateralType string) error {
cdps := k.GetSliceOfCDPsByRatioAndType(ctx, slice, targetRatio, collateralType)
for _, cdp := range cdps {
k.hooks.BeforeCDPModified(ctx, cdp)
k.SynchronizeInterest(ctx, cdp)
}
return nil
}