0

I have a question about the _safeTransfer function in the swap function down below.

if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens 

if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens

Let's say its a Token A / WETH pair.

I transfer 10,000 in token A, then the swap amount is supposed to be 1 WETH.

What if I put 2 WETH instead of 1 WETH like swap(0 A token, 2 WETH, me, "")?

Does the _safeTransfer function still work? I cannot find any code that prevents from this?

And why is the _safeTransfer executed earlier than the reserve calculation?

// this low-level function should be called from a contract which performs important safety checks
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external lock {
    require(amount0Out > 0 || amount1Out > 0, 'UniswapV2: INSUFFICIENT_OUTPUT_AMOUNT');
    (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
    require(amount0Out < _reserve0 && amount1Out < _reserve1, 'UniswapV2: INSUFFICIENT_LIQUIDITY');

    uint balance0;
    uint balance1;
    { // scope for _token{0,1}, avoids stack too deep errors
    address _token0 = token0;
    address _token1 = token1;
    require(to != _token0 && to != _token1, 'UniswapV2: INVALID_TO');
    if (amount0Out > 0) _safeTransfer(_token0, to, amount0Out); // optimistically transfer tokens
    if (amount1Out > 0) _safeTransfer(_token1, to, amount1Out); // optimistically transfer tokens
    if (data.length > 0) IUniswapV2Callee(to).uniswapV2Call(msg.sender, amount0Out, amount1Out, data);
    balance0 = IERC20(_token0).balanceOf(address(this));
    balance1 = IERC20(_token1).balanceOf(address(this));
    }
    uint amount0In = balance0 > _reserve0 - amount0Out ? balance0 - (_reserve0 - amount0Out) : 0;
    uint amount1In = balance1 > _reserve1 - amount1Out ? balance1 - (_reserve1 - amount1Out) : 0;
    require(amount0In > 0 || amount1In > 0, 'UniswapV2: INSUFFICIENT_INPUT_AMOUNT');
    { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
    uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));
    uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));
    require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'UniswapV2: K');
    }

    _update(balance0, balance1, _reserve0, _reserve1);
    emit Swap(msg.sender, amount0In, amount1In, amount0Out, amount1Out, to);
}

2 Answers 2

0

Tokens must first be transferred aprior so that it doesn't produce an improper (newly updated) reserve calculation for when balance0 and balance1 are called (necessary to calculate new reserve update), as seen in the following lines:

balance0 = IERC20(_token0).balanceOf(address(this));

balance1 = IERC20(_token1).balanceOf(address(this));

And, so that they can properly calculate balance0Adjusted / balance1Adjusted in the following lines:

uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(3));

uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(3));

Which are necessary to perform the proper assertions before the actual updates (using new updated balances) are made in pool reserves, as per:

_update(balance0, balance1, _reserve0, _reserve1);

2
  • Thank you for the answer, What I understand is that even though I put higher value as the amount1Out, and it transfered the tokens to me, it would end up being reverted in the update function ? As a result, the swap function will be failed.
    – DHC
    Oct 16, 2023 at 3:38
  • They are two separate things; the _update function is referencing the pool balances in the LP; the safe transfer is for the token; if your tests or understanding is failing you; you could as a last resort try out my Uniswappy package (github.com/icmoore/uniswappy); its a python refactor of the Uniswap V2 code which can be used for running spit ball tests (such as this) to double check or debug your work
    – Ian Moore
    Oct 16, 2023 at 3:59
0

It's a part of pattern sometimes used in blockchain programming. Uniswap describes it as "all swaps are flash swaps":

Flash swaps are an integral feature of Uniswap V2. In fact, under the hood, all swaps are actually flash swaps! This simply means that pair contracts send output tokens to the recipient before enforcing that enough input tokens have been received. This is slightly atypical, as one might expect a pair to ensure it's received payment before delivery. However, because Ethereum transactions are atomic, we can roll back the entire swap if it turns out that the contract hasn't received enough tokens to make itself whole by the end of the transaction.

The goal is enable permissionless borrowing of tokens in the pool (flash loans) that can be used for any purposes as long as you return the assets or their equivalent to the pool in the same transaction.

What if I put 2 WETH instead of 1 WETH like swap(0 A token, 2 WETH, me, "")?

No, because this is guaranteed to revert, unless you have already transferred some tokens to the pool, before calling this. But If you put some data as an argument and implemented some callback function that sends enough A token or WETH back to the pool, that could work, if there's enough WETH in the pool in the first place.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.