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
Rank: 18/36
Findings: 3
Award: $1,018.09
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: serial-coder
Also found by: 0x11singh99, Jorgect, Mrxstrange, Rhaydden, josephdara, nonseodion, unix515
249.8161 USDC - $249.82
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
🌟 Selected for report: Dup1337
Also found by: 0x11singh99, NentoR, cheatc0d3, nonseodion, serial-coder, shealtielanz
159.9366 USDC - $159.94
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
_pendleLpOracle
and _pendleChild
for address(0)File : contracts/DerivativeOracles/PendleChildLpOracle.sol 21: constructor( address _pendleLpOracle, address _pendleChild ) CustomOracleSetup() { priceFeedPendleLpOracle = IPriceFeed( _pendleLpOracle ); pendleChildToken = IPendleChildToken( _pendleChild ); }
_priceFeedChainLinkEth
and _oraclePendlePt
for address(0)File : contracts/DerivativeOracles/PendleLpOracle.sol 32: constructor( address _pendleMarketAddress, IPriceFeed _priceFeedChainLinkEth, IOraclePendle _oraclePendlePt, string memory _oracleName, uint32 _twapDuration )
_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 )
_priceFeedChainLinkEth
, and _oraclePendlePt
for address(0)File : contracts/DerivativeOracles/PtOraclePure.sol 24: constructor( address _pendleMarketAddress, IPriceFeed _priceFeedChainLinkEth, IOraclePendle _oraclePendlePt, string memory _oracleName, uint32 _twapDuration )
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.
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;
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;
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 ); }
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 ); }
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 ); }
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;
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 ) ); }
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 ) ); }
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 ) ); }
nonReentrant
will not work in depositExactAmount
functionHow 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)
File : contracts/WrapperHub/AaveHub.sol 172: function depositExactAmountETH( uint256 _nftId ) public payable 177: nonReentrant returns (uint256)
block.timestamp
can be executed whenever miner wants so use custom deadlineUsing 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 } ) );
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(
File : contracts/WiseOracleHub/OracleHelper.sol 265: function getETHPriceInUSD() 521: function sequencerIsDead()
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
CustomOracleSetup
is not necessaryThe 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 {
File : contracts/DerivativeOracles/PendleChildLpOracle.sol 78: function getRoundData( 83: returns ( uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound )
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;
abstract
if not deployed separatelyFile : contracts/FeeManager/DeclarationsFeeManager.sol contract DeclarationsFeeManager is FeeManagerEvents, OwnableMaster {
addresses
spelling wrong can confuse or create errors
File : contracts/FeeManager/FeeManager.sol -884: function getPoolTokenAdressesByIndex( +884: function getPoolTokenAddressesByIndex(
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) {
AmountTooSmall()
is updated to AmountTooBig()
to indicate that the amount requested for sending is larger than the available balanceIf 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(); }
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
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 ); }
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 ); }
#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
🌟 Selected for report: 0xAnah
Also found by: 0x11singh99, 0xhacksmithh, K42, albahaca, dharma09
608.3337 USDC - $608.33
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
[G-01] Cache function calls outside of the loop avoid 3 function call in loop saves significant gas
[G-02] State variables can be cached outside of the loop instead of re-reading them on each iteration
[G-04] Re-arrange state variable order to save storage slots (Saves ~2000 Gas)
[G-05] State variables can be packed by truncating timestamp(Instance Missed by bot)(Gas Saved ~4000 GAS)
[G-06] State variables only set in the constructor should be declared immutable(Gas Saved ~4200 Gas)
[G-08] Refactor PtOracleDerivative::latestAnswer
function to fail early saves 2 external call
[G-09] Refactor PtOraclePure::latestAnswer
function to fail early saves 1 external call
[G-11] State variables can be packed into fewer storage slot by reducing their size (saves ~8000 Gas)
[G-12] Use directly msg.sender
instead of reading address from storage
if both are equal (Saves: ~900 Gas)
[G-14] Refactor CallOptionalReturn::_callOptionalReturn
function to save gas
All these findings are good findings and 100% safe to implement at no security/logic risk. They all are found by thorough manual review.
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.
_getActiveBalance()
, totalLpAssets()
and _getBalanceLpBalanceController()
outside of the loopFile : 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;
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.
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: }
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.
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: }
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;
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;
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 SlotSince 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 SlotSince 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;
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;
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;
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;
PtOracleDerivative::latestAnswer
function to fail early saves 2 external callanswerUsdFeed
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: }
PtOraclePure::latestAnswer
function to fail early saves 1 external callanswerFeed
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: }
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: }
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;
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 SlotSince 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;
msg.sender
instead of reading address from storage
if both are equal (Saves: ~900 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: }
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: }
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: ];
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: ];
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: ];
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: ];
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();
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: }
Recommended Mitigation Steps:
File : OwnableMaster.sol 92: function claimOwnership() 93: external 94: onlyProposed 95: { -96: master = proposedMaster; +96: master = msg.sender; 97: }
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: }
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: }
CallOptionalReturn::_callOptionalReturn
function to save gasThe 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: }
aaveTokenAddress[_underlyingAsset]
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: )
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: )
File :WrapperHub/AaveHub.sol 447: address aaveToken = aaveTokenAddress[ 448: _underlyingAsset 449: ]; ... 464: uint256 borrowSharesReduction = WISE_LENDING.paybackExactAmount( 465: _nftId, 466: aaveToken, 467: actualAmountDeposit 468: );
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: );
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: }
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: }
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: );
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