Wise Lending - 0x11singh99'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: 18/36

Findings: 3

Award: $1,018.09

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: serial-coder

Also found by: 0x11singh99, Jorgect, Mrxstrange, Rhaydden, josephdara, nonseodion, unix515

Labels

2 (Med Risk)
satisfactory
duplicate-245

Awards

249.8161 USDC - $249.82

External Links

Judge has assessed an item in Issue #286 as 2 risk. The relevant finding follows:

L-06

#0 - c4-judge

2024-03-29T22:26:46Z

trust1995 marked the issue as duplicate of #245

#1 - c4-judge

2024-03-29T22:26:50Z

trust1995 marked the issue as satisfactory

Findings Information

🌟 Selected for report: Dup1337

Also found by: 0x11singh99, NentoR, cheatc0d3, nonseodion, serial-coder, shealtielanz

Labels

bug
grade-b
insufficient quality report
QA (Quality Assurance)
Q-02

Awards

159.9366 USDC - $159.94

External Links

Low-Severity

[L-01] Missing address(0) check in constructor (Note: Instances missed by bot-report)

Not checking for address(0) in the constructor could lead to scenarios where the contract is deployed with invalid or uninitialized parameters, which might cause undesired behavior or even security vulnerabilities

Check _pendleLpOracle and _pendleChild for address(0)

File : contracts/DerivativeOracles/PendleChildLpOracle.sol

21:   constructor(
        address _pendleLpOracle,
        address _pendleChild
    )
        CustomOracleSetup()
    {
        priceFeedPendleLpOracle = IPriceFeed(
            _pendleLpOracle
        );
        pendleChildToken = IPendleChildToken(
            _pendleChild
        );
    }

21-34

Check _priceFeedChainLinkEth and _oraclePendlePt for address(0)

File : contracts/DerivativeOracles/PendleLpOracle.sol

32:  constructor(
        address _pendleMarketAddress,
        IPriceFeed _priceFeedChainLinkEth,
        IOraclePendle _oraclePendlePt,
        string memory _oracleName,
        uint32 _twapDuration
    )

32-38

Check _priceFeedChainLinkUsd, _priceFeedChainLinkEthUsd and _oraclePendlePt for address(0)

File : contracts/DerivativeOracles/PtOracleDerivative.sol

24:  constructor(
        address _pendleMarketAddress,
        IPriceFeed _priceFeedChainLinkUsd,
        IPriceFeed _priceFeedChainLinkEthUsd,
        IOraclePendle _oraclePendlePt,
        string memory _oracleName,
        uint32 _twapDuration
    )

24-31

Check _priceFeedChainLinkEth, and _oraclePendlePt for address(0)

File : contracts/DerivativeOracles/PtOraclePure.sol

24:   constructor(
        address _pendleMarketAddress,
        IPriceFeed _priceFeedChainLinkEth,
        IOraclePendle _oraclePendlePt,
        string memory _oracleName,
        uint32 _twapDuration
    )

24-30

[L-02] Check value before dividing by zero (Note: Instances missed by analyzer-report)

When division by zero occurs, Solidity will throw an exception, causing the transaction to revert. It's essential to include proper checks to ensure that denominators are never zero before performing division operations to prevent errors.

Check POW_USD_FEED * POW_ETH_USD_FEED for zero

File : contracts/DerivativeOracles/PtOracleDerivative.sol

81:   function latestAnswer()
...

122:    return uint256(answerUsdFeed)
            * PRECISION_FACTOR_E18
            / POW_USD_FEED
            * POW_ETH_USD_FEED
            / uint256(answerEthUsdFeed)
            * ptToAssetRate
            / PRECISION_FACTOR_E18;

122-128

[L-03] Unsafe casting from int256 to uint256 (Note: Instances missed by bot-report)

Unsafe casting from int256 to uint256 involves converting a signed integer (int256) to an unsigned integer (uint256) without proper consideration of the potential consequences. This operation can lead to unexpected behavior and errors. If the signed integer's value is negative or larger than the maximum representable value for an unsigned integer, converting it to an unsigned integer will result in overflow.

answerUsdFeed and answerEthUsdFeed are type of int256

File : contracts/DerivativeOracles/PtOracleDerivative.sol

122:     return uint256(answerUsdFeed)
            * PRECISION_FACTOR_E18
            / POW_USD_FEED
            * POW_ETH_USD_FEED
            / uint256(answerEthUsdFeed)
            * ptToAssetRate
            / PRECISION_FACTOR_E18;

122-129

[L-04] It will return zero in everything expect answer

Since chainlink returns every param in latestRoundData but here only answer is returning correct other things will be 0 . So weak validation can be exploited or break the logic.

File : contracts/DerivativeOracles/PendleLpOracle.sol

151:    function latestRoundData()
            external
            view
            returns (
               uint80 roundId,
               int256 answer,
               uint256 startedAt,
               uint256 updatedAt,
               uint80 answeredInRound
            )
       {
        return (
            roundId,
            int256(latestAnswer()),
            startedAt,
            updatedAt,
            answeredInRound
        );
    }

151-169

File : contracts/DerivativeOracles/PtOracleDerivative.sol

146:     function latestRoundData()
            public
            view
            returns (
               uint80 roundId,
               int256 answer,
               uint256 startedAt,
               uint256 updatedAt,
               uint80 answeredInRound
            )
       {
        return (
            roundId,
            int256(latestAnswer()),
            startedAt,
            updatedAt,
            answeredInRound
        );
    }

146-164

File : contracts/DerivativeOracles/PtOraclePure.sol

125:    function latestRoundData()
            public
            view
            returns (
                uint80 roundId,
                int256 answer,
                uint256 startedAt,
                uint256 updatedAt,
                uint80 answeredInRound
            )
        {
        return (
            roundId,
            int256(latestAnswer()),
            startedAt,
            updatedAt,
            answeredInRound
        );
    }

125-143

[L-05] Dividing before multiplying can cause loss of precisions

Dividing before multiplying (/ POW_FEED before * ptToAssetRate) can potentially introduce loss of precision because you're dividing by a potentially large number (POW_FEED) before multiplying by another potentially large number (ptToAssetRate). To minimize the loss of precision, you might consider rearranging the operations to multiply before dividing.

File : contracts/DerivativeOracles/PtOraclePure.sol

68:    function latestAnswer()
...
103:    return uint256(answerFeed)
            * PRECISION_FACTOR_E18
            / POW_FEED
            * ptToAssetRate
            / PRECISION_FACTOR_E18;

103-107

[L-06] Return value not checked, It can proceeds the unwanted transaction when call returns even false

The return value of the external call is not checked for its content before proceeding. This means that even if the external call fails (success == false), but the returned data is non-empty and does not represent a boolean value, the function will not revert, potentially allowing unwanted behavior to proceed. You should explicitly check the content of the returned data to ensure it represents the expected result of the external call. If the call fails or the returned data is unexpected, the function should revert to prevent unintended behavior or vulnerabilities.

File : contracts/TransferHub/ApprovalHelper.sol

13:    function _safeApprove(
           address _token,
           address _spender,
           uint256 _value
        )
         internal
    {
20:     _callOptionalReturn(
            _token,
            abi.encodeWithSelector(
                IERC20.approve.selector,
                _spender,
                _value
            )
        );
    }

