diff --git a/docs/guides/implementing_vm_forks.rst b/docs/guides/implementing_vm_forks.rst
new file mode 100644
index 0000000000..697a67bf73
--- /dev/null
+++ b/docs/guides/implementing_vm_forks.rst
@@ -0,0 +1,55 @@
+Implementing VM forks
+=====================
+
+The Ethereum protocol follows specified rules which continue to be improved through so called
+`Ethereum Improvement Proposals (EIPs) `_. Every now and then the
+community agrees on a few EIPs to become part of the next protocol upgrade. These upgrades happen
+through so called `Hardforks `_ which define:
+
+1. A name for the set of rule changes (e.g. the Istanbul hardfork)
+2. A block number from which on blocks are processed according to these new rules (e.g. `9069000`)
+
+Every client that wants to support the official Ethereum protocol needs to implement these changes
+to remain functional.
+
+
+This guide covers how to implement new hardforks in Py-EVM. The specifics and impact of each rule
+change many vary a lot between different hardforks and it is out of the scope of this guide to
+cover these in depth. This is mainly a reference guide for developers to ensure the process of
+implementing hardforks in Py-EVM is as smooth and safe as possible.
+
+
+Creating the fork module
+------------------------
+
+Every fork is encapsulated in its own module under ``eth.vm.forks.``. To create the
+scaffolding for the fork run ``python scripts/forking/create_fork.py`` and follow the assistent.
+
+.. code:: sh
+
+ $ python scripts/forking/create_fork.py
+ Specify the name of the fork (e.g Muir Glacier):
+ -->ancient tavira
+ Specify the fork base (e.g Istanbul):
+ -->istanbul
+ Check your inputs:
+ New fork:
+ Writing(pascal_case='AncientTavira', lower_dash_case='ancient-tavira', lower_snake_case='ancient_tavira', upper_snake_case='ANCIENT_TAVIRA')
+ Base fork:
+ Writing(pascal_case='Istanbul', lower_dash_case='istanbul', lower_snake_case='istanbul', upper_snake_case='ISTANBUL')
+ Proceed (y/n)?
+ -->y
+ Your fork is ready!
+
+
+Configuring new opcodes
+-----------------------
+
+Configuring new precompiles
+---------------------------
+
+Set the fork as the recent fork
+-------------------------------
+
+Wiring up the tests
+-------------------
diff --git a/docs/guides/index.rst b/docs/guides/index.rst
index 38cae6e525..fd053891a6 100644
--- a/docs/guides/index.rst
+++ b/docs/guides/index.rst
@@ -12,3 +12,4 @@ This section aims to provide hands-on guides to demonstrate how to use Py-EVM. I
architecture
understanding_the_mining_process
creating_opcodes
+ implementing_vm_forks
diff --git a/scripts/forking/create_fork.py b/scripts/forking/create_fork.py
new file mode 100644
index 0000000000..12eed6fb74
--- /dev/null
+++ b/scripts/forking/create_fork.py
@@ -0,0 +1,117 @@
+import glob
+from typing import NamedTuple
+import pathlib
+import shutil
+
+SCRIPT_BASE_PATH = pathlib.Path(__file__).parent
+SCRIPT_TEMPLATE_PATH = SCRIPT_BASE_PATH / 'template' / 'whitelabel'
+ETH_BASE_PATH = SCRIPT_BASE_PATH.parent.parent / 'eth'
+FORKS_BASE_PATH = ETH_BASE_PATH / 'vm' / 'forks'
+
+INPUT_PROMPT = '-->'
+YES = 'y'
+
+# Given a fork name of Muir Glacier we need to derive:
+# pascal case: MuirGlacier
+# lower_dash_case: muir-glacier
+# lower_snake_case: muir_glacier
+# upper_snake_case: MUIR_GLACIER
+
+
+class Writing(NamedTuple):
+ pascal_case: str
+ lower_dash_case: str
+ lower_snake_case: str
+ upper_snake_case: str
+
+
+WHITELABEL_FORK = Writing(
+ pascal_case="Istanbul",
+ lower_dash_case="istanbul",
+ lower_snake_case="istanbul",
+ upper_snake_case="ISTANBUL",
+)
+
+WHITELABEL_PARENT = Writing(
+ pascal_case="Petersburg",
+ lower_dash_case="petersburg",
+ lower_snake_case="petersburg",
+ upper_snake_case="PETERSBURG",
+)
+
+
+def bootstrap() -> None:
+ print("Specify the name of the fork (e.g Muir Glacier):")
+ fork_name = input(INPUT_PROMPT)
+
+ if not all(x.isalpha() or x.isspace() for x in fork_name):
+ print(f"Can't use {fork_name} as fork name, must be alphabetical")
+ return
+
+ print("Specify the fork base (e.g Istanbul):")
+ fork_base = input(INPUT_PROMPT)
+
+ fork_base_path = FORKS_BASE_PATH / fork_base
+ if not fork_base_path.exists():
+ print(f"No fork exists at {fork_base_path}")
+ return
+
+ writing_new_fork = create_writing(fork_name)
+ writing_parent_fork = create_writing(fork_base)
+
+ print("Check your inputs:")
+ print("New fork:")
+ print(writing_new_fork)
+
+ print("Base fork:")
+ print(writing_parent_fork)
+
+ print("Proceed (y/n)?")
+ proceed = input(INPUT_PROMPT)
+
+ if proceed.lower() == YES:
+ create_fork(writing_new_fork, writing_parent_fork)
+ print("Your fork is ready!")
+
+
+def create_writing(fork_name: str):
+ # Remove extra spaces
+ normalized = " ".join(fork_name.split())
+
+ snake_case = normalized.replace(' ', '_')
+ dash_case = normalized.replace(' ', '-')
+ pascal_case = normalized.title().replace(' ', '')
+
+ return Writing(
+ pascal_case=pascal_case,
+ lower_dash_case=dash_case.lower(),
+ lower_snake_case=snake_case.lower(),
+ upper_snake_case=snake_case.upper(),
+ )
+
+
+def create_fork(writing_new_fork: Writing, writing_parent_fork: Writing) -> None:
+ fork_path = FORKS_BASE_PATH / writing_new_fork.lower_snake_case
+ shutil.copytree(SCRIPT_TEMPLATE_PATH, fork_path)
+ replace_in(fork_path, WHITELABEL_FORK.pascal_case, writing_new_fork.pascal_case)
+ replace_in(fork_path, WHITELABEL_FORK.lower_snake_case, writing_new_fork.lower_snake_case)
+ replace_in(fork_path, WHITELABEL_FORK.lower_dash_case, writing_new_fork.lower_dash_case)
+ replace_in(fork_path, WHITELABEL_FORK.upper_snake_case, writing_new_fork.upper_snake_case)
+
+ replace_in(fork_path, WHITELABEL_PARENT.pascal_case, writing_parent_fork.pascal_case)
+ replace_in(fork_path, WHITELABEL_PARENT.lower_snake_case, writing_parent_fork.lower_snake_case)
+ replace_in(fork_path, WHITELABEL_PARENT.lower_dash_case, writing_parent_fork.lower_dash_case)
+ replace_in(fork_path, WHITELABEL_PARENT.upper_snake_case, writing_parent_fork.upper_snake_case)
+
+
+def replace_in(base_path: pathlib.Path, find_text: str, replace_txt: str) -> None:
+ for filepath in glob.iglob(f'{base_path}/**/*.py', recursive=True):
+ with open(filepath) as file:
+ s = file.read()
+ s = s.replace(find_text, replace_txt)
+ with open(filepath, "w") as file:
+ file.write(s)
+
+
+if __name__ == '__main__':
+ bootstrap()
diff --git a/scripts/forking/template/whitelabel/__init__.py b/scripts/forking/template/whitelabel/__init__.py
new file mode 100644
index 0000000000..aa71eb5226
--- /dev/null
+++ b/scripts/forking/template/whitelabel/__init__.py
@@ -0,0 +1,31 @@
+from typing import (
+ Type,
+)
+
+from eth.rlp.blocks import BaseBlock
+from eth.vm.forks.constantinople import (
+ ConstantinopleVM,
+)
+from eth.vm.state import BaseState
+
+from .blocks import IstanbulBlock
+from .headers import (
+ compute_istanbul_difficulty,
+ configure_istanbul_header,
+ create_istanbul_header_from_parent,
+)
+from .state import IstanbulState
+
+
+class IstanbulVM(ConstantinopleVM):
+ # fork name
+ fork = 'istanbul'
+
+ # classes
+ block_class: Type[BaseBlock] = IstanbulBlock
+ _state_class: Type[BaseState] = IstanbulState
+
+ # Methods
+ create_header_from_parent = staticmethod(create_istanbul_header_from_parent) # type: ignore
+ compute_difficulty = staticmethod(compute_istanbul_difficulty) # type: ignore
+ configure_header = configure_istanbul_header
diff --git a/scripts/forking/template/whitelabel/blocks.py b/scripts/forking/template/whitelabel/blocks.py
new file mode 100644
index 0000000000..83eb4b4ca2
--- /dev/null
+++ b/scripts/forking/template/whitelabel/blocks.py
@@ -0,0 +1,22 @@
+from rlp.sedes import (
+ CountableList,
+)
+from eth.rlp.headers import (
+ BlockHeader,
+)
+from eth.vm.forks.petersburg.blocks import (
+ PetersburgBlock,
+)
+
+from .transactions import (
+ IstanbulTransaction,
+)
+
+
+class IstanbulBlock(PetersburgBlock):
+ transaction_class = IstanbulTransaction
+ fields = [
+ ('header', BlockHeader),
+ ('transactions', CountableList(transaction_class)),
+ ('uncles', CountableList(BlockHeader))
+ ]
diff --git a/scripts/forking/template/whitelabel/computation.py b/scripts/forking/template/whitelabel/computation.py
new file mode 100644
index 0000000000..0d4f37f398
--- /dev/null
+++ b/scripts/forking/template/whitelabel/computation.py
@@ -0,0 +1,20 @@
+from eth.vm.forks.petersburg.computation import (
+ PETERSBURG_PRECOMPILES
+)
+from eth.vm.forks.petersburg.computation import (
+ PetersburgComputation,
+)
+
+from .opcodes import ISTANBUL_OPCODES
+
+ISTANBUL_PRECOMPILES = PETERSBURG_PRECOMPILES
+
+
+class IstanbulComputation(PetersburgComputation):
+ """
+ A class for all execution computations in the ``Istanbul`` fork.
+ Inherits from :class:`~eth.vm.forks.petersburg.PetersburgComputation`
+ """
+ # Override
+ opcodes = ISTANBUL_OPCODES
+ _precompiles = ISTANBUL_PRECOMPILES
diff --git a/scripts/forking/template/whitelabel/headers.py b/scripts/forking/template/whitelabel/headers.py
new file mode 100644
index 0000000000..622990a7db
--- /dev/null
+++ b/scripts/forking/template/whitelabel/headers.py
@@ -0,0 +1,13 @@
+from eth.vm.forks.petersburg.headers import (
+ configure_header,
+ create_header_from_parent,
+ compute_petersburg_difficulty,
+)
+
+
+compute_istanbul_difficulty = compute_petersburg_difficulty
+
+create_istanbul_header_from_parent = create_header_from_parent(
+ compute_istanbul_difficulty
+)
+configure_istanbul_header = configure_header(compute_istanbul_difficulty)
diff --git a/scripts/forking/template/whitelabel/opcodes.py b/scripts/forking/template/whitelabel/opcodes.py
new file mode 100644
index 0000000000..d50da6a4ba
--- /dev/null
+++ b/scripts/forking/template/whitelabel/opcodes.py
@@ -0,0 +1,18 @@
+import copy
+
+from eth_utils.toolz import merge
+
+
+from eth.vm.forks.petersburg.opcodes import (
+ PETERSBURG_OPCODES,
+)
+
+
+UPDATED_OPCODES = {
+ # New opcodes
+}
+
+ISTANBUL_OPCODES = merge(
+ copy.deepcopy(PETERSBURG_OPCODES),
+ UPDATED_OPCODES,
+)
diff --git a/scripts/forking/template/whitelabel/state.py b/scripts/forking/template/whitelabel/state.py
new file mode 100644
index 0000000000..a2f2f10c5d
--- /dev/null
+++ b/scripts/forking/template/whitelabel/state.py
@@ -0,0 +1,9 @@
+from eth.vm.forks.petersburg.state import (
+ PetersburgState
+)
+
+from .computation import IstanbulComputation
+
+
+class IstanbulState(PetersburgState):
+ computation_class = IstanbulComputation
diff --git a/scripts/forking/template/whitelabel/transactions.py b/scripts/forking/template/whitelabel/transactions.py
new file mode 100644
index 0000000000..fe3407b7e5
--- /dev/null
+++ b/scripts/forking/template/whitelabel/transactions.py
@@ -0,0 +1,42 @@
+from eth_keys.datatypes import PrivateKey
+from eth_typing import Address
+
+from eth.vm.forks.petersburg.transactions import (
+ PetersburgTransaction,
+ PetersburgUnsignedTransaction,
+)
+
+from eth._utils.transactions import (
+ create_transaction_signature,
+)
+
+
+class IstanbulTransaction(PetersburgTransaction):
+ @classmethod
+ def create_unsigned_transaction(cls,
+ *,
+ nonce: int,
+ gas_price: int,
+ gas: int,
+ to: Address,
+ value: int,
+ data: bytes) -> 'IstanbulUnsignedTransaction':
+ return IstanbulUnsignedTransaction(nonce, gas_price, gas, to, value, data)
+
+
+class IstanbulUnsignedTransaction(PetersburgUnsignedTransaction):
+ def as_signed_transaction(self,
+ private_key: PrivateKey,
+ chain_id: int=None) -> IstanbulTransaction:
+ v, r, s = create_transaction_signature(self, private_key, chain_id=chain_id)
+ return IstanbulTransaction(
+ nonce=self.nonce,
+ gas_price=self.gas_price,
+ gas=self.gas,
+ to=self.to,
+ value=self.value,
+ data=self.data,
+ v=v,
+ r=r,
+ s=s,
+ )