## Contents - 2. Vulnerability Checklist - 2.1 Reentrancy - 2.2 Oracle Manipulation / Price Feed Attacks - 2.3 Flash Loan Attack Vectors - 2.4 Storage Collisions in Proxies - 2.5 Front-Running / Sandwich Attacks / MEV - 2.6 Access Control Issues - 2.7 Integer Overflow/Underflow - 2.8 Unchecked External Calls - 2.9 Denial of Service Patterns ## 2. Vulnerability Checklist ### 2.1 Reentrancy **Vulnerable:** ```solidity function withdraw(uint256 amount) external { require(balances[msg.sender] >= amount); (bool ok, ) = msg.sender.call{value: amount}(""); require(ok); balances[msg.sender] -= amount; // STATE AFTER CALL — reentrancy } ``` **Fixed (CEI Pattern):** ```solidity function withdraw(uint256 amount) external nonReentrant { require(balances[msg.sender] >= amount); balances[msg.sender] -= amount; // EFFECTS first (bool ok, ) = msg.sender.call{value: amount}(""); // INTERACTION last require(ok); } ``` Cross-function reentrancy: check if any two functions share state and one has an external call before state update. ### 2.2 Oracle Manipulation / Price Feed Attacks **Vulnerable (spot price):** ```solidity function getPrice() public view returns (uint256) { (uint112 r0, uint112 r1, ) = pair.getReserves(); return (uint256(r1) * 1e18) / uint256(r0); // manipulable in same tx } ``` **Fixed (Chainlink + staleness check):** ```solidity function getPrice() public view returns (uint256) { (, int256 answer, , uint256 updatedAt, ) = priceFeed.latestRoundData(); require(answer > 0, "invalid price"); require(block.timestamp - updatedAt < 3600, "stale price"); return uint256(answer); } ``` Also consider TWAP for on-chain pricing: ```solidity // Uniswap V3 TWAP — use OracleLibrary.consult(pool, twapInterval) ``` ### 2.3 Flash Loan Attack Vectors Audit checks: - Can any single-tx deposit + action + withdraw exploit state? - Are governance votes protected by minimum holding periods? - Are liquidity-based calculations snapshottable in one block? **Guard pattern:** ```solidity mapping(address => uint256) public lastDepositBlock; function deposit() external { lastDepositBlock[msg.sender] = block.number; // ... } function vote() external { require(block.number > lastDepositBlock[msg.sender], "same block"); // ... } ``` ### 2.4 Storage Collisions in Proxies **Problem:** Proxy and implementation share storage. Misaligned slots corrupt data. ```solidity // Implementation V1 contract V1 { uint256 public value; // slot 0 address public owner; // slot 1 } // Implementation V2 — WRONG: inserted variable shifts slots contract V2 { uint256 public value; // slot 0 uint256 public newVar; // slot 1 — COLLISION with owner! address public owner; // slot 2 } // Implementation V2 — CORRECT: append only contract V2 { uint256 public value; // slot 0 address public owner; // slot 1 uint256 public newVar; // slot 2 — safe, appended } ``` Use `forge inspect ContractName storage-layout` to verify slot alignment between versions. ### 2.5 Front-Running / Sandwich Attacks / MEV **Vulnerable swap:** ```solidity function swap(uint256 amountIn) external { router.swapExactTokensForTokens(amountIn, 0, path, msg.sender, block.timestamp); // amountOutMin = 0 allows sandwich } ``` **Fixed:** ```solidity function swap(uint256 amountIn, uint256 minOut, uint256 deadline) external { require(block.timestamp <= deadline, "expired"); router.swapExactTokensForTokens(amountIn, minOut, path, msg.sender, deadline); } ``` For sensitive operations, use commit-reveal: ```solidity mapping(bytes32 => uint256) public commits; function commit(bytes32 hash) external { commits[hash] = block.number; } function reveal(uint256 value, bytes32 salt) external { bytes32 h = keccak256(abi.encodePacked(value, salt, msg.sender)); require(commits[h] > 0 && block.number > commits[h] + 1, "too early"); delete commits[h]; _execute(value); } ``` ### 2.6 Access Control Issues **Vulnerable (tx.origin):** ```solidity function withdraw() external { require(tx.origin == owner); // phishing attack via malicious contract } ``` **Fixed:** ```solidity function withdraw() external { require(msg.sender == owner); // or use OpenZeppelin Ownable/AccessControl } ``` Check for: - Missing access modifiers on admin functions - Single-step ownership transfer (use Ownable2Step) - DEFAULT_ADMIN_ROLE granted too broadly - Functions that should be `onlyOwner` but are `public` ### 2.7 Integer Overflow/Underflow **Pre-0.8.0 (vulnerable):** ```solidity // Solidity <0.8.0 uint8 balance = 255; balance += 1; // wraps to 0 silently // Fix: use SafeMath balance = balance.add(1); // reverts on overflow ``` **Post-0.8.0:** Built-in overflow checks. But `unchecked {}` blocks bypass them: ```solidity unchecked { uint8 x = 255; x += 1; // wraps to 0 — intentional? Audit this. } ``` Audit every `unchecked` block. Verify the math genuinely cannot overflow. ### 2.8 Unchecked External Calls **Vulnerable:** ```solidity payable(to).send(amount); // return value ignored — funds may not arrive token.transfer(to, amount); // non-standard tokens may return false ``` **Fixed:** ```solidity (bool ok, ) = payable(to).call{value: amount}(""); require(ok, "ETH transfer failed"); // For ERC20: SafeERC20.safeTransfer(token, to, amount); ``` Also check: `delegatecall` return values, low-level `call` without length check. ### 2.9 Denial of Service Patterns **Unbounded loop (gas griefing):** ```solidity // VULNERABLE: attacker adds thousands of entries function distributeRewards() external { for (uint i = 0; i < recipients.length; i++) { token.transfer(recipients[i], rewards[i]); // OOG if array is huge } } ``` **Fixed (pull pattern):** ```solidity mapping(address => uint256) public pendingRewards; function claimReward() external { uint256 amount = pendingRewards[msg.sender]; pendingRewards[msg.sender] = 0; token.safeTransfer(msg.sender, amount); } ``` Other DoS vectors: - External call in loop (one revert blocks all) - Block gas limit reached via large array iteration - Griefing via forced revert in `receive()` / `fallback()` ---