← Back to all projects

LEARN SOLIDITY DEEP DIVE

Learn Solidity: From Smart Contracts to DeFi Pioneer

Goal: Deeply understand Solidity and the mindset required for smart contract development—from the fundamentals of the Ethereum Virtual Machine (EVM) to building secure, gas-efficient, and complex decentralized applications.


Why Learn Solidity?

Solidity is not just another programming language; it’s a language for creating “world computers.” Unlike traditional programs that run on a single server, Solidity contracts run on the Ethereum blockchain, a decentralized network of thousands of computers that all execute the same code and agree on the same state. This paradigm shift introduces unique challenges and opportunities.

Learning Solidity teaches you to think about:

  • Code as Law: Deployed contract code is immutable and its execution is final. Bugs can have catastrophic financial consequences.
  • Economics of Computation: Every single operation costs money (gas). Optimization is not about speed, but about cost-effectiveness.
  • Adversarial Environment: Your public contracts will be probed and attacked by anonymous actors around the world. Security is not a feature; it is the prerequisite.
  • Decentralized State: You are building systems that manage value and state without a central authority.

After completing these projects, you will:

  • Understand the EVM and its constraints.
  • Write, test, and deploy secure and optimized smart contracts.
  • Master core DeFi concepts by building them yourself.
  • Know how to use professional tools like Foundry, Hardhat, and Ethers.js.
  • Think like a smart contract auditor, spotting potential vulnerabilities.

Core Concept Analysis

The Smart Contract Paradigm

┌─────────────────────────────────────────────────────────────────────────┐
│                       SOLIDITY SOURCE CODE (.sol)                       │
│                                                                         │
│   contract SimpleBank {                                                 │
│       mapping(address => uint) public balances;                         │
│       function deposit() public payable {                               │
│           balances[msg.sender] += msg.value;                            │
│       }                                                                 │
│   }                                                                     │
└─────────────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼ The Solidity Compiler (solc)
┌─────────────────────────────────────────────────────────────────────────┐
│                           EVM BYTECODE + ABI                            │
│                                                                         │
│   Bytecode: 608060405234801561001057600080fd5b... (for the machine)      │
│   ABI: [{"constant":false,"inputs":[],"name":"deposit"...}] (for humans)│
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
                                 │
                                 ▼ Deployment Transaction
┌─────────────────────────────────────────────────────────────────────────┐
│                    ETHEREUM BLOCKCHAIN (Global State)                   │
│                                                                         │
│   A new contract account is created at a specific address.              │
│   The bytecode is stored in its `code` field.                           │
│   Its `storage` is a persistent key-value store (2^256 slots).          │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘
                                 │
          ┌──────────────────────┼──────────────────────┐
          ▼                      ▼                      ▼
┌──────────────────┐  ┌──────────────────┐  ┌──────────────────┐
│  STATE-CHANGING  │  │    READ-ONLY     │  │  SECURITY & COST │
│   TRANSACTIONS   │  │       CALLS      │  │    (The Rules)   │
│                  │  │                  │  │                  │
│ • Sent by a user │  │ • Free to execute│  │ • Gas mechanism  │
│ • Cost gas       │  │ • Cannot change  │  │ • Immutability   │
│ • Change storage │  │   state          │  │ • Re-entrancy    │
│ • Are atomic     │  │ • Run on one node│  │ • Overflow checks│
└──────────────────┘  └──────────────────┘  └──────────────────┘

Key Concepts Explained

1. The EVM & Gas

The Ethereum Virtual Machine (EVM) is a fully isolated, deterministic, Turing-complete state machine. Every node on the network runs it.

  • Gas: Every opcode in the EVM (e.g., ADD, SSTORE) has a cost in “gas”. A transaction’s total fee is gas_used * gas_price. This prevents infinite loops and pays the nodes for their work. SSTORE (writing to storage) is one of the most expensive operations.

2. Storage, Memory, and Calldata

Where data lives is critical in Solidity:

  • storage: Persistent on the blockchain. A key-value store of 256-bit slots. Extremely expensive to write to. State variables live here.
  • memory: Temporary, exists only during a function’s execution. Cheaper. Used for complex computations within a function.
  • calldata: A special, read-only location for data from an external transaction. Even cheaper than memory.

