Capstone
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 Category | Finding Against This Draft |
|---|---|
| Integer overflow/underflow | Clean — 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 control | Not applicable here — there's no owner-only or privileged function in this small a contract to protect in the first place. |
| Reentrancy | A real, genuine finding. withdraw() calls token.transfer() before updating balances[msg.sender]. See below. |
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.
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
| Chapter | What It Contributed to This Capstone |
|---|---|
| 1 | The Solidity syntax itself — state variables, functions, a constructor, external visibility |
| 2 | Why the pragma version matters, and what each function call actually costs in gas |
| 3 | The checks-effects-interactions pattern that fixes the real vulnerability found here |
| 4 | The deposit/withdraw shape this small vault borrows from real DeFi lending and staking design |
| 5 | How a real frontend dApp would call deposit()/withdraw() through a connected wallet |
| 6 | Why a real production version of this contract would likely be owned by a multisig or DAO, not one address |
| 7 | The vulnerability taxonomy this capstone's own audit applied directly, category by category |
| 8 | The real reentrancy lineage this exact bug shares with a documented historical exploit |
| 9 | The custody and regulatory questions applied to this contract's own specific design |
Hands-On Exercises
Three closing exercises applying the full course toolkit to fresh scenarios.
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.
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.