# evmstate > A TypeScript library for tracing, and visualizing EVM state changes with detailed human-readable labeling. ## @polareth/evmstate A TypeScript library for tracing and visualizing EVM state changes with detailed human-readable labeling. ### Overview [@polareth/evmstate](https://www.npmjs.com/package/@polareth/evmstate) traces all state changes after a transaction execution in a local VM, or by watching transactions in incoming blocks. It retrieves and labels storage slots with semantic insights and provides a detailed diff of all changes. As an alternative to event logs for tracking state, it captures and labels every state change with precise semantic information: * Variable names * Mapping keys * Array indices * Decoded values * Path tracing from base slots to final values Powered by [Tevm](https://github.com/evmts/tevm-monorepo) and [whatsabi](https://github.com/shazow/whatsabi). ### Installation ```bash npm install @polareth/evmstate # or pnpm add @polareth/evmstate # or yarn add @polareth/evmstate ``` ### Quick start :::code-group ```ts twoslash [example.ts] // [!include ~/snippets/abi.ts:mappings] // [!include ~/snippets/layout.ts:mappings] // [!include ~/snippets/client.ts] // ---cut--- import { traceState, watchState } from "@polareth/evmstate"; // Trace a transaction // [!include ~/snippets/trace-state.ts:example-featured] // Watch an account's state // [!include ~/snippets/watch-state.ts:watchState] ``` ```ts twoslash [layout.ts] // [!include ~/snippets/layout.ts:mappings] ``` ```ts twoslash [abi.ts] // [!include ~/snippets/abi.ts:mappings] ``` ```ts twoslash [client.ts] // [!include ~/snippets/client.ts] ``` ::: ### Key features * **Complete state tracing**: Track all storage slots accessed during transaction execution * **Human-readable labeling**: Label slots with variable names and provide access paths * **Intelligent key detection**: Extract mapping keys and array indices from transaction data * **Type-aware decoding**: Convert raw storage values to appropriate TypeScript types with automatic type inference ### Example output ```typescript Map { "0xContractAddress" => { "balance": { "current": 0n, "modified": false, }, "nonce": { "current": 0n, "modified": false, }, ... "storage": { "counter": { "kind": "primitive", "name": "counter", "type": "uint256", "trace": [ { "current": { "hex": "0x05", "decoded": 5n }, "modified": true, "next": { "hex": "0x06", "decoded": 6n }, "fullExpression": "counter", "slots": ["0x0000000000000000000000000000000000000000000000000000000000000000"] } ] } } }, "0xCallerAddress" => { "balance": { "current": 1000000000000000000n, "modified": true, "next": 999999998747698854n, }, "nonce": { "current": 0n, "modified": true, "next": 1n, }, ... }, } ``` You can get the result for any address (case-insensitive): ```ts twoslash import { traceState } from "@polareth/evmstate"; // [!include ~/snippets/client.ts] // [!include ~/snippets/abi.ts] // [!include ~/snippets/trace-state.ts:example-featured] // ---cut--- const result = trace.get("0xContractAddress"); ``` ### Documentation * [Installation](/guides/installation) * [Basic usage](/guides/basic-usage) * [Understanding output formats](/reference/output-format) import { Callout } from "vocs/components"; import { Playground } from "../components/playground/index.tsx"; ## Playground This interactive playground demonstrates how different Solidity storage types are traced and visualized. *The client is shared and persisted across sessions (the state of the chain and traces will persist everytime you visit the playground).* ### Usage Guide 1. **Select a function** from the dropdown menu - each function is labeled with a description of what storage type it affects 2. **Enter parameters** if required by the selected function 3. **Click "Execute"** to run the function 4. **View the trace** in the collapsible code block right below 5. **Explore the history** of all executed functions and their traces Try different functions to see how various storage patterns are represented in the trace output! ### Interactive example ### Reference contract ```solidity // [!include ~/../test/contracts/Playground.s.sol] ``` ## API Overview @polareth/evmstate provides three main APIs for analyzing Ethereum state changes: ### traceState Main function for tracing state changes from a transaction. ```typescript twoslash import type { TraceStateOptions, TraceStateResult } from "@polareth/evmstate"; // @ts-expect-error - Function implementation is missing function traceState(options: TraceStateOptions): Promise; ``` Use `traceState` when you need to analyze a single transaction and don't need to reuse configuration. [API reference →](/api/trace-state) ### Tracer Class for creating reusable tracing instances with shared configuration. ```typescript twoslash import type { TraceStateBaseOptions, TraceStateTxParams } from "@polareth/evmstate"; class Tracer { // @ts-expect-error - Constructor implementation is missing constructor(options: TraceStateBaseOptions); // @ts-expect-error - Function implementation is missing traceState(txOptions: TraceStateTxParams): Promise; } ``` Use `Tracer` when you need to perform multiple traces with the same configuration. [API reference →](/api/tracer) ### watchState Function for monitoring state changes for a specific address. ```typescript twoslash import type { WatchStateOptions, DeepReadonly, SolcStorageLayout } from "@polareth/evmstate"; // @ts-expect-error - Function implementation is missing function watchState | undefined = undefined>( options: WatchStateOptions ): Promise<() => void>; ``` Use `watchState` to continuously monitor an address for state changes across multiple blocks. [API reference →](/api/watch-state) ## traceState Analyzes storage access patterns during transaction execution, identifying which contract slots are read and modified, and providing human-readable labels. ### Signature ```typescript twoslash import type { Abi, ContractFunctionName } from "tevm"; import type { TraceStateOptions, TraceStateResult } from "@polareth/evmstate"; // @ts-expect-error - Function implementation is missing function traceState< TAbi extends Abi | readonly unknown[] = Abi, TFunctionName extends ContractFunctionName = ContractFunctionName, >(options: TraceStateOptions): Promise; ``` ### Parameters The function accepts a single options object with these properties: #### Connection options | Property | Type | Description | | ----------- | -------------------------- | ----------------------------------------------------------- | | `rpcUrl` | `string` | Ethereum RPC URL (needs `debug_traceTransaction`) | | `client` | `MemoryClient` | Optional Tevm client instance | | `explorers` | `Record` | Optional (recommended) configuration for contract explorers | #### Transaction options Three ways to specify the transaction to trace: ##### 1. Raw transaction data | Property | Type | Description | | -------- | ---------------------- | ------------------------------------ | | `from` | `Address` | Sender address | | `to` | `Address \| undefined` | Recipient address or omit for deploy | | `data` | `Hex` | Transaction calldata | | `value` | `bigint \| undefined` | Optional ETH amount to send | ##### 2. Contract ABI call | Property | Type | Description | | -------------- | --------------------- | --------------------------- | | `from` | `Address` | Sender address | | `to` | `Address` | Contract address | | `abi` | `TAbi` | Contract ABI | | `functionName` | `TFunctionName` | Function name to call | | `args` | `unknown[]` | Function arguments | | `value` | `bigint \| undefined` | Optional ETH amount to send | ##### 3. Existing transaction | Property | Type | Description | | -------- | ----- | ------------------------- | | `txHash` | `Hex` | Transaction hash to trace | ### Return value Returns a promise that resolves to a record mapping account addresses to their state changes: ```typescript twoslash import type { TraceStateResult } from "@polareth/evmstate"; type ReturnType = TraceStateResult; ``` See the [output format reference](/reference/output-format) for details on the structure. ### Examples #### Simulating a transaction ```typescript twoslash import { traceState } from "@polareth/evmstate"; const trace = await traceState({ rpcUrl: "https://1.rpc.thirdweb.com", from: "0xSenderAddress", to: "0xContractAddress", data: "0xEncodedCalldata", value: 0n, }); const state = trace.get("0xTokenAddress"); console.log(state?.storage); ``` #### Using a contract ABI :::code-group ```typescript twoslash [example.ts] // [!include ~/snippets/abi.ts:erc20] // ---cut--- import { traceState } from "@polareth/evmstate"; const trace = await traceState({ rpcUrl: "https://1.rpc.thirdweb.com", from: "0xSenderAddress", to: "0xTokenAddress", abi: erc20Abi, functionName: "transfer", args: ["0xRecipient", 1000000000000000000n], }); ``` ```typescript twoslash [abi.ts] // [!include ~/snippets/abi.ts:erc20] ``` ::: #### Tracing an existing transaction ```typescript twoslash import { traceState } from "@polareth/evmstate"; const trace = await traceState({ rpcUrl: "https://1.rpc.thirdweb.com", txHash: "0xTransactionHash", }); ``` #### Using a custom Tevm client ```typescript twoslash // ---cut--- import { createMemoryClient, http } from "tevm"; import { mainnet } from "tevm/common"; import { traceState } from "@polareth/evmstate"; const client = createMemoryClient({ common: mainnet, fork: { transport: http("https://1.rpc.thirdweb.com"), blockTag: "latest", }, }); const trace = await traceState({ client, from: "0xSenderAddress", to: "0xContractAddress", data: "0xEncodedCalldata", }); ``` ## Tracer A class that encapsulates the storage access tracing functionality in an object-oriented interface, allowing for reusable tracers with consistent configuration. ### Signature ```typescript twoslash import type { Abi, ContractFunctionName } from "tevm"; import type { TraceStateBaseOptions, TraceStateTxParams, TraceStateResult } from "@polareth/evmstate"; class Tracer { // @ts-expect-error - Constructor implementation is missing constructor(options: TraceStateBaseOptions); // @ts-expect-error - Function implementation is missing traceState< TAbi extends Abi | readonly unknown[] = Abi, TFunctionName extends ContractFunctionName = ContractFunctionName, >(txOptions: TraceStateTxParams): Promise; } ``` ### Constructor ```typescript twoslash import type { TraceStateBaseOptions } from "@polareth/evmstate"; class Tracer { // @ts-expect-error - Constructor implementation is missing constructor(options: TraceStateBaseOptions); } ``` Creates a new `Tracer` instance with shared configuration. #### Parameters | Property | Type | Description | | ----------- | --------------------------------------- | ----------------------------------------- | | `rpcUrl` | `string \| undefined` | Ethereum RPC endpoint URL | | `client` | `MemoryClient \| undefined` | Optional Tevm client instance | | `explorers` | `Record \| undefined` | Optional contract explorers configuration | You must provide either `rpcUrl` or `client`. ### Methods #### traceState ```typescript twoslash import type { Abi, ContractFunctionName } from "tevm"; import type { TraceStateTxParams, TraceStateResult } from "@polareth/evmstate"; class Tracer { // @ts-expect-error - Function implementation is missing traceState< TAbi extends Abi | readonly unknown[] = Abi, TFunctionName extends ContractFunctionName = ContractFunctionName, >(txOptions: TraceStateTxParams): Promise; } ``` Traces the storage access patterns for a transaction, using the shared configuration from the constructor. ##### Parameters | Property | Type | Description | | -------------- | ---------------------------- | --------------------------- | | `from` | `Address` | The sender address | | `to` | `Address \| undefined` | The recipient address | | `data` | `Hex \| undefined` | The transaction calldata | | `value` | `bigint \| undefined` | Optional ETH amount to send | | `abi` | `TAbi \| undefined` | Optional contract ABI | | `functionName` | `TFunctionName \| undefined` | Optional function name | | `args` | `unknown[] \| undefined` | Optional function arguments | Just like the standalone `traceState` function, the `Tracer.traceState` method either takes encoded calldata or an ABI function call. ##### Return value Returns a promise that resolves to a record mapping account addresses to their state changes: ```typescript twoslash import type { TraceStateResult } from "@polareth/evmstate"; type ReturnType = TraceStateResult; ``` See the [output format reference](/reference/output-format) for details on the structure. ### Examples #### Basic usage ```typescript twoslash import { Tracer } from "@polareth/evmstate"; // Create tracer with shared configuration const tracer = new Tracer({ rpcUrl: "https://1.rpc.thirdweb.com", }); // Trace multiple transactions const trace1 = await tracer.traceState({ from: "0xSenderAddress", to: "0xContractAddress", data: "0xCalldata1", }); const trace2 = await tracer.traceState({ from: "0xSenderAddress", to: "0xContractAddress", data: "0xCalldata2", }); ``` #### With custom Tevm client ```typescript twoslash import { createMemoryClient, http } from "tevm"; import { mainnet } from "tevm/common"; import { Tracer } from "@polareth/evmstate"; // Create custom Tevm client const client = createMemoryClient({ common: mainnet, fork: { transport: http("https://1.rpc.thirdweb.com"), blockTag: "latest", }, }); // Create tracer with custom client const tracer = new Tracer({ client }); // Trace a transaction const trace = await tracer.traceState({ from: "0xSenderAddress", to: "0xContractAddress", data: "0xCalldata", value: 1000000000000000000n, // 1 ETH }); ``` #### With contract ABI :::code-group ```typescript twoslash [example.ts] // [!include ~/snippets/abi.ts:erc20] // ---cut--- import { Tracer } from "@polareth/evmstate"; // Create tracer const tracer = new Tracer({ rpcUrl: "https://1.rpc.thirdweb.com", }); // Trace an ERC-20 transfer const transferTrace = await tracer.traceState({ from: "0xSenderAddress", to: "0xTokenAddress", abi: erc20Abi, functionName: "transfer", args: ["0xRecipient", 1000000000000000000n], // recipient, amount }); ``` ```typescript twoslash [abi.ts] // [!include ~/snippets/abi.ts:erc20] ``` ::: #### Tracing an existing transaction ```typescript twoslash import { Tracer } from "@polareth/evmstate"; const tracer = new Tracer({ rpcUrl: "https://1.rpc.thirdweb.com", }); const trace = await tracer.traceState({ txHash: "0x1234567890abcdef...", }); ``` ## watchState Monitors state changes for a specific Ethereum address by watching new blocks and tracing transactions. It provides notifications when the address's state is accessed or modified. ### Signature ```typescript twoslash import type { Address } from "tevm"; import type { WatchStateOptions, StateChange, SolcStorageLayout, DeepReadonly } from "@polareth/evmstate"; // @ts-expect-error - Function implementation is missing function watchState | undefined = undefined>( options: WatchStateOptions, ): Promise<() => void>; ``` ### Parameters The function accepts a single options object with these properties: | Property | Type | Description | | ----------------- | ---------------------------------------------------- | ----------------------------------------------- | | `address` | `Address` | The Ethereum address to monitor | | `onStateChange` | `(stateChange: StateChange) => void` | Callback for state changes | | `onError` | `(error: Error) => void \| undefined` | Optional error handling callback | | `pollingInterval` | `number \| undefined` | Optional polling interval in ms (default: 1000) | | `storageLayout` | `TStorageLayout \| undefined` | Optional contract storage layout | | `abi` | `Abi \| undefined` | Optional contract ABI | | `rpcUrl` | `string \| undefined` | Ethereum RPC endpoint URL | | `client` | `MemoryClient \| undefined` | Optional Tevm client instance | | `explorers` | `Record \| undefined` | Optional contract explorers configuration | ### Return value Returns a promise that resolves to an unsubscribe function: ```typescript twoslash type Unsubscribe = () => void; ``` Call this function to stop watching for state changes. ### Type parameters ```typescript twoslash import type { SolcStorageLayout, DeepReadonly } from "@polareth/evmstate"; type TStorageLayout = /* extends */ DeepReadonly | undefined; ``` By providing a storage layout with the `as const` assertion, TypeScript will infer precise types for all state change properties, enhancing the developer experience with autocompletion and type checking. ### Examples #### Basic usage ```typescript twoslash import { watchState } from "@polareth/evmstate"; // Start watching an address const unsubscribe = await watchState({ rpcUrl: "https://1.rpc.thirdweb.com", address: "0xContractAddress", onStateChange: (stateChange) => { console.log("Transaction:", stateChange.txHash); // Check balance changes if (stateChange.balance?.modified) { console.log("Balance changed:", { from: stateChange.balance.current, to: stateChange.balance.next, }); } // Print storage changes if (stateChange.storage) { console.log("Storage accessed:", Object.keys(stateChange.storage)); } }, onError: (error) => { console.error("Watch error:", error); }, }); // Later, stop watching unsubscribe(); ``` #### With storage layout and ABI :::code-group ```typescript twoslash [example.ts] // [!include ~/snippets/abi.ts:erc20] // [!include ~/snippets/layout.ts:erc20] // ---cut--- import { watchState } from "@polareth/evmstate"; // Watch with enhanced typing const unsubscribe = await watchState({ rpcUrl: "https://1.rpc.thirdweb.com", address: "0xTokenAddress", storageLayout: erc20Layout, // Type assertion with as const abi: erc20Abi, onStateChange: (stateChange) => { // TypeScript knows the exact structure of stateChange.storage // Access totalSupply (if changed) if (stateChange.storage?.totalSupply) { const totalSupply = stateChange.storage.totalSupply.trace[0]; if (totalSupply.modified) { console.log("Total supply changed:", { from: totalSupply.current?.decoded, to: totalSupply.next?.decoded, }); } } // Access balances mapping if (stateChange.storage?.balances) { const balances = stateChange.storage.balances.trace; balances.forEach((entry) => { if (entry.modified) { // TypeScript knows this is an address key const address = entry.path[0].key; console.log(`Balance changed for ${address}:`, { from: entry.current?.decoded, to: entry.next?.decoded, }); } }); } }, }); ``` ```typescript twoslash [layout.ts] // [!include ~/snippets/layout.ts:erc20] ``` ```typescript twoslash [abi.ts] // [!include ~/snippets/abi.ts:erc20] ``` ::: #### With custom Tevm client ```typescript twoslash import { createMemoryClient, http } from "tevm"; import { mainnet } from "tevm/common"; import { watchState } from "@polareth/evmstate"; // Create custom client const client = createMemoryClient({ common: mainnet, fork: { transport: http("https://1.rpc.thirdweb.com"), blockTag: "latest", }, }); // Watch with custom client const unsubscribe = await watchState({ client, address: "0xContractAddress", onStateChange: (stateChange) => { console.log("State changed:", stateChange); }, }); ``` ## Basic usage This guide covers the fundamental ways to use the library to analyze and label state access patterns. ### Core functions The library provides three main entry points: 1. `traceState`: Analyze state changes from a single transaction 2. `Tracer`: Create a reusable instance for multiple traces 3. `watchState`: Monitor ongoing state changes for a specific address ### Tracing a transaction There are three ways to trace a transaction: 1. Using transaction calldata 2. Using a contract ABI 3. Using a transaction hash :::code-group ```typescript twoslash [calldata.ts] // [!include ~/snippets/client.ts] // ---cut--- import { traceState } from "@polareth/evmstate"; // [!include ~/snippets/trace-state.ts:calldata] ``` ```typescript twoslash [abi.ts] // [!include ~/snippets/client.ts] // [!include ~/snippets/abi.ts:mappings] // ---cut--- import { traceState } from "@polareth/evmstate"; // [!include ~/snippets/trace-state.ts:abi] ``` ```typescript twoslash [txHash.ts] // [!include ~/snippets/client.ts] // ---cut--- import { traceState } from "@polareth/evmstate"; // [!include ~/snippets/trace-state.ts:txHash] ``` ::: ### Tracing options There are various ways to configure the client: * Using an existing Tevm client (likely in forking mode) * Using a RPC url (which will internally create a Tevm memory client) See [the Tevm documentation](https://www.tevm.sh/) for [how to create and configure the client](https://node.tevm.sh/core/create-tevm-node). :::code-group ```typescript twoslash [tevm-client.ts] import { createMemoryClient, http } from "tevm"; import { mainnet } from "tevm/common"; import { traceState } from "@polareth/evmstate"; // [!include ~/snippets/client-fork.ts:client-fork] // [!include ~/snippets/trace-state.ts:options-client] ``` ```typescript twoslash [rpc-url.ts] // [!include ~/snippets/client.ts] // ---cut--- import { traceState } from "@polareth/evmstate"; // [!include ~/snippets/trace-state.ts:options-rpc] ``` ::: Finally, you will need to provide an url & API key for at least one supported explorer, so it can fetch ABIs and storage layouts: ```typescript twoslash [explorers.ts] // [!include ~/snippets/client.ts] // ---cut--- import { traceState } from "@polareth/evmstate"; // [!include ~/snippets/trace-state.ts:options-explorers] ``` ### Watching state changes To monitor an address for state changes in real-time: :::code-group ```typescript twoslash [example.ts] // [!include ~/snippets/layout.ts:mappings] // [!include ~/snippets/abi.ts:mappings] // ---cut--- import { watchState } from "@polareth/evmstate"; // [!include ~/snippets/watch-state.ts:watchState] ``` ```typescript twoslash [layout.ts] // [!include ~/snippets/layout.ts:mappings] ``` ```typescript twoslash [abi.ts] // [!include ~/snippets/abi.ts:mappings] ``` ::: ### Next steps * [Example usage](/guides/usage-examples) * [Understanding output format](/reference/output-format) ## Installation ### Installing the package Install @polareth/evmstate using your preferred package manager: ```bash # Using npm npm install @polareth/evmstate # Using pnpm pnpm add @polareth/evmstate # Using yarn yarn add @polareth/evmstate ``` ### RPC provider requirements @polareth/evmstate needs an Ethereum RPC provider that supports the `debug` namespace methods: * `debug_traceTransaction`: For analyzing single transactions * `debug_traceBlock`: For watching state changes across blocks Compatible RPC providers include: * Self-hosted nodes with `debug` namespace enabled (Geth, Erigon) * Some specialized services like Alchemy or QuickNode (may require higher tier plans) ### Example initialization :::code-group ```ts twoslash [example.ts] // [!include ~/snippets/abi.ts:mappings] // [!include ~/snippets/client.ts] // ---cut--- import { traceState } from "@polareth/evmstate"; // Basic usage with abi // [!include ~/snippets/trace-state.ts:example-featured] ``` ```ts twoslash [abi.ts] // [!include ~/snippets/abi.ts:mappings] ``` ```ts twoslash [client.ts] // [!include ~/snippets/client.ts] ``` ::: ### Next steps * [Basic usage examples](/guides/basic-usage) * [Understanding the output format](/reference/output-format) import { Callout } from "vocs/components"; ## Examples This guide covers a few examples of how to use the library. See the [playground](/playground) for an interactive demonstration of the output based on function calls. ### Monitoring ERC20 balances :::code-group ```ts twoslash [example.ts] // [!include ~/snippets/client-fork.ts] // [!include ~/snippets/abi.ts] // [!include ~/snippets/layout.ts] // ---cut--- import { createContract } from "tevm/contract"; import { traceState, type LabeledState } from "@polareth/evmstate"; // [!include ~/snippets/erc20.ts:args] // [!include ~/snippets/erc20.ts:approve] // [!include ~/snippets/erc20.ts:trace-swap] // [!include ~/snippets/erc20.ts:trace-parse] ``` ```ts twoslash [client.ts] import { createMemoryClient, http } from "tevm"; import { mainnet } from "tevm/common"; // [!include ~/snippets/client-fork.ts:client-fork-no-highlight] ``` ```ts twoslash [abis.ts] // [!include ~/snippets/abi.ts:erc20] // [!include ~/snippets/abi.ts:simple-dex] ``` ```ts twoslash [layout.ts] // [!include ~/snippets/layout.ts:erc20] ``` ::: ### Processing complex outputs :::code-group ```ts twoslash [example.ts] // [!include ~/snippets/abi.ts:mappings] // [!include ~/snippets/layout.ts:mappings] // [!include ~/snippets/client.ts] // ---cut--- import { watchState, PathSegmentKind, type StateChange } from "@polareth/evmstate"; // [!include ~/snippets/watch-state.ts:watchState-logs-onStateChange] // [!include ~/snippets/watch-state.ts:watchState-logs-subscribe /unsubscribe2/unsubscribe/] ``` ```ts twoslash [layout.ts] // [!include ~/snippets/layout.ts:mappings] ``` ```ts twoslash [abi.ts] // [!include ~/snippets/abi.ts:mappings] ``` ```ts twoslash [client.ts] // [!include ~/snippets/client.ts] ``` ::: ## Output format This reference documents the structure of data returned by the library's tracing functions. ### Basic structure For `traceState` and `Tracer.traceState`, the output maps account addresses to state changes: ```typescript twoslash import type { TraceStateResult, LabeledState } from "@polareth/evmstate"; import type { Address } from "tevm"; type TraceOutput = TraceStateResult; // which is equivalent to: type TraceOutputMap = Map; // `get`, `has`, `set` all normalize the address to lowercase ``` For `watchState`, the output focuses on a single address and includes the transaction hash: ```typescript twoslash import type { Hex } from "tevm"; import type { LabeledState } from "@polareth/evmstate"; type WatchOutput = LabeledState & { txHash: Hex }; ``` ### Account state Each account in the output has these properties: ```typescript twoslash import type { Hex } from "tevm"; import type { LabeledStorageState } from "@polareth/evmstate"; type LabeledState = { // Intrinsic properties balance: { current: bigint, next?: bigint, modified: boolean }, nonce: { current: number, next?: number, modified: boolean }, code: { current: Hex, next?: Hex, modified: boolean }, // Storage state storage: { [variableName: string]: LabeledStorageState } } ``` ### Storage variables The `storage` field contains labeled variables with detailed traces: ```typescript twoslash import type { LabeledStorageStateTrace } from "@polareth/evmstate"; type LabeledStorageState = { "variableName": { name: string, type?: string, kind?: "primitive" | "mapping" | "dynamic_array" | "static_array" | "struct" | "bytes", trace: Array } } ``` Each `LabeledStorageStateTrace` represents a specific storage access: ```typescript twoslash import type { Hex } from "tevm"; import type { PathSegment } from "@polareth/evmstate"; type LabeledStorageStateTrace = { slots: Array, // Storage slots accessed path: Array, // Semantic path components fullExpression: string, // Human-readable expression (e.g., "balances[0x1234]") current?: { hex: Hex, decoded: any // typed if storage layout is provided }, next?: { hex: Hex, decoded: any // typed if storage layout is provided }, modified: boolean, note?: string // any exception/error during exploration } ``` ### Proxy contracts Proxy contracts are labeled using the implementation contract's ABI and storage layout when possible: :::code-group ```typescript [output.json] // [!include ~/snippets/outputs/proxy.jsonc] ``` ```typescript [trace.ts] // [!include ~/snippets/proxy.ts:proxy-trace] ``` ::: ### Next steps * [Storage types](/reference/storage-types) * [Playground](/playground) ## Storage types This reference explains how the library handles different Solidity storage types. ### Value types Simple values stored directly in slots. #### Integers (uint/int) ```solidity twoslash uint256 public counter = 0; ``` ```typescript "counter": { "kind": "primitive", "name": "counter", "type": "uint256", "trace": [ { "current": { "hex": "0x00", "decoded": 0n }, "modified": true, "next": { "hex": "0x01", "decoded": 1n }, "path": [], "fullExpression": "counter", "slots": ["0x0000000000000000000000000000000000000000000000000000000000000000"] } ] } ``` #### Booleans ```solidity bool public flag = false; ``` ```typescript "flag": { "kind": "primitive", "name": "flag", "type": "bool", "trace": [ { "current": { "hex": "0x00", "decoded": false }, "modified": true, "next": { "hex": "0x01", "decoded": true }, "path": [], "fullExpression": "flag", "slots": ["0x0000000000000000000000000000000000000000000000000000000000000001"] } ] } ``` #### Addresses ```solidity address public owner = address(0); ``` ```typescript "owner": { "kind": "primitive", "name": "owner", "type": "address", "trace": [ { "current": { "hex": "0x0000000000000000000000000000000000000000", "decoded": "0x0000000000000000000000000000000000000000" }, "modified": true, "next": { "hex": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "decoded": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4" }, "path": [], "fullExpression": "owner", "slots": ["0x0000000000000000000000000000000000000000000000000000000000000002"] } ] } ``` ### Mappings Mappings compute slots based on keys and the base slot. #### Simple mapping ```solidity mapping(address => uint256) public balances; ``` ```typescript "balances": { "kind": "mapping", "name": "balances", "type": "mapping(address => uint256)", "trace": [ { "current": { "hex": "0x00", "decoded": 0n }, "modified": true, "next": { "hex": "0x2386f26fc10000", "decoded": 10000000000000000n }, "path": [ { "kind": "mapping_key", "key": "0x1234567890123456789012345678901234567890", "keyType": "address" } ], "fullExpression": "balances[0x1234567890123456789012345678901234567890]", "slots": ["0x8e9c0c9f9fb928592f2fb0a9314450706c27839d034893b88d8ed2f54cf1bd5e"] } ] } ``` #### Nested mapping ```solidity mapping(address => mapping(address => uint256)) public allowances; ``` ```typescript "allowances": { "kind": "mapping", "name": "allowances", "type": "mapping(address => mapping(address => uint256))", "trace": [ { "current": { "hex": "0x00", "decoded": 0n }, "modified": true, "next": { "hex": "0x0de0b6b3a7640000", "decoded": 1000000000000000000n }, "path": [ { "kind": "mapping_key", "key": "0x1234567890123456789012345678901234567890", "keyType": "address" }, { "kind": "mapping_key", "key": "0x5678901234567890123456789012345678901234", "keyType": "address" } ], "fullExpression": "allowances[0x1234...][0x5678...]", "slots": ["0x6a70ff112c49166454439c4e0a5f7db9e9a4ac61f326f232ac9eb1a9b05f4eef"] } ] } ``` ### Arrays Arrays store length at the base slot and elements at computed slots. #### Dynamic arrays ```solidity uint256[] public numbers; ``` ```typescript "numbers": { "kind": "dynamic_array", "name": "numbers", "type": "uint256[]", "trace": [ { // Array length "current": { "hex": "0x02", "decoded": 2n }, "modified": true, "next": { "hex": "0x03", "decoded": 3n }, "path": [ { "kind": "array_length", "name": "_length" } ], "fullExpression": "numbers._length", "slots": ["0x0000000000000000000000000000000000000000000000000000000000000006"] }, { // Element access "current": { "hex": "0x00", "decoded": 0n }, "modified": true, "next": { "hex": "0x2a", "decoded": 42n }, "path": [ { "kind": "array_index", "index": 2n } ], "fullExpression": "numbers[2]", "slots": ["0x5de13444fe158c7b5525d0d208535a5f84ca2f75ce5219b9c55fb55643beb57c"] } ] } ``` #### Fixed-size arrays ```solidity uint256[3] public fixedNumbers; ``` ```typescript "fixedNumbers": { "kind": "static_array", "name": "fixedNumbers", "type": "uint256[3]", "trace": [ { "current": { "hex": "0x00", "decoded": 0n }, "modified": true, "next": { "hex": "0x2a", "decoded": 42n }, "path": [ { "kind": "array_index", "index": 1n } ], "fullExpression": "fixedNumbers[1]", "slots": ["0x0000000000000000000000000000000000000000000000000000000000000008"] } ] } ``` ### Structs Structs organize fields sequentially in storage. ```solidity struct User { uint256 id; address wallet; bool active; } User public admin; ``` ```typescript "admin": { "kind": "struct", "name": "admin", "type": "struct Contract.User", "trace": [ { "current": { "hex": "0x00", "decoded": 0n }, "modified": true, "next": { "hex": "0x01", "decoded": 1n }, "path": [ { "kind": "struct_field", "name": "id" } ], "fullExpression": "admin.id", "slots": ["0x000000000000000000000000000000000000000000000000000000000000000a"] }, { "current": { "hex": "0x0000000000000000000000000000000000000000", "decoded": "0x0000000000000000000000000000000000000000" }, "modified": true, "next": { "hex": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4", "decoded": "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4" }, "path": [ { "kind": "struct_field", "name": "wallet" } ], "fullExpression": "admin.wallet", "slots": ["0x000000000000000000000000000000000000000000000000000000000000000b"] } ] } ``` ### Strings and bytes Strings and bytes can be stored inline (short) or across multiple slots (long). #### Short strings/bytes ```solidity string public shortString = "short string"; ``` ```typescript "shortString": { "kind": "bytes", "name": "shortString", "type": "string", "trace": [ { "current": { "decoded": 12n, "hex": "0x0c", }, "fullExpression": "shortString._length", "modified": false, "path": [ { "kind": "bytes_length", "name": "_length", }, ], "slots": [ "0x0000000000000000000000000000000000000000000000000000000000000000", ], }, { "current": { "decoded": "short string", "hex": "0x73686f727420737472696e67", }, "fullExpression": "shortString", "modified": false, "path": [], "slots": [ "0x0000000000000000000000000000000000000000000000000000000000000000", ], }, ], } ``` #### Long strings/bytes ```solidity bytes public longBytes = "0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd"; ``` ```typescript "longBytes": { "kind": "bytes", "name": "longBytes", "type": "bytes", "trace": [ { "current": { "decoded": 60n, "hex": "0x3c", }, "fullExpression": "longBytes._length", "modified": false, "path": [ { "kind": "bytes_length", "name": "_length", }, ], "slots": [ "0x0000000000000000000000000000000000000000000000000000000000000001", ], }, { "current": { "decoded": "0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", "hex": "0xabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcdabcd", }, "fullExpression": "longBytes", "modified": false, "path": [], "slots": [ "0x0000000000000000000000000000000000000000000000000000000000000001", "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6", "0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf7", ], }, ], } ``` ### Storage packing Multiple small variables can be packed into a single slot: ```solidity uint8 public value1; // 1 byte uint16 public value2; // 2 bytes bool public flag; // 1 byte // All packed into slot 0 ``` ```typescript "value1": { "kind": "primitive", "name": "value1", "type": "uint8", "trace": [ { "current": { "hex": "0x00", "decoded": 0 }, "modified": true, "next": { "hex": "0x2a", "decoded": 42 }, "fullExpression": "value1", "slots": ["0x0000000000000000000000000000000000000000000000000000000000000000"], "offset": 0, "size": 1 } ] } ```