Skip to content

fix(plugin): keep injected artifacts within the localStorage quota - #12

Merged
MatiasOS merged 2 commits into
openscan-explorer:mainfrom
MatiasOS:issue-402-trim-build-info
Sep 11, 2026
Merged

fix(plugin): keep injected artifacts within the localStorage quota#12
MatiasOS merged 2 commits into
openscan-explorer:mainfrom
MatiasOS:issue-402-trim-build-info

Conversation

@MatiasOS

Copy link
Copy Markdown
Member

Part of openscan-explorer/explorer#402: the plugin half. The explorer's own ingestion paths (dev server, ZIP import) have the same problem and follow in a separate PR.

Problem

The plugin hands artifacts to the explorer by inlining a script into index.html that writes them to localStorage["OPENSCAN_ARTIFACTS_JSON_V1"]. Every artifact carried its entire build info — the full solc standard JSON input, with every source in the compilation job — and contracts compiled in the same job each carried their own copy.

On a project of realistic size that outgrows the browser's localStorage quota. setItem throws QuotaExceededError, the inline catch reduces it to a generic console.warn, and the explorer finds no local artifact for any address. A local chain has no Sourcify/Etherscan fallback, so every deployed contract shows as unverified, and nothing in the terminal hints at why. A failed write also left the previous value in place, so after an earlier deploy the explorer could map stale artifacts onto reused deterministic addresses.

Fix

Trim build info to what the explorer reads (artifacts.ts, deployment-tracker.ts). The explorer reads four scalar fields of buildInfo: solcVersion, solcLongVersion, input.language and input.settings.evmVersion. That holds on explorer main and in the 1.2.5-alpha bundle this plugin pins (checked in node_modules/@openscan/explorer/assets). A new toExplorerBuildInfo keeps exactly those. Both loaders now parse the build info into a local, run resolveSourceCode against it (the #399 fallback still needs input.sources), then attach only the trimmed fields. ArtifactData.buildInfo is typed as ExplorerBuildInfo instead of unknown.

No explorer change or release is needed: every field it reads is still there.

Harden the injection (services/webapp.ts):

  • The script calls removeItem before setItem, so a failed write leaves the key empty instead of holding the previous deploy's artifacts.
  • The browser warning includes the error name, so QuotaExceededError is recognisable at a glance.
  • The terminal warns once per process when the serialised payload passes 2M characters. Quotas run 5–10 MB per origin and a character can cost 2 bytes, so the tightest quotas fit ~2.5M characters. The warning is gated because artifacts are re-injected on every HTML request, so an ungated one would repeat on every refresh.

Size reduction

Measured on the example project with forge-std's Test.sol temporarily imported into Counter.sol (not committed), so Counter and TestToken share a ~800K-character build info. Deployed via Ignition and via scripts/deploy.ts, then read the payload straight from the served index.html:

Before After Reduction
Payload characters 1,599,678 5,613 99.65% (~285×)
Stored by Chromium ~3.05 MiB ~5.5 KiB 99.82% (~570×)

Bytes shrink more than characters. Before, 20 non-Latin-1 characters from the forge-std sources made Chromium store the whole value as UTF-16 (2 bytes per character); after, none remain and it is stored at 1 byte per character.

Contract Entry before Entry after Reduction buildInfo before → after
Counter (Ignition) 797,009 1,306 99.84% 795,837 → 134 (99.98%)
Counter (raw deploy, DeploymentTracker) 798,166 1,306 99.84% 796,994 → 134 (99.98%)
TestToken (Ignition) 4,364 2,862 34.4% 1,636 → 134 (91.8%)

TestToken's build info was already small, so what remains of its entry is mostly ABI and source. The trimmed buildInfo is a constant 134 characters however large the compilation job is and however many contracts share it, so the saving grows with project size. This fixture stays under Chromium's quota even before the fix: it demonstrates the reduction, not the quota failure itself.

Verification

  • sourceCode is still present for every entry after the trim.
  • In headless Brave (Chromium 152, driven over CDP), all three contract pages show Verified, compiler 0.8.29+commit.ab55807c and EVM version cancun. localStorage holds the 5,613-character value, and there is no [openscan] Failed to inject artifacts warning.
  • Against the built WebappService: a small payload prints no warning, an oversized one prints exactly one across repeated page loads, and the script removes the key before setting it and logs the error name.
  • pnpm build and pnpm lint pass.

Not included

  • The explorer side of #402: the dev server (injectDevArtifacts) and the ZIP import attach the full build info through their own paths. Normalising inside saveJsonFilesToStorage belongs in the explorer repo, as does the drag-and-drop import that stores "undefined".
  • No tests: the plugin still has no test script, as noted in fix(plugin): resolve contract sources from nested directories #11.

Every injected artifact carried its whole build info - the full solc
standard JSON input, with every source in the compilation job - and
contracts compiled in the same job each carried their own copy. Once a
project pulls in OpenZeppelin or forge-std, that runs to megabytes per
contract and the localStorage write in the served page fails on quota.
A local chain has no Sourcify or Etherscan fallback, so every deployed
contract then shows as unverified.

The explorer reads four scalar fields of it: solcVersion,
solcLongVersion, input.language and input.settings.evmVersion. Source
resolution still falls back to input.sources, so both loaders now keep
the parsed build info local, resolve the source against it, and attach
only those four fields.

With forge-std's Test.sol imported into the example project's Counter,
the payload injected for three deployments drops from 1.6M characters
to 5.6K.

Refs openscan-explorer/explorer#402
A failed injection was invisible in the terminal and vague in the
browser: the inline script reduced any error to a generic console
warning, and the previous value stayed in localStorage. Hardhat derives
deployment addresses from deployer and nonce, so after a redeploy a new
contract could pick up an old contract's ABI and source.

Remove the key before writing it, so a failed write leaves nothing
behind, and put the error name in the browser warning so
QuotaExceededError is recognisable at a glance. Warn in the terminal,
once per process, when the serialised payload passes 2M characters -
roughly where the tightest localStorage quotas give out.

Refs openscan-explorer/explorer#402
@MatiasOS
MatiasOS requested a review from AugustoL September 11, 2026 17:37
@MatiasOS
MatiasOS merged commit 50b04ce into openscan-explorer:main Sep 11, 2026
1 check passed
@MatiasOS
MatiasOS deleted the issue-402-trim-build-info branch September 11, 2026 17:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant