diff --git a/CHANGELOG.md b/CHANGELOG.md index ad6a87c489..2bc426f305 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ - [1096](https://github.com/umee-network/umee/pull/1096) Add `max_collateral_share` to the x/leverage token registry. - [1094](https://github.com/umee-network/umee/pull/1094) Added TotalCollateral query. - [1099](https://github.com/umee-network/umee/pull/1099) Added TotalBorrowed query. +- [1141](https://github.com/umee-network/umee/pull/1141) Add `max_supply_utilization` and `min_collateral_liquidity` to the x/leverage token registry. ### Improvements diff --git a/app/test_helpers.go b/app/test_helpers.go index 6084fdf229..4e34dde191 100644 --- a/app/test_helpers.go +++ b/app/test_helpers.go @@ -107,20 +107,23 @@ func IntegrationTestNetworkConfig() network.Config { // Modify the x/leverage genesis state leverageGenState.Registry = append(leverageGenState.Registry, leveragetypes.Token{ - BaseDenom: BondDenom, - SymbolDenom: DisplayDenom, - Exponent: 6, - ReserveFactor: sdk.MustNewDecFromStr("0.100000000000000000"), - CollateralWeight: sdk.MustNewDecFromStr("0.050000000000000000"), - LiquidationThreshold: sdk.MustNewDecFromStr("0.050000000000000000"), - BaseBorrowRate: sdk.MustNewDecFromStr("0.020000000000000000"), - KinkBorrowRate: sdk.MustNewDecFromStr("0.200000000000000000"), - MaxBorrowRate: sdk.MustNewDecFromStr("1.50000000000000000"), - KinkUtilization: sdk.MustNewDecFromStr("0.200000000000000000"), - LiquidationIncentive: sdk.MustNewDecFromStr("0.180000000000000000"), - EnableMsgSupply: true, - EnableMsgBorrow: true, - Blacklist: false, + BaseDenom: BondDenom, + SymbolDenom: DisplayDenom, + Exponent: 6, + ReserveFactor: sdk.MustNewDecFromStr("0.1"), + CollateralWeight: sdk.MustNewDecFromStr("0.05"), + LiquidationThreshold: sdk.MustNewDecFromStr("0.05"), + BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), + KinkBorrowRate: sdk.MustNewDecFromStr("0.2"), + MaxBorrowRate: sdk.MustNewDecFromStr("1.5"), + KinkUtilization: sdk.MustNewDecFromStr("0.2"), + LiquidationIncentive: sdk.MustNewDecFromStr("0.18"), + EnableMsgSupply: true, + EnableMsgBorrow: true, + Blacklist: false, + MaxCollateralShare: sdk.MustNewDecFromStr("1"), + MaxSupplyUtilization: sdk.MustNewDecFromStr("1.0"), + MinCollateralLiquidity: sdk.MustNewDecFromStr("0"), }) // Marshal the modified state and add it back into appGenState diff --git a/docs/architecture/ADR-003-borrow-assets.md b/docs/architecture/ADR-003-borrow-assets.md index 95c1affb62..b3b0264721 100644 --- a/docs/architecture/ADR-003-borrow-assets.md +++ b/docs/architecture/ADR-003-borrow-assets.md @@ -47,69 +47,6 @@ The calculated borrow limit, which weighs collateral uTokens against borrowed as Note also that as a consequence of uToken interest, the asset value of uToken collateral increases over time, meaning a user who repays positions in full and redeems collateral uTokens will receive back more base assets than they deposited originally, reducing the effective interest. -### Collateral utilization - -Definitions: - -- `total_supply(tokenA)`: total amount of tokenA provided to the leverage protocol (including coins marked as a collateral). -- `available_supply(tokenA) = total_supply(tokenA) - reserve - total_borrow(tokenA)`: amount of tokenA available in the system for borrowing. -- `supply_utilization(tokenA) = total_borrow(tokenA) / (total_supply(tokenA) - reserve)`. It equals 0 if there is no borrow for tokenA. It equals 1 if all ledable tokenA are borrowed. -- `total_collateral(tokenA)`: total amount of tokenA used as a collateral. - -We define a _token collateral utilization_: - -```go -collateral_utilization(tokenA) = total_collateral(tokenA) / available_supply(tokenA) -``` - -Note: system must not allow to have available_supply to equal zero. - -Intuition: we want collateral utilization to grow when there is less liquid tokenA available in the system to cover the liquidation. -Collateral utilization of tokenA is growing when suppliers withdraw their tokenA collateral or when borrowers take a new loan of tokenA. -If a `tokenA` is not used a collateral then it's _collateral utilization_ is zero. -It is bigger than 1 when available supply is lower than the amount of `tokenA` used as a collateral. -When it is `N`, it means that only `1/N` of the collateral is available for redemption (u/tokenA -> tokenA). - -#### Examples - -Let's say we have 1000A (token A) supplied to the system (for lending or collateral). Below let's consider a state with total amount of A borrowed (B) and total amount of B used as a collateral (C) and computed collateral utilization (CU): - -1. B=0, C=0 → CU=0 -1. B=0, C=500 → CU=0.5 -1. B=0, C=1000 → CU=1 -1. B=500, C=0 → CU=0 -1. B=500, C=500 → CU=1 -1. B=500, C=1000 → CU=2 -1. B=999, C=0 → CU=0 -1. B=999, C=500 → CU=500 -1. B=999, C=100 → CU=1000 - -#### Motivation - -High collateral utilization is dangerous for the system: - -- When collateral utilization is above 1, liquidators may not be able to withdraw their the liquidated collateral. -- Liquidators, when liquidating a borrower, they get into position their _uToken_. - In case of bad market conditions and magnified liquidations, liquidators will like to redeem the _uToken_ for the principle (the underlying token). - However, when there are many `uToken` redeem operation, the collateral utilization is approaching to 1 and liquidators won't be able to get the principle and sell it to monetize their profits. - This will dramatically increase the risk of getting a profit by liquidators and could cause the system being insolvent. - -Let's draw the following scenario to picture the liquidators risk: - -1. Alice is providing \$1.2M USD supply. -2. Bob is providing \$1.5M in Luna as a collateral and borrows 1M USD from Alice. -3. Charlie provides \$2M in BTC as a collateral and borrows $1.4M in Luna from Bob. -4. Charlie predicts Luna collapse and sells the Luna. -5. Luna is sinking and Bob position has to be liquidated. However: - - Suppliers can liquidate Bob, but they can only redeem up to 6.6% of `u/Luna` because the rest is not available (Charlie borrowed it). - - Charlie will not pay off her borrow position - she will wait for the final collapse and buy Luna cheaply. - - Liquidators will not take the risk of obtaining and holding `u/Luna` when there is a risk of Luna sinking deep. -6. In case of the big crash, liquidators won't perform a liquidation, Bob will run away with 1M USD, system will end up with a bad debt and obligation to pay Alice. - -We propose to set a per token parameter: **max collateral utilization** stored in the `Token` registry (see ADR-004). The system will forbid to make any collateral related operation if the operation would move token _collateral utilization_ above _max collateral utilization_. - -Stable assets will have high max collateral utilization (can go even to 50). Volatile assets should have significant smaller collateral utilization, and assets with high risk should have max collateral utilization close to 1. - ## Detailed Design For the purposes of borrowing and repaying assets, as well as marking uTokens as collateral, the `umee/x/leverage` module does not mint or burn tokens. diff --git a/docs/architecture/ADR-010-market-params.md b/docs/architecture/ADR-010-market-params.md new file mode 100644 index 0000000000..49ff2123f6 --- /dev/null +++ b/docs/architecture/ADR-010-market-params.md @@ -0,0 +1,163 @@ +# ADR 010: Market Parameters + +## Changelog + +- Jul 4, 2022: Max collateral utilization added to ADR-003 (@robert-zaremba) +- Jul 18, 2022: Initial draft moved discussion to ADR-101 (@toteki) + +## Status + +Proposed + +## Context + +Umee manages the health of individual borrowers using borrow limits, collateral weights, and liquidation thresholds, but it currently lacks parameters that manages the health of token markets as a whole. + +Several new parameters and alternatives are being proposed. + +## Decision + +Parameters we decide to use will be listed below in subsections: +- Max Collateral Share +- Max Supply Utilization +- Min Collateral Liquidity + +Rejected or alternative implementations will appear in the alternatives section: +- Max Collateral Utilization + +### Useful Definitions + +The following terms may appear in multiple discussions below: + +- `total_supply(token)` total amount of a base token which has been supplied to the system, including that which has been borrowed out, plus outstanding interest, minus reserves. +- `total_supply(utoken)` total amount of uTokens of a given type in existence. When exchanged for base tokens, this amount is worth exactly the total supply. +- `total_collateral(utoken)` total amount of a uToken marked as collateral. +- `total_collateral(token)` total amount of a uToken marked as collateral, multiplied by its uToken exchange rate to get the amount of base token collateral it represents. +- `total_borrowed(token)` the sum of all existing debt in a base token, including interest. +- `reserved(token)` the amount of tokens in the module balance which are reserved for paying off bad debt. These tokens are excluded from total supply. +- `available(token)` the amount of supplied tokens which have not been borrowed out or reserved. + +These equations follow: + +```go +available(token) = module_balance(token) - reserved(token) +total_supply(token) = total_borrowed(token) + available(token) +uToken_exchange_rate(token) = total_supply(token) / total_supply(utoken) +``` + +### Maximum Supply Utilization + +One proposed parameter is `MaxSupplyUtilization`, to be defined per token. + +```go +supply_utilization(token) = total_borrowed(token) / total_supply(token) // ranges 0 - 1 +``` + +For example, if `1000 ATOM` are supplied and `200 ATOM` are borrowed, `supply_utilization` is `0.2`. + +Implementing `MaxSupplyUtilization` would restrict `MsgBorrow` from increasing `total_borrowed` above a desired level. + +It may or may not restrict `MsgWithdraw` from decreasing `total_supply(token)` below a desired level - adding this restriction might trap suppliers in a liquidity crisis in exchange for keeping more supply available for `MsgLiquidate`. + +| Message Type | Current Decision | +| - | - | +| `MsgBorrow` | Restrict | +| `MsgWithdraw` | Allow | +| `MsgLiquidate` | Allow | + +`MaxSupplyUtilization` could still be indirectly exceeded by borrow interest accruing. + +Additionally, dynamic interest rates, which functioned as supply utilization ranged from `0` to `1`, would instead be adjusted expect values from `0` to `MaxSupplyUtilization`. + +The motivation for restricting supply utilization is to reduce the likelihood of situations where suppliers cannot use `MsgWithdraw` due to borrowers borrowing all available supply. + +This parameter overlaps in function with `MinCollateralLiquidity`. By lowering `MaxSupplyUtilization`, we create a buffer zone where `MsgBorrow` cannot reduce liquidity any further, but `MsgWithdraw` is still available. This helps protect suppliers and makes it more difficult to reach `MinCollateralLiquidity` overall. + +### Maximum Collateral Share + +Another proposed parameter is `MaxCollateralShare`, to be defined per token. + +```go +total_collateral_value(token) = total_collateral(token) * oracle_price_usd(token) +collateral_share(token) = total_collateral_value(token) / Sum_All_Tokens(total_collateral_value) // ranges 0 - 1 +``` + +For example, if `10 ATOM` collateral exists at a price of `$20` alongside `100 OSMO` collateral at a price of `$1`, then `collateral_share(ATOM)` is `10 * $20 / $300 = 0.66` and `collateral_share(OSMO)` is `$100 / $300 = 0.33`. + +Implementing `MaxCollateralShare` would restrict `MsgCollateralize` from increasing `total_collateral` above a desired level. + +`MaxCollateralShare` could still be indirectly exceeded by fluctuating oracle prices, `MsgDecollateralize` or `MsgLiquidate` of other tokens, or interest accruing in one denom faster than another. + +### Minimum Collateral Liquidity + +Another proposed parameter is `MinCollateralLiquidity`, to be defined per token. + +It is the reciprocal of `MaxCollateralUtilization`, functionally equivalent in which messages it would restrict. + +```go +collateral_liquidity(token) = available(token) / total_collateral(token) // ranges 0 - ∞ +``` + +For example if `200 ATOM` of collateral exists and the `x/leverage` module only has `40 ATOM` available (with the rest of supply being borrowed out), then `collateral_liquidity(ATOM) = 0.2`. + +Stable assets can have low `MinCollateralLiquidity` (as low as `0.02`, but more likely around `0.15`). Volatile assets should have significantly safer values, for example `0.5` or `1`. + +Implementing `MinCollateralLiquidity` would restrict `MsgBorrow` and `MsgWithdraw` from decreasing `available_supply`, or `MsgCollateralize` from increasing `total_collateral` in certain market conditions. + +It may or may not allow `MsgLiquidate` to decrease `available_supply` under the same conditions, to prevent crises. + +| Message Type | Current Decision | +| - | - | +| `MsgBorrow` | Restrict | +| `MsgWithdraw` | Restrict | +| `MsgLiquidate` | Allow | + +`MinCollateralLiquidity` could still be indirectly exceeded by supply interest accruing on a collateral denom's uToken exchange rate. + +It has also been proposed separately to factor `collateral_liquidity` (or `collateral_utilization`) into dynamic interest rates, to enhance the current model which uses `supply_utilization` (see ADR-004). + +## Alternatives + +### Collateral Utilization + +One proposed parameter is `MaxCollateralUtilization`, to be defined per token. + +It is the reciprocal of `MinCollateralLiquidity`, so the motivation and tradeoffs will be the same. + +```go +collateral_utilization(token) = total_collateral(token) / available(token) // ranges 0 - ∞ +``` + +For example if `200 ATOM` of collateral exists and the `x/leverage` module only has `40 ATOM` available (with the rest of supply being borrowed out), then `collateral_utilization(ATOM) = 5`. + +This quantity has the property of increasing rapidly (as 1/N -> 0) when available supply is under stress. + +Stable assets will have high max collateral utilization (can go even to 50). Volatile assets should have significantly smaller collateral utilization, and assets with high risk should have max collateral utilization close to 1. + +#### Motivation + +High collateral utilization is dangerous for the system: When collateral utilization is above 1, liquidators may not be able to withdraw their the liquidated collateral. + +Let's draw the following scenario to picture the liquidators risk: + +> | User | Supply | Collateral | Borrowed | +> | - | - | - | - | +> | Alice| $1.2M BTC | - | - | +> | Bob | - | $1.5M LUNA | $1M BTC | +> | Charlie | - | $2M BTC | $1.4M LUNA | +> +> - Charlie predicts Luna collapse and sells the Luna. +> - Luna is sinking and Bob's position has to be liquidated. However: +> - Liquidators can liquidate Bob, but they can only redeem up to 6.6% of `u/Luna` because the rest is not available (Charlie borrowed it). +> - Charlie will not pay off her borrow position - she will wait for the final collapse and buy Luna cheaply. +> - Liquidators will not take the risk of obtaining and holding `u/Luna` when there is a risk of Luna sinking deep. +> - In case of the big crash, knowledgeable liquidators won't liquidate Bob, Bob will run away with $1M of BTC, and the system will end up with a bad debt and obligation to pay Alice. + +## Consequences + +### Positive +- Multiple ways of preserving market health +- Per-token parameters allow fine grained control + +### Negative +- Users may find restrictions unfair, e.g. "why can't I borrow just because other people's collateral is too high?" \ No newline at end of file diff --git a/docs/architecture/README.md b/docs/architecture/README.md index db9409c2c5..407f12523c 100644 --- a/docs/architecture/README.md +++ b/docs/architecture/README.md @@ -36,6 +36,7 @@ Note the context/background should be written in the present tense. - [ADR-006: Oracle](./ADR-006-oracle.md) - [ADR-007: Bad debt](./ADR-007-bad-debt.md) - [ADR-008: Borrow tracking](./ADR-008-borrow-tracking.md) +- [ADR-010: Market health parameters](./ADR-009-market-params.md) ### Accepted diff --git a/proto/umee/leverage/v1/leverage.proto b/proto/umee/leverage/v1/leverage.proto index 5649ece131..9c3d579dd5 100644 --- a/proto/umee/leverage/v1/leverage.proto +++ b/proto/umee/leverage/v1/leverage.proto @@ -10,7 +10,7 @@ option (gogoproto.goproto_getters_all) = false; // Params defines the parameters for the leverage module. message Params { option (gogoproto.goproto_stringer) = false; - // The complete_liquidation_threshold determines how far over their borrow + // Complete Liquidation Threshold determines how far over their borrow // limit a borrower must be in order for their positions to be liquidated // fully in a single event. string complete_liquidation_threshold = 2 [ @@ -18,7 +18,7 @@ message Params { (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"complete_liquidation_threshold\"" ]; - // The minimum_close_factor determines the portion of a borrower's position + // Minimum Close Factor determines the portion of a borrower's position // that can be liquidated in a single event, when the borrower is just barely // over their borrow limit. string minimum_close_factor = 3 [ @@ -26,14 +26,14 @@ message Params { (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"minimum_close_factor\"" ]; - // The oracle_reward_factor determines the portion of interest accrued on + // Oracle Reward Factor determines the portion of interest accrued on // borrows that is sent to the oracle module to fund its reward pool. string oracle_reward_factor = 4 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"oracle_reward_factor\"" ]; - // The small_liquidation_size determines the USD value at which a borrow is + // Small Liquidation Size determines the USD value at which a borrow is // considered small enough to be liquidated in a single transaction, bypassing // dynamic close factor. string small_liquidation_size = 5 [ @@ -43,24 +43,24 @@ message Params { ]; } -// Token defines a token, along with its capital metadata, in the Umee capital -// facility that can be supplied and borrowed. +// Token defines a token, along with its metadata and parameters, in the Umee +// capital facility that can be supplied and borrowed. message Token { option (gogoproto.equal) = true; - // The base_denom defines the denomination of the underlying base token. + // Base Denom is the denomination of the underlying base token. string base_denom = 1 [(gogoproto.moretags) = "yaml:\"base_denom\""]; - // The reserve factor defines what portion of accrued interest of the asset - // type goes to reserves. + // Reserve Factor defines what portion of accrued interest goes to reserves + // when this token is borrowed. string reserve_factor = 2 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"reserve_factor\"" ]; - // The collateral_weight defines what amount of the total value of the asset - // can contribute to a users borrowing power. If the collateral_weight is + // Collateral Weight defines what portion of the total value of the asset + // can contribute to a users borrowing power. If the collateral weight is // zero, using this asset as collateral against borrowing will be disabled. string collateral_weight = 3 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", @@ -68,7 +68,7 @@ message Token { (gogoproto.moretags) = "yaml:\"collateral_weight\"" ]; - // The liquidation_threshold defines what amount of the total value of the + // Liquidation Threshold defines what amount of the total value of the // asset can contribute to a user's liquidation threshold (above which they // become eligible for liquidation). string liquidation_threshold = 4 [ @@ -77,7 +77,7 @@ message Token { (gogoproto.moretags) = "yaml:\"liquidation_threshold\"" ]; - // The base_borrow_rate defines the minimum interest rate for borrowing this + // Base Borrow Rate defines the minimum interest rate for borrowing this // asset. string base_borrow_rate = 5 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", @@ -85,31 +85,31 @@ message Token { (gogoproto.moretags) = "yaml:\"base_borrow_rate\"" ]; - // The kink_borrow_rate defines the interest rate for borrowing this - // asset when utilization equals to 'kink_utilization'. + // Kink Borrow Rate defines the interest rate for borrowing this + // asset when supply utilization is equal to 'kink_utilization'. string kink_borrow_rate = 6 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"kink_borrow_rate\"" ]; - // The max_borrow_rate defines the interest rate for borrowing this - // asset (seen when supply utilization is 100%). + // Max Borrow Rate defines the interest rate for borrowing this + // asset when supply utilization is at its maximum. string max_borrow_rate = 7 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"max_borrow_rate\"" ]; - // The kink_utilization defines the value where the kink rate kicks off for - // borrow rates. + // Kink Utilization defines the supply utilization value where + // the kink in the borrow interest rate function occurs. string kink_utilization = 8 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", (gogoproto.nullable) = false, (gogoproto.moretags) = "yaml:\"kink_utilization\"" ]; - // The liquidation_incentive determines the portion of bonus collateral of + // Liquidation Incentive determines the portion of bonus collateral of // a token type liquidators receive as a liquidation reward. string liquidation_incentive = 9 [ (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", @@ -117,24 +117,27 @@ message Token { (gogoproto.moretags) = "yaml:\"liquidation_incentive\"" ]; - // The symbol_denom and exponent are solely used to update the oracle's accept - // list of allowed tokens. + // Symbol Denom is the human readable denomination of this token. string symbol_denom = 10 [(gogoproto.moretags) = "yaml:\"symbol_denom\""]; + + // Exponent is the power of ten by which to multiply, in order to convert + // an amount of the token denoted in its symbol denom to the actual amount + // of its base denom. uint32 exponent = 11 [(gogoproto.moretags) = "yaml:\"exponent\""]; - // Allows supplying for lending or collateral using this token. Note that - // withdrawing is always enabled. Disabling supplying would be one step in - // phasing out an asset type. + // EnableMsgSupply allows supplying for lending or collateral using this + // token. Note that withdrawing is always enabled. Disabling supplying would + // be one step in phasing out an asset type. bool enable_msg_supply = 12 [(gogoproto.moretags) = "yaml:\"enable_msg_supply\""]; - // Allows borrowing of this token. Note that repaying is always enabled. - // Disabling borrowing would be one step in phasing out an asset type, but - // could also be used from the start for asset types meant to be collateral - // only, like meTokens. + // EnableMsgBorrow allows borrowing of this token. Note that repaying is + // always enabled. Disabling borrowing would be one step in phasing out an + // asset type, but could also be used from the start for asset types meant + // to be collateral only, like meTokens. bool enable_msg_borrow = 13 [(gogoproto.moretags) = "yaml:\"enable_msg_borrow\""]; - // This should only be used to eliminate an asset completely. A blacklisted + // Blacklist should only be used to eliminate an asset completely. A blacklisted // asset is treated as though its oracle price is zero, and thus ignored by // calculations such as collateral value and borrow limit. Can still be repaid // or withdrawn, but not liquidated. A blacklisted token must have enable_msg_supply @@ -142,12 +145,31 @@ message Token { // oracle and price feeder as well. bool blacklist = 14; - // Maximum collateral share specifies how much of the - // system's overall collateral be provided by a single token. - // Value is a percent; allowed values are in [0, 100] range. - // 100 means that the token has no restriction. 10 means maximum 10% of total - // collateral value can provided by this token. - uint32 max_collateral_share = 15 [ - (gogoproto.moretags) = "yaml:\"max_collateral_share\"" + // Max Collateral Share specifies how much of the system's overall collateral + // can be provided by a given token. 1.0 means that the token has no restriction. + // 0.1 means maximum 10% of system's total collateral value can provided by this token. + string max_collateral_share = 15 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"max_collateral_share\"" + ]; + + // Max Supply Utilization specifies the maximum supply utilization a token is + // allowed to reach as a direct result of user borrowing. It can still be exceeded + // due to withdrawals, interest, and liquidations. + string max_supply_utilization = 16 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"max_supply_utilization\"" + ]; + + // Min Collateral Liquidity specifies the minimum collateral liquidity a token is + // allowed to reach as a direct result of users borrowing, collateralizing, or + // withdrawing assets. Liquidity can only drop below this value due to interest + // or liquidations. + string min_collateral_liquidity = 17 [ + (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec", + (gogoproto.nullable) = false, + (gogoproto.moretags) = "yaml:\"min_collateral_liquidity\"" ]; } diff --git a/x/leverage/client/tests/tests.go b/x/leverage/client/tests/tests.go index 9dbf8f6470..fc08842724 100644 --- a/x/leverage/client/tests/tests.go +++ b/x/leverage/client/tests/tests.go @@ -260,20 +260,23 @@ func (s *IntegrationTestSuite) TestLeverageScenario() { Registry: []types.Token{ { // must match app/test_helpers.go/IntegrationTestNetworkConfig - BaseDenom: umeeapp.BondDenom, - SymbolDenom: umeeapp.DisplayDenom, - Exponent: 6, - ReserveFactor: sdk.MustNewDecFromStr("0.1"), - CollateralWeight: sdk.MustNewDecFromStr("0.05"), - LiquidationThreshold: sdk.MustNewDecFromStr("0.05"), - BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), - KinkBorrowRate: sdk.MustNewDecFromStr("0.2"), - MaxBorrowRate: sdk.MustNewDecFromStr("1.5"), - KinkUtilization: sdk.MustNewDecFromStr("0.2"), - LiquidationIncentive: sdk.MustNewDecFromStr("0.18"), - EnableMsgSupply: true, - EnableMsgBorrow: true, - Blacklist: false, + BaseDenom: umeeapp.BondDenom, + SymbolDenom: umeeapp.DisplayDenom, + Exponent: 6, + ReserveFactor: sdk.MustNewDecFromStr("0.1"), + CollateralWeight: sdk.MustNewDecFromStr("0.05"), + LiquidationThreshold: sdk.MustNewDecFromStr("0.05"), + BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), + KinkBorrowRate: sdk.MustNewDecFromStr("0.2"), + MaxBorrowRate: sdk.MustNewDecFromStr("1.5"), + KinkUtilization: sdk.MustNewDecFromStr("0.2"), + LiquidationIncentive: sdk.MustNewDecFromStr("0.18"), + EnableMsgSupply: true, + EnableMsgBorrow: true, + Blacklist: false, + MaxCollateralShare: sdk.MustNewDecFromStr("1"), + MaxSupplyUtilization: sdk.MustNewDecFromStr("1"), + MinCollateralLiquidity: sdk.MustNewDecFromStr("0"), }, }, }, @@ -310,9 +313,8 @@ func (s *IntegrationTestSuite) TestLeverageScenario() { }, false, &types.QuerySupplyAPYResponse{}, - // Borrow rate * (1 - ReserveFactor - OracleRewardFactor) - // 1.50 * (1 - 0.10 - 0.01) = 0.89 * 1.5 = 1.335 - &types.QuerySupplyAPYResponse{APY: sdk.MustNewDecFromStr("1.335")}, + // Supply utilization is zero, so supply APY is 0% + &types.QuerySupplyAPYResponse{APY: sdk.MustNewDecFromStr("0")}, }, testQuery{ "query borrow APY", @@ -322,10 +324,8 @@ func (s *IntegrationTestSuite) TestLeverageScenario() { }, false, &types.QueryBorrowAPYResponse{}, - // This is an edge case technically - when effective supply, meaning - // module balance + total borrows, is zero, utilization (0/0) is - // interpreted as 100% so max borrow rate (150% APY) is used. - &types.QueryBorrowAPYResponse{APY: sdk.MustNewDecFromStr("1.50")}, + // Supply utilization is 0% so base borrow rate (2% APY) is used. + &types.QueryBorrowAPYResponse{APY: sdk.MustNewDecFromStr("0.02")}, }, } diff --git a/x/leverage/gov_handler_test.go b/x/leverage/gov_handler_test.go index 5084dfc3ad..7a5a97d391 100644 --- a/x/leverage/gov_handler_test.go +++ b/x/leverage/gov_handler_test.go @@ -17,20 +17,23 @@ import ( func newTestToken(base, symbol, reserveFactor string) types.Token { return types.Token{ - BaseDenom: base, - SymbolDenom: symbol, - Exponent: 6, - ReserveFactor: sdk.MustNewDecFromStr(reserveFactor), - CollateralWeight: sdk.MustNewDecFromStr("0.25"), - LiquidationThreshold: sdk.MustNewDecFromStr("0.25"), - BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), - KinkBorrowRate: sdk.MustNewDecFromStr("0.22"), - MaxBorrowRate: sdk.MustNewDecFromStr("1.52"), - KinkUtilization: sdk.MustNewDecFromStr("0.8"), - LiquidationIncentive: sdk.MustNewDecFromStr("0.1"), - EnableMsgSupply: true, - EnableMsgBorrow: true, - Blacklist: false, + BaseDenom: base, + SymbolDenom: symbol, + Exponent: 6, + ReserveFactor: sdk.MustNewDecFromStr(reserveFactor), + CollateralWeight: sdk.MustNewDecFromStr("0.25"), + LiquidationThreshold: sdk.MustNewDecFromStr("0.25"), + BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), + KinkBorrowRate: sdk.MustNewDecFromStr("0.22"), + MaxBorrowRate: sdk.MustNewDecFromStr("1.52"), + KinkUtilization: sdk.MustNewDecFromStr("0.8"), + LiquidationIncentive: sdk.MustNewDecFromStr("0.1"), + EnableMsgSupply: true, + EnableMsgBorrow: true, + Blacklist: false, + MaxCollateralShare: sdk.MustNewDecFromStr("1.0"), + MaxSupplyUtilization: sdk.MustNewDecFromStr("0.9"), + MinCollateralLiquidity: sdk.MustNewDecFromStr("0.0"), } } diff --git a/x/leverage/keeper/borrows.go b/x/leverage/keeper/borrows.go index bcfffd08db..5c0ff682aa 100644 --- a/x/leverage/keeper/borrows.go +++ b/x/leverage/keeper/borrows.go @@ -59,19 +59,37 @@ func (k Keeper) GetAvailableToBorrow(ctx sdk.Context, denom string) sdk.Int { // SupplyUtilization calculates the current supply utilization of a token denom. func (k Keeper) SupplyUtilization(ctx sdk.Context, denom string) sdk.Dec { + // Current utilization is calculated using a hypothetical zero-amount borrow + return k.supplyUtilizationAfterBorrow(ctx, sdk.NewCoin(denom, sdk.ZeroInt())) +} + +// supplyUtilizationAfterBorrow calculates the resulting supply utilization of a token +// denom if a proposed borrow were to execute. +func (k Keeper) supplyUtilizationAfterBorrow(ctx sdk.Context, borrow sdk.Coin) sdk.Dec { // Supply utilization is equal to total borrows divided by the token supply // (including borrowed tokens yet to be repaid and excluding tokens reserved). - moduleBalance := k.ModuleBalance(ctx, denom).ToDec() - reserveAmount := k.GetReserveAmount(ctx, denom).ToDec() - totalBorrowed := k.GetTotalBorrowed(ctx, denom).Amount.ToDec() + moduleBalance := k.ModuleBalance(ctx, borrow.Denom) + reserveAmount := k.GetReserveAmount(ctx, borrow.Denom) + totalBorrowed := k.GetTotalBorrowed(ctx, borrow.Denom).Amount tokenSupply := totalBorrowed.Add(moduleBalance).Sub(reserveAmount) - // This edge case can be safely interpreted as 100% utilization. - if totalBorrowed.GTE(tokenSupply) { - return sdk.OneDec() + // Adjust based on the proposed borrow. Note that token supply is unchanged because + // module balance would decrease by the same amount total borrows increase. + totalBorrowed = totalBorrowed.Add(borrow.Amount) + + // This case is impossible to reach in practice + if tokenSupply.IsNegative() { + return sdk.MaxSortableDec + } + + if tokenSupply.IsZero() { + return sdk.ZeroDec() } - return totalBorrowed.Quo(tokenSupply) + // Utilization ranges 0 to 1 generally, or > 1 when reserves exceed module balance. + // In this calculation, utilization will exceed 1 only if the proposed borrow is greater + // than unreserved module balance. + return totalBorrowed.ToDec().Quo(tokenSupply.ToDec()) } // CalculateBorrowLimit uses the price oracle to determine the borrow limit (in USD) provided by diff --git a/x/leverage/keeper/borrows_test.go b/x/leverage/keeper/borrows_test.go index ac99f78e98..4b3b1fd6f8 100644 --- a/x/leverage/keeper/borrows_test.go +++ b/x/leverage/keeper/borrows_test.go @@ -118,10 +118,10 @@ func (s *IntegrationTestSuite) TestGetAvailableToBorrow() { // we do not test empty denom, as that will cause a panic } -func (s *IntegrationTestSuite) TestDeriveBorrowUtilization() { - // unregistered denom (0 borrowed and 0 lending pool is considered 100%) +func (s *IntegrationTestSuite) TestDeriveSupplyUtilization() { + // unregistered denom (0% utilization) utilization := s.tk.SupplyUtilization(s.ctx, "abcd") - s.Require().Equal(sdk.OneDec(), utilization) + s.Require().Equal(sdk.ZeroDec(), utilization) // creates account which has supplied 1000 uumee, and borrowed 0 uumee addr := s.setupAccount(umeeDenom, 1000, 1000, 0, true) @@ -171,16 +171,16 @@ func (s *IntegrationTestSuite) TestDeriveBorrowUtilization() { // artificially set reserves to 800 uumee s.Require().NoError(s.tk.SetReserveAmount(s.ctx, sdk.NewInt64Coin(umeeDenom, 800))) - // edge case interpreted as 100% utilization (1200 / 1200+200-800) + // edge case interpreted as 200% utilization (1200 / 1200+200-800) utilization = s.tk.SupplyUtilization(s.ctx, umeeDenom) - s.Require().Equal(sdk.MustNewDecFromStr("1.0"), utilization) + s.Require().Equal(sdk.MustNewDecFromStr("2.0"), utilization) // artificially set reserves to 4000 uumee s.Require().NoError(s.tk.SetReserveAmount(s.ctx, sdk.NewInt64Coin(umeeDenom, 4000))) - // impossible case interpreted as 100% utilization (1200 / 1200+200-4000) + // impossible case interpreted as near-infinite utilization (1200 / 1200+200-4000) utilization = s.tk.SupplyUtilization(s.ctx, umeeDenom) - s.Require().Equal(sdk.MustNewDecFromStr("1.0"), utilization) + s.Require().Equal(sdk.MaxSortableDec, utilization) } func (s *IntegrationTestSuite) TestCalculateBorrowLimit() { diff --git a/x/leverage/keeper/genesis.go b/x/leverage/keeper/genesis.go index af8b63d5ce..b6fc400036 100644 --- a/x/leverage/keeper/genesis.go +++ b/x/leverage/keeper/genesis.go @@ -143,7 +143,7 @@ func (k Keeper) getAllBadDebts(ctx sdk.Context) []types.BadDebt { prefix := types.KeyPrefixBadDebt badDebts := []types.BadDebt{} - iterator := func(key, val []byte) error { + iterator := func(key, _ []byte) error { addr := types.AddressFromKey(key, prefix) denom := types.DenomFromKeyWithAddress(key, prefix) diff --git a/x/leverage/keeper/interest.go b/x/leverage/keeper/interest.go index ee3eacf483..074572edac 100644 --- a/x/leverage/keeper/interest.go +++ b/x/leverage/keeper/interest.go @@ -46,7 +46,7 @@ func (k Keeper) DeriveBorrowAPY(ctx sdk.Context, denom string) sdk.Dec { } // DeriveSupplyAPY derives the current supply interest rate on a token denom -// using its supply utilization borrow APY. Returns zero on invalid asset. +// using its supply utilization and borrow APY. Returns zero on invalid asset. func (k Keeper) DeriveSupplyAPY(ctx sdk.Context, denom string) sdk.Dec { token, err := k.GetTokenSettings(ctx, denom) if err != nil { diff --git a/x/leverage/keeper/invariants.go b/x/leverage/keeper/invariants.go index 120d80d240..497615b6be 100644 --- a/x/leverage/keeper/invariants.go +++ b/x/leverage/keeper/invariants.go @@ -202,7 +202,7 @@ func BorrowAPYInvariant(k Keeper) sdk.Invariant { // Iterate through all denoms of registered tokens in the // keeper, ensuring none have a negative borrow APY. - err := k.iterate(ctx, tokenPrefix, func(key, val []byte) error { + err := k.iterate(ctx, tokenPrefix, func(key, _ []byte) error { denom := types.DenomFromKey(key, tokenPrefix) borrowAPY := k.DeriveBorrowAPY(ctx, denom) @@ -238,7 +238,7 @@ func SupplyAPYInvariant(k Keeper) sdk.Invariant { // Iterate through all denoms of registered tokens in the // keeper, ensuring none have a negative supply APY. - err := k.iterate(ctx, tokenPrefix, func(key, val []byte) error { + err := k.iterate(ctx, tokenPrefix, func(key, _ []byte) error { denom := types.DenomFromKey(key, tokenPrefix) supplyAPY := k.DeriveSupplyAPY(ctx, denom) @@ -274,7 +274,7 @@ func InterestScalarsInvariant(k Keeper) sdk.Invariant { // Iterate through all denoms of registered tokens in the // keeper, ensuring none have an interest scalar less than one. - err := k.iterate(ctx, tokenPrefix, func(key, val []byte) error { + err := k.iterate(ctx, tokenPrefix, func(key, _ []byte) error { denom := types.DenomFromKey(key, tokenPrefix) scalar := k.getInterestScalar(ctx, denom) diff --git a/x/leverage/keeper/iter.go b/x/leverage/keeper/iter.go index fcf1623da4..7be4bc5c1e 100644 --- a/x/leverage/keeper/iter.go +++ b/x/leverage/keeper/iter.go @@ -31,7 +31,7 @@ func (k Keeper) GetAllBadDebts(ctx sdk.Context) []types.BadDebt { prefix := types.KeyPrefixBadDebt badDebts := []types.BadDebt{} - iterator := func(key, val []byte) error { + iterator := func(key, _ []byte) error { addr := types.AddressFromKey(key, prefix) denom := types.DenomFromKeyWithAddress(key, prefix) badDebts = append(badDebts, types.NewBadDebt(addr.String(), denom)) @@ -52,7 +52,7 @@ func (k Keeper) GetAllBadDebts(ctx sdk.Context) []types.BadDebt { func (k Keeper) GetAllRegisteredTokens(ctx sdk.Context) []types.Token { tokens := []types.Token{} - iterator := func(key, val []byte) error { + iterator := func(_, val []byte) error { var t types.Token if err := t.Unmarshal(val); err != nil { // improperly marshaled Token should never happen @@ -180,7 +180,7 @@ func (k Keeper) GetEligibleLiquidationTargets(ctx sdk.Context) ([]sdk.AccAddress liquidationTargets := []sdk.AccAddress{} checkedAddrs := map[string]struct{}{} - iterator := func(key, val []byte) error { + iterator := func(key, _ []byte) error { // get borrower address from key addr := types.AddressFromKey(key, prefix) @@ -228,7 +228,7 @@ func (k Keeper) GetEligibleLiquidationTargets(ctx sdk.Context) ([]sdk.AccAddress func (k Keeper) SweepBadDebts(ctx sdk.Context) error { prefix := types.KeyPrefixBadDebt - iterator := func(key, value []byte) error { + iterator := func(key, _ []byte) error { addr := types.AddressFromKey(key, prefix) denom := types.DenomFromKeyWithAddress(key, prefix) diff --git a/x/leverage/keeper/keeper.go b/x/leverage/keeper/keeper.go index e51afd0717..6c8d3cde70 100644 --- a/x/leverage/keeper/keeper.go +++ b/x/leverage/keeper/keeper.go @@ -201,12 +201,20 @@ func (k Keeper) Borrow(ctx sdk.Context, borrowerAddr sdk.AccAddress, borrow sdk. return err } - // Ensure module account has sufficient unreserved tokens to loan out - reservedAmount := k.GetReserveAmount(ctx, borrow.Denom) - availableAmount := k.ModuleBalance(ctx, borrow.Denom) - if borrow.Amount.GT(availableAmount.Sub(reservedAmount)) { + token, err := k.GetTokenSettings(ctx, borrow.Denom) + if err != nil { + return err + } + + su := k.supplyUtilizationAfterBorrow(ctx, borrow) + if su.GT(sdk.OneDec()) { + // Ensure module account has sufficient unreserved tokens to loan out return types.ErrLendingPoolInsufficient.Wrap(borrow.String()) } + if su.GT(token.MaxSupplyUtilization) { + // Ensure max supply utilization is not exceeded + return types.ErrMaxSupplyUtilization + } // Determine amount of all tokens currently borrowed borrowed := k.GetBorrowerBorrows(ctx, borrowerAddr) diff --git a/x/leverage/keeper/keeper_test.go b/x/leverage/keeper/keeper_test.go index cc7b8d4bca..b8ecb5bdb7 100644 --- a/x/leverage/keeper/keeper_test.go +++ b/x/leverage/keeper/keeper_test.go @@ -33,20 +33,23 @@ var ( // creates a test token with reasonable initial parameters func newToken(base, symbol string) types.Token { return types.Token{ - BaseDenom: base, - SymbolDenom: symbol, - Exponent: 6, - ReserveFactor: sdk.MustNewDecFromStr("0.20"), - CollateralWeight: sdk.MustNewDecFromStr("0.25"), - LiquidationThreshold: sdk.MustNewDecFromStr("0.25"), - BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), - KinkBorrowRate: sdk.MustNewDecFromStr("0.22"), - MaxBorrowRate: sdk.MustNewDecFromStr("1.52"), - KinkUtilization: sdk.MustNewDecFromStr("0.8"), - LiquidationIncentive: sdk.MustNewDecFromStr("0.1"), - EnableMsgSupply: true, - EnableMsgBorrow: true, - Blacklist: false, + BaseDenom: base, + SymbolDenom: symbol, + Exponent: 6, + ReserveFactor: sdk.MustNewDecFromStr("0.20"), + CollateralWeight: sdk.MustNewDecFromStr("0.25"), + LiquidationThreshold: sdk.MustNewDecFromStr("0.25"), + BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), + KinkBorrowRate: sdk.MustNewDecFromStr("0.22"), + MaxBorrowRate: sdk.MustNewDecFromStr("1.52"), + KinkUtilization: sdk.MustNewDecFromStr("0.8"), + LiquidationIncentive: sdk.MustNewDecFromStr("0.1"), + EnableMsgSupply: true, + EnableMsgBorrow: true, + Blacklist: false, + MaxCollateralShare: sdk.MustNewDecFromStr("1.00000000000000000"), + MaxSupplyUtilization: sdk.MustNewDecFromStr("1.00000000000000000"), + MinCollateralLiquidity: sdk.MustNewDecFromStr("0.00000000000000000"), } } diff --git a/x/leverage/simulation/operations_test.go b/x/leverage/simulation/operations_test.go index 657295ac70..f97c8bb445 100644 --- a/x/leverage/simulation/operations_test.go +++ b/x/leverage/simulation/operations_test.go @@ -32,52 +32,61 @@ func (s *SimTestSuite) SetupTest() { ctx := app.NewContext(checkTx, tmproto.Header{}) umeeToken := types.Token{ - BaseDenom: umeeapp.BondDenom, - ReserveFactor: sdk.MustNewDecFromStr("0.25"), - CollateralWeight: sdk.MustNewDecFromStr("0.5"), - LiquidationThreshold: sdk.MustNewDecFromStr("0.5"), - BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), - KinkBorrowRate: sdk.MustNewDecFromStr("0.2"), - MaxBorrowRate: sdk.MustNewDecFromStr("1.0"), - KinkUtilization: sdk.MustNewDecFromStr("0.8"), - LiquidationIncentive: sdk.MustNewDecFromStr("0.1"), - SymbolDenom: umeeapp.DisplayDenom, - Exponent: 6, - EnableMsgSupply: true, - EnableMsgBorrow: true, - Blacklist: false, + BaseDenom: umeeapp.BondDenom, + ReserveFactor: sdk.MustNewDecFromStr("0.25"), + CollateralWeight: sdk.MustNewDecFromStr("0.5"), + LiquidationThreshold: sdk.MustNewDecFromStr("0.5"), + BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), + KinkBorrowRate: sdk.MustNewDecFromStr("0.2"), + MaxBorrowRate: sdk.MustNewDecFromStr("1.0"), + KinkUtilization: sdk.MustNewDecFromStr("0.8"), + LiquidationIncentive: sdk.MustNewDecFromStr("0.1"), + SymbolDenom: umeeapp.DisplayDenom, + Exponent: 6, + EnableMsgSupply: true, + EnableMsgBorrow: true, + Blacklist: false, + MaxCollateralShare: sdk.MustNewDecFromStr("1"), + MaxSupplyUtilization: sdk.MustNewDecFromStr("0.9"), + MinCollateralLiquidity: sdk.MustNewDecFromStr("0"), } atomIBCToken := types.Token{ - BaseDenom: "ibc/CDC4587874B85BEA4FCEC3CEA5A1195139799A1FEE711A07D972537E18FDA39D", - ReserveFactor: sdk.MustNewDecFromStr("0.25"), - CollateralWeight: sdk.MustNewDecFromStr("0.8"), - LiquidationThreshold: sdk.MustNewDecFromStr("0.8"), - BaseBorrowRate: sdk.MustNewDecFromStr("0.05"), - KinkBorrowRate: sdk.MustNewDecFromStr("0.3"), - MaxBorrowRate: sdk.MustNewDecFromStr("0.9"), - KinkUtilization: sdk.MustNewDecFromStr("0.75"), - LiquidationIncentive: sdk.MustNewDecFromStr("0.11"), - SymbolDenom: "ATOM", - Exponent: 6, - EnableMsgSupply: true, - EnableMsgBorrow: true, - Blacklist: false, + BaseDenom: "ibc/CDC4587874B85BEA4FCEC3CEA5A1195139799A1FEE711A07D972537E18FDA39D", + ReserveFactor: sdk.MustNewDecFromStr("0.25"), + CollateralWeight: sdk.MustNewDecFromStr("0.8"), + LiquidationThreshold: sdk.MustNewDecFromStr("0.8"), + BaseBorrowRate: sdk.MustNewDecFromStr("0.05"), + KinkBorrowRate: sdk.MustNewDecFromStr("0.3"), + MaxBorrowRate: sdk.MustNewDecFromStr("0.9"), + KinkUtilization: sdk.MustNewDecFromStr("0.75"), + LiquidationIncentive: sdk.MustNewDecFromStr("0.11"), + SymbolDenom: "ATOM", + Exponent: 6, + EnableMsgSupply: true, + EnableMsgBorrow: true, + Blacklist: false, + MaxCollateralShare: sdk.MustNewDecFromStr("1"), + MaxSupplyUtilization: sdk.MustNewDecFromStr("0.9"), + MinCollateralLiquidity: sdk.MustNewDecFromStr("0"), } uabc := types.Token{ - BaseDenom: "uabc", - ReserveFactor: sdk.MustNewDecFromStr("0"), - CollateralWeight: sdk.MustNewDecFromStr("0.1"), - LiquidationThreshold: sdk.MustNewDecFromStr("0.1"), - BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), - KinkBorrowRate: sdk.MustNewDecFromStr("0.22"), - MaxBorrowRate: sdk.MustNewDecFromStr("1.52"), - KinkUtilization: sdk.MustNewDecFromStr("0.87"), - LiquidationIncentive: sdk.MustNewDecFromStr("0.1"), - SymbolDenom: "ABC", - Exponent: 6, - EnableMsgSupply: true, - EnableMsgBorrow: true, - Blacklist: false, + BaseDenom: "uabc", + ReserveFactor: sdk.MustNewDecFromStr("0"), + CollateralWeight: sdk.MustNewDecFromStr("0.1"), + LiquidationThreshold: sdk.MustNewDecFromStr("0.1"), + BaseBorrowRate: sdk.MustNewDecFromStr("0.02"), + KinkBorrowRate: sdk.MustNewDecFromStr("0.22"), + MaxBorrowRate: sdk.MustNewDecFromStr("1.52"), + KinkUtilization: sdk.MustNewDecFromStr("0.87"), + LiquidationIncentive: sdk.MustNewDecFromStr("0.1"), + SymbolDenom: "ABC", + Exponent: 6, + EnableMsgSupply: true, + EnableMsgBorrow: true, + Blacklist: false, + MaxCollateralShare: sdk.MustNewDecFromStr("1"), + MaxSupplyUtilization: sdk.MustNewDecFromStr("0.9"), + MinCollateralLiquidity: sdk.MustNewDecFromStr("0"), } tokens := []types.Token{umeeToken, atomIBCToken, uabc} diff --git a/x/leverage/spec/02_state.md b/x/leverage/spec/02_state.md index 2305483897..7c5e2d77ce 100644 --- a/x/leverage/spec/02_state.md +++ b/x/leverage/spec/02_state.md @@ -35,20 +35,22 @@ The `0x01` prefix above allows a governance-controlled `Token Registry` to be st ```go type Token struct { - BaseDenom string - ReserveFactor sdk.Dec - CollateralWeight sdk.Dec - LiquidationThreshold sdk.Dec - BaseBorrowRate sdk.Dec - KinkBorrowRate sdk.Dec - MaxBorrowRate sdk.Dec - KinkUtilization sdk.Dec - LiquidationIncentive sdk.Dec - SymbolDenom string - Exponent uint32 - EnableMsgSupply bool - EnableMsgBorrow bool - Blacklist bool - MaxCollateralShare uint32 + BaseDenom string + ReserveFactor sdk.Dec + CollateralWeight sdk.Dec + LiquidationThreshold sdk.Dec + BaseBorrowRate sdk.Dec + KinkBorrowRate sdk.Dec + MaxBorrowRate sdk.Dec + KinkUtilization sdk.Dec + LiquidationIncentive sdk.Dec + SymbolDenom string + Exponent uint32 + EnableMsgSupply bool + EnableMsgBorrow bool + Blacklist bool + MaxCollateralShare sdk.Dec + MaxSupplyUtilization sdk.Dec + MinCollateralLiquidity sdk.Dec } ``` diff --git a/x/leverage/types/errors.go b/x/leverage/types/errors.go index f7929df3b2..503aa6acb4 100644 --- a/x/leverage/types/errors.go +++ b/x/leverage/types/errors.go @@ -30,4 +30,7 @@ var ( ErrBorrowNotAllowed = sdkerrors.Register(ModuleName, 1120, "borrowing of asset disabled") ErrBlacklisted = sdkerrors.Register(ModuleName, 1121, "base denom blacklisted") ErrCollateralWeightZero = sdkerrors.Register(ModuleName, 1122, "token collateral weight is zero") + ErrMaxSupplyUtilization = sdkerrors.Register(ModuleName, 1123, "market would exceed MaxSupplyUtilization") + ErrMinCollateralLiquidity = sdkerrors.Register(ModuleName, 1124, "market would fall below MinCollateralLiquidity") + ErrMaxCollateralShare = sdkerrors.Register(ModuleName, 1125, "market total collateral would exceed MaxCollateralShare") ) diff --git a/x/leverage/types/leverage.pb.go b/x/leverage/types/leverage.pb.go index 2358f1caff..b05f664791 100644 --- a/x/leverage/types/leverage.pb.go +++ b/x/leverage/types/leverage.pb.go @@ -26,18 +26,18 @@ const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package // Params defines the parameters for the leverage module. type Params struct { - // The complete_liquidation_threshold determines how far over their borrow + // Complete Liquidation Threshold determines how far over their borrow // limit a borrower must be in order for their positions to be liquidated // fully in a single event. CompleteLiquidationThreshold github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=complete_liquidation_threshold,json=completeLiquidationThreshold,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"complete_liquidation_threshold" yaml:"complete_liquidation_threshold"` - // The minimum_close_factor determines the portion of a borrower's position + // Minimum Close Factor determines the portion of a borrower's position // that can be liquidated in a single event, when the borrower is just barely // over their borrow limit. MinimumCloseFactor github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=minimum_close_factor,json=minimumCloseFactor,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"minimum_close_factor" yaml:"minimum_close_factor"` - // The oracle_reward_factor determines the portion of interest accrued on + // Oracle Reward Factor determines the portion of interest accrued on // borrows that is sent to the oracle module to fund its reward pool. OracleRewardFactor github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=oracle_reward_factor,json=oracleRewardFactor,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"oracle_reward_factor" yaml:"oracle_reward_factor"` - // The small_liquidation_size determines the USD value at which a borrow is + // Small Liquidation Size determines the USD value at which a borrow is // considered small enough to be liquidated in a single transaction, bypassing // dynamic close factor. SmallLiquidationSize github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=small_liquidation_size,json=smallLiquidationSize,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"small_liquidation_size" yaml:"small_liquidation_size"` @@ -75,63 +75,72 @@ func (m *Params) XXX_DiscardUnknown() { var xxx_messageInfo_Params proto.InternalMessageInfo -// Token defines a token, along with its capital metadata, in the Umee capital -// facility that can be supplied and borrowed. +// Token defines a token, along with its metadata and parameters, in the Umee +// capital facility that can be supplied and borrowed. type Token struct { - // The base_denom defines the denomination of the underlying base token. + // Base Denom is the denomination of the underlying base token. BaseDenom string `protobuf:"bytes,1,opt,name=base_denom,json=baseDenom,proto3" json:"base_denom,omitempty" yaml:"base_denom"` - // The reserve factor defines what portion of accrued interest of the asset - // type goes to reserves. + // Reserve Factor defines what portion of accrued interest goes to reserves + // when this token is borrowed. ReserveFactor github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,2,opt,name=reserve_factor,json=reserveFactor,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"reserve_factor" yaml:"reserve_factor"` - // The collateral_weight defines what amount of the total value of the asset - // can contribute to a users borrowing power. If the collateral_weight is + // Collateral Weight defines what portion of the total value of the asset + // can contribute to a users borrowing power. If the collateral weight is // zero, using this asset as collateral against borrowing will be disabled. CollateralWeight github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,3,opt,name=collateral_weight,json=collateralWeight,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"collateral_weight" yaml:"collateral_weight"` - // The liquidation_threshold defines what amount of the total value of the + // Liquidation Threshold defines what amount of the total value of the // asset can contribute to a user's liquidation threshold (above which they // become eligible for liquidation). LiquidationThreshold github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,4,opt,name=liquidation_threshold,json=liquidationThreshold,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"liquidation_threshold" yaml:"liquidation_threshold"` - // The base_borrow_rate defines the minimum interest rate for borrowing this + // Base Borrow Rate defines the minimum interest rate for borrowing this // asset. BaseBorrowRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,5,opt,name=base_borrow_rate,json=baseBorrowRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"base_borrow_rate" yaml:"base_borrow_rate"` - // The kink_borrow_rate defines the interest rate for borrowing this - // asset when utilization equals to 'kink_utilization'. + // Kink Borrow Rate defines the interest rate for borrowing this + // asset when supply utilization is equal to 'kink_utilization'. KinkBorrowRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,6,opt,name=kink_borrow_rate,json=kinkBorrowRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"kink_borrow_rate" yaml:"kink_borrow_rate"` - // The max_borrow_rate defines the interest rate for borrowing this - // asset (seen when supply utilization is 100%). + // Max Borrow Rate defines the interest rate for borrowing this + // asset when supply utilization is at its maximum. MaxBorrowRate github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,7,opt,name=max_borrow_rate,json=maxBorrowRate,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"max_borrow_rate" yaml:"max_borrow_rate"` - // The kink_utilization defines the value where the kink rate kicks off for - // borrow rates. + // Kink Utilization defines the supply utilization value where + // the kink in the borrow interest rate function occurs. KinkUtilization github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,8,opt,name=kink_utilization,json=kinkUtilization,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"kink_utilization" yaml:"kink_utilization"` - // The liquidation_incentive determines the portion of bonus collateral of + // Liquidation Incentive determines the portion of bonus collateral of // a token type liquidators receive as a liquidation reward. LiquidationIncentive github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,9,opt,name=liquidation_incentive,json=liquidationIncentive,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"liquidation_incentive" yaml:"liquidation_incentive"` - // The symbol_denom and exponent are solely used to update the oracle's accept - // list of allowed tokens. + // Symbol Denom is the human readable denomination of this token. SymbolDenom string `protobuf:"bytes,10,opt,name=symbol_denom,json=symbolDenom,proto3" json:"symbol_denom,omitempty" yaml:"symbol_denom"` - Exponent uint32 `protobuf:"varint,11,opt,name=exponent,proto3" json:"exponent,omitempty" yaml:"exponent"` - // Allows supplying for lending or collateral using this token. Note that - // withdrawing is always enabled. Disabling supplying would be one step in - // phasing out an asset type. + // Exponent is the power of ten by which to multiply, in order to convert + // an amount of the token denoted in its symbol denom to the actual amount + // of its base denom. + Exponent uint32 `protobuf:"varint,11,opt,name=exponent,proto3" json:"exponent,omitempty" yaml:"exponent"` + // EnableMsgSupply allows supplying for lending or collateral using this + // token. Note that withdrawing is always enabled. Disabling supplying would + // be one step in phasing out an asset type. EnableMsgSupply bool `protobuf:"varint,12,opt,name=enable_msg_supply,json=enableMsgSupply,proto3" json:"enable_msg_supply,omitempty" yaml:"enable_msg_supply"` - // Allows borrowing of this token. Note that repaying is always enabled. - // Disabling borrowing would be one step in phasing out an asset type, but - // could also be used from the start for asset types meant to be collateral - // only, like meTokens. + // EnableMsgBorrow allows borrowing of this token. Note that repaying is + // always enabled. Disabling borrowing would be one step in phasing out an + // asset type, but could also be used from the start for asset types meant + // to be collateral only, like meTokens. EnableMsgBorrow bool `protobuf:"varint,13,opt,name=enable_msg_borrow,json=enableMsgBorrow,proto3" json:"enable_msg_borrow,omitempty" yaml:"enable_msg_borrow"` - // This should only be used to eliminate an asset completely. A blacklisted + // Blacklist should only be used to eliminate an asset completely. A blacklisted // asset is treated as though its oracle price is zero, and thus ignored by // calculations such as collateral value and borrow limit. Can still be repaid // or withdrawn, but not liquidated. A blacklisted token must have enable_msg_supply // and enable_msg_borrow set to false. Such tokens can be safely removed from the // oracle and price feeder as well. Blacklist bool `protobuf:"varint,14,opt,name=blacklist,proto3" json:"blacklist,omitempty"` - // Maximum collateral share specifies how much of the - // system's overall collateral be provided by a single token. - // Value is a percent; allowed values are in [0, 100] range. - // 100 means that the token has no restriction. 10 means maximum 10% of total - // collateral value can provided by this token. - MaxCollateralShare uint32 `protobuf:"varint,15,opt,name=max_collateral_share,json=maxCollateralShare,proto3" json:"max_collateral_share,omitempty" yaml:"max_collateral_share"` + // Max Collateral Share specifies how much of the system's overall collateral + // can be provided by a given token. 1.0 means that the token has no restriction. + // 0.1 means maximum 10% of system's total collateral value can provided by this token. + MaxCollateralShare github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,15,opt,name=max_collateral_share,json=maxCollateralShare,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"max_collateral_share" yaml:"max_collateral_share"` + // Max Supply Utilization specifies the maximum supply utilization a token is + // allowed to reach as a direct result of user borrowing. It can still be exceeded + // due to withdrawals, interest, and liquidations. + MaxSupplyUtilization github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,16,opt,name=max_supply_utilization,json=maxSupplyUtilization,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"max_supply_utilization" yaml:"max_supply_utilization"` + // Min Collateral Liquidity specifies the minimum collateral liquidity a token is + // allowed to reach as a direct result of users borrowing, collateralizing, or + // withdrawing assets. Liquidity can only drop below this value due to interest + // or liquidations. + MinCollateralLiquidity github_com_cosmos_cosmos_sdk_types.Dec `protobuf:"bytes,17,opt,name=min_collateral_liquidity,json=minCollateralLiquidity,proto3,customtype=github.com/cosmos/cosmos-sdk/types.Dec" json:"min_collateral_liquidity" yaml:"min_collateral_liquidity"` } func (m *Token) Reset() { *m = Token{} } @@ -175,57 +184,60 @@ func init() { func init() { proto.RegisterFile("umee/leverage/v1/leverage.proto", fileDescriptor_8cb1bf9ea641ecc6) } var fileDescriptor_8cb1bf9ea641ecc6 = []byte{ - // 791 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x3f, 0x6f, 0x3b, 0x35, - 0x18, 0xce, 0x41, 0x7e, 0xa5, 0x71, 0x9b, 0xa4, 0xbd, 0xa6, 0xed, 0x09, 0xc2, 0x5d, 0x65, 0x09, - 0xd4, 0xa5, 0x39, 0x0a, 0x4c, 0x19, 0xd3, 0x0a, 0x5a, 0x44, 0x0b, 0xb8, 0x45, 0x95, 0x58, 0x4e, - 0xce, 0xc5, 0x24, 0xa7, 0xf8, 0xce, 0xc1, 0x76, 0xfe, 0x75, 0x61, 0x40, 0xec, 0x8c, 0x2c, 0x48, - 0xfd, 0x18, 0x7c, 0x84, 0x8e, 0x1d, 0x11, 0x43, 0x04, 0xed, 0xc2, 0x9c, 0x99, 0x01, 0x9d, 0x7d, - 0x49, 0x2e, 0x6d, 0xa8, 0x14, 0xb5, 0x53, 0xec, 0xe7, 0x7d, 0xf3, 0x3c, 0xaf, 0xfd, 0x3e, 0xf6, - 0x19, 0x38, 0xdd, 0x90, 0x10, 0x97, 0x92, 0x1e, 0xe1, 0xb8, 0x49, 0xdc, 0xde, 0xe1, 0x74, 0x5c, - 0xe9, 0x70, 0x26, 0x99, 0xb9, 0x11, 0x27, 0x54, 0xa6, 0x60, 0xef, 0xf0, 0xdd, 0x52, 0x93, 0x35, - 0x99, 0x0a, 0xba, 0xf1, 0x48, 0xe7, 0xc1, 0xdf, 0xb3, 0x60, 0xe5, 0x6b, 0xcc, 0x71, 0x28, 0xcc, - 0xdf, 0x0c, 0x60, 0xfb, 0x2c, 0xec, 0x50, 0x22, 0x89, 0x47, 0x83, 0x1f, 0xba, 0x41, 0x03, 0xcb, - 0x80, 0x45, 0x9e, 0x6c, 0x71, 0x22, 0x5a, 0x8c, 0x36, 0xac, 0xb7, 0xf6, 0x8c, 0xfd, 0x5c, 0xed, - 0xea, 0x76, 0xe4, 0x64, 0xfe, 0x1c, 0x39, 0x1f, 0x36, 0x03, 0xd9, 0xea, 0xd6, 0x2b, 0x3e, 0x0b, - 0x5d, 0x9f, 0x89, 0x90, 0x89, 0xe4, 0xe7, 0x40, 0x34, 0xda, 0xae, 0x1c, 0x76, 0x88, 0xa8, 0x1c, - 0x13, 0x7f, 0x3c, 0x72, 0x3e, 0x18, 0xe2, 0x90, 0x56, 0xe1, 0xf3, 0xec, 0x10, 0x95, 0x27, 0x09, - 0x5f, 0xce, 0xe2, 0x97, 0x93, 0xb0, 0xf9, 0x23, 0x28, 0x85, 0x41, 0x14, 0x84, 0xdd, 0xd0, 0xf3, - 0x29, 0x13, 0xc4, 0xfb, 0x1e, 0xfb, 0x92, 0x71, 0xeb, 0x6d, 0x55, 0xd4, 0xd9, 0xd2, 0x45, 0xbd, - 0xa7, 0x8b, 0x5a, 0xc4, 0x09, 0x91, 0x99, 0xc0, 0x47, 0x31, 0xfa, 0x99, 0x02, 0xe3, 0x02, 0x18, - 0xc7, 0x3e, 0x25, 0x1e, 0x27, 0x7d, 0xcc, 0x1b, 0x93, 0x02, 0xb2, 0x2f, 0x2b, 0x60, 0x11, 0x27, - 0x44, 0xa6, 0x86, 0x91, 0x42, 0x93, 0x02, 0x7e, 0x36, 0xc0, 0x8e, 0x08, 0x31, 0xa5, 0x73, 0x1b, - 0x28, 0x82, 0x6b, 0x62, 0xbd, 0x51, 0x35, 0x7c, 0xb5, 0x74, 0x0d, 0xef, 0xeb, 0x1a, 0x16, 0xb3, - 0x42, 0x54, 0x52, 0x81, 0x54, 0x3b, 0x2e, 0x82, 0x6b, 0x52, 0xcd, 0xfe, 0x7a, 0xe3, 0x64, 0xe0, - 0xbf, 0x00, 0xbc, 0xb9, 0x64, 0x6d, 0x12, 0x99, 0x9f, 0x02, 0x50, 0xc7, 0x82, 0x78, 0x0d, 0x12, - 0xb1, 0xd0, 0x32, 0x54, 0x29, 0xdb, 0xe3, 0x91, 0xb3, 0xa9, 0xc9, 0x67, 0x31, 0x88, 0x72, 0xf1, - 0xe4, 0x38, 0x1e, 0x9b, 0x11, 0x28, 0x70, 0x22, 0x08, 0xef, 0x4d, 0x3b, 0xa9, 0xed, 0xf5, 0xf9, - 0xd2, 0x8b, 0xd8, 0xd6, 0x3a, 0xf3, 0x6c, 0x10, 0xe5, 0x13, 0x20, 0xd9, 0xbd, 0x3e, 0xd8, 0xf4, - 0x19, 0xa5, 0x58, 0x12, 0x8e, 0xa9, 0xd7, 0x27, 0x41, 0xb3, 0x25, 0x13, 0xf3, 0x7c, 0xb1, 0xb4, - 0xa4, 0x35, 0x71, 0xf4, 0x23, 0x42, 0x88, 0x36, 0x66, 0xd8, 0x95, 0x82, 0xcc, 0x9f, 0x0c, 0xb0, - 0xbd, 0xf8, 0x3c, 0x69, 0xe7, 0x9c, 0x2f, 0xad, 0x5e, 0xd6, 0xea, 0xff, 0x73, 0x8c, 0x4a, 0x74, - 0xd1, 0xf1, 0x11, 0x60, 0x43, 0x35, 0xa2, 0xce, 0x38, 0x67, 0x7d, 0x8f, 0x63, 0x39, 0x71, 0xcd, - 0xe9, 0xd2, 0xfa, 0xbb, 0xa9, 0xc6, 0xa6, 0xf8, 0x20, 0x2a, 0xc4, 0x50, 0x4d, 0x21, 0x08, 0x4b, - 0x12, 0x8b, 0xb6, 0x83, 0xa8, 0x3d, 0x27, 0xba, 0xf2, 0x32, 0xd1, 0xc7, 0x7c, 0x10, 0x15, 0x62, - 0x28, 0x25, 0xda, 0x01, 0xc5, 0x10, 0x0f, 0xe6, 0x34, 0xdf, 0x51, 0x9a, 0x27, 0x4b, 0x6b, 0xee, - 0x24, 0x77, 0xc4, 0x3c, 0x1d, 0x44, 0xf9, 0x10, 0x0f, 0x52, 0x8a, 0x32, 0x59, 0x66, 0x57, 0x06, - 0x34, 0xb8, 0x56, 0x1b, 0x6f, 0xad, 0xbe, 0xc2, 0x32, 0x53, 0x7c, 0x10, 0x15, 0x63, 0xe8, 0xdb, - 0x19, 0xf2, 0xc4, 0x57, 0x41, 0xe4, 0x93, 0x48, 0x06, 0x3d, 0x62, 0xe5, 0x5e, 0xcf, 0x57, 0x53, - 0xd2, 0x79, 0x5f, 0x9d, 0x4e, 0x60, 0xb3, 0x0a, 0xd6, 0xc5, 0x30, 0xac, 0x33, 0x9a, 0x1c, 0x7f, - 0xa0, 0xb4, 0x77, 0xc7, 0x23, 0x67, 0x2b, 0xb9, 0x5b, 0x52, 0x51, 0x88, 0xd6, 0xf4, 0x54, 0x5f, - 0x01, 0x2e, 0x58, 0x25, 0x83, 0x0e, 0x8b, 0x48, 0x24, 0xad, 0xb5, 0x3d, 0x63, 0x3f, 0x5f, 0xdb, - 0x1a, 0x8f, 0x9c, 0xa2, 0xfe, 0xdf, 0x24, 0x02, 0xd1, 0x34, 0xc9, 0x3c, 0x01, 0x9b, 0x24, 0xc2, - 0x75, 0x4a, 0xbc, 0x50, 0x34, 0x3d, 0xd1, 0xed, 0x74, 0xe8, 0xd0, 0x5a, 0xdf, 0x33, 0xf6, 0x57, - 0x6b, 0xe5, 0xd9, 0xa9, 0x7c, 0x92, 0x02, 0x51, 0x51, 0x63, 0x67, 0xa2, 0x79, 0xa1, 0x90, 0x47, - 0x4c, 0xba, 0xb9, 0x56, 0xfe, 0x19, 0x26, 0x9d, 0x92, 0x66, 0xd2, 0x06, 0x30, 0xcb, 0x20, 0x57, - 0xa7, 0xd8, 0x6f, 0xd3, 0x40, 0x48, 0xab, 0x10, 0x33, 0xa0, 0x19, 0x60, 0x7e, 0x03, 0x4a, 0xb1, - 0x7b, 0x52, 0x17, 0x85, 0x68, 0x61, 0x4e, 0xac, 0xa2, 0x5a, 0xae, 0x93, 0xfa, 0x0e, 0x2d, 0xc8, - 0x8a, 0xbf, 0x43, 0x78, 0x70, 0x34, 0x45, 0x2f, 0x62, 0xb0, 0x9a, 0xfd, 0xe7, 0xc6, 0x31, 0x6a, - 0xe7, 0xb7, 0x7f, 0xdb, 0x99, 0xdb, 0x7b, 0xdb, 0xb8, 0xbb, 0xb7, 0x8d, 0xbf, 0xee, 0x6d, 0xe3, - 0x97, 0x07, 0x3b, 0x73, 0xf7, 0x60, 0x67, 0xfe, 0x78, 0xb0, 0x33, 0xdf, 0x7d, 0x94, 0xea, 0x79, - 0xfc, 0x14, 0x38, 0x88, 0x88, 0xec, 0x33, 0xde, 0x56, 0x13, 0xb7, 0xf7, 0xb1, 0x3b, 0x98, 0xbd, - 0x1e, 0x94, 0x03, 0xea, 0x2b, 0xea, 0x41, 0xf0, 0xc9, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x48, - 0x27, 0xd0, 0x61, 0x5b, 0x08, 0x00, 0x00, + // 845 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xac, 0x56, 0x4f, 0x6f, 0x1c, 0x35, + 0x1c, 0xdd, 0x81, 0x34, 0x24, 0x6e, 0x37, 0x9b, 0x4c, 0x37, 0xe9, 0x08, 0xc2, 0x4e, 0x64, 0x09, + 0x94, 0x4b, 0x33, 0x14, 0x38, 0xe5, 0xb8, 0xad, 0xa0, 0x45, 0x6d, 0x01, 0xa7, 0xa8, 0x12, 0x97, + 0x91, 0x77, 0xd6, 0xec, 0x5a, 0x6b, 0x8f, 0x17, 0xdb, 0xfb, 0x2f, 0x17, 0x0e, 0x88, 0x13, 0x17, + 0x8e, 0x5c, 0x90, 0xfa, 0x31, 0x38, 0x72, 0xcc, 0xb1, 0x47, 0xc4, 0x61, 0x05, 0xc9, 0x85, 0x73, + 0x3e, 0x01, 0x1a, 0x7b, 0xfe, 0x6d, 0x3a, 0x54, 0x1a, 0xa5, 0xa7, 0x9d, 0x79, 0x3f, 0xef, 0x7b, + 0x6f, 0xec, 0x67, 0xff, 0x0c, 0xfc, 0x09, 0x27, 0x24, 0x60, 0x64, 0x4a, 0x24, 0x1e, 0x90, 0x60, + 0x7a, 0x2f, 0x7f, 0x3e, 0x1a, 0x4b, 0xa1, 0x85, 0xbb, 0x9d, 0x0c, 0x38, 0xca, 0xc1, 0xe9, 0xbd, + 0x77, 0xdb, 0x03, 0x31, 0x10, 0xa6, 0x18, 0x24, 0x4f, 0x76, 0x1c, 0xfc, 0x7d, 0x0d, 0xac, 0x7f, + 0x85, 0x25, 0xe6, 0xca, 0xfd, 0xcd, 0x01, 0x9d, 0x48, 0xf0, 0x31, 0x23, 0x9a, 0x84, 0x8c, 0x7e, + 0x3f, 0xa1, 0x7d, 0xac, 0xa9, 0x88, 0x43, 0x3d, 0x94, 0x44, 0x0d, 0x05, 0xeb, 0x7b, 0x6f, 0x1d, + 0x38, 0x87, 0x9b, 0xdd, 0xe7, 0x67, 0x4b, 0xbf, 0xf1, 0xd7, 0xd2, 0xff, 0x70, 0x40, 0xf5, 0x70, + 0xd2, 0x3b, 0x8a, 0x04, 0x0f, 0x22, 0xa1, 0xb8, 0x50, 0xe9, 0xcf, 0x5d, 0xd5, 0x1f, 0x05, 0x7a, + 0x31, 0x26, 0xea, 0xe8, 0x01, 0x89, 0x2e, 0x97, 0xfe, 0x07, 0x0b, 0xcc, 0xd9, 0x31, 0x7c, 0x3d, + 0x3b, 0x44, 0xfb, 0xd9, 0x80, 0xc7, 0x45, 0xfd, 0x59, 0x56, 0x76, 0x7f, 0x00, 0x6d, 0x4e, 0x63, + 0xca, 0x27, 0x3c, 0x8c, 0x98, 0x50, 0x24, 0xfc, 0x0e, 0x47, 0x5a, 0x48, 0xef, 0x6d, 0x63, 0xea, + 0x49, 0x6d, 0x53, 0xef, 0x59, 0x53, 0x55, 0x9c, 0x10, 0xb9, 0x29, 0x7c, 0x3f, 0x41, 0x3f, 0x33, + 0x60, 0x62, 0x40, 0x48, 0x1c, 0x31, 0x12, 0x4a, 0x32, 0xc3, 0xb2, 0x9f, 0x19, 0x58, 0xbb, 0x9e, + 0x81, 0x2a, 0x4e, 0x88, 0x5c, 0x0b, 0x23, 0x83, 0xa6, 0x06, 0x7e, 0x72, 0xc0, 0x9e, 0xe2, 0x98, + 0xb1, 0x95, 0x09, 0x54, 0xf4, 0x94, 0x78, 0x37, 0x8c, 0x87, 0x2f, 0x6b, 0x7b, 0x78, 0xdf, 0x7a, + 0xa8, 0x66, 0x85, 0xa8, 0x6d, 0x0a, 0xa5, 0xe5, 0x38, 0xa1, 0xa7, 0xe4, 0x78, 0xed, 0xd7, 0x17, + 0x7e, 0x03, 0xfe, 0xd1, 0x04, 0x37, 0x9e, 0x89, 0x11, 0x89, 0xdd, 0x4f, 0x01, 0xe8, 0x61, 0x45, + 0xc2, 0x3e, 0x89, 0x05, 0xf7, 0x1c, 0x63, 0x65, 0xf7, 0x72, 0xe9, 0xef, 0x58, 0xf2, 0xa2, 0x06, + 0xd1, 0x66, 0xf2, 0xf2, 0x20, 0x79, 0x76, 0x63, 0xb0, 0x25, 0x89, 0x22, 0x72, 0x9a, 0xaf, 0xa4, + 0x8d, 0xd7, 0xe7, 0xb5, 0x3f, 0x62, 0xd7, 0xea, 0xac, 0xb2, 0x41, 0xd4, 0x4c, 0x81, 0x74, 0xf6, + 0x66, 0x60, 0x27, 0x12, 0x8c, 0x61, 0x4d, 0x24, 0x66, 0xe1, 0x8c, 0xd0, 0xc1, 0x50, 0xa7, 0xe1, + 0xf9, 0xa2, 0xb6, 0xa4, 0x97, 0x25, 0xfa, 0x0a, 0x21, 0x44, 0xdb, 0x05, 0xf6, 0xdc, 0x40, 0xee, + 0x8f, 0x0e, 0xd8, 0xad, 0xde, 0x4f, 0x36, 0x39, 0x4f, 0x6b, 0xab, 0xef, 0x5b, 0xf5, 0xff, 0xd9, + 0x46, 0x6d, 0x56, 0xb5, 0x7d, 0x14, 0xd8, 0x36, 0x0b, 0xd1, 0x13, 0x52, 0x8a, 0x59, 0x28, 0xb1, + 0xce, 0x52, 0xf3, 0xa8, 0xb6, 0xfe, 0x9d, 0xd2, 0xc2, 0x96, 0xf8, 0x20, 0xda, 0x4a, 0xa0, 0xae, + 0x41, 0x10, 0xd6, 0x24, 0x11, 0x1d, 0xd1, 0x78, 0xb4, 0x22, 0xba, 0x7e, 0x3d, 0xd1, 0xab, 0x7c, + 0x10, 0x6d, 0x25, 0x50, 0x49, 0x74, 0x0c, 0x5a, 0x1c, 0xcf, 0x57, 0x34, 0xdf, 0x31, 0x9a, 0x0f, + 0x6b, 0x6b, 0xee, 0xa5, 0x67, 0xc4, 0x2a, 0x1d, 0x44, 0x4d, 0x8e, 0xe7, 0x25, 0x45, 0x9d, 0x7e, + 0xe6, 0x44, 0x53, 0x46, 0x4f, 0xcd, 0xc4, 0x7b, 0x1b, 0x6f, 0xe0, 0x33, 0x4b, 0x7c, 0x10, 0xb5, + 0x12, 0xe8, 0x9b, 0x02, 0x79, 0x25, 0x57, 0x34, 0x8e, 0x48, 0xac, 0xe9, 0x94, 0x78, 0x9b, 0x6f, + 0x2e, 0x57, 0x39, 0xe9, 0x6a, 0xae, 0x1e, 0x65, 0xb0, 0x7b, 0x0c, 0x6e, 0xa9, 0x05, 0xef, 0x09, + 0x96, 0x6e, 0x7f, 0x60, 0xb4, 0xef, 0x5c, 0x2e, 0xfd, 0xdb, 0xe9, 0xd9, 0x52, 0xaa, 0x42, 0x74, + 0xd3, 0xbe, 0xda, 0x23, 0x20, 0x00, 0x1b, 0x64, 0x3e, 0x16, 0x31, 0x89, 0xb5, 0x77, 0xf3, 0xc0, + 0x39, 0x6c, 0x76, 0x6f, 0x5f, 0x2e, 0xfd, 0x96, 0xfd, 0x5f, 0x56, 0x81, 0x28, 0x1f, 0xe4, 0x3e, + 0x04, 0x3b, 0x24, 0xc6, 0x3d, 0x46, 0x42, 0xae, 0x06, 0xa1, 0x9a, 0x8c, 0xc7, 0x6c, 0xe1, 0xdd, + 0x3a, 0x70, 0x0e, 0x37, 0xba, 0xfb, 0xc5, 0xae, 0x7c, 0x65, 0x08, 0x44, 0x2d, 0x8b, 0x3d, 0x51, + 0x83, 0x13, 0x83, 0x5c, 0x61, 0xb2, 0x8b, 0xeb, 0x35, 0x5f, 0xc3, 0x64, 0x87, 0x94, 0x99, 0x6c, + 0x00, 0xdc, 0x7d, 0xb0, 0xd9, 0x63, 0x38, 0x1a, 0x31, 0xaa, 0xb4, 0xb7, 0x95, 0x30, 0xa0, 0x02, + 0x30, 0x5d, 0x0b, 0xcf, 0xc3, 0xd2, 0x41, 0xa1, 0x86, 0x58, 0x12, 0xaf, 0x75, 0xcd, 0xae, 0x55, + 0xc1, 0x99, 0x74, 0x2d, 0x3c, 0xbf, 0x9f, 0xa3, 0x27, 0x09, 0x68, 0x9a, 0x46, 0x29, 0xbf, 0xe5, + 0x88, 0x6e, 0x5f, 0xaf, 0x69, 0x54, 0xb3, 0x42, 0xd4, 0xce, 0x37, 0x47, 0x39, 0xad, 0x3f, 0x3b, + 0xc0, 0xe3, 0x34, 0x2e, 0xbb, 0xb6, 0x79, 0xa2, 0x7a, 0xe1, 0xed, 0x18, 0x27, 0x5f, 0xd7, 0x76, + 0xe2, 0xe7, 0x3d, 0xbc, 0x92, 0x17, 0xa2, 0x3d, 0x4e, 0xe3, 0x62, 0x46, 0x1e, 0x67, 0x85, 0xe3, + 0xb5, 0x7f, 0x5f, 0xf8, 0x4e, 0xf7, 0xe9, 0xd9, 0x3f, 0x9d, 0xc6, 0xd9, 0x79, 0xc7, 0x79, 0x79, + 0xde, 0x71, 0xfe, 0x3e, 0xef, 0x38, 0xbf, 0x5c, 0x74, 0x1a, 0x2f, 0x2f, 0x3a, 0x8d, 0x3f, 0x2f, + 0x3a, 0x8d, 0x6f, 0x3f, 0x2a, 0xd9, 0x48, 0xae, 0x53, 0x77, 0x63, 0xa2, 0x67, 0x42, 0x8e, 0xcc, + 0x4b, 0x30, 0xfd, 0x38, 0x98, 0x17, 0x37, 0x30, 0x63, 0xaa, 0xb7, 0x6e, 0x2e, 0x55, 0x9f, 0xfc, + 0x17, 0x00, 0x00, 0xff, 0xff, 0x2f, 0x2a, 0x49, 0xb5, 0x9f, 0x09, 0x00, 0x00, } func (this *Token) Equal(that interface{}) bool { @@ -289,7 +301,13 @@ func (this *Token) Equal(that interface{}) bool { if this.Blacklist != that1.Blacklist { return false } - if this.MaxCollateralShare != that1.MaxCollateralShare { + if !this.MaxCollateralShare.Equal(that1.MaxCollateralShare) { + return false + } + if !this.MaxSupplyUtilization.Equal(that1.MaxSupplyUtilization) { + return false + } + if !this.MinCollateralLiquidity.Equal(that1.MinCollateralLiquidity) { return false } return true @@ -377,11 +395,40 @@ func (m *Token) MarshalToSizedBuffer(dAtA []byte) (int, error) { _ = i var l int _ = l - if m.MaxCollateralShare != 0 { - i = encodeVarintLeverage(dAtA, i, uint64(m.MaxCollateralShare)) - i-- - dAtA[i] = 0x78 + { + size := m.MinCollateralLiquidity.Size() + i -= size + if _, err := m.MinCollateralLiquidity.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintLeverage(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x8a + { + size := m.MaxSupplyUtilization.Size() + i -= size + if _, err := m.MaxSupplyUtilization.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintLeverage(dAtA, i, uint64(size)) + } + i-- + dAtA[i] = 0x1 + i-- + dAtA[i] = 0x82 + { + size := m.MaxCollateralShare.Size() + i -= size + if _, err := m.MaxCollateralShare.MarshalTo(dAtA[i:]); err != nil { + return 0, err + } + i = encodeVarintLeverage(dAtA, i, uint64(size)) } + i-- + dAtA[i] = 0x7a if m.Blacklist { i-- if m.Blacklist { @@ -584,9 +631,12 @@ func (m *Token) Size() (n int) { if m.Blacklist { n += 2 } - if m.MaxCollateralShare != 0 { - n += 1 + sovLeverage(uint64(m.MaxCollateralShare)) - } + l = m.MaxCollateralShare.Size() + n += 1 + l + sovLeverage(uint64(l)) + l = m.MaxSupplyUtilization.Size() + n += 2 + l + sovLeverage(uint64(l)) + l = m.MinCollateralLiquidity.Size() + n += 2 + l + sovLeverage(uint64(l)) return n } @@ -1227,10 +1277,10 @@ func (m *Token) Unmarshal(dAtA []byte) error { } m.Blacklist = bool(v != 0) case 15: - if wireType != 0 { + if wireType != 2 { return fmt.Errorf("proto: wrong wireType = %d for field MaxCollateralShare", wireType) } - m.MaxCollateralShare = 0 + var stringLen uint64 for shift := uint(0); ; shift += 7 { if shift >= 64 { return ErrIntOverflowLeverage @@ -1240,11 +1290,94 @@ func (m *Token) Unmarshal(dAtA []byte) error { } b := dAtA[iNdEx] iNdEx++ - m.MaxCollateralShare |= uint32(b&0x7F) << shift + stringLen |= uint64(b&0x7F) << shift if b < 0x80 { break } } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLeverage + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthLeverage + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MaxCollateralShare.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 16: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MaxSupplyUtilization", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeverage + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLeverage + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthLeverage + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MaxSupplyUtilization.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 17: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MinCollateralLiquidity", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowLeverage + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return ErrInvalidLengthLeverage + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return ErrInvalidLengthLeverage + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + if err := m.MinCollateralLiquidity.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipLeverage(dAtA[iNdEx:]) diff --git a/x/leverage/types/token.go b/x/leverage/types/token.go index 5f7d52f56a..90101042fc 100644 --- a/x/leverage/types/token.go +++ b/x/leverage/types/token.go @@ -84,8 +84,16 @@ func (t Token) Validate() error { } } - if t.MaxCollateralShare > 100 { - return sdkerrors.ErrInvalidRequest.Wrap("Token.MaxCollateralShare must be in [0; 100] range") + if t.MaxCollateralShare.IsNegative() || t.MaxCollateralShare.GT(sdk.OneDec()) { + return sdkerrors.ErrInvalidRequest.Wrap("Token.MaxCollateralShare must be between 0 and 1") + } + + if t.MaxSupplyUtilization.IsNegative() || t.MaxSupplyUtilization.GT(sdk.OneDec()) { + return sdkerrors.ErrInvalidRequest.Wrap("Token.MaxSupplyUtilization must be between 0 and 1") + } + + if t.MinCollateralLiquidity.IsNegative() { + return sdkerrors.ErrInvalidRequest.Wrap("Token.MinCollateralLiquidity must not be negative") } return nil diff --git a/x/leverage/types/token_test.go b/x/leverage/types/token_test.go index 1d6055c320..d0bb71aaaa 100644 --- a/x/leverage/types/token_test.go +++ b/x/leverage/types/token_test.go @@ -22,21 +22,23 @@ func TestUpdateRegistryProposal_String(t *testing.T) { Description: "test", Registry: []types.Token{ { - BaseDenom: "uumee", - SymbolDenom: "umee", - Exponent: 6, - ReserveFactor: sdk.NewDec(40), - CollateralWeight: sdk.NewDec(43), - LiquidationThreshold: sdk.NewDec(66), - BaseBorrowRate: sdk.NewDec(32), - KinkBorrowRate: sdk.NewDec(26), - MaxBorrowRate: sdk.NewDec(21), - KinkUtilization: sdk.MustNewDecFromStr("0.25"), - LiquidationIncentive: sdk.NewDec(88), - EnableMsgSupply: true, - EnableMsgBorrow: true, - Blacklist: false, - MaxCollateralShare: 10, + BaseDenom: "uumee", + SymbolDenom: "umee", + Exponent: 6, + ReserveFactor: sdk.NewDec(40), + CollateralWeight: sdk.NewDec(43), + LiquidationThreshold: sdk.NewDec(66), + BaseBorrowRate: sdk.NewDec(32), + KinkBorrowRate: sdk.NewDec(26), + MaxBorrowRate: sdk.NewDec(21), + KinkUtilization: sdk.MustNewDecFromStr("0.25"), + LiquidationIncentive: sdk.NewDec(88), + EnableMsgSupply: true, + EnableMsgBorrow: true, + Blacklist: false, + MaxCollateralShare: sdk.MustNewDecFromStr("0.1"), + MaxSupplyUtilization: sdk.MustNewDecFromStr("0.5"), + MinCollateralLiquidity: sdk.MustNewDecFromStr("0.75"), }, }, } @@ -57,7 +59,9 @@ registry: enable_msg_supply: true enable_msg_borrow: true blacklist: false - max_collateral_share: 10 + max_collateral_share: "0.100000000000000000" + max_supply_utilization: "0.500000000000000000" + min_collateral_liquidity: "0.750000000000000000" ` require.Equal(t, expected, p.String()) } @@ -65,21 +69,23 @@ registry: func TestToken_Validate(t *testing.T) { validToken := func() types.Token { return types.Token{ - BaseDenom: "uumee", - SymbolDenom: "umee", - Exponent: 6, - ReserveFactor: sdk.MustNewDecFromStr("0.25"), - CollateralWeight: sdk.MustNewDecFromStr("0.50"), - LiquidationThreshold: sdk.MustNewDecFromStr("0.50"), - BaseBorrowRate: sdk.MustNewDecFromStr("0.01"), - KinkBorrowRate: sdk.MustNewDecFromStr("0.05"), - MaxBorrowRate: sdk.MustNewDecFromStr("1.0"), - KinkUtilization: sdk.MustNewDecFromStr("0.75"), - LiquidationIncentive: sdk.MustNewDecFromStr("0.05"), - EnableMsgSupply: true, - EnableMsgBorrow: true, - Blacklist: false, - MaxCollateralShare: 0, + BaseDenom: "uumee", + SymbolDenom: "umee", + Exponent: 6, + ReserveFactor: sdk.MustNewDecFromStr("0.25"), + CollateralWeight: sdk.MustNewDecFromStr("0.50"), + LiquidationThreshold: sdk.MustNewDecFromStr("0.50"), + BaseBorrowRate: sdk.MustNewDecFromStr("0.01"), + KinkBorrowRate: sdk.MustNewDecFromStr("0.05"), + MaxBorrowRate: sdk.MustNewDecFromStr("1.0"), + KinkUtilization: sdk.MustNewDecFromStr("0.75"), + LiquidationIncentive: sdk.MustNewDecFromStr("0.05"), + EnableMsgSupply: true, + EnableMsgBorrow: true, + Blacklist: false, + MaxCollateralShare: sdk.MustNewDecFromStr("1.0"), + MaxSupplyUtilization: sdk.MustNewDecFromStr("0.9"), + MinCollateralLiquidity: sdk.MustNewDecFromStr("1.0"), } } invalidBaseToken := validToken() @@ -123,10 +129,13 @@ func TestToken_Validate(t *testing.T) { invalidBlacklistedSupply.Blacklist = true invalidMaxCollateralShare := validToken() - invalidMaxCollateralShare.MaxCollateralShare = 101 + invalidMaxCollateralShare.MaxCollateralShare = sdk.MustNewDecFromStr("1.05") - validMaxCollateralShare := validToken() - validMaxCollateralShare.MaxCollateralShare = 100 + invalidMaxSupplyUtilization := validToken() + invalidMaxSupplyUtilization.MaxSupplyUtilization = sdk.MustNewDecFromStr("1.05") + + invalidMinCollateralLiquidity := validToken() + invalidMinCollateralLiquidity.MinCollateralLiquidity = sdk.MustNewDecFromStr("-0.05") testCases := map[string]struct { input types.Token @@ -187,9 +196,13 @@ func TestToken_Validate(t *testing.T) { input: invalidMaxCollateralShare, expectErr: true, }, - "valid max collateral share": { - input: validMaxCollateralShare, - expectErr: false, + "invalid max supply utilization": { + input: invalidMaxSupplyUtilization, + expectErr: true, + }, + "invalid min collateral liquidity": { + input: invalidMinCollateralLiquidity, + expectErr: true, }, }