Skip to content
This repository has been archived by the owner on Mar 3, 2024. It is now read-only.

berndartmueller - Performing the liquidation process reverts due to failing to swap the reward tokens #640

Closed
sherlock-admin opened this issue Aug 30, 2023 · 0 comments
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label High A valid High severity issue Reward A payout will be made for this issue

Comments

@sherlock-admin
Copy link
Contributor

sherlock-admin commented Aug 30, 2023

berndartmueller

high

Performing the liquidation process reverts due to failing to swap the reward tokens

Summary

The liquidateVaultsForToken function in the LiquidationRow contract fails to properly swap the reward tokens, causing the liquidation process to fail.

Vulnerability Detail

During the liquidation process, destination vault LP rewards are claimed, reward tokens are swapped to the desired token (usually the base asset WETH), and distributed to the respective rewarder contracts of the destination vaults. The actual token swap is performed via the swap function of the BaseAsyncSwapper contract.

The liquidation process fails due to two issues:

  1. The LiquidationRow.liquidateVaultsForToken does not approve the BaseAsyncSwapper contract to spend the reward tokens, and the BaseAsyncSwapper contract does not pull in the reward tokens from the LiquidationRow contract. Thus, the swap function fails due to insufficient funds.

  2. The swap function sells the given sellTokenAddress for the desired buyTokenAddress via a configured aggregator service. After the successful swap via the aggregator, the swap function determines the actual amount of buyTokenAddress received (buyTokenAmountReceived) and reverts if it is less than the buyAmount specified in the SwapParams struct, we can conclude that the BaseAsyncSwapper is expected to be the receiver of the swapped tokens.

    The swap function does not transfer the bought buyToken tokens to the caller. This is expected in the case of the LMPVaultRouter contract as the swap function is called via a delegatecall.

    However, the LiquidationRow contract calls the swap function with an ordinary call in the _performLiquidation function, as seen in line 251. Consequently, the LiquidationRow contract does not receive the bought tokens from the swapper, and the liquidation process fails when attempting to queue new rewards in the reward contract due to insufficient funds.

Impact

The second step of the liquidation process within the LiquidationRow contract, specifically, the liquidateVaultsForToken function, is broken and reverts anytime a liquidation is performed.

Code Snippet

src/liquidation/LiquidationRow.sol#L251

240: function _performLiquidation(
241:     uint256 gasBefore,
242:     address fromToken,
243:     address asyncSwapper,
244:     IDestinationVault[] memory vaultsToLiquidate,
245:     SwapParams memory params,
246:     uint256 totalBalanceToLiquidate,
247:     uint256[] memory vaultsBalances
248: ) private {
249:     uint256 length = vaultsToLiquidate.length;
250:     // the swapper checks that the amount received is greater or equal than the params.buyAmount
251: ❌  uint256 amountReceived = IAsyncSwapper(asyncSwapper).swap(params);
252:
253:     // if the fee feature is turned on, send the fee to the fee receiver
254:     if (feeReceiver != address(0) && feeBps > 0) {
255:         uint256 fee = calculateFee(amountReceived);
256:         emit FeesTransfered(feeReceiver, amountReceived, fee);
257:

src/liquidation/BaseAsyncSwapper.sol#L63

19: function swap(SwapParams memory swapParams) public virtual nonReentrant returns (uint256 buyTokenAmountReceived) {
..      // [...]
37:
38:     // we don't need the returned value, we calculate the buyTokenAmountReceived ourselves
39:     // slither-disable-start low-level-calls,unchecked-lowlevel
40:     // solhint-disable-next-line avoid-low-level-calls
41:     (bool success,) = AGGREGATOR.call(swapParams.data);
42:     // slither-disable-end low-level-calls,unchecked-lowlevel
43:
44:     if (!success) {
45:         revert SwapFailed();
46:     }
47:
48:     uint256 buyTokenBalanceAfter = buyToken.balanceOf(address(this));
49:     buyTokenAmountReceived = buyTokenBalanceAfter - buyTokenBalanceBefore;
50:
51:     if (buyTokenAmountReceived < swapParams.buyAmount) {
52:         revert InsufficientBuyAmountReceived(buyTokenAmountReceived, swapParams.buyAmount);
53:     }
54:
55:     emit Swapped(
56:         swapParams.sellTokenAddress,
57:         swapParams.buyTokenAddress,
58:         swapParams.sellAmount,
59:         swapParams.buyAmount,
60:         buyTokenAmountReceived
61:     );
62:
63: ❌  return buyTokenAmountReceived;
64: }

Tool used

Manual Review

Recommendation

Consider also using a delegatecall in the LiquidationRow._performLiquidation function to call the BaseAsyncSwapper.swap function, similar to the LMPVaultRouter contract.

Duplicate of #205

@github-actions github-actions bot added High A valid High severity issue Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label labels Sep 11, 2023
@sherlock-admin2 sherlock-admin2 changed the title Nice Maroon Frog - Performing the liquidation process reverts due to failing to swap the reward tokens berndartmueller - Performing the liquidation process reverts due to failing to swap the reward tokens Oct 3, 2023
@sherlock-admin2 sherlock-admin2 added the Reward A payout will be made for this issue label Oct 3, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Duplicate A valid issue that is a duplicate of an issue with `Has Duplicates` label High A valid High severity issue Reward A payout will be made for this issue
Projects
None yet
Development

No branches or pull requests

2 participants