Smart contract security is the discipline of finding and closing the gap between what a contract's code actually does and what its authors believe it does, since deployed Solidity code is public, immutable, and directly controls real money with no customer-support line to call after a mistake. It matters because a single missed check can drain a protocol in one transaction, and OWASP's own incident data shows losses concentrated in a handful of recurring patterns rather than exotic one-off bugs. The mental model worth keeping throughout: most catastrophic exploits are not clever cryptographic breaks but ordinary logic errors amplified by composability and atomicity β a flash loan, a reentrant call, or a manipulated price feed simply lets an attacker execute that ordinary mistake at unlimited scale inside a single block, so auditing is as much about tracing money and control flow across a whole system as it is about spotting individual unsafe lines.
What This Cheat Sheet Covers
This topic spans 15 focused tables and 83 indexed concepts. Below is a complete table-by-table outline of this topic, spanning foundational concepts through advanced details.
A jump-to index of every table row in this cheat sheet.
An interactive map of every table and concept in this topic.
Table 1: Core State and Arithmetic Vulnerabilities
These are the vulnerability classes every Solidity developer meets first, and they still top the incident data: an external call handing control to another contract before your own state settles, or arithmetic that silently wraps around its type's bounds.
| Vulnerability | Example | Description |
|---|---|---|
(bool ok,) = msg.sender.call{value: bal}("");if (ok) shares[msg.sender] = 0; | β’ External call is made before the internal balance is zeroed β’ A malicious recipient's fallback can re-enter and withdraw repeatedly before state updates | |
uint8 x = 255;x = x + 1; // wraps to 0 in an unchecked block | Arithmetic exceeding a type's fixed range; Solidity β₯0.8.0 reverts on overflow by default, but an explicit unchecked { ... } block silently re-enables wrapping. | |
require(tx.origin == owner); | Using tx.origin instead of msg.sender for authorization lets a malicious intermediate contract that the owner interacts with impersonate them; always authorize on msg.sender. |