L10: Solidity Basics Study Guide
Master the fundamentals of Solidity smart contract development: data types, functions, state variables, ERC-20 implementation, and basic voting contracts.
Learning Objectives
By the end of this study block, you will be able to:
- Distinguish Solidity data types and choose the correct type for a use case
- Write functions with correct visibility, state mutability, and return types
- Explain the difference between storage, memory, and calldata
- Implement a basic ERC-20 token from scratch
- Build a simple on-chain voting contract with delegation
- Use Remix IDE to compile, deploy, and test contracts on a local VM and testnet
Week-by-Week Study Plan
Week 1 — Language Fundamentals (2-3 hours)
Read the Solidity Introduction and the Types section. Focus on: uint256, address, bool, bytes32, string, mapping, and array. Work through CryptoZombies Lessons 1-3.
Goal: Write a contract from scratch that stores a number, a name, and a mapping of addresses to balances.
Week 2 — Functions, Events, and ERC-20 (2-3 hours)
Study function visibility (public, private, internal, external) and state mutability (view, pure, payable). Read the ERC-20 standard (EIP-20). Implement a minimal ERC-20 token by hand — do not copy-paste from OpenZeppelin yet.
Goal: Deploy your ERC-20 on the Remix VM, transfer tokens between test accounts, and confirm balance updates.
Week 3 — Voting Contract and Testnet Deployment (2-3 hours)
Study the Solidity Voting example from the official docs. Understand how struct, mapping, and delegation work together. Deploy your ERC-20 and a voting contract to Sepolia. Use the hands-on tools guide for faucet and deployment steps.
Goal: Deploy both contracts to Sepolia, verify them on Etherscan, and demonstrate a complete vote via Remix.
Key Concepts to Review
Value Types vs Reference Types
Value types (uint, bool, address, bytes32): copied on assignment, stored directly in storage slot.
Reference types (array, struct, mapping): you must specify data location — storage (on-chain, persistent), memory (in-function, temporary), or calldata (read-only, from external call). Forgetting location causes a compile error.
Key gotcha: mapping values default to zero — there is no "key not found" error. Check logic carefully.
Function Visibility
public: callable by anyone, inside or outside the contract. Generates a getter for state variables.
external: only callable from outside (more gas-efficient for large inputs — uses calldata).
internal: callable within the contract and derived contracts. Used for shared logic.
private: callable only within the same contract. Does not prevent reading from the blockchain directly — it only hides from other contracts.
State Mutability
view: reads state but does not modify it. No gas when called externally (off-chain).
pure: neither reads nor modifies state. Useful for math helpers.
payable: can receive ETH. Without this, sending ETH to the function reverts.
Omitting a mutability modifier means the function can read and write state — the most expensive default.
ERC-20 Standard Functions
totalSupply(): returns total tokens minted.
balanceOf(address): returns token balance of an address.
transfer(to, amount): move tokens from msg.sender to to.
approve(spender, amount): authorize spender to pull up to amount from your balance.
transferFrom(from, to, amount): pull tokens from from to to, using pre-approved allowance.
allowance(owner, spender): returns remaining approved amount.
Events: Transfer(from, to, value) and Approval(owner, spender, value) must be emitted.
Voting Contract Patterns
Struct for voters: struct Voter { bool voted; uint vote; uint weight; address delegate; } — packs all voter state in one mapping.
Delegation: Voter A delegates to B, who may have already delegated to C. Follow the chain to the final delegate (avoid loops).
Chairperson role: One address has permission to call giveRightToVote. Access control using require(msg.sender == chairperson).
Winner determination: Iterate over proposals array and find the maximum voteCount. Cannot use sorting on-chain efficiently — linear scan is standard.
Practice Exercises
SimpleStorage to store a list of numbers (using a dynamic array). Add a function to push a new number, a function to get a number by index, and a function to return the total count of stored numbers.
uint256[] public numbers; for the array. The push function: function addNumber(uint256 n) public { numbers.push(n); }. The getter: function getNumber(uint256 i) public view returns (uint256) { return numbers[i]; }. Count: function count() public view returns (uint256) { return numbers.length; }. Note: accessing an out-of-bounds index reverts — no bounds check needed explicitly.
burn function to your ERC-20 token. The function should reduce the caller's balance and the total supply by the specified amount. Emit a Transfer event to the zero address.
function burn(uint256 amount) public { require(balanceOf[msg.sender] >= amount, "Insufficient balance"); balanceOf[msg.sender] -= amount; totalSupply -= amount; emit Transfer(msg.sender, address(0), amount);}Using
address(0) as the destination in the Transfer event is the ERC-20 convention for burn events. Wallets and explorers recognize this pattern.
deadline variable set in the constructor. Modify vote() so that voting is only allowed before the deadline. Use block.timestamp for the current time.
uint256 public deadline; and set deadline = block.timestamp + _durationSeconds;In the
vote() function, add at the top: require(block.timestamp < deadline, "Voting has ended");Important caveat:
block.timestamp can be manipulated by validators by a few seconds. For high-stakes contracts, use block numbers instead of timestamps, or add a buffer of at least 15 seconds.
MintableToken contract where only the owner (set in the constructor) can call a mint(address to, uint256 amount) function. Use a custom error instead of a require string to save gas.
error NotOwner(); at the top of the contract (outside any function).In
mint(): if (msg.sender != owner) revert NotOwner();Custom errors (introduced in Solidity 0.8.4) are cheaper than require strings because the error selector (4 bytes) is cheaper to store and transmit than an ABI-encoded string. This pattern is now preferred in production contracts.
Recommended Resources
Official Documentation
-
Solidity Language Documentation v0.8.20
DOCS
The authoritative reference. Read "Introduction to Smart Contracts", "Types", and "Units and Globally Available Variables".
-
EIP-20: Token Standard
STANDARD
The original ERC-20 specification. Short and readable — understanding this directly is more valuable than copying OpenZeppelin.
Interactive Learning
-
CryptoZombies
INTERACTIVE
Game-based Solidity tutorial. Complete Lessons 1-6 for full coverage of L10 concepts. Free, browser-based, no setup.
-
Ethernaut by OpenZeppelin
INTERACTIVE
Security-focused puzzle game. Levels 0 (Hello), 1 (Fallback), and 2 (Fal1out) are accessible after completing L10 fundamentals.
Video
-
Solidity in 16 minutes - Nader Dabit
VIDEO
Quick overview of Solidity syntax and structure. Good for building a mental model before diving into the docs.
-
Solidity Full Course - freeCodeCamp / Patrick Collins
VIDEO
16-hour comprehensive course. Use as a reference — watch specific sections rather than the whole video.
Self-Check Questions
Test your understanding before moving to L11
- Can you explain the difference between
storageandmemoryin Solidity? When would you use each? - Why does a
mappingnot revert when you access a key that was never set? - What are the 6 required functions in the ERC-20 standard? What are the 2 required events?
- What is the purpose of the
approve+transferFrompattern? Why not allow direct transfers? - Can you trace the delegation chain in the Voting contract? What happens if voter A delegates to B, and B has already voted?
- What does
pragma solidity ^0.8.20mean? What does the caret (^) symbol do? - What is an event in Solidity? Why do contracts emit events rather than returning values from state-changing functions?
If you can answer all of these, you are ready for L11: Building DeFi, where you will combine these building blocks into a functioning automated market maker.