Here are my notes for the Uniswap V2 topic study.
Overview
Liquidity
Token x = 200
Token y = 200
l = liquidity (how big the AMM is)
How to determine the amount of token that goes in/out on a trade, is ruled by this equation:
x·y = l^2

The amount of liquidity (l) is very important, as the bigger the curve, the better price you’ll get when you do a trade, between x & y.
Contracts Uniswap V2
Factory Contract
To create a pair contract (ie ETH/USDT).
Router Contract
Instead of calling directly the pair contracts (ETH/USD, DAI/ETH, DAI/MKR etc.), where you can accidentally make a mistake, you call the Router contract. It’s an intermediate router, to safely add liquidity to the pair contracts or swap in a pair contract.
Swaps can interact with a single pair contract or with multiple pair contracts.
You can also call the Router contract to create a new pair contract, which in turn will call the Factory contract.
Swap
Math
Alice swaps dx for dy. How much amount of dy will she get?
x0·y0 = l^2
(x0+dx)·(y0-dy) = l^2
x0·y0 = (x0+dx)·(y0-dy)
Getting dy (without including swap fees):
dy = (y0 · dx) / (x0 + dx)
Fees
Swap fee charged on token in: 0 <= f <= 1.
Swap fee = f·dx , in Uniswap V2 the fee is f = 0.003
dy with fee:
dy = (dx·(1-f)·y0) / (x0 + dx·(1-f))
Swap Contract Calls
Swap WETH -> DAI
- The user calls Router
– swapExactTokensForTokens: try to convert all the WETH to DAI
– swapExactEthForTokens: same but ETH is not an ERC20 - The user calls transferFrom (WETH from the user to the pair contract)
- The Router contract calls swap
- The Router contract calls transfer (DAI from the pair contract to the user)
Multi-Hop Swap WETH -> MKR
We have: DAI/WETH and DAI/MKR
- The user calls Router
– swapExactTokensForTokens: try to convert all the WETH to DAI - The user calls transferFrom (WETH from the user to the pair contract DAI/WETH)
- The Router contract calls swap DAI/WETH
- The Router contract transfer (DAI to the pair contract, instead of the user)
- The Router contract calls swap DAI/MKR
- The Router contract calls transfer (MKR from the pair contract to the user)
Code Walkthrough: v2-periphery (router)
- swapExactTokensForTokens: This function lets you swap a specific amount of one token for another token, by using all the input for max output.
- Calculate the output:
getAmountsOutfigure out how many tokens you’ll receive - Check slippage: ensures the calculated output ≥
amountOutMin - Transfer your tokens to the first liquidity pool
- Execute the swap, which hops through each pool in the path, ultimately sending the output tokens
to
- Calculate the output:
function swapExactTokensForTokens(
uint amountIn,
uint amountOutMin,
address[] calldata path,
address to,
uint deadline
) external virtual override ensure(deadline) returns (uint[] memory amounts) {
// calculates swap outputs
// amounts[0] = input
// amounts[last] = output
// amounts[rest] = intermediate outputs
amounts = UniswapV2Library.getAmountsOut(factory, amountIn, path);
require(
amounts[amounts.length - 1] >= amountOutMin,
'UniswapV2Router: INSUFFICIENT_OUTPUT_AMOUNT'
);
TransferHelper.safeTransferFrom(
path[0], msg.sender, UniswapV2Library.pairFor(factory, path[0], path[1]), amounts[0]
);
_swap(amounts, path, to);
}
- _swap: This function executes the token swap(s) across liquidity pools. It handles both single-hop and multi-hop swaps.
- The tokens you’re swapping must already be in the first pool before calling this function
- It loops through each pair of tokens in the path:
path = [TokenA, WETH, TokenB]
Iteration 0: TokenA → WETH (send output to WETH/TokenB pool)
Iteration 1: WETH → TokenB (send output to final recipient) - The
tologic:- Not the last swap? → Send tokens directly to the next liquidity pool (chaining)
- Last swap? → Send tokens to the final recipient (_to)
- This chaining is gas efficient: tokens flow pool-to-pool without touching the router.
function _swap(uint[] memory amounts, address[] memory path, address _to) internal virtual {
// i | path[i] | path[i + 1]
// 0 | path[0] | path[1]
// 1 | path[1] | path[2]
// n-2 | path[n-2] | path[n-1]
for (uint i; i < path.length - 1; i++) {
(address input, address output) = (path[i], path[i + 1]);
(address token0,) = UniswapV2Library.sortTokens(input, output);
uint amountOut = amounts[i + 1];
(uint amount0Out, uint amount1Out) = input == token0 ? (uint(0), amountOut) : (amountOut, uint(0));
address to = i < path.length - 2 ? UniswapV2Library.pairFor(factory, output, path[i + 2]) : _to;
IUniswapV2Pair(UniswapV2Library.pairFor(factory, input, output)).swap(
amount0Out, amount1Out, to, new bytes(0)
);
}
}
Visual Example (AI):
Path: [DAI, WETH, USDC]
[DAI/WETH Pool] --WETH--> [WETH/USDC Pool] --USDC--> [You]
Swap #1 Swap #2
(to=next pool) (to=_to)
- getAmountsOut: This function calculates how many tokens you’ll get at each step of a swap path, before the swap actually happens. It’s a read-only preview.
// performs chained getAmountOut calculations on any number of pairs
function getAmountsOut(address factory, uint amountIn, address[] memory path) internal view returns (uint[] memory amounts) {
require(path.length >= 2, 'UniswapV2Library: INVALID_PATH');
amounts = new uint[](path.length);
amounts[0] = amountIn;
for (uint i; i < path.length - 1; i++) {
(uint reserveIn, uint reserveOut) = getReserves(factory, path[i], path[i + 1]);
amounts[i + 1] = getAmountOut(amounts[i], reserveIn, reserveOut);
}
}
Visual Example (AI):
Input: amountIn=100, path=[DAI, WETH, USDC]
Output: amounts = [100, 0.05, 98]
│ │ └── Final USDC you'll receive
│ └── WETH from first swap
└── Your DAI input
- getReserves refers to the amount of balance of tokens that are locked inside the contract.
I created a test to get amounts out here. Adding 1 WETH, we would get that amount of DAI, and then with that amount of DAI, we would get that amount of MKR:

