Smart Contract Security: Bugs, Audits & Bounties
01 · Concept — what problem does it solve?
Ordinary software has three safety nets that smart contracts mostly lack. You can patch it quietly. You can roll back a bad transaction. And the code is usually private, so an attacker has to guess.
A deployed contract is the opposite on all three: its code is public, its bytecode often cannot be changed, and a successful exploit settles atomically — the funds are gone in the same block. That turns every unfound bug into an open bounty for whoever finds it first, with no legal process between them and the money. Smart-contract security is the discipline of finding the bug before that person does.
In plain English
Imagine a vending machine bolted to a public square, with its blueprints posted on the front, holding real cash, that can never be opened up and fixed. Everyone who might want to shake it loose can read exactly how it's built. Security here means going over the design harder than the thieves will.
02 · Mechanics
The recurring bug classes
- . A contract makes an external call before updating its own books, and the callee calls back in while the books are stale. The defence is checks-effects-interactions ordering and a reentrancy guard.
- Access control. A privileged function that anyone can call, or an initialiser that can be run twice — especially on proxies, where an uninitialised implementation is a live target.
- and price manipulation. Acting on a price the attacker can move inside one transaction. See Oracles.
- Rounding and precision. Integer division in share-based vaults can be steered so the first depositor captures everyone else's deposits. Small arithmetic, large loss.
- Unsafe composability. Assuming the token or contract on the other side of a call behaves like a plain — fee-on-transfer, callbacks, or upgradeable logic all break that.
- Toolchain bugs. The code is correct and the compiler is not. See the box below.
- Upgrade and admin keys. Not a code bug at all: the address allowed to swap the logic is a trust assumption sitting inside the protocol. See Key Management.
The defensive ladder — each rung catches different things, and none replaces the others:
- Tests, including fuzzing and invariant tests that assert properties like "total shares never exceed total assets".
- Static analysis for known dangerous patterns.
- Audit — a time-boxed human review of a specific version of the code.
- Formal verification — a mathematical proof that named properties hold.
- — pay outsiders to look, for years, at the deployed system.
- Runtime limits — deposit caps, timelocks, pause switches and monitoring, so a bug you missed has a bounded blast radius.
03 · Formulas
There is no reliable number for "probability this contract has a bug", which is exactly why the honest framing is about exposure and incentives.
expected_loss = P(exploitable bug) × value_at_risk
// P is unobservable; audits lower it, they cannot certify zero.
// value_at_risk is the one term you fully control — via deposit caps.
// a simple incentive model for bounties (a model, not a measurement)
attacker_take ≈ value_at_risk × (1 − laundering_haircut) − legal_risk
whitehat_take = min(bounty_cap, bounty_pct × value_at_risk)
// disclosure is rational for a finder only when whitehat_take is competitive
// with attacker_take — so bounties must scale with what the contract holds.
// layered defences multiply only if they fail independently
P(all layers miss) ≈ Π pᵢ // ← the assumption that breaks below
04 · Edge cases & risks
- Immutability cuts both ways. It removes the operator's power to rug you — and the operator's power to fix a discovered bug. Upgradeable contracts reverse the trade, so the question becomes who holds the upgrade key and how long the is.
- Scope is the seam. Most audit reports list what was not reviewed. Exploits live in those gaps: the oracle adapter, the bridge, the governance module added last month.
- Economic exploits are not code bugs. A contract can execute exactly as written and still be drained through a manipulated price or a governance vote. A clean audit does not address them — see DeFi Risk Taxonomy and MEV.
- Bounties work on rational finders. They do nothing about attackers who will not take a percentage — including state-backed groups — which is why runtime limits matter.
- Age helps, imperfectly. Code that has held value for years is better evidence than a fresh audit, but the Vyper case shows a long track record can hide a latent flaw that only one specific call sequence exposes.
A protocol's Vyper source passes several clean audits, then is drained through a bug in the compiler that built it. Why did the audits not catch it?
Which control best limits the damage from a vulnerability nobody has found yet?