Vulnerability Details
In LendingPool contract, function deposit() and withdraw()is used to deposit and withdraw token:
function withdraw(uint256 amount) external nonReentrant whenNotPaused onlyValidAmount(amount) {
if (withdrawalsPaused) revert WithdrawalsArePaused();
ReserveLibrary.updateReserveState(reserve, rateData);
@> _ensureLiquidity(amount);
(uint256 amountWithdrawn, uint256 amountScaled, uint256 amountUnderlying) = ReserveLibrary.withdraw(
reserve,
rateData,
amount,
msg.sender
);
_rebalanceLiquidity();
emit Withdraw(msg.sender, amountWithdrawn);
}
Function _ensureLiquidity() will withdraw token from curve vault if it is do not have enough token:
function _ensureLiquidity(uint256 amount) internal {
if (address(curveVault) == address(0)) {
return;
}
uint256 availableLiquidity = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
if (availableLiquidity < amount) {
uint256 requiredAmount = amount - availableLiquidity;
_withdrawFromVault(requiredAmount);
}
}
It set msg.senderis owner and address(this)is receiver, which is not true because in this case, receiver should be reserve.reserveRTokenAddressand owner is address(this):
function _withdrawFromVault(uint256 amount) internal {
@> curveVault.withdraw(amount, address(this), msg.sender, 0, new address[](0));
totalVaultDeposits -= amount;
}
withdraw()function in curve vault link:
def withdraw(
assets: uint256,
receiver: address,
owner: address,
max_loss: uint256 = 0,
strategies: DynArray[address, MAX_QUEUE] = []
) -> uint256:
"""
@notice Withdraw an amount of asset to `receiver` burning `owner`s shares.
@dev The default behavior is to not allow any loss.
@param assets The amount of asset to withdraw.
@param receiver The address to receive the assets.
@param owner The address who's shares are being burnt.
@param max_loss Optional amount of acceptable loss in Basis Points.
@param strategies Optional array of strategies to withdraw from.
@return The amount of shares actually burnt.
"""
shares: uint256 = self._convert_to_shares(assets, Rounding.ROUND_UP)
self._redeem(msg.sender, receiver, owner, assets, shares, max_loss, strategies)
return shares
Impact
Unable to withdraw token when liquidity is not enough, even when token in curve balance is avaiable.
Recommendations
Update param when call withdraw() to curve vault.