name: debug-diff description: Debug a failed diffyscan verification run. Analyzes diffs, identifies root causes. Use when diffyscan exits with non-zero or shows unexpected diffs. argument-hint: [config-path-or-contract-address]
Help debug a failed diffyscan verification. The user may provide a config path, contract address, or describe the failure.
Diagnostic steps
1. Check the digest output
Each run creates a timestamped directory under digest/. The timestamp is int(time.time()) captured at process start (see diffyscan/utils/constants.py):
digest/{timestamp}/logs.txt-- full run log (written byLoggerindiffyscan/utils/logger.py)digest/{timestamp}/diffs/{contract_address}/{filename}.html-- per-file HTML source diff reports
Find the most recent run:
ls -lt digest/ | grep "^d" | head -5
Then inspect its contents:
# View the log
cat digest/<timestamp>/logs.txt
# List HTML diff reports for a specific contract
ls digest/<timestamp>/diffs/<contract_address>/
2. Identify the type of failure
Source code diffs -- files differ between GitHub and the blockchain explorer:
- Check if the
commitingithub_repomatches what was actually deployed - Check if
relative_rootis correct (sources may live in a subdirectory of the repo) - Check if entries in
dependencieshave the rightcommithashes andrelative_rootpaths - Look for import path mismatches (flat vs nested -- may need
--support-browniefor brownie-verified contracts) - Open the HTML diff files (
digest/{timestamp}/diffs/{address}/*.html) to see exactly which lines differ - The report table in logs shows columns:
#,Filename,Found,Diffs,Origin,Report
Bytecode diffs -- compiled bytecode does not match on-chain:
- Missing or wrong constructor arguments -- see
constructor_calldataorconstructor_argsunder thebytecode_comparisonconfig key - Missing libraries -- check if the contract uses external libraries that need addresses in
bytecode_comparison.libraries - Wrong EVM version -- the explorer may report a different version than expected
- Immutable variables --
deep_match_bytecode()indiffyscan/utils/binary_verifier.pycompares instruction-by-instruction and tolerates differences that fall within known immutable reference regions. If all diffs are in immutable positions it logs a warning and reports a non-match. To accept it, add a granularallowed_diffs.bytecoderule with the exact on-chainimmutablesvalues (diffyscan prints a ready-to-paste suggestion in the final summary) rather than a blanket wildcard. Differences outside immutable regions raiseBinVerifierError, whichprocess_configcatches and records as a non-match (match=False) — it does not abort the run - Optimizer settings mismatch -- the solcInput from the explorer includes optimizer settings; the GitHub recompilation must match
- Flat-source contracts -- see Known limitations section below
Compilation errors (raised as CompileError):
- Missing GitHub sources -- a dependency is not configured or has a wrong
relative_root. The error message is:"missing GitHub sources for bytecode compilation; count=N; first=path1, path2..."(fromrun_bytecode_diff()indiffyscan/diffyscan.py) - Solc version mismatch -- check that the compiler version exists for the platform (solc binaries are cached in
~/.cache/diffyscan/solc/or equivalent viaXDG_CACHE_HOME)
Constructor calldata errors (raised as CalldataError from diffyscan/utils/calldata.py):
"No constructor calldata found for 0x... (not in config and not in explorer metadata)"-- need to addconstructor_calldataorconstructor_argsto the config"Contract 0x... found in both 'constructor_args' and 'constructor_calldata'"-- only one should be specified per contract
Network/API errors:
- Explorer API rate limiting -- try
--cache-explorerto reuse cached responses (cached in.diffyscan_cache/) - RPC errors -- check that
REMOTE_RPC_URLenv var is valid and the node supportseth_call - Explorer token missing -- the config key
explorer_token_env_varnames the env var holding the API token; if absent, falls back toETHERSCAN_EXPLORER_TOKEN
3. Common fixes
| Symptom | Likely fix |
|---|---|
"missing GitHub sources for bytecode compilation" |
Add the missing dependency to dependencies in the config with correct url, commit, and relative_root |
| Source diffs in OpenZeppelin or other dependency imports | Check the dependency commit hash matches what was used at deploy time |
| Bytecode mismatch after constructor | Add constructor_calldata (raw hex) or constructor_args (ABI-typed values) for the contract under bytecode_comparison |
"Failed to infer source path for library '...' from explorer metadata" |
Add library addresses to bytecode_comparison.libraries keyed by "path/to/File.sol": {"LibName": "0xAddr"} |
| All files show diffs | Wrong commit or relative_root in github_repo |
| Single contract fails | May need a per-contract constructor_calldata or constructor_args entry |
"Failed in binary comparison: Bytecodes have differences not on the immutable reference position" |
Real bytecode mismatch -- check compiler version, optimizer settings, EVM version, and library addresses |
"Exiting with non-zero code due to unallowed diffs" |
Either fix the diffs or add a granular allowed_diffs rule to the config for known-acceptable diffs (copy the suggestion diffyscan prints in the final summary; prefer immutables/byte_ranges/line_ranges/files/cbor_metadata over any: true) |
"Contract name in config does not match with blockchain explorer ... !=" |
Contract not verified on explorer — comment it out or verify it on the explorer first |
"Failed to get calldata: Explorer metadata has empty constructor calldata for 0x..." |
Factory-created contract — Etherscan has no constructor args. Add constructor_calldata manually (extract via getsourcecode API ConstructorArguments, debug_traceTransaction trace, or cross-chain reuse) |
"HTTP error: 404 ... contents/<path>.sol" |
Missing or wrong dependencies entry — the explorer source uses a path prefix not covered by config dependencies. Add a mapping for that prefix |
"err: intrinsic gas too low" |
Chain gas model needs a higher deployment gas cap (known on Mantle) — set deployment_gas_limit in the config (e.g. 30000000000) |
| All proxy bytecode diffs say "immutable reference position" | Normal for TransparentUpgradeableProxy — ProxyAdmin address baked in as immutable. Add an allowed_diffs.bytecode rule with the exact immutables values (from the printed suggestion) to accept it |
4. Suggest a re-run command
After fixing, suggest:
uv run diffyscan <config-path> --yes --cache-explorer --cache-github
--yes(-Y) skips the interactive prompt before each contract--cache-explorer(-E) reuses cached explorer responses from.diffyscan_cache/--cache-github(-G) reuses cached GitHub file fetches--support-brownieenables recursive retrieval for brownie-verified contracts with flattened import paths
To accept known diffs, use config allowed_diffs rules. (The former --allow-source-diff / --allow-bytecode-diff CLI flags have been removed; they were blanket any: true shorthands.) When a diff is uncovered, diffyscan prints a ready-to-paste allowed_diffs snippet in the final summary — paste it into the config and replace the placeholder reason, tightening any: true to a granular facet (immutables, byte_ranges, cbor_metadata, line_ranges, files) wherever possible. See the "Granular allowlists" section of the README.
Known limitations
If any of these apply, explicitly tell the user — these are inherent constraints of the tool, not bugs to debug.
Flat-source (non-standard-JSON) contracts
When a contract was verified on the explorer as a single flattened file (the explorer's SourceCode is a plain string, not JSON wrapped in {{}}), diffyscan cannot reliably reproduce the bytecode. Two problems arise:
- Lost compiler settings — diffyscan reconstructs a minimal solc input with only basic optimizer settings. Remappings, via-IR, and other original compiler settings are not available from the explorer for flat-verified contracts. Bytecode mismatches are expected even when source diffs are clean.
- Contract name used as source key — the explorer's
ContractNamefield is used as the solc source file key (e.g.{"sources": {"MyContract": {...}}}). If the explorer does not returnContractName, or the name does not match the actualcontractdeclaration in the Solidity source, the compiled output cannot be remapped andget_target_compiled_contract()will fail.
How to detect: check the explorer response or logs — if the solc input has a source key that looks like a contract name (no .sol extension, no path separators) rather than a file path, the contract was flat-verified.
What to tell the user: explain that bytecode comparison is unreliable for this contract due to the flat-source limitation. Recommend accepting the mismatch with an allowed_diffs.bytecode rule (use any: true here only because the bytecode genuinely cannot be reproduced — and say so in the reason), and note that source comparison is still valid.