Skip to content

Commit

Permalink
fix: tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Jean-Grimal committed Feb 4, 2025
1 parent 4f449ca commit 3fc4662
Show file tree
Hide file tree
Showing 5 changed files with 298 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,13 @@ export const check = async <
srcToken = mainnetAddresses.usds!;
}

// Handle Midas Tokens

({ srcAmount, srcToken } = await encoder.handleMidasTokens(
market.params.collateralToken,
seizedAssets,
));

switch (true) {
// In case of Usual tokens, there aren't much liquidity outside of curve, so we use it instead of 1inch/paraswap
// Process USD0/USD0++ collateral liquidation with specific process (using curve)
Expand Down
27 changes: 22 additions & 5 deletions packages/liquidation-sdk-viem/src/LiquidationEncoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,10 @@ export class LiquidationEncoder<
const tries: SwapAttempt[] = [];
let dstAmount = 0n;

if (initialSrcToken === marketParams.loanToken) {
return { dstAmount: srcAmount };
}

while (true) {
const bestSwap = await fetchBestSwap({
chainId,
Expand Down Expand Up @@ -629,26 +633,39 @@ export class LiquidationEncoder<
};
}

const previewRedeemInstant = Midas.previewRedeemInstant(redemptionParams);
const previewRedeemInstantData =
Midas.previewRedeemInstant(redemptionParams);

if (!previewRedeemInstant) {
if (!previewRedeemInstantData) {
return {
srcAmount: seizedAssets,
srcToken: collateralToken,
};
}

const { amountTokenOutWithoutFee, feeAmount } = previewRedeemInstantData;

if (feeAmount > 0n) {
this.erc20Approve(collateralToken, redemptionVault, feeAmount);
}

this.pushCall(
redemptionVault,
0n,
encodeFunctionData({
abi: RedemptionVaultAbi,
functionName: "redeemInstant",
args: [tokenOut, seizedAssets, previewRedeemInstant],
args: [tokenOut, seizedAssets, amountTokenOutWithoutFee],
}),
);

return { srcAmount: previewRedeemInstant, srcToken: tokenOut };
return {
srcAmount: Midas.convertFromBase18(
amountTokenOutWithoutFee,
redemptionParams.tokenOutDecimals,
),
srcToken: tokenOut,
};
}

async getRedemptionParams(
Expand Down Expand Up @@ -759,7 +776,7 @@ export class LiquidationEncoder<
}

async getRedemptionVaultDailyLimits(vault: Address) {
const currentDayNumber = Math.round(Date.now()) / 1000 / (60 * 60 * 24);
const currentDayNumber = Math.round(Date.now() / 1000 / (60 * 60 * 24));
return readContract(this.client, {
address: vault,
abi: RedemptionVaultAbi,
Expand Down
20 changes: 13 additions & 7 deletions packages/liquidation-sdk-viem/src/tokens/midas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,15 +63,18 @@ export namespace Midas {

if (!tokenData) return undefined;

return _truncate(
(feeData.amountMTokenWithoutFee * usdData.mTokenRate) /
tokenData.tokenRate,
params.tokenOutDecimals,
);
return {
amountTokenOutWithoutFee: _truncate(
(feeData.amountMTokenWithoutFee * usdData.mTokenRate) /
tokenData.tokenRate,
params.tokenOutDecimals,
),
feeAmount: feeData.feeAmount,
};
}

function _calcAndValidateRedeem(params: PreviewRedeemInstantParams) {
if (params.minAmount < params.amountMTokenIn) return undefined;
if (params.minAmount > params.amountMTokenIn) return undefined;

const feeAmount = _getFeeAmount(params);

Expand Down Expand Up @@ -132,7 +135,10 @@ export namespace Midas {
return convertToBase18(convertFromBase18(value, decimals), decimals);
}

function convertFromBase18(originalAmount: bigint, decidedDecimals: bigint) {
export function convertFromBase18(
originalAmount: bigint,
decidedDecimals: bigint,
) {
return convert(originalAmount, 18n, decidedDecimals);
}

Expand Down
228 changes: 228 additions & 0 deletions packages/liquidation-sdk-viem/test/examples/midas.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import nock from "nock";
import "evm-maths";
import fetchMock from "fetch-mock";

import {
ChainId,
type InputMarketParams,
type MarketId,
addresses,
} from "@morpho-org/blue-sdk";
import { BLUE_API_BASE_URL, Time, format } from "@morpho-org/morpho-ts";

import { blueAbi, fetchMarket, fetchToken } from "@morpho-org/blue-sdk-viem";
import { Flashbots } from "@morpho-org/liquidation-sdk-viem";
import { type AnvilTestClient, testAccount } from "@morpho-org/test";
import dotenv from "dotenv";
import { erc20Abi, maxUint256, 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";
import { type LiquidationTestContext, test } from "../midasSetup.js";

dotenv.config();

fetchMock.config.fallbackToNetwork = true;
fetchMock.config.overwriteRoutes = false;
fetchMock.config.warnOnFallback = false;

const healthyDiffSlot =
"0x0000000000000000000000000000000000000000000000000000000000000034";

const { morpho } = addresses[ChainId.EthMainnet];

const borrower = testAccount(1);

describe("midas liquidation", () => {
beforeEach<LiquidationTestContext<typeof mainnet>>(async ({ client }) => {
vi.spyOn(Flashbots, "sendRawBundle").mockImplementation(async (txs) => {
for (const serializedTransaction of txs) {
await client.sendRawTransaction({ serializedTransaction });
}
});
});

afterEach(async () => {
vi.useRealTimers();
vi.restoreAllMocks();
fetchMock.restore();
});

const syncTimestamp = async (client: AnvilTestClient, timestamp?: bigint) => {
timestamp ??= (await client.timestamp()) + 60n;

vi.useFakeTimers({
now: Number(timestamp) * 1000,
toFake: ["Date"], // Avoid faking setTimeout, used to delay retries.
});

await client.setNextBlockTimestamp({ timestamp });

return timestamp;
};

// Cannot run concurrently because `fetch` is mocked globally.
test.sequential(
`should liquidate on the mTBILL/USDC market`,
async ({ client, encoder }) => {
const collateralPriceUsd = 1.015852;
const ethPriceUsd = 2_600;

const marketId =
"0xb98ad8501bd97ce0684b30b3645e31713e658e98d1955e8b677fb2585eaa9893" as MarketId; // mTBILL/USDC (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 mTokenDataFeed = "0xfCEE9754E8C375e145303b7cE7BEca3201734A2B";
const tokenOutDataFeed = "0x3aAc6fd73fA4e16Ec683BD4aaF5Ec89bb2C0EdC2";

// overwrite data feeds healthy diff slot to make the price feed healthy in the future

await client.setStorageAt({
address: mTokenDataFeed,
index: healthyDiffSlot,
value: maxUint256.toString(16) as `0x${string}`,
});

await client.setStorageAt({
address: tokenOutDataFeed,
index: healthyDiffSlot,
value: maxUint256.toString(16) as `0x${string}`,
});

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)! - 10n;

await client.deal({
erc20: loanToken.address,
account: borrower.address,
amount: borrowed,
});
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 as InputMarketParams,
borrowed,
0n,
borrower.address,
"0x",
],
});

await client.writeContract({
account: borrower,
address: morpho,
abi: blueAbi,
functionName: "borrow",
args: [
market.params as InputMarketParams,
borrowed,
0n,
borrower.address,
borrower.address,
],
});

await syncTimestamp(
client,
(await client.timestamp()) + Time.s.from.w(5n),
);

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,
spotPriceEth: collateralPriceUsd / ethPriceUsd,
},
loanAsset: {
address: market.params.loanToken,
decimals: loanToken.decimals,
priceUsd: 1,
spotPriceEth: 1 / ethPriceUsd,
},
},
},
],
},
},
});

