Common Vulnerabilities
Security issues to watch for and avoid
Common smart contract vulnerabilities and how to prevent them.
1. Reentrancy
Problem: External call before state update allows attacker to re-enter function.
// VULNERABLE
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool success, ) = msg.sender.call{value: amount}("");
balances[msg.sender] -= amount; // Too late!
}
// FIXED - Checks-Effects-Interactions
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
balances[msg.sender] -= amount; // Update first
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
// ALTERNATIVE - ReentrancyGuard
modifier nonReentrant() {
require(!locked, "No reentrancy");
locked = true;
_;
locked = false;
}
2. Integer Overflow
Problem: Arithmetic exceeds max/min values.
// Solidity 0.8+ has built-in protection
uint256 a = b + c; // Reverts on overflow
// For custom logic
require(balances[to] + amount >= balances[to], "Overflow");
3. Access Control
Problem: Missing permission checks.
// VULNERABLE
function mint(address to, uint256 amount) external {
balances[to] += amount; // Anyone can mint
}
// FIXED
modifier onlyOwner() {
require(owners[msg.sender], "Not owner");
_;
}
function mint(address to, uint256 amount) external onlyOwner {
balances[to] += amount;
}
4. Timestamp Dependence
Problem: Using block.timestamp for randomness or precise timing.
// VULNERABLE - Predictable
uint256 random = uint256(keccak256(block.timestamp));
// BETTER - Multiple entropy sources
uint256 random = uint256(keccak256(abi.encodePacked(
block.timestamp, block.difficulty, block.gaslimit
)));
5. Unchecked Call Returns
Problem: External call failures go unnoticed.
// VULNERABLE
target.call(data); // May fail silently
// FIXED
(bool success, ) = target.call(data);
require(success, "Call failed");
6. Front-Running
Problem: Attackers see pending transactions and act first.
Prevention: Use commit-reveal schemes, accept higher bids, or add randomized delays.
7. Denial of Service
Problem: Unbounded operations exhaust gas.
// VULNERABLE
for (uint256 i = 0; i < data.length; i++) {
_process(data[i]); // May run out of gas
}
// FIXED - Batched
function processBatch(uint256 start, uint256 end) external {
require(end - start <= 50, "Batch too large");
for (uint256 i = start; i < end; i++) {
_process(data[i]);
}
}
Testing
it("Should prevent reentrancy", async function () {
await attacker.attack();
expect(await vault.balance()).to.equal(initialBalance);
});
it("Should enforce access control", async function () {
await expect(contract.connect(user).adminFunction())
.to.be.revertedWith("Admin only");
});
Resources
- Slither - Static analysis
- Mythril - Fuzzing
- SWC Registry
Next: Best Practices • Audit Checklist
Contribute
Found an issue or want to contribute?
Help us improve this documentation by editing this page on GitHub.