3. Key Data Types & Patterns

  • address: Holds a 20-byte Ethereum account. address payable can receive Ether.
  • uint256 / int256: The standard integer sizes, as the EVM works with a 256-bit word size.
  • mapping(key => value): A hash table. The most common way to store user-related data (e.g., mapping(address => uint) balances).
  • msg.sender / msg.value: Global variables available in every function call. msg.sender is the address that called the function, and msg.value is the amount of Ether sent with the call.

4. Visibility: public, external, internal, private

  • public: Anyone can call (user or other contract). The compiler automatically creates a “getter” function for public state variables.
  • external: Can only be called from outside the contract (by users or other contracts). Often cheaper than public.
  • internal: Can only be called from within the contract or by derived contracts.
  • private: Can only be called from within the contract itself (not derived contracts). Note: private only restricts access within the EVM. All data on the blockchain is public and can be read by anyone.

5. The Checks-Effects-Interactions Pattern

A crucial security pattern to prevent re-entrancy attacks. When writing a function:

  1. Checks: Perform all require() statements first (validate inputs, check permissions).
  2. Effects: Write to your contract’s state variables (balances[user] = 0).
  3. Interactions: Call other contracts or send Ether.

By updating your state before the external call, you prevent the external contract from calling back into your function and exploiting an inconsistent state.


Project List

The projects are designed to build upon each other, starting with Solidity basics and moving into core DeFi and security concepts.


Project 1: The Simple Bank & Faucet

  • File: LEARN_SOLIDITY_DEEP_DIVE.md
  • Main Programming Language: Solidity
  • Alternative Programming Languages: Vyper
  • Coolness Level: Level 2: Practical but Forgettable
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 1: Beginner
  • Knowledge Area: Smart Contracts / State Management
  • Software or Tool: Remix IDE, Sepolia Testnet
  • Main Book: “Mastering Ethereum” by A. Antonopoulos & G. Wood - Chapter 7

What you’ll build: A basic smart contract that can accept Ether deposits from users, track their balances, allow them to withdraw their funds, and a “faucet” function to get a small amount of test currency.

Why it teaches Solidity: This is the “Hello, World!” of smart contracts. It forces you to learn the most fundamental concepts: state variables (mapping), payable functions, access control (msg.sender), and security invariants (require).

Core challenges you’ll face:

  • Tracking balances → maps to using the mapping(address => uint) pattern
  • Accepting Ether → maps to payable functions and msg.value
  • Ensuring security → maps to using require for access control and preventing underflows
  • Sending Ether → maps to the transfer or call methods and the Checks-Effects-Interactions pattern

Key Concepts:

  • State Variables: Solidity Docs - “State Variables”
  • payable modifier: Solidity by Example - “Payable”
  • require() statement: Solidity by Example - “Require”
  • msg.sender: Solidity Docs - “Globally Available Variables”

Difficulty: Beginner Time estimate: Weekend Prerequisites: Basic programming knowledge.

Real world outcome: A contract deployed on a testnet. You can use a wallet like MetaMask to call your deposit function with some test Ether and see your balance update. Then, you can call withdraw to get it back.

Implementation Hints:

  • Use a mapping mapping(address => uint) public balances; to store who owns what. The public keyword will automatically give you a “getter” function.
  • Your deposit function must be marked payable. Inside it, you’ll add msg.value to balances[msg.sender].
  • Your withdraw function should take a uint amount as an argument.
    1. Check: require(balances[msg.sender] >= amount, "Insufficient balance");
    2. Effect: balances[msg.sender] -= amount;
    3. Interaction: payable(msg.sender).transfer(amount); (This is the basic, safe way to send Ether).

Learning milestones:

  1. You can deposit and see your balance → You understand state variables and payable functions.
  2. You can’t withdraw more than you deposited → You understand require.
  3. Another user can’t withdraw your funds → You understand msg.sender.
  4. You write a withdraw function that is safe from re-entrancy → You’ve learned the Checks-Effects-Interactions pattern.

Project 2: Your Own ERC-20 Token

  • File: LEARN_SOLIDITY_DEEP_DIVE.md
  • Main Programming Language: Solidity
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 2. The “Micro-SaaS / Pro Tool”
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Token Standards / Fungible Tokens
  • Software or Tool: Hardhat, OpenZeppelin Contracts
  • Main Book: “Mastering Ethereum” - Chapter 8

What you’ll build: A standard, fungible token (like USDC or SHIB). You will implement the ERC-20 interface from scratch to understand every function.

Why it teaches Solidity: ERC-20 is the most widespread token standard. Building one teaches you how to implement a standard interface, a crucial skill for interoperability. You’ll also learn the approve/transferFrom pattern, a cornerstone of DeFi composability.

