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 = 1and- _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 - nonReentrantmodifier to block recursive calls
- Updates 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)check
- Transaction 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 overflow
- require(a >= b, "underflow")- prevents subtraction underflow
- Checks happen BEFORE arithmetic operations 
 
- Supply Cap Enforcement: - Actually enforces - MAX_SUPPLYlimit
- Prevents 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 failsVerification 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
