Capstone

Smart Contracts, DeFi & Web3 Security
Course 2 · Chapter 10 · Capstone: Designing (and Auditing) a Small DeFi Application

Nine chapters built every piece separately: writing Solidity, understanding gas, real design patterns, DeFi mechanics, the dApp stack, DAOs, a vulnerability taxonomy, two real documented exploits, and the real risks that live outside the code entirely. This capstone puts it together — designing a small staking vault, auditing an early draft against Chapter 7's own taxonomy, finding a real, genuine vulnerability, fixing it, and then asking Chapter 9's own custody and regulatory questions of the finished contract.

The Design: SimpleStake

The application is deliberately small: users deposit an ERC-20 token, earn no complex yield mechanics (kept out of scope, matching this course's own consistent honesty about what it doesn't cover), and can withdraw their full deposit back out at any time. Even this minimal scope is enough to genuinely exercise every real tool this course has built.

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

contract SimpleStakeDraft {
    mapping(address => uint256) public balances;
    IERC20 public immutable token;

    constructor(address _token) {
        token = IERC20(_token);
    }

    function deposit(uint256 amount) external {
        token.transferFrom(msg.sender, address(this), amount);
        balances[msg.sender] += amount;
    }

    function withdraw(uint256 amount) external {
        require(balances[msg.sender] >= amount, "insufficient balance");
        token.transfer(msg.sender, amount);
        balances[msg.sender] -= amount;
    }
}

The Audit: Applying Chapter 7's Own Taxonomy

Reading this draft against Chapter 7's own real vulnerability categories, one by one:

Chapter 7 CategoryFinding Against This Draft
Integer overflow/underflowClean — the pragma solidity ^0.8.20 line means this contract compiles with Solidity's own built-in overflow and underflow checks, active by default in every version since Solidity 0.8.0. balances[msg.sender] -= amount would revert automatically rather than underflow.
Access controlNot applicable here — there's no owner-only or privileged function in this small a contract to protect in the first place.
ReentrancyA real, genuine finding. withdraw() calls token.transfer() before updating balances[msg.sender]. See below.
The Real Vulnerability This is the exact structural shape Chapter 7 already named: an external call happening before the contract's own internal state is updated. If token were a malicious or compromised ERC-20 implementation with a hook that calls back into SimpleStakeDraft during transfer(), that reentrant call would see balances[msg.sender] still at its original, pre-withdrawal value — and could call withdraw() again before the first call ever reaches its own final line. This is structurally the same category of flaw behind The DAO hack Chapter 7 used to introduce reentrancy in the first place, just relocated from ETH transfers to a token contract's own hook.

The Fix: Checks-Effects-Interactions

function withdraw(uint256 amount) external {
    require(balances[msg.sender] >= amount, "insufficient balance");  // Check
    balances[msg.sender] -= amount;                                // Effect
    token.transfer(msg.sender, amount);                            // Interaction
}

Reordering these three lines to match Chapter 3's own checks-effects-interactions pattern closes the vulnerability completely. By the time the external transfer() call happens, balances[msg.sender] has already been reduced — so even a fully malicious reentrant call sees a balance too low to withdraw against a second time.

Auditing in Practice This is genuinely what a real audit looks like at small scale: reading a specific contract against a known taxonomy of vulnerability classes, one category at a time, rather than searching for bugs with no structure at all. A single real finding — missed here in the draft's very first version — was enough to fully compromise every user's own deposited funds.

Custody and Regulation, Applied to This Contract

Chapter 9's own questions apply directly here too, and the honest answers are more nuanced than "smart contracts are non-custodial by default." While a user's tokens sit inside SimpleStake, that user does not hold the private key controlling those specific tokens anymore — the contract's own code does, and the user is trusting that code (not a company, but code all the same) to behave exactly as written. This is a real, genuine form of custody, just one governed by auditable, public logic rather than a company's own internal, unverifiable controls, which is precisely why the fix above matters so much: the contract's own correctness is the custody guarantee.

On regulation: if SimpleStake paid out yield and were marketed with the promise that the project's own team would keep improving the protocol to increase returns, it would map onto several of the Howey Test's own four prongs directly, the same way Chapter 9's own hypothetical token pitch did. A staking contract that simply returns exactly what a user deposited, with no yield and no promotional promise of future value from anyone else's efforts, sits much further from that real legal test — a genuine, material difference this capstone's own minimal design happens to land on the safer side of.

Chapter-by-Chapter Attribution

ChapterWhat It Contributed to This Capstone
1The Solidity syntax itself — state variables, functions, a constructor, external visibility
2Why the pragma version matters, and what each function call actually costs in gas
3The checks-effects-interactions pattern that fixes the real vulnerability found here
4The deposit/withdraw shape this small vault borrows from real DeFi lending and staking design
5How a real frontend dApp would call deposit()/withdraw() through a connected wallet
6Why a real production version of this contract would likely be owned by a multisig or DAO, not one address
7The vulnerability taxonomy this capstone's own audit applied directly, category by category
8The real reentrancy lineage this exact bug shares with a documented historical exploit
9The custody and regulatory questions applied to this contract's own specific design
What This Course Doesn't Cover This capstone is a genuine, worked demonstration of the audit process at small scale — it is not a substitute for real production security tooling. It doesn't cover static analysis tools like Slither, formal verification, professional third-party audit firms, or the real, ongoing bug-bounty programs serious protocols run continuously rather than as a one-time check. Real capital deployed on a real contract deserves real professional review, not a single pass through one course's own taxonomy.

Hands-On Exercises

Three closing exercises applying the full course toolkit to fresh scenarios.

Exercise 1
A colleague proposes fixing the reentrancy vulnerability by adding a require(!inProgress) "locking" flag instead of reordering the three lines into checks-effects-interactions order. Explain whether this alternative fix would genuinely work, and which approach this chapter's own fix actually used.
Exercise 2
Using this chapter's own custody discussion, explain why a user's tokens sitting inside SimpleStake are not the same thing as "fully non-custodial," even though no company or exchange is involved anywhere in the design.
Exercise 3
Redesign SimpleStake so it pays depositors a fixed yield, marketed with the promise that the development team will keep building features to grow returns. Using Chapter 9's own Howey Test material, explain how this specific change shifts the contract's own real regulatory exposure.

Quick Reference — Course Complete

  • Chapters 1–10 — Solidity basics, the EVM and gas, design patterns, DeFi mechanics, dApps, DAOs, a vulnerability taxonomy, two real documented exploits, regulation and custody, and this capstone auditing a real vulnerability end to end.
  • Course complete — Smart Contracts, DeFi & Web3 Security is now finished, 10/10 chapters.
  • The full Blockchain & Web3 project is now complete — Blockchain & Web3 Fundamentals (10 chapters) plus Smart Contracts, DeFi & Web3 Security (10 chapters), 20 chapters total across both courses.