Degen Code

Degen Code

Share this post

Degen Code
Degen Code
UniswapV3 — Quoter Contract
Copy link
Facebook
Email
Notes
More

UniswapV3 — Quoter Contract

The Bartlett of the Blockchain

Oct 01, 2022
∙ Paid
16

Share this post

Degen Code
Degen Code
UniswapV3 — Quoter Contract
Copy link
Facebook
Email
Notes
More
Share

Compared to the mindfuck of ticks, liquidity and square root prices from the Pool contract, and the obscure bit/byte/word manipulation of TickLens, the Quoter seems quite tame and maybe even … boring?

It’s a workhorse contract, designed to do one thing: give accurate quotes for exact input and output swaps. That’s it!

It is a sister contract to the Router, and you’ll find many of the arguments and function names very familiar.

The router is designed for gas efficiency, so it doesn’t have a lot of “extras”. The UniswapV2 Router allowed you to get amounts in and out, in addition to performing the swaps themselves. The UniswapV3 Router just does swaps, and the nearest analog to getAmountsOut lives in the Quoter.

Contract Functions

The Quoter offers four simple functions, styled after the four from the Router:

  • function quoteExactInput(
    bytes memory path,
    uint256 amountIn
    ) external returns (
    uint256 amountOut
    )

  • function quoteExactInputSingle(

    address tokenIn,

    address tokenOut,

    uint24 fee,

    uint256 amountIn,

    uint160 sqrtPriceLimitX96

    ) external returns (
    uint256amountOut
    )

  • function quoteExactOutput(
    bytes memory path,
    uint256 amountOut
    ) external returns (
    uint256 amountIn
    )

  • function quoteExactOutputSingle(

    address tokenIn,

    address tokenOut,

    uint24 fee,

    uint256 amountOut,

    uint160 sqrtPriceLimitX96

    ) external returns (
    uint256amountIn
    )

Console Exploration

We will return to our familiar DAI/WETH pool at 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8, test the various functions that the Quoter provides, and use it to learn more about how the Pool contract works with respect to swaps and prices.

To the console!

I am going to fork from Ethereum mainnet at block height 15654820. If you follow along later, you can use this same fork height to get exact matches for all examples that follow.

(.venv) devil@hades:~/bots$ BLOCK=15654820 brownie console --network mainnet-fork-atblock
Brownie v1.19.1 - Python development framework for Ethereum

BotsProject is the active project.

Launching 'ganache-cli --chain.vmErrorsOnRPCResponse true --wallet.totalAccounts 10 --hardfork istanbul --fork.url https://rpc.ankr.com/eth@15654820 --miner.blockGasLimit 12000000 --wallet.mnemonic brownie --server.port 6969'...
Brownie environment is ready.
>>> chain.height
15654821

Now, make some objects for handy access to the Quoter, Router, TickLens, our LP, and the tokens in question:

>>> quoter = Contract.from_explorer(
    '0xb27308f9F90D607463bb33eA1BeBb41C27CE5AB6'
    )

>>> router = Contract.from_explorer(
    '0xE592427A0AEce92De3Edee1F18E0157C05861564'
    )

>>> lens = Contract.from_explorer(
    '0xbfd8137f7d1516D3ea5cA83523914859ec47F573'
    )

>>> lp = Contract.from_explorer(
    '0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8'
    )

>>> weth = Contract.from_explorer(
    '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'
    )

>>> dai = Contract.from_explorer(
    '0x6B175474E89094C44Da98b954EedeAC495271d0F'
    )

Now let’s get ourselves some fake WETH and approve it for use at the Router:

>>> weth.deposit({'from':accounts[0],'value':10*10**18})
Transaction sent: 0xab33517ae3ebb4a9c593adaa62c093f4ceb4739cc2add95768331ec5c6da5c8a
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 6
  Transaction confirmed   Block: 15654822   Gas used: 43738 (0.36%)

<Transaction '0xab33517ae3ebb4a9c593adaa62c093f4ceb4739cc2add95768331ec5c6da5c8a'>

>>> weth.balanceOf(accounts[0])
10000000000000000000

>>> weth.approve(router.address,weth.balanceOf(accounts[0]),{'from':a
ccounts[0]})
Transaction sent: 0x4ff074901f98b32b1aaabc03a803df7f9115c2a49e8add769345273ead189987
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 7
  Transaction confirmed   Block: 15654823   Gas used: 43952 (0.37%)

<Transaction '0x4ff074901f98b32b1aaabc03a803df7f9115c2a49e8add769345273ead189987'>

