What is Fork Testing?
Fork Testing (also known as Fixtures) is an Ethereum Development Environment feature that optimizes test execution for smart contracts. It enables:- Snapshotting blockchain state - Avoiding recreation of the entire blockchain state for each test
- Using remote state locally - Any modifications only affect the local (forked) network
- No private key requirements - Test against remote network state without managing keys
- Debugging tools - Use
console.logand other debugging features during testing
Why is Hedera Different?
Standard EVM Contracts Work Out-of-the-Box
Fork testing works seamlessly for standard EVM smart contracts that don’t involve Hedera-specific services. The local test networks provided by development environments are replicas of the Ethereum network.Hedera System Contracts Require Emulation
Fork testing does not work out-of-the-box for contracts that use Hedera-specific services like:- Hedera Token Service (HTS) at address
0x167 - Exchange Rate at address
0x168 - PRNG at address
0x169 - Hedera Account Service at address
0x16a
0xfe (invalid opcode):
EvmError: InvalidFEOpcode when running tests. This is precisely where the hedera-forking library comes in to provide an emulation layer for Hedera System Contracts. For example, with Hardhat, the plugin intercepts JSON-RPC calls to return the appropriate bytecode and state for HTS and for Foundry, the library uses ffi to fetch state from the Mirror Node. This is explained in detail below.
How Hedera Forking Works
The hedera-forking project provides an emulation layer for Hedera System Contracts written in Solidity. Since it’s written in Solidity, it can be executed in any development network environment.Architecture Overview
The project consists of two main components:- Solidity Contracts - Provide HTS emulation designed for forked networks
- JS Package - Hooks into JSON-RPC layer to fetch appropriate data when HTS or Hedera Tokens are invoked (used by Hardhat)
HtsSystemContract implementation. This contract provides the behavior of HTS, but it is state agnostic - meaning the HTS and token state must be provided elsewhere.
Given Foundry and Hardhat provide different capabilities, they differ significantly in how the state is provided to HTS.
How Token State is Retrieved
Foundry and Hardhat use different mechanisms to retrieve token state from the
Mirror Node. Understanding these differences is important for troubleshooting
and optimizing your fork testing workflow.
Foundry Library Approach
The Foundry library uses a proactive prefetch approach where token state is fetched within Solidity contracts before it’s needed. Key Components:HtsSystemContractJson- ExtendsHtsSystemContractwith JSON data source supportMirrorNodeFFI- Fetches data from Mirror Node using Foundry’sfficheatcode
- When your test calls
Hsc.htsSetup(), the library deploysHtsSystemContractJsonat address0x167 - When you access token state (e.g.,
balanceOf),HtsSystemContractJsonoverrides the slot access - The contract calls
MirrorNodeFFIwhich usesffito executecurl(or PowerShell on Windows) to fetch data from the Mirror Node - The fetched data is written to storage using
vm.storecheatcode (notsstore) to avoidStateChangeDuringStaticCallerrors - The data is then returned to your test
- Foundry does not allow creating a JSON-RPC forwarder like Hardhat
- However, Foundry allows hooking into internal contract calls via cheatcodes
- The
fficheatcode enables executing external commands (likecurl) from Solidity
Hardhat Plugin Approach
The Hardhat plugin uses a reactive interception approach where a Worker thread intercepts JSON-RPC calls made by Hardhat. Key Components:- JSON-RPC Forwarder - A Worker thread that intercepts
eth_getCodeandeth_getStorageAtcalls - MirrorNodeClient (JavaScript) - Fetches data from Mirror Node using the
fetchAPI
- When your test runs, Hardhat makes JSON-RPC calls to fetch remote state
- The Hardhat plugin’s Worker intercepts
eth_getCodeandeth_getStorageAtcalls - For
eth_getCode(0x167): Returns the compiledHtsSystemContractbytecode - For
eth_getCode(tokenAddress): Returns the HIP-719 Token Proxy bytecode - For
eth_getStorageAt(token, slot): Uses the storage layout to map the slot to a field, then fetches the value from Mirror Node - The fetched data is returned to Hardhat’s local network
- Hardhat does not allow hooking into internal contract calls (see issue #56)
- The plugin must intercept at the JSON-RPC level before Hardhat processes the requests
- The Worker thread runs asynchronously to handle the interception
Comparison: Foundry vs Hardhat Approaches
| Aspect | Foundry Library | Hardhat Plugin |
|---|---|---|
| State Fetching | Proactive (prefetch in Solidity) | Reactive (intercept JSON-RPC) |
| Data Fetcher | MirrorNodeFFI (Solidity + curl) | MirrorNodeClient (JavaScript + fetch) |
| Hook Point | Internal contract calls via cheatcodes | JSON-RPC layer via Worker thread |
| Requirement | ffi = true in foundry. toml | chainId and workerPort in config |
| OS Dependency | curl (Unix) or PowerShell (Windows) | Node.js fetch API |
| Storage Writes | vm.store cheatcode | Returned via JSON-RPC response |
HTS Supported Methods
The emulation layer supports a subset of HTS functionality. Refer to https://github.com/hashgraph/hedera-forking#hedera-token-service-supported-methods for the latest list.Limitations and Important Notes
Key Limitations
- Behavior differences - Some edge cases may behave differently in emulation vs. the real Hedera network.
- Block number considerations - When forking from a specific block, ensure your deployed contracts exist at that block number.
- Rate limiting - When running many tests (especially fuzz tests), you may hit RPC rate limits. Consider lowering fuzz run counts.
-
Storage layout constraints - Solidity
mappings compute storage slots that are not reversible, which required special handling in the emulation layer. -
Foundry
ffirequirement - The Foundry library requiresffi = truewhich allows executing external commands. This is necessary forcurlcalls to the Mirror Node. -
Hardhat async limitations - The Hardhat plugin requires manual configuration of
chainIdandworkerPortbecause Hardhat plugin loading is synchronous.
Development Framework Support
Foundry
The Foundry library usesffi (Foreign Function Interface) to fetch remote state from the Mirror Node using curl (or PowerShell on Windows).
Key setup:
- Enable
ffi = trueinfoundry.toml - Call
Hsc.htsSetup()in your test setup
- Intercepts the storage slot access
- Uses
MirrorNodeFFIto callcurlviaffi - Parses the JSON response
- Writes data using
vm.storecheatcode
Hardhat
The Hardhat plugin intercepts JSON-RPC calls (eth_getCode and eth_getStorageAt) to provide HTS emulation.
Key setup:
- Install
@hashgraph/system-contracts-forking - Import the plugin in
hardhat.config.ts - Configure
chainIdandworkerPortin forking config