Liquidations miss delegate call to swapper
The LiquidationRow contract is an orchestrator for the claiming process. It is primarily used to collect rewards for vaults. It has a method called liquidateVaultsForToken. Based on docs this method is for: Conducts the liquidation process for a specific token across a list of vaults, performing the necessary balance adjustments, initiating the swap process via the asyncSwapper, taking a fee from the received amount, and queues the remaining swapped tokens in the MainRewarder associated with each vault.\\n```\\nfunction liquidateVaultsForToken(\\n    address fromToken,\\n    address asyncSwapper,\\n    IDestinationVault[] memory vaultsToLiquidate,\\n    SwapParams memory params\\n) external nonReentrant hasRole(Roles.LIQUIDATOR_ROLE) onlyWhitelistedSwapper(asyncSwapper) {\\n    uint256 gasBefore = gasleft();\\n\\n    (uint256 totalBalanceToLiquidate, uint256[] memory vaultsBalances) =\\n        _prepareForLiquidation(fromToken, vaultsToLiquidate);\\n    _performLiquidation(\\n        gasBefore, fromToken, asyncSwapper, vaultsToLiquidate, params, totalBalanceToLiquidate, vaultsBalances\\n    );\\n}\\n```\\n\\nThe second part of the function is performing the liquidation by calling _performLiquidation. A problem is at the beginning of it. IAsyncSwapper is called to swap tokens.\\n```\\nfunction _performLiquidation(\\n    uint256 gasBefore,\\n    address fromToken,\\n    address asyncSwapper,\\n    IDestinationVault[] memory vaultsToLiquidate,\\n    SwapParams memory params,\\n    uint256 totalBalanceToLiquidate,\\n    uint256[] memory vaultsBalances\\n) private {\\n    uint256 length = vaultsToLiquidate.length;\\n    // the swapper checks that the amount received is greater or equal than the params.buyAmount\\n    uint256 amountReceived = IAsyncSwapper(asyncSwapper).swap(params);\\n    // // rest of code\\n}\\n```\\n\\nAs you can see the LiquidationRow doesn't transfer the tokens to swapper and swapper doesn't pul them either (swap function here). Because of this the function reverses.\\nI noticed that there is no transfer back to LiquidationRow from Swapper either. Tokens can't get in or out.\\nWhen I searched the codebase, I found that Swapper is being called on another place using the delegatecall method. This way it can operate with the tokens of the caller. The call can be found here - LMPVaultRouter.sol:swapAndDepositToVault. So I think that instead of missing transfer, the problem is actually in the way how swapper is called.
Change the async swapper call from the normal function call to the low-level delegatecall function the same way it is done in LMPVaultRouter.sol:swapAndDepositToVault.\\nI would like to address that AsyncSwapperMock in LiquidationRow.t.sol is a poorly written mock and should be updated to represent how the AsyncSwapper work. It would be nice to update the test suite for LiquidationRow because its current state won't catch this. If you check the LiquidationRow.t.sol tests, the mock swap function only mints tokens, no need to use delegatecall. This is why tests missed this vulnerability.
Rewards collected through LiquidationRow claimsVaultRewards get stuck in the contract. Liquidation can't be called because it reverts when Swapper tries to work with tokens it doesn't possess.
```\\nfunction liquidateVaultsForToken(\\n    address fromToken,\\n    address asyncSwapper,\\n    IDestinationVault[] memory vaultsToLiquidate,\\n    SwapParams memory params\\n) external nonReentrant hasRole(Roles.LIQUIDATOR_ROLE) onlyWhitelistedSwapper(asyncSwapper) {\\n    uint256 gasBefore = gasleft();\\n\\n    (uint256 totalBalanceToLiquidate, uint256[] memory vaultsBalances) =\\n        _prepareForLiquidation(fromToken, vaultsToLiquidate);\\n    _performLiquidation(\\n        gasBefore, fromToken, asyncSwapper, vaultsToLiquidate, params, totalBalanceToLiquidate, vaultsBalances\\n    );\\n}\\n```\\n