code-423n4 / 2022-03-sublime-findings

0 stars 0 forks source link

Gas Optimizations #36

Open code423n4 opened 2 years ago

code423n4 commented 2 years ago

Multiple mappings can be combined into a single mapping of a value to a struct

  1. File: contracts/PooledCreditLine/LenderPool.sol (lines 109-121)
    /**
     * @notice Mapping that stores constants for pooledCredit creditLine against it's id
     */
    mapping(uint256 => LenderPoolConstants) public pooledCLConstants;
    /**
     * @notice Mapping that stores variables for pooledCredit creditLine against it's id
     */
    mapping(uint256 => LenderPoolVariables) public pooledCLVariables;
    /**
     * @notice Mapping that stores total pooledCreditLine token supply against the creditLineId
     * @dev Since ERC1155 tokens don't support the totalSupply function it is maintained here
     */
    mapping(uint256 => uint256) public totalSupply;
  2. File: contracts/PooledCreditLine/PooledCreditLine.sol (lines 184-198)

    /**
     * @notice stores the collateral shares in a pooled credit line per collateral strategy
     * @dev creditLineId => collateralShares
     **/
    mapping(uint256 => uint256) public depositedCollateralInShares;
    
    /**
     * @notice stores the variables to maintain a pooled credit line
     **/
    mapping(uint256 => PooledCreditLineVariables) public pooledCreditLineVariables;
    
    /**
     * @notice stores the constants related to a pooled credit line
     **/
    mapping(uint256 => PooledCreditLineConstants) public pooledCreditLineConstants;

++i/i++ should be unchecked{++i}/unchecked{++i} when it is not possible for them to overflow, as is the case when used in for- and while-loops

  1. File: contracts/PooledCreditLine/LenderPool.sol (line 670)
        for (uint256 i; i < ids.length; ++i) {

<array>.length should not be looked up in every loop of a for-loop

Even memory arrays incur the overhead of bit tests and bit shifts to calculate the array length

  1. File: contracts/PooledCreditLine/LenderPool.sol (line 670)
        for (uint256 i; i < ids.length; ++i) {

Using calldata instead of memory for read-only arguments in external functions saves gas

  1. File: contracts/Verification/twitterVerifier.sol (line 88)
        string memory _name,
  2. File: contracts/Verification/twitterVerifier.sol (line 89)
        string memory _version
  3. File: contracts/Verification/twitterVerifier.sol (line 120)
        string memory _twitterId,
  4. File: contracts/Verification/twitterVerifier.sol (line 121)
        string memory _tweetId,

internal functions only called once can be inlined to save gas

  1. File: contracts/PooledCreditLine/LenderPool.sol (lines 694-698)
    function _rebalanceInterestWithdrawn(
        uint256 id,
        uint256 amount,
        address from,
        address to
  2. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 388)
    function _limitBorrowedInUSD(address _borrowToken, uint256 _borrowLimit, uint256 _minBorrowAmount) internal view {
  3. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 671)
    function _createRequest(Request calldata _request) internal returns (uint256) {
  4. File: contracts/PooledCreditLine/PooledCreditLine.sol (lines 693-701)
    function _notifyRequest(
        uint256 _id,
        address _lenderVerifier,
        address _borrowToken,
        address _borrowAssetStrategy,
        uint256 _borrowLimit,
        uint256 _minBorrowedAmount,
        uint256 _collectionPeriod,
        bool _areTokensTransferable
  5. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 897)
    function _borrow(uint256 _id, uint256 _amount) internal {
  6. File: contracts/PooledCreditLine/PooledCreditLine.sol (lines 955-959)
    function _withdrawBorrowAmount(
        address _asset,
        address _strategy,
        uint256 _amountInTokens
    ) internal returns (uint256) {
  7. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1019)
    function _repay(uint256 _id, uint256 _amount) internal returns (uint256) {
  8. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1223)
    function updateStateOnPrincipalChange(uint256 _id, uint256 _updatedPrincipal) internal {

Multiple if-statements with mutually-exclusive conditions should be changed to if-else statements

If two conditions are the same, their blocks should be combined

  1. File: contracts/PooledCreditLine/LenderPool.sol (lines 676-688)

            if (from == address(0)) {
                totalSupply[id] = totalSupply[id].add(amount);
            } else if (to == address(0)) {
                uint256 supply = totalSupply[id];
                require(supply >= amount, 'T3');
                totalSupply[id] = supply - amount;
            } else {
                require(pooledCLConstants[id].areTokensTransferable, 'T4');
            }
    
            if (from == address(0)) {
                return;
            }

Use a more recent version of solidity

Use a solidity version of at least 0.8.0 to get overflow protection without SafeMath Use a solidity version of at least 0.8.2 to get compiler automatic inlining Use a solidity version of at least 0.8.3 to get better struct packing and cheaper multiple storage reads Use a solidity version of at least 0.8.4 to get custom errors, which are cheaper at deployment than revert()/require() strings Use a solidity version of at least 0.8.10 to have external calls skip contract existence checks if the external call has a return value

  1. File: contracts/Verification/twitterVerifier.sol (line 2)
    pragma solidity 0.7.6;
  2. File: contracts/PooledCreditLine/LenderPool.sol (line 2)
    pragma solidity 0.7.6;
  3. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 2)
    pragma solidity 0.7.6;

Splitting require() statements that use && saves gas

See this issue for an example

  1. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 642)
        require(_request.borrowAsset != address(0) && _request.collateralAsset != address(0), 'R4');

require() or revert() statements that check input arguments should be at the top of the function

  1. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 394)
        require(_minBorrowAmount <= _borrowLimit, 'ILB2');
  2. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 778)
        require(_amount <= _withdrawableCollateral, 'WC1');
  3. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 900)
        require(_amount <= calculateBorrowableAmount(_id), 'B3');

