-
Notifications
You must be signed in to change notification settings - Fork 169
/
quota.go
259 lines (224 loc) · 8.42 KB
/
quota.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
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
package quota
import (
"strings"
"time"
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
channeltypes "github.com/cosmos/ibc-go/v7/modules/core/04-channel/types"
"github.com/cosmos/ibc-go/v7/modules/core/exported"
"github.com/umee-network/umee/v6/util"
"github.com/umee-network/umee/v6/util/coin"
"github.com/umee-network/umee/v6/util/store"
ltypes "github.com/umee-network/umee/v6/x/leverage/types"
"github.com/umee-network/umee/v6/x/uibc"
)
var ten = sdk.MustNewDecFromStr("10")
// GetAllOutflows returns sum of outflows of all tokens in USD value.
func (k Keeper) GetAllOutflows() (sdk.DecCoins, error) {
iter := sdk.KVStorePrefixIterator(k.store, keyPrefixDenomOutflows)
return store.LoadAllDecCoins(iter, len(keyPrefixDenomOutflows))
}
// GetTokenOutflows returns sum of denom outflows in USD value in the DecCoin structure.
func (k Keeper) GetTokenOutflows(denom string) sdk.DecCoin {
// When token outflow is not stored in store it will return 0
amount, _ := store.GetDec(k.store, keyTokenOutflow(denom), "total_outflow")
return sdk.NewDecCoinFromDec(denom, amount)
}
// SetTokenOutflows saves provided updated IBC outflows as a pair: USD value, denom name in the
// DecCoin structure.
func (k Keeper) SetTokenOutflows(outflows sdk.DecCoins) {
for _, q := range outflows {
k.SetTokenOutflow(q)
}
}
// SetOutflowSum save the total outflow of ibc-transfer amount.
func (k Keeper) SetOutflowSum(amount sdk.Dec) {
err := store.SetDec(k.store, keyOutflowSum, amount, "total_outflow_sum")
util.Panic(err)
}
// GetOutflowSum returns the total outflow of ibc-transfer amount.
func (k Keeper) GetOutflowSum() sdk.Dec {
// When total outflow is not stored in store it will return 0
amount, _ := store.GetDec(k.store, keyOutflowSum, "total_outflow")
return amount
}
// SetTokenOutflow save the outflows of denom into store.
func (k Keeper) SetTokenOutflow(outflow sdk.DecCoin) {
key := keyTokenOutflow(outflow.Denom)
err := store.SetDec(k.store, key, outflow.Amount, "total_outflow")
util.Panic(err)
}
// GetAllInflows returns inflows of all registered tokens in USD value.
func (k Keeper) GetAllInflows() (sdk.DecCoins, error) {
iter := sdk.KVStorePrefixIterator(k.store, keyPrefixDenomInflows)
return store.LoadAllDecCoins(iter, len(keyPrefixDenomInflows))
}
// SetTokenInflows saves provided updated IBC inflows as a pair: USD value, denom name in the
// DecCoin structure.
func (k Keeper) SetTokenInflows(inflows sdk.DecCoins) {
for _, q := range inflows {
k.SetTokenInflow(q)
}
}
// SetTokenInflow save the inflow of denom into store.
func (k Keeper) SetTokenInflow(inflow sdk.DecCoin) {
key := keyTokenInflow(inflow.Denom)
err := store.SetDec(k.store, key, inflow.Amount, "token_inflow")
util.Panic(err)
}
// GetTokenInflow returns the inflow of denom from store.
func (k Keeper) GetTokenInflow(denom string) sdk.DecCoin {
key := keyTokenInflow(denom)
// When token inflow is not stored in store it will return 0
amount, _ := store.GetDec(k.store, key, "token_inflow")
return sdk.NewDecCoinFromDec(denom, amount)
}
// GetInflowSum returns the total inflow of ibc-transfer amount.
func (k Keeper) GetInflowSum() sdk.Dec {
// When total inflow is not stored in store it will return 0
amount, _ := store.GetDec(k.store, keyInflowSum, "total_inflows")
return amount
}
// SetInflowSum save the total inflow of ibc-transfer amount.
func (k Keeper) SetInflowSum(amount sdk.Dec) {
err := store.SetDec(k.store, keyInflowSum, amount, "total_inflows")
util.Panic(err)
}
// SetExpire save the quota expire time of ibc denom into.
func (k Keeper) SetExpire(expires time.Time) error {
return store.SetBinValue(k.store, keyQuotaExpires, &expires, "expire")
}
// GetExpire returns ibc-transfer quota expires time.
func (k Keeper) GetExpire() (*time.Time, error) {
return store.GetBinValue[*time.Time](k.store, keyQuotaExpires, "expire")
}
// ResetAllQuotas will zero the ibc-transfer quotas
func (k Keeper) ResetAllQuotas() error {
qd := k.GetParams().QuotaDuration
newExpires := k.blockTime.Add(qd)
if err := k.SetExpire(newExpires); err != nil {
return err
}
zero := sdk.NewDec(0)
// outflows
k.SetOutflowSum(zero)
ps := k.PrefixStore(keyPrefixDenomOutflows)
store.DeleteByPrefixStore(ps)
// inflows
k.SetInflowSum(zero)
ps = k.PrefixStore(keyPrefixDenomInflows)
store.DeleteByPrefixStore(ps)
return nil
}
// CheckAndUpdateQuota checks if adding a newOutflow doesn't exceed the max quota and
// updates the current quota metrics.
func (k Keeper) CheckAndUpdateQuota(denom string, newOutflow sdkmath.Int) error {
params := k.GetParams()
exchangePrice, err := k.getExchangePrice(denom, newOutflow)
if err != nil {
if ltypes.ErrNotRegisteredToken.Is(err) {
return nil
} else if err != nil {
return err
}
}
o := k.GetTokenOutflows(denom)
o.Amount = o.Amount.Add(exchangePrice)
inToken := k.GetTokenInflow(denom)
if !params.TokenQuota.IsZero() {
if o.Amount.GT(params.TokenQuota) ||
o.Amount.GT(params.InflowOutflowTokenQuotaBase.Add((params.InflowOutflowQuotaRate.Mul(inToken.Amount)))) {
return uibc.ErrQuotaExceeded
}
}
// Allow outflow either of two conditions
// 1. Outflow Sum <= Total Outflow Quota
// 2. OR Outflow Sum <= params.InflowOutflowQuotaBase + (params.InflowOutflowQuotaRate * sum of all inflows)
outflowSum := k.GetOutflowSum().Add(exchangePrice)
inflowSum := k.GetInflowSum()
if !params.TotalQuota.IsZero() {
if outflowSum.GT(params.TotalQuota) ||
outflowSum.GT(params.InflowOutflowQuotaBase.Add(inflowSum.Mul(params.InflowOutflowQuotaRate))) {
return uibc.ErrQuotaExceeded
}
}
k.SetTokenOutflow(o)
k.SetOutflowSum(outflowSum)
return nil
}
func (k Keeper) getExchangePrice(denom string, amount sdkmath.Int) (sdk.Dec, error) {
transferCoin := sdk.NewCoin(denom, amount)
var (
err error
exchangeRate sdk.Dec
)
// convert to base asset if it is `uToken`
if coin.HasUTokenPrefix(denom) {
// NOTE: to avoid ctx, we can use similar approach: create a leverage keeper builder
transferCoin, err = k.leverage.ToToken(*k.ctx, transferCoin)
if err != nil {
return sdk.Dec{}, err
}
}
ts, err := k.leverage.GetTokenSettings(*k.ctx, transferCoin.Denom)
if err != nil {
return sdk.Dec{}, err
}
// get the exchange price (eg: UMEE) in USD from oracle using SYMBOL Denom eg: `UMEE` (uumee)
exchangeRate, err = k.oracle.Price(*k.ctx, strings.ToUpper(ts.SymbolDenom))
if err != nil {
return sdk.Dec{}, err
}
// calculate total exchange rate
powerReduction := ten.Power(uint64(ts.Exponent))
return sdk.NewDecFromInt(transferCoin.Amount).Quo(powerReduction).Mul(exchangeRate), nil
}
// UndoUpdateQuota subtracts `amount` from quota metric of the ibc denom.
func (k Keeper) UndoUpdateQuota(denom string, amount sdkmath.Int) error {
o := k.GetTokenOutflows(denom)
exchangePrice, err := k.getExchangePrice(denom, amount)
if err != nil {
// Note: skip the ibc-transfer quota checking if `denom` is not support by leverage
if ltypes.ErrNotRegisteredToken.Is(err) {
return nil
} else if err != nil {
return err
}
}
// We ignore the update if the result is negative (due to quota reset on epoch)
o.Amount = o.Amount.Sub(exchangePrice)
if o.Amount.IsNegative() {
return nil
}
k.SetTokenOutflow(o)
outflowSum := k.GetOutflowSum()
k.SetOutflowSum(outflowSum.Sub(exchangePrice))
return nil
}
// RecordIBCInflow will save the inflow amount if token is registered otherwise it will skip
func (k Keeper) RecordIBCInflow(packet channeltypes.Packet, denom, amount string,
) exported.Acknowledgement {
denom = uibc.ExtractDenomFromPacketOnRecv(packet, denom)
ts, err := k.leverage.GetTokenSettings(*k.ctx, denom)
if err != nil {
if ltypes.ErrNotRegisteredToken.Is(err) {
return nil // skip recording inflow if the token is not registered
}
k.ctx.Logger().Error("can't get x/leverage token settings", "error", err)
return channeltypes.NewErrorAcknowledgement(err)
}
// get the exchange price (eg: UMEE) in USD from oracle using SYMBOL Denom eg: `UMEE`
exchangeRate, err := k.oracle.Price(*k.ctx, strings.ToUpper(ts.SymbolDenom))
if err != nil {
return channeltypes.NewErrorAcknowledgement(err)
}
// calculate total exchange rate
powerReduction := ten.Power(uint64(ts.Exponent))
inflowInUSD := sdk.MustNewDecFromStr(amount).Quo(powerReduction).Mul(exchangeRate)
tokenInflow := k.GetTokenInflow(denom)
tokenInflow.Amount = tokenInflow.Amount.Add(inflowInUSD)
k.SetTokenInflow(tokenInflow)
totalInflowSum := k.GetInflowSum()
k.SetInflowSum(totalInflowSum.Add(inflowInUSD))
return nil
}