Skip to content

Commit

Permalink
Merge pull request #175 from morpho-org/feature/integ-274-handle-bad-…
Browse files Browse the repository at this point in the history
…debt-events

Feature/integ 274 handle bad debt events
  • Loading branch information
Rubilmax authored Dec 2, 2024
2 parents b930a74 + f647328 commit 9f272df
Show file tree
Hide file tree
Showing 2 changed files with 202 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -135,8 +135,11 @@ export const check = async <
.map((_v, i) => seizableCollateral / 2n ** BigInt(i))
.filter(
(seizedAssets) =>
collateralToken.toUsd(seizedAssets)! > parseEther("1000"), // Do not try seizing less than $1000 collateral.
)
collateralToken.toUsd(seizedAssets)! > parseEther("1000") ||
(accrualPosition.collateral === seizedAssets &&
loanToken.toUsd(accrualPosition.borrowAssets)! >
parseEther("1000")),
) // Do not try seizing less than $1000 collateral, except if we can realize more than $1000 bad debt.
.map(async (seizedAssets) => {
const repaidShares =
market.getLiquidationRepaidShares(seizedAssets)!;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ import {
type MarketId,
addresses,
} from "@morpho-org/blue-sdk";
import { BLUE_API_BASE_URL, ZERO_ADDRESS, format } from "@morpho-org/morpho-ts";
import {
BLUE_API_BASE_URL,
Time,
ZERO_ADDRESS,
format,
} from "@morpho-org/morpho-ts";
import type { BuildTxInput } from "@paraswap/sdk";

import {
Expand All @@ -25,7 +30,13 @@ import {
mainnetAddresses,
} from "@morpho-org/liquidation-sdk-viem";
import { type AnvilTestClient, testAccount } from "@morpho-org/test";
import { encodeFunctionData, erc20Abi, maxUint256, parseUnits } from "viem";
import {
encodeFunctionData,
erc20Abi,
maxUint256,
parseEther,
parseUnits,
} from "viem";
import type { mainnet } from "viem/chains";
import { afterEach, beforeEach, describe, expect, vi } from "vitest";
import { check } from "../../examples/whitelisted-erc4626-1inch.js";
Expand Down Expand Up @@ -54,6 +65,7 @@ const pendleRedeemApiMatcher = new RegExp(`${Pendle.getRedeemApiUrl(1)}.*`);
const { morpho } = addresses[ChainId.EthMainnet];

const borrower = testAccount(1);
const liquidator = testAccount(2);

describe("erc4626-1inch", () => {
let swapMockAddress: Address;
Expand Down Expand Up @@ -719,6 +731,189 @@ describe("erc4626-1inch", () => {
},
);

// Cannot run concurrently because `fetch` is mocked globally.
test.sequential(
`should liquidate on standard market with bad debt but low collateral amount`,
async ({ client, encoder }) => {
const collateralPriceUsd = 3_129;
const ethPriceUsd = 2_653;

const marketId =
"0xb8fc70e82bc5bb53e773626fcc6a23f7eefa036918d7ef216ecfb1950a94a85e" as MarketId; // wstETH/WETH (96.5%)

const market = await fetchMarket(marketId, client);
const [collateralToken, loanToken] = await Promise.all([
fetchToken(market.params.collateralToken, client),
fetchToken(market.params.loanToken, client),
]);

const collateral = parseUnits("10000", collateralToken.decimals);
await client.deal({
erc20: collateralToken.address,
account: borrower.address,
amount: collateral,
});
await client.approve({
account: borrower,
address: collateralToken.address,
args: [morpho, maxUint256],
});
await client.writeContract({
account: borrower,
address: morpho,
abi: blueAbi,
functionName: "supplyCollateral",
args: [market.params, collateral, borrower.address, "0x"],
});

const borrowed = market.getMaxBorrowAssets(collateral)! - 1n;
await client.deal({
erc20: loanToken.address,
account: borrower.address,
amount: borrowed - market.liquidity,
});
await client.approve({
account: borrower,
address: loanToken.address,
args: [morpho, maxUint256],
});
await client.writeContract({
account: borrower,
address: morpho,
abi: blueAbi,
functionName: "supply",
args: [
market.params,
borrowed - market.liquidity, // 100% utilization after borrow.
0n,
borrower.address,
"0x",
],
});

await client.writeContract({
account: borrower,
address: morpho,
abi: blueAbi,
functionName: "borrow",
args: [
market.params as Pick<
typeof market.params,
"collateralToken" | "loanToken" | "oracle" | "irm" | "lltv"
>,
borrowed,
0n,
borrower.address,
borrower.address,
],
});

await client.setNextBlockTimestamp({
timestamp: (await client.timestamp()) + Time.s.from.y(1n),
});

nock(BLUE_API_BASE_URL)
.post("/graphql")
.reply(200, { data: { markets: { items: [{ uniqueKey: marketId }] } } })
.post("/graphql")
.reply(200, {
data: {
assetByAddress: {
priceUsd: ethPriceUsd,
spotPriceEth: 1,
},
marketPositions: {
items: [
{
user: {
address: borrower.address,
},
market: {
uniqueKey: marketId,
collateralAsset: {
address: market.params.collateralToken,
decimals: collateralToken.decimals,
priceUsd: collateralPriceUsd,
},
loanAsset: {
address: market.params.loanToken,
decimals: loanToken.decimals,
priceUsd: ethPriceUsd,
spotPriceEth: 1 / ethPriceUsd,
},
},
},
],
},
},
});

await client.deal({
erc20: loanToken.address,
account: liquidator.address,
amount: maxUint256,
});
await client.approve({
account: liquidator,
address: loanToken.address,
args: [morpho, maxUint256],
});
await client.writeContract({
account: liquidator,
address: morpho,
abi: blueAbi,
functionName: "liquidate",
args: [
market.params,
borrower.address,
collateral - parseEther("0.01"),
0n,
"0x",
],
});

await syncTimestamp(client, await client.timestamp());

const postLiquidationAccrualPosition = await fetchAccrualPosition(
borrower.address as Address,
marketId,
client,
);

const collateralValue =
(collateralPriceUsd *
Number(postLiquidationAccrualPosition.collateral)) /
10 ** collateralToken.decimals;
const loanValue =
(ethPriceUsd * Number(postLiquidationAccrualPosition.borrowAssets)) /
10 ** loanToken.decimals;

expect(loanValue).toBeGreaterThan(1000);
expect(collateralValue).toBeLessThan(1000);

mockOneInch(encoder, [
{
srcAmount: parseEther("0.01"),
dstAmount: "14035289781000635",
},
]);
mockParaSwap(encoder, [
{ srcAmount: parseEther("0.01"), dstAmount: "14035289781000635" },
]);

await check(encoder.address, client, client.account, [marketId]);

const finalAccrualPosition = await fetchAccrualPosition(
borrower.address as Address,
marketId,
client,
);

expect(finalAccrualPosition.borrowShares).toEqual(0n);
expect(finalAccrualPosition.collateral).toEqual(0n);
},
);

// Cannot run concurrently because `fetch` is mocked globally.
test.sequential(
`should liquidate on a PT standard market before maturity`,
Expand Down

0 comments on commit 9f272df

Please sign in to comment.