Hunter workflow for verified and unverified contracts: Etherscan Contract tab, Sourcify exact vs match, ABI decoding, and read-only cast storage on a public example. Local forks only for any
Learn how to install Kali Linux dual boot alongside Windows 10/11. Complete step-by-step guide with partitioning, GRUB setup, troubleshooting, and safety warnings to prevent data loss.
13 min read
Transparent vs UUPS, uninitialized implementations, and storage-layout collisions — local forge inspect lab. No live upgrades.
35 min read
How to read smart contracts on Etherscan is a daily hunter skill: most of your time is spent reading strangers' code (or the absence of it), not writing your own vault. This Andrax tutorial — by Syed Zada Abrar — covers ~40% theory, ~60% practical.
Prerequisites: Solidity Essentials for Bug Hunters; Wallet, RPC & Block Explorer Setup; How to Start Web3 Bug Hunting in 2026. Hub: Web3 Bug Hunting Learning Path 2026.
Ethics fence (read this first): everything below that touches a live chain is read-only — viewing verified source, decoding ABIs, and querying public storage slots. Any exploit experiment belongs on Remix VM, Foundry/Anvil, or a public CTF. Never send confirmation transactions that abuse live user funds. Immunefi permanently bans testing on mainnet or public testnets of in-scope code (Immunefi rules).
Primary references: Sourcify, Sourcify exact match vs match, Solidity storage layout, cast storage, Etherscan contract verification docs.
On explorers, verified means someone uploaded source (and compiler settings) that recompile to the deployed bytecode — not that an auditor blessed the contract, and not that it is safe to use.
| Status | What you get | Hunter meaning |
|---|---|---|
| Verified (explorer) | Human-readable Solidity/Vyper + usually ABI | Fastest path: read modifiers, external calls, auth |
| Sourcify Exact Match | Byte-for-byte match including metadata fingerprint | Highest confidence the sources are the author's compile set |
| Sourcify Match (formerly “partial”) | Functional bytecode match; comments/names/metadata may differ | Still readable; treat identifiers as less authoritative |
| Unverified | Runtime bytecode (+ maybe ABI from elsewhere) | Slower: selectors, storage guesses, decompilers — higher uncertainty |
Sourcify's docs explain Exact Match vs Match clearly: Exact Match means the metadata hash fingerprint matches; a Match still means the logic compiles to the same runtime code aside from that fingerprint (docs). Verification ≠ safety. Sourcify itself says verifiers do not audit contents.
Hunter takeaway: verified source is a gift. Treat unverified bytecode as incomplete evidence — never invent Critical severity from a guessed decompiler comment alone.
Open a verified contract on Etherscan (or a Sourcify-backed explorer). Skim in the same order as the Solidity essentials guide, but now with explorer UI affordances:
Flattened single-file verifies are easier to Ctrl-F. Multi-file preserves imports and is closer to how the team ships. Prefer multi-file when available; use forge flatten later only in your local clone when you need a single audit notebook.
The ABI lists function signatures and types. It does not prove:
onlyOwner is correctly implementedinitialize cannot be called twiceHunters decode calldata with the ABI, then prove behavior against source (or against a local fork). Tools: explorer's Decode Input Data, Foundry cast 4byte-calldata / cast decode-calldata, and openchain.xyz-backed selector lookups via Cast.
Pick one well-known verified contract and one unverified (or “not verified”) contract on Ethereum mainnet. Famous tokens such as WETH9 at 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 are useful because explorers show verified source and you can practice read-only inspection without inventing a random DeFi target (Etherscan token page).
For the verified contract, write five bullets:
external/public functions that move value or rightsonlyOwner, roles, custom)call / delegatecall / token transfer you spotCross-check the same address on Sourcify Lookup if available. Note Exact Match vs Match.
For an unverified contract (any small unverified deployment you find on a public explorer — do not attack it):
cast codesize <addr> --rpc-url $RPC once Foundry is installed).cast 4byte <selector> or the explorer's decoder when known.Write one paragraph: “What would I need (source, ABI, or both) before filing any report?”
Solidity packs state into 32-byte slots. Layout rules live in the official docs (Layout of State Variables in Storage):
keccak256-based slot formulas — you do not “see” all keys by reading slot 0private does not hide data from chain observers (the Solidity essentials guide reminder)Hunters use storage to:
initialized flag flippedYou never need a private key to read storage.
cast storage on a public example (read-only)Install Foundry if you have not (installation / foundryup). Set a public Ethereum RPC (ETH_RPC_URL or --rpc-url). No private keys.
Example — WETH9 (verified, public):
# Runtime bytecode size (read-only)
cast codesize 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 --rpc-url $ETH_RPC_URL
# Raw storage at slot 0 (whatever the contract stores there — document, don't invent meaning)
cast storage 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 0 --rpc-url $ETH_RPC_URL
# Optional: compare against a view call if ABI is known
cast call 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 "name()(string)" --rpc-url $ETH_RPC_URL
cast storage returns the raw 32-byte word at a slot (reference). For mappings you will later use cast index to compute the slot from key + base slot — still read-only.
Lab write-up (required):
| Slot / call | Raw result | Meaning from verified source | Confidence |
|---|---|---|---|
| slot 0 | 0x… | (fill from source) | High / Medium / Guess |
name() | … | string view | High |
Rules for this lab:
cast send against mainnet protocolsWhen you have a local Foundry project (next spoke), prefer forge inspect Contract storage-layout on your compiled artifacts — that teaches packing without guessing live DeFi slots.
Common tasks:
# Resolve a selector
cast 4byte 0xa9059cbb
# Decode calldata (selector + args)
cast decode-calldata "transfer(address,uint256)" 0xa9059cbb000000000000000000000000...
# Pretty-print unknown calldata (online selector DB)
cast pretty-calldata 0xa9059cbb000000000000000000000000...
On Etherscan, open a transaction → Input Data → Decode Input Data when the contract is verified. For unverified callees, you may only get the selector until you find an ABI elsewhere (Sourcify, another chain's verify, a GitHub commit — always verify it matches bytecode).
Smell checklist while decoding:
delegatecall targets in proxy admin txsinitialize / upgradeTo called by a non-timelock EOAWhen source is missing:
Decompilers (e.g. community tools) produce hypotheses, not evidence. Pair every decompiler claim with a Foundry test on a fork after you are inside authorized scope — never as a mainnet spray.
If the explorer shows “Proxy,” you are usually looking at:
Read both sources. Storage that matters often lives in the proxy; logic lives in the implementation. EIP-1967 implementation slot is a common pattern — later the proxy guide goes deep. For now: whenever you see delegatecall, re-ask the Solidity essentials guide's question — “whose storage, whose auth?”
# Read-only helper many hunters use (implementation slot helpers exist in Cast)
cast implementation 0xYourProxy --rpc-url $ETH_RPC_URL
(Only against addresses you are studying for learning or in-scope review — still no exploit txs.)
| Trap | Better habit |
|---|---|
| Trusting the green “Contract Source Code Verified” badge as “safe” | Verified ≠ audited ≠ bug-free |
| Reporting from decompiler comments alone | Reproduce on local fork with a real test |
| Clicking Write Contract on a live vault to “confirm” | Use Anvil/Remix VM |
| Ignoring Sourcify Match vs Exact Match | Note match quality in your workbook |
Assuming slot 0 is always owner | Confirm against source / forge inspect |
| Pasting private keys into cast commands for “reads” | Reads need RPC only |
Address:
Chain:
Verified? Etherscan / Sourcify Exact / Sourcify Match / None
Compiler / optimizer:
Value-moving entry points:
Auth modifiers:
External calls / hooks:
Proxy? Impl address:
Storage notes (slot → meaning → confidence):
Open questions before any report:
Ethics check: read-only on live chain? PoC plan is local/CTF/scope?
Sometimes Etherscan shows verified source while Sourcify shows a Match (not Exact), or another explorer disagrees. Record:
If two verifications disagree functionally, stop and treat the on-chain bytecode as ground truth: recompile locally with each candidate source set until one matches. Filing against the wrong source file is an expensive embarrassment.
Verified pages often decode constructor arguments. Immutables are baked into runtime bytecode. When hunting proxies or cloned contracts (minimal proxies / clones), confirm which addresses were immutably set — routers, oracles, owners — before assuming an admin can still change them via storage.
# Creation bytecode / explorer “Contract Creation” tx is the usual place to study constructor args
# Cast helpers exist for some workflows; always pair with verified source
cast creation-code --help >/dev/null 2>&1 || true
Use the Foundry book when you need the exact cast subcommand for your installed version — CLI surfaces evolve (Cast reference hub).
Educational content by Syed Zada Abrar for Andrax (andraxpentester.in). Authorized research only.
How to write an Immunefi-ready bug report: structure, PoC expectations, severity language — template practice only.
40 min read
Sign in to leave a comment.