Two hundred and one lines of Uniswap V2, written in a version of Solidity that is not the one you have been reading. A pinned pragma, a library for addition, two reserves packed beside a clock, and a swap that sends the tokens out before it works out what came in.
16 steps~32 min3 nodes for your map
01 · Two hundred and one lines, cold
Two hundred and one lines, and this file is the core of Uniswap V2. Every pair of tokens that ever traded through it runs this exact text. The act opens the way the rest of it will go: a real file, read cold, with nothing arranged for you in advance.
Read it the way you would open an unfamiliar file at work. Find the declarations, work out what is stored, then walk the functions in the order they matter. The vocabulary is all behind you. What is ahead is a version of the language you have not read yet.
02 · A pin, and a file you have already read
Line 1 pins one compiler with an equals sign rather than a caret. Every contract you have read so far has been in the 0.8 series. This one is 0.5, and most of the code deployed on chain was written under rules that are not the ones you know.
Now read line 11. `UniswapV2ERC20` is the file you went through line by line in checkpoint three, all ninety four of them, unchanged. The token you took apart in week two turns out to be half of a contract that has held billions.
03 · A library for addition
Line 12 attaches a library to `uint`, so a sum anywhere in this file is written `x.add(y)`. Line 13 does the same for a fixed point type you will meet at line 79. A library that exists in order to do addition is worth stopping on.
Ask why a contract would need to import addition. You learned in checkpoint two that from 0.8 onward the compiler checks arithmetic itself. Line 1 says this file is older than that. Work out what `add` is doing that a plain `+` is not.
Line 12 lets a plain addition be written `x.add(y)`. Under the compiler line 1 pins, what does that actually buy the file?
04 · Baked in, and set at birth
Two constants and three addresses. Line 15 is a fixed number. Line 16 is the first four bytes of a hashed function signature, the selector you met when you read a raw call. Neither has a storage slot and neither can ever change.
Lines 18 to 20 are the opposite. No `constant`, so all three sit in storage, and two of them hold nothing at all until something writes them. These are the lines that fix a pair to one particular pair of tokens.
05 · Two reserves and a clock
Lines 22 to 24 are the ones to slow down for. Two reserves at one hundred and twelve bits each, and a timestamp at thirty two. Add the three widths together and you get two hundred and fifty six, which is one storage slot exactly, and the comment says so out loud.
Lines 26 to 28 are plain `uint` and take slots of their own. Two of them accumulate a price multiplied by seconds. `kLast` holds the product of the reserves. Hold all six names, because everything below moves one of them.
06 · A flag that is down while the body runs
Line 30 declares a `uint` holding one, and lines 31 to 36 are a modifier written by hand. You read modifiers in act two. This one is four lines long: check a value, write it, run the body, write it back.
The underscore on line 34 is where the decorated function's own body is spliced in. So the value is zero for exactly as long as that body runs, and a call arriving at line 32 inside that window fails the check. Tap the four lines in order.
07 · Two small functions
Nine lines between them, and every operation in the file leans on both. `getReserves` hands back three values at once by naming them in its signature and then assigning to those names, which is why its body contains no `return` statement.
`_safeTransfer` builds a call by hand out of the selector on line 16 and sends it with `token.call`. Line 46 is the half worth reading twice: it accepts either no returned data at all, or returned data that decodes to true.
08 · Announcements, and the lines that name the tokens
Four events: one for each operation, plus `Sync`, which goes out every single time the reserves are written. Then a constructor that stores exactly one thing, the address that deployed this contract, and calls it the factory.
Line 66 is the other half of that arrangement, because the two tokens arrive afterwards rather than in the constructor. Line 67 is one comparison and a short string. Custom errors did not exist in 0.5, so every message in this file is text.
09 · The only place the reserves are written
`_update` is private, and every path in the file ends here. Line 74 refuses balances that would not fit in a `uint112`. Lines 82 to 84 write the three packed values and line 85 announces them. Nothing else in the file touches the reserves.
Lines 79 and 80 are the other half of the job, and they run only inside the condition on line 77. Work out what that condition is really testing before you read any further.
10 · A fee that is paid in shares
Line 90 calls out to the factory and asks where a fee should be sent. If the answer is the zero address then `feeOn` is false and almost nothing in this function runs. Everything under line 93 depends on somebody having turned that on.
When it is on, lines 95 and 96 take square roots of two products, the stored one from `kLast` and the current one. If the first has grown, lines 98 to 101 mint new shares to that address. Growth in the product is what gets paid.
11 · mint, and the difference it reads
The first of the three real operations. Lines 112 and 113 ask each token what this contract holds. Lines 114 and 115 subtract the reserves from those answers. What arrived is the difference between the balance and the count, and nobody passes in an amount.
Line 119 splits two cases. An empty pool gets the square root of the product, less a small fixed quantity minted to the zero address. Otherwise line 123 takes the smaller of two ratios. Then line 128 makes the count agree with the balances again.
12 · burn, the mirror
Line 140 reads this contract's own balance of its own shares. They were sent here before the call, exactly the way the tokens were for `mint`, and whatever is sitting here is the quantity being returned.
Lines 144 and 145 work the two payouts out from the balances rather than from the reserves, and lines 148 and 149 send them. Then lines 150 and 151 read the balances a second time, because the two transfers have just changed them, and line 153 writes that result.
13 · swap, and the order it does things in
The third operation, and the one to read carefully. Start at the signature on line 159: amounts out, a recipient, and a blob of bytes. No price, no input amount, nothing at all about what the caller intends to pay.
Lines 170 and 171 send the requested tokens out. Line 172 calls the recipient back if any bytes were passed. Lines 173 and 174 then read what this contract actually holds, lines 176 and 177 subtract to find what came in, and line 182 checks a product.
14 · Two ways to make them agree
Balances and reserves can drift apart, because anyone may send a token to any address and nothing in this file is consulted when they do. The last two functions are the two ways of ending that disagreement, and both carry the modifier from line 31.
`skim` sends the excess on to an address of your choosing and leaves the reserves untouched. `sync` does the reverse, calling `_update` with the balances so the count rises to meet them. One moves the tokens, the other moves the number.
15 · The whole file
Two hundred and one lines with the comments dimmed away. A pinned compiler, a library for addition, six state variables, a four line modifier, one private writer, three operations and two reconcilers. That is the entire core of Uniswap V2.
Not one word of it was new to you, only older. So be exact about what these lines settle, and equally exact about what they cannot settle, no matter how carefully anybody reads them.
16 · An older dialect, read whole
Nothing here was harder than the act behind you. It was older. A pinned pragma, a library where an operator now suffices, short strings where a custom error would go today, widths chosen so three values share one slot. Those are the marks of a year, not of difficulty.
And the beat worth carrying is line 11. The file you read in week two, byte for byte, holding up half of a contract that has held billions. You had already read half of this one before you ever opened it.
BANK_DBowner: the bank
you2,400
what the app is actually showing you
BANK_DBowner: the bank ✍
you2,400their pen
you hold a claim. they hold the pen.
your digital life
BANK · you2,400the bank ✍
INSTAGRAM · you2.1M followersMeta ✍
STEAM · you134 gamesValve ✍
AIRLINE · you58,200 milesthe airline ✍
four tables. zero pens that are yours.
BANK_DBowner: the bank ✍
you2,400
DENIED ✗
try both pens
PLATFORM_DBowner: the platform ✍
her · 8 years2,000,000 followers
one automated decision away
your row stands on all three
FTX_DBowner: FTX ✍
you5 BTC
the row stayed. the backing did not.
CARD_DBowner: your bank ✍
TV you never bought−1,100
fraud reversal+1,100 ✓
someone holds the pen, so someone can fix it
?_DBowner: nobody
youstill yours?
can a table exist that nobody owns?
?_DBowner: ̶n̶o̶b̶o̶d̶y̶
you100
no owner, no pen, no trust?
keeper 1
you100
keeper 2
you100
keeper 3
you100
keeper 4
you100
keeper 5
you100
no THE copy, only copies.
keeper 2
you100
keeper 3
you100
keeper 4
you100
keeper 5
you100
your copy
you100
five copies. one of them is yours.
one attacker, ten thousand faces.
writing costs watts. faking voters buys nothing.
proof of work, burn energy to vote.
rewrite one line, break every lock after it.
the price buys trustlessness. the office already has trust.
ownerless ledger
you?
nobody owns the table. so who owns your row?
Three new nodes on your map
older dialects · packed slots · out before in · +10 Lynx