Skip to content
Foundational10 min readUpdated September 2026

Delivering Software You Cannot Patch

Continuous delivery assumes you can ship a fix. On-chain, you cannot, and a defect is a withdrawal of funds. The answer is not waterfall — it is moving iteration upstream and making the irreversible step as small as possible.

Almost every practice in modern delivery rests on one assumption that nobody bothers to state, because in ordinary software it is so obviously true that stating it would be strange. The assumption is that you can ship a fix. Deploy small, learn fast, roll forward, roll back — every one of those instructions borrows against the same credit line. When something goes wrong, you get another turn.

Deploy a contract to a public chain and the credit line is gone. The bytecode at that address is what it is: no hotfix, no rollback, no maintenance window, no quiet revert at two in the morning. And the failure mode is not a degraded experience or an error rate on a dashboard. A defect in a contract holding value is a withdrawal of funds, executed by an adversary who is financially motivated, able to read your code, and under no obligation to tell you anything first.

The usual reaction, in organisations that have previously shipped ordinary software, is to conclude that iterative delivery does not apply here and to reach for a long, documented, phase-gated programme. That reaction is understandable and it is wrong — wrong in a way that makes the risk worse, not better. When the last step is irreversible, you need more iteration, not less. You just need it somewhere other than production. And the asymmetry between the cost of a defect and the cost of a delay should reshape your entire delivery design, not merely your test suite.

What actually changes when rollback is impossible

Be precise about which assumptions break, because not all of them do, and the temptation is to throw away things that still work.

Deployment is no longer a reversible experiment. Deployment is cheap in ordinary systems precisely because it is undoable, which is what licenses the whole canary-and-rollback discipline. On-chain, the deploy transaction is final. You can deploy a replacement and migrate, but the original still exists, still holds whatever it holds, and anything approved against it is still approved.

The defect is discovered by someone who benefits from not telling you. Ordinary production defects are reported by users who want them fixed. On-chain defects are found by people whose optimal move is to exploit them silently. Your time to detection is not governed by your alerting; it is governed by how long the adversary chooses to wait.

The blast radius is not bounded by your system. Contracts compose. Something built on top of yours may hold assumptions you never published, and a dependency you integrate can be manipulated in ways that turn your correct code into a loss.

Everything is public before it matters. Your code is readable, pending transactions are visible before they settle, and adversaries can fork the chain and rehearse an attack against your live state for free. You never had security through obscurity.

The cost function is asymmetric and steep. In ordinary delivery, shipping late and shipping badly are comparably bad. Here the loss from an irreversible defect can exceed the entire value of the programme that produced it, while the loss from a two-week delay is two weeks. When one arm of a decision is bounded and the other is not, you do not split the difference.

What does not change is more interesting. Small changes are still easier to verify than large ones. Feedback is still more useful early than late. Automatic evidence still beats evidence assembled by hand. The principles survive intact; it is the place you apply them that moves.

Why the answer is not a twelve-month waterfall

The waterfall instinct is rational in its premises and wrong in its conclusion. The premise — if you cannot correct after release, you must get it right before release — is true. The conclusion drawn, that you therefore need a long sequential process with verification concentrated at the end, does not follow. Three reasons it fails here specifically, and fails worse than usual.

Verification at the end is verification at maximum batch size. The argument in batch size and the cost of big releases applies with the dial turned to maximum. A year-long programme arrives at its first serious adversarial review carrying a year of coupled, unreviewed design decisions, including interactions between components that were individually reasonable. This is precisely the condition under which review is least effective and most expensive.

Design errors are the dominant failure class, and long cycles hide them longest. The serious losses here are rarely a missing semicolon. They are economic-logic errors: an invariant that fails under an edge case, an assumption about a price source an adversary can violate, a call sequence nobody imagined. Those are found by adversarial thinking against working code, and working code arrives late in a waterfall.

Specification cannot be relied upon to be complete. The justification for front-loading is that the specification captures the truth. In an adversarial domain it captures what you thought of, and the attacks that matter come from what you did not. You discover those by building, simulating and attacking — not by writing more of the document.

There is a second, quieter failure. A long cycle produces enormous pressure at the end: the deadline is public, the capital is committed, and the engineer who raises a concern in month eleven is the engineer who costs the organisation a quarter. That is the pressure structure which produces a deployment somebody privately doubted, and it is manufactured by the shape of the process.

Moving iteration upstream

Here is the reframe that makes the whole thing tractable. Iteration is not the same as deployment. You iterate to get feedback about whether the thing works, and production is merely one place — usually the cheapest, in ordinary software — to get it. When production becomes the most expensive place imaginable, you buy the feedback somewhere else. This domain has unusually rich substitutes: deterministic execution and public state let you reconstruct production conditions faithfully in a way most industries cannot.

A local chain, running on every commit. The full system deployed and exercised in seconds as part of the ordinary build — the loop that should be fastest and used most.

Mainnet forking. Run your contracts against the real dependencies, balances and configurations they will meet in production. This catches an entire class of integration assumption that a mocked test suite will happily confirm for you.

