Introduction


Factory Contract

Factory Implementation

parameters = PoolParameters({
    factory: address(this),
    token0: tokenX,
    token1: tokenY,
    tickSpacing: tickSpacing
});

pool = address(
    new UniswapV3Pool{
        salt: keccak256(abi.encodePacked(tokenX, tokenY, tickSpacing))
    }()
);

delete parameters;

This piece looks weird because parameters is not used. Uniswap uses Inversion of Control to pass parameters to a pool during deployment. Let’s look at the updated Pool contract constructor:

// src/UniswapV3Pool.sol
contract UniswapV3Pool is IUniswapV3Pool {
    ...
    constructor() {
        (factory, token0, token1, tickSpacing) = IUniswapV3PoolDeployer(
            msg.sender
        ).parameters();
    }
    ..
}

Aha! Pool expects its deployer to implement the IUniswapV3PoolDeployer interface (which only defines the parameters() getter) and calls it in the constructor during deployment to get the parameters. This is what the flow looks like:

  1. Factory: defines parameters state variable (implements IUniswapV3PoolDeployer) and sets it before deploying a pool.
  2. Factory: deploys a pool.
  3. Pool: in the constructor, calls the parameters() function on its deployer and expects that pool parameters are returned.
  4. Factory: calls delete parameters; to clean up the slot of the parameters state variable and to reduce gas consumption. This is a temporary state variable that has a value only during a call to createPool().

Pool Initialization


The PoolAddress Library


Swap Path

Path Library

In code, a swap path is a sequence of bytes. In Solidity, a path can be built like this:

bytes.concat(
    bytes20(address(weth)),
    bytes3(uint24(60)),
    bytes20(address(usdc)),
    bytes3(uint24(10)),
    bytes20(address(usdt)),
    bytes3(uint24(60)),
    bytes20(address(wbtc))
);

These are the functions that we’ll need to implement:

  1. calculating the number of pools in a path;
  2. figuring out if a path has multiple pools;
  3. extracting first pool parameters from a path;
  4. proceeding to the next pair in a path;
  5. and decoding first pool parameters.

Multi-Pool Swaps


User Interface

Introducing the Auto Router

Auto Router V2

Auto Router npm Package

Router02 | Uniswap

npm: @uniswap/smart-order-router

Code

https://github.com/Uniswap/swap-router-contracts

Logic

How do we find the shortest path between two tokens in such a mess?

The most suitable solution for such kinds of tasks is based on a graph. A graph is a data structure that consists of nodes (objects representing something) and edges (links connecting nodes). We can turn that mess of pools into a graph where each node is a token (that has a pool) and each edge is a pool this token belongs to. So a pool represented as a graph is two nodes connected with an edge. The above pools become this graph:

The biggest advantage graphs give us is the ability to traverse its nodes, from one node to another, to find paths. Specifically, we’ll use A* search algorithm. Feel free to learn about how the algorithm works, but, in our app, we’ll use a library to make our life easier. The set of libraries we’ll use is ngraph.ngraph for building graphs and ngraph.path for finding paths (it’s the latter that implements A* search algorithm, as well as some others).

In the UI app, let’s create a pathfinder. This will be a class that, when instantiated, turns a list of pairs into a graph to later use the graph to find the shortest path between two tokens.

https://github.com/Jeiwan/uniswapv3-code/blob/milestone_4/ui/src/lib/pathFinder.js


Tick Rounding


Code

Check repo