diff --git a/packages/dma-contracts/contracts/actions/morpho-blue/Borrow.sol b/packages/dma-contracts/contracts/actions/morpho-blue/Borrow.sol index 26b77bb4..89c9c372 100644 --- a/packages/dma-contracts/contracts/actions/morpho-blue/Borrow.sol +++ b/packages/dma-contracts/contracts/actions/morpho-blue/Borrow.sol @@ -2,30 +2,29 @@ pragma solidity ^0.8.15; import { Executable } from "../common/Executable.sol"; -import { Write, UseStore } from "../common/UseStore.sol"; -import { OperationStorage } from "../../core/OperationStorage.sol"; +import { UseStorageSlot, StorageSlot, Write, Read } from "../../libs/UseStorageSlot.sol"; +import { ServiceRegistry } from "../../core/ServiceRegistry.sol"; import { BorrowData } from "../../core/types/MorphoBlue.sol"; import { MORPHO_BLUE } from "../../core/constants/MorphoBlue.sol"; import { IMorpho } from "../../interfaces/morpho-blue/IMorpho.sol"; +import { UseRegistry } from "../../libs/UseRegistry.sol"; /** * @title Borrow | Morpho Blue Action contract * @notice Borrows token from MorphoBlue's lending pool */ -contract MorphoBlueBorrow is Executable, UseStore { - using Write for OperationStorage; +contract MorphoBlueBorrow is Executable, UseStorageSlot, UseRegistry { + using Write for StorageSlot.TransactionStorage; - constructor(address _registry) UseStore(_registry) {} + constructor(address _registry) UseRegistry(ServiceRegistry(_registry)) {} /** - * @dev Look at UseStore.sol to get additional info on paramsMapping - * * @param data Encoded calldata that conforms to the BorrowData struct */ function execute(bytes calldata data, uint8[] memory) external payable override { BorrowData memory borrowData = parseInputs(data); - IMorpho morphoBlue = IMorpho(registry.getRegisteredService(MORPHO_BLUE)); + IMorpho morphoBlue = IMorpho(getRegisteredService(MORPHO_BLUE)); morphoBlue.borrow(borrowData.marketParams, borrowData.amount, 0, address(this), address(this)); store().write(bytes32(borrowData.amount)); diff --git a/packages/dma-contracts/contracts/actions/morpho-blue/Deposit.sol b/packages/dma-contracts/contracts/actions/morpho-blue/Deposit.sol index 2bf62938..5008f848 100644 --- a/packages/dma-contracts/contracts/actions/morpho-blue/Deposit.sol +++ b/packages/dma-contracts/contracts/actions/morpho-blue/Deposit.sol @@ -2,25 +2,24 @@ pragma solidity ^0.8.15; import { Executable } from "../common/Executable.sol"; -import { UseStore, Write, Read } from "../common/UseStore.sol"; -import { OperationStorage } from "../../core/OperationStorage.sol"; +import { UseStorageSlot, StorageSlot, Write, Read } from "../../libs/UseStorageSlot.sol"; +import { ServiceRegistry } from "../../core/ServiceRegistry.sol"; import { DepositData } from "../../core/types/MorphoBlue.sol"; import { MORPHO_BLUE } from "../../core/constants/MorphoBlue.sol"; import { IMorpho } from "../../interfaces/morpho-blue/IMorpho.sol"; +import { UseRegistry } from "../../libs/UseRegistry.sol"; /** * @title Deposit | Morpho Blue Action contract * @notice Deposits the specified asset as collateral on MorphoBlue's lending pool */ -contract MorphoBlueDeposit is Executable, UseStore { - using Write for OperationStorage; - using Read for OperationStorage; +contract MorphoBlueDeposit is Executable, UseStorageSlot, UseRegistry { + using Write for StorageSlot.TransactionStorage; + using Read for StorageSlot.TransactionStorage; - constructor(address _registry) UseStore(_registry) {} + constructor(address _registry) UseRegistry(ServiceRegistry(_registry)) {} /** - * @dev Look at UseStore.sol to get additional info on paramsMapping - * * @param data Encoded calldata that conforms to the DepositData struct * @param paramsMap Maps operation storage values by index (index offset by +1) to execute calldata params */ @@ -29,15 +28,14 @@ contract MorphoBlueDeposit is Executable, UseStore { uint256 mappedDepositAmount = store().readUint( bytes32(depositData.amount), - paramsMap[1], - address(this) + paramsMap[1] ); uint256 actualDepositAmount = depositData.sumAmounts - ? mappedDepositAmount + depositData.amount - : mappedDepositAmount; + ? mappedDepositAmount + depositData.amount + : mappedDepositAmount; - IMorpho morphoBlue = IMorpho(registry.getRegisteredService(MORPHO_BLUE)); + IMorpho morphoBlue = IMorpho(getRegisteredService(MORPHO_BLUE)); morphoBlue.supplyCollateral( depositData.marketParams, actualDepositAmount, diff --git a/packages/dma-contracts/contracts/actions/morpho-blue/Payback.sol b/packages/dma-contracts/contracts/actions/morpho-blue/Payback.sol index 04cdcf20..60128a5d 100644 --- a/packages/dma-contracts/contracts/actions/morpho-blue/Payback.sol +++ b/packages/dma-contracts/contracts/actions/morpho-blue/Payback.sol @@ -2,37 +2,60 @@ pragma solidity ^0.8.15; import { Executable } from "../common/Executable.sol"; -import { UseStore, Write, Read } from "../common/UseStore.sol"; -import { OperationStorage } from "../../core/OperationStorage.sol"; +import { UseStorageSlot, StorageSlot, Write, Read } from "../../libs/UseStorageSlot.sol"; +import { ServiceRegistry } from "../../core/ServiceRegistry.sol"; import { PaybackData } from "../../core/types/MorphoBlue.sol"; import { MORPHO_BLUE } from "../../core/constants/MorphoBlue.sol"; -import { IMorpho } from "../../interfaces/morpho-blue/IMorpho.sol"; +import { Id, IMorpho, MarketParams } from "../../interfaces/morpho-blue/IMorpho.sol"; +import { MarketParamsLib } from "../../libs/morpho-blue/MarketParamsLib.sol"; +import { MorphoLib } from "../../libs/morpho-blue/MorphoLib.sol"; +import { SharesMathLib } from "../../libs/morpho-blue/SharesMathLib.sol"; +import { UseRegistry } from "../../libs/UseRegistry.sol"; /** * @title Payback | MorphoBlue Action contract * @notice Pays back a specified amount to Morpho Blue's lending pool */ -contract MorphoBluePayback is Executable, UseStore { - using Write for OperationStorage; - using Read for OperationStorage; +contract MorphoBluePayback is Executable, UseStorageSlot, UseRegistry { + using Write for StorageSlot.TransactionStorage; + using Read for StorageSlot.TransactionStorage; + using MarketParamsLib for MarketParams; + using MorphoLib for IMorpho; + using SharesMathLib for uint256; - constructor(address _registry) UseStore(_registry) {} + constructor(address _registry) UseRegistry(ServiceRegistry(_registry)) {} /** - * @dev Look at UseStore.sol to get additional info on paramsMapping. - * * @param data Encoded calldata that conforms to the PaybackData struct * @param paramsMap Maps operation storage values by index (index offset by +1) to execute calldata params */ function execute(bytes calldata data, uint8[] memory paramsMap) external payable override { PaybackData memory paybackData = parseInputs(data); - paybackData.amount = store().readUint(bytes32(paybackData.amount), paramsMap[0], address(this)); + paybackData.amount = store().readUint(bytes32(paybackData.amount), paramsMap[0]); - IMorpho morphoBlue = IMorpho(registry.getRegisteredService(MORPHO_BLUE)); + IMorpho morphoBlue = IMorpho(getRegisteredService(MORPHO_BLUE)); address onBehalf = paybackData.onBehalf == address(0) ? address(this) : paybackData.onBehalf; - morphoBlue.repay(paybackData.marketParams, paybackData.amount, 0, onBehalf, bytes("")); + + if (paybackData.paybackAll) { + Id id = paybackData.marketParams.id(); + + // Need to call accrueInterest to get the latest snapshot of the shares/asset ratio + morphoBlue.accrueInterest(paybackData.marketParams); + + uint256 totalBorrowAssets = morphoBlue.totalBorrowAssets(id); + uint256 totalBorrowShares = morphoBlue.totalBorrowShares(id); + uint256 shares = morphoBlue.borrowShares(id, onBehalf); + uint256 assetsMax = shares.toAssetsUp(totalBorrowAssets, totalBorrowShares); + + require(paybackData.amount >= assetsMax, "MorphoBluePayback: payback amount too low"); + paybackData.amount = assetsMax; + + morphoBlue.repay(paybackData.marketParams, 0, shares, onBehalf, bytes("")); + } else { + morphoBlue.repay(paybackData.marketParams, paybackData.amount, 0, onBehalf, bytes("")); + } store().write(bytes32(paybackData.amount)); } diff --git a/packages/dma-contracts/contracts/actions/morpho-blue/Withdraw.sol b/packages/dma-contracts/contracts/actions/morpho-blue/Withdraw.sol index a9ec4f31..3529f561 100644 --- a/packages/dma-contracts/contracts/actions/morpho-blue/Withdraw.sol +++ b/packages/dma-contracts/contracts/actions/morpho-blue/Withdraw.sol @@ -2,20 +2,22 @@ pragma solidity ^0.8.15; import { Executable } from "../common/Executable.sol"; -import { UseStore, Write } from "../common/UseStore.sol"; -import { OperationStorage } from "../../core/OperationStorage.sol"; +import { UseStorageSlot, StorageSlot, Write, Read } from "../../libs/UseStorageSlot.sol"; +import { ServiceRegistry } from "../../core/ServiceRegistry.sol"; import { WithdrawData } from "../../core/types/MorphoBlue.sol"; import { MORPHO_BLUE } from "../../core/constants/MorphoBlue.sol"; import { IMorpho } from "../../interfaces/morpho-blue/IMorpho.sol"; +import { UseRegistry } from "../../libs/UseRegistry.sol"; /** * @title Withdraw | MorphoBlue Action contract * @notice Withdraw collateral from Morpho Blue's lending pool + * with the amount to withdraw being read from the proxies t/x storage slot */ -contract MorphoBlueWithdraw is Executable, UseStore { - using Write for OperationStorage; +contract MorphoBlueWithdraw is Executable, UseStorageSlot, UseRegistry { + using Write for StorageSlot.TransactionStorage; - constructor(address _registry) UseStore(_registry) {} + constructor(address _registry) UseRegistry(ServiceRegistry(_registry)) {} /** * @param data Encoded calldata that conforms to the WithdrawData struct @@ -23,7 +25,7 @@ contract MorphoBlueWithdraw is Executable, UseStore { function execute(bytes calldata data, uint8[] memory) external payable override { WithdrawData memory withdrawData = parseInputs(data); - IMorpho morphoBlue = IMorpho(registry.getRegisteredService(MORPHO_BLUE)); + IMorpho morphoBlue = IMorpho(getRegisteredService(MORPHO_BLUE)); morphoBlue.withdrawCollateral( withdrawData.marketParams, withdrawData.amount, diff --git a/packages/dma-contracts/contracts/actions/morpho-blue/WithdrawAuto.sol b/packages/dma-contracts/contracts/actions/morpho-blue/WithdrawAuto.sol new file mode 100644 index 00000000..be802119 --- /dev/null +++ b/packages/dma-contracts/contracts/actions/morpho-blue/WithdrawAuto.sol @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: AGPL-3.0-or-later +pragma solidity ^0.8.15; + +import { Executable } from "../common/Executable.sol"; +import { UseStorageSlot, StorageSlot, Write, Read } from "../../libs/UseStorageSlot.sol"; +import { ServiceRegistry } from "../../core/ServiceRegistry.sol"; +import { WithdrawData } from "../../core/types/MorphoBlue.sol"; +import { MORPHO_BLUE } from "../../core/constants/MorphoBlue.sol"; +import { IMorpho } from "../../interfaces/morpho-blue/IMorpho.sol"; +import { UseRegistry } from "../../libs/UseRegistry.sol"; + +/** + * @title Withdraw | MorphoBlue Action contract + * @notice Withdraw collateral from Morpho Blue's lending pool + * with the amount to withdraw being read from an OperationStorage slot + */ +contract MorphoBlueWithdrawAuto is Executable, UseStorageSlot, UseRegistry { + using Write for StorageSlot.TransactionStorage; + using Read for StorageSlot.TransactionStorage; + + constructor(address _registry) UseRegistry(ServiceRegistry(_registry)) {} + + /** + * @param data Encoded calldata that conforms to the WithdrawData struct + */ + function execute(bytes calldata data, uint8[] memory paramsMap) external payable override { + WithdrawData memory withdraw = parseInputs(data); + + uint256 mappedWithdrawAmount = store().readUint( + bytes32(0), + paramsMap[0] + ); + + IMorpho morphoBlue = IMorpho(getRegisteredService(MORPHO_BLUE)); + morphoBlue.withdrawCollateral( + withdraw.marketParams, + mappedWithdrawAmount, + address(this), + withdraw.to + ); + + store().write(bytes32(mappedWithdrawAmount)); + } + + function parseInputs(bytes memory _callData) public pure returns (WithdrawData memory params) { + return abi.decode(_callData, (WithdrawData)); + } +} + diff --git a/packages/dma-contracts/contracts/core/types/MorphoBlue.sol b/packages/dma-contracts/contracts/core/types/MorphoBlue.sol index 26e77822..6ecbc288 100644 --- a/packages/dma-contracts/contracts/core/types/MorphoBlue.sol +++ b/packages/dma-contracts/contracts/core/types/MorphoBlue.sol @@ -24,4 +24,5 @@ struct PaybackData { MarketParams marketParams; uint256 amount; address onBehalf; + bool paybackAll; } diff --git a/packages/dma-contracts/contracts/interfaces/morpho-blue/IMorpho.sol b/packages/dma-contracts/contracts/interfaces/morpho-blue/IMorpho.sol index 12142863..4982e737 100644 --- a/packages/dma-contracts/contracts/interfaces/morpho-blue/IMorpho.sol +++ b/packages/dma-contracts/contracts/interfaces/morpho-blue/IMorpho.sol @@ -3,57 +3,55 @@ pragma solidity >=0.5.0; type Id is bytes32; -struct MarketParams { - address loanToken; - address collateralToken; - address oracle; - address irm; - uint256 lltv; -} + struct MarketParams { + address loanToken; + address collateralToken; + address oracle; + address irm; + uint256 lltv; + } /// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest /// accrual. -struct Position { - uint256 supplyShares; - uint128 borrowShares; - uint128 collateral; -} + struct Position { + uint256 supplyShares; + uint128 borrowShares; + uint128 collateral; + } /// @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 additional shares accrued by `feeRecipient` since the last /// interest accrual. -struct Market { - uint128 totalSupplyAssets; - uint128 totalSupplyShares; - uint128 totalBorrowAssets; - uint128 totalBorrowShares; - uint128 lastUpdate; - uint128 fee; -} - -struct Authorization { - address authorizer; - address authorized; - bool isAuthorized; - uint256 nonce; - uint256 deadline; -} - -struct Signature { - uint8 v; - bytes32 r; - bytes32 s; -} - -/// @title IMorpho -/// @author Morpho Labs -/// @custom:contact security@morpho.org -/// @notice Interface of Morpho. -interface IMorpho { + struct Market { + uint128 totalSupplyAssets; + uint128 totalSupplyShares; + uint128 totalBorrowAssets; + uint128 totalBorrowShares; + uint128 lastUpdate; + uint128 fee; + } + + struct Authorization { + address authorizer; + address authorized; + bool isAuthorized; + uint256 nonce; + uint256 deadline; + } + + struct Signature { + uint8 v; + bytes32 r; + bytes32 s; + } + +/// @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,51 +64,20 @@ 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); /// @notice Whether the `lltv` is enabled. function isLltvEnabled(uint256 lltv) external view returns (bool); - /// @notice Whether `authorized` is authorized to modify `authorizer`'s positions. + /// @notice Whether `authorized` is authorized to modify `authorizer`'s position on all markets. /// @dev Anyone is authorized to modify their own positions, regardless of this variable. function isAuthorized(address authorizer, address authorized) external view returns (bool); /// @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; @@ -124,13 +91,14 @@ interface IMorpho { function enableLltv(uint256 lltv) external; /// @notice Sets the `newFee` for the given market `marketParams`. + /// @param newFee The new fee, scaled by WAD. /// @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`. @@ -143,7 +111,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. @@ -154,14 +124,18 @@ interface IMorpho { /// `liquidate` from being used under certain market conditions. /// - A very high price returned by the oracle can make the computation of `maxBorrow` in `_isHealthy` overflow, or /// the computation of `assetsRepaid` in `liquidate` overflow. + /// @dev The borrow share price of a market with less than 1e4 assets borrowed can be decreased by manipulations, to + /// the point where `totalBorrowShares` is very large and borrowing overflows. function createMarket(MarketParams memory marketParams) external; /// @notice Supplies `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's /// `onMorphoSupply` function with the given `data`. - /// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the caller - /// is guaranteed to have `assets` tokens pulled from their balance, but the possibility to mint a specific amount - /// of shares is given for full compatibility and precision. + /// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the + /// caller is guaranteed to have `assets` tokens pulled from their balance, but the possibility to mint a specific + /// amount of shares is given for full compatibility and precision. /// @dev Supplying a large amount can revert for overflow. + /// @dev Supplying an amount of shares may lead to supply more or fewer assets than expected due to slippage. + /// Consider using the `assets` parameter to avoid this. /// @param marketParams The market to supply assets to. /// @param assets The amount of assets to supply. /// @param shares The amount of shares to mint. @@ -177,10 +151,12 @@ interface IMorpho { bytes memory data ) external returns (uint256 assetsSupplied, uint256 sharesSupplied); - /// @notice Withdraws `assets` or `shares` on behalf of `onBehalf` to `receiver`. + /// @notice Withdraws `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`. /// @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. @@ -196,12 +172,14 @@ interface IMorpho { address receiver ) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn); - /// @notice Borrows `assets` or `shares` on behalf of `onBehalf` to `receiver`. - /// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the caller - /// is guaranteed to borrow `assets` of tokens, but the possibility to mint a specific amount of shares is given for - /// full compatibility and precision. + /// @notice Borrows `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`. + /// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the + /// caller is guaranteed to borrow `assets` of tokens, but the possibility to mint a specific amount of shares is + /// given for full compatibility and precision. /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @dev Borrowing a large amount can revert for overflow. + /// @dev Borrowing an amount of shares may lead to borrow fewer assets than expected due to slippage. + /// Consider using the `assets` parameter to avoid this. /// @param marketParams The market to borrow assets from. /// @param assets The amount of assets to borrow. /// @param shares The amount of shares to mint. @@ -221,6 +199,9 @@ 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. + /// @dev An attacker can front-run a repay with a small repay making the transaction revert for underflow. /// @param marketParams The market to repay assets to. /// @param assets The amount of assets to repay. /// @param shares The amount of shares to burn. @@ -251,7 +232,7 @@ interface IMorpho { bytes memory data ) external; - /// @notice Withdraws `assets` of collateral on behalf of `onBehalf` to `receiver`. + /// @notice Withdraws `assets` of collateral on behalf of `onBehalf` and sends the assets to `receiver`. /// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions. /// @dev Withdrawing an amount corresponding to more collateral than supplied will revert for underflow. /// @param marketParams The market to withdraw collateral from. @@ -271,6 +252,7 @@ interface IMorpho { /// @dev Either `seizedAssets` or `repaidShares` should be zero. /// @dev Seizing more than the collateral balance will underflow and revert without any error message. /// @dev Repaying more than the borrow balance will underflow and revert without any error message. + /// @dev An attacker can front-run a liquidation with a small repay making the transaction revert for underflow. /// @param marketParams The market of the position. /// @param borrower The owner of the position. /// @param seizedAssets The amount of collateral to seize. @@ -289,6 +271,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. @@ -310,6 +296,73 @@ interface IMorpho { 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); +}