Property-based testing and fuzzing. State the property that must always hold and let a fuzzer attack it with adversarially chosen inputs, rather than asserting that particular inputs give particular outputs.

Invariant testing against sequences. Declare the system-level properties that must hold after any sequence of calls by any actor, then have the tooling generate those sequences. Most economic-logic defects are sequence defects, and this is the loop that finds them.

Simulation of economic conditions. Adversarial price movements, liquidity shocks, transaction ordering chosen by an adversary. The question is not whether the code executes but whether the incentives hold.

Testnets and staged environments. Good for integration, operations, indexing, front ends and rehearsing the deployment runbook. Weak as a security signal, because testnet lacks adversaries and real capital.

Each of these is an iteration loop, cheap and repeatable. Collectively they keep the tight feedback cadence that makes iterative delivery work, while the irreversible step happens once, late, and small.

LoopTypical cycle timeWhat it tells youWhat it cannot tell you
Local chain testsSecondsDoes the intended behaviour workWhether the intent was right
Fuzzing and property testsMinutes to hoursDo stated properties survive hostile inputsProperties you failed to state
Invariant testingHoursDo system properties survive arbitrary call sequencesCross-protocol economic effects
Mainnet forkingMinutesDoes it work against real dependencies and stateFuture dependency behaviour
SimulationHours to daysDo the incentives hold under stressAdversary creativity
Internal adversarial reviewDaysDesign and economic flawsBlind spots the team shares
External auditWeeks, with lead timeIndependent judgement on logicCompleteness — no audit is a guarantee
MainnetIrreversibleThe truthAnything, in time to help

The table is the argument. Every row above the bottom one is somewhere you can be wrong for free, and the discipline is to exhaust them in order.

Making the irreversible step small

The second half of the strategy is to reduce what is at risk in the one step you cannot take back.

Deploy less code. Every line at a value-bearing address is attack surface that cannot be retracted. Scope discipline is a security control here, not a project-management preference. The feature that is nice-to-have is also permanent.

Keep the on-chain component minimal. Most of a working system does not need to be on-chain, and the rest can be delivered with ordinary practices — see the on-chain boundary.

Prefer simple, well-understood constructions. Novelty is risk. A pattern deployed widely and attacked for years carries information your clever alternative does not, even if yours is theoretically superior.

Stage the exposure, not just the code. You cannot roll back a deployment, but you can control how much value it touches. Deposit caps raised on a schedule, restricted allowlists at launch, limited functionality enabled first — a canary achieved with limits rather than traffic routing. Separating deployment from activation belongs here too: contracts can be deployed, verified and inspected by third parties before they are connected to value, and that window is free verification time.

Decide the upgrade posture deliberately. Immutable and upgradeable are both defensible; drifting into one by accident is not. An upgrade mechanism is a trust assumption imposed on users, and it is argued out in upgradeability is a trust decision.

The asymmetry should reshape more than the test suite

The most common half-measure is to accept the risk argument and respond by buying more testing. Testing is necessary. It is not the whole adaptation, because the asymmetry touches almost every part of how the work is organised.

Scope decisions become risk decisions. The product manager adding a feature is adding permanent attack surface. That trade-off needs to be visible at the point of the decision, not discovered at audit.

Definition of done acquires teeth. For a contract change, done is not "merged and tested". It includes stated invariants, fuzz runs at a defined depth, a threat model entry, a deployment runbook and a reviewed upgrade posture — the definition of done as a contract applied where the stakes justify the ceremony.

Your external dependencies acquire lead times. Review capacity is committed well ahead, and treating it as a stage gate rather than a scheduled constraint is how programmes freeze code they are not ready to freeze. See audit scheduling and the code freeze.

Key management becomes a delivery concern. Deployment keys, upgrade keys, pause authority and signer topology are part of the release design, not an afterthought handed to operations. The best contract verification in the world is worth nothing against a compromised upgrade key.

The culture has to reward stopping. The most valuable thing an engineer can do here is say "I do not understand this interaction well enough to sign it off" in the week before launch. If that costs them socially, you have designed a process that suppresses exactly the signal it exists to collect.

The underlying idea runs through this whole library: verification is the bottleneck, and the work is to make it continuous, cheap and mechanical rather than a gate at the end. The general form is in verification is the new bottleneck. This domain is where it is least deniable.

What to do on Monday

Draw your delivery path and mark the exact point at which a mistake becomes irreversible. Most teams have never drawn this line and are surprised by where it sits — often earlier than expected, because an approval or a configured address can be as final as the deployment itself.

For everything upstream of it, ask how fast each loop is and how often it runs. If your invariant suite takes an hour and runs weekly, it is not an iteration loop, it is a report. Get one loop from days to minutes this week.

For the irreversible step, write down three numbers: how much code is being deployed, how much value it can hold in the first month, and how many people have attacked it with hostile intent rather than reading it for correctness. Reduce the first, cap the second, increase the third. Those three moves do more for your risk position than any process you could introduce in the same period.