Uniswap / examples

120 stars 147 forks source link

Calculation of Quoting for WETH to USDC is Wrong #42

Open arjunaskykok opened 1 year ago

arjunaskykok commented 1 year ago

Using this configuration in v3-sdk/quoting/config.ts:

  tokens: {
    in: USDC_TOKEN,
    amountIn: 1000,
    out: WETH_TOKEN,
    poolFee: FeeAmount.MEDIUM,
  },

I got this result (which is correct):

Selection_140

On Mar 4th, 2023, 1 ETH is $1570. So with $1000, we would get around 0.63 ETH.

But when I switched the tokens, I got the wrong result. I used this configuration:

  tokens: {
    in: WETH_TOKEN,
    amountIn: 1,
    out: USDC_TOKEN,
    poolFee: FeeAmount.MEDIUM,
  },

Selection_141

The result should be ~1570 USDC.

balazsotakomaiya commented 1 year ago

Hey @arjunaskykok ,

I've ran into the same problem, and here's what I've found after a bit of debugging:

In the example, you're using the token addresses returned by the poolContract to call the quoteExactInputSingle function.

Like so:

 const [token0, token1, fee] = await Promise.all([
    poolContract.token0(),
    poolContract.token1(),
    poolContract.fee(),
  ])

Whereas, what appears to be the case, is that the order in which the contract returns token0 and token1 isn't guaranteed, in fact it appears to be returning the tokens in alphabetical order. One might call it consistently inconsistent.

The solution is quite simple, albeit, the examples should be updated to reflect this.

Simply don't use the token addresses returned by the pool contract when calling quoteExactInputSingle, as you already know the token addresses anyways.

  const quotedAmountOut = await quoterContract.callStatic.quoteExactInputSingle(
    CurrentConfig.tokens.in.address, // instead of: await poolContract.token0()
    CurrentConfig.tokens.out.address, // instead of: await poolContract.token1()
    poolConstants.fee,
    fromReadableAmount(
      CurrentConfig.tokens.amountIn,
      CurrentConfig.tokens.in.decimals
    ).toString(),
    0
  )
arjunaskykok commented 1 year ago

@balazsotakomaiya , Thanks for the solution. It works!

MetaMmodern commented 7 months ago

@balazsotakomaiya fantastic, great investigation and thank you for this. However, I'd like to find out: what are token0() and token1() calls used for then, if we still need to pass them into computePoolAddress call? What's the point of this roundabout? The documentation says:

This guide fetches this information first in order to show how to fetch any metadata, which will be expanded on in future guides.

But it's weird, what data might me needed that we don't already have? Is it possible to get token address for example without knowing it beforehand?