- getAmountsIn: The getAmountsIn function calculates how much input token you need to receive a specific output amount when swapping through multiple liquidity pools. It works backwards from the desired output

Example: you want to receive exactly 1000 USDC at the end. How much DAI do you need to start with?
- Path: [DAI, WETH, USDC] (3 tokens = 2 hops).
- Pool Reserves (hypothetical):
| Pool | Reserve In | Reserve Out |
|---|---|---|
| DAI/WETH | 500,000 DAI | 250 WETH |
| WETH/USDC | 200 WETH | 400,000 USDC |
We know the last amount of the array [DAI, WETH, USDC]
amounts = [?, ?, 1000] // We want 1000 USDC out

I created a test to get amounts out here.
- swap

Spot Price
The spot price is the instantaneous exchange rate between two tokens at a specific point on the curve. For a constant product AMM where x·y = k:
- Spot price of X in terms of Y = y / x (how much Y you get per unit of X)
- Spot price of Y in terms of X = x / y (how much X you get per unit of Y)
Mathematically, it’s the absolute value of the slope of the tangent line at that point: |dy/dx| = y/x.
Example, at point (25, 9):
- Spot price = 9/25 = 0.36
- This means: 1 token X ≈ 0.36 token Y at that instant

The spot price is theoretical, it’s what you’d get for an infinitely small trade. Real trades move along the curve, so larger swaps get worse rates (slippage). The tangent line visualizes this: actual trades follow the curve, not the straight line.
Execution Price
The execution price is the actual rate you get when making a real trade, it’s the total output divided by the total input:
Execution price = |Δy| / Δx
- Spot price = rate for an infinitely small trade (tangent slope)
- Execution price = rate for your actual trade size (secant slope)

