From b4a8e3613cca5200e72722a267426adfe47493ba Mon Sep 17 00:00:00 2001 From: Anil Kumar <150643132+anil-rome@users.noreply.github.com> Date: Mon, 27 Jul 2026 21:39:40 +0300 Subject: [PATCH] verify: point Sourcify at Rome's own instance, and sweep a network's deployments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `hardhat verify` was submitting to sourcify.dev, which answers "Chain 200010 not found" because it has never heard of these chains. Setting verify.sourcify.apiUrl to Rome's own instance is the whole fix; Etherscan and Blockscout are switched off rather than left to fail on every run, since no Rome chain is listed on either. Proven against live deployments rather than in the abstract — five contracts that were unverified on Hadrian now verify with one command each: BatchReader, PythPullAdapterImpl, SwitchboardV3AdapterImpl, CachedPythAdapterImpl, and CachedFeedAdapter (that one needs --contract, because a test harness in the tree shares its bytecode). scripts/verify-deployments.sh sweeps every address in deployments/.json, skips what is already verified, and classifies what it cannot do rather than reporting a bare failure count: · needs --contract bytecode matches several contracts · needs --constructor-args-path constructor takes arguments, and the values are not in the manifest · not this project's no solc metadata or no bytecode — a precompile handle or a foreign contract, of which the manifest holds 32 · drifted bytecode matches nothing in current source That last bucket is the reason to verify at deploy time: on Hadrian eight addresses are in it, and no amount of care later will verify them, because today's source no longer produces that bytecode. Advisory by design — exits 0 so a deploy is never failed by a verification quirk. A read loop rather than mapfile, because macOS ships bash 3.2 and a script that only runs on CI is one nobody can check locally; and the already-verified pre-check retries once, or a single timeout moves the counts between runs. --- hardhat.config.ts | 14 +++++ scripts/verify-deployments.sh | 101 ++++++++++++++++++++++++++++++++++ 2 files changed, 115 insertions(+) create mode 100755 scripts/verify-deployments.sh diff --git a/hardhat.config.ts b/hardhat.config.ts index 72f1618..1a86aa8 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -3,6 +3,20 @@ import { configVariable, defineConfig } from "hardhat/config"; export default defineConfig({ plugins: [hardhatToolboxViemPlugin], + + // Source verification. Rome runs its own Sourcify instance for every non-mainnet + // chain; without apiUrl the plugin submits to sourcify.dev, which answers + // "Chain 200010 not found" because it has never heard of these chains. Etherscan + // and Blockscout are switched off rather than left to fail on every run — no + // Rome chain is listed on either. + verify: { + sourcify: { + enabled: true, + apiUrl: "https://verify.testnet.romeprotocol.xyz", + }, + etherscan: { enabled: false }, + blockscout: { enabled: false }, + }, solidity: { profiles: { default: { diff --git a/scripts/verify-deployments.sh b/scripts/verify-deployments.sh new file mode 100755 index 0000000..f2a2050 --- /dev/null +++ b/scripts/verify-deployments.sh @@ -0,0 +1,101 @@ +#!/usr/bin/env bash +# Verify every deployed address recorded for a network, and say what it could not do. +# +# Why this exists: verification was manual, so coverage depended on who remembered. +# Measured on Hadrian before this: 35 of 42 registry contracts verified, and the +# seven gaps were almost all one deploy's worth of oracle adapters — the signature +# of a step someone skipped, not of a system that cannot verify. +# +# Advisory by design. It exits 0 even when some contracts cannot be verified, so a +# deploy is never failed by a verification quirk; the summary names what needs a +# human. Two quirks are expected and are NOT failures of this script: +# +# HHE80010 the bytecode matches more than one contract (a harness alongside the +# real one) — needs --contract :. +# HHE80018 the constructor takes arguments — needs --constructor-args-path, and +# the values are not in the deployments manifest. +# +# A third case is a real dead end: HHE80009, bytecode matching nothing in the +# current source, which means the deployment predates a source change and today's +# code cannot reproduce it. Verifying AT DEPLOY TIME is the only fix for that, which +# is the point of calling this from the deploy workflow rather than by hand later. +# +# Usage: scripts/verify-deployments.sh # e.g. hadrian +set -uo pipefail + +NET="${1:-}" +[[ -n "$NET" ]] || { echo "usage: $0 (a hardhat network name)"; exit 2; } + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +MANIFEST="$ROOT/deployments/$NET.json" +[[ -f "$MANIFEST" ]] || { echo "no deployments manifest for '$NET' at $MANIFEST"; exit 2; } + +# Read the chain id and verifier straight out of hardhat.config.ts, so this script +# cannot drift from what `hardhat verify` actually talks to. Both are single literals +# there; a miss is loud rather than silent. +CHAIN_ID="$(grep -A4 "^ $NET: {" "$ROOT/hardhat.config.ts" | grep -oE 'chainId: [0-9]+' | grep -oE '[0-9]+' | head -1)" +VERIFIER="$(grep -oE 'apiUrl: "[^"]+"' "$ROOT/hardhat.config.ts" | head -1 | sed 's/.*"\(.*\)"/\1/')" +[[ -n "$CHAIN_ID" && -n "$VERIFIER" ]] || { echo "could not read chainId/apiUrl for '$NET' from hardhat.config.ts"; exit 2; } + +echo "network $NET (chain $CHAIN_ID) → $VERIFIER" + +# A read loop rather than mapfile: macOS ships bash 3.2, which has no mapfile, and a +# script that only works on the CI runner is a script nobody can check locally. +ADDRS=() +while IFS= read -r _a; do [[ -n "$_a" ]] && ADDRS+=("$_a"); done < <(python3 - "$MANIFEST" <<'PY' + +import json, re, sys +seen, out = set(), [] +def walk(o): + if isinstance(o, dict): + for v in o.values(): walk(v) + elif isinstance(o, list): + for v in o: walk(v) + elif isinstance(o, str) and re.fullmatch(r"0x[0-9a-fA-F]{40}", o): + k = o.lower() + if k not in seen: + seen.add(k); out.append(o) +walk(json.load(open(sys.argv[1]))) +print("\n".join(out)) +PY +) + +already=0; verified=0; needs_help=0; drifted=0; foreign=0 +ATTENTION=() + +for addr in "${ADDRS[@]}"; do + # Retry the pre-check once: a single timeout otherwise reclassifies an already + # verified contract as a gap, and the summary changes between runs for no reason. + code="$(curl -s -o /dev/null -w '%{http_code}' -m 20 "$VERIFIER/v2/contract/$CHAIN_ID/$addr")" + [[ "$code" == "200" ]] || code="$(curl -s -o /dev/null -w '%{http_code}' -m 30 "$VERIFIER/v2/contract/$CHAIN_ID/$addr")" + if [[ "$code" == "200" ]]; then + already=$((already + 1)); continue + fi + out="$(cd "$ROOT" && npx hardhat verify --network "$NET" "$addr" 2>&1)" + if grep -q "verified successfully" <<<"$out"; then + verified=$((verified + 1)); echo " verified $addr" + elif grep -q "HHE80010" <<<"$out"; then + needs_help=$((needs_help + 1)); ATTENTION+=("$addr needs --contract (bytecode matches several)") + elif grep -q "HHE80018" <<<"$out"; then + needs_help=$((needs_help + 1)); ATTENTION+=("$addr needs --constructor-args-path") + elif grep -qE "HHE80005|HHE80004" <<<"$out"; then + # HHE80005 "compiled with Solidity <0.4.7" means the address carries no solc + # metadata, and HHE80004 means no bytecode at all — a Rome precompile handle. + # Either way it is + # not a contract this project compiled. The manifest records plenty of those, + # and counting them as gaps would make the summary useless. + foreign=$((foreign + 1)) + elif grep -q "HHE80009" <<<"$out"; then + drifted=$((drifted + 1)); ATTENTION+=("$addr bytecode matches nothing in current source — deployed before a source change") + else + needs_help=$((needs_help + 1)); ATTENTION+=("$addr $(grep -oE 'HHE[0-9]+.*' <<<"$out" | head -1)") + fi +done + +echo +echo "of ${#ADDRS[@]} addresses in the manifest:" +echo " already verified $already · newly verified $verified · need a human $needs_help · drifted $drifted · not this project's $foreign" +if (( ${#ATTENTION[@]} > 0 )); then + for line in "${ATTENTION[@]}"; do echo " · $line"; done +fi +exit 0