And make a snapshot in case we need to return to this state:

>>> chain.snapshot()

The first thing we’d like to verify is that the Quoter gives the correct amounts that we would get from the Router. First we will get a quote for an exact input of 10 WETH, then submit that same input to the router and check. For an exact input swap through a single pool, use the quoteExactInputSingle function:

>>> quoter.quoteExactInputSingle(weth.address, dai.address, 3000, 10*10**
18, 0)
  File "<console>", line 1, in <module>
  File "brownie/network/contract.py", line 1861, in __call__
    return self.transact(*args)
  File "brownie/network/contract.py", line 1729, in transact
    raise AttributeError(
AttributeError: Final argument must be a dict of transaction parameters that includes a `from` field specifying the sender of the transaction

WHAT? Why do I need to generate a transaction?

The tricky thing about the Quoter is that the methods are marked external but not view. These functions consume gas, and if you broadcast them on a live blockchain they will cost money!

The way to manage this is to use the built-in call() method, which will simulate the transaction using eth_call without broadcasting a transaction and burning real gas.

Do not convert this to a transaction, because Brownie (and any other web3 library) will happily broadcast these transactions and drain gas from your bot operator account.

Once more for clarity: Always use call() for Quoter functions!

So now let’s try again with the call() method added:

>>> quoter.quoteExactInputSingle.call(
    weth.address, 
    dai.address, 
    3000, 
    10*10**18, 
    0
)
13036497268644356695772

Now it behaves the way you expect. In fact, the way this contract works is by reverting as part of normal execution, and then passing the return value that you want as the revert message! Check out the Quoter contract source if you’re interested in seeing this mechanism.

Convert the decimals to discover that the swap value is approximately 13,036 DAI:

>>> 13036497268644356695772 / 10**dai.decimals()
13036.497268644356

Now let’s do the same transaction in the Router, and see what we get:

>>> tx = router.exactInputSingle(
    (
        weth.address,
        dai.address,
        3000,
        accounts[0],
        99999999999999999,
        10*10**18,
        0,
        0
    ),
    {'from':accounts[0]}
)

Transaction sent: 0x11d02677df4edc5993fa94dd175faf9cb0bc0f9cb169af715e06cad7e648b406
  Gas price: 0.0 gwei   Gas limit: 12000000   Nonce: 8
  Transaction confirmed   Block: 15654824   Gas used: 105387 (0.88%)

>>> tx.info()
Transaction was Mined 
---------------------
Tx Hash: 0x11d02677df4edc5993fa94dd175faf9cb0bc0f9cb169af715e06cad7e648b406
From: 0x66aB6D9362d4F35596279692F0251Db635165871
To: 0xE592427A0AEce92De3Edee1F18E0157C05861564
Value: 0
Function: Transaction
Block: 15654824
Gas Used: 105387 / 12000000 (0.9%)

Events In This Transaction
--------------------------
├── 0x6B175474E89094C44Da98b954EedeAC495271d0F
│   └── Transfer
│       ├── src: 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8
│       ├── dst: 0x66aB6D9362d4F35596279692F0251Db635165871
│       └── wad: 13036497268644356695772
│   
├── 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2
│   └── Transfer
│       ├── src: 0x66aB6D9362d4F35596279692F0251Db635165871
│       ├── dst: 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8
│       └── wad: 10000000000000000000
│   
└── 0xC2e9F25Be6257c210d7Adf0D4Cd6E3E881ba25f8
    └── Swap
        ├── sender: 0xE592427A0AEce92De3Edee1F18E0157C05861564
        ├── recipient: 0x66aB6D9362d4F35596279692F0251Db635165871
        ├── amount0: -13036497268644356695772
        ├── amount1: 10000000000000000000
        ├── sqrtPriceX96: 2191278529536427425406189775
        ├── liquidity: 1538646794036765605560899
        └── tick: -71761

The transfer events show that the pool swapped my 10000000000000000000 WETH for 13036497268644356695772 DAI. This is the same value that the Quoter gave me, so we can be confident that the contracts are in agreement.

Now let’s revert the chain back to our original position and check that the balances are as before:

>>> chain.revert()
15654823
>>> dai.balanceOf(accounts[0])
0
>>> weth.balanceOf(accounts[0])
10000000000000000000

Here Comes the Curveball

Did you know there are two Quoter contracts?

This post is for paid subscribers

Already a paid subscriber? Sign in
© 2025 BowTiedDevil
Privacy ∙ Terms ∙ Collection notice
Start writingGet the app
Substack is the home for great culture

Share

Copy link
Facebook
Email
Notes
More