diff --git a/hardhat.config.ts b/hardhat.config.ts index e4d30cbd..74e473ce 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -11,7 +11,6 @@ import "@nomicfoundation/hardhat-foundry"; import "@nomicfoundation/hardhat-network-helpers"; import "@typechain/hardhat"; -import blueConfig from "./lib/morpho-blue/hardhat.config"; dotenv.config(); @@ -23,12 +22,22 @@ const config: HardhatUserConfig = { gasPrice: 0, initialBaseFeePerGas: 0, allowBlocksWithSameTimestamp: true, - accounts: { - count: 201, // must be odd - }, }, }, - solidity: blueConfig.solidity, + solidity: { + compilers: [ + { + version: "0.8.19", + settings: { + optimizer: { + enabled: true, + runs: 200, + }, + viaIR: true, + }, + }, + ], + }, mocha: { timeout: 3000000, }, diff --git a/src/mocks/ERC20Mock.sol b/src/mocks/ERC20Mock.sol deleted file mode 100644 index f2470bd8..00000000 --- a/src/mocks/ERC20Mock.sol +++ /dev/null @@ -1,13 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {ERC20} from "../../lib/morpho-blue/lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; - -contract ERC20Mock is ERC20 { - constructor(string memory _name, string memory _symbol) ERC20(_name, _symbol) {} - - function setBalance(address account, uint256 amount) external { - _burn(account, balanceOf(account)); - _mint(account, amount); - } -} diff --git a/src/mocks/MorphoMock.sol b/src/mocks/MorphoMock.sol deleted file mode 100644 index daae802c..00000000 --- a/src/mocks/MorphoMock.sol +++ /dev/null @@ -1,17 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {Id, MarketParams, MarketParamsLib, ErrorsLib, Morpho} from "../../lib/morpho-blue/src/Morpho.sol"; - -contract MorphoMock is Morpho { - using MarketParamsLib for MarketParams; - - constructor(address newOwner) Morpho(newOwner) {} - - function accrueInterest(MarketParams memory marketParams) external { - Id id = marketParams.id(); - require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); - - _accrueInterest(marketParams, id); - } -} diff --git a/src/mocks/OracleMock.sol b/src/mocks/OracleMock.sol deleted file mode 100644 index ffccee1f..00000000 --- a/src/mocks/OracleMock.sol +++ /dev/null @@ -1,12 +0,0 @@ -// SPDX-License-Identifier: GPL-2.0-or-later -pragma solidity ^0.8.0; - -import {IOracle} from "../../lib/morpho-blue/src/interfaces/IOracle.sol"; - -contract OracleMock is IOracle { - uint256 public price; - - function setPrice(uint256 newPrice) external { - price = newPrice; - } -} diff --git a/test/SpeedJumpIrmTest.sol b/test/forge/SpeedJumpIrmTest.sol similarity index 100% rename from test/SpeedJumpIrmTest.sol rename to test/forge/SpeedJumpIrmTest.sol diff --git a/test/hardhat/irm/Irm.spec.ts b/test/hardhat/irm/Irm.spec.ts index c43ba725..52a98cf7 100644 --- a/test/hardhat/irm/Irm.spec.ts +++ b/test/hardhat/irm/Irm.spec.ts @@ -1,16 +1,12 @@ -import { AbiCoder, MaxUint256, keccak256, toBigInt } from "ethers"; +import { AbiCoder, keccak256, toBigInt } from "ethers"; import hre from "hardhat"; import _range from "lodash/range"; -import { ERC20Mock, AdaptativeCurveIrm, MorphoMock, OracleMock } from "types"; -import { MarketParamsStruct } from "types/src/mocks/MorphoMock"; +import { AdaptativeCurveIrm } from "types"; +import { MarketParamsStruct } from "types/lib/morpho-blue/src/interfaces/IIrm"; import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; import { setNextBlockTimestamp } from "@nomicfoundation/hardhat-network-helpers/dist/src/helpers/time"; -// Without the division it overflows. -const initBalance = MaxUint256 / 10000000000000000n; -const oraclePriceScale = 1000000000000000000000000000000000000n; - let seed = 42; const random = () => { seed = (seed * 16807) % 2147483647; @@ -35,151 +31,65 @@ const randomForwardTimestamp = async () => { const block = await hre.ethers.provider.getBlock("latest"); const elapsed = random() < 1 / 2 ? 0 : (1 + Math.floor(random() * 100)) * 12; // 50% of the time, don't go forward in time. + const newTimestamp = block!.timestamp + elapsed; + await setNextBlockTimestamp(block!.timestamp + elapsed); + + return newTimestamp; }; describe("irm", () => { let admin: SignerWithAddress; - let suppliers: SignerWithAddress[]; - let borrowers: SignerWithAddress[]; - let morpho: MorphoMock; - let borrowable: ERC20Mock; - let collateral: ERC20Mock; - let oracle: OracleMock; let irm: AdaptativeCurveIrm; let marketParams: MarketParamsStruct; - let id: Buffer; - - const updateMarket = (newMarket: Partial) => { - marketParams = { ...marketParams, ...newMarket }; - id = identifier(marketParams); - }; beforeEach(async () => { - const allSigners = await hre.ethers.getSigners(); - - const users = allSigners.slice(0, -1); - - [admin] = allSigners.slice(-1); - suppliers = users.slice(0, users.length / 2); - borrowers = users.slice(users.length / 2); - - const ERC20MockFactory = await hre.ethers.getContractFactory("ERC20Mock", admin); - - borrowable = await ERC20MockFactory.deploy("DAI", "DAI"); - collateral = await ERC20MockFactory.deploy("Wrapped BTC", "WBTC"); - - const OracleMockFactory = await hre.ethers.getContractFactory("OracleMock", admin); - - oracle = await OracleMockFactory.deploy(); - - await oracle.setPrice(oraclePriceScale); - - const MorphoFactory = await hre.ethers.getContractFactory("MorphoMock", admin); - - morpho = await MorphoFactory.deploy(admin.address); - - const morphoAddress = await morpho.getAddress(); + [admin] = await hre.ethers.getSigners(); const AdaptativeCurveIrmFactory = await hre.ethers.getContractFactory("AdaptativeCurveIrm", admin); irm = await AdaptativeCurveIrmFactory.deploy( - morphoAddress, + await admin.getAddress(), 4000000000000000000n, 1585489599188n, 900000000000000000n, 317097919n, ); - const borrowableAddress = await borrowable.getAddress(); - const collateralAddress = await collateral.getAddress(); - const oracleAddress = await oracle.getAddress(); const irmAddress = await irm.getAddress(); - updateMarket({ - borrowableToken: borrowableAddress, - collateralToken: collateralAddress, - oracle: oracleAddress, + marketParams = { + // Non-zero address to include calldata gas cost. + collateralToken: irmAddress, + loanToken: irmAddress, + oracle: irmAddress, irm: irmAddress, - lltv: BigInt.WAD / 2n + 1n, - }); - - await morpho.enableLltv(marketParams.lltv); - await morpho.enableIrm(marketParams.irm); - await morpho.createMarket(marketParams); - - for (const user of users) { - await borrowable.setBalance(user.address, initBalance); - await borrowable.connect(user).approve(morphoAddress, MaxUint256); - await collateral.setBalance(user.address, initBalance); - await collateral.connect(user).approve(morphoAddress, MaxUint256); - } + lltv: 0, + }; - hre.tracer.nameTags[morphoAddress] = "Morpho"; - hre.tracer.nameTags[collateralAddress] = "Collateral"; - hre.tracer.nameTags[borrowableAddress] = "Borrowable"; - hre.tracer.nameTags[oracleAddress] = "Oracle"; hre.tracer.nameTags[irmAddress] = "IRM"; }); it("should simulate gas cost [main]", async () => { - for (let i = 0; i < suppliers.length; ++i) { - logProgress("main", i, suppliers.length); - - const supplier = suppliers[i]; - - let assets = BigInt.WAD * toBigInt(1 + Math.floor(random() * 100)); - - await randomForwardTimestamp(); - - await morpho.connect(supplier).supply(marketParams, assets, 0, supplier.address, "0x"); - - await randomForwardTimestamp(); - - await morpho.connect(supplier).withdraw(marketParams, assets / 2n, 0, supplier.address, supplier.address); - - const borrower = borrowers[i]; - - const market = await morpho.market(id); - const liquidity = market.totalSupplyAssets - market.totalBorrowAssets; - - assets = assets.min(liquidity / 2n); - - await randomForwardTimestamp(); - - await morpho.connect(borrower).supplyCollateral(marketParams, assets, borrower.address, "0x"); - - await randomForwardTimestamp(); - - await morpho.connect(borrower).borrow(marketParams, assets / 2n, 0, borrower.address, borrower.address); - - await randomForwardTimestamp(); - - await morpho.connect(borrower).repay(marketParams, assets / 4n, 0, borrower.address, "0x"); - - await randomForwardTimestamp(); - - await morpho.connect(borrower).withdrawCollateral(marketParams, assets / 8n, borrower.address, borrower.address); + for (let i = 0; i < 200; ++i) { + logProgress("main", i, 200); + + const lastUpdate = await randomForwardTimestamp(); + + const totalSupplyAssets = BigInt.WAD * toBigInt(1 + Math.floor(random() * 100)); + const totalBorrowAssets = totalSupplyAssets.min(BigInt.WAD * toBigInt(1 + Math.floor(random() * 50))); + + await irm.borrowRate(marketParams, { + fee: 0, + lastUpdate, + totalSupplyAssets: totalSupplyAssets, + totalBorrowAssets: totalBorrowAssets, + // Non-zero shares to include calldata gas cost. + totalSupplyShares: 1000000000000n, + totalBorrowShares: 1000000000000n, + }); } }); - - it("should trace borrow rate [borrowRate]", async () => { - const supplier = suppliers[0]; - - await morpho.connect(supplier).supply(marketParams, BigInt.WAD, 0, supplier.address, "0x"); - - const borrower = borrowers[0]; - - await morpho.connect(borrower).supplyCollateral(marketParams, BigInt.WAD, borrower.address, "0x"); - await morpho.connect(borrower).borrow(marketParams, BigInt.WAD / 2n, 0, borrower.address, borrower.address); - - const block = await hre.ethers.provider.getBlock("latest"); - await setNextBlockTimestamp(block!.timestamp + 60 * 60 * 24); - - hre.tracer.printNext = true; - hre.tracer.enableAllOpcodes = true; - await morpho.accrueInterest(marketParams); - }); }); diff --git a/tsconfig.json b/tsconfig.json index c98af8f4..e538e2c1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,15 +1,15 @@ { "compilerOptions": { - "target": "esnext", + "target": "es2020", + "module": "nodenext", + "moduleResolution": "nodenext", + "outDir": "dist", + "baseUrl": ".", "strict": true, "esModuleInterop": true, - "rootDir": ".", - "baseUrl": ".", - "outDir": "dist", - "moduleResolution": "node", "resolveJsonModule": true, "declaration": true }, - "include": ["src", "types", "test/hardhat"], - "files": ["./hardhat.config.ts"] + "include": ["types", "test/hardhat"], + "files": ["hardhat.config.ts"] }