Skip to main content

Try it Live

Run Hardfork examples in the interactive playground
Conceptual Guide - For API reference and method documentation, see Hardfork API.
Ethereum hardforks are backwards-incompatible protocol upgrades that introduce new features, fix bugs, and improve performance across the network. This guide teaches hardfork fundamentals using Tevm.

What Are Hardforks?

Hardforks are coordinated protocol changes that require all nodes to upgrade. Unlike soft forks (backward-compatible), hardforks break compatibility with older node software - nodes that don’t upgrade cannot validate new blocks. Key characteristics:
  • Backwards-incompatible - Old nodes reject new blocks
  • Network-wide activation - All validators must upgrade
  • Block number or timestamp - Activation at specific height or time
  • EIP bundling - Multiple Ethereum Improvement Proposals (EIPs) per fork

Why Hardforks Are Needed

Hardforks enable protocol evolution without fragmenting the network:
  1. Bug fixes - Fix security vulnerabilities (DAO fork, Spurious Dragon)
  2. Performance improvements - Optimize gas costs, add new opcodes
  3. Feature additions - New transaction types (EIP-1559, EIP-4844), consensus changes (The Merge)
  4. Security hardening - Remove exploitable edge cases, strengthen validation

Timeline of Major Hardforks

import { Hardfork } from 'tevm';

// Early era (2015-2017)
Hardfork.FRONTIER          // July 2015 - Genesis block
Hardfork.HOMESTEAD         // March 2016 - Gas cost adjustments
Hardfork.BYZANTIUM         // October 2017 - EIP-140 REVERT, EIP-211 RETURNDATASIZE

// Stability era (2019)
Hardfork.CONSTANTINOPLE    // February 2019 - EIP-1283 SSTORE gas
Hardfork.ISTANBUL          // December 2019 - EIP-1344 CHAINID, EIP-2200 gas

// Modern era (2021-2024)
Hardfork.BERLIN            // April 2021 - EIP-2929 access list gas costs
Hardfork.LONDON            // August 2021 - EIP-1559 base fee mechanism
Hardfork.MERGE             // September 2022 - Proof of Stake consensus
Hardfork.SHANGHAI          // April 2023 - EIP-3855 PUSH0 opcode
Hardfork.CANCUN            // March 2024 - EIP-4844 blob transactions

// Compare versions
console.log(Hardfork.compare(Hardfork.LONDON, Hardfork.CANCUN)); // -1 (London is earlier)

Feature Activation

Hardforks activate at predetermined block numbers (pre-Merge) or timestamps (post-Merge):
import { Hardfork } from 'tevm';

// Block-based activation (pre-Merge)
const berlinBlock = 12244000;   // Mainnet Berlin activation
const londonBlock = 12965000;   // Mainnet London activation

// Timestamp-based activation (post-Merge)
const shanghaiTimestamp = 1681338455;  // April 13, 2023
const cancunTimestamp = 1710338135;    // March 13, 2024

// Version comparison for feature gating
import { InvalidFormatError } from 'tevm/errors'

function getGasCostMultiplier(hardfork: string, blockNumber: number): number {
  const fork = Hardfork(hardfork);
  if (!fork) {
    throw new InvalidFormatError("Invalid hardfork", {
      value: hardfork,
      expected: "Valid hardfork name",
      code: "INVALID_HARDFORK"
    })
  }

  // Berlin changed access list gas pricing
  if (Hardfork.isAtLeast(fork, Hardfork.BERLIN)) {
    return 2600; // EIP-2929 cold SLOAD cost
  }

  return 800; // Pre-Berlin SLOAD cost
}

console.log(getGasCostMultiplier("istanbul", 11000000)); // 800
console.log(getGasCostMultiplier("berlin", 13000000));   // 2600

Checking Hardfork Support

Use version comparison to gate features based on hardfork:
import { Hardfork } from 'tevm';
import { InvalidFormatError, ValidationError } from 'tevm/errors'

function validateTransaction(tx: any, networkHardfork: string) {
  const fork = Hardfork(networkHardfork);
  if (!fork) {
    throw new InvalidFormatError("Invalid hardfork", {
      value: networkHardfork,
      expected: "Valid hardfork name",
      code: "INVALID_HARDFORK"
    })
  }

  // Type 0/1: All hardforks
  if (tx.type === 0 || tx.type === 1) {
    return true;
  }

  // Type 2 (EIP-1559): London+
  if (tx.type === 2) {
    if (!Hardfork.hasEIP1559(fork)) {
      throw new ValidationError("EIP-1559 transactions require London hardfork or later", {
        value: tx.type,
        expected: "London or later hardfork",
        code: "UNSUPPORTED_TX_TYPE"
      })
    }
    return true;
  }

  // Type 3 (EIP-4844): Cancun+
  if (tx.type === 3) {
    if (!Hardfork.hasEIP4844(fork)) {
      throw new ValidationError("Blob transactions require Cancun hardfork or later", {
        value: tx.type,
        expected: "Cancun or later hardfork",
        code: "UNSUPPORTED_TX_TYPE"
      })
    }
    return true;
  }

  throw new ValidationError("Unknown transaction type", {
    value: tx.type,
    expected: "Transaction type 0, 1, 2, or 3",
    code: "UNKNOWN_TX_TYPE"
  })
}

// Valid - Cancun supports all transaction types
validateTransaction({ type: 3 }, "cancun");

// Throws - London doesn't support blob transactions
try {
  validateTransaction({ type: 3 }, "london");
} catch (e) {
  if (e instanceof ValidationError) {
    console.error(e.name);    // "ValidationError"
    console.error(e.message); // "Blob transactions require Cancun hardfork or later"
    console.error(e.code);    // "UNSUPPORTED_TX_TYPE"
  }
}

Comparing Hardfork Features

Compare feature availability across hardforks:
import { Hardfork } from 'tevm';
import { InvalidFormatError } from 'tevm/errors'

function compareFeatures(forkA: string, forkB: string) {
  const a = Hardfork(forkA);
  const b = Hardfork(forkB);
  if (!a || !b) {
    throw new InvalidFormatError("Invalid hardfork", {
      value: !a ? forkA : forkB,
      expected: "Valid hardfork name",
      code: "INVALID_HARDFORK"
    })
  }

  return {
    comparison: Hardfork.compare(a, b),
    features: {
      eip1559: {
        [forkA]: Hardfork.hasEIP1559(a),
        [forkB]: Hardfork.hasEIP1559(b)
      },
      push0: {
        [forkA]: Hardfork.hasEIP3855(a),
        [forkB]: Hardfork.hasEIP3855(b)
      },
      blobs: {
        [forkA]: Hardfork.hasEIP4844(a),
        [forkB]: Hardfork.hasEIP4844(b)
      },
      transientStorage: {
        [forkA]: Hardfork.hasEIP1153(a),
        [forkB]: Hardfork.hasEIP1153(b)
      }
    }
  };
}

const comparison = compareFeatures("london", "cancun");
console.log(comparison);
// {
//   comparison: -1,  // London < Cancun
//   features: {
//     eip1559: { london: true, cancun: true },
//     push0: { london: false, cancun: true },
//     blobs: { london: false, cancun: true },
//     transientStorage: { london: false, cancun: true }
//   }
// }

Hardfork-Dependent Features

Common features tied to specific hardforks:

EIP-1559 Base Fee (London)

import { Hardfork } from 'tevm';
import { InvalidFormatError, ValidationError } from 'tevm/errors'

function calculateMaxFee(hardfork: string, baseFee: bigint, priorityFee: bigint): bigint {
  const fork = Hardfork(hardfork);
  if (!fork) {
    throw new InvalidFormatError("Invalid hardfork", {
      value: hardfork,
      expected: "Valid hardfork name",
      code: "INVALID_HARDFORK"
    })
  }

  if (Hardfork.hasEIP1559(fork)) {
    // London+: maxFeePerGas = baseFee + maxPriorityFeePerGas
    return baseFee + priorityFee;
  } else {
    // Pre-London: Only gas price (no base fee)
    throw new ValidationError("EIP-1559 not supported before London", {
      value: hardfork,
      expected: "London or later hardfork",
      code: "EIP1559_NOT_SUPPORTED"
    })
  }
}

console.log(calculateMaxFee("london", 20n, 2n));   // 22n
console.log(calculateMaxFee("cancun", 15n, 3n));   // 18n

EIP-3855 PUSH0 Opcode (Shanghai)

import { Hardfork } from 'tevm';
import { InvalidFormatError } from 'tevm/errors'

function supportsOptimizedBytecode(hardfork: string): boolean {
  const fork = Hardfork(hardfork);
  if (!fork) {
    throw new InvalidFormatError("Invalid hardfork", {
      value: hardfork,
      expected: "Valid hardfork name",
      code: "INVALID_HARDFORK"
    })
  }

  // PUSH0 (0x5f) allows cheaper zero-pushing
  return Hardfork.hasEIP3855(fork);
}

console.log(supportsOptimizedBytecode("london"));   // false
console.log(supportsOptimizedBytecode("shanghai")); // true
console.log(supportsOptimizedBytecode("cancun"));   // true

EIP-4844 Blob Transactions (Cancun)

import { Hardfork } from 'tevm';
import { InvalidFormatError } from 'tevm/errors'

function supportsBlobTransactions(hardfork: string): boolean {
  const fork = Hardfork(hardfork);
  if (!fork) {
    throw new InvalidFormatError("Invalid hardfork", {
      value: hardfork,
      expected: "Valid hardfork name",
      code: "INVALID_HARDFORK"
    })
  }

  // EIP-4844 blob transactions for L2 data availability
  return Hardfork.hasEIP4844(fork);
}

console.log(supportsBlobTransactions("shanghai")); // false
console.log(supportsBlobTransactions("cancun"));   // true

EIP-1153 Transient Storage (Cancun)

import { Hardfork } from 'tevm';
import { InvalidFormatError } from 'tevm/errors'

function supportsTransientStorage(hardfork: string): boolean {
  const fork = Hardfork(hardfork);
  if (!fork) {
    throw new InvalidFormatError("Invalid hardfork", {
      value: hardfork,
      expected: "Valid hardfork name",
      code: "INVALID_HARDFORK"
    })
  }

  // TLOAD/TSTORE opcodes for cheaper temporary storage
  return Hardfork.hasEIP1153(fork);
}

console.log(supportsTransientStorage("london"));  // false
console.log(supportsTransientStorage("cancun"));  // true

Resources

Next Steps