From 1c0d4d24795340dfb4fe7176b249c69c3eeae263 Mon Sep 17 00:00:00 2001 From: alcueca Date: Fri, 13 Oct 2023 13:39:32 +0100 Subject: [PATCH] compiles, health factor seems ok, liquidations are off --- src/Morpho.sol | 43 ++++++++----------- src/interfaces/IOracle.sol | 3 ++ src/mocks/OracleMock.sol | 18 +++++++- test/forge/BaseTest.sol | 2 +- .../integration/BorrowIntegrationTest.sol | 12 +++--- .../integration/CallbacksIntegrationTest.sol | 8 ++-- .../integration/LiquidateIntegrationTest.sol | 14 +++--- .../integration/RepayIntegrationTest.sol | 4 +- .../WithdrawCollateralIntegrationTest.sol | 6 +-- test/forge/invariant/MorphoInvariantTest.sol | 2 +- 10 files changed, 63 insertions(+), 49 deletions(-) diff --git a/src/Morpho.sol b/src/Morpho.sol index 0f24d1a9f..22fbf972e 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -344,9 +344,7 @@ contract Morpho is IMorpho { _accrueInterest(marketParams, id); - uint256 collateralPrice = IOracle(marketParams.oracle).price(); - - require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION); + require(!_isHealthy(marketParams, id, borrower), ErrorsLib.HEALTHY_POSITION); uint256 repaidAssets; { @@ -357,13 +355,19 @@ contract Morpho is IMorpho { ); if (seizedAssets > 0) { - repaidAssets = - seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor); + repaidAssets = IOracle(marketParams.oracle).value( + marketParams.collateralToken, + marketParams.loanToken, + seizedAssets + ).wDivUp(liquidationIncentiveFactor); repaidShares = repaidAssets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); } else { repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares); - seizedAssets = - repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice); + seizedAssets = IOracle(marketParams.oracle).value( + marketParams.loanToken, + marketParams.collateralToken, + repaidAssets.wMulDown(liquidationIncentiveFactor) + ); } } @@ -480,28 +484,19 @@ contract Morpho is IMorpho { /// @dev Returns whether the position of `borrower` in the given market `marketParams` is healthy. /// @dev Assumes that the inputs `marketParams` and `id` match. + /// @dev Rounds in favor of the protocol, so one might not be able to borrow exactly `maxBorrow` but one unit less. function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) { if (position[id][borrower].borrowShares == 0) return true; - - uint256 collateralPrice = IOracle(marketParams.oracle).price(); - - return _isHealthy(marketParams, id, borrower, collateralPrice); - } - - /// @dev Returns whether the position of `borrower` in the given market `marketParams` with the given - /// `collateralPrice` is healthy. - /// @dev Assumes that the inputs `marketParams` and `id` match. - /// @dev Rounds in favor of the protocol, so one might not be able to borrow exactly `maxBorrow` but one unit less. - function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice) - internal - view - returns (bool) - { uint256 borrowed = uint256(position[id][borrower].borrowShares).toAssetsUp( market[id].totalBorrowAssets, market[id].totalBorrowShares ); - uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) - .wMulDown(marketParams.lltv); + + uint256 collateralValue = IOracle(marketParams.oracle).value( + marketParams.collateralToken, + marketParams.loanToken, + uint256(position[id][borrower].collateral + )); + uint256 maxBorrow = collateralValue.wMulDown(marketParams.lltv); return maxBorrow >= borrowed; } diff --git a/src/interfaces/IOracle.sol b/src/interfaces/IOracle.sol index 576230b5b..1115818b8 100644 --- a/src/interfaces/IOracle.sol +++ b/src/interfaces/IOracle.sol @@ -11,4 +11,7 @@ interface IOracle { /// 10**(loan token decimals) assets of loan token with `36 + loan token decimals - collateral token decimals` /// decimals of precision. function price() external view returns (uint256); + + /// @notice Returns the value of an amount of base token in terms of quote token. + function value(address baseToken, address quoteToken, uint256 baseAmount) external view returns (uint256 quoteAmount); } diff --git a/src/mocks/OracleMock.sol b/src/mocks/OracleMock.sol index fb0b06271..2bb3f6877 100644 --- a/src/mocks/OracleMock.sol +++ b/src/mocks/OracleMock.sol @@ -2,11 +2,27 @@ pragma solidity ^0.8.0; import {IOracle} from "../interfaces/IOracle.sol"; +import {SharesMathLib} from "../libraries/SharesMathLib.sol"; +import {MathLib} from "../libraries/MathLib.sol"; +import "../libraries/ConstantsLib.sol"; contract OracleMock is IOracle { + using SharesMathLib for uint256; + using MathLib for uint256; + + address public loanToken; + address public collateralToken; uint256 public price; - function setPrice(uint256 newPrice) external { + function setPrice(address newLoanToken, address newCollateralToken, uint256 newPrice) external { + loanToken = newLoanToken; + collateralToken = newCollateralToken; price = newPrice; } + + // In _isHealthy, we value collateral tokens in loan token terms, so collateral is base and loan is quote. + function value(address baseToken, address quoteToken, uint256 baseAmount) public view override returns (uint256 quoteAmount) { + if (baseToken == loanToken && quoteToken == collateralToken) quoteAmount = price.mulDivDown(ORACLE_PRICE_SCALE, baseAmount); + if (baseToken == collateralToken && quoteToken == loanToken) quoteAmount = baseAmount.mulDivDown(price, ORACLE_PRICE_SCALE); + } } diff --git a/test/forge/BaseTest.sol b/test/forge/BaseTest.sol index 3a59787a0..36454e853 100644 --- a/test/forge/BaseTest.sol +++ b/test/forge/BaseTest.sol @@ -76,7 +76,7 @@ contract BaseTest is Test { oracle = new OracleMock(); - oracle.setPrice(ORACLE_PRICE_SCALE); + oracle.setPrice(address(loanToken), address(collateralToken), ORACLE_PRICE_SCALE); irm = new IrmMock(); diff --git a/test/forge/integration/BorrowIntegrationTest.sol b/test/forge/integration/BorrowIntegrationTest.sol index 1733c7872..86ff7e67d 100644 --- a/test/forge/integration/BorrowIntegrationTest.sol +++ b/test/forge/integration/BorrowIntegrationTest.sol @@ -72,7 +72,7 @@ contract BorrowIntegrationTest is BaseTest { amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT); _supply(amountSupplied); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); collateralToken.setBalance(BORROWER, amountCollateral); @@ -95,7 +95,7 @@ contract BorrowIntegrationTest is BaseTest { amountSupplied = bound(amountSupplied, 1, amountBorrowed - 1); _supply(amountSupplied); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); collateralToken.setBalance(BORROWER, amountCollateral); @@ -118,7 +118,7 @@ contract BorrowIntegrationTest is BaseTest { amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT); _supply(amountSupplied); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); collateralToken.setBalance(BORROWER, amountCollateral); @@ -161,7 +161,7 @@ contract BorrowIntegrationTest is BaseTest { amountSupplied = bound(amountSupplied, expectedAmountBorrowed, MAX_TEST_AMOUNT); _supply(amountSupplied); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); collateralToken.setBalance(BORROWER, amountCollateral); @@ -195,7 +195,7 @@ contract BorrowIntegrationTest is BaseTest { amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT); _supply(amountSupplied); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); collateralToken.setBalance(ONBEHALF, amountCollateral); @@ -241,7 +241,7 @@ contract BorrowIntegrationTest is BaseTest { amountSupplied = bound(amountSupplied, expectedAmountBorrowed, MAX_TEST_AMOUNT); _supply(amountSupplied); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); collateralToken.setBalance(ONBEHALF, amountCollateral); diff --git a/test/forge/integration/CallbacksIntegrationTest.sol b/test/forge/integration/CallbacksIntegrationTest.sol index 62578aa19..48e6c835e 100644 --- a/test/forge/integration/CallbacksIntegrationTest.sol +++ b/test/forge/integration/CallbacksIntegrationTest.sol @@ -126,7 +126,7 @@ contract CallbacksIntegrationTest is uint256 collateralAmount; (collateralAmount, loanAmount,) = _boundHealthyPosition(0, loanAmount, oracle.price()); - oracle.setPrice(ORACLE_PRICE_SCALE); + oracle.setPrice(address(loanToken), address(collateralToken), ORACLE_PRICE_SCALE); loanToken.setBalance(address(this), loanAmount); collateralToken.setBalance(address(this), collateralAmount); @@ -147,7 +147,7 @@ contract CallbacksIntegrationTest is uint256 collateralAmount; (collateralAmount, loanAmount,) = _boundHealthyPosition(0, loanAmount, oracle.price()); - oracle.setPrice(ORACLE_PRICE_SCALE); + oracle.setPrice(address(loanToken), address(collateralToken), ORACLE_PRICE_SCALE); loanToken.setBalance(address(this), loanAmount); collateralToken.setBalance(address(this), collateralAmount); @@ -156,7 +156,7 @@ contract CallbacksIntegrationTest is morpho.supplyCollateral(marketParams, collateralAmount, address(this), hex""); morpho.borrow(marketParams, loanAmount, 0, address(this), address(this)); - oracle.setPrice(0.99e18); + oracle.setPrice(address(loanToken), address(collateralToken), 0.99e18); loanToken.setBalance(address(this), loanAmount); loanToken.approve(address(morpho), 0); @@ -173,7 +173,7 @@ contract CallbacksIntegrationTest is uint256 collateralAmount; (collateralAmount, loanAmount,) = _boundHealthyPosition(0, loanAmount, oracle.price()); - oracle.setPrice(ORACLE_PRICE_SCALE); + oracle.setPrice(address(loanToken), address(collateralToken), ORACLE_PRICE_SCALE); loanToken.setBalance(address(this), loanAmount); morpho.supply(marketParams, loanAmount, 0, address(this), hex""); diff --git a/test/forge/integration/LiquidateIntegrationTest.sol b/test/forge/integration/LiquidateIntegrationTest.sol index 9bd12bc3e..42d5c1eb9 100644 --- a/test/forge/integration/LiquidateIntegrationTest.sol +++ b/test/forge/integration/LiquidateIntegrationTest.sol @@ -51,7 +51,7 @@ contract LiquidateIntegrationTest is BaseTest { amountSeized = bound(amountSeized, 1, amountCollateral); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); loanToken.setBalance(LIQUIDATOR, amountBorrowed); collateralToken.setBalance(BORROWER, amountCollateral); @@ -94,14 +94,14 @@ contract LiquidateIntegrationTest is BaseTest { loanToken.setBalance(LIQUIDATOR, amountBorrowed); collateralToken.setBalance(BORROWER, amountCollateral); - oracle.setPrice(type(uint256).max / amountCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), type(uint256).max / amountCollateral); vm.startPrank(BORROWER); morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex""); morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER); vm.stopPrank(); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); uint256 expectedRepaidShares = expectedRepaid.toSharesDown(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id)); @@ -162,14 +162,14 @@ contract LiquidateIntegrationTest is BaseTest { loanToken.setBalance(LIQUIDATOR, amountBorrowed); collateralToken.setBalance(BORROWER, amountCollateral); - oracle.setPrice(type(uint256).max / amountCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), type(uint256).max / amountCollateral); vm.startPrank(BORROWER); morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex""); morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER); vm.stopPrank(); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); vm.prank(LIQUIDATOR); @@ -235,14 +235,14 @@ contract LiquidateIntegrationTest is BaseTest { loanToken.setBalance(LIQUIDATOR, amountBorrowed); collateralToken.setBalance(BORROWER, amountCollateral); - oracle.setPrice(type(uint256).max / amountCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), type(uint256).max / amountCollateral); vm.startPrank(BORROWER); morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex""); morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER); vm.stopPrank(); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); params.expectedRepaidShares = params.expectedRepaid.toSharesDown(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id)); diff --git a/test/forge/integration/RepayIntegrationTest.sol b/test/forge/integration/RepayIntegrationTest.sol index dffc67bd4..09fe2d3ee 100644 --- a/test/forge/integration/RepayIntegrationTest.sol +++ b/test/forge/integration/RepayIntegrationTest.sol @@ -47,7 +47,7 @@ contract RepayIntegrationTest is BaseTest { amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT); _supply(amountSupplied); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); amountRepaid = bound(amountRepaid, 1, amountBorrowed); uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0); @@ -90,7 +90,7 @@ contract RepayIntegrationTest is BaseTest { amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT); _supply(amountSupplied); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0); sharesRepaid = bound(sharesRepaid, 1, expectedBorrowShares); diff --git a/test/forge/integration/WithdrawCollateralIntegrationTest.sol b/test/forge/integration/WithdrawCollateralIntegrationTest.sol index 8a5b0ce58..c3d5512c8 100644 --- a/test/forge/integration/WithdrawCollateralIntegrationTest.sol +++ b/test/forge/integration/WithdrawCollateralIntegrationTest.sol @@ -68,7 +68,7 @@ contract WithdrawCollateralIntegrationTest is BaseTest { amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT); _supply(amountSupplied); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); collateralToken.setBalance(BORROWER, amountCollateral); @@ -100,7 +100,7 @@ contract WithdrawCollateralIntegrationTest is BaseTest { Math.min(MAX_COLLATERAL_ASSETS - amountCollateral, type(uint256).max / priceCollateral - amountCollateral) ); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); collateralToken.setBalance(BORROWER, amountCollateral + amountCollateralExcess); @@ -133,7 +133,7 @@ contract WithdrawCollateralIntegrationTest is BaseTest { amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT); _supply(amountSupplied); - oracle.setPrice(priceCollateral); + oracle.setPrice(address(loanToken), address(collateralToken), priceCollateral); amountCollateralExcess = bound( amountCollateralExcess, diff --git a/test/forge/invariant/MorphoInvariantTest.sol b/test/forge/invariant/MorphoInvariantTest.sol index edb423473..8797b9e53 100644 --- a/test/forge/invariant/MorphoInvariantTest.sol +++ b/test/forge/invariant/MorphoInvariantTest.sol @@ -194,7 +194,7 @@ contract MorphoInvariantTest is InvariantTest { function setPrice(uint256 price) external { price = bound(price, MIN_PRICE, MAX_PRICE); - oracle.setPrice(price); + oracle.setPrice(address(loanToken), address(collateralToken), price); } function setFeeNoRevert(uint256 marketSeed, uint256 newFee) external {