Introduction
Smart contracts are self-executing programs that run on blockchain networks like Ethereum. They handle valuable assets such as cryptocurrency, tokens, and digital ownership. Because real money is involved, security becomes extremely important.
One of the most common and dangerous vulnerabilities in Solidity smart contracts is the reentrancy attack. If not handled properly, attackers can repeatedly call a function and drain funds from your contract.
In this article, we will understand what reentrancy attacks are, how they work, and most importantly, how to write secure Solidity smart contracts to prevent them.
What is a Reentrancy Attack?
A reentrancy attack happens when a contract sends funds to an external address before updating its internal state. The external contract can call back into the original function before the state is updated.
This allows the attacker to withdraw funds multiple times.
How It Works (Step-by-Step)
User requests withdrawal
Contract sends Ether to attacker
Attacker's fallback function triggers
It calls withdraw() again before balance updates
Funds are drained repeatedly
Vulnerable Smart Contract Example
pragma solidity ^0.8.0;
contract Vulnerable {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint amount = balances[msg.sender];
require(amount > 0);
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
balances[msg.sender] = 0;
}
}
Problem in the Code
Ether is sent before updating balance
External call allows reentry
No protection mechanism
Secure Smart Contract (Fixed Version)
pragma solidity ^0.8.0;
contract Secure {
mapping(address => uint) public balances;
function deposit() public payable {
balances[msg.sender] += msg.value;
}
function withdraw() public {
uint amount = balances[msg.sender];
require(amount > 0);
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
require(success);
}
}
Fix Applied
State updated before external call
Prevents repeated withdrawals
Key Techniques to Prevent Reentrancy Attacks
1. Checks-Effects-Interactions Pattern
This is the most important rule in Solidity security.
Steps:
Check conditions (require)
Update state
Interact with external contracts
Example:
balances[msg.sender] = 0;
msg.sender.call{value: amount}("");
2. Use Reentrancy Guard (Mutex Lock)
You can use a lock to prevent multiple calls.
bool private locked;
modifier noReentrant() {
require(!locked, "No reentrancy");
locked = true;
_;
locked = false;
}
Use it:
function withdraw() public noReentrant {
// secure logic
}
3. Use OpenZeppelin ReentrancyGuard
Instead of writing your own logic, use trusted libraries.
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract Safe is ReentrancyGuard {
function withdraw() public nonReentrant {
// safe code
}
}
Join the conversation! Your thoughts help the community grow.