Smart Contract Security 101
Smart contract vulnerabilities can lead to devastating financial losses and compromise user trust. This guide examines three critical vulnerability categories with hands-on examples that demonstrate both vulnerable patterns and secure implementations.
Learn to identify and prevent the most critical security vulnerabilities in smart contracts through practical examples. This section covers real attack vectors with vulnerable and secure code implementations, along with comprehensive prevention strategies.
What you'll achieve: Recognize vulnerable code patterns, understand attack mechanisms, and learn to implement secure alternatives.
Prerequisites
✅ Required:
Understanding of Solidity function execution
Basic knowledge of EVM call mechanics
✅ Recommended:
Access to Remix IDE for testing examples
Somnia Testnet setup for deployment testing
Vulnerability Overview
The following table categorizes vulnerabilities by severity and implementation difficulty:
Reentrancy
Critical
High
Medium
Very High
Access Control
Critical
Medium
Low
High
Integer Overflow
High
Low
Low
Medium
1. Reentrancy Vulnerabilities
Understanding the Attack
Reentrancy occurs when a contract calls an external contract before updating its internal state, allowing the external contract to call back and exploit the inconsistent state.
Vulnerable Implementation Analysis
Why This Contract Is Vulnerable:
Interaction (external call) occurs before updating internal state.
No reentrancy guard or checks-effects-interactions.
Attackers can re-enter withdraw() during the external call and drain funds.
Attack Mechanism
Attack contract & setup
Attacker contract:
contract Attacker {
ReentrancyVulnerable target;
uint256 public constant ATTACK_AMOUNT = 1 ether;
constructor(address _target) {
target = ReentrancyVulnerable(_target);
}
function attack() external payable {
require(msg.value >= ATTACK_AMOUNT, "need at least 1 ETH");
target.deposit{value: ATTACK_AMOUNT}();
target.withdraw(ATTACK_AMOUNT);
}
receive() external payable {
if (address(target).balance >= ATTACK_AMOUNT) {
target.withdraw(ATTACK_AMOUNT);
}
}
}
Initial state example:
Vulnerable contract has 10 ETH from other users
Attacker deposits 1 ETH
Secure Implementation
This secure contract uses a reentrancy guard to prevent recursive calls:
Key Components:
Reentrancy Guard Variables:
_NOT_ENTERED = 1
and_ENTERED = 2
: Lock states_status
: Tracks if function is currently executing
nonReentrant Modifier:
Checks if function is already running (
_status == _NOT_ENTERED
)Sets lock before execution (
_status = _ENTERED
)Releases lock after completion (
_status = _NOT_ENTERED
)
Secure withdraw() Function:
Uses
nonReentrant
modifier to block recursive callsUpdates balance BEFORE making external call (checks-effects-interactions pattern)
External call cannot trigger another withdrawal due to the guard
How It Prevents Attacks:
First call sets
_status = _ENTERED
Any reentrant call fails the
require(_status == _NOT_ENTERED)
checkTransaction reverts with "reentrant" error
Only one withdrawal per transaction is possible
Security Features:
Reentrancy protection through state locking
State updates before external calls
Automatic revert on attack attempts
Security fixes:
Checks-Effects-Interactions: state updated before external calls.
Reentrancy guard (mutex) via nonReentrant modifier.
Double protection: both CEI and guard prevent recursive drains.
Prevention Strategies (summary)
CEI Pattern
Update state before external calls
High
Low
Reentrancy Guard
Mutex-style protection
Very High
Medium
Pull Payment
Users withdraw instead of push
High
Low
OpenZeppelin ReentrancyGuard
Battle-tested implementation
Very High
Medium
2. Access Control Vulnerabilities
Understanding the Flaw
Poor access control allows unauthorized users to execute privileged functions, leading to complete contract compromise.
Vulnerable Implementation Analysis
Critical issues:
Any role-holder can grant the same role to others → role escalation.
No owner/admin separation.
No events emitted for role changes (no audit trail).
Attack Mechanism
Secure Implementation
This secure contract implements proper role hierarchy:
Key Security Features:
Immutable Owner:
Owner address cannot be changed after deployment
Only owner can grant/revoke ADMIN roles
Role Hierarchy:
Owner > Admin > Writer
Only ADMIN can grant WRITER roles
WRITER cannot grant any roles
Secure Role Management:
Different permissions for granting ADMIN vs other roles
Input validation prevents zero address assignments
Events log all role changes for audit trail
How it prevents attacks:
WRITER cannot escalate to ADMIN (only owner can grant ADMIN)
Clear separation of permissions
Complete audit trail of all role changes
How Security Works:
Attack Prevention:
Alice (WRITER) tries to grant ADMIN to Bob → Fails (only owner can grant ADMIN)
Bob tries to grant himself ADMIN → Fails (only owner can grant ADMIN)
Role escalation is impossible
Legitimate Operations:
Owner can grant ADMIN roles
ADMIN can grant WRITER roles
All changes are logged with events
Result:
Clear hierarchy prevents unauthorized escalation
Complete audit trail of all role changes
Attack is blocked by proper permission checks
Best Practices (summary)
Role Hierarchy
Prevent privilege escalation
Owner > Admin > User
Immutable Owner
Prevent ownership takeover
Set in constructor
Event Logging
Enable audit trails
Emit on all role changes
Zero Address Check
Prevent accidental locks
Validate addresses
3. Integer Overflow/Underflow
Understanding the Issue
Integer overflow/underflow occurs when arithmetic operations exceed the maximum or minimum values for the data type, potentially causing unexpected behavior or security vulnerabilities in financial calculations.
Vulnerable Implementation Analysis
Critical vulnerabilities:
Unchecked addition (overflow) on balances and totalSupply.
Unchecked subtraction (underflow) in transfer.
Batch operations amplify overflow risks.
No access control on minting.
Attack Mechanisms
Secure Implementation
This secure contract prevents overflow/underflow attacks:
Key Security Features:
Overflow/Underflow Checks:
require(a + b >= a, "overflow")
- detects addition overflowrequire(a >= b, "underflow")
- prevents subtraction underflowChecks happen BEFORE arithmetic operations
Supply Cap Enforcement:
Actually enforces
MAX_SUPPLY
limitPrevents unlimited token creation
Access Control:
Only owner can mint tokens
Prevents unauthorized token creation
Input Validation:
Checks for zero addresses and amounts
Batch size limits prevent gas attacks
How it prevents attacks:
All arithmetic operations are validated before execution
Supply limits prevent economic manipulation
Access controls prevent unauthorized minting
Batch limits prevent gas-based attacks
How Security Works:
Attack Prevention:
Overflow/underflow checks prevent arithmetic attacks
Supply cap enforcement prevents unlimited token creation
Access control restricts minting to owner only
Input validation prevents edge cases
Result:
All arithmetic operations are safe
Economic model is protected
Attacks are blocked before execution
Protection Methods (summary)
Built-in Protection
0.8+
Very High
Low
SafeMath Library
<0.8
High
Medium
Manual Checks
Any
High
Low
Unchecked Blocks
0.8+ (when safe)
N/A
Very Low
Prevention Strategies (Consolidated)
Reentrancy Prevention
Primary: Checks-Effects-Interactions pattern
Secondary: Reentrancy guards (e.g., OpenZeppelin ReentrancyGuard)
Additional: Pull payments, limit gas forwarded, prefer transfer() for simple ETH sends
Access Control Best Practices
Use battle-tested libraries: OpenZeppelin AccessControl, Ownable
Implement clear role hierarchy and immutable owner
Emit events for role changes and use multisig for critical roles
Integer Overflow Protection
Use Solidity 0.8+ (automatic checks)
Add explicit pre-operation checks where appropriate
Enforce supply caps and input limits
For legacy code, use SafeMath
General Principles
Defense in depth: stack protections
Fail securely: default to safe state on errors
Principle of least privilege: minimal necessary permissions
Thorough testing and professional audits before mainnet deployment
Testing Vulnerabilities
Use these commands and steps to test examples in Remix (manual steps):
# Deploy vulnerable contract in Remix
# Deploy attacker contract and execute attack
# Observe behavior and balances
# Deploy secure version and verify attack fails
Verification steps:
Deploy vulnerable contract on testnet (Somnia Testnet recommended).
Attempt exploit using attacker contract.
Observe vulnerability in action.
Deploy secure version with protections.
Verify exploit fails against secure implementation.
You can successfully identify and exploit vulnerabilities in a controlled test environment and verify mitigations on secure contracts.
Common Vulnerability Patterns
Red flags in code review:
External calls before state updates
Missing access control modifiers
Unchecked arithmetic operations
Missing input validation
No event emissions for critical actions
Hardcoded addresses or values
Complex inheritance hierarchies
Missing reentrancy protection
Security scanning tools:
Slither
Static Analysis
High
Free
MythX
Comprehensive
Very High
Paid
Securify
Academic
Medium
Free
Manticore
Symbolic Execution
High
Free
Additional Resources
SWC Registry - Smart Contract Weakness Classification
✅ Verification: You can identify vulnerable patterns and understand how attacks work.
🎉 Congratulations! You've mastered the most critical smart contract vulnerabilities, prevention strategies and secure coding patterns.
Last updated