Common Vulnerabilities
Smart contracts are immutable once deployed, so vulnerabilities can lead to loss of funds or system failure. Understanding common vulnerabilities is crucial for writing secure contracts.
Common vulnerabilities
1) Re-entrancy attack
Definition:
Occurs when a contract calls an external contract, and the external contract re-enters the calling contract before the first execution finishes, potentially draining funds.
Example:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Vulnerable {
mapping(address => uint256) public balances;
function withdraw() public {
uint256 amount = balances[msg.sender];
require(amount > 0, "Insufficient balance");
(bool success, ) = msg.sender.call{value: amount}(""); // Vulnerable
require(success, "Transfer failed");
balances[msg.sender] = 0; // State updated after sending Ether
}
}
Solution:
- Update state before making external calls.
- Use
ReentrancyGuardfrom OpenZeppelin.
balances[msg.sender] = 0;
(bool success, ) = msg.sender.call{value: amount}("");
2) Integer overflow / underflow
Definition:
Occurs when arithmetic exceeds the maximum or minimum value of a variable type.
Example:
uint8 x = 255;
x += 1; // Overflow: x becomes 0
Solution:
- Use Solidity
0.8.x(built-in overflow and underflow checks). - For older versions, use the
SafeMathlibrary from OpenZeppelin.
using SafeMath for uint256;
uint256 result = a.add(b);
3) Front-running
Definition:
An attacker observes pending transactions and submits a transaction with higher gas to execute before the original transaction, exploiting transaction ordering.
Example:
- Bidding or trading contracts are susceptible if the highest bidder is updated after external calls.
Solution:
- Use commit-reveal schemes.
- Avoid trusting transaction order.
4) Denial of service (DoS)
Definition:
Prevents users from executing contract functions by exploiting logic flaws or gas exhaustion.
Example:
- A contract iterates over an array of users; if the array grows too large, the function runs out of gas.
Solution:
- Avoid unbounded loops on storage arrays.
- Use mappings instead of arrays for critical logic.
5) Tx.origin attack
Definition:
Attackers trick users into calling malicious contracts by using tx.origin
for authentication instead of msg.sender.
Example:
if (tx.origin == owner) {
// Grants access, unsafe
}
Solution:
- Always use
msg.senderfor authorization, nevertx.origin.
Best practices for secure coding
- Use the latest Solidity version – includes built-in security checks.
- Mark functions
payablecarefully to avoid accidental Ether transfers. - Use
requireandassertfor validations. - Avoid unbounded loops to prevent gas exhaustion attacks.
- Check external calls carefully, especially
callanddelegatecall. - Restrict access with modifiers such as
onlyOwner. - Use OpenZeppelin libraries for trusted, audited implementations.
- Write unit tests and perform audits before deployment.
Using OpenZeppelin contracts
OpenZeppelin is a widely used library of audited, reusable smart contract components. It provides:
- ERC20, ERC721, and ERC1155 token standards.
- Access control (
Ownable,AccessControl). - Security modules (
ReentrancyGuard,SafeMath). - Upgradeable contract patterns.
Example: Secure ERC20 token
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
contract MyToken is ERC20, Ownable {
constructor() ERC20("MyToken", "MTK") {
_mint(msg.sender, 1000 * 10**18);
}
}
Explanation:
ERC20provides the standard token implementation.Ownableadds ownership-based access control.- Reduces the risk of implementing insecure token logic from scratch.
Summary table
| Vulnerability | Description / Fix |
|---|---|
| Re-entrancy | External calls re-enter contract / Use ReentrancyGuard, update state first |
| Integer overflow / underflow | Arithmetic exceeds type limits / Use Solidity >= 0.8 or SafeMath |
| Front-running | Transaction order exploited / Use commit-reveal, improve logic |
| Denial of service (DoS) | Functions fail due to gas limits / Avoid unbounded loops, use mappings |
| Tx.origin attack | Using tx.origin for auth / Always use msg.sender |
| Best practices | Latest Solidity, modifiers, require/assert, OpenZeppelin |
| OpenZeppelin contracts | Audited libraries for tokens, access control, and security |
Continue Learning
Explore more topics in Solidity or browse other tutorials.