Blockchain · Guide

Smart Contract Security

Why this code gets attacked harder than any other, the bug classes that keep recurring, and how audits actually work.

— min read Blockchain

Adversarial By Default

Contract code is public, permanently deployed, and holds money that anyone can try to take. The attacker sees your source, can simulate against real state for free, and only needs to be right once.

That inverts the usual economics of software defects. A web bug is a support ticket; a contract bug is a withdrawal you cannot reverse, and there is a standing financial incentive for strangers to look for it. So the discipline is closer to cryptography than to product engineering: conservative patterns, established libraries, and a strong preference for the boring option.

Reentrancy & Common Bugs

Reentrancy is the canonical bug. When your contract sends value to an address, that address may be a contract, and it runs code — which can call back into your function before the first call has finished updating state.

// Vulnerable: the balance is cleared after the external call
function withdraw() external {
    uint256 amount = balances[msg.sender];
    (bool ok, ) = msg.sender.call{value: amount}("");  // attacker re-enters here
    require(ok);
    balances[msg.sender] = 0;                          // too late
}

// Safe: checks, then effects, then interactions
function withdraw() external {
    uint256 amount = balances[msg.sender];
    require(amount > 0);
    balances[msg.sender] = 0;                          // effect first
    (bool ok, ) = msg.sender.call{value: amount}("");  // interaction last
    require(ok);
}

The rule the fix encodes is checks-effects-interactions: validate, update your own state, and only then call out. A reentrancy guard is a useful belt on top, but ordering is the actual defence.

Bug classShape
Missing access controlA privileged function anyone can call
Unchecked return valuesA failed transfer treated as success
Front-runningThe mempool is public; your transaction can be jumped
Integer assumptionsDecimals, rounding and truncation losing value each call
Delegatecall to untrusted codeSomeone else's code writing your storage
Denial of serviceA loop over an array anyone can grow until it runs out of gas

Oracle & Economic Attacks

The expensive incidents are frequently not code bugs at all. The code does exactly what it says, using a number an attacker was able to move.

A price oracle brings off-chain reality on-chain, and reading a price directly from a single pool is the classic mistake: a flash loan borrows an enormous sum with no collateral, distorts the pool, and the contract prices a trade against a number that existed for one transaction.

Weak sourceSturdier
Spot price from one poolTime-weighted average across blocks
A single reporterA decentralised oracle network with aggregation
No sanity boundsReject moves outside a plausible range
No staleness checkReject a price older than a threshold
Assume every input an attacker can influence within one transaction is hostile — pool balances, block timestamps, transaction ordering. Flash loans make capital an attacker no longer needs to own.

Auditing & Formal Verification

An audit is a time-boxed review by people who do this constantly. It raises the bar considerably and guarantees nothing — several audited protocols have been drained — so it belongs in a chain of defences rather than at the end of one.

LayerFinds
Static analysis (Slither)Known bad patterns, fast and free
Fuzzing and invariantsProperty violations you did not anticipate
Manual auditLogic and economic flaws tools cannot see
Formal verificationMathematical proof that a property always holds
Bug bountyContinuous review, priced against the exploit

Formal verification proves a stated property over all possible inputs — total supply never exceeds the cap, no path lets a user withdraw more than they deposited. It is strong and narrow: it proves what you asked, so an unstated assumption is still a hole.

Operational controls matter as much as code review: a timelock on upgrades so users can exit, a multisig rather than one key, a pause switch, and caps that limit what a single incident can drain.

Interview Questions

What is reentrancy?

Sending value to an address runs that address's code, which can call back into your function before it finished updating state — so balances are read again before they were cleared.

What is the checks-effects-interactions pattern?

Validate inputs, update your own state, then make external calls. Ordering is the real defence; a reentrancy guard is a secondary control.

Why is reading a spot price from one pool dangerous?

A flash loan can move that pool within a single transaction, so the contract prices against a number the attacker created. Time-weighted averages and aggregated oracle networks resist that.

What do flash loans change?

They remove capital as a barrier. An attacker can borrow an enormous sum with no collateral, provided it is repaid in the same transaction, so any attack profitable at scale is available to anyone.

Does an audit make a contract safe?

No. It is a time-boxed review that raises the bar. Audited protocols have been drained — it belongs alongside static analysis, fuzzing, invariants, bounties and operational limits.

What does formal verification actually prove?

That a stated property holds for all inputs — a supply cap, a withdrawal bound. It is exactly as complete as the properties you thought to state.

Quick Quiz

1. Reentrancy is prevented primarily by…
2. A flash loan lets an attacker…
3. A safer price source is…
4. A completed audit means…
5. Formal verification proves…