Common smart contract mistakes and how to avoid them
After auditing and building dozens of smart contracts, we've seen the same mistakes come up again and again. Here are the most common ones and how to avoid them.
Reentrancy remains the #1 vulnerability despite being well-known. The fix is simple: follow the checks-effects-interactions pattern and use reentrancy guards for any function that makes external calls. But developers still forget, especially in complex multi-contract systems.
Integer overflow/underflow was a huge issue before Solidity 0.8.0. Now the compiler checks by default, but some developers disable these checks for gas optimization without fully understanding the risks. Unless you're absolutely sure, leave them on.
Access control mistakes are surprisingly common. Forgetting to add `onlyOwner` modifiers, using tx.origin instead of msg.sender, or having broken initialization in upgradeable contracts. Always use OpenZeppelin's access control libraries.
Flash loan attacks exploit the ability to borrow large amounts without collateral within a single transaction. If your protocol relies on spot prices or can be manipulated by large trades, you're vulnerable. Use TWAPs and be very careful with any price-dependent logic.
Finally, storage collisions in upgradeable contracts. When upgrading, you need to be extremely careful about storage layout. Tools like OpenZeppelin's upgrade plugins can help, but this remains a common source of bugs.
Key Takeaways
- Always use checks-effects-interactions and reentrancy guards
- Don't disable overflow checks without deep understanding
- Use established access control libraries
- Protect against flash loan attacks with TWAPs
- Be careful with storage layout in upgradeable contracts