Why contract bugs are so costly
A smart contract controls value with code that usually cannot be changed after deployment, and assets taken from it are hard to trace and mostly irrecoverable. ethereum.org puts total losses from contract defects at easily over $1 billion.[1]
Its examples show the scale: the 2016 DAO hack took 3.6M ETH, the Parity multisig hack lost $30M, and a separate Parity bug left over $300M in ETH locked forever. None of these needed a broken blockchain; each was a flaw in contract logic.[1]
The OWASP Smart Contract Top 10 (2025)
OWASP publishes a Smart Contract Top 10 as an awareness document for web3 developers; the edition on owasp.org is dated 2025. It was built from an analysis of 149 security incidents in SolidityScan's Web3HackHub (2024) and two 2024 loss reports, which together document over $1.42 billion in losses.[2]
The ranking runs: SC01 access control, SC02 price oracle manipulation, SC03 logic errors, SC04 lack of input validation, SC05 reentrancy, SC06 unchecked external calls, SC07 flash loan attacks, SC08 integer overflow and underflow, SC09 insecure randomness, and SC10 denial of service.[2]
For deeper work, OWASP also maintains the Smart Contract Security Verification Standard (SCSVS), an open standard for designing, building and testing secure smart contracts. Its latest stable version is 0.0.1, dated September 2024.[3]
| Rank | Risk | Common defense |
|---|---|---|
| SC01 | Access control | Role checks on every privileged function; multisig owner |
| SC02 | Price oracle manipulation | Avoid spot DEX prices; use time-weighted or external oracles |
| SC03 | Logic errors | Invariant tests and independent review |
| SC04 | Lack of input validation | Validate every parameter before changing state |
| SC05 | Reentrancy | Checks-effects-interactions; reentrancy guard |
Reentrancy: a worked example
Reentrancy happens when a contract sends funds before it updates its own records. The receiving contract calls back into the withdraw function while the first call is still running, and each nested call sees the old balance. That is how the DAO hack drained funds in 2016.[1]
Take a vault holding 10 ETH, where an attacker deposited 1 ETH. The vulnerable withdraw function sends 1 ETH and only then sets the attacker's balance to 0. The attacker's fallback function re-enters withdraw on each payment, so the check passes 10 times and the vault is empty before the balance is ever cleared.[1]
The Solidity docs prescribe the checks-effects-interactions pattern: check the inputs, update state (set the balance to 0), and only then make the external call. With that order the second call sees a zero balance and fails, so the attacker gets back only the 1 ETH they put in.[4]
OpenZeppelin Contracts adds a second layer: its ReentrancyGuard utility provides a nonReentrant modifier that blocks nested calls into protected functions.[5]
See also: OpenZeppelin
Arithmetic, access and upgrades
Since version 0.8.0, the Solidity compiler rejects code that results in integer overflow or underflow, which closed a bug class behind many early token exploits. ethereum.org advises that contracts compiled with older versions use explicit checks or a library such as SafeMath.[1]
For privileged functions, ethereum.org recommends a multisig that needs signatures from a minimum number of owners, for example 3 of 5, instead of a single owner key. An emergency stop should itself sit behind an onchain vote, a timelock or a multisig, so users do not have to trust one developer.[1]
The Solidity docs also warn never to use tx.origin for authorization, and note that a contract receiving Ether through transfer or send can rely only on a 2,300 gas stipend, which is not enough to modify storage. The docs add that code inside unchecked blocks wraps silently on overflow.[4]
Denial of service, SC10 in OWASP's list, often comes from loops. The Solidity docs have a section on gas limits and loops: a loop over an array that anyone can grow may one day need more gas than a block allows, and then the function can never run again. Bounded loops and pull-based payouts, where each user withdraws their own share, avoid that trap.[4]
Testing, audits and bug bounties
ethereum.org lists the layers most teams stack: unit and fuzz tests, static analysis, an external audit, and a bug bounty after launch. It suggests sizing bounty payouts in proportion to the funds at risk, so reporting a flaw pays better than exploiting it.[1]
An audit is a snapshot of one code version. ethereum.org recommends event monitoring after deployment, and names governance takeovers funded by flash loans as a risk that a timelock on proposals helps prevent.[1]
See also: Foundry · Hardhat · Tenderly · Cyfrin Updraft security course
Contract security for users
Most people meet contract risk through a token approval. Under the ERC-20 standard, an approval allows the spender to withdraw from your account multiple times, up to the approved amount, and a new approval overwrites the old one. It stays in place until you change it, so if the approved contract is later exploited, the approval is the path to your wallet.[6]
Tools such as Revoke.cash list the approvals a wallet has granted and let you cancel them; its card lists support for 100+ networks. Revoking costs a normal network fee, which is small next to an open unlimited approval on a drained protocol.
See also: Revoke.cash
Oracles, flash loans and randomness
DeFi contracts often read a token price from a DEX to value collateral. ethereum.org warns that onchain DEX spot prices are open to manipulation: an attacker can pump an asset's spot price with a flash loan right before interacting with a lending contract, so the protocol values collateral too high. It suggests a time-weighted average price (TWAP) oracle over a longer window.[1]
Flash loans make those attacks cheap because the attacker borrows the capital and repays it in the same transaction. OWASP lists flash loan attacks as SC07 and oracle manipulation as SC02. The usual defense is to price assets from sources that one transaction cannot move, and to test every function under the assumption that the caller controls a very large balance for one block.[2]
Randomness is the other trap. Because blockchains are deterministic, OWASP notes that generating secure randomness is hard and predictable values can be exploited in lotteries and token distributions; it ranks insecure randomness as SC09. Lotteries, mints and game logic need a randomness source that players cannot predict or influence.[2]
The bottom line
If you write contracts, the cheapest wins come first: compile with Solidity 0.8 or later, apply checks-effects-interactions on every function that sends value, and put admin keys behind a multisig with a timelock. Those three steps target integer overflow (SC08), reentrancy (SC05) and access control (SC01) on OWASP's 2025 list before any audit starts. If you only use contracts, revoke approvals you no longer need.[2]
Educational content, not financial advice. Crypto assets are volatile; do your own research.