The Wall I Keep Hitting
I'm in week N of trying to integrate yet another Solana DEX into my MEV bot, and I just stared at a 404 page for two full minutes. The documentation link in the project's GitHub README points to a domain that no longer resolves. The Discord pinned message has a Notion link. The Notion page is "last updated 11 months ago." The information I need — what's at a particular byte offset of a particular account, what a specific error code actually means, why an instruction requires the exact account list it does — isn't anywhere a search engine can reach.
So I do what I've been doing more and more: I open the source code.
And I keep doing it until I understand the thing well enough to write a JSON description of it for my own bot.
This is the rhythm of building anything serious in crypto right now. The protocols move fast. The docs don't. And every developer I respect has quietly converged on the same conclusion — the code is the documentation, and that's not a complaint. It's the operating principle.
RTFM Is Dead. Long Live RTFS.
When I was a kid learning to program, the phrase was RTFM — "read the freaking manual." It was an accusation. If you asked a question whose answer was in the docs, you got told to RTFM and that was the end of the conversation.
Then open source ate the world. And a quieter shift happened in hacker culture: RTFM became RTFS — "read the freaking source." Same energy, different target. RTFS is not aimed at the question-asker. It's aimed at the documentation itself, which was either never written or has long since rotted. As the Jargon File puts it, RTFS is what you say when the manual was supposed to exist but doesn't.
The numbers back this up. In the 2017 GitHub Open Source Survey, the vast majority of respondents reported encountering "incomplete or outdated documentation" as a problem when using open source — making it one of the most widely reported problems in the ecosystem. That's not a minority gripe. That's the default condition.
A recent 2025 arXiv preprint on DeFi dependency documentation found that a substantial portion of the actual on-chain dependencies of major protocols like Uniswap and Lido are absent from their official documentation — the on-chain reality and the documented reality have drifted apart, and the on-chain reality is the one that mints money.
I used to treat this gap as a failure mode — something to file a bug about, something that would surely get fixed. I no longer do. I treat it as the weather. You don't argue with the weather. You bring a jacket.
Code Never Lies; Comments Sometimes Do
Three quotes have been running through my head this month, scribbled in the margins of my notes:
"Code never lies; comments sometimes do." — Ron Jeffries
"Truth can only be found in one place: the code." — Robert C. Martin, Clean Code
"No matter what the documentation says, the source code is the ultimate truth, the best and most definitive and up-to-date documentation you're likely to find." — Jeff Atwood, Coding Horror
These aren't poetic flourishes. They're a practical claim about where the ground truth lives. Documentation is an asynchronous summary written by a human about what code does. Comments are even worse — they're written at one point in time, never get tested, and silently drift away from the lines they describe. Specifications are aspirational. READMEs are marketing copy with code snippets.
The code, by contrast, is what actually executes. It is the only artifact whose behavior gets continuously validated — by users, on real inputs, on every block. If the comment says one thing and the code does another, the comment is lying. The code is just running.
This is why "the source code is the ultimate truth" isn't romantic. It's tautological. The code is what's running. Anything else is a story about what's running.
How Seniors Actually Read Code
For the longest time, I assumed senior engineers were just faster readers — like they had some kind of code-comprehension superpower where lines streamed past them and crystallized into understanding.
A Medium essay I came across this month — "I Finally Learned How Senior Engineers Read Code" — kicked the legs out from under that assumption. The author describes a production bug that three mid-level engineers couldn't solve in four hours. A senior walked in and resolved it in twelve minutes.
The difference wasn't reading speed. The senior read less code, not more.
What they did instead:
- Traced data flow. Where does the input come from? Where does the output go? What's the API contract? What's the schema? They map the rivers before walking into the forest.
- Searched for invariants. Validation code. Words like "must," "always," "never" in comments and assertions. These are the load-bearing assumptions of the system, and they tell you what the author was afraid of.
- Used git history as context, not blame. When a line looks weird, the question isn't "who wrote this garbage" — it's "what was the commit message, what bug did this fix, what constraint forced this particular shape?"
- Actively ignored irrelevant code. This is the one I keep getting wrong. I'll read every helper function on the path because I "might as well understand it." A senior skips it. They mark it black-box and move on.
I've been catching myself, repeatedly, treating a codebase like a Stephen King novel — top to bottom, every page, every line, completion as the goal. The lesson, and I am very much still learning it, is that reading code is more like reading a road map than reading a novel. You don't read a road map cover to cover. You navigate it. You zoom in only where you actually need to go.
The rest is scenery. Useful to know it's there. Not useful to memorize it.
Tests Are the Real Documentation
Uncle Bob frames it precisely:
"Unit tests are the lowest-level design documentation of the system — unambiguous, accurate, written in a language the target understands, and formal enough to be executable." — Robert C. Martin
A Capgemini engineering essay gives a concrete example of what this looks like in practice. The author tells a story about needing to integrate Apache Camel's Mail Component into a project. The official documentation didn't tell them how to mock an email server, or how the configuration actually worked, or how the JavaMail integration was wired up. So they went to the project's unit tests. Everything was there — setup, dependencies, teardown, expected behavior, the exact API shape. The tests were a faster, more accurate, more complete tutorial than the documentation could have been.
Why are tests better docs than docs?
- They're executable. If they pass, they're true. If they fail, the project's CI knows about it within minutes.
- They show intent. Not "this is what the function returns" but "this is how the original author imagined the function would be used."
- They include the surrounding context. Setup, fixtures, mocks — everything you need to actually run the thing, not just understand it abstractly.
- They don't go stale. When the code changes and the test breaks, somebody fixes it that day. When the code changes and the docs go stale, nobody notices for a year.
This was a quiet epiphany. The next time I'm trying to figure out how to call into a library, the first place I now look isn't the README. It's /tests. I get to working code faster, with fewer false starts, and I come out the other side with a mental model that's actually grounded in how the maintainers use their own library — not how they imagined other people might.
Git Blame Is a Time Machine, Not a Witch Hunt
git blame has terrible branding. The name implies it's a tool for finding who to point fingers at. In practice — and this is the thing seniors keep telling me — it's a time machine.
When I see a line of code that looks weird, the question I'm trying to answer is rarely "who." It's "why." Why does this constant have this specific value? Why is this null check here? Why does this branch exist? Why is there a guard against an input that, on its face, can't happen?
git blame answers "who," but the answer is a key that unlocks "why." Once I know the commit that introduced a line, I can read the commit message, look at the diff, find the issue or PR it references, and recover the original context. The line stops being a mystery and becomes a chapter of a story. Almost every "redundant" check I've ever investigated turned out to be a scar from a bug — the commit that added it has a message like "fix overflow on extreme swap size," and now you understand. The line isn't weird. It's a scar.
DataCamp has a solid practical tutorial on this, and one tip I now use constantly is the --ignore-rev flag, which lets you exclude pure-style-change commits (formatters, mass renames, lint sweeps). Without it, every line points back to "the day we switched formatters" and the history is useless. With it, you see the meaningful history underneath.
Code tells you what. History tells you why.
I jotted that in my notebook three weeks ago. I keep coming back to it.
A classic 2016 walkthrough of reading the Ethereum C++ client demonstrates the same approach at architecture scale. The author wanted to know where Ethereum stored blockchain data on disk — a question the documentation didn't answer. He cloned the repo, grepped for references to LevelDB across all files, sorted the files by reference count, and discovered that one file dominated. Inside that file, he found that Ethereum aliased LevelDB internally as ldb, that the database backend could in principle be swapped for RocksDB, and that the block insertion process was substantially more complex than the docs suggested — including validation of "fathers, uncles, cousins, grandma" relationships among blocks that he'd never seen mentioned in any spec. None of that was in the documentation. All of it was in the source, retrievable by anyone willing to grep, sort, and read.
In Crypto, Code Isn't Just Documentation — It's Law
Outside of crypto, the gap between docs and code is annoying. Inside crypto, it's existential.
Blockchain's foundational pitch is "don't trust, verify." That pitch is only meaningful when the thing you're verifying — the actual code that processes transactions — is public and readable. As an OpenSourceForU essay notes, open source code allows developers, researchers, and independent auditors to examine blockchain protocols for bugs, vulnerabilities, or malicious logic. Without that access, the "verify" in "don't trust, verify" is a marketing slogan.
The cautionary tales here aren't hypothetical. OneCoin and BitConnect are routinely cited as Ponzi schemes that succeeded in part because their codebases were closed. With no public chain to audit and no source to read, the "blockchain" was a black box that nobody could falsify until it collapsed. The OpenSourceForU piece notes that OneCoin never had a publicly verifiable blockchain or open codebase, which allowed it to mask a Ponzi scheme from investors. The fraud and the closed code were not separable problems — closing the code is what made the fraud possible.
The flip side is Bitcoin, which has had roughly seventeen years of continuous public code review by a global pool of developers. Every consensus-critical line has been stared at, attacked, and stress-tested for nearly two decades. That's the actual security model. Not "trusted developers," not "audited by a Big Four firm." Continuous open code review by an adversarial audience.
And in DeFi, the gap between docs and code becomes attacker territory. Public incident reports describe attackers analyzing unverified contracts deployed by various DeFi protocols using available decompilation tooling, identifying vulnerabilities like integer overflows in bonding-curve mechanisms, and draining funds across multiple incidents — losses adding up to tens of millions of dollars in aggregate (NewsBTC, 2024). In one case, attackers minted vast quantities of tokens at near-zero cost by exploiting an overflow they'd found by reading the disassembled bytecode.
The lesson is uncomfortable: whoever can read the source code can also find vulnerabilities first. In DeFi, code reading isn't a productivity skill. It's a security discipline. The team that can navigate an unfamiliar contract and reason about its behavior faster is the team that finds the bug before it's exploited — and the team that ships fewer of their own.
This is also, I think, why the closed-source startup in crypto reads as a smell to so many of us. In Silicon Valley generally, "proprietary" can be a moat. In crypto, "proprietary" is a flashing red light that says "you have no way to verify what I'm doing with your money." Bitcoin's longevity isn't despite being open. It's because of it.
What the Solana Documentation Doesn't Cover
Let me get concrete about what "the docs don't cover this" looks like on Solana, because abstractly it sounds like a complaint and concretely it's a strategy.
When I'm integrating a new DEX, the documentation will usually cover the happy path. It tells me the program ID. It tells me, roughly, what a swap looks like. If the project uses a framework that auto-generates an interface description, I can fetch that and at least learn what types the instructions expect. Past that, things get vague fast.
What the docs typically do not tell me:
- The exact ordering of accounts in the instruction, because the framework hides this behind named parameters that map to positions in ways that depend on framework version.
- Which accounts are writable and which are read-only — which matters because guessing wrong means the transaction simulation rejects before it even reaches the program.
- How the program handles edge cases — zero-input swaps, swaps that exhaust a tick, swaps where the user is also the LP.
- The full list of custom error codes the program can emit. Documentation tends to list the friendly ones. The ones that actually fire in production tend to be undocumented numeric values you have to chase through the source.
- How the program interacts with other programs via cross-program invocation, and what assumptions it makes about the state of accounts it doesn't directly own.
None of these are unknowable. All of them are in the source code, and most of them are in the tests, if the project ships tests. The work isn't conceptually hard. It's a matter of accepting that the documentation is a starting point, not an endpoint, and budgeting time accordingly.
One pattern from the wider Solana ecosystem is worth knowing: in a 2024 Medium guide on reverse-engineering closed-source Solana programs, the author describes how the msg! log macros sprinkled through Solana programs end up being the most useful breadcrumbs for code navigation. A program emits msg!("Withdrawing tokens...") somewhere in its withdrawal path. If you grep the source for that exact string, you immediately find the function you care about. If the source isn't public, you can still see the log output on-chain and use it to triangulate behavior. Logs are documentation by accident.
My Actual Toolkit for Code-First Investigation
I want to make this concrete because vague advice ("just read the source!") helps no one. Here's what's actually in rotation in my daily work right now, drawn largely from a guide on exploring large open source codebases that I keep open in a tab.
Find the most-modified files
When I drop into an unfamiliar repo, I run a variant of:
git log --pretty=format: --name-only | sort | uniq -c | sort -rg | head -10
The output is a ranked list of the files that have been edited most often. These are the files that do the work. They're where the bugs hide, where the design decisions accumulate, and where the project's actual personality lives. Ignoring the rarely-touched files isn't laziness; it's triage. Most files in most repos are background scenery.
Read the first commits
Then I jump to the other extreme — the initial commits. The first commits of a project tend to encode the original vision in a few thousand lines, before the codebase grew defensive scar tissue. If the project still looks recognizable to its founders, the first commits are the cleanest possible introduction to its architecture. It's the difference between meeting someone in middle age and seeing their senior portrait from high school — same person, but the bones are easier to see in the early picture.
Trace via error-handling keywords
When I'm trying to understand the boundaries of a system, I grep for throw, catch, try, panic!, log.error. Error handling is where modules touch each other. Errors propagate across abstraction layers; following them gives you the actual control-flow graph, not the one in the architecture diagram. The architecture diagram is what the team wishes was true. The error paths are what's true on a bad day.
Use the right search tool for the job
- ripgrep (
rg) for fast literal and regex search across a checkout. It's fast enough that grepping a large repo feels like searching in-memory. - ast-grep when I need structural search — "find every place where this method is called on this type" — without getting drowned in false positives from comments and strings. ast-grep parses the actual syntax tree rather than the raw bytes, so it ignores formatting and finds things spelled slightly differently.
- Sourcegraph when I'm searching across multiple repositories at once and want semantic cross-references.
- ctags when I want jump-to-definition in editors that don't speak the language natively.
The thing these tools share: they let me read code by not reading code I don't need to.
Read the tests before the implementation
This one keeps surprising me with how well it works. When I'm trying to understand what a function does, the unit test is usually faster than the function itself. The test gives me example inputs, expected outputs, the harness around the function, and the corner cases the author worried about. Then, when I read the implementation, I already have a mental model to map it onto. Implementation-first reading is like watching a baseball game without knowing the rules. Test-first reading is like watching the same game after someone has explained what an inning is.
Use git blame --ignore-rev
For any line that's been around for a while, blame the file, ignore the format-only commits, walk the chain of commits backward, read the messages, read the linked PRs. Recover the why.
Doc Norton's "One Small Step" Approach
One more methodology has stuck with me from this month's reading. Doc Norton, in a Substack essay on dealing with large undocumented codebases, argues for what he calls "one small step at a time."
The core moves are:
- Problem-centered exploration. Start from the feature or behavior you need to modify, not from a survey of the whole system. Use module naming conventions to navigate. Follow the call graph from your entry point outward only as far as you actually need.
- Write characterization tests. Before changing anything, write tests that document the code's current behavior. As Norton puts it: "I'll spend time writing character tests around the behavior I can observe or infer from the code itself." This is the most underrated tool in the kit. It converts a black-box module into a tested module without requiring you to understand it first.
- Minimal-change strategy. Write a failing test for what you want to change, make the simplest implementation that passes, and watch for unexpected breakage in the existing characterization tests.
- The Mikado Method. When refactoring a tightly coupled system, identify the goal, attempt the smallest possible change, note what breaks, undo the change, fix the breakage first, then try again. Build a tree of dependencies rather than tearing through them.
What I like about this is that it doesn't pretend the undocumented codebase is going to become documented. It accepts the codebase as-is, builds a small ring of trusted behavior around the part you need, and grows that ring outward only as the work demands. You're not trying to understand the whole thing. You're trying to make this one change safely.
Three Honest Caveats
I want to be careful not to oversell this. Reading code instead of docs has real costs.
It's slow at first. A newcomer to a codebase will not, in a single afternoon, replicate what well-written docs would teach in twenty minutes. Code-reading is a skill that compounds; the first few hundred hours are uphill, and there is no shortcut. Selling "just read the source" to a junior who has never navigated a large codebase is unkind.
It can replicate bad assumptions. If the code is wrong, code-reading teaches you the wrong thing — but with high confidence, because you "saw it yourself." Tests, comments, official specs (when they exist), and a second set of eyes act as cross-checks. None of them are infallible, but ground-truthing against several of them beats trusting any one in isolation.
Bytecode-only contracts are partial truth. Ethereum smart contracts deployed without verified source are a particularly hard case: even if you can run the bytecode through a decompiler, what you get back is not what the developer wrote. Verifying that deployed bytecode matches a public source repo is itself a discipline. The OpenSourceForU essay flags this as a remaining gap in the "don't trust, verify" promise.
So "code is documentation" doesn't mean "documentation is worthless." It means: when the two disagree, the code is right. And when the two simply don't both exist, the code is the only thing you have.
What This Means for Building in Crypto
I started this episode trying to articulate why I'm spending so much of my bot-development time reading other people's code. The honest surface answer is that I have no choice — the docs aren't there, or aren't current, or weren't ever written. But the deeper answer is that this is the right way to work in this space, not a compromise.
Crypto runs on the premise that anyone can verify anything. That premise collapses the moment your team can't read source. Every team building seriously on Solana or Ethereum or any open chain is implicitly accepting that source-reading is the floor of the craft, not the ceiling.
In practice, this changes a few habits:
- I budget time for reading. When I plan a new DEX integration, I assume documentation will be incomplete and I block calendar hours to read the program source, the auto-generated interface, the integration tests, and the on-chain account layouts. I don't pretend the docs will be enough and then act surprised when they aren't.
- I treat tests as deliverables. The tests I write for my own code aren't just a CI gate — they're the documentation the next person who reads this code (probably me, three months from now) is going to lean on.
- I write commit messages with the next reader in mind. If "why" is going to live in git history, then git history has to be readable. A commit message that just says "fix bug" is leaving the future on read.
- I look for projects that publish their code as a default, not an afterthought. The closed-source startup in crypto, increasingly, is a smell.
The Agile Manifesto, of all places, called this twenty years ago: "working software over comprehensive documentation." Not "instead of documentation." Over. The working software is what you commit to. The documentation is the helpful but optional layer on top.
The code is what runs. The code is what gets verified. The code is what's true.
And when the docs aren't there — and they often aren't — that isn't a crisis. It's just the rest of the job.
Key Takeaways
- Documentation gaps are the default, not the exception. The 2017 GitHub Open Source Survey found that incomplete or outdated documentation was one of the most widely reported problems in open source.
- RTFM became RTFS for a reason. When the manual doesn't exist, the source code is the only ground truth — and in open source, that's a feature, not a bug, per the Jargon File.
- Seniors read less code, not more. Trace data flow, search for invariants, and skip what you don't need. The 12-minute-vs-4-hour story from bhavyansh001's Medium piece is about strategy, not raw reading speed.
- Tests are living documentation. They're executable, current, and reveal intent. As Capgemini's engineering blog shows with its Apache Camel example: read
/testsbefore/README. - Git history is a time machine for "why." Use
git blame --ignore-revto skip formatter commits and surface the commits that actually changed behavior. - In crypto, code-reading is a security discipline. Whoever reads the source first finds the bug first — and the DeFi incidents that came from unverified contracts in recent years are a direct reminder that attackers are already reading what defenders haven't bothered to.
Disclaimer
This article is for informational and educational purposes only and does not constitute financial, investment, legal, or professional advice. Content is produced independently and supported by advertising revenue. While we strive for accuracy, this article may contain unintentional errors or outdated information. Readers should independently verify all facts and data before making decisions. Company names and trademarks are referenced for analysis purposes under fair use principles. Always consult qualified professionals before making financial or legal decisions.