slither . --detect reentrancy-eth,reentrancy-no-eth,unprotected-upgrade,arbitrary-send-eth| # | Vulnerability | Severity | SWC ID | Detection | Prevention |
|---|---|---|---|---|---|
| 1 | Reentrancy | Critical | SWC-107 | Slither reentrancy-eth, Mythril | Checks-Effects-Interactions + ReentrancyGuard |
| 2 | Integer Overflow/Underflow | High | SWC-101 | Mythril symbolic execution | Solidity 0.8.x+ (built-in) or SafeMath for <0.8 |
| 3 | Access Control (Unprotected Functions) | Critical | SWC-105 | Slither unprotected-upgrade | OpenZeppelin Ownable/AccessControl + explicit modifiers |
| 4 | Front-Running (TX Order Dependence) | High | SWC-114 | Manual review | Commit-reveal schemes, batch auctions, Flashbots Protect |
| 5 | Oracle Manipulation | Critical | N/A | Manual review, historical analysis | TWAP oracles, Chainlink price feeds, multi-oracle design |
| 6 | Unchecked Return Values | High | SWC-104 | Slither unchecked-lowlevel | Always check return of .call(), .send(), .transfer() |
| 7 | Delegatecall to Untrusted Callee | Critical | SWC-112 | Slither delegatecall-loop | Never delegatecall to user-supplied addresses |
| 8 | tx.origin Authentication | High | SWC-115 | Slither tx-origin | Use msg.sender instead of tx.origin for auth |
| 9 | Flash Loan Attacks | Critical | N/A | Manual review, invariant testing | Same-block price checks, delay mechanisms, access control |
| 10 | Storage Collision (Proxy Patterns) | Critical | N/A | Slither uninitialized-storage | EIP-1967 storage slots, EIP-7201 namespaced storage |
| 11 | DoS with Failed Call | Medium | SWC-113 | Slither calls-loop | Pull-over-push payment pattern |
| 12 | Timestamp Dependence | Low | SWC-116 | Mythril | Avoid block.timestamp for critical logic; 15s tolerance |
START
|-- Is the contract upgradeable (proxy pattern)?
| |-- YES --> Check for storage collision (EIP-1967/7201), uninitialized proxy,
| | selfdestruct in implementation, and delegatecall safety. See #7, #10.
| +-- NO |
|-- Does the contract handle ETH or token transfers?
| |-- YES --> Check for reentrancy (#1), unchecked returns (#6), flash loan vectors (#9).
| | Apply Checks-Effects-Interactions + ReentrancyGuard.
| +-- NO |
|-- Does the contract use external price data?
| |-- YES --> Check for oracle manipulation (#5) and front-running (#4).
| | Verify TWAP window, Chainlink heartbeat, multi-oracle fallback.
| +-- NO |
|-- Does the contract have admin/owner functions?
| |-- YES --> Check for access control (#3), centralization risks, privilege escalation.
| | Verify role separation, timelocks, multi-sig requirements.
| +-- NO |
+-- DEFAULT --> Run full Slither + Mythril scan, review all 12 vulnerability classes above.
Clone the repository, install dependencies, and verify the project compiles cleanly. [src4]
# Clone and set up
git clone <target-repo>
cd <target-repo>
# Install dependencies (Hardhat or Foundry)
npm install # for Hardhat projects
# or
forge install # for Foundry projects
# Compile to verify no errors
npx hardhat compile # Hardhat
# or
forge build # Foundry
Verify: Compilation succeeds with zero errors and zero warnings.
Slither detects 90+ vulnerability patterns in seconds. Run it first to catch low-hanging fruit. [src4]
# Install Slither
pip install slither-analyzer
# Run full analysis
slither . --json slither-report.json
# Run targeted high-severity detectors
slither . --detect reentrancy-eth,reentrancy-no-eth,arbitrary-send-eth,\
unprotected-upgrade,suicidal,controlled-delegatecall,tx-origin
Verify: Review all High and Medium findings in slither-report.json.
Mythril explores execution paths to find vulnerabilities Slither misses (e.g., complex integer issues, path-dependent bugs). [src7]
# Install Mythril
pip install mythril
# Analyze a single contract
myth analyze contracts/Vault.sol --solv 0.8.20
# Deep analysis (more execution depth, slower)
myth analyze contracts/Vault.sol --execution-timeout 300 --max-depth 50
Verify: Review report for any SWC-101, SWC-107, or SWC-104 issues.
Property-based fuzzing discovers edge cases that static analysis cannot. Define invariants and let the fuzzer try to break them. [src5]
# Foundry fuzzing (built-in)
forge test --fuzz-runs 10000
# Echidna (Trail of Bits property-based fuzzer)
echidna . --contract TestVault --config echidna.yaml
Verify: forge test -vvv -- all fuzz tests pass with 10,000+ runs.
Systematic line-by-line review focusing on business logic, which tools cannot fully assess. [src2]
Key checklist items: all external/public functions have access control, state changes happen before external calls, no unbounded loops, all unchecked blocks are intentionally safe, proxy storage layout matches, events emitted for state changes.
Verify: Document each finding with severity, location, and recommended fix.
Consolidate all findings into a structured report with severity classifications. [src5]
Verify: Every Critical and High finding has a recommended fix and has been discussed with the development team.
// VULNERABLE: State update AFTER external call (SWC-107)
contract VulnerableVault {
mapping(address => uint256) public balances;
function withdraw() external {
uint256 amount = balances[msg.sender];
(bool success, ) = msg.sender.call{value: amount}("");
require(success, "Transfer failed");
balances[msg.sender] = 0; // Too late -- attacker re-enters
}
}
// SECURE: Checks-Effects-Interactions + ReentrancyGuard
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
contract SecureVault is ReentrancyGuard {
mapping(address => uint256) public balances;
function withdraw() external nonReentrant {
uint256 amount = balances[msg.sender];
require(amount > 0, "No balance");
balances[msg.sender] = 0; // EFFECT first
(bool success, ) = msg.sender.call{value: amount}(""); // INTERACTION last
require(success, "Transfer failed");
}
}
// VULNERABLE: No access control (SWC-105) -- anyone can mint
contract VulnerableToken {
function mint(address to, uint256 amount) external {
_mint(to, amount);
}
}
// SECURE: Role-based access with OpenZeppelin
import "@openzeppelin/contracts/access/AccessControl.sol";
contract SecureToken is AccessControl {
bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor() { _grantRole(DEFAULT_ADMIN_ROLE, msg.sender); }
function mint(address to, uint256 amount) external onlyRole(MINTER_ROLE) {
_mint(to, amount);
}
}
// VULNERABLE: tx.origin phishing (SWC-115)
require(tx.origin == owner, "Not owner");
// An attacker contract tricks owner into calling it,
// then calls this function -- tx.origin is still the owner
// SECURE: msg.sender cannot be spoofed
require(msg.sender == owner, "Not owner");
// BAD -- SWC-104: ignoring return value of .call()
function sendEth(address to, uint256 amount) external {
to.call{value: amount}("");
// If the call fails silently, ETH is lost
}
// GOOD -- check return and revert on failure
function sendEth(address to, uint256 amount) external {
(bool success, ) = to.call{value: amount}("");
require(success, "ETH transfer failed");
}
// BAD -- state update after external call
function withdraw(uint256 amount) external {
require(balances[msg.sender] >= amount);
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok);
balances[msg.sender] -= amount; // too late!
}
// GOOD -- state update before external call + reentrancy guard
function withdraw(uint256 amount) external nonReentrant {
require(balances[msg.sender] >= amount, "Insufficient");
balances[msg.sender] -= amount; // effect first
(bool ok, ) = msg.sender.call{value: amount}("");
require(ok, "Transfer failed");
}
// BAD -- SWC-113: array grows unbounded, eventually hits gas limit
function distributeRewards() external {
for (uint i = 0; i < recipients.length; i++) {
payable(recipients[i]).transfer(rewards[i]);
}
}
// GOOD -- each user withdraws their own rewards
mapping(address => uint256) public pendingRewards;
function claimReward() external {
uint256 reward = pendingRewards[msg.sender];
require(reward > 0, "No reward");
pendingRewards[msg.sender] = 0;
(bool ok, ) = msg.sender.call{value: reward}("");
require(ok, "Transfer failed");
}
// BAD -- SWC-115: vulnerable to phishing via relay contract
require(tx.origin == owner, "Not authorized");
// GOOD -- msg.sender cannot be spoofed by intermediate contracts
require(msg.sender == owner, "Not authorized");
unchecked { } blocks bypasses overflow protection. Fix: grep for unchecked and verify each usage is safe. [src6]msg.value does not decrease per iteration -- the same ETH can be "spent" multiple times. Fix: track remaining value in a local variable and decrement it. [src2]increaseAllowance/decreaseAllowance or approve to 0 first (required for USDT). [src5]@openzeppelin/upgrades plugin for automatic layout checks. [src3]pragma solidity ^0.8.0 means different auditors may compile with different versions. Fix: pin to exact version like pragma solidity 0.8.20;. [src6]forge test --fork-url to test against production state. [src4]# Run Slither static analysis (all detectors)
slither . --json report.json
# Run Slither with specific high-severity detectors only
slither . --detect reentrancy-eth,reentrancy-no-eth,arbitrary-send-eth,controlled-delegatecall,unprotected-upgrade
# List all Slither detectors and their severity
slither . --list-detectors
# Run Mythril on a specific contract
myth analyze contracts/MyContract.sol --solv 0.8.20
# Run Mythril on deployed contract (requires RPC)
myth analyze --address 0xCONTRACT --rpc https://eth-mainnet.g.alchemy.com/v2/KEY
# Run Foundry fuzz tests (10k runs)
forge test --fuzz-runs 10000 -vvv
# Run Echidna property-based fuzzer
echidna . --contract MyContractTest --config echidna.yaml --test-mode assertion
# Verify contract source matches deployed bytecode
forge verify-contract --chain-id 1 --compiler-version 0.8.20 0xADDRESS contracts/MyContract.sol:MyContract
# Generate storage layout diff for proxy upgrades
forge inspect MyContractV1 storage-layout > v1.json
forge inspect MyContractV2 storage-layout > v2.json
diff v1.json v2.json
| Solidity Version | Status | Key Security Changes | Migration Notes |
|---|---|---|---|
| 0.8.20+ | Current | Custom errors (gas savings), transient storage (EIP-1153) | Pin exact version; verify optimizer settings match audit |
| 0.8.0-0.8.19 | Supported | Built-in overflow/underflow checks, ABI coder v2 default | Remove SafeMath imports; review unchecked blocks |
| 0.7.x | Legacy | Last version requiring SafeMath | Upgrade to 0.8.x; remove SafeMath; test all arithmetic |
| 0.6.x | EOL | No overflow protection, different ABI encoding | Must upgrade; extensive re-audit required |
| 0.4.x-0.5.x | EOL | Many known vulnerabilities, no built-in protections | Full rewrite recommended |
| Use When | Don't Use When | Use Instead |
|---|---|---|
| Deploying contracts that handle user funds (DeFi, vaults, bridges) | Building a read-only contract with no value transfer | Basic unit testing for view-only contracts |
| Launching a token (ERC-20, ERC-721, ERC-1155) | Writing a private/internal test contract on a devnet | Foundry unit tests for development testing |
| Implementing upgradeable proxy patterns | Contract is a simple wrapper around a well-audited library | Code review of integration points only |
| Building cross-chain bridges or oracle integrations | Smart contract is on a non-EVM chain (Solana, Aptos) | Chain-specific audit tools (Anchor, Move Prover) |
| Post-hack incident response and forensic analysis | You need a gas optimization review only | Gas profiling tools (forge snapshot, hardhat-gas-reporter) |