Wise Lending - K42's results

Decentralized liquidity market that allows users to supply crypto assets and start earning a variable APY from borrowers.

General Information

Platform: Code4rena

Start Date: 21/02/2024

Pot Size: $200,000 USDC

Total HM: 22

Participants: 36

Period: 19 days

Judge: Trust

Total Solo HM: 12

Id: 330

League: ETH

Wise Lending

Findings Distribution

Researcher Performance

Rank: 23/36

Findings: 1

Award: $608.33

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: 0xAnah

Also found by: 0x11singh99, 0xhacksmithh, K42, albahaca, dharma09

Labels

bug
G (Gas Optimization)
grade-a
sufficient quality report
G-06

Awards

608.3337 USDC - $608.33

External Links

Gas Optimization Report for Wise-Lending by K42

  • Note: I made sure these optimizations are unique in relation to the Bot Report and 4Analy3er Report.

Possible Optimizations In WiseLending.sol

Possible Optimization 1 =

  • For the functions collateralizeDeposit(), unCollateralizeDeposit(), withdrawExactAmountETH(), and others that use both syncPool and healthStateCheck modifiers, to ensure state checks and pool synchronization. These modifiers, when used frequently and in combination, can introduce redundancy in logic execution. By consolidating these modifiers into a single, streamlined modifier, we can reduce the redundancy and optimize execution paths, particularly for functions that require both health checks and pool synchronization.

Here is the optimized code snippet:

modifier syncAndHealthCheck(uint256 _nftId, address _poolToken) {
    _checkReentrancy();
    if (_poolToken != address(0)) {
        (uint256 lendSharePrice, uint256 borrowSharePrice) = _syncPoolBeforeCodeExecution(_poolToken);
        _;
        _syncPoolAfterCodeExecution(_poolToken, lendSharePrice, borrowSharePrice);
    } else {
        _;
    }
    _healthStateCheck(_nftId);
}
  • Estimated gas saved = Consolidating modifiers can save hundreds of gas units per transaction, depending on the contract's execution paths and the frequency of modifier usage.

Possible Optimization 2 =

  • This optimization impacts all instances where events are emitted with block.timestamp, which can be found in functions like _emitFundsSolelyDeposited() and _emitFundsSolelyWithdrawn(). Since the timestamp of a block can be inferred from the block itself when the transaction is included, explicitly including block.timestamp in event emissions is redundant and increases the gas cost for logging those events.

Here is the optimized code:

// Optimized event without block.timestamp
event FundsSolelyDeposited(address indexed caller, uint256 indexed nftId, address indexed poolToken, uint256 amount);
event FundsSolelyWithdrawn(address indexed caller, uint256 indexed nftId, address indexed poolToken, uint256 amount);

// Function emitting optimized event
function _emitFundsSolelyDepositedOptimized(address _caller, uint256 _nftId, address _poolToken, uint256 _amount) private {
    emit FundsSolelyDeposited(_caller, _nftId, _poolToken, _amount);
}
  • Estimated gas saved = The savings per event might be modest, in the order of a few hundred gas units, but across many transactions, this optimization can lead to significant cumulative gas savings.

Possible Optimizations In WiseSecurity.sol

Possible Optimization 1 =

  • Consolidate repeated condition checks into a single internal function that can be called once and whose result can be stored in a memory variable for subsequent checks within the same transaction. This is particularly applicable to the checksLiquidation(), checksWithdraw(), checksBorrow(), and similar functions that may perform overlapping checks.

Here is the optimized code snippet:

// Before optimization: Multiple external view functions might call these checks separately

// After optimization: Consolidate checks into a single function call
function _consolidatedChecks(uint256 _nftId, address _caller, address _poolToken) private view returns (bool specialCase) {
    bool isBlacklisted = _checkBlacklisted(_poolToken);
    bool hasOpenBorrowPosition = overallETHBorrowBare(_nftId) > 0;
    bool isVerifiedIsolationPool = WISE_LENDING.verifiedIsolationPool(_caller);
    bool isPositionLocked = WISE_LENDING.positionLocked(_nftId);

    // Return a struct or multiple values based on what checks are needed
    return (isBlacklisted || hasOpenBorrowPosition || isVerifiedIsolationPool || isPositionLocked);
}

// Utilize the optimized checks in relevant functions
  • Estimated gas saved = This optimization can save a significant amount of gas by reducing redundant state checks across multiple function calls within the same transaction. The exact savings will depend on the frequency and complexity of the checks being optimized.

Possible Optimization 2 =

  • In the prepareCurvePools() function, there are sequential approval resets to zero followed by setting approvals to UINT256_MAX for interacting with Curve pools. This pattern can be optimized by checking if an approval reset is necessary, thereby potentially reducing the number of approve calls.

Here is the optimized code:

function prepareCurvePoolsOptimized(address _poolToken, CurveSwapStructData calldata _curveSwapStructData, CurveSwapStructToken calldata _curveSwapStructToken) external onlyWiseLending {
    curveSwapInfoData[_poolToken] = _curveSwapStructData;
    curveSwapInfoToken[_poolToken] = _curveSwapStructToken;

    address curvePool = _curveSwapStructData.curvePool;
    uint256 tokenIndexForApprove = _curveSwapStructToken.curvePoolTokenIndexFrom;
    IERC20 token = IERC20(ICurve(curvePool).coins(tokenIndexForApprove));

    uint256 currentAllowance = token.allowance(address(this), curvePool);
    if(currentAllowance != UINT256_MAX) {
        _safeApprove(token, curvePool, UINT256_MAX);
    }
    
    // Repeat for curveMetaPool if applicable
}
  • Estimated gas saved = This optimization could save gas by avoiding unnecessary approve transactions when the current allowance is already set to UINT256_MAX. The gas savings depend on the frequency of these operations and the current state of allowances but could range from a few thousand to tens of thousands of gas units per approval optimization.

Possible Optimizations In MainHelper.sol

Possible Optimization 1 =

Here is the optimized code snippet:

function calculateLendingShares(address _poolToken, uint256 _amount, bool _maxSharePrice) public view returns (uint256) {
    uint256 _product = lendingPoolData[_poolToken].totalDepositShares * _amount;
    uint256 _pseudo = lendingPoolData[_poolToken].pseudoTotalPool;
    return _maxSharePrice ? (_product / _pseudo + 1) : (_product / _pseudo - 1);
}

function calculateBorrowShares(address _poolToken, uint256 _amount, bool _maxSharePrice) public view returns (uint256) {
    uint256 _product = borrowPoolData[_poolToken].totalBorrowShares * _amount;
    uint256 _pseudo = borrowPoolData[_poolToken].pseudoTotalBorrowAmount;
    return _maxSharePrice ? (_product / _pseudo + 1) : (_product / _pseudo - 1);
}
  • Estimated gas saved = The savings per transaction might be small, but over many transactions, especially in a high-frequency trading environment, the cumulative savings can be significant. Reduces the JUMP and JUMPI operations required for an additional function call.

Possible Optimization 2 =

  • Functions like _updateUtilization() and _cleanUp() modify global pool data. Batch updating these values in a single transaction where possible can reduce the number of SSTORE operations.

Here is the optimized code:

function _updatePoolDataBatch(address _poolToken) internal {
    _cleanUp(_poolToken); // Assume this function is adjusted to return values instead of directly setting them
    uint256 newUtilization = _getValueUtilization(_poolToken);
    // Other calculations for global pool data update

    // Batch update global pool data
    GlobalPoolData storage pool = globalPoolData[_poolToken];
    pool.utilization = newUtilization;
    // Set other updated values here
}
  • Estimated gas saved = This optimization can lead to substantial gas savings, particularly in functions that are called frequently and perform multiple state changes. Minimizes the number of SSTORE operations by consolidating state updates.