Core challenges you’ll face:

  • Implementing an interface → maps to adhering to a standard (the EIP-20 spec)
  • Managing token supply → maps to minting, burning, and tracking totalSupply
  • Handling allowances → maps to the approve and transferFrom flow, which allows contracts to spend tokens on your behalf
  • Emitting events → maps to logging actions (Transfer, Approval) for off-chain applications to consume

Key Concepts:

  • ERC-20 Standard: EIP-20 official specification
  • Interfaces: Solidity by Example - “Interfaces”
  • Events: Solidity by Example - “Events”
  • Using libraries: OpenZeppelin’s ERC20 implementation (as a reference)

Difficulty: Intermediate Time estimate: Weekend Prerequisites: Project 1.

Real world outcome: You’ll have a token that can be added to MetaMask and transferred between accounts on a testnet. You can see the Transfer events being fired in a block explorer like Etherscan.

// A snippet of the interface you will implement
interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address recipient, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

Implementation Hints:

  • You’ll need two core mappings: one for balances (mapping(address => uint)) and one for allowances (mapping(address => mapping(address => uint))).
  • The transfer function is straightforward: decrease sender’s balance, increase receiver’s balance, emit Transfer event.
  • The approve function is where a user (msg.sender) allows a spender to withdraw up to amount tokens from their account.
  • The transferFrom function is called by the spender to execute the transfer. It must check the allowance, then perform the transfer and decrease the allowance. This is how you “pay” a DeFi protocol.

Learning milestones:

  1. You can transfer tokens between two accounts → You’ve implemented the core logic.
  2. You can approve a contract and it can transferFrom your account → You understand how DeFi protocols interact with tokens.
  3. Your token shows up correctly in MetaMask and Etherscan → You’ve correctly implemented the metadata functions (name, symbol, decimals).
  4. You understand why approve can be risky and learn about safeApprove → You’re starting to think about security nuances.

Project 3: A Crowdfunding Platform (Kickstarter)

  • File: LEARN_SOLIDITY_DEEP_DIVE.md
  • Main Programming Language: Solidity
  • Coolness Level: Level 3: Genuinely Clever
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: State Machines / DeFi
  • Software or Tool: Foundry, Ethers.js
  • Main Book: N/A, use online tutorials and docs.

What you’ll build: A contract that allows a project creator to raise funds. Contributors can send Ether. If the goal is met by the deadline, the creator can withdraw the funds. If not, contributors can claim a refund.

Why it teaches Solidity: This project is all about managing state transitions and time-based logic. It forces you to create a robust state machine and handle different outcomes securely, which is a common pattern in more complex applications.

Core challenges you’ll face:

  • Building a state machine → maps to using an enum for states (Funding, Successful, Failed) and require statements to control transitions
  • Time-based logic → maps to using block.timestamp to manage deadlines
  • Secure fund withdrawal → maps to the “pull-over-push” pattern to prevent issues with sending Ether
  • Differentiating roles → maps to storing the project creator’s address (owner) and ensuring only they can perform certain actions

Key Concepts:

  • State Machines: Solidity by Example - “State Machine”
  • block.timestamp: Solidity Docs - “Globally Available Variables”
  • Pull-over-Push Pattern: Consensys Best Practices - “Favor Pull over Push”

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 1.

Real world outcome: A deployed contract where users can contribute, and depending on the outcome, either the project owner withdraws the full amount or the contributors get their money back. You can test this entire flow using a simple script.

Implementation Hints:

  • Use an enum State { Funding, Successful, Failed } and a state variable to track the current state.
  • Store the owner, goal, and deadline as immutable variables set in the constructor.
  • The contribute function should only work when state == State.Funding.
  • Create a checkIfFundingComplete function that can be called after the deadline. It will check address(this).balance >= goal and transition the state to Successful or Failed.
  • Crucially, for refunds, don’t loop through contributors and send them Ether (a “push” pattern). Instead, create a claimRefund function that allows each contributor to “pull” their funds back individually. This is safer and more gas-efficient.

Learning milestones:

  1. You can contribute, and the contract balance increases → Basic state change works.
  2. The contract correctly transitions from Funding to Successful or Failed after the deadline → Your state machine and time-based logic work.
  3. The project owner can withdraw funds only if the goal was met → You’ve secured the owner’s function.
  4. Contributors can get a refund only if the goal failed → You’ve secured the contributors’ function and implemented the pull-over-push pattern.

