web3.path

PHASE 10 Security · ~5 hours

Smart Contract Security

In web2, a bug means you push a hotfix. In web3, a bug means your $50M gets drained in a 15s mempool race. Security isn't a stage — it's the core of the job.

Goal — recognize the top 10 on-chain vulnerability classes, use Slither + fuzzing, and develop a reflex for safe patterns.

1. Reentrancy — the original sin

A contract calls out to an external address before updating its own state. The external contract (attacker) calls back in and drains.

// BAD
function withdraw() external {
    uint256 bal = balances[msg.sender];
    (bool ok,) = msg.sender.call{value: bal}("");    // ← hands control to attacker
    require(ok);
    balances[msg.sender] = 0;                        // ← too late
}

Fix — checks-effects-interactions:

function withdraw() external {
    uint256 bal = balances[msg.sender];
    balances[msg.sender] = 0;                        // effect first
    (bool ok,) = msg.sender.call{value: bal}("");    // interaction last
    require(ok);
}

Or use a ReentrancyGuard modifier (OpenZeppelin).

Analogy — ATM withdraws cash but debits your account only after you take the cash. You stand there, grab, press retry before the screen refreshes → infinite money. CEI is "debit first, dispense second".

2. Integer overflow/underflow

Solidity ≥0.8 reverts on overflow by default. Pre-0.8 contracts used SafeMath. Still bite you in unchecked { } blocks and in assembly.

3. Access control

// BAD: anyone can withdraw
function rescue(uint256 amt) external { payable(msg.sender).transfer(amt); }
// GOOD
function rescue(uint256 amt) external onlyOwner { ... }

Every state-mutating function needs an explicit answer to "who can call this?" — default to locked-down.

4. tx.origin for auth — don't

// BAD
require(tx.origin == owner, "not owner");

If owner calls a malicious contract that calls your contract, tx.origin is still owner → attacker wins. Use msg.sender.

5. Front-running & MEV

The mempool is public. Anyone can see your pending tx and pay more gas to execute first.

Analogy — it's HFT, but the exchange intentionally broadcasts your order before matching it, and anyone with a modem can jump the queue.

6. Price-oracle manipulation

Flash-loan + manipulate a low-liquidity DEX price + call your lending contract that uses that DEX as an oracle. $100M gone in 1 tx. Don't use a single DEX spot price as an oracle. Use Chainlink or TWAPs (time-weighted average prices).

7. Delegatecall + storage collision

contract Proxy {
    address implementation;                          // slot 0
    fallback() external { implementation.delegatecall(msg.data); }
}
contract Logic {
    uint256 count;                                   // ← slot 0 collides with implementation!
    function bump() external { count++; }            // overwrites Proxy's impl → total hijack
}

Fix: use EIP-1967 — store implementation at keccak256("eip1967.proxy.implementation") - 1. OpenZeppelin's upgradeable contracts do this correctly.

8. Signature replay

Users sign a message; attacker replays it on another chain (same chainId not included) or after the nonce was reused. Always include: chainId, contract address, nonce, expiry in the signed payload. EIP-712 does this for you.

9. Denial of Service via unbounded loops

// Iterating a user-controlled array — attacker adds 10,000 entries
for (uint i = 0; i < holders.length; i++) pay(holders[i]);

Fix with a pull pattern: let each user claim themselves.

10. Other classics

11. Tooling

ToolWhat it finds
Slither (Trail of Bits)Static analysis; top 40 patterns. Run in CI.
Mythril / MythXSymbolic execution, deeper but slower
Foundry fuzz / forge test --fuzz-runs 10000Property tests with random inputs
EchidnaInvariant fuzzing — "assert balance >= 0 always"
CertoraFormal verification (expensive, SaaS)

12. A Slither run, in 30 seconds

pip install slither-analyzer
slither contracts/MyToken.sol
# or with hardhat
slither .

It will complain about a lot. Most findings are "informational" — read each one, decide, document, suppress with // slither-disable-next-line.

13. Secure-by-default habits

14. Project

Deliverable — find the Ethernaut CTF (ethernaut.openzeppelin.com) and solve levels 1–10. Write up each exploit as a short postmortem. Then run Slither on your Phase 6 token and fix or justify every finding.

Quiz

Q. Your DEX quotes prices from a Uniswap v2 pool that has $50k of liquidity. A user reports a flash-loan drained your lending pool. Root cause?
Classic oracle manipulation. Fix: Chainlink feed or a multi-block TWAP.
← Phase 9Phase 11: DeFi →