diff --git a/src/Morpho.sol b/src/Morpho.sol index 0f24d1a9f..c03fcda19 100644 --- a/src/Morpho.sol +++ b/src/Morpho.sol @@ -1,7 +1,16 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.19; -import {Id, IMorpho, MarketParams, Position, Market, Authorization, Signature} from "./interfaces/IMorpho.sol"; +import { + Id, + IMorphoStaticTyping, + IMorphoBase, + MarketParams, + Position, + Market, + Authorization, + Signature +} from "./interfaces/IMorpho.sol"; import { IMorphoLiquidateCallback, IMorphoRepayCallback, @@ -26,7 +35,7 @@ import {SafeTransferLib} from "./libraries/SafeTransferLib.sol"; /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice The Morpho contract. -contract Morpho is IMorpho { +contract Morpho is IMorphoStaticTyping { using MathLib for uint128; using MathLib for uint256; using UtilsLib for uint256; @@ -36,39 +45,40 @@ contract Morpho is IMorpho { /* IMMUTABLES */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase bytes32 public immutable DOMAIN_SEPARATOR; /* STORAGE */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase address public owner; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase address public feeRecipient; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoStaticTyping mapping(Id => mapping(address => Position)) public position; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoStaticTyping mapping(Id => Market) public market; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase mapping(address => bool) public isIrmEnabled; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase mapping(uint256 => bool) public isLltvEnabled; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase mapping(address => mapping(address => bool)) public isAuthorized; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase mapping(address => uint256) public nonce; - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoStaticTyping mapping(Id => MarketParams) public idToMarketParams; /* CONSTRUCTOR */ - /// @notice Constructs the contract. /// @param newOwner The new owner of the contract. constructor(address newOwner) { require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS); - owner = newOwner; DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this))); + owner = newOwner; + + emit EventsLib.SetOwner(newOwner); } /* MODIFIERS */ @@ -81,7 +91,7 @@ contract Morpho is IMorpho { /* ONLY OWNER FUNCTIONS */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function setOwner(address newOwner) external onlyOwner { require(newOwner != owner, ErrorsLib.ALREADY_SET); @@ -90,7 +100,7 @@ contract Morpho is IMorpho { emit EventsLib.SetOwner(newOwner); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function enableIrm(address irm) external onlyOwner { require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET); @@ -99,7 +109,7 @@ contract Morpho is IMorpho { emit EventsLib.EnableIrm(irm); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function enableLltv(uint256 lltv) external onlyOwner { require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET); require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED); @@ -109,7 +119,7 @@ contract Morpho is IMorpho { emit EventsLib.EnableLltv(lltv); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner { Id id = marketParams.id(); require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); @@ -125,7 +135,7 @@ contract Morpho is IMorpho { emit EventsLib.SetFee(id, newFee); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function setFeeRecipient(address newFeeRecipient) external onlyOwner { require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET); @@ -136,7 +146,7 @@ contract Morpho is IMorpho { /* MARKET CREATION */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function createMarket(MarketParams memory marketParams) external { Id id = marketParams.id(); require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED); @@ -152,7 +162,7 @@ contract Morpho is IMorpho { /* SUPPLY MANAGEMENT */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function supply( MarketParams memory marketParams, uint256 assets, @@ -183,7 +193,7 @@ contract Morpho is IMorpho { return (assets, shares); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function withdraw( MarketParams memory marketParams, uint256 assets, @@ -194,8 +204,8 @@ contract Morpho is IMorpho { Id id = marketParams.id(); require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT); - // No need to verify that onBehalf != address(0) thanks to the authorization check. require(receiver != address(0), ErrorsLib.ZERO_ADDRESS); + // No need to verify that onBehalf != address(0) thanks to the following authorization check. require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED); _accrueInterest(marketParams, id); @@ -218,7 +228,7 @@ contract Morpho is IMorpho { /* BORROW MANAGEMENT */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function borrow( MarketParams memory marketParams, uint256 assets, @@ -229,8 +239,8 @@ contract Morpho is IMorpho { Id id = marketParams.id(); require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT); - // No need to verify that onBehalf != address(0) thanks to the authorization check. require(receiver != address(0), ErrorsLib.ZERO_ADDRESS); + // No need to verify that onBehalf != address(0) thanks to the following authorization check. require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED); _accrueInterest(marketParams, id); @@ -252,7 +262,7 @@ contract Morpho is IMorpho { return (assets, shares); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function repay( MarketParams memory marketParams, uint256 assets, @@ -286,7 +296,7 @@ contract Morpho is IMorpho { /* COLLATERAL MANAGEMENT */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data) external { @@ -306,15 +316,15 @@ contract Morpho is IMorpho { IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver) external { Id id = marketParams.id(); require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); require(assets != 0, ErrorsLib.ZERO_ASSETS); - // No need to verify that onBehalf != address(0) thanks to the authorization check. require(receiver != address(0), ErrorsLib.ZERO_ADDRESS); + // No need to verify that onBehalf != address(0) thanks to the following authorization check. require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED); _accrueInterest(marketParams, id); @@ -330,7 +340,7 @@ contract Morpho is IMorpho { /* LIQUIDATION */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function liquidate( MarketParams memory marketParams, address borrower, @@ -377,9 +387,13 @@ contract Morpho is IMorpho { uint256 badDebtShares; if (position[id][borrower].collateral == 0) { badDebtShares = position[id][borrower].borrowShares; - uint256 badDebt = badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares); - market[id].totalSupplyAssets -= badDebt.toUint128(); + uint256 badDebt = UtilsLib.min( + market[id].totalBorrowAssets, + badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares) + ); + market[id].totalBorrowAssets -= badDebt.toUint128(); + market[id].totalSupplyAssets -= badDebt.toUint128(); market[id].totalBorrowShares -= badDebtShares.toUint128(); position[id][borrower].borrowShares = 0; } @@ -398,7 +412,7 @@ contract Morpho is IMorpho { /* FLASH LOANS */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function flashLoan(address token, uint256 assets, bytes calldata data) external { IERC20(token).safeTransfer(msg.sender, assets); @@ -411,16 +425,16 @@ contract Morpho is IMorpho { /* AUTHORIZATION */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function setAuthorization(address authorized, bool newIsAuthorized) external { isAuthorized[msg.sender][authorized] = newIsAuthorized; emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized); } - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external { - require(block.timestamp < authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED); + require(block.timestamp <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED); require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE); bytes32 hashStruct = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization)); @@ -445,6 +459,14 @@ contract Morpho is IMorpho { /* INTEREST MANAGEMENT */ + /// @inheritdoc IMorphoBase + function accrueInterest(MarketParams memory marketParams) external { + Id id = marketParams.id(); + require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED); + + _accrueInterest(marketParams, id); + } + /// @dev Accrues interest for the given market `marketParams`. /// @dev Assumes that the inputs `marketParams` and `id` match. function _accrueInterest(MarketParams memory marketParams, Id id) internal { @@ -452,26 +474,23 @@ contract Morpho is IMorpho { if (elapsed == 0) return; - if (market[id].totalBorrowAssets != 0) { - uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]); - uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed)); - market[id].totalBorrowAssets += interest.toUint128(); - market[id].totalSupplyAssets += interest.toUint128(); - - uint256 feeShares; - if (market[id].fee != 0) { - uint256 feeAmount = interest.wMulDown(market[id].fee); - // The fee amount is subtracted from the total supply in this calculation to compensate for the fact - // that total supply is already increased by the full interest (including the fee amount). - feeShares = - feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares); - position[id][feeRecipient].supplyShares += feeShares; - market[id].totalSupplyShares += feeShares.toUint128(); - } - - emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares); + uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]); + uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed)); + market[id].totalBorrowAssets += interest.toUint128(); + market[id].totalSupplyAssets += interest.toUint128(); + + uint256 feeShares; + if (market[id].fee != 0) { + uint256 feeAmount = interest.wMulDown(market[id].fee); + // The fee amount is subtracted from the total supply in this calculation to compensate for the fact + // that total supply is already increased by the full interest (including the fee amount). + feeShares = feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares); + position[id][feeRecipient].supplyShares += feeShares; + market[id].totalSupplyShares += feeShares.toUint128(); } + emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares); + // Safe "unchecked" cast. market[id].lastUpdate = uint128(block.timestamp); } @@ -508,7 +527,7 @@ contract Morpho is IMorpho { /* STORAGE VIEW */ - /// @inheritdoc IMorpho + /// @inheritdoc IMorphoBase function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) { uint256 nSlots = slots.length; diff --git a/src/interfaces/IIrm.sol b/src/interfaces/IIrm.sol index 2615e3a7b..db2aaf873 100644 --- a/src/interfaces/IIrm.sol +++ b/src/interfaces/IIrm.sol @@ -9,12 +9,10 @@ import {MarketParams, Market} from "./IMorpho.sol"; /// @notice Interface that Interest Rate Models (IRMs) used by Morpho must implement. interface IIrm { /// @notice Returns the borrow rate of the market `marketParams`. - /// @param marketParams The MarketParams struct of the market. - /// @param market The Market struct of the market. + /// @dev Assumes that `market` corresponds to `marketParams`. function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256); /// @notice Returns the borrow rate of the market `marketParams` without modifying any storage. - /// @param marketParams The MarketParams struct of the market. - /// @param market The Market struct of the market. + /// @dev Assumes that `market` corresponds to `marketParams`. function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256); } diff --git a/src/interfaces/IMorpho.sol b/src/interfaces/IMorpho.sol index 02aa4e204..781a32f0a 100644 --- a/src/interfaces/IMorpho.sol +++ b/src/interfaces/IMorpho.sol @@ -46,14 +46,12 @@ struct Signature { bytes32 s; } -/// @title IMorpho -/// @author Morpho Labs -/// @custom:contact security@morpho.org -/// @notice Interface of Morpho. -interface IMorpho { +/// @dev This interface is used for factorizing IMorphoStaticTyping and IMorpho. +/// @dev Consider using the IMorpho interface instead of this one. +interface IMorphoBase { /// @notice The EIP-712 domain separator. - /// @dev Warning: In case of a hardfork, every EIP-712 signed message based on this domain separator can be reused - /// on the forked chain because the domain separator would be the same. + /// @dev Warning: Every EIP-712 signed message based on this domain separator can be reused on another chain sharing + /// the same chain id because the domain separator would be the same. function DOMAIN_SEPARATOR() external view returns (bytes32); /// @notice The owner of the contract. @@ -66,25 +64,6 @@ interface IMorpho { /// @dev The recipient receives the fees of a given market through a supply position on that market. function feeRecipient() external view returns (address); - /// @notice The state of the position of `user` on the market corresponding to `id`. - function position(Id id, address user) - external - view - returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral); - - /// @notice The state of the market corresponding to `id`. - function market(Id id) - external - view - returns ( - uint128 totalSupplyAssets, - uint128 totalSupplyShares, - uint128 totalBorrowAssets, - uint128 totalBorrowShares, - uint128 lastUpdate, - uint128 fee - ); - /// @notice Whether the `irm` is enabled. function isIrmEnabled(address irm) external view returns (bool); @@ -98,15 +77,7 @@ interface IMorpho { /// @notice The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures. function nonce(address authorizer) external view returns (uint256); - /// @notice The market params corresponding to `id`. - /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer - /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`. - function idToMarketParams(Id id) - external - view - returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv); - - /// @notice Sets `newOwner` as owner of the contract. + /// @notice Sets `newOwner` as `owner` of the contract. /// @dev Warning: No two-step transfer ownership. /// @dev Warning: The owner can be set to the zero address. function setOwner(address newOwner) external; @@ -123,10 +94,10 @@ interface IMorpho { /// @dev Warning: The recipient can be the zero address. function setFee(MarketParams memory marketParams, uint256 newFee) external; - /// @notice Sets `newFeeRecipient` as recipient of the fee. - /// @dev Warning: The fee recipient can be set to the zero address. - /// @dev Warning: The fee to be accrued on each market won't belong to the old fee recipient after calling this - /// function. + /// @notice Sets `newFeeRecipient` as `feeRecipient` of the fee. + /// @dev Warning: If the fee recipient is set to the zero address, fees will accrue there and will be lost. + /// @dev Modifying the fee recipient will allow the new recipient to claim any pending fees not yet accrued. To + /// ensure that the current recipient receives all due fees, accrue interest manually prior to making any changes. function setFeeRecipient(address newFeeRecipient) external; /// @notice Creates the market `marketParams`. @@ -139,7 +110,9 @@ interface IMorpho { /// - The token balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount /// on `transfer` and `transferFrom`. In particular, tokens with fees on transfer are not supported. /// - The IRM should not re-enter Morpho. - /// @dev Here is a list of properties on the market's dependencies that could break Morpho's liveness properties: + /// - The oracle should return a price with the correct scaling. + /// @dev Here is a list of properties on the market's dependencies that could break Morpho's liveness properties + /// (funds could get stuck): /// - The token can revert on `transfer` and `transferFrom` for a reason other than an approval or balance issue. /// - A very high amount of assets (~1e35) supplied or borrowed can make the computation of `toSharesUp` and /// `toSharesDown` overflow. @@ -177,6 +150,8 @@ interface IMorpho { /// @dev Either `assets` or `shares` should be zero. To withdraw max, pass the `shares`'s balance of `onBehalf`. /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @dev Withdrawing an amount corresponding to more shares than supplied will revert for underflow. + /// @dev It is advised to use the `shares` input when withdrawing the full position to avoid reverts due to + /// conversion roundings between shares and assets. /// @param marketParams The market to withdraw assets from. /// @param assets The amount of assets to withdraw. /// @param shares The amount of shares to burn. @@ -217,6 +192,8 @@ interface IMorpho { /// `onMorphoReplay` function with the given `data`. /// @dev Either `assets` or `shares` should be zero. To repay max, pass the `shares`'s balance of `onBehalf`. /// @dev Repaying an amount corresponding to more shares than borrowed will revert for underflow. + /// @dev It is advised to use the `shares` input when repaying the full position to avoid reverts due to conversion + /// roundings between shares and assets. /// @param marketParams The market to repay assets to. /// @param assets The amount of assets to repay. /// @param shares The amount of shares to burn. @@ -277,6 +254,10 @@ interface IMorpho { /// @notice Executes a flash loan. /// @dev Flash loans have access to the whole balance of the contract (the liquidity and deposited collateral of all /// markets combined, plus donations). + /// @dev Warning: Not ERC-3156 compliant but compatibility is easily reached: + /// - `flashFee` is zero. + /// - `maxFlashLoan` is the token's balance of this contract. + /// - The receiver of `assets` is the caller. /// @param token The token to flash loan. /// @param assets The amount of assets to flash loan. /// @param data Arbitrary data to pass to the `onMorphoFlashLoan` callback. @@ -295,6 +276,69 @@ interface IMorpho { /// @param signature The signature. function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external; + /// @notice Accrues interest for the given market `marketParams`. + function accrueInterest(MarketParams memory marketParams) external; + /// @notice Returns the data stored on the different `slots`. function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory); } + +/// @dev This interface is inherited by Morpho so that function signatures are checked by the compiler. +/// @dev Consider using the IMorpho interface instead of this one. +interface IMorphoStaticTyping is IMorphoBase { + /// @notice The state of the position of `user` on the market corresponding to `id`. + /// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest + /// accrual. + function position(Id id, address user) + external + view + returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral); + + /// @notice The state of the market corresponding to `id`. + /// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual. + /// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual. + /// @dev Warning: `totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last interest + /// accrual. + function market(Id id) + external + view + returns ( + uint128 totalSupplyAssets, + uint128 totalSupplyShares, + uint128 totalBorrowAssets, + uint128 totalBorrowShares, + uint128 lastUpdate, + uint128 fee + ); + + /// @notice The market params corresponding to `id`. + /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer + /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`. + function idToMarketParams(Id id) + external + view + returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv); +} + +/// @title IMorpho +/// @author Morpho Labs +/// @custom:contact security@morpho.org +/// @dev Use this interface for Morpho to have access to all the functions with the appropriate function signatures. +interface IMorpho is IMorphoBase { + /// @notice The state of the position of `user` on the market corresponding to `id`. + /// @dev Warning: For `feeRecipient`, `p.supplyShares` does not contain the accrued shares since the last interest + /// accrual. + function position(Id id, address user) external view returns (Position memory p); + + /// @notice The state of the market corresponding to `id`. + /// @dev Warning: `m.totalSupplyAssets` does not contain the accrued interest since the last interest accrual. + /// @dev Warning: `m.totalBorrowAssets` does not contain the accrued interest since the last interest accrual. + /// @dev Warning: `m.totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last + /// interest accrual. + function market(Id id) external view returns (Market memory m); + + /// @notice The market params corresponding to `id`. + /// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer + /// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`. + function idToMarketParams(Id id) external view returns (MarketParams memory); +} diff --git a/src/interfaces/IOracle.sol b/src/interfaces/IOracle.sol index 576230b5b..482737ef9 100644 --- a/src/interfaces/IOracle.sol +++ b/src/interfaces/IOracle.sol @@ -5,6 +5,7 @@ pragma solidity >=0.5.0; /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice Interface that oracles used by Morpho must implement. +/// @dev It is the user's responsibility to select markets with safe oracles. interface IOracle { /// @notice Returns the price of 1 asset of collateral token quoted in 1 asset of loan token, scaled by 1e36. /// @dev It corresponds to the price of 10**(collateral token decimals) assets of collateral token quoted in diff --git a/src/libraries/MathLib.sol b/src/libraries/MathLib.sol index 2ec633055..653db4f87 100644 --- a/src/libraries/MathLib.sol +++ b/src/libraries/MathLib.sol @@ -8,33 +8,33 @@ uint256 constant WAD = 1e18; /// @custom:contact security@morpho.org /// @notice Library to manage fixed-point arithmetic. library MathLib { - /// @dev (x * y) / WAD rounded down. + /// @dev Returns (`x` * `y`) / `WAD` rounded down. function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, y, WAD); } - /// @dev (x * WAD) / y rounded down. + /// @dev Returns (`x` * `WAD`) / `y` rounded down. function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivDown(x, WAD, y); } - /// @dev (x * WAD) / y rounded up. + /// @dev Returns (`x` * `WAD`) / `y` rounded up. function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) { return mulDivUp(x, WAD, y); } - /// @dev (x * y) / d rounded down. + /// @dev Returns (`x` * `y`) / `d` rounded down. function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { return (x * y) / d; } - /// @dev (x * y) / d rounded up. + /// @dev Returns (`x` * `y`) / `d` rounded up. function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) { return (x * y + (d - 1)) / d; } - /// @dev The sum of the first three non-zero terms of a Taylor expansion of e^(nx) - 1, to approximate a continuous - /// compound interest rate. + /// @dev Returns the sum of the first three non-zero terms of a Taylor expansion of e^(nx) - 1, to approximate a + /// continuous compound interest rate. function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) { uint256 firstTerm = x * n; uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD); diff --git a/src/libraries/periphery/MorphoBalancesLib.sol b/src/libraries/periphery/MorphoBalancesLib.sol index 9f9fbd693..3afabfd5f 100644 --- a/src/libraries/periphery/MorphoBalancesLib.sol +++ b/src/libraries/periphery/MorphoBalancesLib.sol @@ -10,10 +10,6 @@ import {MorphoLib} from "./MorphoLib.sol"; import {SharesMathLib} from "../SharesMathLib.sol"; import {MarketParamsLib} from "../MarketParamsLib.sol"; -interface IMorphoMarketStruct { - function market(Id id) external view returns (Market memory); -} - /// @title MorphoBalancesLib /// @author Morpho Labs /// @custom:contact security@morpho.org @@ -41,10 +37,11 @@ library MorphoBalancesLib { { Id id = marketParams.id(); - Market memory market = IMorphoMarketStruct(address(morpho)).market(id); + Market memory market = morpho.market(id); uint256 elapsed = block.timestamp - market.lastUpdate; + // Skipped if elapsed == 0 of if totalBorrowAssets == 0 because interest would be null. if (elapsed != 0 && market.totalBorrowAssets != 0) { uint256 borrowRate = IIrm(marketParams.irm).borrowRateView(marketParams, market); uint256 interest = market.totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed)); @@ -65,7 +62,7 @@ library MorphoBalancesLib { } /// @notice Returns the expected total supply assets of a market after having accrued interest. - function expectedTotalSupply(IMorpho morpho, MarketParams memory marketParams) + function expectedTotalSupplyAssets(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256 totalSupplyAssets) @@ -74,7 +71,7 @@ library MorphoBalancesLib { } /// @notice Returns the expected total borrow assets of a market after having accrued interest. - function expectedTotalBorrow(IMorpho morpho, MarketParams memory marketParams) + function expectedTotalBorrowAssets(IMorpho morpho, MarketParams memory marketParams) internal view returns (uint256 totalBorrowAssets) @@ -91,9 +88,11 @@ library MorphoBalancesLib { (, totalSupplyShares,,) = expectedMarketBalances(morpho, marketParams); } - /// @notice Returns the expected supply balance of a user on a market after having accrued interest. + /// @notice Returns the expected supply assets balance of `user` on a market after having accrued interest. /// @dev Warning: Wrong for `feeRecipient` because their supply shares increase is not taken into account. - function expectedSupplyBalance(IMorpho morpho, MarketParams memory marketParams, address user) + /// @dev Warning: Withdrawing a supply position using the expected assets balance can lead to a revert due to + /// conversion roundings between shares and assets. + function expectedSupplyAssets(IMorpho morpho, MarketParams memory marketParams, address user) internal view returns (uint256) @@ -105,8 +104,10 @@ library MorphoBalancesLib { return supplyShares.toAssetsDown(totalSupplyAssets, totalSupplyShares); } - /// @notice Returns the expected borrow balance of a user on a market after having accrued interest. - function expectedBorrowBalance(IMorpho morpho, MarketParams memory marketParams, address user) + /// @notice Returns the expected borrow assets balance of `user` on a market after having accrued interest. + /// @dev Warning: repaying a borrow position using the expected assets balance can lead to a revert due to + /// conversion roundings between shares and assets. + function expectedBorrowAssets(IMorpho morpho, MarketParams memory marketParams, address user) internal view returns (uint256) diff --git a/src/libraries/periphery/MorphoLib.sol b/src/libraries/periphery/MorphoLib.sol index de82e6d7c..c366d1a6b 100644 --- a/src/libraries/periphery/MorphoLib.sol +++ b/src/libraries/periphery/MorphoLib.sol @@ -8,6 +8,7 @@ import {MorphoStorageLib} from "./MorphoStorageLib.sol"; /// @author Morpho Labs /// @custom:contact security@morpho.org /// @notice Helper library to access Morpho storage variables. +/// @dev Warning: Supply and borrow getters may return outdated values that do not include accrued interest. library MorphoLib { function supplyShares(IMorpho morpho, Id id, address user) internal view returns (uint256) { bytes32[] memory slot = _array(MorphoStorageLib.positionSupplySharesSlot(id, user)); diff --git a/src/mocks/IrmMock.sol b/src/mocks/IrmMock.sol index e72197725..114d0bc27 100644 --- a/src/mocks/IrmMock.sol +++ b/src/mocks/IrmMock.sol @@ -10,6 +10,8 @@ contract IrmMock is IIrm { using MathLib for uint128; function borrowRateView(MarketParams memory, Market memory market) public pure returns (uint256) { + if (market.totalSupplyAssets == 0) return 0; + uint256 utilization = market.totalBorrowAssets.wDivDown(market.totalSupplyAssets); // Divide by the number of seconds in a year. diff --git a/test/forge/BaseTest.sol b/test/forge/BaseTest.sol index 3a59787a0..a26dab28e 100644 --- a/test/forge/BaseTest.sol +++ b/test/forge/BaseTest.sol @@ -4,6 +4,7 @@ pragma solidity ^0.8.0; import "../../lib/forge-std/src/Test.sol"; import "../../lib/forge-std/src/console.sol"; +import {IMorpho} from "../../src/interfaces/IMorpho.sol"; import "../../src/interfaces/IMorphoCallbacks.sol"; import {IrmMock} from "../../src/mocks/IrmMock.sol"; import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol"; @@ -66,7 +67,7 @@ contract BaseTest is Test { OWNER = makeAddr("Owner"); FEE_RECIPIENT = makeAddr("FeeRecipient"); - morpho = new Morpho(OWNER); + morpho = IMorpho(address(new Morpho(OWNER))); loanToken = new ERC20Mock(); vm.label(address(loanToken), "LoanToken"); @@ -222,7 +223,7 @@ contract BaseTest is Test { uint256 collateral = morpho.collateral(_id, onBehalf); uint256 collateralPrice = IOracle(_marketParams.oracle).price(); - uint256 borrowed = morpho.expectedBorrowBalance(_marketParams, onBehalf); + uint256 borrowed = morpho.expectedBorrowAssets(_marketParams, onBehalf); return bound( assets, @@ -236,7 +237,7 @@ contract BaseTest is Test { view returns (uint256) { - uint256 supplyBalance = morpho.expectedSupplyBalance(_marketParams, onBehalf); + uint256 supplyBalance = morpho.expectedSupplyAssets(_marketParams, onBehalf); return bound(assets, 0, MAX_TEST_AMOUNT.zeroFloorSub(supplyBalance)); } @@ -266,7 +267,7 @@ contract BaseTest is Test { { Id _id = _marketParams.id(); - uint256 supplyBalance = morpho.expectedSupplyBalance(_marketParams, onBehalf); + uint256 supplyBalance = morpho.expectedSupplyAssets(_marketParams, onBehalf); uint256 liquidity = morpho.totalSupplyAssets(_id) - morpho.totalBorrowAssets(_id); return bound(assets, 0, MAX_TEST_AMOUNT.min(supplyBalance).min(liquidity)); @@ -296,7 +297,7 @@ contract BaseTest is Test { Id _id = _marketParams.id(); uint256 maxBorrow = _maxBorrow(_marketParams, onBehalf); - uint256 borrowed = morpho.expectedBorrowBalance(_marketParams, onBehalf); + uint256 borrowed = morpho.expectedBorrowAssets(_marketParams, onBehalf); uint256 liquidity = morpho.totalSupplyAssets(_id) - morpho.totalBorrowAssets(_id); return bound(assets, 0, MAX_TEST_AMOUNT.min(maxBorrow - borrowed).min(liquidity)); @@ -336,7 +337,7 @@ contract BaseTest is Test { uint256 collateral = morpho.collateral(_id, borrower); uint256 collateralPrice = IOracle(_marketParams.oracle).price(); - uint256 maxRepaidAssets = morpho.expectedBorrowBalance(_marketParams, borrower); + uint256 maxRepaidAssets = morpho.expectedBorrowAssets(_marketParams, borrower); uint256 maxSeizedAssets = maxRepaidAssets.wMulDown(_liquidationIncentiveFactor(_marketParams.lltv)).mulDivDown( ORACLE_PRICE_SCALE, collateralPrice ); @@ -372,7 +373,7 @@ contract BaseTest is Test { function _isHealthy(MarketParams memory _marketParams, address user) internal view returns (bool) { uint256 maxBorrow = _maxBorrow(_marketParams, user); - uint256 borrowed = morpho.expectedBorrowBalance(_marketParams, user); + uint256 borrowed = morpho.expectedBorrowAssets(_marketParams, user); return maxBorrow >= borrowed; } @@ -385,12 +386,6 @@ contract BaseTest is Test { return bound(lltv, 0, WAD - 1); } - function _accrueInterest(MarketParams memory market) internal { - collateralToken.setBalance(address(this), 1); - morpho.supplyCollateral(market, 1, address(this), hex""); - morpho.withdrawCollateral(market, 1, address(this), address(10)); - } - function neq(MarketParams memory a, MarketParams memory b) internal pure returns (bool) { return (Id.unwrap(a.id()) != Id.unwrap(b.id())); } diff --git a/test/forge/integration/AccrueInterestIntegrationTest.sol b/test/forge/integration/AccrueInterestIntegrationTest.sol index 25b91098b..3b91564f7 100644 --- a/test/forge/integration/AccrueInterestIntegrationTest.sol +++ b/test/forge/integration/AccrueInterestIntegrationTest.sol @@ -8,6 +8,13 @@ contract AccrueInterestIntegrationTest is BaseTest { using MorphoLib for IMorpho; using SharesMathLib for uint256; + function testAccrueInterestMarketNotCreated(MarketParams memory marketParamsFuzz) public { + vm.assume(neq(marketParamsFuzz, marketParams)); + + vm.expectRevert(bytes(ErrorsLib.MARKET_NOT_CREATED)); + morpho.accrueInterest(marketParamsFuzz); + } + function testAccrueInterestNoTimeElapsed(uint256 amountSupplied, uint256 amountBorrowed) public { uint256 collateralPrice = oracle.price(); uint256 amountCollateral; @@ -28,7 +35,7 @@ contract AccrueInterestIntegrationTest is BaseTest { uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id); uint256 totalSupplySharesBeforeAccrued = morpho.totalSupplyShares(id); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); assertEq(morpho.totalBorrowAssets(id), totalBorrowBeforeAccrued, "total borrow"); assertEq(morpho.totalSupplyAssets(id), totalSupplyBeforeAccrued, "total supply"); @@ -49,7 +56,7 @@ contract AccrueInterestIntegrationTest is BaseTest { uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id); uint256 totalSupplySharesBeforeAccrued = morpho.totalSupplyShares(id); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); assertEq(morpho.totalBorrowAssets(id), totalBorrowBeforeAccrued, "total borrow"); assertEq(morpho.totalSupplyAssets(id), totalSupplyBeforeAccrued, "total supply"); @@ -86,12 +93,9 @@ contract AccrueInterestIntegrationTest is BaseTest { uint256 expectedAccruedInterest = totalBorrowBeforeAccrued.wMulDown(borrowRate.wTaylorCompounded(blocks * BLOCK_TIME)); - collateralToken.setBalance(address(this), 1); - morpho.supplyCollateral(marketParams, 1, address(this), hex""); vm.expectEmit(true, true, true, true, address(morpho)); emit EventsLib.AccrueInterest(id, borrowRate, expectedAccruedInterest, 0); - // Accrues interest. - morpho.withdrawCollateral(marketParams, 1, address(this), address(this)); + morpho.accrueInterest(marketParams); assertEq(morpho.totalBorrowAssets(id), totalBorrowBeforeAccrued + expectedAccruedInterest, "total borrow"); assertEq(morpho.totalSupplyAssets(id), totalSupplyBeforeAccrued + expectedAccruedInterest, "total supply"); @@ -151,12 +155,9 @@ contract AccrueInterestIntegrationTest is BaseTest { params.totalSupplySharesBeforeAccrued ); - collateralToken.setBalance(address(this), 1); - morpho.supplyCollateral(marketParams, 1, address(this), hex""); vm.expectEmit(true, true, true, true, address(morpho)); emit EventsLib.AccrueInterest(id, params.borrowRate, params.expectedAccruedInterest, params.feeShares); - // Accrues interest. - morpho.withdrawCollateral(marketParams, 1, address(this), address(this)); + morpho.accrueInterest(marketParams); assertEq( morpho.totalSupplyAssets(id), diff --git a/test/forge/integration/AuthorizationIntegrationTest.sol b/test/forge/integration/AuthorizationIntegrationTest.sol index d8cb5b178..263eaa550 100644 --- a/test/forge/integration/AuthorizationIntegrationTest.sol +++ b/test/forge/integration/AuthorizationIntegrationTest.sol @@ -22,7 +22,7 @@ contract AuthorizationIntegrationTest is BaseTest { uint256 blocks ) public { blocks = _boundBlocks(blocks); - authorization.deadline = block.timestamp; + authorization.deadline = block.timestamp - 1; // Private key must be less than the secp256k1 curve order. privateKey = bound(privateKey, 1, type(uint32).max); @@ -40,7 +40,7 @@ contract AuthorizationIntegrationTest is BaseTest { } function testAuthorizationWithSigWrongPK(Authorization memory authorization, uint256 privateKey) public { - authorization.deadline = bound(authorization.deadline, block.timestamp + 1, type(uint256).max); + authorization.deadline = bound(authorization.deadline, block.timestamp, type(uint256).max); // Private key must be less than the secp256k1 curve order. privateKey = bound(privateKey, 1, type(uint32).max); @@ -55,7 +55,7 @@ contract AuthorizationIntegrationTest is BaseTest { } function testAuthorizationWithSigWrongNonce(Authorization memory authorization, uint256 privateKey) public { - authorization.deadline = bound(authorization.deadline, block.timestamp + 1, type(uint256).max); + authorization.deadline = bound(authorization.deadline, block.timestamp, type(uint256).max); authorization.nonce = bound(authorization.nonce, 1, type(uint256).max); // Private key must be less than the secp256k1 curve order. @@ -71,7 +71,7 @@ contract AuthorizationIntegrationTest is BaseTest { } function testAuthorizationWithSig(Authorization memory authorization, uint256 privateKey) public { - authorization.deadline = bound(authorization.deadline, block.timestamp + 1, type(uint256).max); + authorization.deadline = bound(authorization.deadline, block.timestamp, type(uint256).max); // Private key must be less than the secp256k1 curve order. privateKey = bound(privateKey, 1, type(uint32).max); @@ -89,7 +89,7 @@ contract AuthorizationIntegrationTest is BaseTest { } function testAuthorizationFailsWithReusedSig(Authorization memory authorization, uint256 privateKey) public { - authorization.deadline = bound(authorization.deadline, block.timestamp + 1, type(uint256).max); + authorization.deadline = bound(authorization.deadline, block.timestamp, type(uint256).max); // Private key must be less than the secp256k1 curve order. privateKey = bound(privateKey, 1, type(uint32).max); diff --git a/test/forge/integration/CreateMarketIntegrationTest.sol b/test/forge/integration/CreateMarketIntegrationTest.sol index f0f31822b..1fd2eb083 100644 --- a/test/forge/integration/CreateMarketIntegrationTest.sol +++ b/test/forge/integration/CreateMarketIntegrationTest.sol @@ -84,13 +84,12 @@ contract CreateMarketIntegrationTest is BaseTest { morpho.createMarket(marketParamsFuzz); vm.stopPrank(); - (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv) = - morpho.idToMarketParams(marketParamsFuzzId); - - assertEq(marketParamsFuzz.loanToken, loanToken, "loanToken != loanToken"); - assertEq(marketParamsFuzz.collateralToken, collateralToken, "collateralToken != collateralToken"); - assertEq(marketParamsFuzz.oracle, oracle, "oracle != oracle"); - assertEq(marketParamsFuzz.irm, irm, "irm != irm"); - assertEq(marketParamsFuzz.lltv, lltv, "lltv != lltv"); + MarketParams memory params = morpho.idToMarketParams(marketParamsFuzzId); + + assertEq(marketParamsFuzz.loanToken, params.loanToken, "loanToken != loanToken"); + assertEq(marketParamsFuzz.collateralToken, params.collateralToken, "collateralToken != collateralToken"); + assertEq(marketParamsFuzz.oracle, params.oracle, "oracle != oracle"); + assertEq(marketParamsFuzz.irm, params.irm, "irm != irm"); + assertEq(marketParamsFuzz.lltv, params.lltv, "lltv != lltv"); } } diff --git a/test/forge/integration/LiquidateIntegrationTest.sol b/test/forge/integration/LiquidateIntegrationTest.sol index 81cd98a4d..f9227b635 100644 --- a/test/forge/integration/LiquidateIntegrationTest.sol +++ b/test/forge/integration/LiquidateIntegrationTest.sol @@ -296,4 +296,24 @@ contract LiquidateIntegrationTest is BaseTest { morpho.totalSupplyAssets(id), params.totalSupplyBeforeLiquidation - params.expectedBadDebt, "total supply" ); } + + function testBadDebtOverTotalBorrowAssets() public { + uint256 collateralAmount = 10 ether; + uint256 loanAmount = 1 ether; + _supply(loanAmount); + + collateralToken.setBalance(BORROWER, collateralAmount); + vm.startPrank(BORROWER); + morpho.supplyCollateral(marketParams, collateralAmount, BORROWER, hex""); + morpho.borrow(marketParams, loanAmount, 0, BORROWER, BORROWER); + // Trick to inflate shares, so that the computed bad debt is greater than the total debt of the market. + morpho.borrow(marketParams, 0, 1, BORROWER, BORROWER); + vm.stopPrank(); + + oracle.setPrice(1e36 / 100); + + loanToken.setBalance(LIQUIDATOR, loanAmount); + vm.prank(LIQUIDATOR); + morpho.liquidate(marketParams, BORROWER, collateralAmount, 0, hex""); + } } diff --git a/test/forge/integration/OnlyOwnerIntegrationTest.sol b/test/forge/integration/OnlyOwnerIntegrationTest.sol index aa2586f51..fb7f8c016 100644 --- a/test/forge/integration/OnlyOwnerIntegrationTest.sol +++ b/test/forge/integration/OnlyOwnerIntegrationTest.sol @@ -12,6 +12,12 @@ contract OnlyOwnerIntegrationTest is BaseTest { new Morpho(address(0)); } + function testDeployEmitOwner() public { + vm.expectEmit(); + emit EventsLib.SetOwner(OWNER); + new Morpho(OWNER); + } + function testSetOwnerWhenNotOwner(address addressFuzz) public { vm.assume(addressFuzz != OWNER); diff --git a/test/forge/libraries/periphery/MorphoBalancesLibTest.sol b/test/forge/libraries/periphery/MorphoBalancesLibTest.sol index e99a5fbf4..fca8b9c64 100644 --- a/test/forge/libraries/periphery/MorphoBalancesLibTest.sol +++ b/test/forge/libraries/periphery/MorphoBalancesLibTest.sol @@ -21,7 +21,7 @@ contract MorphoBalancesLibTest is BaseTest { uint256 virtualTotalBorrowShares ) = morpho.expectedMarketBalances(marketParams); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); assertEq(virtualTotalSupplyAssets, morpho.totalSupplyAssets(id), "total supply assets"); assertEq(virtualTotalBorrowAssets, morpho.totalBorrowAssets(id), "total borrow assets"); @@ -34,11 +34,11 @@ contract MorphoBalancesLibTest is BaseTest { { _generatePendingInterest(amountSupplied, amountBorrowed, timeElapsed, fee); - uint256 expectedTotalSupply = morpho.expectedTotalSupply(marketParams); + uint256 expectedTotalSupplyAssets = morpho.expectedTotalSupplyAssets(marketParams); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); - assertEq(expectedTotalSupply, morpho.totalSupplyAssets(id)); + assertEq(expectedTotalSupplyAssets, morpho.totalSupplyAssets(id)); } function testExpectedTotalBorrow(uint256 amountSupplied, uint256 amountBorrowed, uint256 timeElapsed, uint256 fee) @@ -46,11 +46,11 @@ contract MorphoBalancesLibTest is BaseTest { { _generatePendingInterest(amountSupplied, amountBorrowed, timeElapsed, fee); - uint256 expectedTotalBorrow = morpho.expectedTotalBorrow(marketParams); + uint256 expectedTotalBorrowAssets = morpho.expectedTotalBorrowAssets(marketParams); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); - assertEq(expectedTotalBorrow, morpho.totalBorrowAssets(id)); + assertEq(expectedTotalBorrowAssets, morpho.totalBorrowAssets(id)); } function testExpectedTotalSupplyShares( @@ -63,7 +63,7 @@ contract MorphoBalancesLibTest is BaseTest { uint256 expectedTotalSupplyShares = morpho.expectedTotalSupplyShares(marketParams); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); assertEq(expectedTotalSupplyShares, morpho.totalSupplyShares(id)); } @@ -73,9 +73,9 @@ contract MorphoBalancesLibTest is BaseTest { { _generatePendingInterest(amountSupplied, amountBorrowed, timeElapsed, fee); - uint256 expectedSupplyBalance = morpho.expectedSupplyBalance(marketParams, address(this)); + uint256 expectedSupplyBalance = morpho.expectedSupplyAssets(marketParams, address(this)); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); uint256 actualSupplyBalance = morpho.supplyShares(id, address(this)).toAssetsDown( morpho.totalSupplyAssets(id), morpho.totalSupplyShares(id) @@ -89,9 +89,9 @@ contract MorphoBalancesLibTest is BaseTest { { _generatePendingInterest(amountSupplied, amountBorrowed, timeElapsed, fee); - uint256 expectedBorrowBalance = morpho.expectedBorrowBalance(marketParams, address(this)); + uint256 expectedBorrowBalance = morpho.expectedBorrowAssets(marketParams, address(this)); - _accrueInterest(marketParams); + morpho.accrueInterest(marketParams); uint256 actualBorrowBalance = morpho.borrowShares(id, address(this)).toAssetsUp( morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id) diff --git a/test/forge/libraries/periphery/MorphoLibTest.sol b/test/forge/libraries/periphery/MorphoLibTest.sol index 864bee16b..8ff015d4a 100644 --- a/test/forge/libraries/periphery/MorphoLibTest.sol +++ b/test/forge/libraries/periphery/MorphoLibTest.sol @@ -1,8 +1,6 @@ // SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.0; -import {MorphoBalancesLib} from "../../../../src/libraries/periphery/MorphoBalancesLib.sol"; - import "../../BaseTest.sol"; contract MorphoLibTest is BaseTest { @@ -50,21 +48,21 @@ contract MorphoLibTest is BaseTest { function testSupplyShares(uint256 amountSupplied, uint256 amountBorrowed, uint256 timestamp, uint256 fee) public { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (uint256 expectedSupplyShares,,) = morpho.position(id, address(this)); + uint256 expectedSupplyShares = morpho.position(id, address(this)).supplyShares; assertEq(morpho.supplyShares(id, address(this)), expectedSupplyShares); } function testBorrowShares(uint256 amountSupplied, uint256 amountBorrowed, uint256 timestamp, uint256 fee) public { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (, uint256 expectedBorrowShares,) = morpho.position(id, BORROWER); + uint256 expectedBorrowShares = morpho.position(id, BORROWER).borrowShares; assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares); } function testCollateral(uint256 amountSupplied, uint256 amountBorrowed, uint256 timestamp, uint256 fee) public { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (,, uint256 expectedCollateral) = morpho.position(id, BORROWER); + uint256 expectedCollateral = morpho.position(id, BORROWER).collateral; assertEq(morpho.collateral(id, BORROWER), expectedCollateral); } @@ -73,7 +71,7 @@ contract MorphoLibTest is BaseTest { { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (uint256 expectedTotalSupplyAssets,,,,,) = morpho.market(id); + uint256 expectedTotalSupplyAssets = morpho.market(id).totalSupplyAssets; assertEq(morpho.totalSupplyAssets(id), expectedTotalSupplyAssets); } @@ -82,7 +80,7 @@ contract MorphoLibTest is BaseTest { { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (, uint256 expectedTotalSupplyShares,,,,) = morpho.market(id); + uint256 expectedTotalSupplyShares = morpho.market(id).totalSupplyShares; assertEq(morpho.totalSupplyShares(id), expectedTotalSupplyShares); } @@ -91,7 +89,7 @@ contract MorphoLibTest is BaseTest { { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (,, uint256 expectedTotalBorrowAssets,,,) = morpho.market(id); + uint256 expectedTotalBorrowAssets = morpho.market(id).totalBorrowAssets; assertEq(morpho.totalBorrowAssets(id), expectedTotalBorrowAssets); } @@ -100,21 +98,21 @@ contract MorphoLibTest is BaseTest { { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (,,, uint256 expectedTotalBorrowShares,,) = morpho.market(id); + uint256 expectedTotalBorrowShares = morpho.market(id).totalBorrowShares; assertEq(morpho.totalBorrowShares(id), expectedTotalBorrowShares); } function testLastUpdate(uint256 amountSupplied, uint256 amountBorrowed, uint256 timestamp, uint256 fee) public { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (,,,, uint256 expectedLastUpdate,) = morpho.market(id); + uint256 expectedLastUpdate = morpho.market(id).lastUpdate; assertEq(morpho.lastUpdate(id), expectedLastUpdate); } function testFee(uint256 amountSupplied, uint256 amountBorrowed, uint256 timestamp, uint256 fee) public { _testMorphoLibCommon(amountSupplied, amountBorrowed, timestamp, fee); - (,,,,, uint256 expectedFee) = morpho.market(id); + uint256 expectedFee = morpho.market(id).fee; assertEq(morpho.fee(id), expectedFee); } } diff --git a/test/forge/libraries/periphery/MorphoStorageLibTest.sol b/test/forge/libraries/periphery/MorphoStorageLibTest.sol index 8f88f4de0..98a7cd3e8 100644 --- a/test/forge/libraries/periphery/MorphoStorageLibTest.sol +++ b/test/forge/libraries/periphery/MorphoStorageLibTest.sol @@ -93,17 +93,12 @@ contract MorphoStorageLibTest is BaseTest { assertEq(abi.decode(abi.encode(values[9]), (bool)), morpho.isAuthorized(authorizer, BORROWER)); assertEq(uint256(values[10]), morpho.nonce(authorizer)); - ( - address expectedloanToken, - address expectedCollateralToken, - address expectedOracle, - address expectedIrm, - uint256 expectedLltv - ) = morpho.idToMarketParams(id); - assertEq(abi.decode(abi.encode(values[11]), (address)), expectedloanToken); - assertEq(abi.decode(abi.encode(values[12]), (address)), expectedCollateralToken); - assertEq(abi.decode(abi.encode(values[13]), (address)), expectedOracle); - assertEq(abi.decode(abi.encode(values[14]), (address)), expectedIrm); - assertEq(uint256(values[15]), expectedLltv); + MarketParams memory expectedParams = morpho.idToMarketParams(id); + + assertEq(abi.decode(abi.encode(values[11]), (address)), expectedParams.loanToken); + assertEq(abi.decode(abi.encode(values[12]), (address)), expectedParams.collateralToken); + assertEq(abi.decode(abi.encode(values[13]), (address)), expectedParams.oracle); + assertEq(abi.decode(abi.encode(values[14]), (address)), expectedParams.irm); + assertEq(uint256(values[15]), expectedParams.lltv); } } diff --git a/test/morpho_tests.tree b/test/morpho_tests.tree index dff911e85..a2faa9950 100644 --- a/test/morpho_tests.tree +++ b/test/morpho_tests.tree @@ -242,9 +242,9 @@ └── it should transfer assets of token from the sender to Morpho . └── setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external - ├── when block.timestamp >= authorization.deadline + ├── when block.timestamp > authorization.deadline │ └── revert with SIGNATURE_EXPIRED - └── when block.timestamp < deadline + └── when block.timestamp <= deadline ├── when authorization.nonce != nonce[authorization.authorizer] │ └── revert with INVALID_NONCE └── when authorization.nonce == nonce[authorization.authorizer]