Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V3SwapRouter.V3SwapExactOutput() has the wrong implementation since it applies the #12

Closed
howlbot-integration bot opened this issue Nov 4, 2024 · 4 comments
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working primary issue Highest quality submission among a set of duplicates 🤖_19_group AI based duplicate group recommendation sponsor disputed Sponsor cannot duplicate the issue, or otherwise disagrees this is an issue sufficient quality report This report is of sufficient quality unsatisfactory does not satisfy C4 submission criteria; not eligible for awards

Comments

@howlbot-integration
Copy link

Lines of code

V3SwapRouter.sol#L191-L202

Vulnerability details

Proof of Concept

First of all, let's understand how a multi-hop exact output swap should work.

In a multi-hop exact output swap, we specify the exact amount of tokens we want to receive as the final output in the last pool, and the swap logic works backward from the last pool to calculate the input needed at each preceding pool to meet this exact output requirement.

The following specifies amountOut as the exact amount for the output token for the LAST pool:

function v3SwapExactOutput(
    address recipient,
    uint256 amountOut,
    uint256 amountInMaximum,
    bytes calldata path,
    address payer
  ) internal {
    maxAmountInCached = amountInMaximum;
    (int256 amount0Delta, int256 amount1Delta, bool zeroForOne) =
      _swap(-amountOut.toInt256(), recipient, path, payer, false);        // first argument is the exact output amount as ```-amountOut```

    uint256 amountOutReceived = zeroForOne ? uint256(-amount1Delta) : uint256(-amount0Delta);

    if (amountOutReceived != amountOut) revert V3InvalidAmountOut();

    maxAmountInCached = DEFAULT_MAX_AMOUNT_IN;
  }

It calls _swap() using a negative integer -amountOut as the first argument.

The problem lies in the implementation in _swap():

function _swap(int256 amount, address recipient, bytes calldata path, address payer, bool isExactIn)
    private
    returns (int256 amount0Delta, int256 amount1Delta, bool zeroForOne)
  {
    (address tokenIn, uint24 fee, address tokenOut) = path.decodeFirstPool();

    zeroForOne = isExactIn ? tokenIn < tokenOut : tokenOut < tokenIn;

    (amount0Delta, amount1Delta) = IKatanaV3Pool(computePoolAddress(tokenIn, tokenOut, fee)).swap( // slippage control is relaxed
      recipient, zeroForOne, amount, (zeroForOne ? MIN_SQRT_RATIO + 1 : MAX_SQRT_RATIO - 1), abi.encode(path, payer)
    );
  }

Instead of passing the first argument amount, which is supposed to be the exact amount for the output token for the LAST pool, it applies it as the argument for calling the swap function of the FIRST pool. As a result, This results in the first pool using amountOut as the required output, causing incorrect calculations across the multi-hop path.

Impact: loss of funds due to wrong calculation or non-function of the transaction.

Recommended Mitigation Steps

Start with the Last Pool: Decode and apply amountOut to the last pool in the path, calculating backward to determine the required input for each previous pool.

Assessed type

Math

@howlbot-integration howlbot-integration bot added 3 (High Risk) Assets can be stolen/lost/compromised directly 🤖_19_group AI based duplicate group recommendation bug Something isn't working primary issue Highest quality submission among a set of duplicates sufficient quality report This report is of sufficient quality labels Nov 4, 2024
howlbot-integration bot added a commit that referenced this issue Nov 4, 2024
@khangvv
Copy link

khangvv commented Nov 4, 2024

When executing the V3_SWAP_EXACT_OUT command, the path is expected to be in reverse order, as noted in this reference. For context, we’ve maintained Uniswap’s original implementation for this part of the code without modifications, ensuring compatibility with any Uniswap SDK for constructing swap parameters.

@khangvv khangvv added the sponsor disputed Sponsor cannot duplicate the issue, or otherwise disagrees this is an issue label Nov 4, 2024
@khanhtaskymaviscom
Copy link

As @khangvv mentioned, path.decodeFirstPool() actually returns "the LAST pool" in your term, so it should be correct here.

@alex-ppg
Copy link

The Warden attempts to outline a vulnerability in the way multi-hop exact output swaps are executed which stems from a misunderstanding of the original Uniswap V3 system this behavior has been copied from. The path is specified in an inverse direction to facilitate its gas-efficient processing as correctly implemented in the current code.

@c4-judge
Copy link

alex-ppg marked the issue as unsatisfactory:
Invalid

@c4-judge c4-judge added the unsatisfactory does not satisfy C4 submission criteria; not eligible for awards label Nov 11, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
3 (High Risk) Assets can be stolen/lost/compromised directly bug Something isn't working primary issue Highest quality submission among a set of duplicates 🤖_19_group AI based duplicate group recommendation sponsor disputed Sponsor cannot duplicate the issue, or otherwise disagrees this is an issue sufficient quality report This report is of sufficient quality unsatisfactory does not satisfy C4 submission criteria; not eligible for awards
Projects
None yet
Development

No branches or pull requests

4 participants