Ethereum Basics

Ethereum expanded blockchain from a payment network to a world computer. Understanding Ethereum is essential because Solana borrows many concepts while making different architectural choices.

The Vision: A World Computer

Bitcoin's Script is intentionally limited. Vitalik Buterin asked: what if we made a blockchain with a Turing-complete programming language?

The result was Ethereum—a blockchain that can run arbitrary programs called smart contracts.

Text
Bitcoin: "Send 5 BTC to Alice"

Ethereum: "If condition X is met, execute function Y,
          which might transfer tokens, update data,
          call other contracts, and emit events"

Ethereum's Account Model

Unlike Bitcoin's UTXOs, Ethereum uses an account-based model:

Externally Owned Accounts (EOAs)

Regular user accounts controlled by private keys:

JavaScript
// EOA structure
{
  address: "0x742d35Cc6634C0532925a3b844Bc9e7595f...",
  balance: 1500000000000000000n,  // 1.5 ETH in wei
  nonce: 42,  // Number of transactions sent
}

Contract Accounts

Accounts controlled by code:

JavaScript
// Contract account structure
{
  address: "0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984",
  balance: 50000000000000000000n,  // 50 ETH
  nonce: 1,  // Number of contracts created
  code: "0x6080604052...",  // EVM bytecode
  storage: {
    // Key-value storage
    "0x0": "0x0000...0001",
    "0x1": "0x0000...abc",
  }
}

Comparison with Bitcoin

Text
Bitcoin UTXO Model:
┌────────────┐  ┌────────────┐  ┌────────────┐
│ UTXO 0.5₿  │  │ UTXO 0.3₿  │  │ UTXO 0.2₿  │
│ Owner: A   │  │ Owner: A   │  │ Owner: A   │
└────────────┘  └────────────┘  └────────────┘
Alice's "balance" = sum of her UTXOs

Ethereum Account Model:
┌──────────────────────────┐
│  Address: 0x123...       │
│  Balance: 1.0 ETH        │
│  Nonce: 5                │
└──────────────────────────┘
Alice has one account with a balance

Smart Contracts

A smart contract is code stored on the blockchain that executes when called.

Simple Example: Counter

Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract Counter {
    uint256 private count;

    // Anyone can read the count
    function getCount() public view returns (uint256) {
        return count;
    }

    // Anyone can increment
    function increment() public {
        count += 1;
    }

    // Anyone can decrement (if count > 0)
    function decrement() public {
        require(count > 0, "Count cannot be negative");
        count -= 1;
    }
}

How Contracts Execute

  1. User sends transaction calling increment()
  2. Transaction included in block
  3. Every node executes the function
  4. State change (count += 1) recorded
  5. New state becomes part of blockchain
Text
Transaction:
┌──────────────────────────────────────┐
│ from: 0xUser...                      │
│ to: 0xCounter... (contract address)  │
│ data: 0xd09de08a (function selector) │
│ value: 0                             │
│ gasLimit: 50000                      │
│ gasPrice: 20 gwei                    │
└──────────────────────────────────────┘

The Ethereum Virtual Machine (EVM)

The EVM is a stack-based virtual machine that executes smart contract bytecode.

EVM Architecture

Text
┌─────────────────────────────────────────────────┐
│                     EVM                          │
├─────────────────────────────────────────────────┤
│  Stack (1024 items max, each 256 bits)          │
│  ┌─────┬─────┬─────┬─────┬───────────────┐     │
│  │ top │     │     │     │               │     │
│  └─────┴─────┴─────┴─────┴───────────────┘     │
├─────────────────────────────────────────────────┤
│  Memory (byte-addressable, grows dynamically)   │
│  ┌─────────────────────────────────────────┐   │
│  │ 0x00 0x01 0x02 ... (volatile)           │   │
│  └─────────────────────────────────────────┘   │
├─────────────────────────────────────────────────┤
│  Storage (256-bit keys → 256-bit values)        │
│  ┌─────────────────────────────────────────┐   │
│  │ slot[0] = value                         │   │
│  │ slot[1] = value  (persistent)           │   │
│  └─────────────────────────────────────────┘   │
└─────────────────────────────────────────────────┘

EVM Opcodes

Text
Common opcodes:
PUSH1 0x60   - Push 1 byte onto stack
ADD          - Pop two, push sum
MUL          - Pop two, push product
SSTORE       - Store to persistent storage
SLOAD        - Load from storage
CALL         - Call another contract
RETURN       - End execution, return data
REVERT       - Abort and revert state

Execution Example

Text
Solidity: count += 1;

Compiles to:
SLOAD 0      // Load count from storage slot 0
PUSH1 1      // Push 1
ADD          // Add them
SSTORE 0     // Store result back to slot 0

Stack trace:
1. SLOAD 0Stack: [5]         (count was 5)
2. PUSH1 1Stack: [5, 1]
3. ADDStack: [6]
4. SSTORE 0Stack: []          (count is now 6)

Gas: Ethereum's Resource Model

Every operation costs gas. Gas serves multiple purposes:

  1. Prevents infinite loops: Transactions have a gas limit
  2. Prices computation: More complex operations cost more
  3. Creates fee market: Users compete for block space

Gas Costs

Text
Operation          Gas Cost
─────────────────────────────
ADD                3
MUL                5
SSTORE (new)       20,000
SSTORE (update)    5,000
SLOAD              2,100
CALL (to warm)     100
CALL (to cold)     2,600
CREATE             32,000

Transaction Fees

JavaScript
// Fee calculation
const gasUsed = 50000; // Gas units consumed
const gasPrice = 20; // Gwei per gas unit
const feeWei = gasUsed * gasPrice * 1e9; // 1 gwei = 1e9 wei
const feeEth = feeWei / 1e18; // 0.001 ETH

// Post-EIP-1559:
const baseFee = 15; // Set by protocol
const priorityFee = 2; // Tip to validator
const maxFee = 20; // Max willing to pay
const effectiveGasPrice = Math.min(baseFee + priorityFee, maxFee);

Solidity Deep Dive

Solidity is Ethereum's primary smart contract language.

Data Types

Solidity
contract DataTypes {
    // Value types
    bool public flag = true;
    uint256 public number = 42;
    int256 public signedNumber = -10;
    address public owner = msg.sender;
    bytes32 public hash = keccak256("hello");

    // Reference types
    string public name = "Ethereum";
    bytes public data = hex"deadbeef";

    // Arrays
    uint256[] public dynamicArray;
    uint256[10] public fixedArray;

    // Mappings (hash table)
    mapping(address => uint256) public balances;

    // Structs
    struct User {
        string name;
        uint256 balance;
        bool isActive;
    }
    mapping(address => User) public users;

    // Enums
    enum State { Pending, Active, Closed }
    State public currentState = State.Pending;
}

Functions and Visibility

Solidity
contract Functions {
    uint256 private value;

    // Public: anyone can call
    function setValue(uint256 _value) public {
        value = _value;
    }

    // External: only callable from outside
    function externalOnly() external view returns (uint256) {
        return value;
    }

    // Internal: only this contract and derived
    function _internalHelper() internal pure returns (uint256) {
        return 42;
    }

    // Private: only this contract
    function _privateHelper() private view returns (uint256) {
        return value * 2;
    }

    // View: reads state, doesn't modify
    function getValue() public view returns (uint256) {
        return value;
    }

    // Pure: doesn't read or modify state
    function double(uint256 x) public pure returns (uint256) {
        return x * 2;
    }
}

Events

Events allow contracts to log information efficiently:

Solidity
contract Token {
    mapping(address => uint256) public balances;

    // Event declaration
    event Transfer(
        address indexed from,
        address indexed to,
        uint256 value
    );

    function transfer(address to, uint256 amount) public {
        require(balances[msg.sender] >= amount, "Insufficient balance");

        balances[msg.sender] -= amount;
        balances[to] += amount;

        // Emit event (stored in logs, not state)
        emit Transfer(msg.sender, to, amount);
    }
}

Events are:

  • Cheap (only ~375 gas + data)
  • Indexed (searchable by indexed parameters)
  • Not accessible by contracts (only external applications)

Complete Example: ERC-20 Token

Solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IERC20 {
    function totalSupply() external view returns (uint256);
    function balanceOf(address account) external view returns (uint256);
    function transfer(address to, uint256 amount) external returns (bool);
    function allowance(address owner, address spender) external view returns (uint256);
    function approve(address spender, uint256 amount) external returns (bool);
    function transferFrom(address from, address to, uint256 amount) external returns (bool);

    event Transfer(address indexed from, address indexed to, uint256 value);
    event Approval(address indexed owner, address indexed spender, uint256 value);
}

contract MyToken is IERC20 {
    string public name = "My Token";
    string public symbol = "MTK";
    uint8 public decimals = 18;

    uint256 private _totalSupply;
    mapping(address => uint256) private _balances;
    mapping(address => mapping(address => uint256)) private _allowances;

    constructor(uint256 initialSupply) {
        _totalSupply = initialSupply * 10**decimals;
        _balances[msg.sender] = _totalSupply;
        emit Transfer(address(0), msg.sender, _totalSupply);
    }

    function totalSupply() external view override returns (uint256) {
        return _totalSupply;
    }

    function balanceOf(address account) external view override returns (uint256) {
        return _balances[account];
    }

    function transfer(address to, uint256 amount) external override returns (bool) {
        _transfer(msg.sender, to, amount);
        return true;
    }

    function allowance(address owner, address spender) external view override returns (uint256) {
        return _allowances[owner][spender];
    }

    function approve(address spender, uint256 amount) external override returns (bool) {
        _allowances[msg.sender][spender] = amount;
        emit Approval(msg.sender, spender, amount);
        return true;
    }

    function transferFrom(address from, address to, uint256 amount) external override returns (bool) {
        require(_allowances[from][msg.sender] >= amount, "Insufficient allowance");
        _allowances[from][msg.sender] -= amount;
        _transfer(from, to, amount);
        return true;
    }

    function _transfer(address from, address to, uint256 amount) internal {
        require(from != address(0), "Transfer from zero address");
        require(to != address(0), "Transfer to zero address");
        require(_balances[from] >= amount, "Insufficient balance");

        _balances[from] -= amount;
        _balances[to] += amount;

        emit Transfer(from, to, amount);
    }
}

Ethereum vs Solana: Key Differences

Understanding Ethereum helps you appreciate Solana's design choices:

AspectEthereumSolana
Account ModelContracts have code + storagePrograms are stateless, data in separate accounts
ExecutionSequential (single-threaded)Parallel (Sealevel runtime)
StateStored in contractStored in accounts passed to programs
GasDynamic pricing (EIP-1559)Fixed fee + compute units
Block Time~12 seconds~400ms
Throughput~30 TPS~65,000 TPS (theoretical)
LanguageSolidity (custom)Rust (general purpose)
VMEVM (stack-based)BPF (register-based)

Why Solana Made Different Choices

Sequential Execution Problem:

Text
Ethereum processes transactions one-by-one:
Tx1Tx2Tx3Tx4...

If Tx2 depends on Tx1's result, that's fine.
But Tx3 and Tx4 might be independent—still sequential.

Solana's Solution:

Text
Solana requires transactions to declare accounts upfront:
Tx1: [Account A, Account B]
Tx2: [Account C, Account D]
Tx3: [Account A, Account C]

Tx1 and Tx2 are independent → execute in parallel
Tx3 depends on both → execute after

Stateless Programs:

Text
Ethereum: Contract code and data live together
┌─────────────────────────┐
│ Contract 0x123          │
│ ├── code (bytecode)     │
│ └── storage (state)     │
└─────────────────────────┘

Solana: Program code and data are separate
┌──────────────┐    ┌──────────────┐
│ Program 0xABC│    │ Account 0xDEF│
│ └── code     │    │ └── data     │
└──────────────┘    └──────────────┘
Program can operate on any account passed to it

Common Misconceptions

"Ethereum is too slow"

Layer 2 solutions (Optimism, Arbitrum, zkSync) process thousands of TPS while inheriting Ethereum's security.

"Gas fees are always high"

Fees vary with network demand. During low activity, transactions cost pennies. High fees during congestion indicate demand, not a flaw.

"Smart contracts are immutable"

While deployed code can't be changed, proxy patterns allow upgradeable contracts. This is both a feature and a risk.

Key Takeaways

  1. Ethereum = blockchain + Turing-complete smart contracts
  2. Account model differs from Bitcoin's UTXOs
  3. Gas prevents abuse and creates a fee market
  4. EVM executes bytecode deterministically across all nodes
  5. Solana made different trade-offs for higher throughput

Try It Yourself

  1. Read a contract: Go to Etherscan, find the USDC contract, and read its verified source code.

  2. Trace a transaction: Find a recent Uniswap swap on Etherscan and trace the internal calls.

  3. Calculate gas: If a transaction uses 150,000 gas at 30 gwei, what's the fee in ETH?

  4. Compare fees: Check current gas prices on ETH Gas Station and Solana Explorer. How do they compare?


Next: Blocks & Transactions - Understanding the fundamental data structures of blockchains.