-
Notifications
You must be signed in to change notification settings - Fork 170
/
limits.go
228 lines (195 loc) · 9.39 KB
/
limits.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
package keeper
import (
sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/umee-network/umee/v6/util/coin"
"github.com/umee-network/umee/v6/x/leverage/types"
)
// userMaxWithdraw calculates the maximum amount of uTokens an account can currently withdraw and the amount of
// these uTokens which are non-collateral. Input denom should be a base token. If oracle prices are missing for
// some of the borrower's collateral (other than the denom being withdrawn), computes the maximum safe withdraw
// allowed by only the collateral whose prices are known.
func (k *Keeper) userMaxWithdraw(ctx sdk.Context, addr sdk.AccAddress, denom string) (sdk.Coin, sdk.Coin, error) {
uDenom := coin.ToUTokenDenom(denom)
availableTokens := sdk.NewCoin(denom, k.AvailableLiquidity(ctx, denom))
availableUTokens, err := k.ToUToken(ctx, availableTokens)
if err != nil {
return sdk.Coin{}, sdk.Coin{}, err
}
walletUtokens := k.bankKeeper.SpendableCoins(ctx, addr).AmountOf(uDenom)
unbondedCollateral := k.unbondedCollateral(ctx, addr, uDenom)
maxWithdraw := coin.Zero(uDenom)
position, err := k.GetAccountPosition(ctx, addr, false)
if err == nil {
maxWithdrawValue, fullWithdrawal := position.MaxWithdraw(denom)
if fullWithdrawal {
maxWithdraw = k.GetCollateral(ctx, addr, uDenom)
} else {
maxWithdraw, err = k.UTokenWithValue(ctx, uDenom, maxWithdrawValue, types.PriceModeLow)
}
}
if nonOracleError(err) {
// non-oracle errors fail the transaction (or query)
return sdk.Coin{}, sdk.Coin{}, err
}
if err != nil {
// oracle errors cause max withdraw to only be wallet uTokens
withdrawAmount := sdk.MinInt(walletUtokens, availableUTokens.Amount)
return sdk.NewCoin(uDenom, withdrawAmount), sdk.NewCoin(uDenom, withdrawAmount), nil
}
// find the minimum of max withdraw (from positions) or unbonded collateral (incentive module)
if unbondedCollateral.Amount.LT(maxWithdraw.Amount) {
maxWithdraw = unbondedCollateral
}
// add wallet uTokens to the unused amount from collateral
maxWithdraw.Amount = maxWithdraw.Amount.Add(walletUtokens)
// reduce amount to withdraw if it exceeds available liquidity
maxWithdraw.Amount = sdk.MinInt(maxWithdraw.Amount, availableUTokens.Amount)
return maxWithdraw, sdk.NewCoin(uDenom, walletUtokens), nil
}
// userMaxBorrow calculates the maximum amount of a given token an account can currently borrow.
// input denom should be a base token. If oracle prices are missing for some of the borrower's
// collateral, computes the maximum safe borrow allowed by only the collateral whose prices are known.
func (k *Keeper) userMaxBorrow(ctx sdk.Context, addr sdk.AccAddress, denom string) (sdk.Coin, error) {
if coin.HasUTokenPrefix(denom) {
return sdk.Coin{}, types.ErrUToken
}
availableTokens := k.AvailableLiquidity(ctx, denom)
position, err := k.GetAccountPosition(ctx, addr, false)
if nonOracleError(err) {
// non-oracle errors fail the transaction (or query)
return sdk.Coin{}, err
}
if err != nil {
// oracle errors cause max borrow to be zero
return sdk.NewCoin(denom, sdk.ZeroInt()), nil
}
maxBorrowValue := position.MaxBorrow(denom)
maxBorrow, err := k.TokenWithValue(ctx, denom, maxBorrowValue, types.PriceModeHigh)
if nonOracleError(err) {
// non-oracle errors fail the transaction (or query)
return sdk.Coin{}, err
}
if err != nil {
// oracle errors cause max borrow to be zero
return sdk.NewCoin(denom, sdk.ZeroInt()), nil
}
// also cap borrow amount at available liquidity
maxBorrow.Amount = sdk.MinInt(maxBorrow.Amount, availableTokens)
return maxBorrow, nil
}
// maxCollateralFromShare calculates the maximum amount of collateral a utoken denom
// is allowed to have, taking into account its associated token's MaxCollateralShare
// under current market conditions. If any collateral denoms other than this are missing
// oracle prices, calculates a (lower) maximum amount using the collateral with known prices.
func (k *Keeper) maxCollateralFromShare(ctx sdk.Context, denom string) (sdkmath.Int, error) {
token, err := k.GetTokenSettings(ctx, coin.StripUTokenDenom(denom))
if err != nil {
return sdk.ZeroInt(), err
}
// if a token's max collateral share is zero, max collateral is zero
if token.MaxCollateralShare.LTE(sdk.ZeroDec()) {
return sdk.ZeroInt(), nil
}
// if a token's max collateral share is 100%, max collateral is its uToken supply
if token.MaxCollateralShare.GTE(sdk.OneDec()) {
return k.GetUTokenSupply(ctx, denom).Amount, nil
}
// if a token's max collateral share is less than 100%, additional restrictions apply
systemCollateral := k.GetAllTotalCollateral(ctx)
thisDenomCollateral := sdk.NewCoin(denom, systemCollateral.AmountOf(denom))
// get USD collateral value for all other denoms, skipping those which are missing oracle prices
otherDenomsValue, err := k.VisibleCollateralValue(
ctx,
systemCollateral.Sub(thisDenomCollateral),
types.PriceModeSpot,
)
if err != nil {
return sdk.ZeroInt(), err
}
// determine the max USD value this uToken's collateral is allowed to have by MaxCollateralShare
maxValue := otherDenomsValue.Quo(sdk.OneDec().Sub(token.MaxCollateralShare)).Mul(token.MaxCollateralShare)
// determine the amount of base tokens which would be required to reach maxValue,
// using the higher of spot or historic prices
udenom := coin.ToUTokenDenom(denom)
maxUTokens, err := k.UTokenWithValue(ctx, udenom, maxValue, types.PriceModeHigh)
if err != nil {
return sdk.ZeroInt(), err
}
// return the computed maximum or the current uToken supply, whichever is smaller
return sdk.MinInt(k.GetUTokenSupply(ctx, denom).Amount, maxUTokens.Amount), nil
}
// ModuleAvailableLiquidity calculates the maximum available liquidity of a Token denom from the module can be used,
// respecting the MinCollateralLiquidity set for given Token.
func (k Keeper) ModuleAvailableLiquidity(ctx sdk.Context, denom string) (sdkmath.Int, error) {
// Get module liquidity for the Token
liquidity := k.AvailableLiquidity(ctx, denom)
// Get module collateral for the associated uToken
totalCollateral := k.GetTotalCollateral(ctx, coin.ToUTokenDenom(denom))
totalTokenCollateral, err := k.ToTokens(ctx, sdk.NewCoins(totalCollateral))
if err != nil {
return sdkmath.Int{}, err
}
// Get min_collateral_liquidity for the denom
token, err := k.GetTokenSettings(ctx, denom)
if err != nil {
return sdkmath.Int{}, err
}
minCollateralLiquidity := token.MinCollateralLiquidity
// The formula to calculate the module_available_liquidity is as follows:
//
// min_collateral_liquidity = (module_liquidity - module_available_liquidity) / module_collateral
// module_available_liquidity = module_liquidity - min_collateral_liquidity * module_collateral
moduleAvailableLiquidity := sdk.NewDecFromInt(liquidity).Sub(
minCollateralLiquidity.MulInt(totalTokenCollateral.AmountOf(denom)),
)
return sdk.MaxInt(moduleAvailableLiquidity.TruncateInt(), sdk.ZeroInt()), nil
}
// ModuleMaxWithdraw calculates the maximum available amount of uToken to withdraw from the module given the amount of
// user's spendable tokens. The calculation first finds the maximum amount of non-collateral uTokens the user can
// withdraw up to the amount in their wallet, then determines how much collateral can be withdrawn in addition to that.
// The returned value is the sum of the two values.
func (k Keeper) ModuleMaxWithdraw(ctx sdk.Context, spendableUTokens sdk.Coin) (sdkmath.Int, error) {
denom := coin.StripUTokenDenom(spendableUTokens.Denom)
moduleAvailableLiquidity, err := k.ModuleAvailableLiquidity(ctx, denom)
if err != nil {
return sdk.ZeroInt(), err
}
if spendableUTokens.Amount.GTE(moduleAvailableLiquidity) {
return moduleAvailableLiquidity, nil
}
// Get module collateral for the uDenom
totalCollateral := k.GetTotalCollateral(ctx, spendableUTokens.Denom)
totalTokenCollateral, err := k.ToTokens(ctx, sdk.NewCoins(totalCollateral))
if err != nil {
return sdk.ZeroInt(), err
}
// If after subtracting all the user_spendable_utokens from the module_available_liquidity,
// the result is higher than the total module_collateral,
// we can withdraw user_spendable_utokens + module_collateral.
if moduleAvailableLiquidity.Sub(spendableUTokens.Amount).GTE(totalTokenCollateral.AmountOf(denom)) {
return spendableUTokens.Amount.Add(totalTokenCollateral.AmountOf(denom)), nil
}
liquidity := k.AvailableLiquidity(ctx, denom)
token, err := k.GetTokenSettings(ctx, denom)
if err != nil {
return sdk.ZeroInt(), err
}
minCollateralLiquidity := token.MinCollateralLiquidity
// At this point we know that there is enough module_available_liquidity to withdraw user_spendable_utokens.
// Now we need to get the module_available_collateral after withdrawing user_spendable_utokens:
//
// min_collateral_liquidity = (module_liquidity - user_spendable_utokens - module_available_collateral)
// / (module_collateral - module_available_collateral)
//
// module_available_collateral = (module_liquidity - user_spendable_utokens - min_collateral_liquidity
// * module_collateral) / (1 - min_collateral_liquidity)
moduleAvailableCollateral := (sdk.NewDecFromInt(liquidity.Sub(spendableUTokens.Amount)).Sub(
minCollateralLiquidity.MulInt(
totalTokenCollateral.AmountOf(denom),
),
)).Quo(sdk.NewDec(1).Sub(minCollateralLiquidity))
// Adding (user_spendable_utokens + module_available_collateral) we obtain the max uTokens the account can
// withdraw from the module.
return spendableUTokens.Amount.Add(moduleAvailableCollateral.TruncateInt()), nil
}