State variables should be cached in stack variables rather than re-reading them from storage

The instances below point to the second access of a state variable within a function. Less obvious optimizations include having local storage variables of mappings within state variable mappings or mappings within state variable structs, having local storage variables of structs within mappings, or having local caches of state variable contracts/addresses.

  1. File: contracts/Verification/twitterVerifier.sol (line 209)
        emit SignerUpdated(signerAddress);
  2. File: contracts/PooledCreditLine/LenderPool.sol (line 267)
            SAVINGS_ACCOUNT.approve(_borrowAsset, address(POOLED_CREDIT_LINE), type(uint256).max);
  3. File: contracts/PooledCreditLine/LenderPool.sol (line 267)
            SAVINGS_ACCOUNT.approve(_borrowAsset, address(POOLED_CREDIT_LINE), type(uint256).max);
  4. File: contracts/PooledCreditLine/LenderPool.sol (line 258)
        pooledCLConstants[_id].borrowAsset = _borrowAsset;
  5. File: contracts/PooledCreditLine/LenderPool.sol (line 283)
        require(block.timestamp < pooledCLConstants[_id].startTime, 'L2');
  6. File: contracts/PooledCreditLine/LenderPool.sol (line 318)
        address _borrowAsset = pooledCLConstants[_id].borrowAsset;
  7. File: contracts/PooledCreditLine/LenderPool.sol (line 334)
        address _strategy = pooledCLConstants[_id].borrowAssetStrategy;
  8. File: contracts/PooledCreditLine/LenderPool.sol (line 396)
        address _borrowAsset = pooledCLConstants[_id].borrowAsset;
  9. File: contracts/PooledCreditLine/LenderPool.sol (line 431)
        address _borrowAsset = pooledCLConstants[_id].borrowAsset;
  10. File: contracts/PooledCreditLine/LenderPool.sol (line 534)
                block.timestamp > pooledCLConstants[_id].startTime &&
  11. File: contracts/PooledCreditLine/LenderPool.sol (line 586)
            totalSupply[_id] < pooledCLConstants[_id].minBorrowAmount
  12. File: contracts/PooledCreditLine/LenderPool.sol (line 683)
                require(pooledCLConstants[id].areTokensTransferable, 'T4');
  13. File: contracts/PooledCreditLine/LenderPool.sol (line 356)
        pooledCLVariables[_id].sharesHeld = pooledCLVariables[_id].sharesHeld.sub(_sharesBorrowed);
  14. File: contracts/PooledCreditLine/LenderPool.sol (line 371)
        pooledCLVariables[_id].sharesHeld = pooledCLVariables[_id].sharesHeld.add(_sharesRepaid);
  15. File: contracts/PooledCreditLine/LenderPool.sol (line 439)
        pooledCLVariables[_id].sharesHeld = pooledCLVariables[_id].sharesHeld.sub(_interestSharesToWithdraw);
  16. File: contracts/PooledCreditLine/LenderPool.sol (line 465)
        uint256 _borrowerInterestShares = pooledCLVariables[_id].borrowerInterestShares;
  17. File: contracts/PooledCreditLine/LenderPool.sol (line 563)
            pooledCLVariables[_id].sharesHeld = pooledCLVariables[_id].sharesHeld.sub(_interestSharesWithdrawable);
  18. File: contracts/PooledCreditLine/LenderPool.sol (line 649)
            pooledCLVariables[_id].collateralHeld = pooledCLVariables[_id].collateralHeld.sub(_lenderCollateralShare);
  19. File: contracts/PooledCreditLine/LenderPool.sol (line 710)
        uint256 borrowerInterestOnTransferAmount = pooledCLVariables[id].lenders[from].borrowerInterestSharesWithdrawn.mul(amount).div(
  20. File: contracts/PooledCreditLine/LenderPool.sol (line 677)
                totalSupply[id] = totalSupply[id].add(amount);
  21. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 644)
        require(IStrategyRegistry(strategyRegistry).registry(_request.collateralAssetStrategy) != 0, 'R6');
  22. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 653)
        require(IVerification(verification).verifiers(_request.lenderVerifier), 'R14');
  23. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 392)
        require(isWithinLimits(_poolsizeInUSD, _borrowLimitMin, borrowLimitLimits.max), 'ILB1');
  24. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 406)
        require(!(borrowLimitLimits.min == _min && borrowLimitLimits.max == _max), 'UBLL2');
  25. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 418)
        require(!(idealCollateralRatioLimits.min == _min && idealCollateralRatioLimits.max == _max), 'UICRL2');
  26. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 647)
        require(isWithinLimits(_request.collateralRatio, idealCollateralRatioLimits.min, idealCollateralRatioLimits.max), 'R9');
  27. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 430)
        require(!(borrowRateLimits.min == _min && borrowRateLimits.max == _max), 'UBRL2');
  28. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 646)
        require(isWithinLimits(_request.borrowRate, borrowRateLimits.min, borrowRateLimits.max), 'R8');
  29. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 442)
        require(!(collectionPeriodLimits.min == _min && collectionPeriodLimits.max == _max), 'UCPL2');
  30. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 648)
        require(isWithinLimits(_request.collectionPeriod, collectionPeriodLimits.min, collectionPeriodLimits.max), 'R10');
  31. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 454)
        require(!(durationLimits.min == _min && durationLimits.max == _max), 'UDL2');
  32. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 649)
        require(isWithinLimits(_request.duration, durationLimits.min, durationLimits.max), 'R11');
  33. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 466)
        require(!(defaultGracePeriodLimits.min == _min && defaultGracePeriodLimits.max == _max), 'UDGPL2');
  34. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 650)
        require(isWithinLimits(_request.defaultGracePeriod, defaultGracePeriodLimits.min, defaultGracePeriodLimits.max), 'R12');
  35. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 478)
        require(!(gracePenaltyRateLimits.min == _min && gracePenaltyRateLimits.max == _max), 'UGPRL2');
  36. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 651)
        require(isWithinLimits(_request.gracePenaltyRate, gracePenaltyRateLimits.min, gracePenaltyRateLimits.max), 'R13');
  37. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 759)
        depositedCollateralInShares[_id] = depositedCollateralInShares[_id].add(_sharesDeposited);
  38. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 846)
        depositedCollateralInShares[_id] = depositedCollateralInShares[_id].sub(_amountInShares);
  39. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 722)
        pooledCreditLineVariables[_id].status = PooledCreditLineStatus.ACTIVE;
  40. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 982)
        uint256 _currentPrincipal = pooledCreditLineVariables[_id].principal;
  41. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1042)
        uint256 _principal = pooledCreditLineVariables[_id].principal;
  42. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1063)
            pooledCreditLineVariables[_id].totalInterestRepaid
  43. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1100)
        pooledCreditLineVariables[_id].status = PooledCreditLineStatus.LIQUIDATED;
  44. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1132)
        require(pooledCreditLineVariables[_id].principal == 0, 'C2');
  45. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1203)
            pooledCreditLineVariables[_id].status = currentStatus;
  46. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1226)
        pooledCreditLineVariables[_id].lastPrincipalUpdateTime = block.timestamp;
  47. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 747)
        address _strategy = pooledCreditLineConstants[_id].collateralAssetStrategy;
  48. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 817)
            pooledCreditLineConstants[_id].borrowAsset
  49. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 865)
        address _strategy = pooledCreditLineConstants[_id].collateralAssetStrategy;
  50. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 875)
        address _borrowAsset = pooledCreditLineConstants[_id].borrowAsset;
  51. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 901)
        address _borrowAsset = pooledCreditLineConstants[_id].borrowAsset;
  52. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 934)
            pooledCreditLineConstants[_id].borrowAsset
  53. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1008)
            pooledCreditLineConstants[_id].borrowAsset
  54. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1022)
        address _borrowAsset = pooledCreditLineConstants[_id].borrowAsset;
  55. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1041)
        uint256 _penaltyRate = pooledCreditLineConstants[_id].gracePenaltyRate;
  56. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1087)
            currentCollateralRatio < pooledCreditLineConstants[_id].idealCollateralRatio ||
  57. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1117)
        address _borrowAsset = pooledCreditLineConstants[_id].borrowAsset;
  58. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1240)
            pooledCreditLineConstants[_id].borrowAsset

Usage of uints/ints smaller than 32 bytes (256 bits) incurs overhead

When using elements that are smaller than 32 bytes, your contract’s gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.

https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html Use a larger size then downcast where needed

  1. File: contracts/Verification/twitterVerifier.sol (line 117)
        uint8 _v,
  2. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 164)
        uint128 borrowLimit;
  3. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 165)
        uint128 borrowRate;

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.

  1. File: contracts/Verification/twitterVerifier.sol (line 171)
    function unregisterUser(address _user) external onlyOwner {
  2. File: contracts/Verification/twitterVerifier.sol (line 189)
    function updateVerification(address _verification) external onlyOwner {
  3. File: contracts/Verification/twitterVerifier.sol (line 203)
    function updateSignerAddress(address _signerAddress) external onlyOwner {
  4. File: contracts/Verification/twitterVerifier.sol (line 217)
    function updateSignValidity(uint256 _signValidity) external onlyOwner {
  5. File: contracts/PooledCreditLine/LenderPool.sol (lines 247-256)
    function create(
        uint256 _id,
        address _lenderVerifier,
        address _borrowAsset,
        address _borrowAssetStrategy,
        uint256 _borrowLimit,
        uint256 _minBorrowAmount,
        uint256 _collectionPeriod,
        bool _areTokensTransferable
    ) external onlyPooledCreditLine nonReentrant override {
  6. File: contracts/PooledCreditLine/LenderPool.sol (line 355)
    function borrowed(uint256 _id, uint256 _sharesBorrowed) external override onlyPooledCreditLine nonReentrant {
  7. File: contracts/PooledCreditLine/LenderPool.sol (lines 366-370)
    function repaid(
        uint256 _id,
        uint256 _sharesRepaid,
        uint256 _interestShares
    ) external override onlyPooledCreditLine nonReentrant {
  8. File: contracts/PooledCreditLine/LenderPool.sol (line 380)
    function requestCancelled(uint256 _id) external override onlyPooledCreditLine {
  9. File: contracts/PooledCreditLine/LenderPool.sol (line 394)
    function terminate(uint256 _id, address _to) external override onlyPooledCreditLine nonReentrant {
  10. File: contracts/PooledCreditLine/LenderPool.sol (line 752)
    function updateStartFeeFraction(uint256 _startFeeFraction) external onlyOwner {
  11. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 404)
    function updateBorrowLimitLimits(uint256 _min, uint256 _max) external onlyOwner {
  12. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 416)
    function updateIdealCollateralRatioLimits(uint256 _min, uint256 _max) external onlyOwner {
  13. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 428)
    function updateBorrowRateLimits(uint256 _min, uint256 _max) external onlyOwner {
  14. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 440)
    function updateCollectionPeriodLimits(uint256 _min, uint256 _max) external onlyOwner {
  15. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 452)
    function updateDurationLimits(uint256 _min, uint256 _max) external onlyOwner {
  16. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 464)
    function updateDefaultGracePeriodLimits(uint256 _min, uint256 _max) external onlyOwner {
  17. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 476)
    function updateGracePenaltyRateLimits(uint256 _min, uint256 _max) external onlyOwner {
  18. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 492)
    function updatePriceOracle(address _priceOracle) external onlyOwner {
  19. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 508)
    function updateSavingsAccount(address _savingsAccount) external onlyOwner {
  20. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 524)
    function updateProtocolFeeFraction(uint256 _protocolFeeFraction) external onlyOwner {
  21. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 540)
    function updateProtocolFeeCollector(address _protocolFeeCollector) external onlyOwner {
  22. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 556)
    function updateStrategyRegistry(address _strategyRegistry) external onlyOwner {
  23. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 572)
    function updateVerification(address _verification) external onlyOwner {
  24. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 720)
    function accept(uint256 _id, uint256 _amount) external nonReentrant override onlyLenderPool {
  25. File: contracts/PooledCreditLine/PooledCreditLine.sol (lines 772-776)
    function withdrawCollateral(
        uint256 _id,
        uint256 _amount,
        bool _toSavingsAccount
    ) external nonReentrant onlyCreditLineBorrower(_id) {
  26. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 791)
    function withdrawCollateral(uint256 _id, bool _toSavingsAccount) external nonReentrant onlyCreditLineBorrower(_id) {
  27. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 893)
    function borrow(uint256 _id, uint256 _amount) external nonReentrant onlyCreditLineBorrower(_id) {
  28. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1077)
    function liquidate(uint256 _id) external override nonReentrant onlyLenderPool returns (address, uint256) {
  29. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1130)
    function close(uint256 _id) external nonReentrant onlyCreditLineBorrower(_id) {
  30. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1143)
    function cancelRequest(uint256 _id) external nonReentrant onlyCreditLineBorrower(_id) {
  31. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1158)
    function terminate(uint256 _id) external nonReentrant onlyOwner {
  32. File: contracts/PooledCreditLine/PooledCreditLine.sol (line 1172)
    function cancelRequestOnLowCollection(uint256 _id) external nonReentrant override onlyLenderPool {
ritik99 commented 2 years ago

All suggestions are valid and the report is highly detailed.