/
inflation.go
146 lines (127 loc) · 4.64 KB
/
inflation.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
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/furya-official/mage/x/Magemint/types"
)
// this is the same value used in the x/hard
const (
SecondsPerYear = uint64(31536000)
)
// Minter wraps the logic of a single source of inflation. It calculates the amount of coins to be
// minted to match a yearly `rate` of inflating the `basis`.
type Minter struct {
name string // name of the minting. used as event attribute to report how much is minted
rate sdk.Dec // the yearly apy of the mint. ex. 20% => 0.2
basis sdk.Int // the base amount of coins that is inflated
destMaccName string // the destination module account name to mint coins to
mintDenom string // denom of coin this minter is responsible for minting
}
// NewMinter returns creates a new source of minting inflation
func NewMinter(
name string, rate sdk.Dec, basis sdk.Int, mintDenom string, destMaccName string,
) Minter {
return Minter{
name: name,
rate: rate,
basis: basis,
destMaccName: destMaccName,
mintDenom: mintDenom,
}
}
// AccumulateInflation calculates the number of coins that should be minted to match a yearly `rate`
// for interest compounded each second of the year over `secondsPassed` seconds.
// `basis` is the base amount of coins that is inflated.
func (m Minter) AccumulateInflation(secondsPassed uint64) (sdk.Coin, error) {
// calculate the rate factor based on apy & seconds passed since last block
inflationRate, err := m.CalculateInflationRate(secondsPassed)
if err != nil {
return sdk.Coin{}, err
}
amount := inflationRate.MulInt(m.basis).TruncateInt()
return sdk.NewCoin(m.mintDenom, amount), nil
}
// CalculateInflationRate converts an APY into the factor corresponding with that APY's accumulation
// over a period of secondsPassed seconds.
func (m Minter) CalculateInflationRate(secondsPassed uint64) (sdk.Dec, error) {
perSecondInterestRate, err := apyToSpy(m.rate.Add(sdk.OneDec()))
if err != nil {
return sdk.ZeroDec(), err
}
rate := perSecondInterestRate.Power(secondsPassed)
return rate.Sub(sdk.OneDec()), nil
}
// AccumulateAndMintInflation defines the sources of inflation, determines the seconds passed since
// the last mint, and then mints each source of inflation to the defined destination.
func (k Keeper) AccumulateAndMintInflation(ctx sdk.Context) error {
params := k.GetParams(ctx)
// determine seconds since last mint
previousBlockTime := k.GetPreviousBlockTime(ctx)
if previousBlockTime.IsZero() {
previousBlockTime = ctx.BlockTime()
}
secondsPassed := ctx.BlockTime().Sub(previousBlockTime).Seconds()
// calculate totals before any minting is done to prevent new mints affecting the values
totalSupply := k.totalSupply(ctx)
totalBonded := k.totalBondedTokens(ctx)
bondDenom := k.bondDenom(ctx)
// define minters for each contributing piece of inflation
minters := []Minter{
// staking rewards
NewMinter(
types.AttributeKeyStakingRewardMint,
params.StakingRewardsApy,
totalBonded,
bondDenom,
k.stakingRewardsFeeCollectorName,
),
// community pool inflation
NewMinter(
types.AttributeKeyCommunityPoolMint,
params.CommunityPoolInflation,
totalSupply,
bondDenom,
k.communityPoolModuleAccountName,
),
}
mintEventAttrs := make([]sdk.Attribute, 0, len(minters))
for _, minter := range minters {
// calculate amount for time passed
amount, err := minter.AccumulateInflation(uint64(secondsPassed))
if err != nil {
return err
}
// mint & transfer coins
inflation := sdk.NewCoins(amount)
if err := k.mintCoinsToModule(ctx, inflation, minter.destMaccName); err != nil {
return err
}
// add attribute to event
mintEventAttrs = append(mintEventAttrs, sdk.NewAttribute(
minter.name,
inflation.String(),
))
}
// ------------- Bookkeeping -------------
// bookkeep the previous block time
k.SetPreviousBlockTime(ctx, ctx.BlockTime())
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeMint,
sdk.NewAttribute(types.AttributeKeyTotalSupply, totalSupply.String()),
sdk.NewAttribute(types.AttributeKeyTotalBonded, totalBonded.String()),
sdk.NewAttribute(types.AttributeSecondsPassed, fmt.Sprintf("%f", secondsPassed)),
).AppendAttributes(mintEventAttrs...),
)
return nil
}
// apyToSpy converts the input annual interest rate. For example, 10% apy would be passed as 1.10.
// SPY = Per second compounded interest rate is how cosmos mathematically represents APY.
func apyToSpy(apy sdk.Dec) (sdk.Dec, error) {
// Note: any APY greater than 176.5 will cause an out-of-bounds error
root, err := apy.ApproxRoot(SecondsPerYear)
if err != nil {
return sdk.ZeroDec(), err
}
return root, nil
}