EVM Architecture Slides Lesson 01
The World Computer
Understanding Virtual Machines, Global State, and the architecture of the Ethereum Virtual Machine (EVM).
The EVM
A distributed state machine that executes arbitrary code across thousands of nodes.
Global State
A single truth maintained by consensus, updated via atomic transactions.
Smart Contracts
Immutable logic that defines how the state can be modified by users.
How State Changes
State (S)
Old Ledger
apply(S, T) → S'
Validation &
Consensus
State (S')
New Ledger
// The Vending Machine Analogy
Initial State: Balance $0, 10 sodas
Transaction: Insert $2, select soda
New State: Balance $2, 9 sodas
The EVM Architecture
Stack-based Machine
LIFO structure for processing opcodes. Most operations happen here (size: 1024 items).
Memory (Ephemeral)
Linear byte-array, cleared between function calls. Costs gas to expand.
Storage (Persistent)
Permanent key-value store mapping 256-bit keys to 256-bit values. Very expensive.
Smart Contract Bytecode
EVM Interpreter
Stack
Memory
Storage
Hello World Contract
HelloWorld.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract HelloWorld {
// State variable (stored in Storage)
string public message;
constructor(string memory \_init) {
message = \_init;
}
function update(string memory \_msg) public {
message = \_msg;
}
}
Pragma
Tells the compiler which version of Solidity to use.
State Variable
Stored permanently on the blockchain. Notice the "public" keyword creates a getter.
Memory Keyword
Strings must specify location. 'memory' means it's temporary during the call.
Hello World Lab Worksheet World Computer Lab
Lesson 01: Deployment & State Basics
Student Name
Date
Objective
In this lab, you will use the Remix IDE to write, compile, and deploy your first smart contract. You will explore how data is stored in the Storage area of the EVM and observe how transactions change the global state.
Part 1: The EVM Mental Model
1. Explain the difference between Memory and Storage in the context of the EVM. Why does this distinction matter for cost?
2. You are deploying a "Counter" contract. Initially, the counter is 0. If two users send transactions to increment it at the exact same time, what ensures the final state is 2 and not 1?
Remix Setup Steps
Navigate to remix.ethereum.org
Create a new file named SimpleStorage.sol
Select the Solidity Compiler tab and set the version to 0.8.x
Navigate to the Deploy & Run Transactions tab. Ensure the environment is set to Remix VM (Cancun/London).
Part 3: Implementation
Complete the code for the SimpleStorage contract below. It should store an unsigned integer and provide a function to update it.
// SPDX-License-Identifier: MIT
pragma
solidity ^0.8.0;
contract
SimpleStorage {
// 1. Define a private uint256 named 'storedNumber'
// 2. Create a function 'set' that updates the number
function
set(uint256 _num) public {
}
// 3. Create a function 'get' that returns the number
function
get() public view returns (uint256) {
}
}
Part 4: Deployment & Gas
Deploy your contract in Remix. Look at the Terminal Console output for the deployment transaction.
Transaction Hash
Gas Used (Deployment)
Why does the set function cost gas, while the get function (when called externally) does not?
Final Reflection
"Smart contracts are not smart, and they aren't legal contracts." Based on your experience in this lab, explain what they actually are in 2-3 sentences.
EVM Fundamentals Teacher Guide Teacher Guide
Lesson 01: EVM Fundamentals & Basic State
Lesson Context
This lesson introduces blockchain as a World Computer . The goal is to move students away from the idea of blockchain as just a "ledger for currency" and toward the idea of a distributed operating system. This is a foundational shift that requires understanding state .
Key Learning Objectives
Describe the EVM as a state machine.
Identify the storage locations (Stack, Memory, Storage).
Deploy a contract and interact with state variables.
Pacing Guide
Hook & Intro 10m
Theory (Slides) 20m
Lab Workshop 45m
Debrief 15m
Prerequisites
Basic programming logic, understanding of hash functions.
Teaching Narratives
The Vending Machine Hook
Ask: "How does a vending machine know it's okay to give you a soda?" It checks its state (money in, inventory count). A smart contract is just a digital vending machine whose logic can't be tampered with by the machine owner.
State Transitions
Emphasize that the blockchain doesn't "store" coins; it stores a ledger of balances . A transaction is a request to the EVM to change that ledger from State A to State B.
Lab Guidance & Answers
Lab Question 02 Answer
The Answer: Transactions are atomic and ordered within a block. Even if sent at the same time, the miner/proposer orders them sequentially (T1 then T2). T1 increments 0 to 1; T2 increments 1 to 2.
Common Pitfalls in Remix
Compiler Version: Students often forget to match the pragma statement to the compiler dropdown.
Environment: Ensure they are on "Remix VM" for instant feedback, not "Injected Provider" (which requires a real wallet/MetaMask).
Function Return: Remind students that `view` functions are free because they don't change state; they just read the local node's copy.
Debrief Questions
Q: Why is "Storage" so much more expensive than "Memory"?
A: Because Storage must be copied and stored permanently by EVERY node in the network forever. Memory is discarded after the transaction executes.
Q: Can you "delete" a smart contract after deployment?
A: Usually no (unless selfdestruct is used, but that's deprecated). Code is immutable and lives on the ledger forever.
Access Control Slides Lesson 02
Guarding the Vault
Solidity Fundamentals: Mappings, Structs, and the power of Function Modifiers for access control.
Access Control
Restricting sensitive functions to specific users (e.g., the owner).
Mappings
Efficient key-value lookup tables, the backbone of token tracking.
Structs
Defining custom data types to group related information together.
Data Structures: Mappings
Concept
Think of a mapping as a hash table that is virtually initialized to every possible key.
mapping(address => uint256) balances;
O(1) Lookup time
No length property (can't iterate)
Default value is 0 / false
Example: Voting Status
mapping(address => bool) hasVoted;
Perfect for checking permissions or ownership without looping through a list.
The "Invisible" Key Problem
"In Solidity, you cannot iterate over a mapping. If you need a list of all voters, you must also maintain an array of their addresses."
Custom Types: Structs
struct Proposal {
string description;
uint256 voteCount;
bool executed;
}
Structs allow you to define complex objects that save space in storage.
Grouping Logic
Instead of 3 different mappings, keep related data in one record.
Combination
mapping(uint => Proposal) proposals;
You can nest structs inside mappings for powerful data management.
Function Modifiers
modifier onlyOwner() {
require(msg.sender == owner,
"Not owner");
_; // Continue execution
}
function withdraw() public onlyOwner {
// sensitive logic here
}
The Underscore (_)
The _ is a special character that tells Solidity to "go back to the original function body and execute it now."
DRY
Don't Repeat Yourself. Use modifiers for common checks.
Readability
Security logic is visible at the function signature level.
Secure Voting Coding Activity The Locked Vault
Coding Activity: Access Control & Data Logic
Complexity
Level 2
The Challenge
You are building a secure digital vault . The vault holds a secret message that only the Vault Keeper can update. However, multiple Authorized Users can read the secret.
Requirement A Restrict the updateSecret function to the owner only.
Requirement B Keep a list of who has successfully opened the vault using a mapping.
Part 1: Data Architecture
Before coding, define the state variables and modifiers needed.
1. State Variables
Which data types will you use for the owner and the list of authorized users?
address public owner;
mapping(________ => ________) public isAuthorized;
2. Modifier Logic
Write the pseudocode logic for a modifier that checks if msg.sender is the owner.
// Check if sender is owner...
// If not, revert with error message...
// Else, continue...
Part 2: Coding Implementation
Fill in the missing blocks to complete the SecureVault contract.
// SPDX-License-Identifier: MIT
pragma
solidity ^0.8.0;
contract
SecureVault {
address public owner;
string private secret;
mapping(address => bool) public hasAccess;
// 1. Constructor: Initialize owner
constructor
() {
}
// 2. Modifier: Only owner can call
modifier
onlyOwner() {
_;
}
// 3. Function: Grant access (Only owner)
function
grantAccess(address _user) public modifier? {
}
}
Part 3: Structuring Complex Data
Now, let's upgrade the vault. Instead of just a boolean for access, we want to store a UserRecord struct that tracks the last time they accessed the vault.
struct UserRecord {
bool isAuthorized;
uint256 lastAccessTimestamp;
}
Write a function getSecret() that:
Checks if the msg.sender is authorized in the struct.
Updates the lastAccessTimestamp to the current time (block.timestamp).
Returns the secret message.
function getSecret() public returns (string memory) {
// Your implementation here...
}
Logic and Modifiers Answer Key Answer Key & Logic Guide
Activity: The Locked Vault
Instructor Use Only
Part 1: Logic Mapping
1. Variables
address public owner;
mapping(address => bool) public isAuthorized;
2. Modifier Logic
require(msg.sender == owner, "Not authorized");
_;
Part 2: Coding Completion
// 1. Constructor
constructor
() {
owner = msg.sender;
}
// 2. Modifier
modifier
onlyOwner() {
require(msg.sender == owner, "Caller is not the owner");
_;
}
// 3. Function
function
grantAccess(address _user) public onlyOwner {
isAuthorized[_user] = true;
}
Part 3: Advanced Logic (getSecret)
function getSecret() public returns (string memory) {
// Check authorization within mapping of structs
require(userRecords[msg.sender].isAuthorized, "Access Denied");
// Update timestamp in state (Storage)
userRecords\[msg.sender\].lastAccessTimestamp = block.timestamp;
// Return the private string
return secret;
}
Security Reflection Answer
No, "private" does not mean "secret." In Solidity, the private keyword only prevents other contracts from reading the variable. Because the blockchain is public, any node operator can inspect the raw storage of a contract and see the value. To truly hide data, one must use Encryption off-chain or Zero-Knowledge Proofs .
Helpful Analogy
Compare private to a glass safe. Everyone can see what's inside (transparency), but they can't touch it or change it (integrity).
Common Error
Students often forget the _ in modifiers. Without it, the modified function will never execute its own body.
Gas Optimization Slides Lesson 03
The Gas Hunter
Economic Engineering: Analyzing gas costs, optimizing storage, and writing efficient Solidity code.
Gas 101
Every opcode costs "Gas units". Users pay in ETH (Gas Price × Gas Used).
Storage vs Memory
Storage is 100x more expensive. Managing data location is the #1 optimization.
Optimization Patterns
Short-circuiting, packing variables, and avoiding loops in state updates.
The Economics of Code
In traditional dev, code quality is about speed . In Blockchain, it's about money .
The Gas Equation
Fee = Gas Used × BaseFee
Gas Used: Determined by your code's complexity.
Base Fee: Determined by network congestion.
Out of Gas Error
If a transaction exceeds its limit, it fails, but the gas is still spent . Inefficient code is a liability.
The Halting Problem
Gas prevents infinite loops from crashing the network. You pay for every step of execution.
The Price List (Opcodes)
Opcode Description Gas Cost ADD / SUB Arithmetic operations 3 MLOAD / MSTORE Memory operations 3 SSTORE (set) Saving to Storage (new slot) 20,000 SLOAD (read) Reading from Storage 2,100* BALANCE Getting account balance 2,600*
*Costs vary based on "Cold" vs "Warm" access (EIP-2929).
Gas Saving Checklist
1
Avoid Loops in Storage
Never read/write to storage inside a for-loop. Copy to memory first!
2
Short-Circuiting
Put the cheapest check first in && and || statements.
3
Variable Packing
Group small types (uint8, bool) together in structs to share 256-bit slots.
4
Delete Unused Data
Using the delete keyword on storage items gives you a gas refund!
Gas Hunter Worksheet The Gas Hunter
Workshop: Code Refactoring & Cost Analysis
Student ID
Scenario: The Bloated Loop
An amateur developer wrote the following function to calculate the sum of all user balances. This function works fine in testing with 5 users, but fails on the mainnet once the user count grows.
Inefficient Code
uint256[] public balances;
uint256 public totalLiquidity;
function calculateTotal() public {
// Reset storage variable
totalLiquidity = 0;
// Loop through every balance in the array
for (uint i = 0; i < balances.length; i++) {
// Update Storage in every iteration!
totalLiquidity += balances\[i\];
}
}
1. Identify the Bottleneck
Every totalLiquidity += ... line triggers an SSTORE opcode. Based on your reference sheet, why is this catastrophic for a large array?
2. Memory vs. Storage Solution
Explain how using a temporary local variable inside the function could reduce the number of storage writes to exactly one.
Part 2: The Refactor
Refactor the calculateTotal function below to be gas-efficient. Use Memory for intermediate calculations.
function
calculateTotal() public {
}
Challenge: Variable Packing
Solidity stores data in 256-bit (32 byte) slots. The compiler can pack multiple smaller variables into one slot if they are declared sequentially.
Unpacked (3 Slots)
uint256 a; // Slot 0
uint128 b; // Slot 1
uint256 c; // Slot 2
Packed (2 Slots)
// Rearrange the variables above to
// occupy only 2 slots:
Did You Know?
Deleting a variable in storage can refund up to 1/5th of the total transaction gas cost!
Storage vs Memory Cheat Sheet Solidity Data Cheat Sheet
Storage vs. Memory vs. Calldata
Location Persistence Gas Cost Best Used For... Storage Permanent (lives on the blockchain ledger) Extreme (20k+ Gas) Account balances, ownership records, configuration. Memory Temporary (cleared after function call) Low (Scales linearly) Intermediate math, temporary strings, loops. Calldata Temporary (Immutable input data) Lowest (Cheapest) Large function arguments (arrays/structs) from users.
The "SSTORE" Rule
Updating a value from 0 to non-zero is 20,000 gas .
Updating an existing non-zero value is 2,900 gas (warm).
Setting a value back to 0 triggers a refund .
// Bad: 10 storage writes
for(uint i=0; i<10; i++) balance += 1;
// Good: 1 storage write
uint temp = balance;
for(uint i=0; i<10; i++) temp += 1;
balance = temp;
Variable Packing
Group types that add up to ≤ 256 bits together. Solidity will pack them into a single 32-byte slot.
WRONG:
uint8 a; // Slot 1
uint256 b; // Slot 2 (Gap used!)
uint8 c; // Slot 3
RIGHT:
uint256 b; // Slot 1
uint8 a; // Slot 2
uint8 c; // Slot 2 (Packed with a)
Network Terminology
Gwei
Smallest unit for gas prices.
(1 Gwei = 10-9 ETH)
Priority Fee
The "tip" you give to the miner/validator for faster inclusion.
Gas Limit
The absolute maximum gas you are willing to spend.
Token Standards Slides Lesson 04
Money Lego Sets
Composability and Standards: Understanding ERC-20, ERC-721, and Inter-Contract Communication.
ERC-20
The standard for fungible tokens (currencies, voting power).
ERC-721
The standard for non-fungible tokens (NFTs, unique assets).
Composability
Contracts talking to contracts to build complex financial systems.
ERC-20: Fungibility
Every unit is identical. If I give you 1 Token A and you give me back 1 Token A, nothing has changed.
mapping(address => uint256) balances;
transfer(address to, uint256 amt)
approve(address spender, uint256 amt)
Total Supply: 1,000,000
Alice: 500
Bob: 200
"An ERC-20 is just a mapping and a set of standardized functions that external apps expect to exist."
Building with Money LEGOs
User
Lending App
Accepts tokens and issues yield.
Calls Interface
DAI Token
transferFrom()
Why it works:
Because the Lending App knows exactly how transferFrom() works for ANY ERC-20, it can support thousands of tokens without ever knowing their internal code.
ERC-721: Non-Fungibility
mapping(uint256 => address) owners;
mapping(address => uint256) balances;
function ownerOf(uint256 tokenId) {
return owners[tokenId];
}
"Instead of a balance of units, we track the owner of a specific ID."
Metadata
Linked to a tokenURI which points to JSON data (image, attributes).
Uniqueness
Token ID #1 is not equal to Token ID #2. They are distinct assets.
Standard = Compatibility
Money Legos Project Guide Project: Money LEGOs
Inter-Contract Communication
Version 1.0.4
The Mission
You are building a Token Reward System . You need to write a "RewardManager" contract that interacts with an existing ERC-20 token contract. When a user completes a task in your manager, the manager tells the Token contract to send them rewards.
Project Requirements
Interface Definition: Define the ERC-20 interface for the functions transfer and balanceOf.
External Call: Use the interface to call the token contract from within your distributeReward function.
Balance Check: Ensure the manager contract actually holds enough tokens before attempting a transfer.
Part 1: The Call Stack
Sketch the interaction between the User, the RewardManager, and the Token contract.
Diagram Space
Part 2: Implementation
Fill in the missing logic to allow the RewardManager to communicate with the ERC20 contract.
// 1. Define the Interface (No implementation needed)
interface
IERC20 {
function
transfer(address recipient, uint256 amount) external returns (bool);
function
balanceOf(address account) external view returns (uint256); }
contract
RewardManager {
address public rewardTokenAddress;
// Initialize with the address of the ERC20 token
constructor
(address _token) {
rewardTokenAddress = _token;
}
// 2. Implement the reward logic
function
claimReward(uint256 _amount) public {
// TODO: Wrap the address in the interface
// TODO: Call the transfer function on the token contract
}
}
Project Challenge
What happens if the rewardTokenAddress is not actually a contract address, but a regular user wallet? What will happen to the IERC20(rewardTokenAddress).transfer(...) call?
Decentralized Logic Sequence
M-LEGO-04
ERC Interface Reference Standard
Interfaces
Cheat Sheet
ERC-20
Fungible tokens. Used for currency, voting, and utility. All units are equal.
Required Functions
totalSupply() Total units in existence.
balanceOf(addr) Units owned by address.
transfer(to, amt) Moves tokens from sender.
approve(spdr, amt) Sets allowance for others.
allowance(own, spdr) Check approved amount.
transferFrom(f, t, a) Moves tokens via allowance.
ERC-721
Non-Fungible Tokens. Unique IDs. Used for digital art, real estate, and domain names.
Required Functions
ownerOf(tokenId) Find owner of specific ID.
safeTransferFrom(...) Standard transfer method.
getApproved(tokenId) Who is approved for this ID?
setApprovalForAll(...) Approve operator for ALL IDs.
Implementing an Interface
// Wrap the address
IERC20 token = IERC20(address);
// Call standardized function
token.transfer(recipient, amount);
1. Define the interface at the top of your file (only function signatures).
2. Cast the target contract address to the interface type.
3. Execute the call as if it were a local function.
Testnet Deployment Checklist Mainnet Launch
Deployment & Configuration Checklist
Deployment to a live network (even a testnet) requires precision. One mistake in the constructor arguments or environment variables can result in a bricked contract or lost funds. Complete this checklist during your Hardhat deployment lab.
Phase 1: Environment Setup
Private Key Security
Is your private key stored in a .env file and added to .gitignore? (NEVER push keys to GitHub).
RPC Configuration
Do you have a valid Infura or Alchemy URL for the Sepolia testnet in your hardhat.config.js?
Testnet Faucet
Does your deployment address have at least 0.1 SepoliaETH to cover gas costs?
Phase 2: Contract Readiness
Compilation Check
Run npx hardhat compile. Are there any warnings about state mutability or unused variables?
Constructor Arguments
If your contract has a constructor, are the arguments correctly defined in your deployment script?
Event Implementation
Does every major state change (transfers, ownership shifts) emit an event?
Phase 3: Verification
Contract Address
Record your deployed address: 0x...
Etherscan Verification
Use hardhat-verify to upload source code. Can you view your functions on the explorer?
Warning
"Code is Law. Once deployed to a public network, you cannot 'un-send' the transaction. Always double-check your logic on a local fork before hitting the live network."
Event Logging Lab Guide Event Logging Lab
Connecting the Chain to the UI
Background
Smart contracts are isolated. They cannot perform "callbacks" to a website or mobile app. Instead, they write to the transaction logs . Front-end applications use libraries like ethers.js or viem to listen for these logs and update the interface in real-time.
event LogUpdate(
address indexed user,
string message
);
Lab Goal
Modify your SimpleStorage contract to emit an event every time the data changes. Then, use Etherscan to find and decode the log.
Step 1: The Contract
Complete the following contract by adding an event and emitting it.
pragma solidity ^0.8.0;
contract EventLab {
// TODO: Define an event 'ValueSet' that takes (address user, uint256 value)
uint256 public value;
function set(uint256 _v) public {
value = _v;
// TODO: Emit the ValueSet event
}
}
Step 2: Log Analysis
Deploy the contract and call the set function. Look at the Logs tab in your transaction details.
Topic [0]
This is the hash of the event signature. Copy it below:
0x...
Topic [1]
If you used 'indexed', the user address should appear here.
0x...
Critical Thinking
If you want to build a "Leaderboard" of users who have set the highest values, would it be better to store a list in a contract array (Storage) or to use a service like The Graph to index your events? Why?
Deployment Pipeline Slides Lesson 05
Launch Sequence
Deployment, Frameworks, and Event Logging: Transitioning from local IDEs to the live Blockchain ecosystem.
Hardhat / Foundry
Professional development environments for testing and scripting deployments.
Event Logging
How the blockchain "shouts" to the outside world. Crucial for UI updates.
Testnets
Deploying to Sepolia or Holesky to simulate real-world conditions without cost.
The Professional Pipeline
1
Local Environment
Fast testing on Hardhat Network. Instant blocks, no gas cost.
2
Testnet
Live network (Sepolia). Latency and real consensus, but tokens have no value.
3
Mainnet
Real value, real risk. Code is immutable. Audits are mandatory.
$ npx hardhat run scripts/deploy.js --network sepolia Launch initiated...
Event Logging
Contracts cannot talk to the internet. They can only emit Events which off-chain apps (Websites) listen for.
Why use Events?
UI Updates: Tell your React app when a transfer is complete.
Efficiency: Events are much cheaper than storage.
Indexing: Graph protocols use events to index data.
event Transfer(
address indexed from,
address indexed to,
uint256 amount
);
function sendMoney(...) public {
// ... logic ...
emit Transfer(msg.sender, to, amt);
}
The "Indexed" Keyword
Allows off-chain tools to filter events by specific addresses (topics).
Trust but Verify
Deployment is only 50% of the job. For your users to trust you, they must see your Source Code verified on Etherscan.
ABI
The Application Binary Interface. The 'instruction manual' your frontend needs to talk to the contract.
Block Explorer
Where every transaction, event, and state change is publicly visible for audit.