Forking is the most valuable and reusable technique you will learn here. Overlook it and you’re gonna have a bad time.
What Is Forking?
“Fork” is an overloaded term in the Ethereum ecosystem. Generally, a fork is a divergence from some path at a clear point.
The divergence can be done in public, or in private. We need to understand both mechanisms.
Consensus Forking
Developers of core EVM software do not have direct control over the network. When they want to implement a structural change, they do it via social consensus. They propose improvements, integrate the improvements into the node software running the network, and propose a schedule for when those improvements should be enabled.
When the proposed time, block, or other marker is reached, the network is forked through pressure from node operators. Node operators running the new software will activate the new feature, propose new blocks according to the proposed rules. Node operators running old software will continue to work as usual, and will propose new blocks according to the previous rules.
If the majority of node operators are running the new software, then the proposed rules go into effect and the network moves forward using them.
If the majority of node operators are running the old software, the proposed rules do not go into effect and the network will continue operating as normal.
The set of rules that more node operators are using will establish the canonical blockchain.
The Ethereum network has been forked in-place many times, and you can read the Ethereum Foundation’s History Page to learn about some of the more well-known forks.
Private Forking
Building a block that differs from the consensus “tip” is also an act of forking.
The mechanism of the EVM is open source, with the rules of its operation clearly outlined in the Ethereum Yellow Paper.
Anyone can write software to build and validate blocks against the EVM specification, and/or communicate with the consensus layer.
This is where private forking tools come in. They implement the building and validation rules of EVM, expose the same JSON-RPC endpoints so clients can interact with them, but omit the consensus component that attempts to add the block to the live chain.
The private fork allows you to access chain state at an arbitrary block, and perform operations on it. It does this by requesting state data from the live chain as needed, while tracking any subsequent changes locally.
This is very useful in a non-obvious way.
Why Should I Fork?
The most obvious benefit for users is the ability to check what happens if they send a transaction, without that transaction going out to the live network.
This is useful for users and developers.
Users
It can be difficult to understand the full scope of a smart contract, even when you have its source code.
Private forks help protect you from malicious transactions, because FAFO is not an effective security technique!
Pretend that someone has shared a link to an airdrop claim website, which asks to connect to your wallet and sign a transaction. Should you? Probably not, but if you’re curious, you can execute that transaction against a private fork and see what it does.
Interpreting what happens after that is still difficult, but it’s far better than throwing caution to the wind and raw-dogging transactions into a random contract.
Developers
Private forking allows developers to quickly deploy and test contracts without exposing their operations or spending real money.
Screw up your deployment script? Fix it up, restart the fork, try again.
You can control the fee structure, block production, EVM version, and state of the chain.
You can assign arbitrary balances to accounts.
You can send transactions from impersonated accounts without their private key. You can replay old transactions or build a block with reordered transactions.
You can simulate attacks on your contract from unknown and known addresses, and interact with external contracts as if you owned them.
Another key advantage of forking is that a developer does not need to operate a node as part of their workflow. Chain data needed to start and maintain the fork is fetched on-demand from the live network, and thus can be wrapped into CI/CD pipelines or executed on servers & containers with limited resources.
What Toolkit Should I Use?
The most commonly used toolkits are Hardhat and Foundry.
Hardhat, which is a single package, allows forking with the node command.
Foundry, which is a suite of several tools, allows forking with the anvil tool.
Follow the installation instructions on the project page to install these tools on your system.
How Do I Set Up The Fork?
I will use Anvil to demonstrate setting up a private fork from Ethereum Mainnet. To access the current chain state, Anvil requires an HTTP or websocket endpoint. We will use the free endpoint from Ankr, at URL https://rpc.ankr.com/eth
By default, Anvil launches on the standard execution node port, 8545. If you have a node running already, Anvil will error. If needed, specify the port as shown:
btd@dev:~$ anvil --fork-url https://rpc.ankr.com/eth --port 6969
_ _
(_) | |
__ _ _ __ __ __ _ | |
/ _` | | '_ \ \ \ / / | | | |
| (_| | | | | | \ V / | | | |
\__,_| |_| |_| \_/ |_| |_|
0.2.0 (1ca9b85 2024-04-15T02:34:33.060051395Z)
https://github.com/foundry-rs/foundry
Available Accounts
==================
(0) 0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266 (10000.0000000000000 ETH)
(1) 0x70997970C51812dc3A010C7d01b50e0d17dc79C8 (10000.0000000000000 ETH)
(2) 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC (10000.0000000000000 ETH)
(3) 0x90F79bf6EB2c4f870365E785982E1f101E93b906 (10000.0000000000000 ETH)
(4) 0x15d34AAf54267DB7D7c367839AAf71A00a2C6A65 (10000.0000000000000 ETH)
(5) 0x9965507D1a55bcC2695C58ba16FB37d819B0A4dc (10000.0000000000000 ETH)
(6) 0x976EA74026E726554dB657fA54763abd0C3a0aa9 (10000.0000000000000 ETH)
(7) 0x14dC79964da2C08b23698B3D3cc7Ca32193d9955 (10000.0000000000000 ETH)
(8) 0x23618e81E3f5cdF7f54C3d65f7FBc0aBf5B21E8f (10000.0000000000000 ETH)
(9) 0xa0Ee7A142d267C1f36714E4a8F75612F20a79720 (10000.0000000000000 ETH)
Private Keys
==================
(0) 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80
(1) 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d
(2) 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a
(3) 0x7c852118294e51e653712a81e05800f419141751be58f605c371e15141b007a6
(4) 0x47e179ec197488593b187f80a00eb0da91f1b9d0b13f8733639f19c30a34926a
(5) 0x8b3a350cf5c34c9194ca85829a2df0ec3153be0318b5e2d3348e872092edffba
(6) 0x92db14e403b83dfe3df233f83dfa3a0d7096f21ca9b0d6d6b8d88b2b4ec1564e
(7) 0x4bbbf85ce3377467afe5d46f804f221813b2bb87f24d81f60f1fcdbf7cbf4356
(8) 0xdbda1821b80551c9d65939329250298aa3472ba22feea921c0cf5d620ea67b97
(9) 0x2a871d0798f97d79848a013d4936a73bf4cc922c825d33c1cf7073dff6d409c6
Wallet
==================
Mnemonic: test test test test test test test test test test test junk
Derivation path: m/44'/60'/0'/0/
Fork
==================
Endpoint: https://rpc.ankr.com/eth
Block number: 19699321
Block hash: 0x56591247c42c9c953c6a6ebbda278a020dec21fd6679f73b6df8eb990d27488e
Chain ID: 1
Base Fee
==================
7068389409
Gas Limit
==================
30000000
Genesis Timestamp
==================
1713648638
Listening on 127.0.0.1:6969
The genesis timestamp is 1713648638, which is the UNIX timestamp when the fork was launched. It sets the base fee to match the fee of the current chain tip (block 19699321) of the endpoint provided. It matches the chain ID (1 for Ethereum Mainnet).
It also creates fake testing accounts generated from a mnemonic seed and derivation path. It assigns each a 10,000 Ether balance and prints its private key and public address. Refer to Part I in this series for more on accounts and keys.
Connecting To The Fork
The fork is now running and ready to create new blocks. It will accept connections from standard wallet software or web3 libraries like ethers.js or web3.py.
Metamask
The easiest way to try this out is to add the Anvil network to some wallet software. I’ll use Metamask.
Navigate to the Networks menu in settings, and use the following settings:
Network Name:
Anvil Fork
New RPC URL:
http://localhost:6969
Chain ID:
1
Currency Symbol:
ETH
Now import an account using the private key 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80, which we got from Anvil. Then rename it to “Anvil Test Account 0” or similar, so it’s obvious where this came from.
Connect to the new network and switch to the imported account. Metamask will display the 10,000 Ether balance.
Let’s generate a transaction from this account. Clicking Send, transfer 1 Ether to Vitalik (0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045).
Wait a moment for the transaction to be confirmed, and then observe that the balance is reduced by 1 Ether and the gas consumed by the transfer.
In the console running Anvil, you’ll see various commands including one for the transaction:
[...]
eth_call
eth_getTransactionCount
eth_call
eth_sendRawTransaction
Transaction: 0x73dd418971212a4592a256426883a1b44ae73bb3820ea5350f6fcf347dc58f11
Gas used: 21000
Block Number: 19699321
Block Hash: 0xcf1385acba8c3937ac243fb48b7c82fb2786fb9a6d8d7080aa78b8960514ac88
Block Time: "Sun, 20 Apr 2024 22:24:44 +0000"
eth_blockNumber
eth_getBlockByNumber
eth_getTransactionReceipt
[...]
The various commands shown in the log (eth_call
, eth_blockNumber
, eth_sendRawTransaction
) indicate calls that Metamask is making to generate, broadcast, and confirm the transaction.
Ape Framework
You can connect Ape to an already-running fork, or you can have it create the fork on-demand. Connecting to an already-running fork is simple, just pass the URL as a network option to the console
command:
(ape) btd@dev:~/code/ape_testing$ ape console --network ethereum:mainnet:http://localhost:6969
INFO: Connecting to a 'anvil' node.
In [1]:
Alternatively, you can use the ape-foundry
plugin, configure the endpoint in ape-config.yaml
, and connect to the special mainnet-fork
network. Ape will launch Anvil, connect to it automatically, and stop the fork when done:
ape-config.yaml
name: Ape Testing
plugins:
- name: foundry
foundry:
host: auto
fork:
ethereum:
mainnet:
upstream_provider: geth
geth:
ethereum:
mainnet:
uri: https://rpc.ankr.com/eth
(ape) btd@dev:~/code/ape_testing$ ape console --network ethereum:mainnet-fork
INFO: Starting 'anvil' process.
INFO: Connecting to existing Erigon node at https://rpc.ankr.com/[hidden].
In [1]: