Skip to content
HomeAboutServicesGuidesContact

Guide

How protocols lose money

The defect classes that actually cost funds, organised by what the attacker is doing rather than by what the bug is called.

In short

  • Vulnerability taxonomies are organised for auditors. This one is organised by what the attacker wants, because that is the order a defender should think in.
  • Most losses are not exotic. Reentrancy, stale price feeds, unprotected initialisers and unchecked return values have been understood for years and still account for a large share of incidents.
  • The expensive classes are rarely in the contract alone. Bridges, governance and upgrade paths fail at the seams between systems, where each side assumed the other was checking.
  • A defect class is not a bug. It is a shape that bugs take, which is why a reviewer works through classes systematically rather than hunting for individual mistakes.

Why organise by objective

Most vulnerability lists are organised the way a reviewer works: by mechanism, in the order a checklist walks them. That is useful to the person doing the review and close to useless to the person deciding whether to fund one.

An attacker does not set out to perform a reentrancy. They set out to remove funds from a contract, and reentrancy is one of several shapes that attempt can take. Organising by objective puts the classes that achieve the same end next to each other, which is how a defender should see them.

In plain terms: the question worth asking of your own system is not which bugs it might contain. It is what someone would be trying to do to it, and which routes are open to them.

Taking the funds directly

The oldest objective and still the most common. The attacker wants balances moved, and the contract has a path that allows it.

Reentrancy is the archetype. A contract sends value to an external address before updating its own accounting, and the receiving contract calls back in while the first call is still open, finding a balance that has not yet been decremented. The withdrawal repeats until the pool is empty.

Unchecked external call results are quieter and nearly as costly. Some token implementations signal failure by returning false rather than reverting. A contract that ignores the return value records a payment that never moved, and its accounting drifts from reality until someone notices the shortfall.

Arbitrary external calls are the structural version of the same problem. A function that lets a caller specify both a target address and the calldata to send is a function that lets a caller use the contract as a signer for operations nobody designed.

In plain terms: these are failures of ordering and of trust. Update state before calling out, treat every external call as a call to hostile code, and check what comes back.

Rigging the price

If a system decides anything based on a price, the price is an attack surface. Liquidations, collateral ratios, swap rates and reward calculations all read a number from somewhere, and that somewhere can usually be influenced.

Spot-price manipulation is the direct form. A protocol reads the current ratio of an on-chain pool and treats it as the market price. An attacker moves that ratio with a large trade, acts against the distorted figure, and restores it in the same transaction. Flash loans removed the capital requirement that once made this impractical.

Stale feeds are the passive form, and more common in practice. An oracle stops updating and the consuming contract keeps using the last value it received as though it were current. No manipulation is required; the attacker simply trades against yesterday price while the market moves.

In plain terms: reject answers older than the heartbeat plus a grace window, and pause rather than default through when a feed stalls. A price that cannot be trusted should stop the system rather than quietly misprice it.

Jumping the queue

Transactions wait in a public queue before they execute, and the order they execute in is purchasable. Anything whose value depends on being early is exposed.

Sandwiching is the retail-facing version. A pending swap with no minimum output and no deadline is a blank cheque: a bot moves the price before the victim transaction executes, lets it fill at the worse rate, and restores the price afterwards, keeping the difference.

Initialisation races are the deployment version. An upgradeable contract whose initialiser is callable by anyone has a window between deployment and configuration in which a watcher can claim the owner seat first. The team then owns a contract they do not control.

In plain terms: deployment order on a public chain is a race, and code that assumes it will win needs to stop assuming. Initialise atomically with deployment, and require a minimum output and a deadline on every trade.

Becoming the owner

Rather than defeat the access control, acquire it. This class is where the largest single losses tend to sit, because privileged access usually bypasses every other protection at once.

Missing or wrong modifiers are the simple case: a function that should be restricted is not, or checks the wrong role. These are found by systematically enumerating every state-changing function against who should be able to call it, which is exactly why that enumeration is worth doing.

Proxy and upgrade paths are the structural case. An upgradeable system has an admin that can replace the implementation, and replacing the implementation replaces every rule the contract enforces. If that admin is a single key rather than a multisig behind a timelock, the system is as secure as that one key.

Delegatecall into untrusted code is the subtle case. Executing another contract code against your own storage means a library you do not control can rewrite your variables, including the one that records who the owner is.

In plain terms: the question to ask of any deployed system is what its most privileged role could do on its worst day, and how long anybody would have to notice.

Buying the rules

Where governance controls the parameters, acquiring enough votes is a legitimate-looking route to changing them. Nothing is broken; the mechanism is used exactly as designed, by someone whose interests differ from the people who designed it.

Borrowed voting power is the acute form. Where voting weight is measured at the moment of the vote rather than at a snapshot taken beforehand, an attacker can borrow tokens, vote, and return them within one transaction. The proposal passes with capital the attacker never owned.

Quorum and delay settings are the slow form. A quorum low enough to pass with a modest holding, or a timelock short enough that nobody can react, turns governance from a protection into a mechanism of attack.

In plain terms: a timelock does not prevent a hostile proposal. It gives everyone watching time to act before the proposal lands, which only helps if the delay is long enough to be noticed and someone is actually watching.

Breaking the seam between systems

The costliest incidents in this industry have mostly not been contract bugs. They have been failures at the boundary between two systems, where each side assumed the other was performing a check.

A bridge is the clearest example. It is two or more systems that must agree about events neither can observe directly, and its security lives in how a message is proved, who is trusted to attest to it, and what happens when those parties disagree. The contracts are the easy part.

Message replay and proof verification are where bridges fail concretely: a message accepted twice, a signature set that can be reconstituted, a validator threshold low enough to collude at. Integration boundaries inside a single protocol fail the same way, with one contract trusting input another contract never validated.

In plain terms: a bridge is only as sound as its weakest chain, and a protocol is only as sound as the assumptions it makes about everything it calls.

Breaking the proof itself

Systems that rely on cryptography rather than on re-execution fail in a category of their own, and it is the one least visible to a reviewer without the specific background.

Under-constrained circuits are the characteristic failure of zero-knowledge systems. A constraint that should bind a variable simply does not, so the proving system accepts values nobody intended and produces a valid proof of a false statement. Nothing reverts, nothing looks wrong, and the verifier agrees.

Trusted setup and randomness are the adjacent risk. A proving system whose setup parameters were generated in a way nobody can verify is a system whose soundness rests on a ceremony rather than on mathematics.

Signature handling fails more mundanely: a malleable signature accepted twice, a replay across chains because the chain identifier was never part of the signed payload, a nonce that does not increment.

In plain terms: a proof verifying is not the same as a proof being about what you think it is about. These defects do not surface as failures; they surface as correct-looking operations that should never have been permitted.

Making it stop

Not every attack takes funds. Some make the system unusable, which for a protocol holding collateral can be equally expensive.

Unbounded loops are the common mechanism. A function that iterates over a list which grows without limit eventually exceeds the block gas limit and can never complete again. If that function is how users claim rewards or exit a position, the exit is closed for exactly the users who have been there longest.

Liquidation economics are the subtler mechanism. If the incentive paid to liquidators drops below the gas cost of performing a liquidation during congestion, nobody liquidates, and bad positions stay open in precisely the market conditions where they do the most damage.

In plain terms: a system that cannot be used is not safe merely because the funds are still in it. Availability under adversarial conditions is part of the design, not a performance concern.

The quiet ones

Classes that rarely produce a headline and steadily produce losses.

Precision loss: integer division that truncates, performed before multiplication rather than after, stranding remainders in a contract nobody can withdraw from. Individually negligible, permanent and compounding in aggregate.

Silent failures: operations that fail without reverting and without emitting anything, so monitoring sees nothing and the discrepancy surfaces weeks later in a reconciliation.

Missing events on privileged actions: an admin changes a fee recipient or rotates a price feed and the chain records no announcement. Nobody watching can tell a hostile change from a routine one, because neither is visible.

In plain terms: these are the findings that get accepted rather than fixed, which is often the right decision. They should still be written down, because the aggregate matters even when each instance does not.

Where each class gets caught

Not every class is found the same way, and the differences decide what a defender should buy. A review reads code and reasons about it; monitoring watches a deployed system and reacts. Some classes are only visible to one of the two.

ClassFound by reviewVisible to monitoring
Taking funds directlyYes, systematicallyOnly once it is happening
Price manipulationYes, as a design weaknessYes, as an anomaly in real time
Queue ordering and front-runningYes, in the transaction designPartially, by pattern
Privileged access and upgrade pathsYes, by enumerating rolesYes, when a privileged call fires
Governance captureYes, in the parametersYes, when a proposal is submitted
Bridge and integration seamsYes, on both sides if both are in scopePartially, on message volume
Availability and gas exhaustionYes, by modelling growthYes, when claims start failing
Precision and silent failuresYes, by invariant testingOnly through reconciliation
Proof-system defectsYes, with the specific expertiseNo

What this list is for

A defect class is not a bug. It is a shape that bugs take, and that distinction is why a review works through classes systematically rather than hunting for individual mistakes. A reviewer who checks every external call for reentrancy will find reentrancy wherever it exists; a reviewer looking for clever bugs will find the ones they happen to think of.

The same applies to reading a report. The classes above are roughly what a competent technical audit covers, which makes them a reasonable check against a scope statement: if a system has a bridge and the scope does not mention message verification, that gap is worth a question.

No list is complete, and this one is not. Novel mechanisms produce novel failures, which is the part of the work that cannot be reduced to a checklist and the reason manual review still costs what it does.

It is also bounded in a second way worth stating. Every class above describes something that happens on chain, because that is what a contract audit examines. A protocol can lose money without any of them occurring, through a swapped front end, a deploy credential, or a signer’s machine, and none of that is visible to a review of the contracts.

Questions

Are these ranked by frequency or by cost?

Neither precisely. They are grouped by what the attacker is trying to achieve. Taking funds directly is the most frequent objective; failures at the seam between systems, particularly bridges, have produced the largest individual losses.

If these classes are well understood, why do they keep happening?

Because knowing a class exists is not the same as checking every instance of it. Reentrancy is understood by every competent Solidity developer and still appears, usually in a path nobody thought of as a value transfer. Systematic coverage is the defence, not awareness.

Does an audit cover all of these?

A technical review covers most of them against the scope it names. Economic and incentive failures sit at the boundary and are not always included, so confirm whether they are in scope rather than assuming. Governance and bridge trust models are usually separate pieces of work.

Which of these can monitoring catch after deployment?

Price manipulation, privileged changes and liquidation failures all produce on-chain signals in real time. Reentrancy and access-control defects do not announce themselves before they are used, which is why they belong to review rather than monitoring.

The work behind this

Related reading