The Grammar · Contracts Talking to Contracts · Week 9 · Checkpoint 20
at Once Everything
Two hundred and six lines of production OpenZeppelin, and not one word in them you have not already read. The act closes on a file assembled entirely out of parts you own: a record under a hashed key, a modifier taking an argument, a custom error, an interface announced with an OR.
16 steps~30 min3 nodes for your map
01 · Two hundred and six lines, nothing new
Two hundred and six lines, and this is the last file of the act. Read the whole of it and you will not meet one word you have not already read somewhere behind you. That is not a coincidence, and it is the reason this particular file is here at the end.
So read it for recognition rather than for vocabulary. What it keeps is a table of which addresses may call which functions, and every part of that table arrived in a checkpoint you have finished. Several steps below will name the one each piece came from.
02 · Three names after is
Line 49 opens the contract and three names follow `is`. Context came past in checkpoint twelve, the file holding one function that answers who is calling. ERC165 you read whole in checkpoint thirteen, and IAccessControl is the same shape as the interface you read beside it that day.
`abstract` on line 49 means what it meant then: this file never deploys by itself. Lines 6 to 8 say where each parent lives, and the third of them is where this file's events and errors are declared, so names will appear below that were never written here.
03 · A record under a hashed key
Lines 50 to 53 declare a struct and line 55 puts it behind a mapping. That is checkpoint ten arriving again: a `bytes32` key, and a record rather than a number underneath it. This record has two fields, and the first of them is a mapping of its own.
Line 51 takes an address and answers with a `bool`, so a role is a set of yes and no. Line 52 holds another `bytes32`. Line 57 is `constant`, checkpoint three, and the value it fixes is thirty two bytes of zero. Decide what that zero is actually doing.
Line 55 keys records by `bytes32`, and line 57 fixes `DEFAULT_ADMIN_ROLE` at thirty two zeros. What does that particular value buy the file?
04 · The gate, written once
The board has moved down and now runs from line 49 to line 106. Lines 63 to 66 are a modifier, which you met in checkpoint five, and it takes a parameter, which you met in checkpoint ten. The registry's gate took a node. This one takes a role.
Line 64 hands that role to a name beginning with an underscore, and line 65 is the underscore on its own, where the body of whichever function carries this modifier drops in. Four lines, and you have read every one of these shapes before. Nothing on them is written.
05 · Two hops into the record
`hasRole` on line 76 is public, view and virtual, and its body is a single line. Read line 77 from left to right. `_roles[role]` picks one record out of the mapping. `.hasRole[account]` then indexes the mapping sitting inside that record. Two lookups in one expression, and no `if` anywhere.
The field and the function share a name and are not the same thing. One is the table declared inside the struct on line 51. The other is the public reader declared on line 76, and the compiler tells them apart by what is written in front of each.
06 · The line you already predicted
Line 70 is checkpoint thirteen turning up in production. A comparison against this contract's own interface id, an `||`, and `super.supportsInterface` on the right of it. You worked out what that shape answers before you had ever opened a file that used it in earnest.
Line 68 points at the inherited documentation rather than repeating it, the way checkpoint twelve did. Line 69 carries `override`, because ERC165 declared this function first and this line replaces it. Now answer the question you answered then, against the real line.
07 · One name, two declarations
`_checkRole` is declared twice, on line 84 and on line 92, and checkpoint nineteen gave that its name. One name, told apart by the parameter list. The first takes a role on its own. The second takes a role and an address, and it is the one holding the actual work.
Line 85 is the entire body of the first. It calls the second, filling in the missing argument with `_msgSender()`, which is the single function Context contributed on line 6. The shorter declaration exists so that nothing calling it has to say who is calling.
08 · The whole path, in five lines
Now put the pieces in order. A function is written with `onlyRole` and a role in its signature, and before its own body runs the call travels five lines. Every one of those five is lit on this board, and you have already read each of them on its own.
Nothing on the path writes anything. Three of the lines only carry a value along, one reads a single `bool` out of the record, and the last one stops the transaction. Tap each in turn and read what it adds to the question being asked.
09 · The other field, and its reader
Line 105 reads the other field of the record, `adminRole`, and hands it straight back. One reader per field, which is how the registry in checkpoint ten was arranged as well, except that this record has only two fields and so this file needs only two of these.
So every role names another role as its admin, and the naming lives inside the record. A role nobody has configured answers here with thirty two zeros, which is the constant on line 57, and that is the sentence you worked out six steps ago.
10 · The modifier's argument is a call
Lines 108 to 160 now. `grantRole` on line 120 and `revokeRole` on line 135 are one declaration written twice, and each ends with `onlyRole(getRoleAdmin(role))`. The argument handed to the modifier is not a constant. It is the reader from two steps ago, run against the role that just arrived.
Then the split from checkpoint twelve, exactly as it was there. Line 121 hands the work to `_grantRole` and line 136 to `_revokeRole`, and neither of these public functions touches the table itself. Three lines each, and one of the three is a brace.
11 · The one that is written differently
Read line 154 against line 135 and something is missing. `renounceRole` ends at `public virtual`. No modifier, nothing at all between the parameter list and the opening brace, and it is the only function in the file that writes and is arranged that way.
What it has instead is lines 155 to 157, a comparison written into its own body. The second parameter is an address, and line 159 hands that same address on to `_revokeRole`. Work out what those three lines settle before you read any further.
12 · A role whose admin is another role
The last board, lines 162 to 206. `_setRoleAdmin` is five lines and it writes the second field of one record. Line 168 reads the old value first, line 169 writes the new one into `_roles[role].adminRole`, and line 170 announces the two of them alongside the role they belong to.
This is the line that lets one role be the admin of another. Nothing anywhere pins an admin to the constant on line 57, so a contract built on this file may point a role's `adminRole` at whichever role it likes, and every role starts at zero until this runs.
13 · Internal, virtual, and it answers
`_grantRole` on line 180 is `internal virtual`, which is checkpoint eleven, and it returns a `bool` that none of the public writers above bothered to read. Line 181 asks whether the account already holds the role. Line 182 writes `true` into the record, line 183 announces it, line 184 hands back `true`.
Line 186 is the other half of the answer. If the account already held the role then nothing is written, nothing is announced, and `false` comes back instead. Granting twice costs one write rather than two, and this function says which of the two happened out loud.
14 · The same function, mirrored
`_revokeRole` on line 197 is line 180 with three things turned around. The `!` has gone from line 198, line 199 writes `false` where the other wrote `true`, and line 200 announces the opposite name. Everything else about the two, down to the `else` and the returned `bool`, is identical.
Look at what lines 183 and 200 pass as their third value: `_msgSender()` again, so each announcement carries who did it. Neither event is declared here. Both shapes live in IAccessControl, along with the `indexed` markers you met back in checkpoint three.
15 · The whole file, at once
Every line of it, on one board, with the comments dimmed away. A struct, one mapping, one constant, one modifier, six public functions and five internal ones. That is roughly seventy lines of code sitting inside a file of two hundred and six, and you have now read all of them.
Not one of those lines was new. So be exact about what reading them settles for you, and just as exact about the one question this file cannot answer no matter how carefully anybody reads it.
16 · The act, closed
This act opened with thirty eight lines you could not read without opening a second file. It closes with two hundred and six that needed nothing but what came in between: inheritance, interfaces, calls to strangers, raw calls, where an address lives, what a contract looks like as bytes, and choosing where to stop.
Nothing in this last file was new, and that was the whole exercise. A production contract is not a harder language. It is the same words, arranged by somebody who had read a great many of them. From here the files arrive cold, and nothing is dimmed for you.
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.