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: 20/36
Findings: 1
Award: $896.66
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Dup1337
Also found by: 0x11singh99, NentoR, cheatc0d3, nonseodion, serial-coder, shealtielanz
896.6596 USDC - $896.66
The setBadDebtUserLiquidation function is restricted to be called by only WISE_SECURITY. This however, does not safeguard it against accidental or misuse by bad acting admins. The lack of safeguards for invalid bad debt updates opens this function to attak vectors that can be abused by insiders who are aware of this loophole. For instance an admin acting in bad faith could abuse this to hide escalating risks in the system by making zero-value adjustments. This could also be abused to flood the event logs with misleading event that delay timely analysis of bad debt and associated risks.
function setBadDebtUserLiquidation( uint256 _nftId, uint256 _amount ) external onlyWiseSecurity { _setBadDebtPosition( _nftId, _amount ); emit SetBadDebtPosition( _nftId, _amount, block.timestamp ); }
File: FeeManagerHelper.sol function _setBadDebtPosition( uint256 _nftId, uint256 _amount ) internal { badDebtPosition[_nftId] = _amount; }
Add safeguards for _amount to ensure it is non zero.
class Contract: def __init__(self): self.bad_debt_position = {} def set_bad_debt_user_liquidation(self, nft_id, amount): self._set_bad_debt_position(nft_id, amount) print(f"SetBadDebtPosition event: nft_id={nft_id}, amount={amount}") def _set_bad_debt_position(self, nft_id, amount): self.bad_debt_position[nft_id] = amount # Instantiate the contract contract = Contract() # Scenario 1: Hiding escalating risks by making zero-value adjustments contract.set_bad_debt_user_liquidation(123, 0) contract.set_bad_debt_user_liquidation(456, 0) contract.set_bad_debt_user_liquidation(789, 0) # Scenario 2: Flooding the event logs with misleading events for i in range(1000): contract.set_bad_debt_user_liquidation(i, 0)
The in WiseLending contract has insufficient time validation. It's not checking for the edge case when time difference may be very small during pool initialization or timestamp updates. This can lead to calculations that do not accurately reflect the intended restrictions on share price increases.
File: WiseLending.sol function _getCurrentSharePriceMax( address _poolToken ) private view returns (uint256) { uint256 timeDifference = block.timestamp - timestampsPoolData[_poolToken].initialTimeStamp; return timeDifference * RESTRICTION_FACTOR + PRECISION_FACTOR_E18; }
require(block.timestamp >= timestampsPoolData[_poolToken].initialTimeStamp, "Current time is before the initial timestamp.");
It is solidity security best practices to adopt a fail fast approach for removePoolTokenManual function and other functions as well. This ensures the function execution is halted early if the precondition is not met therefore avoiding any further execution or state changes that are doomed to revert eventually.
File: FeeManager.sol uint256 i; uint256 len = getPoolTokenAddressesLength(); uint256 lastEntry = len - 1; bool found; if (poolTokenAdded[_poolToken] == false) { revert PoolNotPresent(); }
The setAaveFlagBulk function does not provide granular feedback on which specific operation or operations might fail within the batch. If _setAaveFlag reverts for any pair, the whole transaction reverts. This applies to other functions perfoming bulk operations as well.
File: FeeManager.sol function setAaveFlagBulk( address[] calldata _poolTokens, address[] calldata _underlyingTokens ) externals onlyMaster { uint256 i; uint256 l = _poolTokens.length; while (i < l) { _setAaveFlag( _poolTokens[i], _underlyingTokens[i] ); unchecked { ++i; } } }
Introduce granular error handling for batch operations in FeeManager to provide better feedback.
Not limiting batch size explicitly can lead to failed transactions due to the transaction exceeding the block gas limit.
File: FeeManager.sol function setAaveFlagBulk( address[] calldata _poolTokens, address[] calldata _underlyingTokens ) externals onlyMaster { uint256 i; uint256 l = _poolTokens.length; while (i < l) { _setAaveFlag( _poolTokens[i], _underlyingTokens[i] ); unchecked { ++i; } } }
require(_poolTokens.length <= MAX_BATCH_SIZE, "Batch size exceeds limit");
L-06 No circuit breakers for critical functions
For functions that directly impact liquidity in WiseLending add circuit breakers to better safeguard against high market volatility scenarios where platform is not able to facilitate user withdrawals or borrowing.
For instance, Excessive borrowing, especially in volatile market conditions, can significantly impact the platform's overall health and risk profile. A circuit breaker could be triggered by abnormal spikes in borrowing activity, drastic price movements of collateral assets, or when the system's overall health metrics like the total collateralization ratio fall below safe thresholds.
Apply circuit breakers to the following functions:
File: WiseLending.sol
The liquidatePartiallyFromTokens function in WiseLending can execute without any actual repayment of debt. This allows for malicious users to target specific borrowers and affecting the health status of a borrower's position without making a significant contribution to debt reduction.
An attacker observes that the liquidatePartiallyFromTokens function lacks a check for _shareAmountToPay > 0.
The attacker identifies a target borrower's position that they wish to manipulate for potential gain or harm.
The attacker calls liquidatePartiallyFromTokens, specifying a _shareAmountToPay of 0. This is done with the intention to affect the borrower's position.
The function executes without any actual repayment of debt due to the zero _shareAmountToPay.
Despite no value being added or debt reduced, the attacker could potentially trigger unwarranted liquidation processes or affecting the health status of the borrower's position.
The attacker manages to game the system, impacting a borrower's position negatively without contributing to the protocol's health or taking on the intended risk associated with liquidation.
Add this at the beginning of the liquidatePartiallyFromTokens function
require(_shareAmountToPay > 0, "Liquidation amount must be greater than zero.");
For
loops instead of while loops for readabilityfor is a keyword in Solidity and it informs the compiler that it contains information about looping a set of instructions. It is very similar to the while loop; however it is more succinct and readable since all information can be viewed in a single line.
source: https://subscription.packtpub.com/book/programming/9781788831383/5/ch05lvl1sec61/the-for-loop
Change function name from setBlacklistToken to toggleBlacklistToken to better describe the functionality. Same goes for setSecurityWorker though it applies more to the former.
function setBlacklistToken( address _tokenAddress, bool _state ) external onlyMaster() { wasBlacklisted[_tokenAddress] = _state; }
function setSecurityWorker( address _entitiy, bool _state ) external onlyMaster { securityWorker[_entitiy] = _state; }
LOC:
To improve readability even further pack function arguments into a single line of code.
File: FeeManagerHelper.sol function updatePositionCurrentBadDebt( uint256 _nftId ) public { _prepareCollaterals( _nftId ); _prepareBorrows( _nftId ); _updateUserBadDebt( _nftId ); }
LOC:
Almost all files in scope.
Adding more information like number of incentives claimed and addresses for which incentives were claimed can benefit offchain monitoring.
LOC
#0 - c4-pre-sort
2024-03-17T10:46:44Z
GalloDaSballo marked the issue as sufficient quality report
#1 - c4-judge
2024-03-26T11:13:00Z
trust1995 marked the issue as grade-a