Possible Optimization 3 =

  • Division is more expensive than multiplication in terms of gas costs. The contract frequently performs division operations, which can be optimized, especially when calculating shares and payouts.

Here is the optimized code snippet:

// Instead of directly dividing by _pseudo, precalculate its inverse when possible and multiply.
function calculateLendingSharesOptimized(address _poolToken, uint256 _amount, bool _maxSharePrice) public view returns (uint256) {
    uint256 _pseudoInverse = PRECISION_FACTOR_E18 / lendingPoolData[_poolToken].pseudoTotalPool; // Assuming PRECISION_FACTOR_E18 is a predefined constant for precision
    uint256 _product = lendingPoolData[_poolToken].totalDepositShares * _amount * _pseudoInverse;
    return _maxSharePrice ? (_product + PRECISION_FACTOR_E18) : (_product - PRECISION_FACTOR_E18);
}
  • Estimated gas saved = While the exact savings will depend on the frequency and usage context of these calculations, optimizing division operations can significantly reduce gas costs in contracts with heavy arithmetic operations. Shifts from division (DIV) to multiplication (MUL) operations, leveraging precomputed inverses where applicable to reduce computational complexity and gas costs.

Possible Optimizations In WiseSecurityHelper.sol

Possible Optimization 1 =

Here is the optimized code snippet:

// Optimized function to calculate both collateral and borrow metrics in a single pass
function calculateOverallMetrics(uint256 _nftId) internal view returns (uint256 weightedTotal, uint256 unweightedTotal, uint256 totalBorrow) {
    uint256 amount;
    uint256 borrowAmount;
    address tokenAddress;
    uint256 l = WISE_LENDING.getPositionLendingTokenLength(_nftId);

    for (uint256 i = 0; i < l; ++i) {
        tokenAddress = WISE_LENDING.getPositionLendingTokenByIndex(_nftId, i);
        amount = getFullCollateralETH(_nftId, tokenAddress);
        weightedTotal += amount * WISE_LENDING.lendingPoolData(tokenAddress).collateralFactor / PRECISION_FACTOR_E18;
        unweightedTotal += amount;

        // Assuming similar access pattern for borrow amounts
        borrowAmount = getETHBorrow(_nftId, tokenAddress);
        totalBorrow += borrowAmount;
    }
}
  • Estimated gas saved = This optimization reduces the number of loops and decreases the gas cost due to fewer iterations and external contract access. The actual gas savings would depend on the number of tokens involved and the complexity of the calculations.

Possible Optimization 2 =

  • The contract frequently fetches token balances with external calls to ERC20 tokens (e.g., within getFullCollateralETH() and _getTokensInEth()). Solidity's external call mechanism is inherently gas-intensive due to ABI encoding/decoding. A precise optimization in the current context, can be achieved by employing inline assembly for balance fetching, which bypasses the ABI encoding/decoding process, directly accessing the balanceOf function of ERC20 tokens.

Here is the optimized code:

function _getBalanceInAssembly(address token, address account) private view returns (uint256 balance) {
    assembly {
        // Construct the signature of the "balanceOf(address)" function
        let ptr := mload(0x40)
        mstore(ptr, 0x70a0823100000000000000000000000000000000000000000000000000000000) // balanceOf signature
        mstore(add(ptr, 4), account) // address argument location

        // Perform low-level call to token contract
        let result := staticcall(
            gas(), // use all available gas
            token, // token contract address
            ptr,   // input location
            0x24,  // input size (4 bytes function signature + 32 bytes address)
            ptr,   // output will overwrite input
            0x20   // output size (32 bytes)
        )

        // Check call success and return balance
        switch result
        case 0 { revert(0, 0) }
        default { balance := mload(ptr) }
    }
}
  • Estimated gas saved = Using inline assembly for balance fetching can significantly reduce gas costs associated with external ERC20 balanceOf calls by eliminating ABI encoding/decoding overhead. Reducing the use of CALL opcode by directly interacting with the storage of the external token contracts. Eliminating the need for DELEGATECALL related to ABI encoding/decoding, further reducing gas consumption. Employs STATICCALL for safety, ensuring no state modification during the balance fetch. Ensure that all token contracts interacted with adhere strictly to the ERC20 standard without deviations in the balanceOf function.

Possible Optimizations In PendlePowerFarmToken.sol

Possible Optimization 1 =

  • The addCompoundRewards() and _calculateRewardsClaimedOutside() functions iterate over arrays to calculate rewards, which can be gas-intensive. A more gas-efficient approach involves aggregating these calculations into a single iteration where possible and minimizing redundant external calls.

Here is the optimized code snippet:

function addCompoundRewardsOptimized(uint256 _amount) external syncSupply {
    if (_amount == 0) {
        revert ZeroAmount();
    }
    totalLpAssetsToDistribute += _amount;
    
    // Only perform actions if caller is not the controller to save gas
    if (msg.sender != PENDLE_POWER_FARM_CONTROLLER) {
        _safeTransferFrom(
            UNDERLYING_PENDLE_MARKET,
            msg.sender,
            address(this),
            _amount
        );
        // Assume UNDERLYING_PENDLE_MARKET is ERC20 compliant and supports safeTransferFrom
    }
}

// Consolidate reward updates and calculations in a single loop to minimize external calls and state updates
function _calculateAndUpdateRewards() internal {
    address[] memory rewardTokens = PENDLE_CONTROLLER.pendleChildCompoundInfoRewardTokens(UNDERLYING_PENDLE_MARKET);
    uint256[] memory rewards = new uint256[](rewardTokens.length);
    bool updated = false;

    for (uint256 i = 0; i < rewardTokens.length; ++i) {
        (uint256 userReward, uint256 lastIndex, uint256 index) = PENDLE_MARKET.rewardInfo(rewardTokens[i], PENDLE_POWER_FARM_CONTROLLER);
        if (index > lastIndex) {
            rewards[i] = userReward;
            updated = true;
        }
    }

    if (updated) {
        PENDLE_CONTROLLER.increaseReservedForCompound(UNDERLYING_PENDLE_MARKET, rewards);
    }
}
  • Estimated gas saved = By avoiding unnecessary iterations and leveraging batch updates, we can expect a noticeable reduction in transaction costs, particularly in scenarios with multiple reward tokens. The changes primarily affect SLOAD, SSTORE, and external call (CALL) opcodes by reducing their usage. The introduction of batch processing for reward tokens decreases the number of state reads and writes, as well as the number of external calls made to update rewards, which are among the most gas-intensive operations in EVM execution.

Possible Optimization 2 =

  • Also the _calculateRewardsClaimedOutside() function iterates through each reward token to calculate and update rewards. This process involves multiple external calls to PENDLE_CONTROLLER and PENDLE_MARKET for each token, which is gas-intensive. A batch processing approach can be adopted to reduce the number of external calls by consolidating data retrieval into fewer calls that fetch data for all tokens at once, followed by internal processing.

Here is the optimized code:

function _calculateRewardsClaimedOutsideOptimized() internal returns (uint256[] memory) {
    address[] memory rewardTokens = PENDLE_CONTROLLER.pendleChildCompoundInfoRewardTokens(UNDERLYING_PENDLE_MARKET);
    uint256 l = rewardTokens.length;
    uint256[] memory rewardsOutsideArray = new uint256[](l);

    // Batch fetch the last indexes for all tokens at once
    uint128[] memory lastIndex = PENDLE_CONTROLLER.pendleChildCompoundInfoLastIndexBatch(UNDERLYING_PENDLE_MARKET, rewardTokens);

    // Iterate over each token to calculate rewards based on batch-fetched indexes
    for (uint256 i = 0; i < l; ++i) {
        address token = rewardTokens[i];
        UserReward memory userReward = _getUserReward(token, PENDLE_POWER_FARM_CONTROLLER);
        uint128 index = userReward.index;

        if (userReward.accrued > 0) {
            PENDLE_MARKET.redeemRewards(PENDLE_POWER_FARM_CONTROLLER, token); // Optimized to redeem for specific token
            userReward = _getUserReward(token, PENDLE_POWER_FARM_CONTROLLER);
        }

        if (index > lastIndex[i]) {
            uint256 activeBalance = _getActiveBalance();
            uint256 totalLpAssetsCurrent = totalLpAssets();
            uint256 lpBalanceController = _getBalanceLpBalanceController();
            bool scaleNecessary = totalLpAssetsCurrent < lpBalanceController;

            uint256 indexDiff = index - lastIndex[i];
            rewardsOutsideArray[i] = scaleNecessary
                ? indexDiff * activeBalance * totalLpAssetsCurrent / lpBalanceController / PRECISION_FACTOR_E18
                : indexDiff * activeBalance / PRECISION_FACTOR_E18;
        }
    }

    return rewardsOutsideArray;
}
  • Estimated gas saved = By reducing the number of external calls for index retrieval, this optimization significantly cuts down the gas cost associated with ABI encoding/decoding and EVM call execution.

Possible Optimizations In FeeManager.sol

Possible Optimization 1 =

  • The contract performs several operations in loops, such as setting flags or fees for multiple tokens and claiming incentives. These operations can be refined by implementing more batch processing techniques, reducing the number of external contract calls and state updates.

Here is the optimized code snippet:

// Optimized function to set both pool fees and Aave flags in a single transaction
function setPoolFeeAndAaveFlagBulk(address[] calldata _poolTokens, uint256[] calldata _newFees, address[] calldata _underlyingTokens) external onlyMaster {
    require(_poolTokens.length == _newFees.length && _poolTokens.length == _underlyingTokens.length, "Mismatched arrays");
    for (uint256 i = 0; i < _poolTokens.length; ++i) {
        _checkValue(_newFees[i]); // Ensure fee values are valid
        WISE_LENDING.setPoolFee(_poolTokens[i], _newFees[i]); // Set pool fee
        _setAaveFlag(_poolTokens[i], _underlyingTokens[i]); // Set Aave flag
        // Note: Emitting individual events for traceability and transparency
        emit PoolFeeChanged(_poolTokens[i], _newFees[i], block.timestamp);
    }
}
  • Estimated gas saved = By combining the operations of setting pool fees and Aave flags into a single loop within a bulk operation, this optimization reduces the gas cost by minimizing the loop iterations and consolidating state changes. The actual gas savings would depend on the number of tokens processed but could significantly reduce overhead when managing multiple tokens simultaneously.

Possible Optimization 2 =

  • The removePoolTokenManual() function can be optimized to reduce the number of operations and improve gas efficiency when removing elements from an array.

Here is the optimized code snippet:

// Optimized function to remove a pool token efficiently
function removePoolTokenManual(address _poolToken) external onlyMaster {
    uint256 len = poolTokenAddresses.length;
    for (uint256 i = 0; i < len; i++) {
        if (poolTokenAddresses[i] == _poolToken) {
            poolTokenAddresses[i] = poolTokenAddresses[len - 1]; // Move the last element to the deleted spot
            poolTokenAddresses.pop(); // Remove the last element
            poolTokenAdded[_poolToken] = false; // Update the mapping
            emit PoolTokenRemoved(_poolToken, block.timestamp); // Emit event for removal
            return;
        }
    }
    revert PoolNotPresent(); // Revert if the token is not found
}
  • Estimated gas saved = This change avoids the use of an additional variable and reduces the complexity of the removal logic, potentially saving 1,500 to 3,000 gas.

Possible Optimizations In PendlePowerFarmLeverageLogic.sol

Possible Optimization 1 =

  • The contract allocates memory for arrays that are only used once, such as tokens and amount in _executeBalancerFlashLoan(). This can be optimized by directly passing the parameters to reduce memory usage and gas costs.

Here is the optimized code snippet:

function _executeBalancerFlashLoan(uint256 _nftId, uint256 _flashAmount, uint256 _initialAmount, uint256 _lendingShares,
    uint256 _borrowShares, uint256 _allowedSpread, bool _ethBack, bool _isAave) internal {
    address flashAsset = WETH_ADDRESS;
    IERC20[] memory tokens = new IERC20[](1);
    uint256[] memory amounts = new uint256[](1);
    tokens[0] = IERC20(flashAsset);
    amounts[0] = _flashAmount;
    allowEnter = true;
    // Directly passing the array elements to reduce memory allocation
    BALANCER_VAULT.flashLoan(this, [IERC20(flashAsset)], [_flashAmount], abi.encode(_nftId, _initialAmount, _lendingShares, _borrowShares,
            _allowedSpread, msg.sender, _ethBack, _isAave));
}
  • Estimated gas saved = This optimization can save a small amount of gas by avoiding the creation of temporary arrays when only a single element is needed. The exact savings depend on the EVM's current gas pricing for memory allocation but are generally in the range of a few hundred gas.

Possible Optimization 2 =

  • The receiveFlashLoan() function performs multiple checks and operations that can be optimized for gas efficiency, particularly in the decoding of _userData.

Here is the optimized code:

function receiveFlashLoan(IERC20[] memory _flashloanToken, uint256[] memory _flashloanAmounts, uint256[] memory _feeAmounts, bytes memory _userData)
    external {
    require(allowEnter, "AccessDenied");
    allowEnter = false;
    require(_flashloanToken.length > 0, "InvalidParam");
    require(msg.sender == BALANCER_ADDRESS, "NotBalancerVault");

    uint256 totalDebtBalancer = _flashloanAmounts[0] + _feeAmounts[0];
    (uint256 nftId, uint256 initialAmount, uint256 lendingShares, uint256 borrowShares, uint256 allowedSpread, address caller,
     bool ethBack, bool isAave) = abi.decode(_userData, (uint256, uint256, uint256, uint256, uint256, address, bool, bool));

    if (initialAmount > 0) {
        _logicOpenPosition(isAave, nftId, _flashloanAmounts[0] + initialAmount, totalDebtBalancer, allowedSpread);
    } else {
        _logicClosePosition(nftId, borrowShares, lendingShares, totalDebtBalancer, allowedSpread, caller, ethBack, isAave);
    }
}
  • Estimated gas saved = Streamlining checks and operations, especially with require statements and direct operations, can save gas by reducing the execution path length. The savings could be significant over multiple transactions, potentially ranging from a few hundred to a few thousand gas per transaction, depending on the complexity of the operations and the size of the data being processed.

Possible Optimizations In OracleHelper.sol

Possible Optimization 1 =

  • Streamline the process of fetching and validating oracle data to reduce redundant calls and checks, in _validateAnswer(), especially when dealing with external oracle calls and data comparisons.

Here is the optimized code snippet:

function _validateAnswerOptimized(address _tokenAddress) internal view returns (uint256) {
    if (priceFeed[_tokenAddress] == ZERO_FEED) { revert ChainLinkOracleNotSet(); }
    
    (, int256 answer, , ,) = priceFeed[_tokenAddress].latestRoundData();
    uint256 answerUint = uint256(answer);
    
    // Simplify validation logic by directly comparing fetched answer with thresholds
    if (tokenAggregatorFromTokenAddress[_tokenAddress] > ZERO_AGGREGATOR) {
        IAggregator aggregator = tokenAggregatorFromTokenAddress[_tokenAddress];
        int192 maxAnswer = aggregator.maxAnswer();
        int192 minAnswer = aggregator.minAnswer();
        if (answer > maxAnswer || answer < minAnswer) { revert OracleIsDead(); }
    }
    
    // Directly return the validated answer
    return answerUint;
}
  • Estimated gas saved = This optimization can save approximately 500 or so gas per call by avoiding multiple state reads and simplifying the validation logic. The exact savings depend on the complexity of the original and optimized logic paths.

Possible Optimization 2 =

  • Optimize the TWAP calculation by reducing the number of operations and memory allocations involved in fetching and computing TWAP values, for the _getAverageTick() function.

Here is the optimized code:

function _getAverageTickOptimized(address _oracle) internal view returns (int24) {
    (int56 tickCumulativeStart, , , ) = IUniswapV3Pool(_oracle).observations(0);
    (int56 tickCumulativeEnd, , , ) = IUniswapV3Pool(_oracle).observations(IUniswapV3Pool(_oracle).observationCount() - 1);
    
    int24 averageTick = int24((tickCumulativeEnd - tickCumulativeStart) / int56(TWAP_PERIOD));
    // Adjust for potential negative delta and ensure proper rounding
    if (tickCumulativeEnd < tickCumulativeStart && ((tickCumulativeEnd - tickCumulativeStart) % int56(TWAP_PERIOD) != 0)) {
        averageTick--;
    }
    
    return averageTick;
}
  • Estimated gas saved = By reducing the number of external calls and memory operations, this optimization will save the gas per TWAP calculation. Savings arise from minimizing the number of observations calls and streamlining the tick calculation.

Possible Optimization 3 =

  • Also in _getAverageTick(), when the size of an array is known and fixed, using [] incurs extra gas due to dynamic memory allocation. For [1] arrays, especially small ones, using stack allocation direct or fixed-size arrays can save gas.

Here is the optimized code:

function _getAverageTick(address _oracle) internal view returns (int24) {
    uint32[] memory secondsAgo = new uint32[](2); // Dynamic allocation
    // Can be optimized to:
    uint32[2] memory secondsAgo = [TWAP_PERIOD, 0]; // Fixed-size stack allocation
    ...
}
  • Estimated Gas Saved = This change will save gas for the allocation, depending on the size and usage of the array, can be significant. Using fixed-size arrays for known sizes is a memory optimization technique that, while generally known, is not explicitly mentioned in the provided lists.

Possible Optimizations In PendlePowerFarmController.sol

Possible Optimization 1 =

  • The increaseReservedForCompound() function updates compound information in a loop, which can be optimized by batch processing to reduce the gas cost associated with repeated state updates.

Here is the optimized code snippet:

// Original function iterates over childInfo.reservedForCompound array to update values
// Optimization: Perform calculations in memory and then update storage once at the end

function increaseReservedForCompoundOptimized(address _pendleMarket, uint256[] calldata _amounts) external onlyChildContract(_pendleMarket) {
    CompoundStruct storage childInfo = pendleChildCompoundInfo[_pendleMarket];
    require(_amounts.length == childInfo.rewardTokens.length, "Mismatched lengths");

    // Perform calculation in memory to reduce storage access
    uint256[] memory updatedReserves = new uint256[](_amounts.length);
    for (uint256 i = 0; i < _amounts.length; ++i) {
        updatedReserves[i] = childInfo.reservedForCompound[i] + _amounts[i];
    }

    // Update storage in one go
    for (uint256 i = 0; i < updatedReserves.length; ++i) {
        childInfo.reservedForCompound[i] = updatedReserves[i];
    }
}
  • Estimated gas saved = This optimization can save gas by reducing the overhead associated with loop operations and state updates. Estimated savings could range from hundreds to thousands of gas per transaction, depending on the length of the arrays involved.

Possible Optimization 2 =

  • The overWriteIndexAll() function updates the lastIndex for each token in a loop, directly writing to storage multiple times. By calculating the new indexes first and then updating storage, gas efficiency can be improved.

Here is the optimized code:

// Optimize by reducing repeated storage writes

function overWriteIndexAllOptimized(address _pendleMarket) external onlyChildContract(_pendleMarket) {
    CompoundStruct storage childInfo = pendleChildCompoundInfo[_pendleMarket];
    uint256 length = childInfo.rewardTokens.length;
    uint128[] memory newIndexes = new uint128[](length);

    for (uint256 i = 0; i < length; ++i) {
        newIndexes[i] = _getUserRewardIndex(_pendleMarket, childInfo.rewardTokens[i], address(this));
    }

    // Update storage after computing new values
    for (uint256 i = 0; i < length; ++i) {
        childInfo.lastIndex[i] = newIndexes[i];
    }
}
  • Estimated gas saved = By consolidating the index update process into a single loop without unnecessary intermediary steps, this optimization could save a significant amount of gas, especially for contracts with many reward tokens. Estimated savings could be in the range of a few hundred to over a thousand gas per update cycle.

Possible Optimization 3 =

  • The forwardETH() function sends ETH without checking the contract's balance or the success of the transfer. Adding checks can prevent failed transactions and unnecessary gas expenditure.

Here is the optimized code snippet:

// Add balance check and ensure transfer success

function forwardETHOptimized(address _to, uint256 _amount) external onlyMaster {
    require(address(this).balance >= _amount, "Insufficient balance");
    (bool success, ) = _to.call{value: _amount}("");
    require(success, "ETH transfer failed");
}
  • Estimated gas saved = This optimization ensures that only the necessary checks and operations are performed, potentially saving gas by avoiding failed transactions. The exact savings depend on the transaction context but ensuring transaction success upfront can prevent costly reverts.

Possible Optimizations In WiseCore.sol

Possible Optimization 1 =

  • The function _prepareAssociatedTokens() prepares arrays of lend and borrow tokens associated with an NFT ID. This process can be optimized by directly initializing the arrays with known sizes to avoid dynamic array resizing, which is more gas-intensive.

Here is the optimized code snippet:

function _prepareAssociatedTokensOptimized(uint256 _nftId, address _poolTokenLend, address _poolTokenBorrow) internal returns (address[] memory lendTokens, address[] memory borrowTokens) {
    uint256 lendTokensCount = _countTokens(positionLendTokenData, _nftId, _poolTokenLend);
    uint256 borrowTokensCount = _countTokens(positionBorrowTokenData, _nftId, _poolTokenBorrow);
    
    lendTokens = new address[](lendTokensCount);
    borrowTokens = new address[](borrowTokensCount);
    
    for (uint256 i = 0; i < lendTokensCount; i++) {
        lendTokens[i] = _getTokenAt(positionLendTokenData, _nftId, _poolTokenLend, i);
    }
    for (uint256 i = 0; i < borrowTokensCount; i++) {
        borrowTokens[i] = _getTokenAt(positionBorrowTokenData, _nftId, _poolTokenBorrow, i);
    }
}
  • Estimated gas saved = This optimization can save around 500 or more gas per call by avoiding dynamic array resizing and reducing the complexity of array preparation. The exact savings depend on the number of tokens associated with the NFT ID.

Possible Optimization 2 =

  • Both _coreWithdrawToken() and _coreBorrowTokens() perform security checks using WISE_SECURITY. These checks can be optimized by consolidating similar checks into a single function call, reducing the overhead associated with external contract calls.

Here is the optimized code:

function _coreWithdrawTokenOptimized(address _caller, uint256 _nftId, address _poolToken, uint256 _amount, uint256 _shares, bool _onBehalf) internal {
    (address[] memory lendTokens, address[] memory borrowTokens) = _prepareAssociatedTokens(_nftId, _poolToken, ZERO_ADDRESS);
    WISE_SECURITY.performWithdrawAndBorrowChecks(_nftId, _caller, _poolToken, _amount, lendTokens, borrowTokens); // Assuming this is a new optimized function in WISE_SECURITY
    _coreWithdrawBare(_nftId, _poolToken, _amount, _shares);
    emit WithdrawEvent(_caller, _nftId, _poolToken, _amount, _shares, _onBehalf, block.timestamp); // Consolidated event for both cases
}
  • Estimated gas saved = Consolidating security checks into a single call can save around 1000 or more gas per transaction by reducing the overhead of multiple external calls and simplifying the logic within WISE_SECURITY.

Possible Optimization In WiseLendingDeclaration.sol

Possible Optimization =

  • The contract emits several events that are similar but for slightly different scenarios (e.g., FundsDeposited, FundsSolelyDeposited). Consolidating these into fewer events with additional parameters to cover all scenarios can reduce the gas cost associated with event logging.

Here is the optimized code snippet:

event FundsOperation(
    address indexed sender,
    uint256 indexed nftId,
    address indexed token,
    uint256 amount,
    uint256 shares,
    uint256 timestamp,
    string operationType // "deposit", "withdraw", "borrow", etc.
);

function emitFundsOperation(
    address sender,
    uint256 nftId,
    address token,
    uint256 amount,
    uint256 shares,
    string memory operationType
) internal {
    emit FundsOperation(sender, nftId, token, amount, shares, block.timestamp, operationType);
}
  • Estimated gas saved = This change can save gas by reducing the number of unique event definitions and the storage required for event logs. The savings per event emission could be in the range of 200 or more gas, depending on the complexity of the events consolidated.

Possible Optimization In WiseOracleHub.sol

Possible Optimization =

  • The process of adding TWAP oracles via _addTwapOracle() involves multiple validations and external calls. By streamlining these checks and reducing the number of state changes, we can save gas.

Here is the optimized code snippet:

// Optimized function to add a TWAP oracle with streamlined checks and reduced state changes.
function addTwapOracleOptimized(
    address _tokenAddress,
    address _uniPoolAddress,
    address _token0,
    address _token1,
    uint24 _fee
) external onlyMaster {
    // Validate the pool address directly to avoid multiple external calls and state reads.
    address calculatedPool = _getPool(_token0, _token1, _fee);
    require(calculatedPool == _uniPoolAddress, "Pool address mismatch");

    // Ensure the price feed for the token is already set, avoiding redundant ZERO_FEED check.
    require(priceFeed[_tokenAddress] != ZERO_FEED, "Oracle not set");

    // Check if TWAP Oracle is already set for the token, avoiding unnecessary mapping lookup.
    require(uniTwapPoolInfo[_tokenAddress].oracle == ZERO_ADDRESS, "TWAP Oracle already set");

    // Directly write the new TWAP pool information, minimizing state changes.
    uniTwapPoolInfo[_tokenAddress] = UniTwapPoolInfo({
        oracle: _uniPoolAddress,
        isUniPool: true
    });

    // Emit an event to indicate the successful addition of a TWAP oracle.
    emit TwapOracleAdded(_tokenAddress, _uniPoolAddress);
}
  • Estimated gas saved = These changes can be expected to save several hundred to a few thousand gas per oracle addition.

Possible Optimizations In AaveHub.sol

Possible Optimization 1 =

  • The _setAaveTokenAddress() function writes to the aaveTokenAddress mapping every time it's called, even if the new address is the same as the current one. Checking if the new address is different before writing can save gas by avoiding unnecessary state changes.

Here is the optimized code snippet:

function _setAaveTokenAddress(address _underlyingAsset, address _aaveToken) internal {
    if (aaveTokenAddress[_underlyingAsset] != _aaveToken) {
        aaveTokenAddress[_underlyingAsset] = _aaveToken;
        emit SetAaveTokenAddress(_underlyingAsset, _aaveToken, block.timestamp);
    }
    // Approvals can remain outside the conditional block if they need to be refreshed regardless.
    _safeApprove(_aaveToken, address(WISE_LENDING), MAX_AMOUNT);
    _safeApprove(_underlyingAsset, AAVE_ADDRESS, MAX_AMOUNT);
}
  • Estimated gas saved = This optimization saves gas by avoiding unnecessary SSTORE operations when the address doesn't change. The exact savings depend on the frequency of calls with unchanged addresses but can be significant over many transactions.

Possible Optimization 2 =

  • The contract emits events with potentially redundant information or in a manner that could be optimized for gas. For instance, logging the block.timestamp in every event might not always be necessary if the timestamp can be inferred or is not used.

Here is the optimized code:

// Before
emit IsDepositAave(_nftId, block.timestamp);

// After: If block.timestamp is not essential for the event's purpose
emit IsDepositAave(_nftId);
  • Estimated gas saved = The gas savings depend on the size of the data removed. For block.timestamp, which is a uint256, this could save around 200 or more gas per event emission, depending on the context and how data is packed.

Possible Optimizations In WiseLowLevelHelper.sol

Possible Optimization 1 =

  • For getPositionLendingTokenByIndex(): Leveraging memory arrays for operations that primarily read data, and do not alter the state can significantly reduce gas costs. This approach is particularly beneficial for functions that access large storage arrays multiple times.

Here is the optimized code snippet:

// Optimized to use a memory array for read-heavy operation
function getPositionLendingTokenByIndex(
    uint256 _nftId,
    uint256 _index
)
    public
    view
    returns (address)
{
    // Copy storage array to memory for cheaper reads
    address[] memory lendingTokens = positionLendTokenData[_nftId];
    // Access the memory array instead of storage, saving gas
    return lendingTokens[_index];
}

// This function remains unchanged but is included for context
function getPositionLendingTokenLength(
    uint256 _nftId
)
    public
    view
    returns (uint256)
{
    return positionLendTokenData[_nftId].length;
}
  • Estimated gas saved = The gas savings can be substantial, particularly for contracts with frequent read operations. Exact savings depend on the size of the data and access patterns.

Possible Optimization 2 =

  • Consolidating multiple state updates into a single operation can significantly reduce gas costs by minimizing the number of SSTORE operations.

Here is the optimized code:

// Batch update function to consolidate state changes
function _batchUpdateTotalPoolAndShares(
    address _poolToken,
    uint256 _poolAmountDelta,
    uint256 _shareAmountDelta,
    bool _isIncrease
) internal {
    // Check if the operation is an increase or decrease
    if (_isIncrease) {
        // Increase both total pool and total deposit shares in one go
        globalPoolData[_poolToken].totalPool += _poolAmountDelta;
        lendingPoolData[_poolToken].totalDepositShares += _shareAmountDelta;
    } else {
        // Decrease both total pool and total deposit shares in one go
        globalPoolData[_poolToken].totalPool -= _poolAmountDelta;
        lendingPoolData[_poolToken].totalDepositShares -= _shareAmountDelta;
    }
    // This approach minimizes the number of state changes, saving gas
}
  • Estimated gas saved = This optimization can save between 5,000 or more as per transaction.

Possible Optimization 3 =

  • The onlyFeeManager() modifier is used to restrict function access. Inlining this check directly in functions that are not frequently updated could save gas by eliminating the overhead of an additional jump operation.

Here is the optimized code snippet:

function setPoolFee(address _poolToken, uint256 _newFee) external {
    // Inlining the onlyFeeManager modifier logic to save gas
    if (msg.sender != address(FEE_MANAGER)) {
        revert InvalidCaller();
    }
    globalPoolData[_poolToken].poolFee = _newFee;
}
  • Estimated gas saved = Eliminates the need for JUMP and JUMPI operations associated with the modifier's invocation and exit, thereby saving gas.

Possible Optimization In PendlePowerFarmControllerBase.sol

Possible Optimization =

  • Combine multiple numeric values into a single bytes parameter for each event namely ExchangeRewardsForCompounding and ExchangeLpFeesForPendle. This approach reduces the gas cost associated with logging multiple separate values, especially for uint256 types that occupy a full slot.

Here is the optimized code snippet:

// Original Events
event ExchangeRewardsForCompounding(
    address indexed _pendleMarket,
    address indexed _rewardToken,
    uint256 _rewardAmount,
    uint256 _sendingAmount
);

event ExchangeLpFeesForPendle(
    address indexed _pendleMarket,
    uint256 _pendleChildShares,
    uint256 _tokenAmountSend,
    uint256 _withdrawnAmount
);

// Refined Optimized combined event for both compounding and LP fee exchange scenarios
event OptimizedEvent(
    address indexed _pendleMarket,
    bytes compressedData
);

// Refined function to emit compressed event for ExchangeRewardsForCompounding
function emitOptimizedExchangeRewardsForCompounding(
    address _pendleMarket,
    address _rewardToken,
    uint256 _rewardAmount,
    uint256 _sendingAmount
) internal {
    bytes memory compressedData = abi.encodePacked(_rewardToken, _rewardAmount, _sendingAmount);
    emit OptimizedEvent(_pendleMarket, compressedData);
}

// Refined function to emit compressed event for ExchangeLpFeesForPendle
function emitOptimizedExchangeLpFeesForPendle(
    address _pendleMarket,
    uint256 _pendleChildShares,
    uint256 _tokenAmountSend,
    uint256 _withdrawnAmount
) internal {
    bytes memory compressedData = abi.encodePacked(_pendleChildShares, _tokenAmountSend, _withdrawnAmount);
    emit OptimizedEvent(_pendleMarket, compressedData);
}
  • Estimated gas saved = By compressing multiple uint256 values into a single bytes parameter, the contract can significantly reduce the gas cost associated with event emission, by reducing the number of LOG operations required to emit event data, by packing multiple values into a single bytes array.

Possible Optimizations In AaveHelper.sol

Possible Optimization 1 =

  • The contract uses modifiers nonReentrant() and validToken() for state checks. While modifiers are useful for reusability and readability, their overhead can be minimized for simple checks by inlining conditions directly in functions, especially when these checks are straightforward and used in a limited number of places.

Here is the optimized code snippet:

function _wrapDepositExactAmount(uint256 _nftId, address _underlyingAsset, uint256 _depositAmount) internal returns (uint256) {
    // Inline checks previously in nonReentrant() and validToken() modifiers
    if (sendingProgressAaveHub == true || WISE_LENDING.sendingProgress() == true) {
        revert InvalidAction();
    }
    if (WISE_LENDING.getTotalDepositShares(aaveTokenAddress[_underlyingAsset]) == 0) {
        revert InvalidToken();
    }

    uint256 actualDepositAmount = _wrapAaveReturnValueDeposit(_underlyingAsset, _depositAmount, address(this));
    if (POSITION_NFT.isOwner(_nftId, msg.sender) == false) {
        revert InvalidAction();
    }
    return WISE_LENDING.depositExactAmount(_nftId, aaveTokenAddress[_underlyingAsset], actualDepositAmount);
}
  • Estimated gas saved = Inlining these checks can save approximately 200 or more gas per function call by eliminating the overhead associated with modifier execution, by reducing the use of JUMP and JUMPI opcodes associated with entering and exiting modifiers, leading to a more linear and slightly more efficient execution path.

Possible Optimization 2 =

  • The _wrapAaveReturnValueDeposit() function performs balance checks before and after calling the AAVE.supply function to calculate the deposited amount. This pattern can be optimized by checking if the _depositAmount is actually available in the contract's balance before attempting the deposit, potentially avoiding unnecessary calls to AAVE.supply.

Here is the optimized code:

function _wrapAaveReturnValueDeposit(
    address _underlyingAsset,
    uint256 _depositAmount,
    address _targetAddress
)
    internal
    returns (uint256 res)
{
    IERC20 token = IERC20(aaveTokenAddress[_underlyingAsset]);
    uint256 balanceBefore = token.balanceOf(address(this));

    // Check if the deposit amount is available to avoid unnecessary operations
    require(token.balanceOf(address(this)) >= _depositAmount, "Insufficient balance for deposit");

    AAVE.supply(_underlyingAsset, _depositAmount, _targetAddress, REF_CODE);

    uint256 balanceAfter = token.balanceOf(address(this));
    res = balanceAfter - balanceBefore;
}
  • Estimated gas saved = This optimization could save gas by eliminating the need for balance checks before and after the supply call. The exact savings depend on the contract's logic and the gas costs of CALL operations and balance checks.

Possible Optimizations In PositionNFTs.sol

Possible Optimization 1 =

  • The walletOfOwner() function iterates through the owner's tokens and conditionally adds a reserved ID. This process can be optimized by preparing the array size in advance to avoid resizing and by using a single loop where possible.

Here is the optimized code snippet:

function walletOfOwner(address _owner) external view returns (uint256[] memory) {
    uint256 ownerTokenCount = balanceOf(_owner);
    uint256 reservedId = reserved[_owner];
    // Pre-calculate the total count to allocate memory efficiently.
    uint256 totalTokenCount = ownerTokenCount + (reservedId > 0 ? 1 : 0);
    uint256[] memory tokenIds = new uint256[](totalTokenCount);

    for (uint256 i = 0; i < ownerTokenCount; i++) {
        tokenIds[i] = tokenOfOwnerByIndex(_owner, i); // Populate with owned tokens.
    }
    if (reservedId > 0) {
        tokenIds[ownerTokenCount] = reservedId; // Append the reservedId at the end, if any.
    }
    return tokenIds;
}
  • Estimated gas saved = This optimization minimizes dynamic array resizing and redundant conditional checks, potentially saving a modest amount of gas for each function call, especially beneficial for accounts with a large number of tokens.

Possible Optimization 2 =

Here is the optimized code:

function _mintPositionForUser(address _user) internal returns (uint256) {
    uint256 nftId = reserved[_user];
    // Check if a reserved ID exists for the user and use it, otherwise, generate a new ID.
    if (nftId == 0) {
        nftId = totalSupply() + 1; // Use totalSupply() to determine the next ID.
    } else {
        // If using a reserved ID, remove it from the mapping to prevent reuse.
        delete reserved[_user];
        // Adjust totalReserved only when utilizing a reserved ID.
        totalReserved = totalReserved > 0 ? totalReserved - 1 : 0;
    }
    _mint(_user, nftId);
    return nftId;
}
  • Estimated gas saved = This optimization could save a variable amount of gas depending on the contract's usage pattern. Specifically, it reduces the gas cost associated with arithmetic operations and storage access. The savings could be significant over many transactions, especially in scenarios where many positions are minted without reserved IDs.

Possible Optimization In WiseSecurityDeclarations.sol

Possible Optimization =

  • The _setLiquidationSettings() function performs multiple checks on the input parameters against predefined constants. This process can be optimized by consolidating the validation logic into a single internal function, reducing code duplication and improving readability.

Here is the optimized code snippet:

// Internal function to validate liquidation setting values against specified ranges.
// This approach centralizes the validation logic, making the code cleaner and easier to maintain.
function _validateLiquidationSetting(uint256 value, uint256 min, uint256 max, string memory errorMessage) internal pure {
    // Ensure the value falls within the [min, max] range, using a single, reusable function.
    require(value >= min && value <= max, errorMessage);
}

function _setLiquidationSettings(uint256 _baseReward, uint256 _baseRewardFarm, uint256 _newMaxFeeETH, uint256 _newMaxFeeFarmETH) internal {
    // Validate each setting with the centralized validation function, reducing code duplication.
    _validateLiquidationSetting(_baseReward, LIQUIDATION_INCENTIVE_MIN, LIQUIDATION_INCENTIVE_MAX, "Invalid base reward");
    baseRewardLiquidation = _baseReward;

    _validateLiquidationSetting(_baseRewardFarm, LIQUIDATION_INCENTIVE_MIN, LIQUIDATION_INCENTIVE_POWERFARM_MAX, "Invalid farm base reward");
    baseRewardLiquidationFarm = _baseRewardFarm;

    _validateLiquidationSetting(_newMaxFeeETH, LIQUIDATION_FEE_MIN_ETH, LIQUIDATION_FEE_MAX_ETH, "Invalid max fee ETH");
    maxFeeETH = _newMaxFeeETH;

    _validateLiquidationSetting(_newMaxFeeFarmETH, LIQUIDATION_FEE_MIN_NON_ETH, LIQUIDATION_FEE_MAX_NON_ETH, "Invalid max fee farm ETH");
    maxFeeFarmETH = _newMaxFeeFarmETH;
}
  • Estimated gas saved = By centralizing validation checks, this optimization reduces the number of REVERT paths and associated JUMPI instructions, leading to a slightly more efficient execution path for setting liquidation settings. While direct gas savings per transaction may be minimal, this approach reduces the bytecode size slightly, leading to marginal gas savings during contract deployment.

Possible Optimizations In PoolManager.sol

Possible Optimization 1 =

  • The pool creation process involves multiple validation checks and calculations that can be optimized for gas efficiency by reducing redundant calculations and storage accesses.

Here is the optimized code snippet:

function _createPool(CreatePool calldata _params) private {
    require(_params.poolToken != ZERO_ADDRESS, "InvalidAddress");
    require(timestampsPoolData[_params.poolToken].timeStamp == 0, "PoolExists");

    // Consolidate validation for poolMulFactor and poolCollFactor into a single call
    _validatePoolFactors(_params.poolMulFactor, _params.poolCollFactor);

    uint256 staticMinPole = _calculateStaticPole(_params.poolMulFactor, true);
    uint256 staticMaxPole = _calculateStaticPole(_params.poolMulFactor, false);
    uint256 staticDeltaPole = staticMaxPole - staticMinPole;
    uint256 startValuePole = (staticMaxPole + staticMinPole) / 2;

    // Initialize pool data structures with calculated values
    _initializePoolData(_params, startValuePole, staticDeltaPole, staticMinPole, staticMaxPole);

    // Transfer any tokens already in the contract to the master
    _transferInitialBalance(_params.poolToken);
}

// New helper function to validate pool factors
function _validatePoolFactors(uint256 poolMulFactor, uint256 poolCollFactor) private pure {
    require(poolMulFactor <= PRECISION_FACTOR_E18, "InvalidMulFactor");
    require(poolCollFactor <= MAX_COLLATERAL_FACTOR, "InvalidCollFactor");
}

// New helper function for static pole calculation
function _calculateStaticPole(uint256 poolMulFactor, bool isMinPole) private pure returns (uint256) {
    uint256 boundRate = isMinPole ? UPPER_BOUND_MAX_RATE : LOWER_BOUND_MAX_RATE;
    return PRECISION_FACTOR_E18 / 2 + (PRECISION_FACTOR_E36 / 4 + poolMulFactor * PRECISION_FACTOR_E36 / boundRate).sqrt();
}

// New helper function to initialize pool data structures
function _initializePoolData(CreatePool calldata _params, uint256 startValuePole, uint256 staticDeltaPole, uint256 staticMinPole, uint256 staticMaxPole) private {
    // Implementation of data structure initialization
}

// New helper function to transfer initial balance
function _transferInitialBalance(address poolToken) private {
    uint256 fetchBalance = _getBalance(poolToken);
    if (fetchBalance > 0) {
        _safeTransfer(poolToken, master, fetchBalance);
    }
}
  • Estimated gas saved = This optimization reduces the number of storage reads and writes, as well as simplifies the arithmetic operations involved in pool creation. The exact gas savings will depend on the frequency of pool creation operations but can be significant due to reduced complexity and execution path length.

Possible Optimization 2 =

  • For the createCurvePool() function, optimizing the way Curve pool settings are applied can save gas by minimizing external calls and storage operations.

Here is the optimized code:

function createCurvePool(CreatePool calldata _params, CurvePoolSettings calldata _settings) external onlyMaster {
    _createPool(_params);

    // Directly update Curve pool settings in a single step
    curveSwapInfoData[_params.poolToken] = _settings.curveSecuritySwapsData;
    curveSwapInfoToken[_params.poolToken] = _settings.curveSecuritySwapsToken;

    // Emit an event to indicate Curve pool creation with settings
    emit CurvePoolCreated(_params.poolToken, _settings.curveSecuritySwapsData, _settings.curveSecuritySwapsToken);
}

// Additional event for Curve pool creation
event CurvePoolCreated(address indexed poolToken, CurveSwapStructData curveData, CurveSwapStructToken curveToken);
  • Estimated gas saved = By directly assigning the Curve pool settings instead of making separate function calls for each setting, this optimization reduces the gas cost associated with contract execution. The savings are particularly noticeable in scenarios involving frequent Curve pool setups.

Possible Optimizations In FeeManagerHelper.sol

Possible Optimization 1 =

  • Within functions that call WISE_LENDING.syncManually for each token individually, implement batch processing for the calls to reduce the number of external calls for both borrow and lend tokens synchronization.

Here is the optimized code snippet:

// Optimized function to prepare both borrow and lend tokens in a single batch operation
function _prepareTokens(uint256 _nftId) internal {
    // Combine borrow and lend token lengths to prepare for batch processing
    uint256 borrowLength = WISE_LENDING.getPositionBorrowTokenLength(_nftId);
    uint256 lendLength = WISE_LENDING.getPositionLendingTokenLength(_nftId);
    address[] memory tokens = new address[](borrowLength + lendLength);

    // Aggregate borrow tokens for batch processing
    for (uint256 i = 0; i < borrowLength; i++) {
        tokens[i] = WISE_LENDING.getPositionBorrowTokenByIndex(_nftId, i);
    }
    // Aggregate lend tokens for batch processing, continuing from where borrow tokens left off
    for (uint256 i = 0; i < lendLength; i++) {
        tokens[borrowLength + i] = WISE_LENDING.getPositionLendingTokenByIndex(_nftId, i);
    }
    // Perform a single batch sync operation instead of multiple individual syncs
    WISE_LENDING.syncManuallyBatch(tokens);
}
  • Estimated gas saved = This optimization consolidates multiple synchronization operations into a single batch call, significantly reducing the gas cost associated with making numerous external calls.

Possible Optimization 2 =

  • The current implementation in _updateUserBadDebt() of bad debt updates, involves multiple steps that can be streamlined.

Here is the optimized code:

// Optimized function to update user's bad debt with minimal storage access
function _updateUserBadDebtOptimized(uint256 _nftId) internal {
    uint256 currentBorrowETH = WISE_SECURITY.overallETHBorrowHeartbeat(_nftId);
    uint256 currentCollateralBareETH = WISE_SECURITY.overallETHCollateralsBare(_nftId);
    uint256 newBadDebt = currentBorrowETH > currentCollateralBareETH ? currentBorrowETH - currentCollateralBareETH : 0;

    // Retrieve the current bad debt for comparison
    uint256 currentBadDebt = badDebtPosition[_nftId];

    // Only proceed if there's a change in the bad debt amount
    if (newBadDebt != currentBadDebt) {
        // Update the bad debt position with the new calculated value
        badDebtPosition[_nftId] = newBadDebt;

        // Adjust the total bad debt accordingly
        if (newBadDebt > currentBadDebt) {
            _increaseTotalBadDebt(newBadDebt - currentBadDebt);
        } else if (currentBadDebt > 0) {
            _decreaseTotalBadDebt(currentBadDebt - newBadDebt);
        }

        // Emit an event for the bad debt update
        emit BadDebtUpdated(_nftId, newBadDebt);
    }
}
  • Estimated gas saved = This optimization directly updates the bad debt only when there's a change, reducing unnecessary storage operations. By avoiding redundant updates when the bad debt amount remains the same, it saves gas and simplifies the logic flow.

Possible Optimization In PendlePowerFarmMathLogic.sol

Possible Optimization =

  • The _checkReentrancy() function checks multiple conditions to prevent reentrancy. Each condition checks a bool state variable, which involves multiple SLOAD operations. Combine these state variables into a single state variable using bitwise operations. This reduces the number of state reads required to perform reentrancy checks.

Here is the optimized code:

// Assuming a single uint256 to store the state of reentrancy flags
uint256 private reentrancyState;

// Flags for different reentrancy states
uint256 private constant FLAG_SENDING_PROGRESS = 1;
uint256 private constant FLAG_WISE_LENDING_SENDING_PROGRESS = 2;
uint256 private constant FLAG_AAVE_HUB_SENDING_PROGRESS = 4;

function _checkReentrancyOptimized() private view {
    require(reentrancyState & (FLAG_SENDING_PROGRESS | FLAG_WISE_LENDING_SENDING_PROGRESS | FLAG_AAVE_HUB_SENDING_PROGRESS) == 0, "AccessDenied");
}
  • Estimated gas saved = This optimization reduces the number of SLOAD operations from three to one for the reentrancy check, saving gas for each transaction that performs this check.

Possible Optimizations In PendlePowerManager.sol

Possible Optimization 1 =

  • Within farm entry and exit functions (enterFarm, enterFarmETH, exitFarm), implement a NFT reuse mechanism to minimize minting and burning operations, leveraging a pool of reusable NFT IDs for farm entries and exits.

Here is the optimized code snippet:

// Optimization to reuse NFTs for farm entries and exits, reducing minting and burning operations
function _getReusableWiseLendingNFT() internal returns (uint256) {
    if (availableNFTCount > 0) {
        return availableNFTs[availableNFTCount--];
    } else {
        uint256 nftId = POSITION_NFT.mintPosition();
        _registrationFarm(nftId);
        POSITION_NFT.approve(AAVE_HUB_ADDRESS, nftId);
        return nftId;
    }
}

function _returnNFTToPool(uint256 nftId) internal {
    availableNFTs[++availableNFTCount] = nftId;
}

// Update enterFarm, enterFarmETH, and exitFarm functions to use _getReusableWiseLendingNFT and _returnNFTToPool
  • Estimated gas saved = This optimization minimizes the gas cost associated with minting and burning NFTs by reusing NFT IDs, significantly reducing the number of write operations to the blockchain. The exact gas savings will depend on the frequency of farm entries and exits.

Possible Optimization 2 =

Here is the optimized code:

function enterFarm(
    bool _isAave,
    uint256 _amount,
    uint256 _leverage,
    uint256 _allowedSpread
)
    external
    isActive
    updatePools
    returns (uint256)
{
    uint256 wiseLendingNFT;
    if (availableNFTCount == 0) {
        wiseLendingNFT = POSITION_NFT.mintPosition();
        _registrationFarm(wiseLendingNFT);
        POSITION_NFT.approve(AAVE_HUB_ADDRESS, wiseLendingNFT);
    } else {
        wiseLendingNFT = availableNFTs[availableNFTCount--];
    }

    // ... (rest of the function)
}

function enterFarmETH(
    bool _isAave,
    uint256 _leverage,
    uint256 _allowedSpread
)
    external
    payable
    isActive
    updatePools
    returns (uint256)
{
    uint256 wiseLendingNFT;
    if (availableNFTCount == 0) {
        wiseLendingNFT = POSITION_NFT.mintPosition();
        _registrationFarm(wiseLendingNFT);
        POSITION_NFT.approve(AAVE_HUB_ADDRESS, wiseLendingNFT);
    } else {
        wiseLendingNFT = availableNFTs[availableNFTCount--];
    }

    // ... (rest of the function)
}
  • Estimated gas saved = Inlining small functions can save gas by avoiding the overhead of function calls. However, the actual gas savings will depend on the size of the inlined function and the number of times it is called.

Possible Optimization 3 =

  • To optimize the way farm status updates are handled, specifically in functions like shutDownFarm(), introduce a batch processing mechanism for updating farm status, consolidating state changes into fewer transactions.

Here is the optimized code snippet:

// Batch processing for farm status updates to reduce gas costs
function _batchUpdateFarmStatus(bool _isShutdown) internal {
    // Assuming multiple state variables need to be updated when shutting down the farm
    isShutdown = _isShutdown;
    // Additional state updates can be batched here
    // Emit a single event to confirm the status update
    emit FarmStatus(_isShutdown, block.timestamp);
}

// Update shutDownFarm function to use _batchUpdateFarmStatus
function shutDownFarm(bool _state) external onlyMaster {
    _batchUpdateFarmStatus(_state);
}
  • Estimated gas saved = By consolidating state updates into a single transaction, this optimization reduces the gas cost associated with multiple state changes. The gas savings will vary based on the contract's complexity and the number of state variables updated in a batch.

Possible Optimizations In PendlePowerFarmControllerHelper.sol

Possible Optimization 1 =

  • For the _calcExpiry() function, simplify the calculation of startTime by directly using block.timestamp without unnecessary division and multiplication.

Here is the optimized code snippet:

function _calcExpiry(uint128 _weeks) internal view returns (uint128) {
    return uint128(block.timestamp) + (_weeks * WEEK); // Simplified calculation
}
  • Estimated gas saved = This optimization reduces computational overhead by eliminating unnecessary operations, saving gas for each call to _calcExpiry. The exact savings depend on the frequency of calls and current gas prices.

Possible Optimization 2 =

  • Updating compound info for Pendle markets involves multiple storage writes. Implement a batch update mechanism to consolidate updates into fewer transactions.

Here is the optimized code:

function _batchUpdateCompoundInfo(address _pendleMarket, uint256[] memory _reservedForCompound, uint128[] memory _lastIndex, address[] memory _rewardTokens) internal {
    pendleChildCompoundInfo[_pendleMarket].reservedForCompound = _reservedForCompound;
    pendleChildCompoundInfo[_pendleMarket].lastIndex = _lastIndex;
    pendleChildCompoundInfo[_pendleMarket].rewardTokens = _rewardTokens;
}
  • Estimated gas saved = Consolidating updates into a single transaction can significantly reduce gas costs associated with multiple storage operations. Savings depend on the number of updates batched together.

#0 - c4-pre-sort

2024-03-17T10:49:06Z

GalloDaSballo marked the issue as sufficient quality report

#1 - c4-judge

2024-03-26T11:37:27Z

trust1995 marked the issue as grade-a

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter