osmosis-labs / isotonic

Smart Contracts for the Lendex Protocol
MIT License
1 stars 0 forks source link

Direct Liquidation Possible #27

Closed ethanfrey closed 2 years ago

ethanfrey commented 2 years ago

When someone has more debt than available credit, their collateral can be liquidated as defined here https://confio.slab.com/posts/credit-agency-ujs0yrvj#hwq1o-liquidation

In #23 we define a query that calculates total debt over all markets. If the total debt is greater than the total available credit, the account can be liquidated.

The way we allow this to be liquidated is that some person can pay off debt in exchange for cheap access to collateral. We add a liquidation_price: Decimal in the credit agency instantiation. Standard value would be "0.92" so that liquidator gets asset at 8% discount.

We add a new message to the Credit Agency:

enum ExecuteMsg {
  Liquidate {
    account: String, // Addr
    collateral: String,  // market_token denom
  }
}

The caller must send "repayment" tokens along with the message, which match an outstanding btoken of this account.

The process is more or less:

Note: we should asset all collateralization_ratio must be lower than liquidation_price, so this can only decrease debt more than it decreases available credit. -> https://github.com/confio/lendex/issues/55

In a future issue, we can allow selling this on a DEX, but that requires more integrations, so we provide a basic interface which can incentivise the creation of bots to hunt for underwater positions.

ethanfrey commented 2 years ago

For this version:

We can handle both of these cases in a future issue. (For point one, simply returning the overpay. For point 2 ???)

ueco-jb commented 2 years ago

@ethanfrey

Assert that the caller provided funds in one token X "repayment"

I don't understand this sentence. Do you mean to compare sent denom with common_token (global for CA)? If account has debt in multiple markets, should we allow sending tokens in market's native denoms? Since it's

funds: Vec<Coin>

that could be possible. Even cheaper for someone, if caller bought those native tokens in better rate than one set by oracle in given moment. Does it makes sense?

Assert there exists a market for "repayment"

If market wouldn't exist, query for total_credit_line would either fail or simply wouldn't return any debt (because how otherwise?).

ethanfrey commented 2 years ago

Assert that the caller provided funds in one token X "repayment"

I don't understand this sentence. Do you mean to compare sent denom with common_token (global for CA)? If account has debt in multiple markets, should we allow sending tokens in market's native denoms? Since it's

funds: Vec<Coin>

that could be possible. Even cheaper for someone, if caller bought those native tokens in better rate than one set by oracle in given moment. Does it makes sense?

We should assert that funds is exactly one element [{denom, amount}], and that the denom matches a registered market.

We only allow liquidations by paying off one debt at a time. Usually, this will make the account positive. One the rare case it is so far underwater, you can make a second message to pay off another market.

Assert there exists a market for "repayment"

If market wouldn't exist, query for total_credit_line would either fail or simply wouldn't return any debt (because how otherwise?).

Yes, this can be handled in a future query, but we should make this failure mode explicit. Eg. "you requested to be repayed in 'utgd' but there is no such market" vs "you requested to be repayed in 'uatom' but the liquidated account has no collateral in that market"

ethanfrey commented 2 years ago

Let me define a few clear test cases. They all have the same setup:

2 markets:

ATOM:

UST:

common_token: OSMO liquidation ratio: 0.92

Initial settings:

Create some investments by "everyone" to represent everyone besides the active user we measure:


Test #1 - no interest

Setup:

User A has 5000 ATOM Deposit 4000 ATOM into market (4000 LATOM) Borrow 75000 UST Ensure total credit line:

Liquidate

Change price ATOM/OSMO to 3.0 Ensure total credit line:

Proper transfer

60.000 UST * 0.1 / 3 / 0.92 = 2000 / 0.92 = 2173 LATOM

Check balance: A / BUST = 15.000 X / LATOM = 2173 A / LATOM = 1827

Check credit line for A:

(any off-by-one or -two errors are acceptable rounding for now... but you can leave a comment to check in the later issue)

ethanfrey commented 2 years ago

Test 2 - with 6 month interest (no repay needed to trigger - it must be charged before transfer)

Setup:

User A has 5000 ATOM Deposit 4000 ATOM into market (4000 LATOM) Borrow 75000 UST Ensure total credit line:

Liquidate

Update block time 1/2 year months (182.5 * 86400 seconds) Change price ATOM/OSMO to 3.0 User X sends 60.000 UST to liquidate against ATOM collateral -> Action succeeds

Proper transfer

Expected interest charges for A before payment: ATOM - 2000 borrowed / 14000 deposit = 3 + (21/7) = 6% 1/2 year = 3% interest 2000 0.03 ATOM iterest * 4000 / 14000 = 17

UST - 95.000 borrowed / 100.000 deposit = 3 + (20 0.95) = 22 1/2 year = 11% interest 75.000 * 0.11 = 8250

Previous state with interest (A)

Repayment: 60.000 UST * 0.1 / 3 / 0.92 = 2000 / 0.92 = 2173 LATOM

Check balance: A / BUST = 23.250 X / LATOM = 2173 A / LATOM = 1844

Check credit line for A:

ueco-jb commented 2 years ago

@ethanfrey Test 2

Deposit 4000 ATOM into market (4000 LATOM) Borrow 75000 UST

and then?

ATOM - 2000 borrowed / 14000 deposit

?

UST - 95.000 borrowed / 100.000 deposit

So I guess there are typos (14000 instead of 4000? but then you rely rest of calculation on that 14000 number?) and you incorrectly stated amount of UST tokens that needs to be borrowed at beginning (75_000 instead of 95_000). Also ATOMs are not being borrowed (wasn't mentioned) and if they were, X cannot borrow 2000 ATOM because after depositing 100_000 UST his credit line is 10_000 * 0.6 ust collateral price / 4.0 atom (original) price = 1500 ATOM so current calculations goes sideways anyway.

Also A can't borrow 95_000 UST (if that wasn't a typo), because after depositing 4000 ATOM his credit line is 4_000 * 4.0 ATOM price * 0.5 atom collateral price / 0.1 ust = 80_000 UST

So anyway, my interest rates are:

rates = (base + slope * utilization) / 2 (half year)
atom = (3% + 20% * (1500/4000)) / 2 = (3% + 20% * 37.5%) / 2 = (3% + 7.5%) / 2 = 5.25% = 5%
ust = (3% + 20% * (75_000/100_000)) / 2 = (3% + 20% * 75%) / 2 = (3% + 15%) / 2 = 9%
ueco-jb commented 2 years ago

I have some issues with results from second test case. https://github.com/confio/lendex/blob/27-direct-liquidation/contracts/lendex-credit-agency/src/multitest/liquidate.rs#L566

I have some small disproportions with values I end up, and it's because of MULTIPLIER from lendex-token (which should be applied based on token ratios, right?).

https://github.com/confio/lendex/blob/27-direct-liquidation/contracts/lendex-token/src/contract.rs#L268

I see, that actually not 60_000 tokens are being burned but 55045, which affects all further calculations.. It looks like those 60_000 tokens are scaled down by 9% interest rates?

ethanfrey commented 2 years ago

Test 2 uses the same "common configuration" as test 1 (the part above the long grey bar)