Project 4: An NFT Collection (ERC-721)

  • File: LEARN_SOLIDITY_DEEP_DIVE.md
  • Main Programming Language: Solidity
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 2: Intermediate
  • Knowledge Area: Token Standards / NFTs / Digital Identity
  • Software or Tool: Hardhat, OpenZeppelin Contracts, IPFS
  • Main Book: “Mastering Ethereum” - Chapter 8

What you’ll build: A generative art NFT collection where users can mint a unique token. Each token will have metadata (image, attributes) stored on IPFS.

Why it teaches Solidity: It introduces you to non-fungible tokens, the backbone of digital collectibles and identity. You’ll learn how to manage ownership of unique assets, associate them with off-chain data (like images), and handle the “minting” process.

Core challenges you’ll face:

  • Implementing ERC-721 → maps to understanding the NFT standard and its differences from ERC-20
  • Tracking ownership of unique IDs → maps to mapping(uint256 => address) for owners
  • On-chain vs. Off-chain data → maps to understanding that you don’t store images on-chain, but a link (URI) to them
  • Writing a secure minting function → maps to controlling supply, setting a price, and preventing exploits

Key Concepts:

  • ERC-721 Standard: EIP-721 official specification
  • IPFS (InterPlanetary File System): A decentralized storage system perfect for NFT metadata.
  • URI and JSON Metadata: The ERC-721 metadata standard.
  • Using Libraries: OpenZeppelin’s ERC721 contracts are the industry standard and a great reference.

Difficulty: Intermediate Time estimate: 1-2 weeks Prerequisites: Project 2.

Real world outcome: You’ll have an NFT collection visible on a marketplace like OpenSea (on a testnet). You can mint an NFT to your wallet, see the image associated with it, and transfer it to a friend.

Implementation Hints:

  • You can inherit from OpenZeppelin’s ERC721.sol contract to get most of the functionality for free. Your job is to write the custom mint function.
  • First, create your images and JSON metadata files. A simple format is 1.json, 2.json, etc. Upload the folder containing these files to a service like Pinata, which pins them to IPFS. You’ll get a single folder hash (a CID).
  • In your contract, store this IPFS CID as a baseURI.
  • Your tokenURI(uint256 tokenId) function (which you’ll override) will then simply return the string concatenation of baseURI and tokenId.toString().
  • Your safeMint function should:
    • Check that the max supply hasn’t been reached.
    • Require the user to pay the correct minting fee (require(msg.value == mintPrice)).
    • Call the internal _safeMint(to, tokenId) function from the OpenZeppelin contract.

Learning milestones:

  1. You can mint an NFT to your address → Your safeMint function and contract setup work.
  2. The NFT shows up on OpenSea with the correct image and attributes → Your tokenURI and metadata are set up correctly.
  3. You can transfer the NFT to another wallet → The core ERC-721 ownership logic is working.
  4. You implement features like a presale list or a Dutch auction → You are building on the basic minting mechanic with more advanced strategies.

Project 5: A Multi-Signature Wallet

  • File: LEARN_SOLIDITY_DEEP_DIVE.md
  • Main Programming Language: Solidity
  • Coolness Level: Level 4: Hardcore Tech Flex
  • Business Potential: 3. The “Service & Support” Model
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Security / Access Control / Wallets
  • Software or Tool: Foundry
  • Main Book: N/A, reference Gnosis Safe’s architecture.

What you’ll build: A smart contract wallet that holds funds and requires M-of-N owners to approve a transaction before it can be executed. For example, requiring 2-of-3 signatures.

Why it teaches Solidity: This is a pure security and logic project. It’s how DAOs and companies manage their treasuries. It teaches you how to design a secure, multi-step workflow on-chain and how to protect against unauthorized access to critical functions.

Core challenges you’ll face:

  • Managing owners and confirmations → maps to complex state management with multiple mappings and structs
  • A two-step transaction process → maps to proposing a transaction vs. executing it
  • Ensuring transaction integrity → maps to preventing replay attacks on signatures or proposals
  • Executing arbitrary transactions → maps to using address.call{value: ...}(data) to interact with any other contract

Key Concepts:

  • Access Control Design: More advanced than a simple onlyOwner modifier.
  • Structs: Solidity by Example - “Structs”
  • Executing External Calls: Solidity Docs - “address.call”
  • Hashing for Integrity: Using keccak256 to create a unique ID for each proposed transaction.

