Fatskills
Practice. Master. Repeat.
Study Guide: Blockchain and Web3 Development: Ethereum and Smart Contracts Smart Contract Security Reentrancy Overflow Frontrunning Auditing
Source: https://www.fatskills.com/cryptocurrency-bitcoin-blockchain-and-more/chapter/blockchain-and-web3-development-blockchain-and-web3-development-ethereum-and-smart-contracts-smart-contract-security-reentrancy-overflow-frontrunning-auditing

Blockchain and Web3 Development: Ethereum and Smart Contracts Smart Contract Security Reentrancy Overflow Frontrunning Auditing

By Fatskills Exam Guides Team — the exam nerds behind 28,500+ quizzes and 2.1M practice questions across 500+ global exams.

⏱️ ~6 min read

What This Is

Smart contract security is the discipline of designing, writing, and reviewing blockchain code so that the contract behaves exactly as the author intends even when hostile actors try to exploit it. Because contracts are immutable and control real value, a single bug can drain millions of dollars. A classic real‑world case is the DAO hack (2016), where an attacker repeatedly called a vulnerable splitDAO() function (a re‑entrancy flaw) and siphoned ~3.6 M ETH. Modern dApps—Uniswap swaps, NFT minting portals, DAO voting contracts—must embed proven defensive patterns from day one.


