Skip to content

Latest commit

 

History

History
102 lines (74 loc) · 5.77 KB

CLAUDE.md

File metadata and controls

102 lines (74 loc) · 5.77 KB

CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

What is Diffyscan

Diffyscan verifies deployed EVM smart contracts match their GitHub source by:

  1. Fetching verified source from blockchain explorers (Etherscan, Blockscout, zkSync, Mantle)
  2. Diffing against GitHub repo sources (HTML diff reports saved under digest/<timestamp>/diffs/)
  3. Optionally recompiling from GitHub and comparing bytecode against on-chain bytecode, handling constructor args, libraries, and immutable references

Commands

# Install dependencies
uv sync --locked --group dev

# Run tests
uv run pytest -q

# Run a single test
uv run pytest tests/test_config_loading.py -q
uv run pytest tests/test_config_loading.py::test_name -q

# Run the CLI
uv run diffyscan configs/<project>/<mainnet|testnet>/<config>.yaml

# Machine-readable run (one JSON document on stdout, logs only in digest/<ts>/logs.txt; format: docs/json-output.md)
uv run diffyscan configs/<project>/<mainnet|testnet>/<config>.yaml --json -E -G

# Format code
uv run black diffyscan/ tests/

# Run pre-commit hooks
uv run pre-commit run --all-files

# Install git hooks (pre-commit + commit-msg)
uv run pre-commit install --hook-type pre-commit --hook-type commit-msg

Task workflows

Use the relevant repository skill; read linked references only when needed:

  • new-config: create or extend a deployment config.
  • validate-config: review config structure and runtime prerequisites.
  • debug-diff: diagnose failed or incomplete verification.
  • allowed-diffs: justify and scope expected differences or tighten wildcards.
  • add-explorer: extend explorer routing or response parsing.

Architecture

Entry point: diffyscan/diffyscan.py:main — parses CLI args, loads config (JSON or YAML), then runs source diff and bytecode diff for each contract.

Core flow

process_config() orchestrates per-config:

  • run_source_diff() — fetches explorer sources and GitHub sources, generates HTML diffs via difflib.HtmlDiff
  • run_bytecode_diff() — compiles GitHub sources with solc, simulates deployment via eth_call, compares bytecode

Utility modules (diffyscan/utils/)

  • explorer.py — largest module; fetches/parses contracts from blockchain explorers, handles multi-chain API differences, library detection, EVM version normalization, solc compilation
  • github.py — GitHub API integration, file fetching with caching, dependency resolution (e.g. @openzeppelin/contracts-v4.4)
  • binary_verifier.py — EVM bytecode parsing into instructions, metadata separation, and difference analysis with immutable region annotations
  • compiler.py — solc binary download (platform-aware), SHA256 verification, compilation via standard JSON
  • encoder.py — ABI encoding for constructor arguments (address, bool, int/uint, bytes, tuples, arrays)
  • calldata.py — resolves constructor calldata from config or explorer metadata
  • node_handler.py — RPC calls: eth_getCode, eth_chainId, eth_call
  • common.py — config loading (with YAML hex address validation), caching with SHA256 validation
  • http_client.py — shared HTTP requests, headers, per-host pacing, bounded transient GET retries and error handling
  • custom_types.py — TypedDict definitions: Config, BinaryConfig, ExplorerContract, GithubRepo
  • custom_exceptions.py — exception hierarchy for handled contract errors

Config schema

Configs live in configs/, laid out state-mate-style as configs/<project>/<mainnet|testnet>/<file>.yaml:

contracts:          { "0xaddr": "ContractName" }
network:            "mainnet"  # optional legacy label, unused at runtime
explorer_hostname:  "api.etherscan.io"
explorer_token_env_var: "ETHERSCAN_EXPLORER_TOKEN"
github_repo:        { url, commit, relative_root }
dependencies:       { "dep_name": { url, commit, relative_root } }
bytecode_comparison: { constructor_calldata, constructor_args, libraries, deployment_from, extra_sources }
                    # libraries keyed by the library's DEFINITION file: { "src/lib/Foo.sol": { "Foo": "0xaddr" } }
                    # explorer-provided libraries are auto-detected and re-keyed to the definition file for solc linking
allowed_diffs:      { bytecode: {...}, source: {...} }  # optional; see below

allowed_diffs declares known/expected diffs per contract so a run still passes while everything else stays verified (validated by diffyscan/utils/allowed_diffs.py). Each rule needs a reason. Prefer the tightest facet — bytecode: immutables, byte_ranges, cbor_metadata, constructor_args/constructor_calldata; source: line_ranges, files. any: true is a blanket wildcard that hides all future drift — avoid it unless a diff genuinely can't be scoped, and say why in the reason. tests/test_no_wildcard_regression.py guards against new wildcards. bytecode_comparison.extra_sources ({ "0xaddr": ["path/to/File.sol"] }, optional) names additional source files to fetch from the configured GitHub repo/commit and add to the compilation — for contracts whose explorer-verified source set omits a file the pinned GitHub source imports (e.g. a newly-added interface). The files are fetched the same way as all others (honoring dependencies), so verification stays honest.

Environment variables

Supports loading from .env (see .env.example), or set directly: GITHUB_API_TOKEN, ETHERSCAN_EXPLORER_TOKEN, REMOTE_RPC_URL (for bytecode comparison).

Code style

  • Formatter: black (enforced via pre-commit)
  • Commit messages: validated by conventional-pre-commit
  • Python >=3.11