Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test: add blue tests #134

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ test = "test"
libs = ["lib"]
via_ir = true
optimizer_runs = 999999 # Etherscan does not support verifying contracts with more optimizer runs.
unchecked_cheatcode_artifacts = true

[profile.default.fuzz]
runs = 4096
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
"scripts": {
"prepare": "husky install && forge install",
"build:forge": "FOUNDRY_PROFILE=build forge build",
"build:blue": "yarn --cwd lib/morpho-blue/ build:forge --out ../../out/",
"build:hardhat": "hardhat compile",
"test:forge": "FOUNDRY_PROFILE=test forge test",
"test:forge": "yarn build:blue && FOUNDRY_PROFILE=test forge test",
Comment on lines +14 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't we agree to only use the "import files technique" from now, which is more robust

"test:hardhat": "hardhat test",
"lint": "yarn lint:forge && yarn lint:ts",
"lint:ts": "prettier --check test/hardhat",
Expand Down
129 changes: 129 additions & 0 deletions test/forge/BlueTest.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../../src/adaptive-curve-irm/interfaces/IAdaptiveCurveIrm.sol";
import "../../src/fixed-rate-irm/interfaces/IFixedRateIrm.sol";

import {IMorpho, MarketParams} from "../../lib/morpho-blue/src/interfaces/IMorpho.sol";
import {MathLib} from "../../lib/morpho-blue/src/libraries/MathLib.sol";
import {MarketParamsLib} from "../../lib/morpho-blue/src/libraries/MarketParamsLib.sol";
import {ORACLE_PRICE_SCALE} from "../../lib/morpho-blue/src/libraries/ConstantsLib.sol";

import "../../lib/forge-std/src/Test.sol";
import {ERC20Mock} from "../../lib/morpho-blue/src/mocks/ERC20Mock.sol";
import {OracleMock} from "../../lib/morpho-blue/src/mocks/OracleMock.sol";

contract BlueTest is Test {
using MathLib for uint256;
using MarketParamsLib for MarketParams;

event BorrowRateUpdate(Id indexed id, uint256 avgBorrowRate, uint256 rateAtTarget);

uint256 internal constant MIN_TEST_AMOUNT = 100;
uint256 internal constant MAX_TEST_AMOUNT = 1e28;
uint256 internal constant MIN_TIME_ELAPSED = 10;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why do we need this?

uint256 internal constant MAX_TIME_ELAPSED = 315360000;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where dois this value come from?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for MAX_TEST_AMOUNT and MIN_TEST_AMOUNT I used the same ones as we use for blue tests
MIN_TIME_ELAPSED is 10sec (approx one block)
MAX_TIME_ELAPSED is 315360000 sec (ten years)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think we can just use 10 year instead then

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's no longer available since solc 0.5.0

image

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can write 10 * 365 days though

uint256 internal constant DEFAULT_TEST_LLTV = 0.8 ether;

address internal OWNER = makeAddr("Owner");
address internal SUPPLIER = makeAddr("Supplier");
address internal BORROWER = makeAddr("Borrower");

IMorpho internal morpho = IMorpho(deployCode("Morpho.sol", abi.encode(OWNER)));
ERC20Mock internal loanToken;
ERC20Mock internal collateralToken;
OracleMock internal oracle;

IAdaptiveCurveIrm internal adaptiveCurveIrm =
IAdaptiveCurveIrm(deployCode("AdaptiveCurveIrm.sol", abi.encode(address(morpho))));
IFixedRateIrm public fixedRateIrm = IFixedRateIrm(deployCode("FixedRateIrm.sol"));
Comment on lines +37 to +39
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we instead have one test that tests with all the IRMs (not sure how to do this) , and all their configs (That is why I was more thinking about doing some formal verification here, maybe with halmos?) ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What kind of properties you are thinking of @MathisGD ? For some of them I think fuzzing would be more appropriate because bounds are not "hardcoded". For example, if the fixed rate IRM has a given rate then we could check that, fuzzing the parameters, this is "more or less" the rate that borrowers get over time. This "more or less" is not formal and difficult to put in a specification. But it would not be difficult with fuzzing, just use approxEqRel with some well-chosen percentage


function setUp() public {
SUPPLIER = makeAddr("Supplier");
BORROWER = makeAddr("Borrower");

loanToken = new ERC20Mock();
vm.label(address(loanToken), "LoanToken");

collateralToken = new ERC20Mock();
vm.label(address(collateralToken), "CollateralToken");

oracle = new OracleMock();

oracle.setPrice(ORACLE_PRICE_SCALE);

vm.startPrank(OWNER);
morpho.enableIrm(address(adaptiveCurveIrm));
morpho.enableIrm(address(fixedRateIrm));
morpho.enableLltv(DEFAULT_TEST_LLTV);
vm.stopPrank();

vm.prank(SUPPLIER);
loanToken.approve(address(morpho), type(uint256).max);

vm.startPrank(BORROWER);
loanToken.approve(address(morpho), type(uint256).max);
collateralToken.approve(address(morpho), type(uint256).max);
vm.stopPrank();
}

/* TESTS */

function testAdaptiveCurveIrm(uint256 amountSupplied, uint256 amountBorrowed, uint256 timeElapsed) public {
amountSupplied = bound(amountSupplied, MIN_TEST_AMOUNT, MAX_TEST_AMOUNT);
amountBorrowed = bound(amountBorrowed, MIN_TEST_AMOUNT, amountSupplied);
timeElapsed = bound(timeElapsed, MIN_TIME_ELAPSED, MAX_TIME_ELAPSED);

MarketParams memory marketParams = MarketParams(
address(loanToken), address(collateralToken), address(oracle), address(adaptiveCurveIrm), DEFAULT_TEST_LLTV
);
morpho.createMarket(marketParams);

loanToken.setBalance(SUPPLIER, amountSupplied);
vm.prank(SUPPLIER);
morpho.supply(marketParams, amountSupplied, 0, SUPPLIER, hex"");

uint256 collateralAmount = amountBorrowed.wDivUp(DEFAULT_TEST_LLTV);
collateralToken.setBalance(BORROWER, collateralAmount);

vm.startPrank(BORROWER);
morpho.supplyCollateral(marketParams, collateralAmount, BORROWER, hex"");
morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
vm.stopPrank();

vm.warp(timeElapsed);

morpho.accrueInterest(marketParams);
}

function testFixedRateIrm(uint256 amountSupplied, uint256 amountBorrowed, uint256 fixedRate, uint256 timeElapsed)
public
{
amountSupplied = bound(amountSupplied, MIN_TEST_AMOUNT, MAX_TEST_AMOUNT);
amountBorrowed = bound(amountBorrowed, MIN_TEST_AMOUNT, amountSupplied);
fixedRate = bound(timeElapsed, 1, fixedRateIrm.MAX_BORROW_RATE());
timeElapsed = bound(timeElapsed, MIN_TIME_ELAPSED, MAX_TIME_ELAPSED);

MarketParams memory marketParams = MarketParams(
address(loanToken), address(collateralToken), address(oracle), address(fixedRateIrm), DEFAULT_TEST_LLTV
);
fixedRateIrm.setBorrowRate(marketParams.id(), fixedRate);
morpho.createMarket(marketParams);

loanToken.setBalance(SUPPLIER, amountSupplied);
vm.prank(SUPPLIER);
morpho.supply(marketParams, amountSupplied, 0, SUPPLIER, hex"");

uint256 collateralAmount = amountBorrowed.wDivUp(DEFAULT_TEST_LLTV);
collateralToken.setBalance(BORROWER, collateralAmount);

vm.startPrank(BORROWER);
morpho.supplyCollateral(marketParams, collateralAmount, BORROWER, hex"");
morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
vm.stopPrank();

vm.warp(timeElapsed);

morpho.accrueInterest(marketParams);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would make sense to test that the accrued intereste are in a certain range (approximate it very roughly). This is because forgetting the line fixedRateIrm.setBorrowRate would still make the test pass, which is a bit loose IMO.
Could do the same thing for the adaptive curve IRM, although finding a rough estimate is a bit harder

}
}
Loading