Key Terms & Code Snippets

  • Reentrancy Attack – A malicious contract invokes a vulnerable function again before the first call finishes, typically via call/fallback.
    solidity // vulnerable function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount);
    (bool ok,) = msg.sender.call{value: amount}("");
    require(ok);
    balances[msg.sender] -= amount; // <-- state change after external call }

  • Check‑Effects‑Interactions (CEI) – Defensive ordering: check conditions, effect state changes, then interact with external contracts.
    solidity function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount, "Insufficient");
    balances[msg.sender] -= amount; // effect
    (bool ok,) = msg.sender.call{value: amount}("");
    require(ok, "Transfer failed"); // interaction }

  • Overflow / Underflow – Arithmetic that wraps around the 256‑bit limit, turning uint256 max + 1 into 0.
    solidity // pre‑0.8.0 vulnerable uint256 total = a + b; // could overflow silently

  • SafeMath / Built‑in Checked ArithmeticSafeMath library (pre‑0.8) or Solidity ≥0.8’s automatic revert on overflow.
    solidity using SafeMath for uint256; uint256 total = a.add(b); // reverts on overflow

  • Front‑Running (MEV) – An attacker observes a pending transaction, then inserts their own transaction with a higher gas price to profit (e.g., sandwiching a Uniswap swap).
    js // simple JS snippet to detect a pending swap provider.on("pending", async (txHash) => {
    const tx = await provider.getTransaction(txHash);
    if (tx && tx.to === uniswapRouter && tx.data.includes("swapExactETHForTokens")) {
    // launch sandwich attack here
    } });

  • Commit‑Reveal Scheme – A pattern that mitigates front‑running by separating commit (hash of secret) from reveal (the secret) across two blocks.
    solidity mapping(address => bytes32) public commitments; function commit(bytes32 hash) external { commitments[msg.sender] = hash; } function reveal(uint256 secret) external {
    require(commitments[msg.sender] == keccak256(abi.encodePacked(secret)), "Bad reveal");
    // proceed with action }

  • Delegatecall Vulnerabilitydelegatecall runs code in the context of the calling contract, so a malicious library can overwrite storage slots.
    solidity // proxy pattern (bool success,) = implementation.delegatecall(msg.data);

  • Access Control (Ownable / Roles) – Restricts privileged functions to specific addresses; misuse can open backdoors.
    solidity contract MyToken is ERC20, Ownable {
    function mint(address to, uint256 amount) external onlyOwner { _mint(to, amount); } }

  • Static Analyzer (Slither, MythX, Oyente) – Automated tools that scan Solidity for known anti‑patterns (reentrancy, uninitialized storage, etc.).

  • Formal Verification (Certora, VeriSol) – Proving mathematically that a contract satisfies invariants; used for high‑value protocols (e.g., Curve, Aave).

  • EIP‑2929 (Gas Cost Changes) – New gas cost rules for SLOAD/SSTORE that affect how you write cheap “checks” and can unintentionally expose re‑entrancy windows.


Step‑by‑Step / Process Flow

  1. Write & lint – Open Remix or VS Code, scaffold a contract with OpenZeppelin’s Ownable and ReentrancyGuard. Run solhint to catch style and basic security warnings.
  2. Compile & test – Use Hardhat (npx hardhat compile) with Solidity 0.8.20. Write unit tests in JavaScript/TypeScript that assert no balance changes after a failed require.
  3. Static analysis – Run slither . and mythx analyze ./contracts. Fix every “Reentrancy” and “Unchecked low‑level call” warning.
  4. Deploy to a testnetnpx hardhat run scripts/deploy.js --network goerli (Infura/Alchemy endpoint). Verify the source on Etherscan for transparency.
  5. Interact & simulate attacks – With Hardhat’s node fork of mainnet, write a script that tries to re‑enter the contract using a malicious attacker contract. Confirm the transaction reverts.
  6. Audit hand‑off – Export the flattened source (hardhat flatten) and a “security checklist” PDF; hand it to an external auditor for a formal review before mainnet launch.

Common Mistakes

  • Mistake: Using tx.origin for authentication (e.g., require(tx.origin == owner)).
    Correction: Use msg.sender and proper role‑based access (onlyOwner). tx.origin can be spoofed through a phishing contract, letting an attacker bypass checks.

  • Mistake: Placing state updates after an external call (the classic re‑entrancy pattern).
    Correction: Follow the CEI pattern; move all balance changes before any call, transfer, or send.

  • Mistake: Relying on assert for input validation.
    Correction: assert is meant for internal invariants and consumes all remaining gas on failure. Use require for user‑controlled checks to get a clean revert and a meaningful error string.

  • Mistake: Forgetting to set the Solidity compiler version (pragma solidity ^0.8.0;) and thus compiling with an older version that lacks built‑in overflow checks.
    Correction: Pin to a recent stable version (e.g., pragma solidity 0.8.20;) and enable optimizer only after testing.

  • Mistake: Hard‑coding gas limits for external calls (call{gas: 5000}) without understanding EIP‑2929.
    Correction: Let the EVM manage gas (call{value: amount}) or explicitly forward gasleft(); otherwise you may open a re‑entrancy window or cause out‑of‑gas failures.


Blockchain Developer Interview / Practical Insights

  1. “Explain the difference between call and delegatecall and when each is safe.”
  2. call executes code in the callee’s context (its storage, address). delegatecall runs the callee’s code as if it were part of the caller, so storage layout must match; safe only when the library is trusted and immutable.

  3. “How would you mitigate front‑running on a token swap?”

  4. Use a commit‑reveal scheme, add a slippage buffer, or route the swap through a private transaction pool (e.g., Flashbots). Demonstrate awareness of MEV and gas‑price bidding.

  5. “What does the ReentrancyGuard modifier do under the hood?”

  6. It sets a storage flag (_status) to ENTERED before the function body runs and reverts if the flag is already set, preventing nested entry.

  7. “Why is ERC‑777 considered safer than ERC‑20 for token transfers?”

  8. ERC‑777 introduces hooks (tokensReceived) that let the recipient reject a transfer, eliminating the need for the sender to check return values and reducing accidental loss.

Quick Check Questions

  1. Scenario: A contract uses require(tx.origin == admin) to restrict a withdraw() function.
    Answer: Dangerous – an attacker can trick a user (the admin) into calling a malicious contract that then calls withdraw(), because tx.origin stays the original EOA.

  2. Scenario: You see uint256 total = a + b; in a Solidity 0.7 contract with no SafeMath.
    Answer: Vulnerable to overflow – the addition could wrap to zero, letting an attacker inflate balances; upgrade to Solidity ≥0.8 or wrap with SafeMath.

  3. Scenario: A DeFi pool’s addLiquidity() updates the pool’s token balance after calling token.transferFrom().
    Answer: Re‑entrancy risk – a malicious ERC‑20 token could re‑enter addLiquidity() during transferFrom, causing double‑counting. Move the state update before the external call.


Last‑Minute Cram Sheet (10 one‑liners)

  1. ⚠️ Never update state after an external call – always use Check‑Effects‑Interactions.
  2. Solidity ≥0.8 automatically reverts on overflow/underflow; older versions need SafeMath.
  3. ReentrancyGuard works by storing a single‑byte status flag (_NOT_ENTERED / _ENTERED).
  4. MEV = Miner/Maximal Extractable Value; front‑running is the most common MEV vector.
  5. delegatecall shares the caller’s storage; always verify the library’s storage layout.
  6. EIP‑2929 raised SLOAD gas from 800 → 2100; batch reads with mapping loops can become expensive.
  7. OpenZeppelin contracts are audited‑by‑default; start every new project from their library.
  8. Static analysis catches 80 % of known bugs; run slither and mythx on every PR.
  9. Commit‑reveal mitigates front‑running but adds a one‑block latency; use it for auctions, voting, or NFT name registration.
  10. Audit checklist – check: re‑entrancy, arithmetic, access control, external calls, upgradeability, and proper event emission.


ADVERTISEMENT