Difficulty: Advanced Time estimate: 2-3 weeks Prerequisites: Projects 1 & 3.

Real world outcome: A deployed wallet that you can send Ether to. To get the Ether out, you (and at least one other owner) must individually sign and submit an approval transaction. Only after enough approvals are collected can the transaction be executed.

Implementation Hints:

  • In the constructor, set the list of owners and the required number of signatures.
  • You’ll need a struct Transaction to store the destination, value, data, and execution status. Store these in an array Transaction[] public transactions;.
  • You’ll also need a mapping mapping(uint => mapping(address => bool)) public confirmations; to track who has confirmed which transaction index.
  • The submitTransaction function allows an owner to propose a new transaction. It adds it to the array and returns its index.
  • The confirmTransaction function allows other owners to approve a transaction index. It should check that the caller is an owner and hasn’t already confirmed.
  • The executeTransaction function can be called by anyone. It will:
    1. Check that the transaction has enough confirmations.
    2. Check that the transaction hasn’t already been executed.
    3. Mark the transaction as executed.
    4. Use address(tx.to).call{value: tx.value}(tx.data).

Learning milestones:

  1. An owner can submit a transaction proposal → The basic data structures are working.
  2. Other owners can confirm the transaction, and the confirmation count increases → State is being updated correctly.
  3. The transaction cannot be executed with too few confirmations → Your core security logic is holding up.
  4. A fully confirmed transaction executes successfully and can’t be executed again → The full lifecycle is complete and secure.

Project 6: Exploiting and Fixing a Re-entrancy Vulnerability

  • File: LEARN_SOLIDITY_DEEP_DIVE.md
  • Main Programming Language: Solidity
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 1. The “Resume Gold”
  • Difficulty: Level 3: Advanced
  • Knowledge Area: Security / Smart Contract Auditing
  • Software or Tool: Foundry or Hardhat
  • Main Book: “Mastering Ethereum” - Chapter 9 (Security)

What you’ll build: You will build two contracts. First, a simple EtherStore contract that is deliberately vulnerable to a re-entrancy attack. Second, an Attack contract that exploits this vulnerability to drain all the Ether from EtherStore. Finally, you will fix the EtherStore contract.

Why it teaches Solidity: It forces you to think like an attacker. Understanding how vulnerabilities are exploited is the best way to learn how to prevent them. This project will burn the Checks-Effects-Interactions pattern into your brain forever.

Core challenges you’ll face:

  • Writing a vulnerable withdraw function → maps to violating the Checks-Effects-Interactions pattern
  • Using a fallback/receive function for the attack → maps to understanding how a contract reacts when it receives Ether
  • The re-entrant call → maps to the core logic of the attack, where the fallback function calls withdraw again before the first call completes
  • Fixing the vulnerability → maps to applying the Checks-Effects-Interactions pattern or using a re-entrancy guard

Key Concepts:

  • Re-entrancy: Consensys Best Practices - “Re-entrancy”
  • Fallback/Receive functions: Solidity by Example - “Fallback”
  • Security Mindset: Thinking about worst-case scenarios.
  • Re-entrancy Guards: OpenZeppelin’s ReentrancyGuard.sol library.

Difficulty: Advanced Time estimate: 1-2 weeks Prerequisites: Project 1.

Real world outcome: You will write a test (using Foundry or Hardhat) that proves your Attack contract can drain the EtherStore contract. Then, you will modify EtherStore, and the same test will now fail, proving your fix works.

Implementation Hints:

  • Vulnerable EtherStore.sol:
    • Have a deposit function.
    • Write a withdraw function that does the interaction before the effect:
      1. Look up user’s balance.
      2. Send the Ether via msg.sender.call{value: amount}("").
      3. Then, set the user’s balance to 0. This is the bug.
  • Attack.sol:
    • Write an attack function that deposits a small amount into EtherStore and then calls EtherStore.withdraw().
    • Implement a receive() payable function. This function will be triggered when EtherStore sends Ether to your Attack contract.
    • Inside receive(), add the logic to call EtherStore.withdraw() again if the EtherStore still has a balance.
  • The Fix:
    • Rewrite the withdraw function in EtherStore to follow Checks-Effects-Interactions.
    • Alternatively, add a bool internal locked; flag and a modifier nonReentrant that sets it to true at the start of the function and false at the end, and requires it to be false to enter.

