MrDeFi
Ethereum2026-04-145 min read

Common Smart Contract Vulnerabilities Every Developer Should Know

An overview of reentrancy, integer overflow, and access control bugs — the vulnerability classes behind most smart contract exploits.

Common smart contract vulnerabilities are recurring patterns of coding mistakes — such as reentrancy, arithmetic overflow, and broken access control — that have caused the large majority of historical exploits in Ethereum /glossary/smart-contract code. Understanding these patterns is considered baseline knowledge for anyone writing or auditing Solidity, not an advanced specialty.

Reentrancy

Reentrancy occurs when a contract calls out to an external contract or address before it has finished updating its own internal state, allowing the external call to "call back" into the original function and repeat an action — such as a withdrawal — before the contract has recorded that the first withdrawal already happened. The historical case study for this pattern, and one of Ethereum's most consequential early incidents, is covered in detail in /blog/what-is-reentrancy-attack. The standard defense is following a strict "checks-effects-interactions" pattern: verify conditions, update internal state, and only then make external calls.

Integer overflow and underflow

Older versions of Solidity allowed numeric variables to silently wrap around when a calculation exceeded their maximum or minimum representable value — for example, subtracting 1 from a variable holding zero could wrap around to an enormous number instead of failing, if the variable type didn't support negative values. This class of bug enabled attackers to manufacture token balances or bypass numeric checks. Modern versions of Solidity include automatic overflow and underflow checks by default, substantially reducing (though not eliminating, in code using unchecked blocks for gas savings) the risk of this vulnerability class.

Access control failures

Access control bugs occur when a function that should be restricted — such as one that mints new tokens, withdraws protocol funds, or upgrades contract logic — is missing the check that verifies the caller is actually authorized to invoke it. This can happen through a simple missing modifier, an incorrectly implemented ownership check, or a function accidentally left public when it should have been restricted to an internal role. Because these functions are often the most powerful ones in a contract, access control mistakes are frequently the most financially damaging class of bug when they slip through.

Other recurring vulnerability patterns

  • Front-running / MEV exposure — because pending transactions are visible in the mempool before confirmation, an observer can sometimes insert their own transaction ahead of a victim's to profit from the outcome, a dynamic explored further under /glossary/mev.
  • Oracle manipulation — contracts relying on external price or data feeds can be exploited if the feed itself can be manipulated, especially feeds sourced from a single, thinly traded market, relevant to the concerns in /blog/what-is-an-oracle-ethereum.
  • Unchecked external call return values — failing to verify whether a call to another contract or address actually succeeded can let a contract proceed as if an action completed when it silently failed.
  • Timestamp and block-data dependence — relying on block timestamps or block numbers for critical logic can be risky since these values have some flexibility for the entity that produces the block.

Denial-of-service patterns

Beyond vulnerabilities that let an attacker steal funds directly, some bugs simply make a contract stop functioning correctly for everyone, which can be just as damaging in practice. A common example involves a contract that loops over an unbounded list — say, paying out rewards to every registered participant in a single transaction. If that list grows large enough, the transaction can exceed the block's gas limit entirely and become permanently impossible to execute, effectively locking whatever function depended on that loop. Careful contract design generally avoids unbounded loops over user-controlled data specifically because of this risk.

Why testing alone isn't always enough

Traditional software testing checks that code behaves correctly for the specific scenarios a developer thought to test. Smart contract vulnerabilities frequently live in the scenarios nobody thought to test — an unusual sequence of calls, an edge case in how two different contracts interact, or a value at the extreme boundary of what's technically possible. This is why more rigorous techniques like fuzz testing (feeding a contract large volumes of randomized input) and formal verification (mathematically proving certain properties always hold) have become increasingly standard for high-value contracts, supplementing rather than replacing conventional test suites.

A quick reference table

Vulnerability Root cause Common defense
Reentrancy External call made before internal state update Checks-effects-interactions pattern
Integer overflow/underflow Arithmetic wrapping past a variable's limits Built-in overflow checks in modern Solidity
Access control failure Missing or incorrect permission check Explicit modifiers, thorough test coverage
Oracle manipulation Reliance on a manipulable single data source Using decentralized, aggregated oracle feeds
Front-running/MEV Visibility of pending transactions in the mempool Commit-reveal schemes, private transaction pools

Why these patterns persist despite being well known

Even with these vulnerability classes broadly documented, new exploits continue to happen — often because a novel combination of otherwise-known patterns interacts in an unexpected way, or because a project's specific business logic introduces a variation auditors and automated tools weren't specifically looking for. This is why /blog/how-to-audit-a-smart-contract treats audits as risk reduction rather than a guarantee, and why learning to write contracts in /blog/what-is-solidity-programming-language means learning these patterns as a core part of the language, not an optional add-on.

Practical takeaway for developers

Defensive coding habits — following the checks-effects-interactions pattern by default, using well-tested libraries rather than writing custom implementations of common functionality, keeping access-restricted functions to a minimum, and getting independent audits before handling real funds — address most of these vulnerability classes at the design stage rather than trying to catch them after the fact.

Bottom line

Reentrancy, integer overflow, and access control failures account for a large share of historical smart contract exploits, and each has well-understood, well-documented defenses. Treating these patterns as fundamental knowledge, rather than edge cases, is the single most effective mindset shift for writing safer Ethereum smart contracts.

Related articles

This article is for educational purposes only and is not financial advice. DeFi involves significant risk, including total loss of funds. Always do your own research.