2

Hi i am trying to understand the reasoning behind the swap function of a pancake pair on the pancakeSwap DEX and the checks it does.

this is the function :

require(amount0Out > 0 || amount1Out > 0, 'Pancake: INSUFFICIENT_OUTPUT_AMOUNT');
        (uint112 _reserve0, uint112 _reserve1,) = getReserves(); // gas savings
        require(amount0Out < _reserve0 && amount1Out < _reserve1, 'Pancake: 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, 'Pancake: 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) IPancakeCallee(to).pancakeCall(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, 'Pancake: INSUFFICIENT_INPUT_AMOUNT');
        { // scope for reserve{0,1}Adjusted, avoids stack too deep errors
        uint balance0Adjusted = balance0.mul(1000).sub(amount0In.mul(2));
        uint balance1Adjusted = balance1.mul(1000).sub(amount1In.mul(2));
        require(balance0Adjusted.mul(balance1Adjusted) >= uint(_reserve0).mul(_reserve1).mul(1000**2), 'Pancake: K');
        }

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

Q1 : in the code above there is a distinction between the balance0 and reserveO as well as balance1 and reserve1 aren't they the same? is the balance of the pair contract an accumulation of fees?

Q2 : I would like to also know about amountOIn, and why it is calculated that way?

Q3 : What is the Pancake: K error and why is it calculated that way?

2 Answers 2

4
+100

balance0 and balance1 are local variables of functions mint, burn and swap while reserve0 and reserve1 are globally declared variables.

why there is a need for computing balance and reserve?

As mentioned in UniswapV2 whitepaper

Another complication is that it is possible for someone to send assets to the pair contract—and thus change its balances and marginal price—without interacting with it, and thus without triggering an oracle update. If the contract simply checked its own balances and updated the oracle based on the current price, an attacker could manipulate the oracle by sending an asset to the contract immediately before calling it for the first time in a block. If the last trade was in a block whose timestamp was X seconds ago, the contract would incorrectly multiply the new price by X before accumulating it, even though nobody has had an opportunity to trade at that price. To prevent this, the core contract caches its reserves after each interaction, and updates the oracle using the price derived from the cached reserves rather than the current reserves. In addition to protecting the oracle from manipulation, this change enables the contract re-architecture described below in section 3.2.

To put it in simple words,

At each swap call, first reserves for both the tokens are fetched, then tokens are transferred optimistically (to allow flash swaps), then there are conditions to check that swap was legal including fees and adjusted balance after a change in the quantity of tokens. Finally, the _update function is called which updates reserves according to new balances & old reserves after swapping.

Reserves are updated on mint, burn, and sync as well.

If there was only no provision updating reserves only after AMM functions, it leads to an attack vector of transferring tokens (which are part of pair) directly without using the AMM function and potentially making currency cheaper and causing imbalance due to which it cannot be used as an oracle.

how are amount0In and amount1In calculated?

amount0In and amount1In are local variables that keep tracking of what quantity of each token inflow during the particular swap. In simple terms, it is checking if the balance (after transferring/receiving) is greater than reserve by amount0Out (this is the case if that token was deposited because reserves are not updated after the last swap) then amount0In is balance0 (total after inflow) + amount0Out(outflow) - _reserve0(last_updated).

Usually, only either of the tokens of pair is sent out of AMM, and in that case, amount0Out or amount1Out, let say amount0Out is 0, so amount0In becomes balance0 (total after inflow) - _reserve0(last_updated). this is not always the case and also both cannot be 0, but it easier to visualize.

If balance(after transferring/receiving) is not greater than reserve by amount0Out then we can be sure that the net token quantity of that was transferred out and hence amount0In would be 0 in that case. This does not mean that a specific token was not transferred to AMM but instead more quantity was transferred out from AMM than that of transferred into AMM. in either way, if transfer out is more than transfer in, it is taken as 0 as it will not have an impact on fees and oracles as they are calculated on net changes!

what is the Pancake: K(or UniswapV2: K) error and why is it calculated that way?

From UniswapV2 docs

The Uniswap constant product formula is “X * Y = K”. Where X and Y represent the respective reserve balances of two ERC-20 tokens, and “K” represents the product of the reserves. It is this “K” to which the “K” error refers.

In essence, the “K” error means that a trade was attempted that somehow left the trading pair with less reserves than should be there, and as a result the transaction is reverted.

This can have a few different causes.

1
  • can amount0out and amount1out both have non 0 < values?
    – darren z
    Dec 29, 2021 at 23:52
3

Here is the TL;DR version.

Q1 balance0 and balance1 vs reserve0 and reserve1

reserve0 and reserve1 are the balances of each tokens in the pair contract just before the swap transaction is processed.

In contrast, balance0 and balance1 are the balances of each tokens in the pair contract after the swap transaction has been processed.

The difference between the two is the increase or decrease of each token due to the swap.

Q2 amount0In calculation

This variable is the amount of token that was sent to the pair contract. If a user withdraws an amount of token 1 and sends an amount of token 0 (a regular swap), then amount0In would simply equal the amount of token 0 sent to the pair contract.

However, it is possible to withdraw token 0 and send back token 0 to the pair (a flash loan of a single token, without swap). The calculation is a bit more complicated to reflect that possibility.

Q3 Pancake: K

All Uniswap forks use the same concept of constant product formula: x*y=k.

reserve0 times reserve1 should always be equal to a constant k. This is what enforces the fair pricing of the swap.

The require statement simply checks that the product of the balances of each tokens after the swap, adjusted for the fee, is still equal to the k constant before the swap.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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