← Course home Module 6 / 19 Blockchain — Part 2

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.

70 min Module 6 ⟠ Part 2

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_1OP_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):

arithmetic.script Bitcoin Script
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.

Start
Empty stack: [ ].
OP_2
Push the integer 2. Stack: [2].
OP_3
Push 3. Stack: [2, 3].
OP_ADD
Pop the top two, push their sum. Stack: [5].
OP_5
Push 5. Stack: [5, 5].
OP_EQUAL
Pop two, push 1 if they are equal, 0 otherwise. Stack: [1].
End
Top of stack is 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.

Step 0
scriptSig runs first. It pushes Bob's signature and public key. Stack: [<sig>, <pubKey>] (top on the right).
Step 1
OP_DUP — Duplicate the top item (Bob's public key). Stack: [<sig>, <pubKey>, <pubKey>].
Step 2
OP_HASH160 — Pop the top, hash it (SHA-256 then RIPEMD-160), push the 20-byte result. Stack: [<sig>, <pubKey>, <computedHash>].
Step 3
scriptPubKey now pushes the expected pubKeyHash that Alice locked the output to. Stack: [<sig>, <pubKey>, <computedHash>, <expectedHash>].
Step 4
OP_EQUALVERIFY — Pop two, compare. If unequal, the script fails. Otherwise stack: [<sig>, <pubKey>].
Step 5
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:

multisig-2-of-3.script Bitcoin Script
// 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>
Reading the lock
"Push the number 2 (the minimum signatures needed), push three public keys, push the number 3 (the total keys), then run 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.
Why 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.
Why this matters
Multisig is Bitcoin's original "smart contract": a spending rule richer than "one person owns this," composed entirely from existing opcodes. No new VM, no new language. In Ethereum you would write this as a Solidity contract; in Bitcoin it is five opcodes. When people argue that Bitcoin Script is "underpowered," the counter-argument is that a huge fraction of useful patterns — multisig, escrow, time-locked refunds, atomic swaps, Lightning channels — can still be built out of this tiny opcode set.

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 same stateRoot, 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

Stack
Stack — 256-bit words, max depth 1024. All operations happen here.
Memory
Memory — Byte-addressable, volatile. Reset after each transaction. Cheap to use.
Storage
Storage — Key-value store (256-bit → 256-bit). Persists on-chain forever. Most expensive resource.
Calldata
Calldata — Read-only input data sent with the transaction. How you pass function parameters.

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

1
Each opcode has a fixed gas cost (e.g. ADD = 3 gas, SSTORE up to 20,000 gas for a new slot — full tiers explained below).
2
The sender sets a gas limit — the maximum gas they are willing to burn on this transaction.
3
The sender sets a fee price. Since August 2021 (EIP-1559) this is actually two numbers — 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.
4
Fee = gas used × effective gas price. Any gas below the limit that was not used is refunded; any margin between maxFeePerGas and the block's base fee + tip is also refunded.
5
If execution exceeds the gas limit → everything reverts (state changes undone) but the full gas up to the limit is still consumed and paid.
🔥

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

1. Write
You write the contract in Solidity (or Vyper) — a high-level language designed for the EVM.
2. Compile
The Solidity compiler (solc) converts your source code into bytecode and an ABI.
3. Deploy
You send a special transaction with no to address. The network assigns the contract a new address.
4. Interact
Users send transactions to the contract address with calldata specifying which function to call.
⚠️

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.

SimpleStorage.sol Solidity
// 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

  1. Script analysis: Write out the stack state at each step when a P2PKH script executes.
  2. Account model comparison: Alice has 10 ETH and sends 3 ETH to Bob. Compare Ethereum (Account) vs Bitcoin (UTXO).
  3. Gas calculation: Calculate the gas for 100 ADDs + 5 SLOADs + 2 SSTOREs. Convert to ETH and USD.
  4. EVM exploration: Find the USDT contract on Etherscan and examine a transfer event.
  5. Design thinking: Why can't Bitcoin Script implement an on-chain voting system? How does Ethereum make it possible?

Self-Check Questions

  1. Why is Bitcoin Script not Turing-complete? What advantage does this give?
  2. What are the two types of Ethereum accounts? How do they differ?
  3. Name the four data locations in the EVM and explain when each is used.
  4. Why does writing to storage cost ~7,000× more gas than an ADD operation?
  5. 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.

Next module

You understand the machine. Now it's time to program it. Module 7 introduces Solidity — the language of Ethereum smart contracts.

Module 7: Solidity Fundamentals →
📋 Course Project

Ready to apply everything you've learned? The end-of-course project combines smart contracts, AI, and Node.js into a real decentralised academic assistant.

View Project Brief →