await client.writeContract({
account: borrower,
address: morpho,
abi: blueAbi,
functionName: "accrueInterest",
args: [market.params as InputMarketParams],
});

client.transport.tracer.next = true;

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

const decimals = Number(loanToken.decimals);

const decimalBalance = await client.readContract({
address: market.params.loanToken,
abi: erc20Abi,
functionName: "balanceOf",
args: [encoder.address],
});

expect(
Number(format.number.of(decimalBalance, decimals)),
).toBeGreaterThan(12);
},
);
});
28 changes: 28 additions & 0 deletions packages/liquidation-sdk-viem/test/midasSetup.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { AnvilTestClient } from "@morpho-org/test";
import { type ViemTestContext, createViemTest } from "@morpho-org/test/vitest";
import { bytecode, executorAbi } from "executooor-viem";
import { type Chain, mainnet } from "viem/chains";
import { LiquidationEncoder } from "../src/index.js";

export interface LiquidationEncoderTestContext<chain extends Chain = Chain> {
encoder: LiquidationEncoder<AnvilTestClient<chain>>;
}

export interface LiquidationTestContext<chain extends Chain = Chain>
extends ViemTestContext<chain>,
LiquidationEncoderTestContext<chain> {}

export const test = createViemTest(mainnet, {
forkUrl: process.env.MAINNET_RPC_URL,
forkBlockNumber: 21_587_766,
}).extend<LiquidationEncoderTestContext<typeof mainnet>>({
encoder: async ({ client }, use) => {
const receipt = await client.deployContractWait({
abi: executorAbi,
bytecode,
args: [client.account.address],
});

await use(new LiquidationEncoder(receipt.contractAddress, client));
},
});

0 comments on commit 3fc4662

Please sign in to comment.