Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore!: update ADR-05 and leverage keeper cosmetic updates #903

Merged
merged 28 commits into from
May 18, 2022
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
1a7e6d8
liquidation review
robert-zaremba May 13, 2022
a98588c
rename CalculateLiquidationLimit -> CalculateMaxBorrow\n\nLiquidation…
robert-zaremba May 13, 2022
0660d7c
few more updates
robert-zaremba May 13, 2022
5e5bf1e
changelog update
robert-zaremba May 13, 2022
72959e1
more adr updates
robert-zaremba May 13, 2022
b87ce08
Merge branch 'main' into robert/update-adr-05
toteki May 14, 2022
cf3c27e
Merge branch 'main' into robert/update-adr-05
toteki May 14, 2022
d78079e
rename ErrBorrowLimitLow to ErrUndercollaterized
robert-zaremba May 16, 2022
dc2856c
rename CalculateMaxBorrow to CalculateLiquidationThreshold
robert-zaremba May 16, 2022
89bd956
commit suggestion
toteki May 16, 2022
1a7623a
update comment
toteki May 16, 2022
829ea88
fix simtest
robert-zaremba May 16, 2022
dd8b119
fix tests
robert-zaremba May 17, 2022
7a09ef3
comment++
toteki May 17, 2022
91b3288
comment++
toteki May 17, 2022
ab4efb6
clarify withdraw undercollateralized error
toteki May 17, 2022
53364b4
clarify borrow undercollateralized error
toteki May 17, 2022
8b9d2ac
update operations test expected error message
toteki May 17, 2022
1966115
rename LiquidationLimit to LiquidationThreshold in a few more placed …
toteki May 17, 2022
62e8a3b
Merge branch 'main' into robert/update-adr-05
toteki May 17, 2022
3bd4716
update proto: rename LiquidationLimit -> LiquidationThreshold
robert-zaremba May 18, 2022
95fe7c5
regenerate proto files
robert-zaremba May 18, 2022
49c4832
rename types in go files
robert-zaremba May 18, 2022
f30caba
final renames
robert-zaremba May 18, 2022
8dd7ab3
Merge branch 'main' into robert/update-adr-05
toteki May 18, 2022
797c4d9
go mod tidy
toteki May 18, 2022
9b90869
misc related ADR changes
toteki May 18, 2022
a4625b8
whitespace
toteki May 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
### API Breaking

