Deployed code that cannot be patched is an unusual engineering situation. In almost every other environment, a bug found on Tuesday is fixed on Wednesday. On a public chain the contract sits there exactly as written, holding value, visible to everyone including the people looking for a way in.

That constraint shapes everything about smart contract security, and it explains why the discipline looks less like ordinary software testing and more like structural engineering, where you are expected to get it right before it is poured.

The Classic Bug Classes

The textbook failures are well understood now, which does not stop them recurring.

Reentrancy remains the most famous: a contract calls out to an external address before updating its own internal accounting, and the recipient calls straight back in to spend the same balance twice. The 2016 attack on The DAO drained several million ether this way and led to the hard fork that produced two Ethereum chains. Arithmetic overflow was the second great classic, largely defused when the compiler began checking arithmetic by default, which is a good illustration of a whole vulnerability class being closed at the language level rather than by developer discipline.

Then there is the quieter one: access control. A function that should be restricted to an owner, or an initialiser that can be called twice, or a proxy left uninitialised after deployment. These are not clever attacks. They are missing modifiers, and they have cost more than the clever ones. Ethereum's own smart contract security documentation catalogues these patterns, and the striking thing is how few of them are exotic.

The Losses That Are Not Code Bugs at All

Here is the part that surprises people coming from a purely engineering framing. Several of the largest incidents on record involved contracts that behaved exactly as written.

Compromised validator or signer keys let an attacker submit perfectly valid transactions. Oracle manipulation feeds a contract a true-looking price that has been distorted for a single block, and the contract liquidates or mints precisely as instructed. Governance capture acquires enough voting power to pass a proposal that drains a treasury legitimately.

In each case an auditor reviewing the code would find nothing wrong, because nothing in the code is wrong. The failure is in the assumptions surrounding it: that the price feed is honest, that the multisig signers are independent, that no single actor can accumulate that much voting weight. Threat modelling has to cover the system, not the file.

What an Audit Can and Cannot Tell You

An audit is a time-boxed review of a specific commit by a small number of people. It is genuinely valuable and it is routinely oversold.

It cannot prove the absence of bugs. It usually excludes the parts of the system that are off-chain, which is often where the keys live. It covers the code as submitted, so any change afterwards, including a routine upgrade, sits outside its scope. And because reports are marketing assets as well as engineering documents, the phrase "audited by" has drifted a long way from "reviewed thoroughly by people who had enough time."

Read the actual report rather than the badge. Check the commit hash it covers, the scope section listing what was excluded, and whether the findings were fixed or merely acknowledged. Firms offering smart contract development services will provide this; the ones that hesitate are telling you something.

Testing That Actually Finds Things

Unit tests confirm the behaviour you thought of. The failures come from the behaviour you did not.

Audit findings reach the public through an unusually short chain. A vulnerability disclosure is picked up, translated and republished within hours, often before the project has commented. Most of that initial spread runs through news agencies, whose framing tends to set how the incident is remembered.

Property-based fuzzing is the highest-value addition for most teams: you assert an invariant that must always hold, such as total supply never exceeding a cap or a pool never paying out more than it holds, and let a fuzzer hammer at it with adversarial inputs for hours. Formal verification goes further and proves properties mathematically, which is expensive and appropriate for the small core of a system holding most of the value. Static analysis is cheap and catches the known patterns before a human wastes time on them.

Anyone doing defi smart contract development should also test against forked mainnet state rather than a clean local chain, because composability means your contract's safety depends on protocols you did not write and cannot control.

Assume You Will Be Wrong

The mature position is to design for the failure rather than only against it. Circuit breakers that pause the contract. Rate limits that cap how much can leave in a given window. Timelocks on upgrades so users can exit before a change takes effect. Multisig control with genuinely independent signers.

Continuous smart contract monitoring belongs in this category too. Watching on-chain activity for anomalies and being able to act within minutes has repeatedly turned what would have been a total loss into a partial one. The smart contract is immutable, but the system around it does not have to be inert.

Key handling deserves the same seriousness, and the reasoning that applies to protecting an individual holding scales up: the logic behind cold wallet storage is the same reasoning that says deployment keys should not live on an internet-connected laptop.

One last thing that gets overlooked in cross-border projects. A smart contract encodes an agreement, but the enforceable agreement is usually still a written one, and if your counterparties operate in another jurisdiction and another language, that document carries the risk the code does not. Getting it handled by a professional legal translation agency is unglamorous next to formal verification, and it addresses a category of loss that no audit will ever look at.