Common Smart Contract Vulnerabilities and How Professional Audits Catch Them

Common smart contract vulnerabilities and how professional audits catch them

Smart contracts are unforgiving in a way most traditional software isn’t. A bug in a web application can usually be patched within minutes of discovery. A bug in a deployed, immutable smart contract holding user funds can be exploited the moment it’s found — and once funds move, there’s often no way to reverse the transaction. Understanding the vulnerability classes that show up again and again in post-mortems, and how a structured audit process is designed to catch them before mainnet, is essential context for any founder evaluating a development or audit partner.

Reentrancy: The Vulnerability That Started It All

Reentrancy is the vulnerability most associated with smart contract security, largely because of The DAO incident in 2016, where an attacker exploited a reentrancy flaw to drain roughly 3.6 million ETH from the decentralized autonomous organization before the flaw could be patched — an event significant enough that it ultimately led to a contentious hard fork of the Ethereum blockchain itself, splitting it into what became Ethereum and Ethereum Classic.

The vulnerability occurs when a contract makes an external call to another contract or address before updating its own internal state. If that external call is to a malicious contract, the attacker can have it call back into the original function before the first execution finishes, repeating a withdrawal or transfer multiple times before the contract’s balance is updated to reflect the first one.

The standard mitigation, now considered baseline practice, is the checks-effects-interactions pattern: validate all conditions first, update internal state second, and only then make any external call. Professional audits check for this pattern systematically across every function that combines external calls with state changes, and tools like Slither flag violations of this pattern automatically as part of static analysis.

Integer Overflow and Underflow

Before Solidity version 0.8.0, arithmetic operations didn’t automatically check for overflow or underflow — if a uint256 variable at its maximum value had 1 added to it, it would silently wrap around to zero, and the reverse was true for underflow. This class of bug was behind several early token-related exploits, where attackers manipulated balance calculations to mint or claim far more tokens than intended.

Modern Solidity (0.8.0 and later) includes automatic overflow and underflow checks by default, which has significantly reduced the frequency of this specific vulnerability in new code. However, it hasn’t eliminated the risk entirely — contracts that deliberately use unchecked blocks for gas optimization, or projects still running on older Solidity versions for legacy reasons, remain exposed. A thorough audit specifically checks every unchecked block to confirm the arithmetic inside genuinely cannot overflow given the contract’s logic, rather than assuming the developer’s judgment was correct.

Access Control Flaws

This is one of the most common vulnerability classes in practice, and one of the least glamorous — which is exactly why it gets missed. Access control flaws occur when a function that should be restricted to a specific role (owner, admin, a particular multisig) is left publicly callable, or when role-checking logic contains a subtle bug that allows unauthorized addresses through.

Common patterns audits look for include: missing onlyOwner or equivalent modifiers on sensitive functions (minting, pausing, upgrading, withdrawing funds), initialization functions that can be called more than once or by any address rather than being locked to a single authorized caller, and role-based access control systems where role assignment logic itself has a gap allowing privilege escalation. Because these bugs are often a single missing modifier rather than complex logic errors, they’re frequently caught through systematic manual review checklists rather than automated tooling alone — a reminder that static analysis and human review are complementary, not substitutes for each other.

Price Oracle Manipulation

As DeFi protocols increasingly rely on external price feeds to determine collateral values, liquidation thresholds, and swap rates, oracle manipulation has become one of the more sophisticated and costly vulnerability classes. The core issue arises when a contract relies on a price source that can itself be manipulated within a single transaction — for example, pulling a spot price directly from a low-liquidity decentralized exchange pool, which an attacker can temporarily skew using a flash loan before the contract reads the price, then reverse immediately after.

Audits addressing this focus on whether a protocol uses time-weighted average prices (TWAPs) or established oracle networks with manipulation-resistant designs, rather than spot prices from a single low-liquidity source, and whether there are sanity checks or circuit breakers that reject prices that deviate implausibly from recent historical values.

Front-Running and Transaction-Ordering Dependence

Because pending transactions are visible in the mempool before they’re confirmed, contracts whose outcomes depend on transaction order can be exploited by attackers who observe a pending transaction and submit their own with a higher gas price to be processed first. This is particularly relevant for anything involving price-sensitive trades, auctions, or first-come-first-served claims. Audits look for functions where outcome depends on execution order and recommend mitigations like commit-reveal schemes, slippage protection parameters, or batch auction mechanisms depending on the specific use case.

How a Structured Audit Process Actually Catches These

To be precise about terminology: Dapplication Tech is a development firm, not an audit firm. We do not sell third-party audit opinions. What we run before code goes to an independent auditor is a structured internal security review, and it layers several methods rather than relying on any single technique:

Denial-of-Service Through Gas Limit and External Call Failures

A less discussed but still common vulnerability class involves contracts that can be pushed into a state where legitimate users can no longer interact with them. This often happens when a contract loops over an unbounded array — for example, iterating through every registered address to distribute a payout — without any limit on how large that array can grow. As the array grows over time, the gas required to complete the loop eventually exceeds the block gas limit, permanently locking the function and, in the worst cases, any funds tied to it.

A related pattern occurs when a contract’s logic depends on an external call succeeding — such as sending ETH to a recipient as part of a larger function — and a malicious or simply broken recipient contract can cause that call to fail, blocking the entire transaction for every other legitimate participant. Audits specifically check for unbounded loops over dynamically sized arrays and confirm that any function combining a payout to an external address with other critical logic uses a pull-payment pattern — where recipients withdraw funds themselves rather than having them pushed to them — precisely to prevent one bad actor from freezing the system for everyone else.

Why Named Historical Incidents Matter for Audit Design

It’s worth being specific about why incidents like The DAO hack are still referenced in audit methodology nearly a decade later: they’re not historical trivia, they’re the reason certain checks exist as standard practice at all. Every major vulnerability class described above has a real, well-documented incident behind it that shaped how the industry tests for it today. An audit process that can point to the specific historical exploit each check is designed to prevent — rather than presenting checks as generic best practice — is demonstrating that its methodology evolved from real, sourced incidents rather than a generic security checklist copied from elsewhere.

No audit process, however rigorous, can offer an absolute guarantee — this is why external, independent audits remain a recommended standard even for teams confident in their internal QA. But a structured process that deliberately targets each of these known vulnerability classes, rather than a general read-through of the code, is what separates a meaningful pre-mainnet audit from a box-checking exercise.

For founders comparing audit providers or evaluating whether their development vendor’s internal QA is sufficient on its own, the practical takeaway is this: ask which of these specific vulnerability classes their process explicitly checks for, and ask them to point to the tooling and methodology behind each check. A vague answer about “thorough testing” is not the same as a process built around the known, documented failure modes that have actually cost the industry billions of dollars to date.

Previous postNext post

Keep your code secure with Dapplication.

Contact us