- [870](https://github.com/umee-network/umee/pull/870) Move proto v1beta1 to v1.
- [903](https://github.com/umee-network/umee/pull/903) (leverage) Renamed `Keeper.CalculateLiquidationLimit` to `CalculateLiquidationThreshold`.

## [v2.0.1](https://github.com/umee-network/umee/releases/tag/v2.0.1) - 2022-04-25

Expand Down
64 changes: 34 additions & 30 deletions docs/architecture/ADR-005-liquidation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
## Changelog

- November 19, 2021: Initial Draft (@toteki)
- May 2022: documentation updates (@robert-zaremba)

## Status

Proposed
Accepted

## Context

When borrowers on Umee exceed their borrow limit due to interest accrual or asset price fluctuations, their positions become eligible for liquidation.

Third party liquidators pay off part of all of the borrower's loan, in exchange for a value of collateral equal to the amount paid off plus an additional percentage (the liquidation incentive).
Third party liquidators pay off part of all of the borrower's loan, in exchange for a value of collateral equal to the amount paid off plus an additional bonus (the liquidation incentive).

We must build the features necessary for liquidators to continuously look out for liquidation opportunities, then carry out chosen liquidations.

Expand All @@ -22,15 +23,17 @@ Additional parameters will be required which define the liquidation incentive an

Liquidation will require one message type `MsgLiquidate`, one per-token parameter `LiquidationIncentive`, and two global parameters `MinimumCloseFactor` and `CompleteLiquidationThreshold`.

There is no event type for when a borrower becomes a valid liquidation target, nor a list of valid targets stored in the module. Liquidators will have to use an off-chain tool to query their nodes periodically.
The blockchain doesn't issue any event to signal that a borrow position can be liquidated, nor provide a list of valid targets. Liquidators will have to use an off-chain tools to query their nodes periodically.

## Detailed Design

A function `IsLiquidationEligible(borrowerAddr)` can be created to determine if a borrower is currently exceeding their borrow limit. Any liquidation attempt against a borrower not over their limit will fail.
We don't provide a function that checks if a given borrower can be liquidated to avoid spamming an app with periodical queries. Any liquidation attempt against a borrower not eligible for liquidation will fail.

A borrower's total borrowed value (expressed in USD) can be computed from their total borrowed tokens and the `x/oracle` price oracle module.
A borrow position is represented by a pair `(borrower_address, coin)`, where borrower address is an entity requesting a loan. A borrower's total borrowed value (expressed in USD) can be computed from their total borrowed tokens and the `x/oracle` price oracle module.

Their borrow limit is calculated similarly using the borrower's uToken balance, their collateral settings, current uToken exchange rates, and token collateral weights.
Their borrow limit is calculated similarly using the borrower's uToken balance, their collateral settings, current uToken exchange rates, and token collateral weights. Liquidation happens when a sum of borrower loans is bigger than the `CalculateLiquidationThreshold(borrower_collateral)`.

During liquidation any of the borrower's collateral tokens can be liquidated to pay off any of their loans.

### Message Types

Expand All @@ -42,8 +45,8 @@ To implement the liquidation functionality of the Asset Facility, one message ty
type MsgLiquidate struct {
Liquidator sdk.AccAddress
Borrower sdk.AccAddress
Repayment sdk.Coin // denom + amount
RewardDenom string
Repayment sdk.Coin // borrow denom + amount
RewardDenom string // collateral denom
}
```

Expand All @@ -52,7 +55,7 @@ Its amount is the maximum amount of asset the liquidator is willing to repay. Th

RewardDenom is the collateral type which the liquidator will receive in exchange for repaying the borrower's loan. It is always a uToken denomination.

It is necessary that messages be signed by the liquidator's account. Thus the method `GetSigners` should return the `Liquidator` address for the message type above.
`Liquidator` is the signer of the message and the account which will do repayment and receive reward.

### Partial Liquidation

Expand All @@ -68,29 +71,26 @@ In the above scenarios, the `MsgLiquidate` should succeed with the maximum amoun

### Token Parameters

In order to incentivize liquidators to target certain collateral types for liquidation first, the token parameter `LiquidationIncentive` is used.
In order to incentivize liquidators to target certain collateral types for liquidation first, we introduce a `LiquidationIncentive` parameter - defined for each supported borrowed denom.

When a `MsgLiquidate` causes liquidation to occur, the liquidator receives collateral equal to (100% + `RewardDenom.LiquidationIncentive`) of the repaid value worth of collateral.

For example, if the liquidation incentive for `uatom` is `0.15`, then the liquidator receives `u/uatom` collateral worth 115% of the borrowed base assets they repaid. The denom of the base assets does not affect this calculation.
For example, if the liquidation incentive for `atom` is `0.15`, then the liquidator receives `u/atom` collateral worth 115% of the borrowed base assets they repaid. The denom of the base assets does not affect this calculation.

### Calculating Liquidation Amounts

When a `MsgLiquidate` is received, the `x/leverage` module must determine if the targeted borrow address is eligible for liquidation.
When a `MsgLiquidate` is received, the `x/leverage` module must determine if the targeted borrow position is eligible for liquidation.

```go
// from MsgLiquidate (liquidatorAddr, borrowerAddr, repayDenom, repayAmount, rewardDenom)

borrowed := GetTotalBorrows(borrowerAddr)
// sum over the value of every sdk.Coin in the total borrowed sdk.Coins
borrowValue := TotalValue(borrowed) // price oracle

collateral := GetCollateralBalance(borrowerAddr)
collateral = MultiplyByCollateralWeight(collateral)
// sum over the value of every sdk.Coin in the total collateral sdk.Coins
collateralValue := TotalValue(collateral) // price oracle
maxBorrowValue := CalculateLiquidationThreshold(collateral)

if borrowValue > collateralValue {
if borrowValue > maxBorrowValue {
// borrower is over borrow limit, and therefore eligible for liquidation
}
```
Expand All @@ -103,10 +103,10 @@ After eligibility is confirmed, parameters governing liquidation can be fetched:
liquidationIncentive, closeFactor := GetLiquidationParameters(rewardDenom, borrowValue, collateralValue)
```

The liquidation incentive is the bonus collateral received when a liquidator repays a borrowed position
(e.g. incentive=`0.2` means liquidator receives 120% the value of their repayment back in collateral).
The liquidation incentive is a collateral bonus received when a liquidator repays a borrowed position
(e.g. incentive=`0.2` means liquidator receives 20% extra of the liquidated collateral).

The close factor is the portion of a borrow position eligible for liquidation in this single liquidation event.
The close factor is the maximum portion of a borrow position eligible for liquidation in a single liquidation event.

See _Dynamic Liquidation Parameters_ section at the bottom of this document.

Expand Down Expand Up @@ -149,24 +149,25 @@ Then the borrow can be repaid and the collateral rewarded using the liquidator's
> the existing liquidation designs well incentivize liquidators but sell excessive amounts of discounted collateral at the borrowers’ expenses.

Examining one existing liquidation scheme ([Compound](https://zengo.com/understanding-compounds-liquidation/)), two main parameters define maximum borrower losses due to liquidation:

- Liquidation Incentive (10%)
- Close Factor (50%)
When a borrower is even 0.0001% over their borrow limit, they stand to lose value equal to 5% of their borrowed value in a single liquidation event.
That is, the liquidator pays off 50% of their borrow and receives collateral worth 55% of its value.
When a borrower is even 0.0001% over their borrow limit, they stand to lose value equal to 5% of their borrowed value in a single liquidation event.
That is, the liquidator liquidates 50% of the borrowed value and receives 5% extra in collateral.

It should be possible to improve upon this aspect of the system by scaling one of the two parameters shown above, based on how far a borrower is over their borrow limit.

> Dynamic close factor
>
> Close factor ranges from `0.0 = MinimumCloseFactor` to 1.0 when the borrower is between 0% and `20% = CompleteLiquidationThreshold` over borrow limit, then stays at 1.0
>
> | Borrow Limit (BL) |Borrowed Value (BV) | BV / BL | Close Factor |
> | - | - | - | - |
> | 100 | 100.1 | 1.001 | 0.005 |
> | 100 | 102 | 1.02 | 0.1 |
> | 100 | 110 | 1.1 | 0.5 |
> | 100 | 130 | 1.2 | 1.0 |
> | 100 | 140 | 1.4 | 1.0 |
> | Borrow Limit (BL) | Borrowed Value (BV) | BV / BL | Close Factor |
> | ----------------- | ------------------- | ------- | ------------ |
> | 100 | 100.1 | 1.001 | 0.005 |
> | 100 | 102 | 1.02 | 0.1 |
> | 100 | 110 | 1.1 | 0.5 |
> | 100 | 130 | 1.2 | 1.0 |
> | 100 | 140 | 1.4 | 1.0 |

The Dynamic Close Factor takes advantage of market forces to reduce excessive collateral selloffs, by reducing the portion of collateral initially eligible for liquidation.
Liquidators would have the chance to liquidate smaller portions of the borrow if profitable and bring the position back into health.
Expand All @@ -189,17 +190,20 @@ In addition to the above, the liquidation tool should be able to read any global
## Consequences

### Positive

- Dynamic close factors reduce excessive risk to collateral

### Negative

- Offchain tool required to effectively scan for liquidation opportunities

### Neutral

- New message type `MsgLiquidate` is created
- New per-token parameter `LiquidationIncentive` will be created to determine liquidation incentives
- New global parameters `MinimumCloseFactor` and `CompleteLiquidationThreshold` will be created for close factors

## References

- [An Empirical Study of DeFi Liquidations:Incentives, Risks, and Instabilities](https://arxiv.org/pdf/2106.06389.pdf)
- [Understanding Compound's Liquidation](https://zengo.com/understanding-compounds-liquidation/)
- [Understanding Compound's Liquidation](https://zengo.com/understanding-compounds-liquidation/)
8 changes: 4 additions & 4 deletions x/leverage/client/tests/suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ func runTestTransactions(s *IntegrationTestSuite, tcs []testTransaction) {
s.Require().NoError(clientCtx.Codec.UnmarshalJSON(out.Bytes(), resp), out.String())

if tc.expectedErr == nil {
s.Require().Equal(0, int(resp.Code))
s.Require().Equal(0, int(resp.Code), "events %v", resp.Events)
} else {
s.Require().Equal(int(tc.expectedErr.ABCICode()), int(resp.Code))
}
Expand Down Expand Up @@ -1551,7 +1551,7 @@ func (s *IntegrationTestSuite) TestCmdBorrow() {
val.Address.String(),
"70uumee",
},
types.ErrBorrowLimitLow,
types.ErrUndercollaterized,
},
{
"borrow",
Expand Down Expand Up @@ -1722,7 +1722,7 @@ func (s *IntegrationTestSuite) TestCmdRepay() {
val.Address.String(),
"10xyz",
},
types.ErrInvalidAsset,
types.ErrInvalidRepayment,
},
{
"invalid asset (uToken)",
Expand All @@ -1731,7 +1731,7 @@ func (s *IntegrationTestSuite) TestCmdRepay() {
val.Address.String(),
"10u/umee",
},
types.ErrInvalidAsset,
types.ErrInvalidRepayment,
},
{
"repay",
Expand Down
9 changes: 5 additions & 4 deletions x/leverage/keeper/borrows.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,12 @@ func (k Keeper) CalculateBorrowLimit(ctx sdk.Context, collateral sdk.Coins) (sdk
return limit, nil
}

// CalculateLiquidationLimit uses the price oracle to determine the liquidation limit
// (in USD) provided by collateral sdk.Coins, using each token's uToken exchange rate and
// liquidation threshold. An error is returned if any input coins are not uTokens or if value
// CalculateLiquidationThreshold determines the maximum borrowed value (in USD) that a
// borrower with given collateral could reach before being eligible for liquidation, using
// each token's oracle price, uToken exchange rate, and liquidation threshold.
// An error is returned if any input coins are not uTokens or if value
// calculation fails.
func (k Keeper) CalculateLiquidationLimit(ctx sdk.Context, collateral sdk.Coins) (sdk.Dec, error) {
func (k Keeper) CalculateLiquidationThreshold(ctx sdk.Context, collateral sdk.Coins) (sdk.Dec, error) {
threshold := sdk.ZeroDec()

for _, coin := range collateral {
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 @@ -528,7 +528,7 @@ func (q Querier) LiquidationLimit(

collateral := q.Keeper.GetBorrowerCollateral(ctx, borrower)

limit, err := q.Keeper.CalculateLiquidationLimit(ctx, collateral)
limit, err := q.Keeper.CalculateLiquidationThreshold(ctx, collateral)
if err != nil {
return nil, err
}
Expand Down
4 changes: 2 additions & 2 deletions x/leverage/keeper/iter.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,8 +196,8 @@ func (k Keeper) GetEligibleLiquidationTargets(ctx sdk.Context) ([]sdk.AccAddress
return err
}

// compute liquidation limit from enabled collateral
liquidationLimit, err := k.CalculateLiquidationLimit(ctx, collateral)
// compute liquidation threshold from enabled collateral
liquidationLimit, err := k.CalculateLiquidationThreshold(ctx, collateral)
if err != nil {
return err
}
Expand Down
Loading