Issue | Instances | |
---|---|---|
GAS-1 | Using bools for storage incurs overhead | 3 |
GAS-2 | For Operations that will not overflow, you could use unchecked | 150 |
GAS-3 | Use Custom Errors | 3 |
GAS-4 | Don't initialize variables with default value | 4 |
GAS-5 | Functions guaranteed to revert when called by normal users can be marked payable |
5 |
GAS-6 | ++i costs less gas than i++ , especially when it's used in for -loops (--i /i-- too) |
2 |
GAS-7 | Splitting require() statements that use && saves gas | 1 |
GAS-8 | Use != 0 instead of > 0 for unsigned integer comparison | 14 |
Use uint256(1) and uint256(2) for true/false to avoid a Gwarmaccess (100 gas), and to avoid Gsset (20000 gas) when changing from ‘false’ to ‘true’, after having been ‘true’ in the past. See source.
Instances (3):
File: Morpho.sol
62: mapping(address => bool) public isIrmEnabled;
64: mapping(uint256 => bool) public isLltvEnabled;
66: mapping(address => mapping(address => bool)) public isAuthorized;
Instances (150):
File: Morpho.sol
13: } from "./interfaces/IMorpho.sol";
13: } from "./interfaces/IMorpho.sol";
20: } from "./interfaces/IMorphoCallbacks.sol";
20: } from "./interfaces/IMorphoCallbacks.sol";
21: import {IIrm} from "./interfaces/IIrm.sol";
21: import {IIrm} from "./interfaces/IIrm.sol";
22: import {IERC20} from "./interfaces/IERC20.sol";
22: import {IERC20} from "./interfaces/IERC20.sol";
23: import {IOracle} from "./interfaces/IOracle.sol";
23: import {IOracle} from "./interfaces/IOracle.sol";
25: import "./libraries/ConstantsLib.sol";
25: import "./libraries/ConstantsLib.sol";
26: import {UtilsLib} from "./libraries/UtilsLib.sol";
26: import {UtilsLib} from "./libraries/UtilsLib.sol";
27: import {EventsLib} from "./libraries/EventsLib.sol";
27: import {EventsLib} from "./libraries/EventsLib.sol";
28: import {ErrorsLib} from "./libraries/ErrorsLib.sol";
28: import {ErrorsLib} from "./libraries/ErrorsLib.sol";
29: import {MathLib, WAD} from "./libraries/MathLib.sol";
29: import {MathLib, WAD} from "./libraries/MathLib.sol";
30: import {SharesMathLib} from "./libraries/SharesMathLib.sol";
30: import {SharesMathLib} from "./libraries/SharesMathLib.sol";
31: import {MarketParamsLib} from "./libraries/MarketParamsLib.sol";
31: import {MarketParamsLib} from "./libraries/MarketParamsLib.sol";
32: import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";
32: import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";
183: position[id][onBehalf].supplyShares += shares;
184: market[id].totalSupplyShares += shares.toUint128();
185: market[id].totalSupplyAssets += assets.toUint128();
216: position[id][onBehalf].supplyShares -= shares;
217: market[id].totalSupplyShares -= shares.toUint128();
218: market[id].totalSupplyAssets -= assets.toUint128();
251: position[id][onBehalf].borrowShares += shares.toUint128();
252: market[id].totalBorrowShares += shares.toUint128();
253: market[id].totalBorrowAssets += assets.toUint128();
283: position[id][onBehalf].borrowShares -= shares.toUint128();
284: market[id].totalBorrowShares -= shares.toUint128();
310: position[id][onBehalf].collateral += assets.toUint128();
332: position[id][onBehalf].collateral -= assets.toUint128();
366: WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
366: WAD.wDivDown(WAD - LIQUIDATION_CURSOR.wMulDown(WAD - marketParams.lltv))
380: position[id][borrower].borrowShares -= repaidShares.toUint128();
381: market[id].totalBorrowShares -= repaidShares.toUint128();
384: position[id][borrower].collateral -= seizedAssets.toUint128();
394: market[id].totalBorrowAssets -= badDebt.toUint128();
395: market[id].totalSupplyAssets -= badDebt.toUint128();
396: market[id].totalBorrowShares -= badDebtShares.toUint128();
437: require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
437: require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
472: uint256 elapsed = block.timestamp - market[id].lastUpdate;
478: market[id].totalBorrowAssets += interest.toUint128();
479: market[id].totalSupplyAssets += interest.toUint128();
486: feeShares = feeAmount.toSharesDown(market[id].totalSupplyAssets - feeAmount, market[id].totalSupplyShares);
487: position[id][feeRecipient].supplyShares += feeShares;
488: market[id].totalSupplyShares += feeShares.toUint128();
536: bytes32 slot = slots[i++];
536: bytes32 slot = slots[i++];
538: assembly ("memory-safe") {
File: interfaces/IIrm.sol
4: import {MarketParams, Market} from "./IMorpho.sol";
File: libraries/EventsLib.sol
4: import {Id, MarketParams} from "../interfaces/IMorpho.sol";
4: import {Id, MarketParams} from "../interfaces/IMorpho.sol";
File: libraries/MarketParamsLib.sol
4: import {Id, MarketParams} from "../interfaces/IMorpho.sol";
4: import {Id, MarketParams} from "../interfaces/IMorpho.sol";
13: uint256 internal constant MARKET_PARAMS_BYTES_LENGTH = 5 * 32;
17: assembly ("memory-safe") {
File: libraries/MathLib.sol
28: return (x * y) / d;
28: return (x * y) / d;
33: return (x * y + (d - 1)) / d;
33: return (x * y + (d - 1)) / d;
33: return (x * y + (d - 1)) / d;
33: return (x * y + (d - 1)) / d;
39: uint256 firstTerm = x * n;
40: uint256 secondTerm = mulDivDown(firstTerm, firstTerm, 2 * WAD);
41: uint256 thirdTerm = mulDivDown(secondTerm, firstTerm, 3 * WAD);
43: return firstTerm + secondTerm + thirdTerm;
43: return firstTerm + secondTerm + thirdTerm;
File: libraries/SafeTransferLib.sol
4: import {IERC20} from "../interfaces/IERC20.sol";
4: import {IERC20} from "../interfaces/IERC20.sol";
6: import {ErrorsLib} from "../libraries/ErrorsLib.sol";
6: import {ErrorsLib} from "../libraries/ErrorsLib.sol";
File: libraries/SharesMathLib.sol
4: import {MathLib} from "./MathLib.sol";
25: return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
25: return assets.mulDivDown(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
30: return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
30: return shares.mulDivDown(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
35: return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
35: return assets.mulDivUp(totalShares + VIRTUAL_SHARES, totalAssets + VIRTUAL_ASSETS);
40: return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
40: return shares.mulDivUp(totalAssets + VIRTUAL_ASSETS, totalShares + VIRTUAL_SHARES);
File: libraries/UtilsLib.sol
4: import {ErrorsLib} from "../libraries/ErrorsLib.sol";
4: import {ErrorsLib} from "../libraries/ErrorsLib.sol";
File: libraries/periphery/MorphoBalancesLib.sol
4: import {Id, MarketParams, Market, IMorpho} from "../../interfaces/IMorpho.sol";
4: import {Id, MarketParams, Market, IMorpho} from "../../interfaces/IMorpho.sol";
4: import {Id, MarketParams, Market, IMorpho} from "../../interfaces/IMorpho.sol";
5: import {IIrm} from "../../interfaces/IIrm.sol";
5: import {IIrm} from "../../interfaces/IIrm.sol";
5: import {IIrm} from "../../interfaces/IIrm.sol";
7: import {MathLib} from "../MathLib.sol";
8: import {UtilsLib} from "../UtilsLib.sol";
9: import {MorphoLib} from "./MorphoLib.sol";
10: import {SharesMathLib} from "../SharesMathLib.sol";
11: import {MarketParamsLib} from "../MarketParamsLib.sol";
42: uint256 elapsed = block.timestamp - market.lastUpdate;
48: market.totalBorrowAssets += interest.toUint128();
49: market.totalSupplyAssets += interest.toUint128();
56: feeAmount.toSharesDown(market.totalSupplyAssets - feeAmount, market.totalSupplyShares);
57: market.totalSupplyShares += feeShares.toUint128();
File: libraries/periphery/MorphoLib.sol
4: import {IMorpho, Id} from "../../interfaces/IMorpho.sol";
4: import {IMorpho, Id} from "../../interfaces/IMorpho.sol";
4: import {IMorpho, Id} from "../../interfaces/IMorpho.sol";
5: import {MorphoStorageLib} from "./MorphoStorageLib.sol";
File: libraries/periphery/MorphoStorageLib.sol
4: import {Id} from "../../interfaces/IMorpho.sol";
4: import {Id} from "../../interfaces/IMorpho.sol";
4: import {Id} from "../../interfaces/IMorpho.sol";
51: uint256(keccak256(abi.encode(user, keccak256(abi.encode(id, POSITION_SLOT))))) + SUPPLY_SHARES_OFFSET
58: + BORROW_SHARES_AND_COLLATERAL_OFFSET
63: return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + TOTAL_SUPPLY_ASSETS_AND_SHARES_OFFSET);
67: return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + TOTAL_BORROW_ASSETS_AND_SHARES_OFFSET);
71: return bytes32(uint256(keccak256(abi.encode(id, MARKET_SLOT))) + LAST_UPDATE_AND_FEE_OFFSET);
91: return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + LOAN_TOKEN_OFFSET);
95: return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + COLLATERAL_TOKEN_OFFSET);
99: return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + ORACLE_OFFSET);
103: return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + IRM_OFFSET);
107: return bytes32(uint256(keccak256(abi.encode(id, ID_TO_MARKET_PARAMS_SLOT))) + LLTV_OFFSET);
File: mocks/ERC20Mock.sol
4: import {IERC20} from "./interfaces/IERC20.sol";
4: import {IERC20} from "./interfaces/IERC20.sol";
13: if (amount > balanceOf[account]) totalSupply += amount - balanceOf[account];
13: if (amount > balanceOf[account]) totalSupply += amount - balanceOf[account];
14: else totalSupply -= balanceOf[account] - amount;
14: else totalSupply -= balanceOf[account] - amount;
30: balanceOf[msg.sender] -= amount;
31: balanceOf[to] += amount;
41: allowance[from][msg.sender] -= amount;
45: balanceOf[from] -= amount;
46: balanceOf[to] += amount;
File: mocks/FlashBorrowerMock.sol
4: import {IERC20} from "./interfaces/IERC20.sol";
4: import {IERC20} from "./interfaces/IERC20.sol";
5: import {IMorpho} from "../interfaces/IMorpho.sol";
5: import {IMorpho} from "../interfaces/IMorpho.sol";
6: import {IMorphoFlashLoanCallback} from "../interfaces/IMorphoCallbacks.sol";
6: import {IMorphoFlashLoanCallback} from "../interfaces/IMorphoCallbacks.sol";
File: mocks/IrmMock.sol
4: import {IIrm} from "../interfaces/IIrm.sol";
4: import {IIrm} from "../interfaces/IIrm.sol";
5: import {MarketParams, Market} from "../interfaces/IMorpho.sol";
5: import {MarketParams, Market} from "../interfaces/IMorpho.sol";
7: import {MathLib} from "../libraries/MathLib.sol";
7: import {MathLib} from "../libraries/MathLib.sol";
19: return utilization / 365 days;
File: mocks/OracleMock.sol
4: import {IOracle} from "../interfaces/IOracle.sol";
4: import {IOracle} from "../interfaces/IOracle.sol";
Source Instead of using error strings, to reduce deployment and runtime cost, you should use Custom Errors. This would save both deployment and runtime cost.
Instances (3):
File: mocks/ERC20Mock.sol
28: require(balanceOf[msg.sender] >= amount, "insufficient balance");
39: require(allowance[from][msg.sender] >= amount, "insufficient allowance");
43: require(balanceOf[from] >= amount, "insufficient balance");
Instances (4):
File: 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;
If a function modifier such as onlyOwner
is used, the function will revert if a normal user tries to pay the function. Marking the function as payable
will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided.
Instances (5):
File: 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 {
Saves 5 gas per loop
Instances (2):
File: Morpho.sol
437: require(authorization.nonce == nonce[authorization.authorizer]++, ErrorsLib.INVALID_NONCE);
536: bytes32 slot = slots[i++];
Instances (1):
File: Morpho.sol
443: require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);
Instances (14):
File: Morpho.sol
180: if (assets > 0) shares = assets.toSharesDown(market[id].totalSupplyAssets, market[id].totalSupplyShares);
189: if (data.length > 0) IMorphoSupplyCallback(msg.sender).onMorphoSupply(assets, data);
213: if (assets > 0) shares = assets.toSharesUp(market[id].totalSupplyAssets, market[id].totalSupplyShares);
248: if (assets > 0) shares = assets.toSharesUp(market[id].totalBorrowAssets, market[id].totalBorrowShares);
280: if (assets > 0) shares = assets.toSharesDown(market[id].totalBorrowAssets, market[id].totalBorrowShares);
290: if (data.length > 0) IMorphoRepayCallback(msg.sender).onMorphoRepay(assets, data);
314: if (data.length > 0) IMorphoSupplyCollateralCallback(msg.sender).onMorphoSupplyCollateral(assets, data);
369: if (seizedAssets > 0) {
405: if (data.length > 0) IMorphoLiquidateCallback(msg.sender).onMorphoLiquidate(repaidAssets, data);
File: interfaces/IERC20.sol
2: pragma solidity >=0.5.0;
File: interfaces/IIrm.sol
2: pragma solidity >=0.5.0;
File: interfaces/IMorpho.sol
2: pragma solidity >=0.5.0;
File: interfaces/IMorphoCallbacks.sol
2: pragma solidity >=0.5.0;
File: interfaces/IOracle.sol
2: pragma solidity >=0.5.0;
Issue | Instances | |
---|---|---|
NC-1 | Return values of approve() not checked |
1 |
Not all IERC20 implementations revert()
when there's a failure in approve()
. The function signature has a boolean return value and they indicate errors that way instead. By not checking the return value, operations that should have marked as failed, may potentially go through without actually approving anything
Instances (1):
File: mocks/FlashBorrowerMock.sol
22: IERC20(token).approve(address(MORPHO), assets);
Issue | Instances | |
---|---|---|
L-1 | abi.encodePacked() should not be used with dynamic types when passing the result to a hash function such as keccak256() |
1 |
L-2 | Empty Function Body - Consider commenting why | 1 |
L-3 | Unsafe ERC20 operation(s) | 1 |
L-4 | Unspecific compiler version pragma | 5 |
L-5 | Use of ecrecover is susceptible to signature malleability | 1 |
[L-1] abi.encodePacked()
should not be used with dynamic types when passing the result to a hash function such as keccak256()
Use abi.encode()
instead which will pad items to 32 bytes, which will prevent hash collisions (e.g. abi.encodePacked(0x123,0x456)
=> 0x123456
=> abi.encodePacked(0x1,0x23456)
, but abi.encode(0x123,0x456)
=> 0x0...1230...456
). "Unless there is a compelling reason, abi.encode
should be preferred". If there is only one argument to abi.encodePacked()
it can often be cast to bytes()
or bytes32()
instead.
If all arguments are strings and or bytes, bytes.concat()
should be used instead
Instances (1):
File: Morpho.sol
440: bytes32 digest = keccak256(abi.encodePacked("\x19\x01", DOMAIN_SEPARATOR, hashStruct));
Instances (1):
File: interfaces/IERC20.sol
9: interface IERC20 {}
Instances (1):
File: mocks/FlashBorrowerMock.sol
22: IERC20(token).approve(address(MORPHO), assets);
Instances (5):
File: interfaces/IERC20.sol
2: pragma solidity >=0.5.0;
File: interfaces/IIrm.sol
2: pragma solidity >=0.5.0;
File: interfaces/IMorpho.sol
2: pragma solidity >=0.5.0;
File: interfaces/IMorphoCallbacks.sol
2: pragma solidity >=0.5.0;
File: interfaces/IOracle.sol
2: pragma solidity >=0.5.0;
The built-in EVM precompile ecrecover is susceptible to signature malleability, which could lead to replay attacks.Consider using OpenZeppelin’s ECDSA library instead of the built-in function.
Instances (1):
File: Morpho.sol
441: address signatory = ecrecover(digest, signature.v, signature.r, signature.s);
Issue | Instances | |
---|---|---|
M-1 | Centralization Risk for trusted owners | 5 |
Contracts have owners with privileged rights to perform admin tasks and need to be trusted to not perform malicious updates or drain funds.
Instances (5):
File: 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 {