Closed c4-bot-2 closed 5 months ago
GalloDaSballo marked the issue as primary issue
this seems to overlap or duplicate for: https://github.com/code-423n4/2024-02-wise-lending-findings/issues/32
GalloDaSballo marked the issue as duplicate of #32
GalloDaSballo marked the issue as sufficient quality report
trust1995 marked the issue as satisfactory
Lines of code
https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarm/PendlePowerManager.sol#L129 https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarm/PendlePowerManager.sol#L172 https://github.com/code-423n4/2024-02-wise-lending/blob/main/contracts/PowerFarms/PendlePowerFarm/PendlePowerFarmLeverageLogic.sol#L560-L573
Vulnerability details
Impact
PowerFarm positions that borrowed aWETH can't be liquidated.
Proof of Concept
PowerFarm positions can only be liquidated through the
PendlePowerFarm.liquidatePartiallyFromToken() function
. If a PowerFarm position is attempted to be liquidated directly in WiseLending by using theWiseLending.liquidatePartiallyFromTokens() function
, the execution will revert because the call to the_checkPositionLocked() function
, this check validates that locked functions can't be liquidated directly on WiseLending!Knowing that PowerFarm positions can't be liquidated directly in WiseLending, let's explore the reason why PowerFarm positions that borrowed aWETH can't be liquidated.
When users enter a farm they have two options to select the token that will be borrowed to repay the flashloan to the balancer vault.
Users control what type of token they want to borrow (either aWETH or WETH), by setting as true or false the parameter
_isAave
when entering a farm.When a user enters a farm, the function executes the next couple of steps:
The first step is that the PowerFarm mints a new position, locks it in WiseLending and approve the AaveHub contract to operate on the new-minted position.
totalReserved and totalSupply() of positions
as per the PositionNFTs contract's state.... }
The second step is to transfer the amount that the user will use to enter the farm.
The third step is open a position, it will request a flashloan from the Balancer_Vault for the total leveraged amount that the position will use, it will use the flashloaned tokens to deposit and add liquidity into a Pendle Market, then it will deposit the received LPTokens from Pendle onto the PENDLE_CHILD. After depositing into the PENDLE_CHILD it will deposit the PENDLE_CHILD_Tokens into WiseLending, this deposit will be used as collateral for a borrow that the position will take to get the required tokens to payback the liquidator
_isAave
parameter plays a key role in this step:true
, the borrowedTokens will be aWETHfalse
, the borrowedTokens will be WETHBorrowing WETH means that the position will end up owning borrowShares on the WETH pool.
WISE_LENDING.borrowExactAmount( _nftId, WETH_ADDRESS, _totalDebtBalancer ); }
The 4th step is to check the debt ratio to validate that the position has a debt ratio under 100%
The 5th step is to repay the flashloan (including the fees)
The 6th step is a fundamental process on the logic of the powerfarms. In this step it is reserved the key that is assigned to the position. Or in other words, the WiseLendingNFT it is linked to a new key that is generated based on the
totalMinted and totalReserved keys
in the PowerFarm.//@audit-info => Gets the nextReserveKey! uint256 keyId = _getNextReserveKey();
//@audit-info => The keyId is assigned as the reservedKey of the user! reservedKeys[_userAddress] = keyId; //@audit-info => The wiseLendingNFTId is assigned as the associated position of the keyId! farmingKeys[keyId] = _wiseLendingNFT;
return keyId; }
function _getNextReserveKey() internal returns (uint256) { return totalMinted + _incrementReserved(); }
function enterFarm( //@audit-info => If true, the borrow will use the AaveHub and will borrow aWETH //@audit-info => If false, the borrow will borrow WETH directly from WiseLending bool _isAave, uint256 _amount, uint256 _leverage, uint256 _allowedSpread ) ... { //@audit-info => Step 2, mints a new position that will be used to take the borrow and deposit the purchased PENDLE_CHILD shares uint256 wiseLendingNFT = _getWiseLendingNFT();
}
function liquidatePartiallyFromToken( //@audit-info => WiseLendingNftId been liquidated uint256 _nftId, ... ) ... { return _coreLiquidation( //@audit-info => WiseLendingNftId been liquidated _nftId, _nftIdLiquidator, _shareAmountToPay ); }
function _coreLiquidation( uint256 _nftId, ... ) ... { //@audit-info => Forwards the nftID of the position being liquidated! if (_checkDebtRatio(_nftId) == true) { revert DebtRatioTooLow(); } }
function _checkDebtRatio( //@audit-info => WiseLendingNftId been liquidated uint256 _nftId ) internal view returns (bool) { //@audit-info => Using the WiseLendingNFT (position's ID) will read if the loan was taken using the AaveHub (aWETH) or directly from WiseLending (WETH)
}
function testFarmShouldEnterPoC() public cheatSetup(true) {
}
[PASS] testFarmShouldEnterPoC() (gas: 24257021) Logs: wiseLendingNFT: 7
keyID: 1
Is position locked: true
function enterFarm( ... ) ... { uint256 wiseLendingNFT = _getWiseLendingNFT();
isAave[wiseLendingNFT] = _isAave;
_openPosition( ... );
uint256 keyId = _reserveKey( msg.sender, wiseLendingNFT );
isAave[keyId] = _isAave;
... }
function enterFarmETH( ... ) ... {
isAave[wiseLendingNFT] = _isAave;
_openPosition( ... );
uint256 keyId = _reserveKey( msg.sender, wiseLendingNFT );
isAave[keyId] = _isAave;
... }
Also, make sure to update the below line in the
PendlePowerManager.exitFarm() function
:Assessed type
Context