The LendingPool contract's mechanism for depositing excess liquidity into the Curve crvUSD vault for extra yeild is broken. Due to a mismatch between accounting and actual token holdings, the LendingPool contract attempts to transfer crvUSD to the vault when the crvUSD is held by the RToken contract, resulting in a revert and causing a whole DoS.
The core issue lies in the inconsistent handling of crvUSD tokens after a user deposit. When a user deposits crvUSD via LendingPool.deposit(), the deposited crvUSD is immediately transferred to the RToken contract using IERC20(reserve.reserveAssetAddress).safeTransferFrom(msg.sender, reserve.reserveRTokenAddress, amount);. The RToken contract then mints corresponding RTokens to the user. This transfer is crucial for the RToken mechanism, but it creates a problem for the subsequent Curve vault deposit. After the deposit, LendingPool._rebalanceLiquidity() is called. This function calculates the desired liquidity buffer (20% of total liquidity) and determines if there is excess liquidity. If excess liquidity exists, LendingPool._depositIntoVault() is called. LendingPool._depositIntoVault() attempts to transfer crvUSD from the LendingPool contract to the CurveCrvUSDVault using curveVault.deposit(amount, address(this));. However, this transfer fails because the LendingPool contract no longer holds the crvUSD. The funds are now held by the RToken contract. The CurveCrvUSDVault deposit reverts due to insufficient balance in the LendingPool contract.
The list of functions affected by this issue is as follows:
The inability to deposit excess liquidity into the Curve vault due to the insufficient balance in the LendingPool contract leads to a total DoS as the LendingPool._depositIntoVault fails, causing no users to deposit their crvUSDC into the LendingPool.
Alice deposits 1000 crvUSD into the LendingPool by calling LendingPool.deposit(1000).
The LendingPool contract transfers the 1000 crvUSD to the RToken contract.
The RToken contract mints 1000 RTokens to Alice.
LendingPool._rebalanceLiquidity() is called. Assume that after the deposit, the total liquidity is 1000 crvUSD. The desired buffer is 20% of 1000, which is 200 crvUSD. The current buffer in LendingPool is 0. Thus, the function identifies 800 crvUSD as excess liquidity.
LendingPool._depositIntoVault(800) is called.
LendingPool._depositIntoVault() attempts to transfer 800 crvUSD from the LendingPool contract to the CurveCrvUSDVault.
The transfer reverts because the LendingPool contract's crvUSD balance is 0. The 1000 crvUSD is held by the RToken contract. The Curve vault deposit fails. Thus alice is unable to deposit her crvUSD into the LendingPool causing DoS.
Use this guide to intergrate foundry into your project: foundry
Create a new file FortisAudits.t.sol in the test directory.
Add the following gist code to the file: Gist Code
Run the test using forge test --mt test_FortisAudits_IncorrectRebalanceLogic -vvvv.
Manul code review.
The issue can be resolved by first transfering the crvUSD from the RToken contract to the LendingPool contract before attempting to deposit it into the Curve vault. This can be achieved by calling the RToken.transferAsset in the RToken contract that allows the LendingPool contract to transfer crvUSD from the RToken then deposit into the Curve vault.
The contest is live. Earn rewards by submitting a finding.
This is your time to appeal against judgements on your submissions.
Appeals are being carefully reviewed by our judges.