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

Refactor: use morpho utils #90

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from 5 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
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,6 @@
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
[submodule "lib/morpho-utils"]
path = lib/morpho-utils
url = https://github.com/morpho-dao/morpho-utils
1 change: 1 addition & 0 deletions lib/morpho-utils
Submodule morpho-utils added at 74f194
1 change: 1 addition & 0 deletions remappings.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
solmate/=lib/solmate/src/
morpho-utils/=lib/morpho-utils/src/
35 changes: 19 additions & 16 deletions src/Blue.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {IIrm} from "src/interfaces/IIrm.sol";
import {IERC20} from "src/interfaces/IERC20.sol";
import {IOracle} from "src/interfaces/IOracle.sol";

import {MathLib} from "src/libraries/MathLib.sol";
import {WadRayMath} from "morpho-utils/math/WadRayMath.sol";
import {SafeTransferLib} from "src/libraries/SafeTransferLib.sol";

uint256 constant WAD = 1e18;
Expand All @@ -31,7 +31,7 @@ function toId(Market calldata market) pure returns (Id) {
}

contract Blue {
using MathLib for uint256;
using WadRayMath for uint256;
using SafeTransferLib for IERC20;

// Storage.
Expand Down Expand Up @@ -111,7 +111,7 @@ contract Blue {
supplyShare[id][msg.sender] = WAD;
totalSupplyShares[id] = WAD;
} else {
uint256 shares = amount.wMul(totalSupplyShares[id]).wDiv(totalSupply[id]);
uint256 shares = amount.wadDivDown(totalSupply[id]).wadMulDown(totalSupplyShares[id]);
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering... isn't this basically equivalent to amount.mulDivDown(totalSupplyShares, totalSupply)?

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

why did you change the order @Rubilmax ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@MerlinEgalite discussed here: #90 (comment)

supplyShare[id][msg.sender] += shares;
totalSupplyShares[id] += shares;
}
Expand All @@ -128,7 +128,7 @@ contract Blue {

accrueInterests(market, id);

uint256 shares = amount.wMul(totalSupplyShares[id]).wDiv(totalSupply[id]);
uint256 shares = amount.wadDivUp(totalSupply[id]).wadMulUp(totalSupplyShares[id]);
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
supplyShare[id][msg.sender] -= shares;
totalSupplyShares[id] -= shares;

Expand All @@ -152,7 +152,7 @@ contract Blue {
borrowShare[id][msg.sender] = WAD;
totalBorrowShares[id] = WAD;
} else {
uint256 shares = amount.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
uint256 shares = amount.wadDivUp(totalBorrow[id]).wadMulUp(totalBorrowShares[id]);
borrowShare[id][msg.sender] += shares;
totalBorrowShares[id] += shares;
}
Expand All @@ -172,7 +172,7 @@ contract Blue {

accrueInterests(market, id);

uint256 shares = amount.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
uint256 shares = amount.wadDivDown(totalBorrow[id]).wadMulDown(totalBorrowShares[id]); // TODO: totalBorrow[id] > 0 because ???
MerlinEgalite marked this conversation as resolved.
Show resolved Hide resolved
borrowShare[id][msg.sender] -= shares;
totalBorrowShares[id] -= shares;

Expand Down Expand Up @@ -222,10 +222,11 @@ contract Blue {
require(!isHealthy(market, id, borrower), "cannot liquidate a healthy position");

// The liquidation incentive is 1 + ALPHA * (1 / LLTV - 1).
uint256 incentive = WAD + ALPHA.wMul(WAD.wDiv(market.lltv) - WAD);
uint256 repaid =
seized.wMul(market.collateralOracle.price()).wDiv(incentive).wDiv(market.borrowableOracle.price());
uint256 repaidShares = repaid.wMul(totalBorrowShares[id]).wDiv(totalBorrow[id]);
uint256 incentive = WAD + ALPHA.wadMulDown(WAD.wadDivDown(market.lltv) - WAD);
uint256 repaid = seized.wadMulUp(market.collateralOracle.price()).wadDivUp(incentive).wadDivUp(
market.borrowableOracle.price()
);
uint256 repaidShares = repaid.wadDivDown(totalBorrow[id]).wadMulDown(totalBorrowShares[id]);

borrowShare[id][borrower] -= repaidShares;
totalBorrowShares[id] -= repaidShares;
Expand All @@ -235,7 +236,7 @@ contract Blue {

// Realize the bad debt if needed.
if (collateral[id][borrower] == 0) {
totalSupply[id] -= borrowShare[id][borrower].wMul(totalBorrow[id]).wDiv(totalBorrowShares[id]);
totalSupply[id] -= borrowShare[id][borrower].wadDivUp(totalBorrowShares[id]).wadMulUp(totalBorrow[id]);
totalBorrowShares[id] -= borrowShare[id][borrower];
borrowShare[id][borrower] = 0;
}
Expand All @@ -252,7 +253,7 @@ contract Blue {
if (marketTotalSupply != 0) {
uint256 marketTotalBorrow = totalBorrow[id];
uint256 borrowRate = market.irm.borrowRate(market);
uint256 accruedInterests = marketTotalBorrow.wMul(borrowRate).wMul(block.timestamp - lastUpdate[id]);
uint256 accruedInterests = marketTotalBorrow.wadMulDown(borrowRate * (block.timestamp - lastUpdate[id]));
totalSupply[id] = marketTotalSupply + accruedInterests;
totalBorrow[id] = marketTotalBorrow + accruedInterests;
}
Expand All @@ -265,10 +266,12 @@ contract Blue {
function isHealthy(Market calldata market, Id id, address user) private view returns (bool) {
uint256 borrowShares = borrowShare[id][user];
if (borrowShares == 0) return true;

// totalBorrowShares[id] > 0 when borrowShares > 0.
uint256 borrowValue =
borrowShares.wMul(totalBorrow[id]).wDiv(totalBorrowShares[id]).wMul(market.borrowableOracle.price());
uint256 collateralValue = collateral[id][user].wMul(market.collateralOracle.price());
return collateralValue.wMul(market.lltv) >= borrowValue;
uint256 borrowValue = borrowShares.wadDivUp(totalBorrowShares[id]).wadMulUp(totalBorrow[id]).wadMulUp(
market.borrowableOracle.price()
);
uint256 collateralValue = collateral[id][user].wadMulDown(market.collateralOracle.price());
return collateralValue.wadMulDown(market.lltv) >= borrowValue;
}
}
21 changes: 0 additions & 21 deletions src/libraries/MathLib.sol
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm against using WadRayMath from morpho-utils, as is at least. We don't need Ray maths, and some other things from it. It would prefer to copy only the used functions (strict minimum) for this particular repo.

Copy link
Contributor

@MathisGD MathisGD Jul 10, 2023

Choose a reason for hiding this comment

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

plus, I like the naming wDiv/wMul (rounding to add) as there is no ambiguity in this code

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 rename function from a lib instead of copy pasting

This file was deleted.

6 changes: 3 additions & 3 deletions src/mocks/IrmMock.sol
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.20;

import {MathLib} from "src/libraries/MathLib.sol";
import {WadRayMath} from "morpho-utils/math/WadRayMath.sol";

import "src/Blue.sol";

contract IrmMock is IIrm {
using MathLib for uint256;
using WadRayMath for uint256;

Blue public immutable blue;

Expand All @@ -16,7 +16,7 @@ contract IrmMock is IIrm {

function borrowRate(Market calldata market) external view returns (uint256) {
Id id = Id.wrap(keccak256(abi.encode(market)));
uint256 utilization = blue.totalBorrow(id).wDiv(blue.totalSupply(id));
uint256 utilization = blue.totalBorrow(id).wadDivDown(blue.totalSupply(id));

// Divide by the number of seconds in a year.
// This is a very simple model (to refine later) where x% utilization corresponds to x% APR.
Expand Down
48 changes: 27 additions & 21 deletions test/forge/Blue.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ pragma solidity 0.8.20;
import {IERC20} from "src/interfaces/IERC20.sol";
import {IOracle} from "src/interfaces/IOracle.sol";

import {WadRayMath} from "morpho-utils/math/WadRayMath.sol";

import "forge-std/Test.sol";
import "forge-std/console.sol";

Expand All @@ -13,7 +15,7 @@ import {OracleMock as Oracle} from "src/mocks/OracleMock.sol";
import {IrmMock as Irm} from "src/mocks/IrmMock.sol";

contract BlueTest is Test {
using MathLib for uint256;
using WadRayMath for uint256;

address private constant BORROWER = address(1234);
address private constant LIQUIDATOR = address(5678);
Expand Down Expand Up @@ -76,25 +78,27 @@ contract BlueTest is Test {
// To move to a test utils file later.

function netWorth(address user) internal view returns (uint256) {
uint256 collateralAssetValue = collateralAsset.balanceOf(user).wMul(collateralOracle.price());
uint256 borrowableAssetValue = borrowableAsset.balanceOf(user).wMul(borrowableOracle.price());
uint256 collateralAssetValue = collateralAsset.balanceOf(user).wadMulDown(collateralOracle.price());
uint256 borrowableAssetValue = borrowableAsset.balanceOf(user).wadMulDown(borrowableOracle.price());
return collateralAssetValue + borrowableAssetValue;
}

function supplyBalance(address user) internal view returns (uint256) {
uint256 supplyShares = blue.supplyShare(id, user);
if (supplyShares == 0) return 0;

uint256 totalShares = blue.totalSupplyShares(id);
uint256 totalSupply = blue.totalSupply(id);
return supplyShares.wMul(totalSupply).wDiv(totalShares);
return supplyShares.wadMulDown(totalSupply).wadDivDown(totalShares);
}

function borrowBalance(address user) internal view returns (uint256) {
uint256 borrowerShares = blue.borrowShare(id, user);
if (borrowerShares == 0) return 0;

uint256 totalShares = blue.totalBorrowShares(id);
uint256 totalBorrow = blue.totalBorrow(id);
return borrowerShares.wMul(totalBorrow).wDiv(totalShares);
return borrowerShares.wadMulUp(totalBorrow).wadDivUp(totalShares);
}

// Invariants
Expand Down Expand Up @@ -292,9 +296,9 @@ contract BlueTest is Test {
vm.prank(BORROWER);
blue.supplyCollateral(market, amountCollateral);

uint256 collateralValue = amountCollateral.wMul(priceCollateral);
uint256 borrowValue = amountBorrowed.wMul(priceBorrowable);
if (borrowValue == 0 || (collateralValue > 0 && borrowValue <= collateralValue.wMul(LLTV))) {
uint256 collateralValue = amountCollateral.wadMulDown(priceCollateral);
uint256 borrowValue = amountBorrowed.wadMulUp(priceBorrowable);
if (borrowValue == 0 || (collateralValue > 0 && borrowValue <= collateralValue.wadMulDown(LLTV))) {
vm.prank(BORROWER);
blue.borrow(market, amountBorrowed);
} else {
Expand Down Expand Up @@ -360,10 +364,10 @@ contract BlueTest is Test {
amountLent = bound(amountLent, 1000, 2 ** 64);

uint256 amountCollateral = amountLent;
uint256 borrowingPower = amountCollateral.wMul(LLTV);
uint256 amountBorrowed = borrowingPower.wMul(0.8e18);
uint256 toSeize = amountCollateral.wMul(LLTV);
uint256 incentive = WAD + ALPHA.wMul(WAD.wDiv(LLTV) - WAD);
uint256 borrowingPower = amountCollateral.wadMulDown(LLTV);
uint256 amountBorrowed = borrowingPower.wadMulDown(0.8e18);
uint256 toSeize = amountCollateral.wadMulDown(LLTV);
uint256 incentive = WAD + ALPHA.wadMulDown(WAD.wadDivDown(LLTV) - WAD);

borrowableAsset.setBalance(address(this), amountLent);
collateralAsset.setBalance(BORROWER, amountCollateral);
Expand All @@ -389,9 +393,10 @@ contract BlueTest is Test {

uint256 liquidatorNetWorthAfter = netWorth(LIQUIDATOR);

uint256 expectedRepaid = toSeize.wMul(collateralOracle.price()).wDiv(incentive).wDiv(borrowableOracle.price());
uint256 expectedNetWorthAfter = liquidatorNetWorthBefore + toSeize.wMul(collateralOracle.price())
- expectedRepaid.wMul(borrowableOracle.price());
uint256 expectedRepaid =
toSeize.wadMulUp(collateralOracle.price()).wadDivUp(incentive).wadDivUp(borrowableOracle.price());
uint256 expectedNetWorthAfter = liquidatorNetWorthBefore + toSeize.wadMulDown(collateralOracle.price())
- expectedRepaid.wadMulDown(borrowableOracle.price());
assertEq(liquidatorNetWorthAfter, expectedNetWorthAfter, "LIQUIDATOR net worth");
assertApproxEqAbs(borrowBalance(BORROWER), amountBorrowed - expectedRepaid, 100, "BORROWER balance");
assertEq(blue.collateral(id, BORROWER), amountCollateral - toSeize, "BORROWER collateral");
Expand All @@ -402,10 +407,10 @@ contract BlueTest is Test {
amountLent = bound(amountLent, 1000, 2 ** 64);

uint256 amountCollateral = amountLent;
uint256 borrowingPower = amountCollateral.wMul(LLTV);
uint256 amountBorrowed = borrowingPower.wMul(0.8e18);
uint256 borrowingPower = amountCollateral.wadMulDown(LLTV);
uint256 amountBorrowed = borrowingPower.wadMulDown(0.8e18);
uint256 toSeize = amountCollateral;
uint256 incentive = WAD + ALPHA.wMul(WAD.wDiv(market.lltv) - WAD);
uint256 incentive = WAD + ALPHA.wadMulDown(WAD.wadDivDown(market.lltv) - WAD);

borrowableAsset.setBalance(address(this), amountLent);
collateralAsset.setBalance(BORROWER, amountCollateral);
Expand All @@ -431,9 +436,10 @@ contract BlueTest is Test {

uint256 liquidatorNetWorthAfter = netWorth(LIQUIDATOR);

uint256 expectedRepaid = toSeize.wMul(collateralOracle.price()).wDiv(incentive).wDiv(borrowableOracle.price());
uint256 expectedNetWorthAfter = liquidatorNetWorthBefore + toSeize.wMul(collateralOracle.price())
- expectedRepaid.wMul(borrowableOracle.price());
uint256 expectedRepaid =
toSeize.wadMulUp(collateralOracle.price()).wadDivUp(incentive).wadDivUp(borrowableOracle.price());
uint256 expectedNetWorthAfter = liquidatorNetWorthBefore + toSeize.wadMulDown(collateralOracle.price())
- expectedRepaid.wadMulDown(borrowableOracle.price());
assertEq(liquidatorNetWorthAfter, expectedNetWorthAfter, "LIQUIDATOR net worth");
assertEq(borrowBalance(BORROWER), 0, "BORROWER balance");
assertEq(blue.collateral(id, BORROWER), 0, "BORROWER collateral");
Expand Down