Skip to content

Commit

Permalink
Merge pull request #7 from morpho-labs/test/estimate-liquidation-gas
Browse files Browse the repository at this point in the history
Estimate gas cost of liquidations
  • Loading branch information
QGarchery authored Jul 10, 2023
2 parents c342527 + e1336df commit 0a1b22c
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 17 deletions.
3 changes: 3 additions & 0 deletions hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ const config: HardhatUserConfig = {
chainId: 1,
gasPrice: 0,
initialBaseFeePerGas: 0,
accounts: {
count: 102,
},
},
},
solidity: {
Expand Down
117 changes: 100 additions & 17 deletions test/hardhat/Blue.spec.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
import { hexZeroPad } from "@ethersproject/bytes";
import { setBalance } from "@nomicfoundation/hardhat-network-helpers";
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers";
import { expect } from "chai";
import { BigNumber, Wallet, constants, utils } from "ethers";
import hre from "hardhat";
import { Blue, OracleMock, ERC20Mock, IrmMock } from "types";

const iterations = 500;
const closePositions = false;
const nbLiquidations = 50;
// The liquidations gas test expects that 2*nbLiquidations + 1 is strictly less than the number of signers.
const initBalance = constants.MaxUint256.div(2);

let seed = 42;

Expand All @@ -18,17 +23,28 @@ function random() {
return (next() - 1) / 2147483646;
}

const abiCoder = new utils.AbiCoder();

function identifier(market: Market) {
const values = Object.values(market);
const encodedMarket = abiCoder.encode(["address", "address", "address", "address", "address", "uint256"], values);

return Buffer.from(utils.keccak256(encodedMarket).slice(2), "hex");
}

interface Market {
borrowableAsset: string;
collateralAsset: string;
borrowableOracle: string;
collateralOracle: string;
irm: string;
lLTV: BigNumber;
lltv: BigNumber;
}

describe("Blue", () => {
let signers: SignerWithAddress[];
let admin: SignerWithAddress;
let liquidator: SignerWithAddress;

let blue: Blue;
let borrowable: ERC20Mock;
Expand All @@ -39,26 +55,29 @@ describe("Blue", () => {
let market: Market;
let id: Buffer;

const initBalance = constants.MaxUint256.div(2);

beforeEach(async () => {
signers = await hre.ethers.getSigners();
admin = signers[2 * nbLiquidations];
liquidator = signers[2 * nbLiquidations + 1];

const ERC20MockFactory = await hre.ethers.getContractFactory("ERC20Mock", signers[0]);
const ERC20MockFactory = await hre.ethers.getContractFactory("ERC20Mock", admin);

borrowable = await ERC20MockFactory.deploy("DAI", "DAI", 18);
collateral = await ERC20MockFactory.deploy("Wrapped BTC", "WBTC", 18);

const OracleMockFactory = await hre.ethers.getContractFactory("OracleMock", signers[0]);
const OracleMockFactory = await hre.ethers.getContractFactory("OracleMock", admin);

borrowableOracle = await OracleMockFactory.deploy();
collateralOracle = await OracleMockFactory.deploy();

const BlueFactory = await hre.ethers.getContractFactory("Blue", signers[0]);
await borrowableOracle.connect(admin).setPrice(BigNumber.WAD);
await collateralOracle.connect(admin).setPrice(BigNumber.WAD);

const BlueFactory = await hre.ethers.getContractFactory("Blue", admin);

blue = await BlueFactory.deploy(signers[0].address);
blue = await BlueFactory.deploy(admin.address);

const IrmMockFactory = await hre.ethers.getContractFactory("IrmMock", signers[0]);
const IrmMockFactory = await hre.ethers.getContractFactory("IrmMock", admin);

irm = await IrmMockFactory.deploy(blue.address);

Expand All @@ -68,20 +87,17 @@ describe("Blue", () => {
borrowableOracle: borrowableOracle.address,
collateralOracle: collateralOracle.address,
irm: irm.address,
lLTV: BigNumber.WAD,
lltv: BigNumber.WAD.div(2),
};

const abiCoder = new utils.AbiCoder();
const values = Object.values(market);
const encodedMarket = abiCoder.encode(["address", "address", "address", "address", "address", "uint256"], values);

id = Buffer.from(utils.keccak256(encodedMarket).slice(2), "hex");
id = identifier(market);

await blue.connect(signers[0]).enableIrm(irm.address);
await blue.connect(signers[0]).createMarket(market);
await blue.connect(admin).enableLltv(market.lltv);
await blue.connect(admin).enableIrm(market.irm);
await blue.connect(admin).createMarket(market);
});

it("should simulate gas cost", async () => {
it("should simulate gas cost [main]", async () => {
for (let i = 1; i < iterations; ++i) {
console.log(i, "/", iterations);

Expand Down Expand Up @@ -115,4 +131,71 @@ describe("Blue", () => {
}
}
});

it("should simulate gas cost [liquidations]", async () => {
let liquidationData = [];

// Create accounts close to liquidation
for (let i = 0; i < 2 * nbLiquidations; ++i) {
const user = signers[i];
const tranche = Math.floor(1 + i / 2);
const lltv = BigNumber.WAD.mul(tranche).div(nbLiquidations + 1);

const amount = BigNumber.WAD.mul(1 + Math.floor(random() * 100));
const borrowedAmount = amount.mul(lltv).div(BigNumber.WAD);
const maxSeize = closePositions ? constants.MaxUint256 : amount.div(2);

market.lltv = lltv;
// We use 2 different users to borrow from a market so that liquidations do not put the borrow storage back to 0 on that market.
// Consequently, we should only create the market on a particular lltv once.
if (i % 2 == 0) {
await blue.connect(admin).enableLltv(market.lltv);
await blue.connect(admin).enableIrm(market.irm);
await blue.connect(admin).createMarket(market);
liquidationData.push({
lltv: lltv,
borrower: user.address,
maxSeize: maxSeize,
});
}

await setBalance(user.address, initBalance);
await borrowable.setBalance(user.address, initBalance);
await borrowable.connect(user).approve(blue.address, constants.MaxUint256);
await collateral.setBalance(user.address, initBalance);
await collateral.connect(user).approve(blue.address, constants.MaxUint256);

await blue.connect(user).supply(market, amount);
await blue.connect(user).supplyCollateral(market, amount);

await blue.connect(user).borrow(market, borrowedAmount);
}

await borrowableOracle.connect(admin).setPrice(BigNumber.WAD.mul(1000));

await setBalance(liquidator.address, initBalance);
await borrowable.connect(liquidator).approve(blue.address, constants.MaxUint256);
await borrowable.setBalance(liquidator.address, initBalance);
for (let i = 0; i < liquidationData.length; i++) {
let data = liquidationData[i];
market.lltv = data.lltv;
await blue.connect(liquidator).liquidate(market, data.borrower, data.maxSeize);
}

for (let i = 0; i < 2 * nbLiquidations; i++) {
const user = signers[i];
const tranche = Math.floor(1 + i / 2);
const lltv = BigNumber.WAD.mul(tranche).div(nbLiquidations + 1);

market.lltv = lltv;
id = identifier(market);

let collat = await blue.collateral(id, user.address);
expect(
!closePositions || collat == BigNumber.from(0),
"did not take the whole collateral when closing the position"
).true;
expect(closePositions || collat != BigNumber.from(0), "unexpectedly closed the position").true;
}
});
});

0 comments on commit 0a1b22c

Please sign in to comment.