A comprehensive library for EVM transaction analysis that provides detailed storage slot access tracking with semantic labeling.
- 🔍 Complete storage access tracing: Identify all storage slots accessed during transaction execution with read/write classification
- 🏷️ Semantic storage labeling: Label storage slots with human-readable variable names, types, and mapping keys/array indices
- 🧠 Intelligent key detection: Automatically extract and match mapping keys and array indices from transaction data
- 🔢 Type-aware value decoding: Convert hex storage values to appropriate JavaScript types based on variable type definitions
- 📦 Storage packing support: Handle packed storage variables within a single 32-byte slot
- 🔄 Proxy support: Detect and resolve proxy contracts for complete implementation analysis
- 🔌 Framework compatibility: Works with popular EVM-compatible frameworks and providers
- 🧪 Comprehensive test coverage: Tested against diverse contract patterns and edge cases
pnpm add @polareth/trace
# or
npm install @polareth/trace
# or
yarn add @polareth/trace
import { traceStorageAccess } from "@polareth/trace";
// Trace a transaction with raw parameters
const trace = await traceStorageAccess({
rpcUrl: "https://1.rpc.thirdweb.com",
from: "0x...", // sender address
to: "0x...", // contract or recipient address (empty for contract creation)
data: "0x...", // calldata
});
// Trace with a typed contract call (similar usage to viem)
const traceWithAbi = await traceStorageAccess({
rpcUrl: "https://1.rpc.thirdweb.com",
from: "0x...", // sender address
to: "0x...", // contract address
abi: erc20Abi, // ERC20 standard ABI
functionName: "transfer",
args: ["0x...", "100000000000000000000"], // address, amount
});
// Trace existing transaction by hash
const traceByHash = await traceStorageAccess({
rpcUrl: "https://1.rpc.thirdweb.com",
txHash: "0x...", // past transaction hash
});
console.log(trace);
// Output shows exactly which storage slots were accessed, with semantic labeling whenever possible
The library offers multiple ways to trace transactions, from simple to advanced scenarios.
The simplest way to use the library is by providing an RPC URL and transaction parameters:
import { traceStorageAccess } from "@polareth/trace";
const trace = await traceStorageAccess({
rpcUrl: "https://1.rpc.thirdweb.com",
from: "0x...", // sender address
to: "0x...", // contract or recipient address (empty for contract creation)
data: "0x...", // calldata
});
For a more developer-friendly approach, use a contract ABI instead of raw transaction data:
import { traceStorageAccess } from "@polareth/trace";
const trace = await traceStorageAccess({
rpcUrl: "https://1.rpc.thirdweb.com",
from: "0x...", // sender address
to: "0x...", // contract address
abi: erc20Abi, // contract ABI
functionName: "transfer",
args: ["0x...", "1000000000000000000"], // recipient, amount
});
You can trace an already executed transaction by its hash:
import { traceStorageAccess } from "@polareth/trace";
const trace = await traceStorageAccess({
rpcUrl: "https://1.rpc.thirdweb.com",
hash: "0x...", // past transaction hash
});
The trace results are structured to clearly show which storage slots were read and written:
// Example output format:
{
"0xAbcd...": { // Contract address
"reads": { // Storage slots that were read
"0x0000...": [
{
"label": "totalSupply",
"type": "uint256",
"current": {
"hex": "0x0de0b6b3a7640000",
"decoded": 1000000000000000000n
}
}
],
"0x1234...": [
{
"label": "balances",
"type": "mapping(address => uint256)",
"current": {
"hex": "0x056bc75e2d63100000",
"decoded": 100000000000000000000n
},
"keys": ["0x1234..."] // Detected mapping key (sender address)
}
]
},
"writes": { // Storage slots that were written
"0x5678...": [
{
"label": "balances",
"type": "mapping(address => uint256)",
"current": {
"hex": "0x00",
"decoded": 0n
},
"next": {
"hex": "0x056bc75e2d63100000",
"decoded": 100000000000000000000n
},
"keys": ["0x5678..."] // Detected mapping key (recipient address)
}
]
},
"intrinsic": { // Account state changes
"balance": {
"current": "0x1234...",
"next": "0x1230..."
},
"nonce": {
"current": "0x01",
"next": "0x02"
}
}
}
}
For a more fine-grained control, you can provide your own Tevm client:
import { createMemoryClient, http } from "tevm";
import { mainnet } from "tevm/common";
import { Tracer, traceStorageAccess } from "@polareth/trace";
// Initialize client
const client = createMemoryClient({
common: mainnet, // pass the common to avoid an unnecessary fetch for the chain config
fork: {
transport: http("https://1.rpc.thirdweb.com"),
blockTag: "latest",
},
});
// Option 1: Pass it directly to the traceStorageAccess function
const trace = await traceStorageAccess({
client,
from: "0x...",
to: "0x...",
data: "0x...",
});
// Option 2: Create a reusable tracer instance
const tracer = new Tracer({ client });
const trace2 = await tracer.traceStorageAccess({
from: "0x...",
to: "0x...",
data: "0x...",
});
TODO: review that and update when implemented
The library handles complex data structures with accurate labeling:
// Example output for complex structures:
// Nested mappings
"0xabcd...": [
{
"label": "userTokenApprovals",
"type": "mapping(address => mapping(uint256 => bool))",
"current": { "hex": "0x01", "decoded": true },
"keys": ["0x1234...", "42"] // [userAddress, tokenId]
}
]
// Arrays with indices
"0xefgh...": [
{
"label": "tokenOwners",
"type": "address[]",
"current": {
"hex": "0x0000000000000000000000001234567890123456789012345678901234567890",
"decoded": "0x1234567890123456789012345678901234567890"
},
"index": 5 // Array index
}
]
// Packed storage variables
"0x0000...": [
{
"label": "smallValue1",
"type": "uint8",
"current": { "hex": "0x00", "decoded": 0 },
"next": { "hex": "0x2a", "decoded": 42 }
},
{
"label": "smallValue2",
"type": "uint8",
"current": { "hex": "0x00", "decoded": 0 },
"next": { "hex": "0x7b", "decoded": 123 },
"offset": 1
},
{
"label": "flag",
"type": "bool",
"current": { "hex": "0x00", "decoded": false },
"next": { "hex": "0x01", "decoded": true },
"offset": 2
}
]
The library is tested against a comprehensive set of contract patterns:
- ✅ Basic Value Types: Integers, booleans, addresses, bytes
- ✅ Storage Packing: Multiple variables packed in a single slot
- ✅ Complex Data Structures:
- Mappings with various key/value types
- Nested mappings (arbitrary depth)
- Fixed and dynamic arrays
- Structs and nested structs
- ✅ Contract Interactions: Calls between multiple contracts
- ✅ Delegate Calls: Storage access through delegate calls
- ✅ Library Patterns: Internal and external libraries
- ✅ Proxy Patterns: Transparent and minimal proxies
- ✅ Assembly Storage Access: Low-level SSTORE/SLOAD operations
- ✅ Token Standards: ERC-20, ERC-721, etc.
The library combines transaction simulation with intelligent storage slot analysis:
- Transaction Simulation: Uses TEVM to simulate the transaction and capture all storage access
- Trace Analysis: Analyzes the EVM execution trace to extract potential mapping keys and array indices
- Type Detection: Identifies variable types from contract metadata or ABI
- Slot Computation: Applies Solidity's storage layout rules to match accessed slots with variable names
- Value Decoding: Converts raw storage values to appropriate JavaScript types
- Result Labeling: Produces a comprehensive report of all storage accesses with semantic labels
- Slot Engine: Computes storage slots for various data structures
- Trace Analyzer: Extracts potential keys/indices from EVM execution traces
- Storage Layout Parser: Extracts contract storage information from metadata
- Value Decoder: Converts hex values to appropriate JavaScript types
pnpm test:unit
- Run unit testspnpm test:staging
- Run staging environment
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature
) - Commit your changes (
git commit -m 'Add some amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
Please make sure your code follows the existing style and passes all tests, including any new tests for verifying the new feature, if applicable.
This project is licensed under the MIT License - see LICENSE for details.