Security is not an afterthought in blockchain development. We have audited more than 30 smart contract codebases, and the same vulnerabilities appear repeatedly. Here are the five that cause the most damage — and how to prevent each.
Why smart contracts are unforgiving
In most software, a bug is fixed with a patch. A deployed smart contract usually cannot be changed, it holds real funds, and its code is public for anyone to study. An attacker needs to find one flaw; you need to have found all of them before launch. That is why review before deployment matters more here than almost anywhere else in software.
1. Reentrancy
An external call lets an attacker re-enter your function before state updates finish. The fix is the checks-effects-interactions pattern and reentrancy guards.
In plain terms: update your own records first, then send funds or call another contract. If the balance is reduced before the transfer happens, re-entering the function finds nothing left to take.
2. Integer overflow and underflow
Arithmetic that wraps around silently corrupts balances. Modern Solidity checks this by default, but custom unchecked blocks reintroduce the risk.
Unchecked blocks are usually added to save gas. Each one deserves a written justification for why the value can never go out of range, and a test that proves it.
3. Access control failures
Missing or wrong permission checks let anyone call privileged functions. Enforce role-based access and test every path.
Pay special attention to initialisation and upgrade functions. A setup function that anyone can call, or call twice, hands over the contract. Privileged keys should sit in a multi-signature wallet, not with one person.
4. Front-running
Public mempools let attackers order transactions to their advantage. Commit-reveal schemes and slippage limits reduce exposure.
Assume that every pending transaction is visible to others before it is confirmed. Any function where being first is profitable, such as trades, auctions or claims, needs a design that does not reward it.
5. Denial of service
Unbounded loops and reliance on external calls can freeze a contract. Design for gas limits and failure isolation.
A loop over a list that users can grow will eventually cost more gas than a block allows. Let users withdraw their own funds individually instead of paying everyone in one transaction, so one failing recipient cannot block the rest.
What a good audit covers
- Manual review by people who understand the business logic, not only the code.
- Automated analysis to catch known patterns quickly.
- Tests that attack the contract, including fuzzing with unexpected inputs.
- A written report with each finding ranked by severity, and a re-check after fixes.
An audit reduces risk; it does not remove it. Treat it as one layer alongside careful design and thorough testing.
Before you send code to an auditor
- Freeze the code. Auditing a moving target wastes the review.
- Write down what the contract is supposed to do, including who may do what.
- Reach high test coverage yourself, so the audit is spent on hard problems.
- Use well-reviewed libraries for standard parts instead of writing your own.
After launch
Monitor the contract for unusual activity, have a tested plan for pausing it if something goes wrong, and consider a bug bounty so that people who find flaws have a reason to tell you first.
An audit is cheaper than an exploit. See our blockchain development services, review the technologies we build with, or book a security review with our team.