13-28

File : contracts/TransferHub/TransferHelper.sol

13:     function _safeTransfer(
            address _token,
            address _to,
            uint256 _value
        )
           internal
      {
20:       _callOptionalReturn(
              _token,
              abi.encodeWithSelector(
                 IERC20.transfer.selector,
                 _to,
                 _value
               )
            );
       }

13-28

File : contracts/TransferHub/TransferHelper.sol

34:     function _safeTransferFrom(
            address _token,
            address _from,
            address _to,
            uint256 _value
        )
           internal
     {
42:        _callOptionalReturn(
                _token,
                abi.encodeWithSelector(
                  IERC20.transferFrom.selector,
                  _from,
                  _to,
                  _value
                )
            );
    }

34-51

[L-07] nonReentrant will not work in depositExactAmount function

How nonReentrant is implemented in this protocol. It can only work when sending ether using their _sendValue function. And on other function this modifier will not work as normally mutex lock works. So use proper Openzeppelin ReentrancyGuard or implement like that it's nonReentrant with extra protocol functionality if need to be added

File : contracts/WrapperHub/AaveHelper.sol

   modifier nonReentrant() {
        _nonReentrantCheck();
        _;
  //@audit no mutex lock
        ...


      function _nonReentrantCheck() internal view {
        if (sendingProgressAaveHub == true) {
            revert InvalidAction();
        }

        if (WISE_LENDING.sendingProgress() == true) {
            revert InvalidAction();
        }
    }
    }
    ```

```solidity
File : contracts/WrapperHub/AaveHub.sol

122:  function depositExactAmount(
        uint256 _nftId,
        address _underlyingAsset,
        uint256 _amount
    )
        public
128:    nonReentrant
        validToken(_underlyingAsset)
        returns (uint256)

122-130

File : contracts/WrapperHub/AaveHub.sol

172:  function depositExactAmountETH(
         uint256 _nftId
     )
        public
        payable
177:    nonReentrant
        returns (uint256)

172-178

[L-08] Using deadline block.timestamp can be executed whenever miner wants so use custom deadline

Using block.timestamp as the deadline in a transaction can indeed introduce vulnerabilities because miners have some control over the timestamp included in the block they are mining. This allows miners to manipulate the timestamp to some extent, potentially allowing them to include transactions with expired deadlines. Miners can manipulate the timestamp to include transactions with expired deadlines. By adjusting the timestamp slightly.

To mitigate these risks, it's generally recommended to use a custom deadline based on a timestamp that is set by the contract or the user. This ensures that the deadline is not reliant on the current block's timestamp, which can be manipulated by miners. The custom deadline should be set to a future timestamp that allows sufficient time for the transaction to be processed.

File : contracts/PowerFarms/PendlePowerFarm/PendlePowerFarmLeverageLogic.sol

