Four names the last checkpoint never opened, and the factory that makes pairs. About a hundred and fourteen lines across five files, four of them under twenty five lines: a library that is seventeen lines of arithmetic, a square root written as a loop, a fraction stored with no decimal point, and an address that follows from its two tokens.
16 steps~30 min3 nodes for your map
01 · Five names, left standing
Checkpoint twenty two read all two hundred and one lines of the pair and left five names closed. Math, UQ112x112, SafeMath and IUniswapV2Callee, plus the factory that creates pairs. Together they come to about a hundred and fourteen lines across five files.
Here is the method, and it will not change. A name you do not know, then the import line that says which file, then that file, then only the function you came for. Lines 3 to 9 are the map for four of the five.
02 · The name that is not on the list
Line 13 attaches UQ112x112 and line 6 imports it. That pairing is the method working cleanly: the name on one line, the file on another. Now run the same check on line 12, which attaches SafeMath to every plain `uint` in the file.
Read lines 3 to 9 again and look for it. Seven imports, and not one of them names SafeMath. The library is in use on line 12 and the file it lives in is not on the list. Work out where it reaches this contract from.
Line 12 of UniswapV2Pair.sol uses SafeMath, but none of lines 3 to 9 imports it. Where does the library reach this file from?
03 · Seventeen lines of arithmetic
Seventeen lines, and this is the entire library that line 12 of the pair attached. A pinned pragma, one comment, and three functions. `add`, `sub` and `mul`, each `internal pure`, each returning a named `z`, each with a body of exactly one `require`.
That is the whole of the thing you were told about. Not a framework and not a pattern. Seventeen lines in a file next to the one that uses it, and every `.add` in the pair resolves to line 6 of this file.
04 · Assign, then compare
Line 7 is worth reading slowly, because the assignment sits inside the comparison. `(z = x + y)` works out the sum and writes it to the named return in one move, and the value of that whole parenthesised expression is the sum itself, which `>= x` then tests.
So the question line 7 asks is: is the total at least as large as one of its parts? Adding a number that cannot be negative can only move you up. If the result came out below `x`, the sum passed the top of a `uint` and came round, and the call stops here.
05 · The other two checks
Line 11 is the mirror of line 7. A difference should be no larger than the number you began with, so if it is larger the subtraction went below zero and came round from the top. One operator changed, one comparison flipped, everything else the same.
Line 15 divides back. Multiply, then divide by the same operand, and if you do not land on `x` again the product did not fit. The `y == 0` in front of it is there because that division would not be legal otherwise. Three functions, three checks, seventeen lines.
06 · min, and the one below it
Math.sol is twenty three lines and holds two functions. Line 7 is all of the first: a conditional expression handing back whichever of the two numbers is smaller. The pair calls it once, at UniswapV2Pair.sol line 123, to pick the lesser of two ratios.
The second function runs from line 11 to line 22 and answers something checkpoint twenty two could only point at. Line 120 of the pair works out the first liquidity ever minted as `Math.sqrt(amount0.mul(amount1))`. This is where that square root is actually computed.
07 · A square root, written as a loop
The comment on line 10 names the method and links the article that describes it. Solidity has no square root operator, so line 11 has to produce one by repeating an operation, and lines 13 to 18 are the whole of that repetition.
Two values, closing on each other. Line 13 sets one from `y` itself, line 14 sets the other to roughly half of `y`, and line 15 keeps the loop going while the second is still below the first. Tap the four lines in the order they run.
08 · The branches, and the value that never runs
Line 12 is the gate. Only a `y` above three reaches the loop at all. Lines 19 and 20 catch one, two and three and answer one, which is the floor of the root of each of them. A `y` of zero matches neither branch and runs nothing.
And nothing in this file ever returns. `z` is named in the signature on line 11, so a `z` nobody wrote is zero, which is the right answer for zero. That is the same naming you read in `getReserves` at UniswapV2Pair.sol line 38.
09 · A fraction, with no decimal point
Twenty lines, and the first six are comment. A binary fixed point number, range zero up to two to the one hundred and twelve minus one, resolution one over two to the one hundred and twelve. Line 9 is the only constant here: `Q112`, held as a `uint224`.
This is how a contract stores a fraction without a decimal point. You keep the numerator multiplied by a fixed power of two, and you remember the scale. Nothing has been rounded away at the point of storage, because nothing has been divided yet.
10 · Run two real numbers through it
Line 13 is the encoding. Take a `uint112`, widen it to a `uint224`, multiply by `Q112`. The comment says it never overflows and the widths say why: a hundred and twelve bits of value, shifted up by a hundred and twelve places, is two hundred and twenty four.
Now put a real pair of numbers through it. UniswapV2Pair.sol line 79 reads `UQ112x112.encode(_reserve1).uqdiv(_reserve0)`, so one reserve becomes the numerator and the other the divisor. Take three and two, and work out what comes back.
11 · Dividing without losing the low bits
Line 18 is one division and one cast. The numerator arrives already scaled, and the divisor is a plain `uint112` widened to match, so the scale passes through the division untouched and what comes out is still a UQ112x112. The comment on line 16 says precisely that.
Which is where the low bits survive. Divide three by two as plain integers and the half is gone, as it was in checkpoint seven. Divide three times the scale by two and the half is still there, sitting in the bits underneath.
12 · Five lines, and one function
Five lines, one of them blank. An interface with a single function in it, and that function is the whole of what UniswapV2Pair.sol line 172 calls. The address it calls is `to`, which is the recipient the caller named in the arguments to `swap` on line 159.
Now read line 1 against the other four files. They pin `=0.5.16`. This one says `>=0.5.0`, because it is written to be imported into somebody else's contract. An interface is a shape for others to match, so it stays loose about the compiler.
13 · What the factory stores
Forty nine lines, and the first thing to find is line 7. Checkpoint twenty two watched `_mintFee` call out to the factory at UniswapV2Pair.sol line 90 and ask it for `feeTo`. Here is the answer: a stored address, written only by lines 40 to 43.
Lines 10 and 11 are the two indexes. A mapping of a mapping, marked `public`, so reading it takes two addresses and gives back one. Then an array of every pair ever made, with its length exposed on lines 19 and 20.
14 · Where pairs come from
Fifteen lines, and this is where every pair that has ever traded came from. Line 24 refuses two of the same. Line 25 sorts. Lines 28 and 29 build the creation code and a salt. Line 31 drops into assembly for one opcode, which is checkpoint eighteen.
Then three writes and an event. Line 33 hands the sorted tokens to the new contract, which is the `initialize` that UniswapV2Pair.sol line 66 accepts from nobody but its factory. Walk the whole thing with the arguments given in the awkward order.
15 · What five files bought, and what they did not
Back to the import block with four of the names opened and the factory read. About a hundred and fourteen lines, four of the five files under twenty five. What made them look closed was that they were named rather than shown.
So be exact about what those files bought you, and equally exact about what is still behind a name on this list. Three of these seven imports have never been opened, and one line of the pair calls code that is in no file here at all.
16 · The files around it
Five files, about a hundred and fourteen lines, and four of them shorter than the contract you read in week one. What made them look closed was that they were named rather than shown, and every one of those names sat on a line you had already read.
The method is four moves and it repeats. A name. The import line. That file. Then the one function you came for, with the rest of it left shut. Checkpoint nineteen did that within a file, and this is the same choosing, made across five of them.
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
following an import · fixed point · where pairs come from · +10 Lynx