- You add Δx = 15 tokens X
- You receive |Δy| ≈ 3.4 tokens Y
- Execution price = 3.4 / 15 ≈ 0.22
But the spot price was 0.36 — you got ~40% less than the “advertised” rate!
As you move along the curve, each additional unit of X gives you less Y. The execution price is the average rate across the whole trade. Larger trades = more slippage = worse execution price.
Slippage
Difference between the price you expect to receive vs what you actually receive.
Example:
– Before swap:
– amount in: 2000 DAI
– calculated amount out: 1 ETH
price of ETH = 2000 DAI / 1 ETH = 2000 DAI
– After swap:
– amount sent: 2000 DAI
– amount received: 0.99 ETH
price of ETH = 2000 DAI / 0.99 ETH = 2000 DAI
The main cause of slippage is market movement and transaction ordering.
Create Pool
We sort the tokens by their int value, then we calculate the bytecode and use create2 to deploy the pair token. There are no constructor arguments for that contract, but there is an initialize function that we call right after deploying the contract, to initialize the token values. Finally we set getPair token for the pair, and also for the reverse pair (which is the same).

Add Liquidity
Here are the maths to calculate the total shares to mint, when a user deposits into a pool.
T= total shares
s = shares to mint
L0 = value of pool before
L1 = value of pool after
s = (L1 - L0 / L0) * T = dx / x0 = dy / y0 --> s = (dx / x0) · T = (dy / y0) · T
The mint shares should be proportional to increase of L0 to L1.Ç
T+s / T = L1 / L0
Here are the maths to calculate how many tokens to receive (and how many shares to burn).
T = total shares
s = shares to burn
L0 = value of pool before
L1 = value of pool after
L0 - L1 = (s / T) · L0
How many dx and dy to add?
Price after add liquidity = price before
(y0 + dy) / (x0 + dx) = y0 / x0 --> dy / dx = y0 / x0
How to calculate the value in the pool?
f(x, y) = √ x · y. --> motivation: x · y = L²
Value of the pool in terms of token x
f(x, y) = 2·x -->this comes from f(x0, y0) = x0 + (x0 / y0) · y0 = 2·x0
Code Dive
- addLiquidity (user -> router)
- getPair and createPair (router -> factory)
- create2 (factory –> DAI/WETH)
- transferFrom (from the user to the pair contract)
- mint (router to DAI/WETH pair contract)
- send shares to the user (LP from DAI/WETH-> user)

- to: the address that will receive the pool shares
- liquidity: amount of pool shares that were minted
Remove Liquidity
The price after removing liquidity must be equal to the price before
dy / dx = y0 / x0
Instead of minting shares, we’ll burn shares.
If we have a pool value function which is:
f(x, y) = √x·y
Then:
(L0 - L1) / L0 = dx / x0 = dy / y0
- Call removeLiquidity from Router
- The router calls transferFrom the user, to send the pool shares to the pair contract DAI/WETH (the user will have to first approve to spend the pool shares to the router)
- The router calls burn, to burn shares
- The pair contract transfers WETH and DAI back to the user

