© 2022
January 31, 2023

Swapping ERC20 tokens – sweepToken Uniswap’s SwapRouter MEV opportunity

After previous article about Uniswap’s SwapRouter bug that permits unspent ETH to be left in SwapRouter and then picked up by a MEV bot, I got replies on Twitter that there is also a possibility to left funds in SwapRouter when swapping ERC-20 tokens.

To sweep ERC20 tokens that may have left in SwapRouter after swap, Uniswap proposed two functions – sweepToken and sweepTokenWithFee – they need to be called after a swap. Case is similar to refundETH from previous article. I recommend you to read it first to familiarise yourself with the concepts as basics will be omitted here for clarity.

So I decided to do similar research for sweepToken and sweepTokenWithFee.


To calculate how much MEV bots could make exploiting this, I decided to calculate:

Let’s find out how many such sweepToken and sweepTokenWithFee calls that are not a part of complex swap transaction (example tx 0x01030b3e6d9f80d15019f7b3cc9065bd84918bb0e50c13c195434d23b76b16b7).

That’s how it looks in Tenderly – just a single sweepToken call.

The idea here is that it is probably a MEV bot that aims to spend as little gas as possible while sweeping the funds.

So as an end result, let’s write an SQL query in Dune to find out such sweeps, calculate amount in USD and number of sweeps for each week starting from May 2021 (SwapRouter was published).

Data extraction

Step 0 – brief solution idea

We need to scan ethereum.traces table for single sweepToken calls that are not part of a complex tx, then find corresponding ERC20 transfer(address recipient, uint256 amount) calls to figure out the amount of token swept, then find USD prices at the time of transfer. Do the same for sweepTokenWithFee.

Step 1 – find single sweepToken calls

  1. input column starts with 0xdf2ab5bb (Keccak256).
  2. trace_address array is [] (empty) – it means it is a high level call.
  3. to is either SwapRouter or SwapRouter02
  4. tx_success = true (successful tx)
  5. block_time > ‘2021-05-01’ (look after SwapRouter is published).

Step 2 – find corresponding ERC20 transfer

Then to get how much assets were swept, scan same table for transfer method call:

  1. input column starts with 0xa9059cbb (transfer(address recipient, uint256 amount) call)
  2. Same block and tx hash.
  3. trace_address array length is 1 (nested call by sweepToken).

Step 3 – find USD prices

We can look up prices.usd table for token that was transferred on step 2:

  1. Join by contract_address and minute columns.

Step 4 – implement query

The query is pretty straightforward. On step 1 we need to extract token address (first parameter of sweepToken) from input column (token address is in bold) – this is done by simple substring:


On step 2 we need to extract amount param from transfer function call. HEX string of token amount is in bold, this is input column as well:


These extractions are easily done by Dune SQL byte array and substring functions.

Then we need to group by week. The resulting query is (Dune):

raw_sweeps AS
        lower('0x' || substr(tr_sweep.input, 35, 40)) AS token,
        bytearray_to_uint256('0x' || substring(tr_transf.input, -64)) AS amount
    FROM ethereum.traces tr_sweep
        JOIN ethereum.traces tr_transf ON
                tr_transf.block_time = tr_sweep.block_time    -- same block
                AND tr_transf.tx_hash = tr_sweep.tx_hash      -- same hash
                AND COALESCE(cardinality(tr_transf.trace_address), 0) = 1 -- subcall
                AND starts_with(lower(tr_transf.input), '0xa9059cbb') -- transfer(address,uint256)
        tr_sweep.block_time > DATE '2021-05-01'
        AND starts_with(lower(tr_sweep.input), '0xdf2ab5bb') -- sweepToken
        AND tr_sweep.tx_success
        AND COALESCE(cardinality(tr_sweep.trace_address), 0) = 0 -- high level call
        AND (tr_sweep.to = 0xE592427A0AEce92De3Edee1F18E0157C05861564 OR tr_sweep.to = 0x68b3465833fb72a70ecdf485e0e4c7bd8665fc45) -- to SwapRouter
    ORDER BY tr_sweep.block_time DESC
sweeps AS
    SELECT rs.*,
        (CAST(rs.amount AS double) / POW(10, toks.decimals)) AS am_token,
        (CAST(rs.amount AS double) / POW(10, toks.decimals)) * COALESCE(price.price, 0) AS price_USD
    FROM raw_sweeps rs
        LEFT JOIN tokens.erc20 toks ON toks.contract_address = rs.token AND toks.blockchain = 'ethereum'
        LEFT JOIN prices.usd price ON 
            price.contract_address = rs.token
            AND price.minute = date_trunc('minute', rs.block_time) AND price.blockchain = 'ethereum'
    date_trunc('week', block_time) AS week,
    SUM(price_USD) AS amount_USD,
    COUNT(*) AS cnt_sweeps,
    arbitrary(tx_hash) AS example_tx
FROM sweeps

Here is the visualisation:

To check the correctness here is the QA query on Dune that gives biggest sweeps sorted by biggest USD amount:

Turned out there were no single sweepTokenWithFee sweeps, so I omitted it for clarity.


I analysed 3 biggest sweeps, tried to figure out how funds were left in SwapRouter with this query:

Swap performed partially due to absence of liquidity, but sweepToken wasn’t called (refundETH was called, that didn’t help in this case).

In this call, according to Tenderly, wasn’t even a try to refund unspent funds:


In this article I tried to calculate how much in USD of ERC20 tokens were swept by MEV bots calling sweepToken in a single call. Looks like it is slightly more than $50K on Mainnet, but the distribution is very uneven with few big spikes. Except the first case ($39712.39 sweep), it looks like the reason of lost funds is users not calling sweepToken and Uniswap didn’t add any protection to prevent this, that they should have been done IMO.

Future research to understand the real severity of this bug (as was pointed out in the comments on Twitter) is to set up archive node and filter all swap txs that left SwapRouter with some funds and then sum them up.

For now I would strongly advise to use Uniswap router-sdk whenever possible where they seem to have these refund checks.

If you have something to say – welcome to this Twitter thread.

About Web3 and blockchain developer.
Interests: Ethereum, Uniswap and DeFi, TypeScript and Go.