All articles
The BuildMay 21, 20269 min read

The Case for Rebuilding Protocols Line by Line (Instead of Forking)

Forking a protocol ships code you don't understand. Rebuilding from scratch with a test suite as forcing function teaches what every defensive choice exists for.

By Carlos (Bloqarl)

TL;DR

  • Forking a protocol like Uniswap V2 or Compound V2 ships working code, but the code carries decisions you didn't make and don't understand. Some of those decisions are load-bearing security defenses.
  • Rebuilding the same protocol from scratch with a passing test suite as forcing function makes the security defenses visible. The test suite fails until you put each one back.
  • Real-world fork-without-understand failures are not rare. Hundred Finance lost ~$7M to a cToken donation attack. Venus on BSC inherited a 5x interest inflation bug from porting Compound V2's Ethereum constants. Multiple Solidly forks inherited Velodrome's reward-lock paths because nobody traced them.
  • The pedagogical value of reconstruction is the discovery that every line is load-bearing. After rebuilding once, you read existing forks differently.
  • Forking is still right for some cases (you have distribution leverage, you don't intend to modify the protocol). But for protocols you'll modify, audit, or operate at scale, building once is cheaper than discovering bugs later.

Why this matters

Most Web3 protocols ship as forks. SushiSwap forked Uniswap V2. Pancakeswap forked SushiSwap. Compound V2 spawned Aave V1 (originally a Compound fork before becoming its own thing), Cream, Hundred Finance, Midas, Venus, Canto v2, Flux Finance. Solidly spawned Velodrome, Aerodrome, Equilibre. Each generation of fork copies code that worked in production, with edits.

The thing copying production code can't transmit is the reason each defensive choice exists. The 1000-wei minimum liquidity lock in Uniswap V2 looks like premature optimization. The dual-arithmetic regime in Uniswap V2's _update() looks like inconsistency. The 80% kink in Compound V2's rate model looks like a magic number. Without context, all of these look like things you can change.

Once you rebuild from scratch, you can't make these mistakes anymore. You've already discovered them. You know why every line exists.

This is the difference between having a working Uniswap V2 fork and understanding a working Uniswap V2 fork. The first ships code; the second ships infrastructure you can audit, modify, and defend.

What you actually learn by rebuilding

Reconstruction makes the implicit explicit.

When you fork Uniswap V2 and read mint(), you see a function that mints LP tokens. When you rebuild it from scratch with a test suite, you discover that:

  • The function must call _mintFee() before reading totalSupply (covered in the _mintFee ordering article).
  • The first deposit must lock 1000 wei to address(0) (covered in the minimum liquidity lock article).
  • The geometric-mean-minus-1000 math forbids tiny first deposits.
  • The proportional math for subsequent deposits picks the minimum of two ratios, not their average.
  • The _update() call at the end has different overflow behavior than the rest of the function (covered in the dual-arithmetic article).

A fork that copies the function inherits all of these correctly. A fork that rewrites the function for "readability" frequently breaks one or more of them. The test suite catches the break the first time you run it; without a test suite, the break ships to production.

Same pattern for Compound V2. When you rebuild accountLiquidity() from scratch, you discover that almost every audit finding in Compound V2 forks lives in or around this function. Why: it touches oracle prices, exchange rates, market enumeration, mantissa math, all in one place. We covered this in the accountLiquidity article.

You don't see this from forking. You see it from rebuilding once and then auditing forks afterward.

Three real cases of fork-without-understand

Hundred Finance ($7M, April 2023)

Hundred Finance was a Compound V2 fork on multiple chains. The bug that drained it was a cToken exchange-rate manipulation via direct token donation. We covered this class in detail in the Compound V2 fork donation attack article.

The attack pattern:

  1. Mint 1 cToken share by depositing 1 wei of the underlying token.
  2. Transfer a large amount of the underlying directly to the cToken contract, bypassing mint().
  3. The exchange rate (cash / supply) skews dramatically.
  4. Subsequent depositors round to zero shares due to integer math.
  5. Drain.

This attack works against any Compound V2 fork that doesn't implement defenses against it (minimum supply, virtual reserves, donation skim). Compound V2 itself was less vulnerable because of integration patterns (tokens are typically deposited via a router that ensures meaningful first deposits), but the cToken contract alone is vulnerable.

Hundred Finance forked the cToken contract without adding defenses. The team didn't know they needed defenses, because they hadn't rebuilt the protocol from scratch and discovered why the original was structured the way it was.

The fix in production now is well-known (mint a permanent virtual supply, similar to Uniswap V2's MINIMUM_LIQUIDITY). It's a one-line change. But you have to understand the threat model to know to make it.

Venus blocksPerYear bug (Shadow Arena H-01)

Venus is BSC's largest lending protocol, a Compound V2 fork. Compound V2 was written for Ethereum. Compound V2's interest accrual math uses blocksPerYear = 2,102,400 (assuming 15-second blocks).

BSC has 3-second blocks. The fork inherited the constant unchanged. Result: 5x interest inflation. A 5% APR market behaved like a 25% APR market. We covered this in the Venus blocksPerYear article.

This is fork-without-understand at the parameter level. The team copied the protocol but not the understanding that some constants are chain-specific.

A team that rebuilt Compound V2 line by line would have implemented blocksPerYear themselves and noticed it as a constant they had to compute. They'd have asked "what's blocksPerYear for our target chain?" and computed it correctly.

Velodrome's three permanent-lock paths

Velodrome inherited three reward-lock paths from Solidly's reward-distribution code. Each path strands bribe rewards through a different mechanism (missing claim window check, wrong-order state update during epoch transitions, rounding error accumulation). We covered these in the Velodrome three-locks article.

Velodrome forked Solidly. Aerodrome forked Velodrome. Equilibre forked Velodrome. Each generation of fork inherits all three paths unless someone explicitly patches them. Most don't.

Why most don't: the bugs are subtle, they only manifest under specific epoch-transition conditions, and the test suites don't cover those conditions. A team rebuilding the reward distribution from scratch with a comprehensive test suite would discover the bugs as test failures. A team forking and assuming "Solidly worked" inherits them.

The test suite as a forcing function

This is the part that distinguishes reconstruction-with-tests from reconstruction-without-tests.

If you rebuild Uniswap V2 from scratch reading the whitepaper, you'll get most of it right. You'll miss some defenses. You won't notice you missed them, because the protocol still appears to work in your basic tests.

If you rebuild Uniswap V2 from scratch with a comprehensive test suite that already exists, you can't miss the defenses. The test suite fails specifically on the defenses you missed. You implement them, the tests pass, you understand what they do.

The test suite is the difference between learning the protocol and copying the protocol's surface. It encodes the threat model that the original team developed over years of production exposure. You inherit that threat model by passing the tests.

This is exactly what Zealynx Academy provides:

  • Uniswap V2 reconstruction: 217 automated tests across 16 sections (2 parts: Core Protocol 1-9, Periphery 10-16). Every defensive choice has a test that fails until you implement it.
  • Compound V2 reconstruction: 207 automated tests across 18 sections. Same pattern: tests force the discovery.

The test suite is not optional. It's the entire pedagogy.

When forking is still the right choice

Reconstruction is not always the right choice. Several legitimate cases for forking:

1. You have distribution leverage and don't intend to modify

If you have a community, a brand, a chain you're launching with, and the protocol you fork is exactly the protocol you want to ship, forking is the right call. You're paying with your reputation if it goes wrong, but you're not paying with engineering time you don't have.

This was Pancakeswap's strategy on BSC. Fork Uniswap V2, brand it for BSC, capture the chain's first-mover liquidity. Pancakeswap didn't try to modify the protocol; they just deployed it on a different chain with a different community.

2. The protocol is small and the modifications are small

If you're forking a 200-line yield aggregator and changing one line, reconstructing from scratch is wasted effort. Read the 200 lines, make your change, ship.

The break-even point is somewhere around protocol complexity meaningful enough that defensive choices are non-obvious. Uniswap V2, Compound V2, Velodrome all clear this bar. A simple staking contract probably doesn't.

3. You're prototyping

Prototyping doesn't need correctness. Forking is faster than rebuilding for the prototype phase. Just don't ship the prototype to mainnet.

4. You have audit firm support

If you fork and contract a serious audit firm to review your modifications, the auditors will catch most fork-without-understand failures. This is expensive (a Compound V2 fork audit can run $50K-150K) but transfers the burden of understanding to specialists.

The downside: audits are point-in-time. The next time you modify the fork, you need another audit, or you ship a fresh fork-without-understand failure.

Related questions

Isn't rebuilding 30+ sections by hand slower than forking? Yes. The Uniswap V2 reconstruction takes most teams 30-50 hours of focused work. Forking takes 30 minutes. The reconstruction is an investment in understanding, not in shipped code. The team that rebuilds, then ships a fork, spends ~50 hours and ships a fork they understand. The team that just forks spends 30 minutes and ships a fork they don't understand.

What if I just read the source code carefully instead of rebuilding? Reading is a weak forcing function. You can read carefully and still miss defenses, because you don't have a test suite telling you "this defense matters". Every senior auditor I know does both: read the source AND rebuild key sections to validate understanding.

Can I rebuild without a test suite if I'm careful? Yes, but you'll miss things. The value of the test suite is precisely that it surfaces what you'd miss. A reconstruction without tests is a slower fork.

What's the right depth to rebuild? For protocols you'll operate or modify in production: rebuild fully. For protocols you'll just integrate with: rebuild the parts your contracts touch. For protocols you'll observe but not interact with: reading + targeted rebuilds of confusing pieces is enough.

Doesn't this advice apply to traditional software too? Yes. The Web3 difference is the cost of being wrong. A bug in a SaaS app annoys users. A bug in a smart contract drains the protocol. The cost asymmetry justifies the higher engineering investment.

Where to see this in Academy

The two flagship reconstructions on Zealynx Academy are Uniswap V2 (217 tests) and Compound V2 (207 tests). Pick either, work through the sections in order, watch the test suite tell you when you've understood each piece.

After you finish either, you'll find that auditing forks of that protocol becomes much faster. You can scan a fork's mint() and immediately spot the defense the fork stripped, because you remember rebuilding it yourself.

That's the entire point of the exercise. The test suite is the medium. The understanding is the deliverable.

Tagged

Web3 EducationAudit MethodologyDeFi