Skip to content

Instantly share code, notes, and snippets.

@hrkrshnn
Created November 13, 2023 19:13
Show Gist options
  • Save hrkrshnn/7f08712222eced848115c025939ec619 to your computer and use it in GitHub Desktop.
Save hrkrshnn/7f08712222eced848115c025939ec619 to your computer and use it in GitHub Desktop.

Report

Gas Optimizations

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

[GAS-1] Using bools for storage incurs overhead

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;

[GAS-2] For Operations that will not overflow, you could use unchecked

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";

[GAS-3] Use Custom Errors

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");

[GAS-4] Don't initialize variables with default value

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;

[GAS-5] Functions guaranteed to revert when called by normal users can be marked payable

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 {

[GAS-6] ++i costs less gas than i++, especially when it's used in for-loops (--i/i-- too)

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++];

[GAS-7] Splitting require() statements that use && saves gas

Instances (1):

File: Morpho.sol

443:         require(signatory != address(0) && authorization.authorizer == signatory, ErrorsLib.INVALID_SIGNATURE);

[GAS-8] Use != 0 instead of > 0 for unsigned integer comparison

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;

Non Critical Issues

Issue Instances
NC-1 Return values of approve() not checked 1

[NC-1] Return values of approve() not checked

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);

Low Issues

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));

[L-2] Empty Function Body - Consider commenting why

Instances (1):

File: interfaces/IERC20.sol

9: interface IERC20 {}

[L-3] Unsafe ERC20 operation(s)

Instances (1):

File: mocks/FlashBorrowerMock.sol

22:         IERC20(token).approve(address(MORPHO), assets);

[L-4] Unspecific compiler version pragma

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;

[L-5] Use of ecrecover is susceptible to signature malleability

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);

Medium Issues

Issue Instances
M-1 Centralization Risk for trusted owners 5

[M-1] Centralization Risk for trusted owners

Impact:

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 {
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment