Detects storage writes for off-chain historical data in Solidity contracts and recommends event emissions for gas savings. Audits events for missing indexed parameters on filterable fields.
How this skill is triggered — by the user, by Claude, or both
Slash command
/decipher-gas-optimizoor:event-loggingThis skill is limited to the following tools:
The summary Claude sees in its skill listing — used to decide when to auto-load this skill
Identify storage arrays used solely for off-chain historical data and replace
Identify storage arrays used solely for off-chain historical data and replace
the writes with event emission. Also audit event declarations for missing
indexed parameters on fields that callers will filter by.
eth_getStorageAt without indexer infrastructure| Rationalization | Why It's Wrong | Required Action |
|---|---|---|
| "We might need to read this on-chain later" | Speculation is not a reason to pay 22,100 gas per push today; if on-chain read is genuinely needed, design for it explicitly | Confirm on-chain read requirement before keeping storage |
| "Events can be lost or missed" | Events are part of the transaction receipt, permanent on-chain once included; off-chain indexers (The Graph, Etherscan) reliably capture them | Use events for historical data; storage only for current state |
| "It's just one array" | One array pushed once per user action at 1,000 users/day = 22,100,000 gas/day wasted | Estimate the volume, not the per-instance cost |
| "Removing storage might break existing callers" | Before removing, verify no external contract reads the array via eth_getStorageAt or ABI calls; confirm off-chain-only access | Audit callers, don't assume |
Trigger on any .sol file containing storage array push() calls or event
declarations without indexed parameters on address/uint256/bytes32 fields.
| Data type | On-chain access needed? | Recommendation |
|---|---|---|
| Historical array pushes | No (only off-chain) | Replace with emit Event() |
| Current state value | Yes | Keep in storage |
Event address/uint256/bytes32 fields used for filtering | — | Add indexed |
| More than 3 filterable fields | — | Index the 3 most important |
string/bytes event field | — | Do NOT index (hashes value, loses original) |
Find storage arrays written but never read on-chain.
Search the contract for storageArray.push(...) calls. For each array,
search the entire contract for any read access: storageArray[i],
storageArray.length, or passing storageArray to an internal function.
If the array is only pushed to and never read on-chain, it is an EV-001
candidate. Confirm the data is only needed off-chain (historical record,
analytics, audit trail).
Replace push() with emit for write-only arrays.
Define an event that captures the same fields as the struct or value being
pushed. Replace array.push(value) with emit EventName(fields...).
Remove the storage array declaration if it has no remaining readers.
Keep any storage variable that holds the current/latest value — only the
historical log is moved to events.
Review all event declarations for indexed coverage.
For each event declaration, identify fields that callers will filter by:
addresses (sender, recipient, owner), IDs (tokenId, orderId), and key
amounts. Add indexed to up to 3 such fields per event. Do not index
string, bytes, or dynamic arrays (indexing hashes them, making the
original value unrecoverable from the topic). Do not index fields that are
never used as filter criteria in off-chain queries.
Only read these files when explicitly needed — do not load all three by default:
| File | Read only when… |
|---|---|
resources/PATTERNS.md | You need EV-002 indexed topic cost calculations or The Graph query impact examples |
resources/CHECKLIST.md | Producing a formal /decipher-gas-optimizoor:analyze report and confirming all storage arrays were audited |
resources/EXAMPLE_FINDING.md | Generating a report and needing the exact output format for a history-array finding |
docs/evm-gas-reference.md | You need LOG opcode costs or the SSTORE vs event gas comparison table |
Report each finding with: pattern ID, storage variable or event name, file and line reference, gas estimate, and the exact change required.
Example finding (EV-001):
EV-001 | PriceOracle.sol:8 | priceHistory[]
Severity : high
Gas saved : ~21,094 gas per updatePrice() call
(LOG2 ~1,006 gas vs cold SSTORE 22,100 gas per history entry)
Before:
uint256[] public priceHistory;
function updatePrice(uint256 newPrice) external {
priceHistory.push(currentPrice); // SSTORE ~22,100 gas cold
currentPrice = newPrice;
}
After:
event PriceUpdated(
uint256 indexed timestamp,
uint256 oldPrice,
uint256 newPrice
);
function updatePrice(uint256 newPrice) external {
uint256 old = currentPrice;
currentPrice = newPrice;
emit PriceUpdated(block.timestamp, old, newPrice); // LOG2 ~1,006 gas
}
Reason: priceHistory is never read on-chain. Off-chain indexers (The Graph,
Etherscan) capture PriceUpdated events. Storage array eliminated entirely.
Verify:
forge test --match-test testUpdatePrice -vvvv # LOG opcode not SSTORE
forge test --gas-report
Example finding (EV-002):
EV-002 | NFTMarket.sol:6 | Sale event missing indexed fields
Severity : low
Gas saved : off-chain filtering ~O(1) vs O(n) scan; on-chain emit
costs +375 gas per indexed topic added
Before:
event Sale(address seller, address buyer, uint256 tokenId, uint256 price);
After:
event Sale(
address indexed seller,
address indexed buyer,
uint256 indexed tokenId,
uint256 price // non-indexed: price not a filter field
);
Reason: seller, buyer, and tokenId are the primary filter keys for
querying sales history. Ethereum bloom filters enable O(1) topic-based
filtering. price is rarely used as a filter key and is retrieved from
the data field.
Verify:
forge test --match-test testBuy -vvvv # LOG4 (3 indexed + event sig topic)
npx claudepluginhub zaryab2000/decipher-gas-optimizoor --plugin decipher-gas-optimizoorOptimizes gas usage in Solidity smart contracts on EVM chains with chain-specific analysis (full for Ethereum L1, calldata-focused for L2s). Scans, fixes, verifies.
Guides querying historical onchain data via Solidity events and The Graph indexers, avoiding inefficient block loops and RPC state reads.