Summary
When rebalancing the liquidity of the Lending Pool, excess collateral is sent to the Curve vault. However, collateral is taken directly from the Lending Pool contract instead of the Rtoken
one, breaking the protocol logic as it is not consistent with the overall system's collateral routing.
Vulnerability Details
LendingPool::rebalanceLiquidity
function is called after a collateral deposit or withdrawal, in order to rebalance the system liquidity. When this function determines that there is excess collateral, _depositIntoVault
function is called. As a general rule, collateral token is held in the RToken
contract, nevertheless, excess amount is taken from the Lending Pool contract instead. This discrepancy compromises the system consistency and even causes the process to revert as the Lending Pool collateral balance is unknown or nonexistent.
> LendingPool.sol
function _rebalanceLiquidity() internal {
if (address(curveVault) == address(0)) {
return;
}
uint256 totalDeposits = reserve.totalLiquidity;
uint256 desiredBuffer = totalDeposits.percentMul(liquidityBufferRatio);
@> uint256 currentBuffer = IERC20(reserve.reserveAssetAddress).balanceOf(reserve.reserveRTokenAddress);
if (currentBuffer > desiredBuffer) {
uint256 excess = currentBuffer - desiredBuffer;
@> _depositIntoVault(excess);
} else if (currentBuffer < desiredBuffer) {
uint256 shortage = desiredBuffer - currentBuffer;
_withdrawFromVault(shortage);
}
emit LiquidityRebalanced(currentBuffer, totalVaultDeposits);
}
> LendingPool.sol
function _depositIntoVault(uint256 amount) internal {
@> IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
@> curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;
}
Impact
Impact: High
Likelihood: High
Tools Used
Manual Review
Recommendations
Add the corresponding logic to take the collateral excess from the RToken
contract.
> LendingPool.sol
function _depositIntoVault(uint256 amount) internal {
+ // Must first approve this contract to spend funds from the RToken contract
+ IERC20(reserve.reserveAssetAddress).safeTransferFrom(
+ reserve.reserveRTokenAddress, // from
+ address(this), // to
+ amount // amount
+ );
IERC20(reserve.reserveAssetAddress).approve(address(curveVault), amount);
curveVault.deposit(amount, address(this));
totalVaultDeposits += amount;
}