Search code examples
ethereumweb3jshardhatuniswap

uniswapRouter.getAmountsOut returns less value of token B than present in reverses


I have the next code to get familiar with uniswap mechanics:

  • create pair contract
  • get reserves
  • invoke getAmountsOut and provide all UNI token reserves
  const pairAddress = await _V2Factory.getPair(uniToken, wethToken);
  const pairContract = new ethers.Contract(
    pairAddress,
    IUniswapV2Pair.abi,
    _provider
  );
  const tmpReserves = await pairContract.getReserves();
  const reserves = [reserves.reserve0, reserves.reserve1];
  console.log("reserves", reserves);

  const output = await router.getAmountsOut(reserves[0], [
    uniToken.address,
    wethToken.address,
  ]);
  console.log("output", output);

Output is:

  • reserves: [ 36851089197799104779745113323n, 281312509765248795074n ]
  • output: [ 36851089197799104779745113323n, 140444953548298972803n ]

The question is why I do not receive as the second element of output array equals to 281312509765248795074n - the full WETH liquidity in pair but just part of liquidity equals to 140444953548298972803n value?

I'm running local hardhat node and 3-rd parties actions should not affect the result. I tried on mainnet as well, obviously results are the same.

Tried also switch from getAmountsOut to getAmountOut, but it behaves the same way:

const output2 = await router.getAmountOut(
    reserves[0],
    reserves[0],
    reserves[1]
  );
  console.log("output2", output2);

output: 143271499822164119007n


Solution

  • Uniswap and other constant product automated market makers (AMM) use the following invariant for each pool:

    enter image description here

    Where x and y are the amounts of the tokens in the pool and k is a constant.

    The function getAmountsOut simulates a trade in the pool. After any trade, x and y changes, but the invariant still holds:

    enter image description here

    If you call getAmountsOut with x as the parameter, you essentially ask (ignoring the swap fees for a moment) "how much y would I get if I doubled x in the pool?" Then x' = 2x and the invariant is:

    enter image description here

    Solving it for y' you get y' = y / 2 balances in the pool. The expected output from this trade is y - y', which is again equal to y / 2 and approximately matches your output. Incidentally, this trade would also push up the price in the pool 4 times, since price is defined as y/x.

    In the real code, the result is slightly different, since:

    1. A 0.3% fee is first subtracted from the input amount x before the trade is made.
    2. There are some inevitable rounding errors, which may further reduce the exact number of tokens bought.