BR Academy
0%
Foundations of Smart Contract Security·Lesson 1 of 20·20 min

Why Audits Matter: The Cost of Exploits

The concept

Smart contracts are different from almost every other kind of software you will ship: the moment a contract goes live and holds real user funds, every line of code becomes a financial liability. There is no "roll back the release" button. If an attacker finds a way to drain a vault, the money is usually gone within a single transaction, often inside a single block. That is why professional auditing exists as its own discipline rather than just being "extra code review."

Three loss patterns account for most of the money lost in DeFi history, and almost none of them involve exotic cryptography:

  • Reentrancy drains — a contract calls out to an external address before it finishes updating its own state, and the external address calls back in to repeat the withdrawal.
  • Oracle/price manipulation — a contract trusts a price it reads from a single source that an attacker can temporarily move, usually with a flash loan.
  • Access-control takeovers — a privileged function (change the fee recipient, mint new tokens, upgrade the contract) is missing the check that should have restricted it to the owner.

Each of these traces back to a handful of missing lines, not a sophisticated zero-day. That is the core intuition this course builds: auditing is mostly about systematically asking "what did the author assume, and is that assumption actually guaranteed by the code?" The cost of a thorough review — days of an auditor's time — is almost always smaller than the cost of a single successful exploit, which is why clients pay for it and why this is a real, billable skill.

Worked example

Consider a simplified vault:

function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount, "insufficient balance");
    (bool ok, ) = msg.sender.call{value: amount}("");
    require(ok, "transfer failed");
    balances[msg.sender] -= amount; // <-- state updated AFTER the external call
}

On a first read this looks correct: it checks the balance, sends the funds, then decrements the balance. The flaw is the order. The .call happens before balances[msg.sender] is reduced. If msg.sender is a contract with a receive() function that calls withdraw() again, the check balances[msg.sender] >= amount still passes — the balance hasn't been reduced yet — and the attacker can withdraw repeatedly in a single transaction until the vault is empty. An auditor's job is to notice that the "effect" (updating balances) happens after the "interaction" (the external call), which is exactly backwards from the safe pattern you'll learn in Module 2.

(See assets/vault-call-order-diagram.png — alt text: "Sequence diagram showing withdraw() calling out to msg.sender before decrementing the balances mapping, with a dashed re-entry arrow looping back into withdraw().")

Exercise

Pick a contract you've written or used before — even a simple token or staking contract. List the three functions in it that touch user funds most directly (commonly deposit, withdraw, and an admin function like setFeeRecipient or pause). For each of the three, write exactly one sentence describing what could go wrong if a check you assumed was there turned out to be missing or ordered incorrectly. Don't fix anything yet — this seed list is the raw material you'll formalize into a real threat model in the next lesson, "The Auditor's Mindset and Threat Modeling."

Recap

  • Nearly all major DeFi losses trace back to a few vulnerability classes: reentrancy, oracle manipulation, and access control — not exotic math.
  • The vault example shows why order of operations (interaction before effect) is the root cause of reentrancy, not the external call itself.
  • You now have a seed list of your contract's three riskiest functions, which becomes the input to the threat-modeling process next.

Why this keeps happening

It's tempting to assume these losses come from inexperienced teams shipping unaudited code, but the historical record says otherwise. Several of the largest DeFi exploits hit protocols that had already paid for one or more professional audits. That isn't an argument against auditing — it's the argument for why this course treats auditing as a process rather than a one-time stamp of approval. An audit covers the code and assumptions that existed at the time it was performed. A protocol that upgrades a contract, adds a new integration, or changes an oracle source afterward has effectively shipped new, unaudited surface area, even if the original codebase was clean. This is why professional firms increasingly recommend audits tied to every material change, not just pre-launch, and why bug bounties exist as an ongoing backstop between formal engagements.

There's a second reason exploits persist even in reviewed code: auditors work from the scope a client gives them, and scope is usually "this contract" rather than "this contract plus every external protocol it composes with." A perfectly secure vault can still be drained if it blindly trusts a price feed or token contract from an integration that wasn't in scope. Part of the auditor's mindset you'll build in the next lesson is learning to ask what's outside the stated scope and flagging that gap explicitly in the report, rather than silently assuming someone else checked it.

Resources

  • Rekt.news — ongoing archive of real DeFi exploit post-mortems, several of them reentrancy or oracle-manipulation losses.
  • SWC Registry — the Smart Contract Weakness Classification, a standard catalog of vulnerability classes referenced throughout this course.
  • Consensys Diligence — Smart Contract Best Practices — the industry-standard reference for the vulnerability patterns introduced in this lesson.