Learning milestones:

  1. Your EtherStore contract can be drained by your Attack contract → You have successfully executed a re-entrancy attack.
  2. You understand exactly why the re-entrant call works → You can trace the execution flow of the attack.
  3. You fix EtherStore by reordering the operations → You have learned the Checks-Effects-Interactions pattern by fixing a real bug.
  4. You fix EtherStore again using a re-entrancy guard modifier → You have learned how to use libraries to implement common security patterns.

Project 7: Upgradable Contracts (Proxy Pattern)

  • File: LEARN_SOLIDITY_DEEP_DIVE.md
  • Main Programming Language: Solidity
  • Coolness Level: Level 5: Pure Magic (Super Cool)
  • Business Potential: 4. The “Open Core” Infrastructure
  • Difficulty: Level 4: Expert
  • Knowledge Area: Contract Architecture / Proxies / delegatecall
  • Software or Tool: Hardhat Upgrades plugin, Foundry
  • Main Book: N/A, use OpenZeppelin docs and blog posts.

What you’ll build: A simple ERC-20 token contract that is upgradable. You will deploy a “proxy” contract and a “logic” contract (V1). Then you will deploy a V2 of the logic contract with a new function, and “upgrade” the proxy to point to V2, all without losing the token balances.

Why it teaches Solidity: It solves the “immutability” problem. Real-world projects need to fix bugs and add features. This project teaches the advanced but essential proxy pattern, which uses a low-level opcode called delegatecall to separate a contract’s state from its logic.

Core challenges you’ll face:

  • Understanding delegatecall → maps to the core of the proxy pattern; it runs another contract’s code in the current contract’s context (storage, msg.sender)
  • Proxy contract logic → maps to a minimal contract that forwards all calls to a logic contract address
  • Storage layout compatibility → maps to the most dangerous part: ensuring that V2 of your contract declares state variables in the same order as V1 to avoid corrupting storage
  • The upgrade process → maps to calling a function on the proxy to change the logic contract address

Key Concepts:

  • Proxy Patterns (UUPS, Transparent): OpenZeppelin Docs - “Proxy Patterns”
  • delegatecall: Solidity Docs - “delegatecall”
  • Storage Slots: The underlying key-value layout of contract storage.

Difficulty: Expert Time estimate: 2-3 weeks Prerequisites: Deep understanding of Solidity, especially storage. Project 2.

Real world outcome:

  1. You deploy TokenV1 and a Proxy pointing to it. You mint tokens to yourself. Your balance is stored in the Proxy’s storage.
  2. You deploy TokenV2 (which might have a new burnMyTokens() function).
  3. You call an upgradeTo(tokenV2Address) function on the Proxy.
  4. You can now call the new burnMyTokens() function on the Proxy address, and your token balance is still intact.

Implementation Hints:

  • Use the OpenZeppelin Upgrades plugins for Hardhat or Foundry. They handle most of the complexity and safety checks. Your goal is to understand what they are doing under the hood.
  • Proxy.sol: The core logic is a fallback() function that gets the logic address and uses inline assembly to delegatecall.
  • LogicV1.sol: A normal ERC-20 contract, but its constructor is replaced by an initialize function. This is because the constructor logic needs to run in the context of the proxy, not when the logic contract is deployed.
  • LogicV2.sol: A copy of V1, but with a new function added. Crucially, do not change the order or type of the existing state variables. You can only add new ones at the end.

Learning milestones:

  1. You have a working Proxy and LogicV1, and can interact with it → You understand the basic setup.
  2. You successfully upgrade the proxy to point to LogicV2 → You have performed a contract upgrade.
  3. You can call a V2 function, and the state from V1 (balances) is preserved → You have achieved a successful, state-preserving upgrade.
  4. You intentionally break storage layout in V3 and see the contract state get corrupted → You learn, through failure, why storage layout is the most critical part of upgradability.

Summary

Project Difficulty Time Main Concept Main Tool
1. Simple Bank Beginner Weekend State, payable, require Remix
2. ERC-20 Token Intermediate Weekend Token Standards, Events Hardhat
3. Crowdfunding Intermediate 1-2 weeks State Machines, Time Foundry
4. NFT Collection Intermediate 1-2 weeks ERC-721, Off-chain data Hardhat/IPFS
5. Multi-Sig Wallet Advanced 2-3 weeks Security, Access Control Foundry
6. Re-entrancy Hack Advanced 1-2 weeks Security, Attack Vectors Foundry
7. Upgradable Contract Expert 2-3 weeks Proxies, delegatecall Hardhat