Skip to content

Commit

Permalink
Merge branch 'main' into sai/396
Browse files Browse the repository at this point in the history
  • Loading branch information
gsk967 authored Jun 28, 2023
2 parents 4abb3ed + 426d498 commit d00630a
Show file tree
Hide file tree
Showing 17 changed files with 298 additions and 66 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/

### Features

- [2114](https://github.com/umee-network/umee/pull/2114) Add borrow factor to `x/leverage`
- [2102](https://github.com/umee-network/umee/pull/2102) and [2106](https://github.com/umee-network/umee/pull/2106) Add `MsgLeveragedLiquidate` to `x/leverage`
- [2085](https://github.com/umee-network/umee/pull/2085) Add `inspect` query to leverage module, which msut be enabled on a node by running with `-l` liquidator query flag.
- [1952](https://github.com/umee-network/umee/pull/1952) Add `x/incentive` module.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ require (
github.com/tendermint/tm-db v0.6.7
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1
google.golang.org/grpc v1.56.1
google.golang.org/protobuf v1.30.0
google.golang.org/protobuf v1.31.0
gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.4.0
mvdan.cc/gofumpt v0.5.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -2254,8 +2254,8 @@ google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
Expand Down
16 changes: 16 additions & 0 deletions x/leverage/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ The leverage module depends directly on `x/oracle` for asset prices, and interac
- [uToken Exchange Rate](#utoken-exchange-rate)
- [Supply Utilization](#supply-utilization)
- [Borrow Limit](#borrow-limit)
- [Borrow Factor](#borrow-factor)
- [Liquidation Threshold](#liquidation-threshold)
- [Borrow APY](#borrow-apy)
- [Supplying APY](#supplying-apy)
Expand Down Expand Up @@ -168,6 +169,21 @@ A user's borrow limit is the sum of the contributions from each denomination of

For tokens with hith historic prices enabled (indicated by a `HistoricMedians` parameter greater than zero), each collateral `TokenValue` is computed with `PriceModeLow`, i.e. the lower of either spot price or historic price is used.

#### Borrow Factor

Each token in the `Token Registry` has a parameter called `CollateralWeight`, always less than 1, which determines the portion of the token's value that goes towards a user's borrow limit, when the token is used as collateral.

An implied parameter `BorrowFactor` is derived from `CollateralWeight` - specifically, it is the minimum of `2.0` and `1/CollateralWeight`.
The maximum borrow factor of `2.0` allows risky or non-collateral assets (`0 <= CollateralWeight < 0.5`) to be borrowed to a certain minimum degree.

When a user is borrowing, their borrow limit is whichever is more restrictive of the following two rules:

- Borrowed value must be less than collateral value times `CollateralWeight` (sum over each collateral asset)
- Borrowed value times `BorrowFactor` (sum over each borrowed asset) must be less than collateral value.

This means that when the original borrow limit based on collateral weight would allow a higher quality collateral to borrow a risky asset with a small margin of safety, the user's effective collateral weight is reduced to that of the riskier asset.
(Or `0.5` at the minimum.)

#### Historic Borrow Limit, Value

The leverage module also makes use of the oracle's historic prices to enforce an additional restriction on borrowing.
Expand Down
27 changes: 22 additions & 5 deletions x/leverage/keeper/borrows.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (
)

// assertBorrowerHealth returns an error if a borrower is currently above their borrow limit,
// under either recent (historic median) or current prices. It returns an error if
// under either recent (historic median) or current prices. Checks using borrow limit based
// on collateral weight, then check separately for borrow limit using borrow factor. Error if
// borrowed asset prices cannot be calculated, but will try to treat collateral whose prices are
// unavailable as having zero value. This can still result in a borrow limit being too low,
// unless the remaining collateral is enough to cover all borrows.
Expand All @@ -21,18 +22,34 @@ func (k Keeper) assertBorrowerHealth(ctx sdk.Context, borrowerAddr sdk.AccAddres
borrowed := k.GetBorrowerBorrows(ctx, borrowerAddr)
collateral := k.GetBorrowerCollateral(ctx, borrowerAddr)

value, err := k.TotalTokenValue(ctx, borrowed, types.PriceModeHigh)
// check health using collateral weight
borrowValue, err := k.TotalTokenValue(ctx, borrowed, types.PriceModeHigh)
if err != nil {
return err
}
limit, err := k.VisibleBorrowLimit(ctx, collateral)
borrowLimit, err := k.VisibleBorrowLimit(ctx, collateral)
if err != nil {
return err
}
if value.GT(limit.Mul(maxUsage)) {
if borrowValue.GT(borrowLimit.Mul(maxUsage)) {
return types.ErrUndercollaterized.Wrapf(
"borrowed: %s, limit: %s, max usage %s", value, limit, maxUsage)
"borrowed: %s, limit: %s, max usage %s", borrowValue, borrowLimit, maxUsage)
}

// check health using borrow factor
weightedBorrowValue, err := k.ValueWithBorrowFactor(ctx, borrowed, types.PriceModeHigh)
if err != nil {
return err
}
collateralValue, err := k.VisibleUTokensValue(ctx, collateral, types.PriceModeLow)
if err != nil {
return err
}
if weightedBorrowValue.GT(collateralValue.Mul(maxUsage)) {
return types.ErrUndercollaterized.Wrapf(
"weighted borrow: %s, collateral value: %s, max usage %s", weightedBorrowValue, collateralValue, maxUsage)
}

return nil
}

Expand Down
18 changes: 9 additions & 9 deletions x/leverage/keeper/collateral.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ func (k Keeper) GetTotalCollateral(ctx sdk.Context, denom string) sdk.Coin {
}

// CalculateCollateralValue uses the price oracle to determine the value (in USD) provided by
// collateral sdk.Coins, using each token's uToken exchange rate. Always uses spot price.
// collateral sdk.Coins, using each token's uToken exchange rate.
// An error is returned if any input coins are not uTokens or if value calculation fails.
func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins) (sdk.Dec, error) {
func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins, mode types.PriceMode) (sdk.Dec, error) {
total := sdk.ZeroDec()

for _, coin := range collateral {
Expand All @@ -86,23 +86,23 @@ func (k Keeper) CalculateCollateralValue(ctx sdk.Context, collateral sdk.Coins)
}

// get USD value of base assets
v, err := k.TokenValue(ctx, baseAsset, types.PriceModeSpot)
v, err := k.TokenValue(ctx, baseAsset, mode)
if err != nil {
return sdk.ZeroDec(), err
}

// add each collateral coin's weighted value to borrow limit
// add each collateral coin's value to borrow limit
total = total.Add(v)
}

return total, nil
}

// VisibleCollateralValue uses the price oracle to determine the value (in USD) provided by
// collateral sdk.Coins, using each token's uToken exchange rate. Always uses spot price.
// collateral sdk.Coins, using each token's uToken exchange rate.
// Unlike CalculateCollateralValue, this function will not return an error if value calculation
// fails on a token - instead, that token will contribute zero value to the total.
func (k Keeper) VisibleCollateralValue(ctx sdk.Context, collateral sdk.Coins) (sdk.Dec, error) {
func (k Keeper) VisibleCollateralValue(ctx sdk.Context, collateral sdk.Coins, mode types.PriceMode) (sdk.Dec, error) {
total := sdk.ZeroDec()

for _, coin := range collateral {
Expand All @@ -113,7 +113,7 @@ func (k Keeper) VisibleCollateralValue(ctx sdk.Context, collateral sdk.Coins) (s
}

// get USD value of base assets
v, err := k.TokenValue(ctx, baseAsset, types.PriceModeSpot)
v, err := k.TokenValue(ctx, baseAsset, mode)
if err == nil {
// for coins that did not error, add their value to the total
total = total.Add(v)
Expand Down Expand Up @@ -169,13 +169,13 @@ func (k *Keeper) VisibleCollateralShare(ctx sdk.Context, denom string) (sdk.Dec,
thisCollateral := sdk.NewCoins(sdk.NewCoin(denom, systemCollateral.AmountOf(denom)))

// get USD collateral value for all uTokens combined, except those experiencing price outages
totalValue, err := k.VisibleCollateralValue(ctx, systemCollateral)
totalValue, err := k.VisibleCollateralValue(ctx, systemCollateral, types.PriceModeSpot)
if err != nil {
return sdk.ZeroDec(), err
}

// get USD collateral value for this uToken only
thisValue, err := k.CalculateCollateralValue(ctx, thisCollateral)
thisValue, err := k.CalculateCollateralValue(ctx, thisCollateral, types.PriceModeSpot)
if err != nil {
return sdk.ZeroDec(), err
}
Expand Down
2 changes: 1 addition & 1 deletion x/leverage/keeper/grpc_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ func (q Querier) AccountSummary(
}

// collateral value always uses spot prices, and this line skips assets that are missing prices
collateralValue, err := q.Keeper.VisibleCollateralValue(ctx, collateral)
collateralValue, err := q.Keeper.VisibleCollateralValue(ctx, collateral, types.PriceModeSpot)
if err != nil {
return nil, err
}
Expand Down
2 changes: 1 addition & 1 deletion x/leverage/keeper/grpc_query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func (s *IntegrationTestSuite) TestQuerier_RegisteredTokens() {
"valid: get the all registered tokens",
"",
types.QueryRegisteredTokens{},
5,
6,
},
{
"valid: get the registered token info by base_denom",
Expand Down
2 changes: 1 addition & 1 deletion x/leverage/keeper/inspector.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func (q Querier) Inspect(
borrowed := k.GetBorrowerBorrows(ctx, addr)
borrowedValue, _ := k.TotalTokenValue(ctx, borrowed, types.PriceModeSpot)
collateral := k.GetBorrowerCollateral(ctx, addr)
collateralValue, _ := k.CalculateCollateralValue(ctx, collateral)
collateralValue, _ := k.CalculateCollateralValue(ctx, collateral, types.PriceModeSpot)
liquidationThreshold, _ := k.CalculateLiquidationThreshold(ctx, collateral)

account := types.InspectAccount{
Expand Down
12 changes: 8 additions & 4 deletions x/leverage/keeper/inspector_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ func TestNeat(t *testing.T) {
"-12.555": -12.55, // truncates default to cent
"-0.00123456789": -0.001234, // truncates <0.01 to millionth
"-0.000000987654321": -0.000000987654321, // <0.000001 gets maximum precision
// edge case: >2^64 displays incorrectly
// this should be fine, since this is a display-only function (not used in transactions)
// which is used on dollar (not token) amounts
"123456789123456789123456789.123456789": -9.223372036854776e+21,
}

for s, f := range cases {
assert.Equal(f, neat(sdk.MustNewDecFromStr(s)))
}

// edge case: >2^64 displays incorrectly
// this should be fine, since this is a display-only function (not used in transactions)
// which is used on dollar (not token) amounts
assert.NotEqual(
123456789123456789123456789.123456789,
neat(sdk.MustNewDecFromStr("123456789123456789123456789.123456789")),
)
}
Loading

0 comments on commit d00630a

Please sign in to comment.