Documentation Index
Fetch the complete documentation index at: https://voltaire.tevm.sh/llms.txt
Use this file to discover all available pages before exploring further.
StorageDiff
Storage slot changes for a single contract address during transaction execution.
Overview
StorageDiff tracks before/after values for storage slots. Used extensively with debug_traceTransaction prestateTracer to analyze state modifications.
Type Definition
type StorageChange = {
readonly from: StorageValue | null; // null = didn't exist
readonly to: StorageValue | null; // null = deleted
};
type StorageDiffType = {
readonly address: Address;
readonly changes: ReadonlyMap<StorageKey, StorageChange>;
};
Usage
Creating Storage Diffs
import { StorageDiff } from '@tevm/voltaire/primitives';
// From Map
const changes = new Map([
[storageKey, { from: null, to: newValue }]
]);
const diff = StorageDiff.from(contractAddress, changes);
// From array
const changesArray = [
[key1, { from: oldValue, to: newValue }],
[key2, { from: value, to: null }] // Deletion
];
const diff2 = StorageDiff.from(contractAddress, changesArray);
Querying Changes
// Get specific slot change
const change = StorageDiff.getChange(diff, storageKey);
if (change) {
console.log(`${change.from} -> ${change.to}`);
}
// Get all changed slots
const keys = StorageDiff.getKeys(diff);
for (const key of keys) {
console.log(`Slot ${key.slot} changed`);
}
// Count changes
const count = StorageDiff.size(diff);
console.log(`${count} slots modified`);
Storage Changes
New Slots
Slot created during execution:
const change = {
from: null, // Didn't exist
to: storageValue // Now has value
};
Updates
Existing slot modified:
const change = {
from: oldValue,
to: newValue
};
Deletions
Slot cleared (SSTORE 0):
const change = {
from: existingValue,
to: null // Deleted
};
Debug Tracing
StorageDiff used with prestateTracer:
// Trace with prestate
const trace = await rpc.debug_traceTransaction(txHash, {
tracer: 'prestateTracer'
});
// Analyze storage changes per account
for (const [address, account] of Object.entries(trace)) {
if (account.storage) {
const changes = new Map();
for (const [slot, value] of Object.entries(account.storage)) {
const key = { address: Address.from(address), slot: BigInt(slot) };
changes.set(key, { from: null, to: StorageValue.from(value) });
}
const diff = StorageDiff.from(Address.from(address), changes);
console.log(`${StorageDiff.size(diff)} storage changes`);
}
}
Gas Analysis
Storage operations have significant gas costs:
SSTORE Gas Costs
- Set (0 → non-zero): 20,000 gas
- Update (non-zero → non-zero): 5,000 gas
- Clear (non-zero → 0): 15,000 gas refund
- Cold access: +2,100 gas
// Calculate gas costs from diff
let totalGas = 0;
for (const [key, change] of diff.changes) {
if (change.from === null && change.to !== null) {
totalGas += 20000; // New slot
} else if (change.from !== null && change.to !== null) {
totalGas += 5000; // Update
} else if (change.to === null) {
totalGas -= 15000; // Refund
}
}
Common Patterns
State Variable Tracking
Map contract state variables to storage slots:
// Track balance changes (slot 0)
const balanceKey = { address: tokenAddress, slot: 0n };
const balanceChange = StorageDiff.getChange(diff, balanceKey);
if (balanceChange) {
console.log(`Balance: ${balanceChange.from} -> ${balanceChange.to}`);
}
Mapping Storage
Solidity mappings use keccak256(key || slot):
// mapping(address => uint256) balances at slot 1
const userAddress = Address.from('0x...');
const slot = 1n;
const storageSlot = keccak256(
concat([pad(userAddress), pad(toBytes(slot))])
);
const key = { address: tokenAddress, slot: BigInt('0x' + storageSlot) };
const change = StorageDiff.getChange(diff, key);
API Reference
Constructors
StorageDiff.from(address, changes) - Create from address and changes
Methods
StorageDiff.getChange(diff, key) - Get change for specific slot
StorageDiff.getKeys(diff) - Get all changed slots
StorageDiff.size(diff) - Count changed slots
See Also