Flash Swap
If a DeFi protocol supports a flashloan, then it allows a smart contract to borrow an amount of tokens from this DeFi protocol, as long as the token is repaid in the same transaction.
Flash Swap Fee
- borrow x0
- repay dx1 = dx0 + fee
- x0 = reserve before
Flash Swap Fee Equation:
x0 - dx + 0.997·dx >= x0. (reserve after >= reserve before)
fee = (3 / 997) · dx
In Uniswap, the fee is collected in the token that is coming in.
Swap dx for dy:
– amount in = dx
– amount in – fee = 0.997 · dx
– swap fee = 0.003 · dx
Flash swap:
– amount out = borrow dx0
– amount in = repay dx1 = dx0 + fee
– amount in – fee = 0.997 · dx1
– flash swap fee = 0.003 · dx1
Flash Swap Contract Calls
- Start flash swap by calling some function in a custom Flash Swap smart contract
- This contract will call the function
swapto the DAI/WETH pair contract - The pair contract will send the amount of tokens requested to the smart contract
- Then the pair contract calls
uniswapV2Callfrom the originator smart contract (it assumes it has one). Your custom code will be then executed - After the custom code is executed inside the function
uniswapV2Callthen the custom contract will have to repay plus fees (transfer) and it will terminate successfully if the amount is the expected one (or greater)
Twap Spot Price Oracle
The danger of using Uniswap V2 spot price as a price oracle is that the spot price is very easy to manipulate.
The internal function _update is executed whenever we call swap, or add/remove liquidity.
It updates reserves and, on the first call per block, price accumulators.

Twap = Time Weighted Average Price
pᵢ = price of token x in terms of token y, between time tᵢ ≤ t ≤ tᵢ₊₁
Δtᵢ = tᵢ₊₁ – tᵢ
Considerations:
- The time difference is not regular: because tx’s sent into the UniswapV2Pair contract are sent at irregular time
- The price jumps at each t: anytime the user sents a tx the price in the pair contract will change

TWAP from t₀ to tₙ
Δt₀ Δt₁ Δt₂ Δtₙ₋₁
─── · p₀ + ─── · p₁ + ─── · p₂ + ⋯ + ─── · pₙ₋₁
tₙ-t₀ tₙ-t₀ tₙ-t₀ tₙ-t₀
TWAP from tₖ to tₙ
n Δtᵢ
∑ ──── · pᵢ
i=k tₙ - tₖ
Cumulative Price
In Solidity we’ll have one state variable called cumulative price
cj = cumulative price up to tⱼ
j-1
tⱼ = ∑ Δtᵢ · pᵢ
i=0
Miss-conception: if you know the TWAP of token x you can know the TWAP of token y:
1 y
────── != TWAP of y despite p = ── = spot price of token x in terms of y
TWAP of x x
Twap Code
We divide reserve1 by reserve0, but we cannot divide a small number by a large number, because the division will be 0 (no decimals supported in Solidity).
price0CumulativeLast += uint(UQ112x112.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price1CumulativeLast += uint(UQ112x112.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
So reserve1 is encoded as UQ112x112: it can represent a number as decimal. It splits a number into 2 parts. The first 112 bits represent decimals and the second 112 represent the whole number.

Arbitrage
Example: buy WETH from a place that is selling it for cheap (UniswapV2) and then sell in another AMM that is willing to buy for a higher price (SushiSwap):
- UniswapV2: 3000 DAI / WETH
- Sushiswap: 3100 DAI / WETH
We don’t need to have DAI in the first place, we can borrow it from somewhere else. Ie.
- UniswapV2: DAI / MKR
- use flash swap to borrow DAI
- execute the arbitrage (buy WETH from Uniswap, by putting in DAI)
- then sell WETH by putting in WETH and getting back DAI
- repay the flas swap
profit = DAI out (3100) - DAI in (3000) - DAI flash swap fee (9) - gas fee (20) = 71 DAI
Alternative arbitrage (we are doing the swap in the reverse order):
- flash swap to borrow ETH
- take the ETH and sell it in Sushi swap to get DAI
- repay flash swap paying with DAI
Optimal Amount In
Arbitrage profit:
-b + √(b² - 4ac)
F(dya) = dyb - dya --> dya* = ----------------------
2a
dya* = amount that maximizes F
f = swap fee (0 <= f <=1)
Xa = AMM A reserve out
Ya = AMM A reserve in
Xb = AMM B reserve in
Yb = AMM B reserve out

a = k²
b = 2·k·Ya·Xb
c = (Ya·Xb)² – (1 – f)²·Xa·Ya·Xb·Yb
k = (1 – f)·Xb + (1 – f)²·Xa