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

Use asset values instead of asset prices in oracle calls #540

Closed
wants to merge 2 commits into from
Closed
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
43 changes: 19 additions & 24 deletions src/Morpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
{
Expand All @@ -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)
);
}
}

Expand Down Expand Up @@ -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;
}
Expand Down
3 changes: 3 additions & 0 deletions src/interfaces/IOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
18 changes: 17 additions & 1 deletion src/mocks/OracleMock.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
2 changes: 1 addition & 1 deletion test/forge/BaseTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
12 changes: 6 additions & 6 deletions test/forge/integration/BorrowIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand All @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down
8 changes: 4 additions & 4 deletions test/forge/integration/CallbacksIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand All @@ -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);
Expand All @@ -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"");
Expand Down
14 changes: 7 additions & 7 deletions test/forge/integration/LiquidateIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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));
Expand Down
4 changes: 2 additions & 2 deletions test/forge/integration/RepayIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
6 changes: 3 additions & 3 deletions test/forge/integration/WithdrawCollateralIntegrationTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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,
Expand Down
2 changes: 1 addition & 1 deletion test/forge/invariant/MorphoInvariantTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
Loading