264:    function _getTokensUniV3(
...

273:     return UNISWAP_V3_ROUTER.exactInputSingle(
            IUniswapV3.ExactInputSingleParams(
                {
                    tokenIn: _tokenIn,
                    tokenOut: _tokenOut,
                    fee: UNISWAP_V3_FEE,
                    recipient: address(this),
280:                deadline: block.timestamp,
                    amountIn: _amountIn,
                    amountOutMinimum: _minOutAmount,
                    sqrtPriceLimitX96: 0
                }
            )
        );

273-286

Non-Critical

[N-01] public functions not called by the contract should be declared external instead (Note: Instances missed by bot-report)

File : contracts/FeeManager/FeeManagerHelper.sol

270:     function updatePositionCurrentBadDebt(

270-274

File : contracts/WiseOracleHub/OracleHelper.sol

265:    function getETHPriceInUSD()

521:    function sequencerIsDead()

265-269, 521-525

File : contracts/WiseSecurity/WiseSecurityHelper.sol

15:     function overallETHCollateralsBoth(

394:    function overallETHBorrowHeartbeat(

672:    function checkTokenAllowed(

687:    function checkHeartbeat(

837:    function checksRegister(

859:    function canLiquidate(

876:    function checkMaxShares(

999:    function getLendingRate(

15-21, 394-400, 672-677, 687-693, 837-843, 859-865, 876-885, 999-1005

[N-02] Writing CustomOracleSetup is not necessary

The PendleChildLpOracle contract inherits from CustomOracleSetup. If CustomOracleSetup does not have any constructor parameters, you don't need to write a constructor in PendleChildLpOracle to explicitly call the constructor of CustomOracleSetup. Solidity will handle this automatically for you.

File : contracts/DerivativeOracles/PendleChildLpOracle.sol

13:   contract PendleChildLpOracle is CustomOracleSetup  {

13

[N-03] Do not implicit return if explicitly returned below (Note: Instances missed by bot report)

File : contracts/DerivativeOracles/PendleChildLpOracle.sol

78:     function getRoundData(

83:     returns (
            uint80 roundId,
            int256 answer,
            uint256 startedAt,
            uint256 updatedAt,
            uint80 answeredInRound
        )

83-89

[N-04] Use one variable instead of using two variables for holding same value

File : contracts/DerivativeOracles/PendleLpOracle.sol

67:    uint256 internal constant POW_FEED_DECIMALS = 10 ** 18;

       // Precision factor for computations.
70:    uint256 internal constant PRECISION_FACTOR_E18 = 1E18;

67-70

[N-05] Make contract abstract if not deployed separately

File : contracts/FeeManager/DeclarationsFeeManager.sol

contract DeclarationsFeeManager is FeeManagerEvents, OwnableMaster {

31

[N-06] Typos

addresses spelling wrong can confuse or create errors

File : contracts/FeeManager/FeeManager.sol

-884:    function getPoolTokenAdressesByIndex(
+884:    function getPoolTokenAddressesByIndex(

884

[N-07] Comment saying function internal but it is public

File : contracts/FeeManager/FeeManagerHelper.sol

240:    /**
241:    * @dev Internal function calculating receive amount for the caller.
        * paybackIncentive is set to 5E16 => 5% incentive for paying back bad debt.
        */
        function getReceivingToken(
           address _paybackToken,
           address _receivingToken,
           uint256 _paybackAmount
        )
249:    public
        view
        returns (uint256 receivingAmount)
    {

240-252

[N-08] The error message AmountTooSmall() is updated to AmountTooBig() to indicate that the amount requested for sending is larger than the available balance

If the balance of the contract is less than the amount to be sent (_amount), the transaction will revert with the error AmountTooBig(). This provides clarity in the function's logic and helps developers understand the reason for the revert in case the condition is not met.

File : contracts/TransferHub/SendValueHelper.sol

12: function _sendValue(
        address _recipient,
        uint256 _amount
    )
        internal
    {
        if (address(this).balance < _amount) {
-            revert AmountTooSmall();
+            revert AmountTooBig();
        }

12-20

[N-09] Change the function name from proposeOwner to proposeMaster (Note: Instances missed by bot report)

Changing the function name from proposeOwner to proposeMaster could be done to better reflect the role or responsibility associated with the action performed by the function.

File : contracts/OwnableMaster.sol

70:  function proposeOwner(
        address _proposedOwner
    )
        external
        onlyMaster

70-74

[N-10] Increment should happen at the end of the loop

Moving the increment operation (++i) to the end of the loop instead of at the beginning ensures that the loop control variable (i) is updated after each iteration of the loop completes. This change affects the behavior of the loop and ensures that the increment operation happens after the loop body executes.

File : contracts/WrapperHub/AaveHelper.sol

254:    for (i; i < l;) {

            address currentAddress = WISE_LENDING.getPositionLendingTokenByIndex(
                _nftId,
                i
            );

261:        unchecked {
262:               ++i;
263:         }

            if (currentAddress == _poolToken) {
                continue;
            }

            WISE_LENDING.preparePool(
                currentAddress
            );

            WISE_LENDING.newBorrowRate(
                _poolToken
            );
        }

254-276

File : contracts/WrapperHub/AaveHelper.sol

290:    for (i; i < l;) {

            address currentAddress = WISE_LENDING.getPositionBorrowTokenByIndex(
                _nftId,
                i
            );

297:        unchecked {
298:            ++i;
299:        }

            if (currentAddress == _poolToken) {
                continue;
            }

            WISE_LENDING.preparePool(
                currentAddress
            );

            WISE_LENDING.newBorrowRate(
                _poolToken
            );
        }

290-312

#0 - c4-pre-sort

2024-03-13T08:54:55Z

GalloDaSballo marked the issue as insufficient quality report

#1 - c4-pre-sort

2024-03-17T10:45:00Z

GalloDaSballo marked the issue as grade-c

#2 - thenua3bhai

2024-03-29T14:26:19Z

Hi @trust1995 Thanks for judging. I reuqest you to please re-evaluate this QA report since this contains 8 Lows and 10 NC findings. Most of them are valid. In which 5 Lows and 7NCs are unique from bot while some findings L-1, L-2, L-3 and N-1,N-3 and N-9 similar to bot but they covers only different instances which were missed by bot, So I included them here since fixing bot instances will not fix these. I also mentioned a note that they are missed by bot. So comparing with other grade-a or grade-b reports this also contains adequate amount of Lows and NCs to be qualified for grade-a or maybe grade-b based on quality evaluated.

I also cheked my low findings with current medium findings. L-06 can be upgraded to medium and can be duplicate of #245 since both are same findings covers smae instances talks about unchecked return value in TransferHelper::_safeTransferFrom() and otehr 2 instances also. Since inside them _callOptionalReturn is called but it's return value never checked which will execute txn even when this function returns false.

L-07 and L-08 are also significant to consider for fixing.

Thanks.

#3 - c4-judge

2024-03-29T22:31:16Z

trust1995 marked the issue as grade-b

Findings Information

🌟 Selected for report: 0xAnah

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

Labels

bug
G (Gas Optimization)
grade-a
edited-by-warden
sufficient quality report
G-01

Awards

608.3337 USDC - $608.33

External Links

Gas Optimizations

Note : G-03, G-05 and G-17 contains only those instances which were missed by bot. Since they are major gas savings so I included those missed instances

Table of Contents

Auditor's Disclaimer :

All these findings are good findings and 100% safe to implement at no security/logic risk. They all are found by thorough manual review.

[G-01] Cache function calls outside of the loop avoid 3 function calls in loop

Since these functions return same value in loop's all iterations and their return value not depending on loop's operations or index . So their return value can be cached outside instead of re-calling them on every iterations saves very significant gas.

Cache _getActiveBalance(), totalLpAssets() and _getBalanceLpBalanceController() outside of the loop

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

236: while (i < l) {
...
277:  uint256 activeBalance = _getActiveBalance();
278:  uint256 totalLpAssetsCurrent = totalLpAssets();
279:  uint256 lpBalanceController = _getBalanceLpBalanceController();
280:
281:       bool scaleNecessary = totalLpAssetsCurrent < lpBalanceController;
282:
283:        rewardsOutsideArray[i] = scaleNecessary
284:             ? indexDiff
285:              * activeBalance
286:              * totalLpAssetsCurrent
287:              / lpBalanceController
288:              / PRECISION_FACTOR_E18
289:                : indexDiff
290:                    * activeBalance
291:                    / PRECISION_FACTOR_E18;

PendlePowerFarmToken.sol#L236-L291

Recommended Mitigation Steps:

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

+277:  uint256 activeBalance = _getActiveBalance();
+278:  uint256 totalLpAssetsCurrent = totalLpAssets();
+279:  uint256 lpBalanceController = _getBalanceLpBalanceController();

236: while (i < l) {
...
-277:  uint256 activeBalance = _getActiveBalance();
-278:  uint256 totalLpAssetsCurrent = totalLpAssets();
-279:  uint256 lpBalanceController = _getBalanceLpBalanceController();
280:
281:       bool scaleNecessary = totalLpAssetsCurrent < lpBalanceController;
282:
283:        rewardsOutsideArray[i] = scaleNecessary
284:             ? indexDiff
285:              * activeBalance
286:              * totalLpAssetsCurrent
287:              / lpBalanceController
288:              / PRECISION_FACTOR_E18
289:                : indexDiff
290:                    * activeBalance
291:                    / PRECISION_FACTOR_E18;

[G-02] State variables can be cached outside of the loop instead of re-reading them on each iteration

Caching storage var. outside loop is very efficient when their value doesn't depends on index/loop operations and fixed for every iteration. Saves Gwarmsload on each iteration.

Cache PENDLE_POWER_FARM_CONTROLLER and PENDLE_MARKET to save 4 SLOADs per iteration

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

236: while (i < l) {
237:     UserReward memory userReward = _getUserReward(
238:         rewardTokens[i],
239:         PENDLE_POWER_FARM_CONTROLLER
240:     );
241:
242:     if (userReward.accrued > 0) {
243:         PENDLE_MARKET.redeemRewards(
244:             PENDLE_POWER_FARM_CONTROLLER
245:         );
246:
247:         userReward = _getUserReward(
248:             rewardTokens[i],
249:             PENDLE_POWER_FARM_CONTROLLER
250:         );
251:     }

PendlePowerFarmToken.sol#L236-L251

Recommended Mitigation Steps:

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

+    address _PENDLE_POWER_FARM_CONTROLLER = PENDLE_POWER_FARM_CONTROLLER;
+    IPendleMarket  _PENDLE_MARKET = PENDLE_MARKET;
236: while (i < l) {
237:     UserReward memory userReward = _getUserReward(
238:         rewardTokens[i],
-239:         PENDLE_POWER_FARM_CONTROLLER
+239:         _PENDLE_POWER_FARM_CONTROLLER
240:     );
241:
242:     if (userReward.accrued > 0) {
-243:         PENDLE_MARKET.redeemRewards(
-244:             PENDLE_POWER_FARM_CONTROLLER
+243:         _PENDLE_MARKET.redeemRewards(
+244:             _PENDLE_POWER_FARM_CONTROLLER
245:         );
246:
247:         userReward = _getUserReward(
248:             rewardTokens[i],
-249:             PENDLE_POWER_FARM_CONTROLLER
+249:             _PENDLE_POWER_FARM_CONTROLLER
250:         );
251:     }

[G-03] Reduce the size of struct variables and pack them together to save storage slots(Instances Missed by bot)(Gas Saved ~ 2000 Gas)

utilization is only set in MainHelper::_updateUtilization function where it's value come from it's _getValueUtilization function which will return the value PRECISION_FACTOR_E18 - (PRECISION_FACTOR_E18 * totalPool / pseudoPool) which will never be more than 1e18 and it will be assigned to utilization. So utilization value will be lesser than 1e18 always. So it is completely safe to reduce the size of it to uint64 to hold it's value.

poolFee is set in WiseLending::setPoolFee function which is called by only FeeManager::setPoolFee function where newFee is passed through a check that newFee can not be more than 1e18. So it is completely safe to reduce the size of it to uint64 to hold it's value.

So utilization and poolFee can be packed together into 1 slot saves 1 Storage Slot.(~2000 Gas)

At every key's value in globalPoolData mapping in WiseLendingDeclaration.sol.

All these contracts with declaration inherited into WiseLending.sol finally through MainHelper.sol and WiseLendingDeclaration and will be deployed as one WiseLending.sol.

File : WiseLendingDeclaration.sol

212:    struct GlobalPoolEntry {
213:        uint256 totalPool;
214:        uint256 utilization;
215:        uint256 totalBareToken;
216:        uint256 poolFee;
217:    }

WiseLendingDeclaration.sol#L212C1-L217C6

Recommended Mitigation Steps:

File : WiseLendingDeclaration.sol

212:    struct GlobalPoolEntry {
213:        uint256 totalPool;
- 214:        uint256 utilization;
215:        uint256 totalBareToken;
- 216:        uint256 poolFee;
+ 216:        uint64 poolFee;
+             uint64 utilization;
217:    }

[G-04] Re-arrange state variable order to save storage slots (Saves ~2000 Gas)

We can re-arrange the order of powerFarmCheck to save 1 storage slot (~2000 Gas) pack with address AAVE_HUB_ADDRESS

File : WiseLendingDeclaration.sol

162: address internal AAVE_HUB_ADDRESS ;
...
186:  bool internal powerFarmCheck;

WiseLendingDeclaration.sol#L186, WiseLendingDeclaration.sol#L162

Recommended Mitigation Steps:

File : WiseLendingDeclaration.sol

162: address internal AAVE_HUB_ADDRESS;
+186:  bool internal powerFarmCheck;
...
-186:  bool internal powerFarmCheck;

[G-05] State variables can be packed by truncating timestamp(Instance Missed by bot)(Gas Saved ~4000 GAS)

The EVM works with 32 byte words. Variables less than 32 bytes can be declared next to each other in storage and this will pack the values together into a single 32 byte storage slot (if values combined are <= 32 bytes). If the variables packed together are retrieved together in functions (more likely with structs), we will effectively save ~2000 gas with every subsequent SLOAD for that storage slot. This is due to us incurring a Gwarmaccess (100 gas) versus a Gcoldsload (2100 gas).

lastUpdateGlobal and address master can be packed in a single slot SAVES: ~2000 Gas, 1 SLOT

Since lastUpdateGlobal holds time. So uint48 is more than sufficient to hold any realistic time.

File : DerivativeOracles/CustomOracleSetup.sol

7:  address public master;
8:  uint256 public lastUpdateGlobal;

CustomOracleSetup.sol#L7-L8

Recommended Mitigation Steps:

File : DerivativeOracles/CustomOracleSetup.sol

7:  address public master;
+8:  uint48 public lastUpdateGlobal;
-8:  uint256 public lastUpdateGlobal;

lastInteraction and MAX_CARDINALITY can be packed in single slot SAVES: ~2000 GAS, 1 Slot

Since lastInteraction hold block.timestamp. So uint64 is more than sufficient to hold time. uint64 can valid to 584942417355.07202148 years.

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol
47:  uint16 public MAX_CARDINALITY;
...
50:  uint256 public lastInteraction;

PendlePowerFarmToken.sol#L47C1-L50C36

Recommended Mitigation Steps:

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol
47:  uint16 public MAX_CARDINALITY;
+50:  uint64 public lastInteraction;
...
-50:  uint256 public lastInteraction;

INITIAL_TIME_STAMP and UNDERLYING_PENDLE_MARKET can be packed in a single slot Saves: ~2000 Gas, 1 Slot

Since INITIAL_TIME_STAMP hold block.timestamp. So uint64 is more than sufficient to hold time. uint64 can valid to 584942417355.07202148 years.

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

28:   address public UNDERLYING_PENDLE_MARKET;
...
61:  uint256 private INITIAL_TIME_STAMP;

PendlePowerFarmToken.sol#L61, PendlePowerFarmToken.sol#L28

Recommended Mitigation Steps:

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

28:   address public UNDERLYING_PENDLE_MARKET;
+61:  uint64 private INITIAL_TIME_STAMP
...
-61:  uint256 private INITIAL_TIME_STAMP;

[G-06] State variables only set in the constructor should be declared immutable

State variables only set in the constructor should be declared immutable as it avoids a Gsset (20000 gas) in the constructor, and replaces the first access in each transaction (Gcoldsload - 2100 gas) and each access thereafter (Gwarmacces - 100 gas) with a PUSH32 (3 gas).

priceFeedPendleLpOracle and pendleChildToken can be marked immutable SAVES:

File : DerivativeOracles/PendleChildLpOracle.sol

15: IPriceFeed public priceFeedPendleLpOracle;
16: IPendleChildToken public pendleChildToken;

PendleChildLpOracle.sol#L15-L16

Recommended Mitigation Steps:

File : DerivativeOracles/PendleChildLpOracle.sol

-15: IPriceFeed public priceFeedPendleLpOracle;
-16: IPendleChildToken public pendleChildToken;

+15: IPriceFeed public immutable priceFeedPendleLpOracle;
+16: IPendleChildToken public immutable pendleChildToken;

[G-07] Remove unused immutable/constant variables

Remove PENDLE_MARKET, PENDLE_SY and POW_FEED_DECIMALS since these variables are never used.

File : DerivativeOracles/PendleLpOracle.sol

32: constructor(
...
51:        PENDLE_MARKET = IPendleMarket(
52:            _pendleMarketAddress
53:        );
...
...
59: IPendleMarket public immutable PENDLE_MARKET;
...
62: IPendleSy public immutable PENDLE_SY;
...
67: uint256 internal constant POW_FEED_DECIMALS = 10 ** 18;

PendleLpOracle.sol#L51-L67

Recommended Mitigation Steps:

File : DerivativeOracles/PendleLpOracle.sol

32: constructor(
...
-51:        PENDLE_MARKET = IPendleMarket(
-52:            _pendleMarketAddress
-53:        );
...
...
-59: IPendleMarket public immutable PENDLE_MARKET;
...
-62: IPendleSy public immutable PENDLE_SY;
...
-67: uint256 internal constant POW_FEED_DECIMALS = 10 ** 18;

[G-08] Refactor PtOracleDerivative::latestAnswer function to fail early saves 2 external call

answerUsdFeed and answerEthUsdFeed are used after the if statements. So make external call after if statement.

File : DerivativeOracles/PtOracleDerivative.sol

81: function latestAnswer()
82:     public
83:     view
84:     returns (uint256)
85: {
86:     (
87:         ,
88:         int256 answerUsdFeed,
89:         ,
90:         ,
91:     ) = USD_FEED_ASSET.latestRoundData();
92:
93:     (
94:         ,
95:         int256 answerEthUsdFeed,
96:         ,
97:         ,
98:     ) = ETH_FEED_ASSET.latestRoundData();
99:
100:     (
101:         bool increaseCardinalityRequired,
102:         ,
103:         bool oldestObservationSatisfied
104:     ) = ORACLE_PENDLE_PT.getOracleState(
105:         PENDLE_MARKET_ADDRESS,
106:         TWAP_DURATION
107:     );
108:
109:     if (increaseCardinalityRequired == true) {
110:         revert CardinalityNotSatisfied();
111:     }
112:
113:     if (oldestObservationSatisfied == false) {
114:         revert OldestObservationNotSatisfied();
115:     }
116:
117:     uint256 ptToAssetRate = ORACLE_PENDLE_PT.getPtToAssetRate(
118:         PENDLE_MARKET_ADDRESS,
119:         TWAP_DURATION
120:     );
121:
122:     return uint256(answerUsdFeed)
123:         * PRECISION_FACTOR_E18
124:         / POW_USD_FEED
125:         * POW_ETH_USD_FEED
126:         / uint256(answerEthUsdFeed)
127:         * ptToAssetRate
128:         / PRECISION_FACTOR_E18;
129: }

PtOracleDerivative.sol#L81-L129

Recommended Mitigation Steps:

File : DerivativeOracles/PtOracleDerivative.sol

81: function latestAnswer()
82:     public
83:     view
84:     returns (uint256)
85: {
-86:     (
-87:         ,
-88:         int256 answerUsdFeed,
-89:         ,
-90:         ,
-91:     ) = USD_FEED_ASSET.latestRoundData();
-92:
-93:     (
-94:         ,
-95:         int256 answerEthUsdFeed,
-96:         ,
-97:         ,
-98:     ) = ETH_FEED_ASSET.latestRoundData();
99:
100:     (
101:         bool increaseCardinalityRequired,
102:         ,
103:         bool oldestObservationSatisfied
104:     ) = ORACLE_PENDLE_PT.getOracleState(
105:         PENDLE_MARKET_ADDRESS,
106:         TWAP_DURATION
107:     );
108:
109:     if (increaseCardinalityRequired == true) {
110:         revert CardinalityNotSatisfied();
111:     }
112:
113:     if (oldestObservationSatisfied == false) {
114:         revert OldestObservationNotSatisfied();
115:     }
+86:     (
+87:         ,
+88:         int256 answerUsdFeed,
+89:         ,
+90:         ,
+91:     ) = USD_FEED_ASSET.latestRoundData();
+92:
+93:     (
+94:         ,
+95:         int256 answerEthUsdFeed,
+96:         ,
+97:         ,
+98:     ) = ETH_FEED_ASSET.latestRoundData();
116:
117:     uint256 ptToAssetRate = ORACLE_PENDLE_PT.getPtToAssetRate(
118:         PENDLE_MARKET_ADDRESS,
119:         TWAP_DURATION
120:     );
121:
122:     return uint256(answerUsdFeed)
123:         * PRECISION_FACTOR_E18
124:         / POW_USD_FEED
125:         * POW_ETH_USD_FEED
126:         / uint256(answerEthUsdFeed)
127:         * ptToAssetRate
128:         / PRECISION_FACTOR_E18;
129: }

[G-09] Refactor PtOraclePure::latestAnswer function to fail early saves 1 external call

answerFeed is used after the if statements so it si better to make external call after these checks.

File : DerivativeOracles/PtOraclePure.sol

68: function latestAnswer()
69:     public
70:     view
71:     returns (uint256)
72: {
73:     (
74:         ,
75:         int256 answerFeed,
76:         ,
77:         ,
78:
79:     ) = FEED_ASSET.latestRoundData();
80:
81:     (
82:         bool increaseCardinalityRequired,
83:         ,
84:         bool oldestObservationSatisfied
85:     ) = ORACLE_PENDLE_PT.getOracleState(
86:         PENDLE_MARKET_ADDRESS,
87:         DURATION
88:     );
89:
90:     if (increaseCardinalityRequired == true) {
91:         revert CardinalityNotSatisfied();
92:     }
93:
94:     if (oldestObservationSatisfied == false) {
95:         revert OldestObservationNotSatisfied();
96:     }
97:
98:     uint256 ptToAssetRate = ORACLE_PENDLE_PT.getPtToAssetRate(
99:         PENDLE_MARKET_ADDRESS,
100:         DURATION
101:     );
102:
103:     return uint256(answerFeed)
104:         * PRECISION_FACTOR_E18
105:         / POW_FEED
106:         * ptToAssetRate
107:         / PRECISION_FACTOR_E18;
108: }

PtOraclePure.sol#L68-L108

Recommended Mitigation Steps:

File : DerivativeOracles/PtOraclePure.sol

68: function latestAnswer()
69:     public
70:     view
71:     returns (uint256)
72: {
-73:     (
-74:         ,
-75:         int256 answerFeed,
-76:         ,
-77:         ,
-78:
-79:     ) = FEED_ASSET.latestRoundData();
80:
81:     (
82:         bool increaseCardinalityRequired,
83:         ,
84:         bool oldestObservationSatisfied
85:     ) = ORACLE_PENDLE_PT.getOracleState(
86:         PENDLE_MARKET_ADDRESS,
87:         DURATION
88:     );
89:
90:     if (increaseCardinalityRequired == true) {
91:         revert CardinalityNotSatisfied();
92:     }
93:
94:     if (oldestObservationSatisfied == false) {
95:         revert OldestObservationNotSatisfied();
96:     }
+73:     (
+74:         ,
+75:         int256 answerFeed,
+76:         ,
+77:         ,
+78:
+79:     ) = FEED_ASSET.latestRoundData();
97:
98:     uint256 ptToAssetRate = ORACLE_PENDLE_PT.getPtToAssetRate(
99:         PENDLE_MARKET_ADDRESS,
100:         DURATION
101:     );
102:
103:     return uint256(answerFeed)
104:         * PRECISION_FACTOR_E18
105:         / POW_FEED
106:         * ptToAssetRate
107:         / PRECISION_FACTOR_E18;
108: }

[G-10] Use above assigned to state variable values directly instead of reading them from storage saves 2 SLOADs

File : FeeManager/DeclarationsFeeManager.sol

90:   incentiveOwnerA = 0xf69A0e276664997357BF987df83f32a1a3F80944;
91:   incentiveOwnerB = 0x8f741ea9C9ba34B5B8Afc08891bDf53faf4B3FE7;
92:
93:   incentiveUSD[incentiveOwnerA] = 98000 * PRECISION_FACTOR_E18;
94:   incentiveUSD[incentiveOwnerB] = 106500 * PRECISION_FACTOR_E18;

DeclarationsFeeManager.sol#L90-L94

Recommended Mitigation Steps:

File : FeeManager/DeclarationsFeeManager.sol

90:   incentiveOwnerA = 0xf69A0e276664997357BF987df83f32a1a3F80944;
91:   incentiveOwnerB = 0x8f741ea9C9ba34B5B8Afc08891bDf53faf4B3FE7;
92:
-93:   incentiveUSD[incentiveOwnerA] = 98000 * PRECISION_FACTOR_E18;
-94:   incentiveUSD[incentiveOwnerB] = 106500 * PRECISION_FACTOR_E18;
+93:   incentiveUSD[0xf69A0e276664997357BF987df83f32a1a3F80944] = 98000 * PRECISION_FACTOR_E18;
+94:   incentiveUSD[0x8f741ea9C9ba34B5B8Afc08891bDf53faf4B3FE7] = 106500 * PRECISION_FACTOR_E18;

[G-11] State variables can be packed into fewer storage slot by reducing their size (saves ~8000 Gas)

The EVM works with 32 byte words. Variables less than 32 bytes can be declared next to each other in storage and this will pack the values together into a single 32 byte storage slot (if values combined are <= 32 bytes). If the variables packed together are retrieved together in functions (more likely with structs), we will effectively save ~2000 gas with every subsequent SLOAD for that storage slot. This is due to us incurring a Gwarmaccess (100 gas) versus a Gcoldsload (2100 gas).

SAVE: ~8000 GAS, 4 SLOT

paybackIncentive and incentiveMaster can be packed in single slot SAVES: ~2000 GAS, 1 SLOT

Since paybackIncentive can max up to 1e18 because of:

File : FeeManager/DeclarationsFeeManager.sol

88: paybackIncentive = 5 * PRECISION_FACTOR_E16;

DeclarationsFeeManager.sol#L88

So we can easily reduce paybackIncentive to uint64 and pack with address incentiveMaster. Since uint64 can easily hold up to 1.8446744073709551615 × 10^19

File : FeeManager/DeclarationsFeeManager.sol

120: uint256 public paybackIncentive;
...
126: address public incentiveMaster;

DeclarationsFeeManager.sol#L120-L126

Recommended Mitigation Steps:

File : FeeManager/DeclarationsFeeManager.sol

-120: uint256 public paybackIncentive;
...
126: address public incentiveMaster;
+120: uint64 public paybackIncentive;

collateralFactor and allowEnter can be packed in single slot SAVE:~2000 Gas, 1 Slot

collateralFactor can't more than 1e18 deu to this check:


  if (_collateralFactor > PRECISION_FACTOR_E18) {
      revert CollateralFactorTooHigh();

PendlePowerFarmDeclarations.sol#L243C1-L245C9

So we can easily reduce collateralFactor to uint64 and pack with bool allowEnter. Since uint64 can easily hold up to 1.8446744073709551615 × 10^19

File : PowerFarms/PendlePowerFarm/PendlePowerFarmDeclarations.sol

46:  bool public allowEnter;
47:
48:    // Ratio between borrow and lend
49:    uint256 public collateralFactor;

PendlePowerFarmDeclarations.sol#L46C1-L49C37

Recommended Mitigation Steps:

File : PowerFarms/PendlePowerFarm/PendlePowerFarmDeclarations.sol

46:  bool public allowEnter;
+49:    uint64 public collateralFactor;
47:
48:    // Ratio between borrow and lend
-49:    uint256 public collateralFactor;

mintFee, MAX_CARDINALITY and PENDLE_POWER_FARM_CONTROLLER can be packed in a single slot Saves: ~4000 Gas, 2 Slot

Since mintFee can't more than 10000 because of this check:

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

if (_newFee > MAX_MINT_FEE) {
            revert FeeTooHigh();
        }

        mintFee = _newFee;

PendlePowerFarmToken.sol#L598C9-L602C27

So we easily reduce mintFee to uint16 and pack both mintFee and MAX_CARDINALITY with address PENDLE_POWER_FARM_CONTROLLER. uint16 can hold up to 65535.

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

29:   address public PENDLE_POWER_FARM_CONTROLLER;
...
47:    uint16 public MAX_CARDINALITY;
48:
49:    uint256 public mintFee;

PendlePowerFarmToken.sol#L47C1-L49C28

Recommended Mitigation Steps:

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

29: address public PENDLE_POWER_FARM_CONTROLLER;
+47:    uint16 public MAX_CARDINALITY;
+49:    uint16 public mintFee;
...
-47:    uint16 public MAX_CARDINALITY;
48:
-49:    uint256 public mintFee;

maxFeeETH, maxFeeFarmETH, baseRewardLiquidation and baseRewardLiquidationFarm can be packed in two slots instead of four SAVES: ~4000 Gas, 2 SLot

maxFeeETH has max value 100e18. maxFeeFarmETH has max value 100e18. baseRewardLiquidation has max value 11e16. baseRewardLiquidationFarm has max value 4e16.

File : WiseSecurity/WiseSecurityDeclarations.sol

 // Max reward ETH for liquidator power farm liquidation
252:    uint256 public maxFeeETH;

    // Max reward ETH for liquidator normal liquidation
255:    uint256 public maxFeeFarmETH;

    // Base reward for liquidator normal liquidation
258:    uint256 public baseRewardLiquidation;

    // Base reward for liquidator power farm liquidation
261:    uint256 public baseRewardLiquidationFarm;

WiseSecurityDeclarations.sol#L251C4-L261C46

Recommended Mitigation Steps:

File : WiseSecurity/WiseSecurityDeclarations.sol

 // Max reward ETH for liquidator power farm liquidation
-252:    uint256 public maxFeeETH;

    // Max reward ETH for liquidator normal liquidation
-255:    uint256 public maxFeeFarmETH;

    // Base reward for liquidator normal liquidation
-258:    uint256 public baseRewardLiquidation;

    // Base reward for liquidator power farm liquidation
-261:    uint256 public baseRewardLiquidationFarm;

+252:    uint128 public maxFeeETH;

    // Max reward ETH for liquidator normal liquidation
+255:    uint128 public maxFeeFarmETH;

    // Base reward for liquidator normal liquidation
+258:    uint128 public baseRewardLiquidation;

    // Base reward for liquidator power farm liquidation
+261:    uint128 public baseRewardLiquidationFarm;

[G-12] Use directly msg.sender instead of reading address from storage if both are equal (Saves: ~900 Gas)

4 Instances

  1. SAVES: 2 SLOADs (~200 Gas)

After the if statement msg.sender and proposedIncentiveMaster are equal so we can use directly msg.sender instead of proposedIncentiveMaster also we can emit msg.sender instead of incentiveMaster because both are same.

File : FeeManager/FeeManager.sol

194:  function claimOwnershipIncentiveMaster()
195:     external
196:  {
197:     if (msg.sender != proposedIncentiveMaster) {
198:         revert NotAllowed();
199:     }
200:
201:    incentiveMaster = proposedIncentiveMaster;
202:    proposedIncentiveMaster = ZERO_ADDRESS;
203:
204:    emit ClaimedOwnershipIncentiveMaster(
205:        incentiveMaster,
206:        block.timestamp
207:     );
208:  }

FeeManager.sol#L194-L208

Recommended Mitigation Steps:

File : FeeManager/FeeManager.sol

194:  function claimOwnershipIncentiveMaster()
195:     external
196:  {
197:     if (msg.sender != proposedIncentiveMaster) {
198:         revert NotAllowed();
199:     }
200:
-201:    incentiveMaster = proposedIncentiveMaster;
+201:    incentiveMaster = msg.sender;
202:    proposedIncentiveMaster = ZERO_ADDRESS;
203:
204:    emit ClaimedOwnershipIncentiveMaster(
-205:        incentiveMaster,
+205:        msg.sender,
206:        block.timestamp
207:     );
208:  }
  1. SAVES: 3 SLOADs (~300 Gas)

After the first if statement it is proved that msg.sender and incentiveOwnerA are equal so we can use msg.sender instead of incentiveOwnerA. It can save 3 sloads.

File : FeeManager/FeeManager.sol

315: function changeIncentiveUSDA(
316:     address _newOwner
317: )
318:     external
319: {
320:     if (msg.sender != incentiveOwnerA) {
321:         revert NotAllowed();
322:     }
323:
324:     if (_newOwner == incentiveOwnerA) {
325:         revert NotAllowed();
326:     }
327:
...
331:
332:     incentiveUSD[_newOwner] = incentiveUSD[
333:         incentiveOwnerA
334:     ];
335:
336:     delete incentiveUSD[
337:         incentiveOwnerA
338:     ];

FeeManager.sol#L315-L338

Recommended Mitigation Steps:

File : FeeManager/FeeManager.sol

315: function changeIncentiveUSDA(
316:     address _newOwner
317: )
318:     external
319: {
320:     if (msg.sender != incentiveOwnerA) {
321:         revert NotAllowed();
322:     }
323:
-324:     if (_newOwner == incentiveOwnerA) {
+324:     if (_newOwner == msg.sender) {
325:         revert NotAllowed();
326:     }
327:
...
331:
332:     incentiveUSD[_newOwner] = incentiveUSD[
-333:         incentiveOwnerA
+333:         msg.sender
334:     ];
335:
336:     delete incentiveUSD[
-337:         incentiveOwnerA
+337:         msg.sender
338:     ];
  1. SAVES: 3 SLOADs (~300 Gas)

After the first if statement it is proved that msg.sender and incentiveOwnerB are equal so we can use msg.sender instead of incentiveOwnerB. It can save 3 sloads.

File : FeeManager/FeeManager.sol

352: function changeIncentiveUSDB(
353:     address _newOwner
354: )
355:     external
356: {
357:     if (msg.sender != incentiveOwnerB) {
358:         revert NotAllowed();
359:     }
360:
361:     if (_newOwner == incentiveOwnerA) {
362:         revert NotAllowed();
363:     }
364:
365:     if (_newOwner == incentiveOwnerB) {
366:         revert NotAllowed();
367:     }
368:
369:     incentiveUSD[_newOwner] = incentiveUSD[
370:         incentiveOwnerB
371:     ];
372:
373:     delete incentiveUSD[
374:         incentiveOwnerB
375:     ];

FeeManager.sol#L352-L375

Recommended Mitigation Steps:

File : FeeManager/FeeManager.sol

352: function changeIncentiveUSDB(
353:     address _newOwner
354: )
355:     external
356: {
357:     if (msg.sender != incentiveOwnerB) {
358:         revert NotAllowed();
359:     }
360:
361:     if (_newOwner == incentiveOwnerA) {
362:         revert NotAllowed();
363:     }
364:
-365:     if (_newOwner == incentiveOwnerB) {
+365:     if (_newOwner == msg.sender) {
366:         revert NotAllowed();
367:     }
368:
369:     incentiveUSD[_newOwner] = incentiveUSD[
-370:         incentiveOwnerB
+370:         msg.sender
371:     ];
372:
373:     delete incentiveUSD[
-374:         incentiveOwnerB
+374:         msg.sender
375:     ];
  1. SAVES: 1 SLOAD(~100 Gas)

Since onlyProposed modifier check insure that proposedMaster == msg.sender:

File : OwnableMaster.sol

   modifier onlyProposed() {
        _onlyProposed();
        _;
    }

    ...

    function _onlyProposed()
        private
        view
    {
        if (msg.sender == proposedMaster) {
            return;
        }

        revert NotProposed();

OwnableMaster.sol#L37-L46

So we can use msg.sender instead of proposedMaster. Saves 1 SLOAD(~100 Gas)

File : OwnableMaster.sol

92:  function claimOwnership()
93:    external
94:     onlyProposed
95:  {
96:     master = proposedMaster;
97:  }

OwnableMaster.sol#L92-L97

Recommended Mitigation Steps:

File : OwnableMaster.sol

92:  function claimOwnership()
93:    external
94:     onlyProposed
95:  {
-96:     master = proposedMaster;
+96:     master = msg.sender;
97:  }

[G-13] Switch the order of if statements to save gas

Rearranging the order of the if statements to reduce gas costs associated with storage loads (SLOAD) when the conditions fail. The rationale is that by placing the condition with a lower gas cost before the condition with a higher gas cost, the likelihood of reaching the higher-cost operation is minimized, resulting in potential gas savings.

File : FeeManager/FeeManager.sol

352: function changeIncentiveUSDB(
353:     address _newOwner
354: )
355:     external
356: {
357:     if (msg.sender != incentiveOwnerB) {
358:         revert NotAllowed();
359:     }
360:
361:     if (_newOwner == incentiveOwnerA) {
362:         revert NotAllowed();
363:     }
364:
365:     if (_newOwner == incentiveOwnerB) {
366:         revert NotAllowed();
367:     }

FeeManager.sol#L352-L367

Recommended Mitigation Steps:

File : FeeManager/FeeManager.sol

352: function changeIncentiveUSDB(
353:     address _newOwner
354: )
355:     external
356: {
357:     if (msg.sender != incentiveOwnerB) {
358:         revert NotAllowed();
359:     }
+365:     if (_newOwner == incentiveOwnerB) {
+366:         revert NotAllowed();
+367:     }
360:
361:     if (_newOwner == incentiveOwnerA) {
362:         revert NotAllowed();
363:     }
364:
-365:     if (_newOwner == incentiveOwnerB) {
-366:         revert NotAllowed();
-367:     }

[G-14] Refactor CallOptionalReturn::_callOptionalReturn function to save gas

The results variable is removed and the condition is directly integrated into the assignment of the call variable. The condition token.code.length > 0 is checked first in the assignment of call. The results condition is combined with the overall condition simplifying the logic. This refactor aims to improve gas efficiency by eliminating unnecessary variables and streamlining the code.

File : TransferHub/CallOptionalReturn.sol

12: function _callOptionalReturn(
13:     address token,
14:     bytes memory data
15: )
16:     internal
17:     returns (bool call)
18: {
19:     (
20:         bool success,
21:         bytes memory returndata
22:     ) = token.call(
23:         data
24:     );
25:
26:     bool results = returndata.length == 0 || abi.decode(
27:         returndata,
28:         (bool)
29:     );
30:
31:     if (success == false) {
32:         revert();
33:     }
34:
35:     call = success
36:         && results
37:         && token.code.length > 0;
38: }

CallOptionalReturn.sol#L12C1-L38C6

Recommended Mitigation Steps:

File : TransferHub/CallOptionalReturn.sol

12: function _callOptionalReturn(
13:     address token,
14:     bytes memory data
15: )
16:     internal
17:     returns (bool call)
18: {
19:     (
20:         bool success,
21:         bytes memory returndata
22:     ) = token.call(
23:         data
24:     );
25:
-26:     bool results = returndata.length == 0 || abi.decode(
-27:         returndata,
-28:         (bool)
-29:     );
30:
31:     if (success == false) {
32:         revert();
33:     }
34:
-35:     call = success
-36:         && results
-37:         && token.code.length > 0;

+35:     call = token.code.length > 0
+36:         && returndata.length == 0 || abi.decode(
+37:    returndata,(bool)
38: }

[G-15] Don't cache state variable if it is used only once

No need to cache aaveTokenAddress[_underlyingAsset]

Instance 1
File : WrapperHub/AaveHelper.sol

122:   address aaveToken = aaveTokenAddress[
123:     _underlyingAsset
124:    ];
125:
126:   uint256 withdrawAmount = WISE_LENDING.withdrawOnBehalfExactShares(
127:        _nftId,
128:        aaveToken,
129:       _shareAmount
130:    )

AaveHelper.sol#L122-L130

Recommended Mitigation Steps:

File : WrapperHub/AaveHelper.sol

-122:   address aaveToken = aaveTokenAddress[
-123:     _underlyingAsset
-124:    ];
125:
126:   uint256 withdrawAmount = WISE_LENDING.withdrawOnBehalfExactShares(
127:        _nftId,
-128:        aaveToken,
+128:        aaveTokenAddress[_underlyingAsset],
129:       _shareAmount
130:    )
Instance 2
File :WrapperHub/AaveHub.sol

447:   address aaveToken = aaveTokenAddress[
448:      _underlyingAsset
449:    ];
...
464:    uint256 borrowSharesReduction = WISE_LENDING.paybackExactAmount(
465:        _nftId,
466:       aaveToken,
467:      actualAmountDeposit
468:      );

AaveHub.sol#L447-L468

Recommended Mitigation Steps:

File :WrapperHub/AaveHub.sol

-447:   address aaveToken = aaveTokenAddress[
-448:      _underlyingAsset
-449:    ];
...
464:    uint256 borrowSharesReduction = WISE_LENDING.paybackExactAmount(
465:        _nftId,
-466:       aaveToken,
+466:       aaveTokenAddress[_underlyingAsset],
467:      actualAmountDeposit
468:      );

[G-16] Remove redundant if statements

This if (_lendingAddress == ZERO_ADDRESS) check is redundant because if it is address(0) it is not going to pass from IWiseLending(lendingAddress).WETH_ADDRESS().

File : WrapperHub/Declarations.sol

51:  WrapperHelper(
52:       IWiseLending(
53:           _lendingAddress
54:       ).WETH_ADDRESS()
55:     )
56:  {
57:     if (_aaveAddress == ZERO_ADDRESS) {
58:         revert NoValue();
59:      }
60:
61:     if (_lendingAddress == ZERO_ADDRESS) {
62:          revert NoValue();
63:        }

Declarations.sol#L51C9-L63C10

Recommended Mitigation Steps:

File : WrapperHub/Declarations.sol

51:  WrapperHelper(
52:       IWiseLending(
53:           _lendingAddress
54:       ).WETH_ADDRESS()
55:     )
56:  {
57:     if (_aaveAddress == ZERO_ADDRESS) {
58:         revert NoValue();
59:      }
60:
-61:     if (_lendingAddress == ZERO_ADDRESS) {
-62:          revert NoValue();
-63:        }

[G-17] State variables should be cached in stack variables rather than re-reading them from storage(Instances Missed by Bot) (Gas Saved ~400 Gas)

SAVE: ~400 GAS, 4 SLOAD`

The instances below point to the second+ access of a state variable within a function. Caching of a state variable replaces each Gwarmaccess (100 gas) with a much cheaper stack read. Other less obvious fixes/optimizations include having local memory caches of state variable structs, or having local caches of state variable contracts/addresses.

PENDLE_CONTROLLER and UNDERLYING_PENDLE_MARKET can be cached to save 2 SLOAD (~200 Gas)

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

222:    address[] memory rewardTokens = PENDLE_CONTROLLER.pendleChildCompoundInfoRewardTokens(
223:        UNDERLYING_PENDLE_MARKET
224:    );
225:
226:        uint128[] memory lastIndex = PENDLE_CONTROLLER.pendleChildCompoundInfoLastIndex(
227:            UNDERLYING_PENDLE_MARKET
228:        );

PendlePowerFarmToken.sol#L222-L228C

Recommended Mitigation Steps:

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

+       IPendleController _PENDLE_CONTROLLER = PENDLE_CONTROLLER;
+        address _UNDERLYING_PENDLE_MARKET = UNDERLYING_PENDLE_MARKET;

-222:    address[] memory rewardTokens = PENDLE_CONTROLLER.pendleChildCompoundInfoRewardTokens(
-223:        UNDERLYING_PENDLE_MARKET
+222:    address[] memory rewardTokens = _PENDLE_CONTROLLER.pendleChildCompoundInfoRewardTokens(
+223:        _UNDERLYING_PENDLE_MARKET
224:    );
225:
-226:        uint128[] memory lastIndex = PENDLE_CONTROLLER.pendleChildCompoundInfoLastIndex(
-227:            UNDERLYING_PENDLE_MARKET
+226:        uint128[] memory lastIndex = _PENDLE_CONTROLLER.pendleChildCompoundInfoLastIndex(
+227:            _UNDERLYING_PENDLE_MARKET
228:        );

Cache lastInteraction to save 1 SLOAD (~100 Gas)

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

395:        if (block.timestamp == lastInteraction) {
396:            return 0;
397:        }
...
...
406:        uint256 additonalAssets = currentRate
407:            * (block.timestamp - lastInteraction)

PendlePowerFarmToken.sol#L395-L407

Recommended Mitigation Steps:

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

+       uint256 _lastInteraction = lastInteraction;

-395:        if (block.timestamp == lastInteraction) {
+395:        if (block.timestamp == _lastInteraction) {
396:            return 0;
397:        }
...
...
406:        uint256 additonalAssets = currentRate
-407:            * (block.timestamp - lastInteraction)
+407:            * (block.timestamp - _lastInteraction)

underlyingLpAssetsCurrent can be cached to save 1 SLOAD (~100 Gas)

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

529: function depositExactAmount(
...

543:        uint256 shares = previewMintShares(
544:            _underlyingLpAssetAmount,
545:            underlyingLpAssetsCurrent
546:        );
...
577:     underlyingLpAssetsCurrent += _underlyingLpAssetAmount;

PendlePowerFarmToken.sol#L529C5-L578C1

Recommended Mitigation Steps:

File : PowerFarms/PendlePowerFarmController/PendlePowerFarmToken.sol

529: function depositExactAmount(
...
+        uint256 cached_underlyingLpAssetsCurrent = underlyingLpAssetsCurrent;
543:        uint256 shares = previewMintShares(
544:            _underlyingLpAssetAmount,
-545:            underlyingLpAssetsCurrent
+545:            cached_underlyingLpAssetsCurrent
546:        );
...
-577:     underlyingLpAssetsCurrent += _underlyingLpAssetAmount;
+577:     underlyingLpAssetsCurrent = cached_underlyingLpAssetsCurrent + _underlyingLpAssetAmount;

#0 - c4-pre-sort

2024-03-17T10:48:55Z

GalloDaSballo marked the issue as sufficient quality report

#1 - c4-judge

2024-03-26T11:22:03Z

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