The eth_call state override set is an optional fourth parameter in the JSON-RPC request that lets you execute a call against a temporary, modified version of the blockchain state. It never persists changes, making it ideal for simulating hypothetical scenarios like a large token balance or an impersonated owner. This guide explains each override field, shows how to compute storage slots, and provides runnable viem and ethers examples.
What Is the eth_call State Override Set?
The standard eth_call method executes a read-only call against the current blockchain state. But what if you want to test how a contract behaves when an address holds a million tokens, or when the owner is someone else? Sending a real transaction would be expensive and irreversible. The solution is the state override set, an optional fourth parameter in the eth_call request object. It is a map of addresses to override objects that the client applies only for that single call. The overrides are ephemeral—they never touch persisted state, and they are discarded immediately after the call returns.
This feature is supported by major Ethereum clients including geth, reth, and Erigon, and is exposed by most RPC providers that proxy eth_call. It is not part of eth_sendRawTransaction—you cannot use overrides to alter state in a real transaction. The official specification is in the Ethereum Execution APIs and the geth JSON-RPC documentation.
The override object for each address can contain up to five fields: balance, nonce, code, state, and stateDiff. Each field lets you manipulate a different aspect of the account or contract at that address. We'll cover each in detail, with concrete examples and pitfalls.
balance: set the ETH balance of an address, in wei (hex or decimal).nonce: set the transaction count of an account.code: replace the bytecode at a contract address, effectively impersonating its logic.state: overwrite specific storage slots of a contract.stateDiff: apply a diff to storage slots, similar tostatebut with subtle client differences (verify per client).
How the State Override Set Works Under the Hood
When you send an eth_call with a state override set, the client creates a temporary in-memory trie that starts as a copy of the current state. It then applies your overrides to that copy. The call is executed against this modified trie, and the result is returned. Because the trie is ephemeral, no changes are written to disk or broadcast to the network. This is why state overrides are perfect for 'what-if' analysis and testing.
The state and stateDiff fields operate on storage slots. A storage slot is a 256-bit key in the contract's storage trie. For simple public variables, the slot number is often just an integer (e.g., slot 0 for the first variable). For mappings, the slot is computed using keccak256(abi.encode(key, uint256(slot))). We'll show you how to compute this in the example section.
One critical pitfall: the stateDiff field is implemented differently across clients. In geth, it is treated as a transient set that is committed after the call, but other clients may interpret it differently. Always verify behavior on your target client or provider. When in doubt, use state instead, which is more universally supported.
- Overrides are applied to a temporary copy of the state trie, not the canonical one.
- The call executes exactly as if the overridden state were real, including contract logic and error handling.
- No gas is consumed for state changes, but the call itself may require gas estimation.
- State overrides are not persisted and cannot be used in
eth_sendRawTransaction.
Common Use Cases for State Override Simulation
Developers typically reach for state overrides in three scenarios. First, simulating a large token balance: you want to test a swap or a transfer guard that requires the caller to hold a minimum amount of an ERC-20 token. Instead of actually transferring tokens, you override the token contract's balance mapping for your address.
Second, impersonating an owner or privileged role: many contracts have onlyOwner modifiers that restrict certain read functions. To test the read path as the owner, you can override the contract's owner storage slot to your address, or override the contract's code to a mock that returns true for isOwner().
Third, testing liquidation logic: decentralized lending protocols often rely on price oracles. By overriding the price feed contract's stored price, you can simulate a price crash and verify that your liquidation bot triggers correctly—all without forking the network or waiting for a real price drop.
- Test token-gated functions without holding the token.
- Simulate owner-only calls without changing the real owner.
- Manipulate oracle prices to test liquidation thresholds.
- Debug complex multi-contract interactions in isolation.
Runnable Example: Simulating a Token Balance and Storage Override
Below is a complete Node.js script using viem (v2.x) that demonstrates three things: (1) reading the ETH balance of an address with a plain eth_call, (2) re-running the same call with a state override that gives the address a huge ETH balance, and (3) overriding a storage slot in an ERC-20 contract to simulate a token balance. The script prints both results side by side.
We'll use the USDC contract on Ethereum mainnet (address 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48) and a hypothetical user address. The storage slot for the balance mapping is computed using keccak256(abi.encode(userAddress, uint256(0))) because the mapping is the first state variable (slot 0).
Before running, install viem and ensure you have an RPC endpoint. You can use any public or private endpoint, but be aware that some providers may not support state overrides—check your provider's documentation. For a reliable endpoint, consider the OnFinality Ethereum RPC or your own node.
// npm install viem
import { createPublicClient, http, keccak256, encodeAbiParameters, parseEther, hexToBigInt } from 'viem';
import { mainnet } from 'viem/chains';
const client = createPublicClient({
chain: mainnet,
transport: http('https://eth-mainnet.public.blastapi.io'), // replace with your endpoint
});
const user = '0xYourAddressHere'; // replace with your address
const usdc = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48';
// 1. Plain eth_call to read ETH balance
const ethBalancePlain = await client.getBalance({ address: user });
console.log('Plain ETH balance:', ethBalancePlain.toString());
// 2. eth_call with state override to set ETH balance to 1000 ETH
const ethBalanceOverridden = await client.getBalance({
address: user,
stateOverride: [
{
address: user,
balance: parseEther('1000'),
},
],
});
console.log('Overridden ETH balance:', ethBalanceOverridden.toString());
// 3. Override USDC balance (storage slot 0 mapping)
// Compute slot: keccak256(abi.encode(user, uint256(0)))
const slot = keccak256(encodeAbiParameters(
[{ type: 'address' }, { type: 'uint256' }],
[user, 0n]
));
// Simulate a call to balanceOf(user) with override
const balanceOfSelector = '0x70a08231'; // balanceOf(address)
const callData = balanceOfSelector + user.slice(2).padStart(64, '0');
const result = await client.call({
account: user,
to: usdc,
data: callData,
stateOverride: [
{
address: usdc,
state: {
[slot]: '0x' + (1000000n * 10n ** 6n).toString(16).padStart(64, '0'), // 1,000,000 USDC (6 decimals)
},
},
],
});
console.log('Simulated USDC balance:', hexToBigInt(result.data).toString());
// Fill in the table below with your results.Expected Output and Results Table
When you run the script, you should see output similar to the following (actual values depend on your address and endpoint):
The overridden ETH balance should be exactly 1000 ETH (1e21 wei). The simulated USDC balance should be 1,000,000 USDC (1e12 base units, since USDC has 6 decimals). If you see different values, check your slot computation and the decimal places.
Record your own results in the table below for verification:
- | Call | Plain Result | Overridden Result | Expected |
- |------|--------------|-------------------|----------|
- | ETH balance | ... | ... | 1000 ETH |
- | USDC balanceOf | ... | ... | 1,000,000 USDC |
Plain ETH balance: 1234567890123456789
Overridden ETH balance: 1000000000000000000000
Simulated USDC balance: 1000000000000000000000000
Computing Storage Slots for Mappings and Arrays
The example above uses the standard Solidity mapping slot formula: for a mapping declared at slot p, the value for key k is stored at keccak256(abi.encode(k, uint256(p))). This is defined in the Solidity documentation. For a mapping at slot 0, the formula simplifies to keccak256(abi.encode(key, uint256(0))).
For dynamic arrays, the length is stored at slot p, and the element at index i is at keccak256(abi.encode(uint256(p))) + i. For nested mappings or structs, you apply the formula recursively. Always verify the slot layout of the specific contract you are targeting, as compiler optimizations can change it.
If you are unsure about the slot number, you can use a tool like cast storage (Foundry) or eth_getStorageAt to inspect the current state. For example, to find the owner slot of a contract, you might read slot 0 and see if it matches the expected owner address.
- Mapping slot:
keccak256(abi.encode(key, uint256(slot))) - Array element:
keccak256(abi.encode(uint256(slot))) + index - Always confirm the slot layout with the contract source or by inspecting storage.
Troubleshooting Common Failures
State overrides can fail for several reasons. The most common is that your RPC provider does not support the stateOverride parameter. Some providers strip it or return an error. If you get an error like missing value for required argument 4, your provider may not support it. Try a different provider or run your own node.
Another issue is incorrect slot computation. If you override the wrong slot, the call will return unexpected results. Double-check the slot number and the encoding. Also, ensure you are using the correct address for the contract and the user.
Finally, be aware of client-specific behavior with stateDiff. As mentioned, geth treats it differently from other clients. If you rely on stateDiff, test it on your target client. For maximum compatibility, use state instead.
- Provider does not support state overrides: switch to a node you control or a provider that explicitly supports it.
- Incorrect storage slot: verify with
eth_getStorageAtor contract source. - Wrong data type: ensure balances are in wei and values are hex-encoded.
stateDiffnot working: usestateinstead, or check client documentation.
State Override vs. Forking: Which Should You Use?
State overrides are not the only way to simulate hypothetical state. You can also use a fork (e.g., Hardhat mainnet fork) to create a local copy of the blockchain and then modify state freely. Forks are more powerful because they allow you to send transactions and test state changes, but they require running a local node and are slower for simple read-only checks.
State overrides are ideal for quick, stateless simulations where you only need to test a single call or a small set of calls. They are also useful in production environments where you cannot afford to run a fork. However, they are limited to read-only calls—you cannot simulate a transaction that changes state.
A plain eth_call without overrides is the simplest option when you only need to read current state. Use it when you don't need to modify anything. The table below summarizes the tradeoffs:
- | Method | Pros | Cons | Best For |
- |--------|------|------|----------|
- | State override | Fast, no setup, no state persistence | Read-only only, provider support varies | Quick what-if checks, production debugging |
- | Fork | Full control, can send transactions | Requires local node, slower | Complex testing, integration tests |
- | Plain eth_call | Simple, universally supported | Cannot modify state | Reading current state |
Next Steps and Further Reading
Now that you understand state overrides, you can apply them to your own testing and debugging workflows. To deepen your knowledge, explore related topics on the OnFinality Learn hub. For example, learn how to decode revert reasons when your simulated call fails, or how to query historical data to understand past state. If you are making many calls, review JSON-RPC batching best practices to improve efficiency.
When choosing an RPC provider, consider the RPC pricing and the API service options. For a comparison of providers, see the RPC Assistant guide on choosing an Ethereum RPC API. If you encounter rate limits, read about Ethereum RPC rate limits and 429s.
Finally, always refer to the official Ethereum Execution APIs specification and geth documentation for the most up-to-date details on state overrides.
- Experiment with overriding
codeto impersonate a contract's logic. - Use state overrides in your test suite to avoid forking for simple cases.
- Share your findings with the community—state overrides are underutilized.