From Bitcoin Script to the Ethereum EVM
Bitcoin proved that programmable money is possible — but its scripting language was intentionally limited. Ethereum asked: what if the blockchain were a general-purpose computer? This module bridges the two worlds.
Recap: The Bridge to Part 2
In Part 1, you built a cryptocurrency from scratch — wallets, transactions, blocks, mining, consensus. You understand how blockchains work. But Bitcoin's blockchain can only do one thing: move coins from address A to address B. What if you wanted the blockchain to execute arbitrary logic?
Part 1 — What You Know
Hash functions, digital signatures, Merkle trees, UTXO model, proof of work, Nakamoto consensus, gossip protocol, forks, Proof of Stake. You can explain how Bitcoin works from private key to global consensus.
Part 2 — What's Next
Programmable blockchains. Smart contracts. The EVM. Solidity. Tokens. DApps. You'll go from understanding blockchains to building on them.
The Question
Bitcoin can transfer value. But can it enforce a rental agreement? Run an auction? Issue a token? No. And that's by design.
Bitcoin Script: Powerful but Limited
Every Bitcoin transaction output contains a small program — written in a language called Bitcoin Script — that defines the condition under which that output can be spent. Spending is not just "send coins": it is proving you can satisfy the program. Every full node on the network runs the program during validation, and the rules of the network are the rules of this tiny language. Understanding it deeply is the foundation for understanding why Ethereum had to invent something bigger.
What "Stack-Based" Actually Means
The Paradigm: Push, Pop, Check
Bitcoin Script is executed on a stack — a last-in, first-out pile of values. A program is just a linear sequence of opcodes (operations) and data items. The execution rules are mechanical:
- Data item. Push it onto the stack.
- Opcode. Pop the operands it needs from the top of the stack, compute, and push the result (if any) back.
- End of program. The script is valid if — and only if — the top of the stack is a non-zero value.
There are no variables, no named functions, no heap, no loops, and no recursion. The only "memory" the program has is the stack itself (plus a small secondary alt stack you will rarely see in practice). That is the entire computational model.
Why a Stack, and Not Variables?
Three reasons. (1) Trivial to implement: every full node — on phones, Raspberry Pis, satellites — must run the exact same VM with bit-for-bit identical results. A stack machine has almost no moving parts, so consensus on its behaviour is easy. (2) No allocator: scripts do not allocate memory dynamically, so there is nothing that can OOM the node. (3) Deterministic cost: because the set of opcodes is small and their behaviour is bounded, nodes can decide up-front whether a script is cheap enough to run without needing a gas meter. Stack machines are the Forth-family heritage the whitepaper hints at — simple enough to audit on paper.
The Mental Model: A Puzzle and Its Solution
Lock + Key
Every Bitcoin output comes with a locking script (also called scriptPubKey) — a little puzzle the output's creator attaches: "the person who wants to spend me must satisfy this program." Later, whoever wants to spend that output must provide an unlocking script (scriptSig) — the solution to the puzzle. The node concatenates solution + puzzle, runs the combined program on an empty stack, and if the stack ends with TRUE, the spend is authorised. The locking script never says "this belongs to address X" — it says "spending me requires providing a signature over this transaction that verifies against the public key whose hash is H." Ownership is whatever the puzzle says it is. This is why Bitcoin can express more than "A pays B": the puzzle can require two-of-three signatures, a preimage of a hash, a future block height, or any combination.
The Opcode Families
Bitcoin Script has roughly 100 usable opcodes grouped into a handful of families. You rarely need more than a dozen to read real scripts, but knowing the categories gives you the vocabulary to parse any script you encounter on a block explorer.
| Family | Examples | What they do |
|---|---|---|
| Push data | OP_0, OP_1…OP_16, OP_PUSHDATA1 |
Push constants or raw bytes onto the stack. |
| Stack manipulation | OP_DUP, OP_DROP, OP_SWAP, OP_OVER |
Rearrange what is already on the stack without doing any math. |
| Arithmetic & logic | OP_ADD, OP_SUB, OP_EQUAL, OP_BOOLAND |
Basic arithmetic and comparisons. Note: many opcodes (OP_MUL, OP_DIV, OP_LSHIFT…) are disabled for safety. |
| Cryptography | OP_HASH160, OP_SHA256, OP_CHECKSIG, OP_CHECKMULTISIG |
Hash things, verify ECDSA or Schnorr signatures. The heart of ownership. |
| Flow control | OP_IF, OP_ELSE, OP_ENDIF, OP_VERIFY, OP_RETURN |
Conditional branches (bounded — no jumps backwards, so no loops). OP_RETURN marks an output as provably unspendable, commonly used to embed small pieces of data on-chain. |
| Time locks | OP_CHECKLOCKTIMEVERIFY (CLTV), OP_CHECKSEQUENCEVERIFY (CSV) |
Refuse to unlock an output until a given block height or relative delay has passed. Added in 2015–2016 (BIPs 65 and 112), this is what makes the Lightning Network possible. |
Worked Example 1: A Trivial Arithmetic Script
Bitcoin's real-world scripts almost never do arithmetic — they do hash checks and signature verification. But arithmetic is the easiest place to see the stack machine in action. Consider this short script (which is not a valid Bitcoin transaction, just a pedagogical program):
OP_2 OP_3 OP_ADD OP_5 OP_EQUAL
Read it left to right; the stack is shown with the top on the right.
[ ].OP_2[2].OP_3[2, 3].OP_ADD[5].OP_5[5, 5].OP_EQUAL[1].1 (non-zero) → script is valid.Notice what the script did not do: it did not declare a variable, it did not name a function, it did not jump. It just pushed, popped, and compared. Every Bitcoin Script on the main network — even the most complex — is the same mechanical shape as this one, with different opcodes doing different work on the stack.
Example: Pay-to-Public-Key-Hash (P2PKH)
The most common Bitcoin transaction type. When Alice pays Bob, the transaction contains two scripts:
- Locking script (scriptPubKey) — set by Alice: "Only someone who can prove they own Bob's public key can spend this."
- Unlocking script (scriptSig) — provided by Bob when he spends: "Here's my public key and my signature."
The node concatenates both scripts — scriptSig first, then scriptPubKey — and executes them on a single stack. The locking script is:
OP_DUP OP_HASH160 <pubKeyHash> OP_EQUALVERIFY OP_CHECKSIG. The unlocking script that Bob provides is just: <sig> <pubKey>. If the final result on the stack is TRUE, the transaction is valid.
[<sig>, <pubKey>] (top on the right).OP_DUP — Duplicate the top item (Bob's public key). Stack: [<sig>, <pubKey>, <pubKey>].OP_HASH160 — Pop the top, hash it (SHA-256 then RIPEMD-160), push the 20-byte result. Stack: [<sig>, <pubKey>, <computedHash>].[<sig>, <pubKey>, <computedHash>, <expectedHash>].OP_EQUALVERIFY — Pop two, compare. If unequal, the script fails. Otherwise stack: [<sig>, <pubKey>].OP_CHECKSIG — Pop the pubKey and signature, verify the signature against the transaction data. Push TRUE. Transaction is valid.Worked Example 2: 2-of-3 Multisig
A real-world pattern. Alice wants three co-signers — herself, her partner, and a lawyer — and wants any two of the three to be able to authorise a spend. The locking script for a 2-of-3 multisig output is:
// Locking script (scriptPubKey), set by Alice:
OP_2 <pubKey_Alice> <pubKey_Partner> <pubKey_Lawyer> OP_3 OP_CHECKMULTISIG
// Unlocking script (scriptSig), later provided by any two of them:
OP_0 <sig_1> <sig_2>
OP_CHECKMULTISIG." The opcode pops all of that off the stack, and checks that at least 2 of the 3 keys have valid signatures over the transaction.OP_0?OP_CHECKMULTISIG has a historical off-by-one bug: it pops one extra item off the stack that it never looks at. By convention, the unlocking script provides OP_0 (a zero byte) as filler so the opcode has something to discard. This bug is locked into consensus forever — fixing it would be a hard fork.What Bitcoin Script Cannot Do
No loops — No for, no while, no backward jumps. Every script runs in bounded time that is a simple function of its length.
No persistent state — Each script runs in isolation. It cannot read what happened in a previous transaction or write something another script can read later. The only "state" that persists between transactions is the UTXO set itself.
No inspection of the transaction — Scripts cannot see transaction details like amounts, recipients, or fees. They cannot answer "pay Bob only if the fee is below 5%." This kills most auction-style or conditional-payment logic.
Not Turing-complete — A formal statement: Bitcoin Script cannot simulate an arbitrary Turing machine. There are computations it cannot express at all, not just ones it makes expensive.
What "Not Turing-Complete" Actually Buys You
A Turing-complete language can loop forever, which means — by Rice's theorem and the halting problem — you cannot reliably answer "will this program terminate?" just by looking at the source. That is a disaster in a system where every full node must run every script and agree on the result within seconds. Bitcoin side-steps the halting problem by construction: no loops, no jumps backwards, bounded opcode count per script. Every script is guaranteed to terminate, in a time you can compute just from its byte length, with no gas meter needed. Ethereum will accept Turing-completeness and pay for it with gas; Bitcoin refused the problem entirely. Neither answer is "right" — they are different engineering trade-offs.
Bitcoin Script Has Not Stood Still
Course material sometimes implies Bitcoin's scripting is frozen in 2009. That is not accurate — it has evolved through a sequence of soft forks, each making scripts smaller, cheaper, or more private without abandoning the stack-based core.
| Type | Year | What changed |
|---|---|---|
| P2PKH | 2009 | The original: lock an output to HASH160(pubKey). The script we just walked through. |
| P2SH (BIP 16) | 2012 | Lock an output to the hash of a script instead of a public key. The spender later reveals the full script and a solution that satisfies it. This is what enabled complex custom scripts (multisig, escrow) without bloating output sizes. |
| SegWit (BIP 141, P2WPKH / P2WSH) | 2017 | Moved signature data out of the main transaction into a separate witness area. Fixed transaction malleability (which unlocked the Lightning Network) and reduced the weight of scripts in block-size accounting. |
| Taproot (BIP 341/342, P2TR) | 2021 | Replaced ECDSA with Schnorr signatures (smaller, aggregatable) and introduced Tapscript. Multi-party scripts now look identical on-chain to single-signer scripts unless a non-cooperative path is taken — a big privacy and fungibility win. |
Why Satoshi Made It This Way
Security by construction. A Turing-complete language on a global consensus network means any bug could halt the network or drain funds, and every node would have to rerun every computation from genesis to verify the chain. Satoshi chose safety over expressiveness. Every Bitcoin Script is guaranteed to terminate — no infinite loops, no gas meter, no surprises — at the cost of being unable to express rich application logic. Ethereum made the exact opposite trade. The next sections explain how.
Ethereum's Big Idea: A World Computer
In 2013, Vitalik Buterin — then 19 years old — published the Ethereum whitepaper. His insight was radical: instead of building a new blockchain for every new application, build a single blockchain that can run any program. A world computer.
The Key Insight
"I thought [those in the Bitcoin community] weren't approaching the problem in the right way. I thought they were going after individual applications; they were trying to kind of explicitly support each [use case] in a sort of Swiss Army knife protocol." — Vitalik Buterin, 2014. Ethereum's solution: one general-purpose platform with a Turing-complete programming language.
Two Types of Accounts
EOA (Externally Owned Account)
Controlled by a private key. This is your wallet — like a Bitcoin address. It can send transactions and hold ETH. No code attached.
Contract Account
Controlled by code. Has its own address, can hold ETH, and executes logic when called. Once deployed, the code is immutable — it runs exactly as written, forever.
Ethereum = A Global State Machine
This phrase is repeated in every Ethereum explainer, but it is rarely unpacked. It is a precise technical claim, and it is the central idea of the protocol — everything else in Part 2 builds on it. Let us take it apart.
Ledger vs State Machine
Bitcoin's chain is a ledger: an append-only list of transactions, from which a receiver can derive balances by summing their unspent outputs. The "state" is implicit — whatever you can compute from the transaction history. Ethereum flips this. The chain explicitly maintains a giant data structure called the world state, and each block says exactly what that state looks like after all of its transactions have been applied. The transactions are just inputs; the state is the point.
What Is the World State?
At any given moment, the Ethereum world state is a single giant dictionary: it maps every 20-byte address that has ever been touched to a small record — the account state — describing that address. There is exactly one world state across the whole network; every honest node agrees on it byte-for-byte. Storing it naively would be impossible (too large to send between peers, too slow to diff), so Ethereum commits to it with a cryptographic data structure we will meet in a moment. First, the shape of the records it contains.
An Account Has Four Fields — Always
Whether you are an EOA or a contract, your account state is exactly these four fields. The EVM treats them uniformly; the only runtime difference between EOAs and contracts is that an EOA's codeHash points at the empty string while a contract's points at real bytecode.
| Field | Meaning | For an EOA | For a contract |
|---|---|---|---|
nonce |
A counter. | Number of transactions this account has successfully sent. | Number of contracts this contract has created (via CREATE). |
balance |
ETH held by the account, in Wei. | Updated on send/receive. | Contracts can receive and hold ETH too. |
storageRoot |
The root hash of this account's own storage trie — a mini key/value store of 32-byte slots that only this account can write to. | Empty — EOAs have no storage. | Every Solidity state variable lives somewhere in this trie. |
codeHash |
Keccak-256 hash of the bytecode associated with this address. | Hash of the empty string. | Hash of the deployed runtime bytecode (immutable once set). |
Why This Shape Matters
Everything you will do in Part 2 is a consequence of these four fields. Deploying a contract sets the codeHash. Calling a function runs the code identified by codeHash against the account's storage trie, possibly changing storageRoot. Sending ETH updates balance on two accounts. Every signed transaction increments the sender's nonce. That is the whole model — four fields, read and written according to rules enforced by the EVM.
Committing the State: the Merkle Patricia Trie
A world state with potentially hundreds of millions of accounts cannot be sent over the wire, nor re-hashed in full every time a transaction changes one byte. Ethereum solves both problems with a Merkle Patricia Trie (MPT): a combination of a trie (efficient prefix-based key lookup) with the Merkle idea from Module 2 (every internal node hashes its children).
Three Properties That Make It Work
- One root, one hash. The entire world state — every account, every storage slot, every byte of code — collapses into a single 32-byte hash called the
stateRoot. If two nodes compute the samestateRoot, they agree on the state of the world. If they compute different roots, one of them is wrong. - Incremental updates are cheap. Changing one storage slot only re-hashes the nodes on the path from that slot up to the root — logarithmic work, not linear. Blocks can change thousands of slots per second without the client needing to re-walk the whole tree.
- Light clients can verify state. Given the
stateRoot, any node can ask a peer for a Merkle proof of a specific account's state (typically 10–20 sibling hashes). The light client verifies the proof in milliseconds and does not need the whole trie.
Note the nesting: the world state's MPT has accounts at its leaves, but each account's storageRoot is itself the root of a separate MPT over that contract's storage. Roots-of-roots-of-roots is how Ethereum commits to multi-gigabyte state with a single hash.
Where the State Root Lives
Every Ethereum block header contains three such roots — this is what ties the state into consensus:
stateRoot— world state after all transactions in this block have executed.transactionsRoot— an MPT over the transactions included in this block.receiptsRoot— an MPT over the receipts produced by those transactions (status, gas used, emitted logs).
When a validator proposes a block, every other validator re-executes the transactions from the parent's stateRoot and checks that they reach the same new stateRoot. If the computed root does not match, the block is rejected. This is the mechanism by which tens of thousands of nodes, run by people who do not know each other, stay bit-for-bit synchronised on a continuously-changing database.
The State Transition Function
T(state, transactions) → new state
Ethereum's protocol, stripped down, is exactly this one function: given the current world state and an ordered list of transactions in a block, produce the new world state. This function — the "state transition function", usually written σ' = Υ(σ, T) in the Yellow Paper — is entirely deterministic. No random numbers (except block.prevrandao, itself derived from the beacon chain), no wall-clock time calls, no file I/O, no network. Two nodes starting from the same σ and applying the same T must produce byte-identical σ'. The whole point of the EVM design we will meet in the next section is to enforce this determinism rigorously, so that every node can independently compute the next state and check that the proposed block is correct.
Where the State Came From: the Genesis Block
State Is Not Created by a Transaction
The world state did not start empty and get built up by transactions alone. On 30 July 2015, Ethereum launched with a hand-crafted genesis block whose stateRoot already committed to roughly 72 million ETH distributed across thousands of addresses — the result of the 2014 crowdsale plus allocations to the Ethereum Foundation and early contributors. Every subsequent state is deterministically derived from that genesis by applying transactions. This is also how private networks work: you write a genesis.json describing the initial account balances and parameters, every node loads it, and the chain starts from there. You will do this yourself in Module 17 when you spin up a private Besu network.
Account Model vs UTXO Model
| Property | Bitcoin (UTXO) | Ethereum (Account) |
|---|---|---|
| Balance tracking | Sum of unspent outputs | Single balance field per account |
| State | Stateless — UTXOs consumed/created | Stateful — accounts persist between TXs |
| Smart contracts | Very limited (Bitcoin Script) | Full Turing-complete EVM |
| Parallel processing | Easier — UTXOs are independent | Harder — shared global state |
| Privacy | Better — new address per TX | Worse — address reuse common |
The Nonce: Ethereum's Replay & Ordering Mechanism
Because Ethereum tracks balances by account (not by UTXO), it faces a problem Bitcoin does not: replay and ordering. If Alice signs a "send 1 ETH to Bob" transaction, what stops Bob from re-broadcasting that same signed transaction ten times and draining Alice's account? And if Alice broadcasts three transactions at once, which one runs first?
Every Account Has a Nonce
Each EOA carries a nonce — a counter starting at 0 that increments by 1 with every transaction the account successfully sends. Every transaction you sign includes the current nonce. The network enforces strict rules: (1) a transaction with a nonce lower than the account's current nonce is rejected (already used), and (2) a transaction with a nonce higher than the next expected one sits in the mempool waiting until its turn comes. This single counter solves replay protection and ordering in one mechanism.
Why You'll Care
Nonces explain behaviours you'll see constantly: transactions "stuck pending" (because a lower-nonce tx is missing), "nonce too low" errors in development (usually from resetting a local chain without telling your wallet), and the reason you cannot submit two unrelated transactions in parallel from the same account — they must queue. Contract accounts have a nonce too, but it tracks how many contracts they have created, not transactions sent.
A Small But Important Detail: Ethereum Uses Keccak-256
Keccak-256, Not SHA-256
In Part 1 you learned that Bitcoin uses SHA-256 everywhere — block headers, txids, Merkle trees. Ethereum makes a different choice: it hashes with Keccak-256. Keccak was the winning submission of the SHA-3 competition, but the version the NIST SHA-3 standard ships is slightly tweaked from the original Keccak. Ethereum locked in the original Keccak before NIST finalised SHA-3, so Ethereum's keccak256(x) and standard SHA3-256(x) produce different hashes for the same input. Every address is the last 20 bytes of keccak256(publicKey), every function selector is the first 4 bytes of keccak256(signature), and every storage slot index is keccak256 of something. If you try to reproduce these hashes with sha256 from Module 2, you will get wrong answers. Use keccak256 (Node.js: the keccak package, or ethers/viem).
The EVM: Ethereum Virtual Machine
The EVM is the runtime that executes smart contract code on every Ethereum node. Think of it as a global, decentralised CPU — except every node runs the same computation and must get the same result.
What Makes the EVM Special
Deterministic — Given the same input, every node produces the exact same output. No randomness, no system calls. Pure computation.
Isolated (sandboxed) — Contract code cannot access the file system or other contracts' storage directly. Only message calls.
Metered — Every operation costs gas. Prevents infinite loops and DoS attacks. Gas runs out → state reverts.
EVM Architecture
Opcodes: The EVM's Instruction Set
Smart contracts are compiled to bytecode — a sequence of low-level instructions called opcodes. The EVM has ~140 opcodes. Here are the key categories:
| Category | Examples |
|---|---|
| Arithmetic | ADD, MUL, SUB, DIV, MOD, EXP |
| Stack operations | PUSH1…PUSH32, POP, DUP1…DUP16, SWAP1…SWAP16 |
| Memory & Storage | MLOAD, MSTORE, SLOAD, SSTORE |
| Control flow | JUMP, JUMPI, STOP, RETURN, REVERT |
| Environment | CALLER, CALLVALUE, GASPRICE, BLOCKHASH |
| Contract interaction | CALL, DELEGATECALL, STATICCALL, CREATE, SELFDESTRUCT* |
* SELFDESTRUCT is effectively deprecated. Since the Cancun upgrade (March 2024, EIP-6780), it no longer deletes a contract's code or storage — it only forwards remaining ETH — except when called in the same transaction that created the contract. Any tutorial that teaches "destroy a contract to refund gas" predates Cancun. Treat SELFDESTRUCT as a legacy opcode you may see in audits, not something to reach for.
CALL vs DELEGATECALL vs STATICCALL
When one contract invokes another, it chooses how with one of three opcodes. The difference is about whose state and whose address the called code sees — this distinction is the foundation of proxy patterns (Module 8) and of a whole class of security bugs (Module 13).
| Opcode | Runs in whose context? | Can modify state? | Typical use |
|---|---|---|---|
CALL |
Callee's storage; msg.sender = caller |
Yes | Normal contract-to-contract call; sending ETH. |
DELEGATECALL |
Caller's storage; msg.sender preserved |
Yes (on the caller's storage!) | Proxy patterns, libraries. The called code acts as if it were part of the caller. |
STATICCALL |
Callee's storage | No — any state change reverts | Safe read-only calls; what Solidity view/pure external calls compile to. |
Why DELEGATECALL Is Powerful and Dangerous
If contract A DELEGATECALLs into contract B's code, B's code runs but reads and writes A's storage. This is how upgradable proxies work: a tiny proxy contract holds all the data, and it delegatecalls into an implementation contract that holds all the logic. Swap the implementation address, and the "same" contract now has new behaviour without losing state. It is also how the Parity multi-sig wallet lost ~$150M in 2017 — a DELEGATECALL into a library that the attacker then reinitialised. You'll see both sides of this in Modules 8 and 13.
Bytecode Example: 1 + 2
The Solidity expression 1 + 2 compiles to three
opcodes: PUSH1 0x01 (push 1), PUSH1 0x02 (push 2),
ADD (pop both, push 3). That's it — the EVM is fundamentally simple. The
complexity comes from composing thousands of these tiny operations.
EVM vs Bitcoin Script — The Fundamental Difference
Bitcoin Script asks: "Is this transaction valid?" The EVM asks: "What is the new state of the world after this transaction?" Bitcoin Script is a validator. The EVM is a computer.
Gas: Paying for Computation
If the EVM is Turing-complete, programs can loop forever. This is the halting problem — you can't know in advance if a program will terminate. Ethereum's solution: make the caller pay for every computational step. That payment is called gas.
Why Gas Exists
Without gas, anyone could deploy a contract with an infinite loop and freeze every node in the network. Gas is both a DoS protection mechanism and an economic incentive: validators are compensated for the computational resources they provide.
First: ETH, Gwei, Wei
Before we can talk about fees, you need the three units Ethereum uses everywhere. ETH is the human-facing unit; Gwei is the unit gas prices are quoted in; Wei is the atomic integer unit the EVM actually works with (there are no floats anywhere in the EVM).
| Unit | Value in Wei | When you see it |
|---|---|---|
| Wei | 1 | Inside contracts. msg.value, balanceOf, transfer arguments are all in Wei. |
| Gwei | 109 (1,000,000,000) | Gas prices. Etherscan shows "23 gwei" — that is 23 × 109 Wei per unit of gas. |
| ETH | 1018 | Wallet balances, trading pairs, human conversation. |
Worked Example
A plain ETH transfer uses 21,000 gas. If the network gas price is 20 gwei, the fee is 21,000 × 20 gwei = 420,000 gwei = 0.00042 ETH. At an ETH price of $3,000 that's about $1.26. Every fee calculation you will ever do in this course follows the same pattern: gas used × gas price (in gwei) → convert to ETH → convert to fiat.
How Gas Works
maxFeePerGas and maxPriorityFeePerGas — explained in the next card. Older wallets show it as a single "gas price"; that is a simplified view of the same thing.maxFeePerGas and the block's base fee + tip is also refunded.EIP-1559: Base Fee + Priority Fee
Since August 2021, every Ethereum block has a base fee — a per-gas price that the protocol itself computes from the previous block's fullness, adjusting up when blocks are full and down when they are empty. Users do not pay the base fee to any validator: it is burned, permanently removed from the ETH supply. On top of the base fee, users optionally add a priority fee (a tip) paid to the block proposer to incentivise inclusion. The split makes fees predictable (you can see next block's base fee before signing) and makes ETH mildly deflationary in high-usage periods.
What Your Wallet Actually Sets
An EIP-1559 transaction carries two fee fields:
maxFeePerGas— the absolute ceiling per unit of gas you will pay. If the block's base fee rises above this, your tx simply waits.maxPriorityFeePerGas— the maximum tip you are willing to pay the proposer, on top of the base fee.
The effective price you actually pay per gas is min(maxFeePerGas, baseFee + maxPriorityFeePerGas). The base-fee portion is burned; the remainder (up to the tip cap) goes to the proposer. Anything below maxFeePerGas you did not use is refunded — so overpaying is usually safe.
Gas Costs: What's Expensive?
| Operation | Gas cost | Why |
|---|---|---|
| ADD / SUB | 3 | Simple CPU operation |
| MUL / DIV | 5 | Slightly more complex |
| SLOAD (read storage) | 2,100 cold · 100 warm | First access in a tx is "cold"; subsequent reads of the same slot are "warm" and far cheaper (EIP-2929, Berlin 2021). |
| SSTORE (write storage) | up to 22,100 | Zero → non-zero is the expensive case (~22,100 cold). Non-zero → non-zero is ~5,000 (cold) or 2,900 (warm). Clearing a slot to zero gets a partial refund. |
| CREATE (deploy contract) | 32,000+ | Allocates new on-chain storage |
| Simple ETH transfer | 21,000 | Base TX cost |
Key Insight: Storage Dominates Cost
Writing to storage costs ~7,000× more than addition. This is why Solidity developers obsess over storage optimisation — and why you'll see patterns like packing multiple values into a single 256-bit slot in Module 8.
Smart Contracts: Code on the Blockchain
A smart contract is just a program that lives at an Ethereum address. It has code (immutable) and storage (mutable). When someone sends a transaction to the contract's address, the EVM executes the code.
The Smart Contract Lifecycle
solc)
converts your source code into bytecode and an ABI.to address. The network assigns the contract a new address.
Immutability: The Double-Edged Sword
Once deployed, a contract's code cannot be changed. If there's a bug, you can't patch it. This is both the greatest strength (trustless) and the greatest risk (The DAO hack lost $60M). In Module 8, you'll learn the proxy pattern.
Solidity Preview: Your First Contract
Don't worry about the syntax yet — Module 7 will cover Solidity
in depth. Just notice the structure: a contract keyword (like a class), a state
variable stored on-chain, and a function that modifies it.
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract SimpleStorage {
uint256 public storedValue; // state variable (on-chain)
function set(uint256 _value) external {
storedValue = _value; // writes to storage
}
function get() external view returns (uint256) {
return storedValue; // reads from storage (free if called off-chain)
}
}
The ABI: How the Outside World Talks to a Contract
Step 2 of the lifecycle mentions that the compiler produces an ABI alongside the bytecode. Bytecode is what the EVM runs; the ABI (Application Binary Interface) is what callers need. It is a JSON description of every public and external function, every event, and every argument type — the contract's public API in machine-readable form. A wallet, a frontend, or another contract uses the ABI to know how to encode a call and decode the response.
Function Selectors: the First 4 Bytes of Calldata
When you call set(42) on the contract above, your wallet does not transmit the string "set(42)". It computes keccak256("set(uint256)"), takes the first 4 bytes of that hash, and prepends them to the ABI-encoded arguments. That 4-byte prefix is the function selector — how the EVM knows which function you meant. The rest of calldata is a sequence of 32-byte words, one per argument (with dynamic types like bytes or string using offset pointers into a tail area). For set(42), the full calldata looks like:
0x60fe47b1 // selector = keccak256("set(uint256)")[0:4]
000000000000000000000000000000000000000000000000000000000000002a // 42, padded to 32 bytes
The first thing compiled Solidity does on entry is read the 4-byte selector and dispatch to the matching function body. No selector match → the fallback function runs, or the call reverts. You will see selectors everywhere: Etherscan's "Input Data" view, in reverting errors, and when building raw transactions.
Events and Logs: The Contract's Notification Channel
Smart contracts cannot push data to users — they cannot open a socket or call an API. Instead they emit events, and off-chain code listens. An emit Transfer(from, to, amount) statement writes a log into that transaction's receipt. Logs live in a special low-cost area of the chain: nodes keep them, clients can query them, but smart contracts cannot read them back. They are the blockchain's equivalent of a server-sent event stream.
Topics, Data, and Why It Matters
Every log has up to 4 topics and a data blob. Topic 0 is almost always the keccak256 hash of the event signature (e.g. Transfer(address,address,uint256)) — the "event selector". Topics 1–3 are the indexed event parameters you marked in Solidity; the rest go into the unindexed data blob. Indexed parameters are searchable (bloom filters let clients ask "give me every Transfer where to = 0x1234…" in milliseconds); unindexed ones are cheaper but opaque to filters. In Module 12 you'll subscribe to these logs from a DApp frontend; every major block explorer is essentially a log indexer.
Putting It All Together: Transaction Lifecycle
Let's trace a complete Ethereum transaction from the moment a user clicks "Send" in their wallet to the final state update on-chain. Before we walk the steps, it helps to know what a transaction actually is — the bytes your wallet signs.
Anatomy of an Ethereum Transaction
A modern (EIP-1559 / type-2) Ethereum transaction is a handful of fields the sender fills in, plus a signature.
| Field | Meaning |
|---|---|
nonce |
Sender account's current nonce (replay protection & ordering — see section 3). |
to |
Recipient address. null or empty when deploying a contract. |
value |
Amount of ETH to send, in Wei. 0 for a pure contract call. |
data (calldata) |
Empty for a plain ETH transfer; selector + ABI-encoded args for a function call; the contract's bytecode + constructor args for a deployment. |
gasLimit |
Max gas the sender is willing to spend. |
maxFeePerGas, maxPriorityFeePerGas |
EIP-1559 fee caps (see section 5). Legacy transactions have a single gasPrice instead. |
chainId |
Identifies the network (mainnet = 1, Sepolia = 11155111, local Hardhat = 31337). Signed into the tx so a mainnet signature cannot be replayed on a testnet. |
v, r, s |
The ECDSA signature over all the fields above. r and s are the signature pair (same ECDSA as Bitcoin, same secp256k1 curve); v is a recovery byte that lets nodes reconstruct the sender's address from the signature — which is why Ethereum transactions do not carry a "from" field. |
The Sender Is Derived, Not Sent
Notice there is no from field. Nodes run ECDSA public-key recovery on (v, r, s) plus the transaction hash to recover the signer's public key, then derive the address as keccak256(pubKey)[12:32]. That is the "sender". If the signature does not recover to an account with sufficient balance and the right nonce, the tx is rejected at the mempool. This is the same cryptography from Module 2, just wired up differently than Bitcoin's input/scriptSig model.
1. Sign
The user's wallet creates a transaction object and signs it with the private key — exactly like Bitcoin (Module 2).
2. Broadcast
The signed transaction is broadcast via gossip protocol. It lands in the mempool of each node.
3. Include
A validator selects the transaction from the mempool and includes it in their proposed block.
4. Execute
The EVM runs the transaction: loads bytecode, sets up context, and processes opcodes one by one.
5. Update State
If execution succeeds: balances change, storage updates, events are emitted. If it fails: all changes roll back, but gas is consumed.
6. Receipt & Logs
A transaction receipt is generated with status, gas used, and event logs. DApps listen for events in real-time.
Etherscan: A Window into the EVM
Block explorers like Etherscan let you inspect every transaction, every contract, every event. You can read contract source code, see internal calls, trace gas usage, and decode function parameters. It's the developer's most important debugging tool — and you'll use it daily in Part 2.
Exercise & Self-Check
Exercises
- Script analysis: Write out the stack state at each step when a P2PKH script executes.
- Account model comparison: Alice has 10 ETH and sends 3 ETH to Bob. Compare Ethereum (Account) vs Bitcoin (UTXO).
- Gas calculation: Calculate the gas for 100 ADDs + 5 SLOADs + 2 SSTOREs. Convert to ETH and USD.
- EVM exploration: Find the USDT contract on Etherscan and examine a transfer event.
- Design thinking: Why can't Bitcoin Script implement an on-chain voting system? How does Ethereum make it possible?
Self-Check Questions
- Why is Bitcoin Script not Turing-complete? What advantage does this give?
- What are the two types of Ethereum accounts? How do they differ?
- Name the four data locations in the EVM and explain when each is used.
- Why does writing to storage cost ~7,000× more gas than an ADD operation?
- What happens when a transaction runs out of gas? Is the fee refunded?
Module Summary
You've crossed the bridge from Bitcoin to Ethereum. You understand why Bitcoin Script is limited by design, how Ethereum introduced a Turing-complete virtual machine (the EVM) with persistent state, why gas exists as both DoS protection and economic mechanism, and how smart contracts go from Solidity source to on-chain execution. In Module 7, you'll write your first Solidity smart contracts.