Skip to content

feat: add zksync EraVM source verification and Abstract chain#2788

Open
coffeexcoin wants to merge 47 commits into
argotorg:stagingfrom
coffeexcoin:feat/zksync
Open

feat: add zksync EraVM source verification and Abstract chain#2788
coffeexcoin wants to merge 47 commits into
argotorg:stagingfrom
coffeexcoin:feat/zksync

Conversation

@coffeexcoin

Copy link
Copy Markdown

Summary

Adds ZKsync EraVM/zksolc source verification support to Sourcify, extending the standard JSON verification API surface.

Zksolc verification is selected by passing zksolcVersion to the normal /v2/verify/{chainId}/{address} JSON-input flow The underlying compilerVersion remains the solc or era-solc version requested by the user, and Sourcify records the final era-solc/solc version used for the verified compilation.

Changes by module

packages/compilers

  • Adds a local zksolc compiler adapter exported from @ethereum-sourcify/compilers.
  • Downloads and caches zksolc binaries from the current Matter Labs release repository, with legacy pre-1.5 binary support from zksolc-bin.
  • Downloads and caches era-solc binaries from the Matter Labs era-solidity releases.
  • Invokes zksolc through standard JSON using --standard-json and --solc.
  • Handles pre-1.5 zksolc CLI differences/nuances:
    • maps settings.enableEraVMExtensions / settings.isSystem to --system-mode
    • maps settings.forceEVMLA / settings.forceEvmla to --force-evmla
    • avoids requesting unsupported evm output for legacy zksolc versions
  • Supports exact upstream Solidity compiler versions like v0.8.26+commit.8a97fa7a by resolving Sourcify's normal native solc binaries and passing them to zksolc.
  • Adds zksolc compiler tests with broader parity coverage against solc compiler, including version normalization, binary URL patterns, legacy repositories, pre/post-1.5 argument differences, and upstream solc selection.

packages/lib-sourcify

  • Adds ZkSolcCompilation for EraVM Solidity compilations.
  • Adds IZkSolcCompiler and includes zksolc in the shared compilation type surface.
  • Keeps the language as Solidity but marks the compilation target VM as eravm.
  • Normalizes and expands compiler-version candidates for zksolc:
    • exact era-solc versions like 0.8.26-1.0.1
    • solc release versions like 0.8.26
    • commit-bearing solc versions like v0.8.26+commit.8a97fa7a
  • For commit-bearing solc versions, tries the exact upstream solc first, then falls back through compatible era-solc editions.
  • Enforces known valid era-solc edition combinations:
    • supports editions 1.0.2, 1.0.1, and 1.0.0
    • excludes 1.0.2 for zksolc versions before 1.5
    • excludes unavailable Solidity/era-solc combinations
  • Retries zksolc verification inside the normal verification flow when a candidate compiles but does not match bytecode.
  • Adjusts output selection so required zksolc outputs are requested without dropping user-provided output selections.
  • Treats zksolc byte-for-byte matches as perfect matches even though EraVM auxdata does not follow normal EVM CBOR metadata assumptions.
  • Disables strict CBOR validation for zksolc bytecode matching, while keeping existing Solidity/Vyper/Fe behavior intact.
  • Exports zksolc compilation metadata in VerificationExport:
    • compiler: "zksolc"
    • compilerVersion: zksolc version
    • zksolc.solcCompilerVersion: final underlying solc/era-solc version used
  • Adds a zksolc compilation test matrix covering:
    • zksolc 1.5.x with newer solc/era-solc behavior
    • pre-1.5 zksolc combinations such as 1.4.1 + 0.8.4-1.0.1
    • older zksolc combinations such as 1.3.17 + 0.7.6-1.0.1
    • invalid era-solc/zksolc combinations
    • compile retry and match retry behavior
    • metadata object parsing
    • perfect vs partial match behavior

services/server

  • Add zksolc verification on the existing JSON-input verification endpoint.
  • Adds optional zksolcVersion to the request body.
  • Adds ZkSolcJsonInput as a Solidity standard JSON superset for zksolc-specific settings.
  • Detects zksolc verification from zksolcVersion and zksolc-specific settings.
  • Rejects zksolc requests unless:
    • the input language is Solidity
    • zksolcVersion is present when zksolc-specific settings are used
    • the target chain has zksolc support enabled
  • Adds ZkSolcLocal and passes zksolc/era-solc/solc repository paths through CLI and worker initialization.
  • Adds config entries for zksolcRepo and eraSolcRepo.
  • Updates API docs to describe zksolc verification via Solidity standard JSON and to show an EraVM example request.
  • Updates persisted verification output mapping so zksolc verified contracts store:
    • compiler = "zksolc"
    • version = <zksolcVersion>
    • additional_input.era_solc_version = <final underlying solc/era-solc version>
  • Adds tests for service validation, worker threading, API request behavior, and database column mapping.

Chain configuration

  • Adds chain-level zksolc capability metadata:
    • zksolc.supported: true | false
  • Threads this field through SourcifyChain and generated chain config loading.
  • Enables Abstract Mainnet in the default local chain config:
    • chain id 2741
    • RPC https://api.mainnet.abs.xyz
    • zksolc.supported: true
  • Non-zksolc chains reject zksolc verification attempts before worker compilation.

Database

  • Adds a Sourcify-owned migration allowing additional_input.era_solc_version on compiled_contracts.
  • Updates the Sourcify database schema snapshot accordingly.
  • Does not update the upstream services/database/database-specs submodule.

Verification behavior

For zksolc requests, supports both explicit era-solc compiler versions and normal Solidity compiler versions:

  • If compilerVersion is an era-solc version, Sourcify uses that exact era-solc binary.
  • If compilerVersion is a commit-bearing Solidity release, for example v0.8.26+commit.8a97fa7a, Sourcify first tries the exact upstream solc binary through zksolc.
  • If the exact upstream solc candidate fails to compile or fails bytecode matching, Sourcify falls back through compatible era-solc candidates until one matches or all candidates are exhausted.
  • If compilerVersion is a plain Solidity release, Sourcify expands it directly to compatible era-solc candidates.

This is intended to support explorer-style submissions where users only know the Solidity compiler version while still allowing exact era-solc requests when that is known; this supports existing tooling and legacy verifications/compile artifacts that may not bear the exact solc fork edition (v1.0.x)

Testing

Automated coverage added or expanded:

  • packages/compilers/test/zksolcCompiler.spec.ts
  • packages/lib-sourcify/test/Compilation/ZkSolcCompilation.spec.ts
  • packages/lib-sourcify/test/SourcifyChain.spec.ts
  • services/server/test/integration/apiv2/verification/verify.json.spec.ts
  • services/server/test/unit/VerificationService.spec.ts
  • services/server/test/unit/utils/database-util.spec.ts
  • services/server/test/unit/verificationWorker.spec.ts

Manual verification performed against Abstract Mainnet contracts from abscan/Etherscan-compatible source metadata, including exact upstream solc fallback behavior for commit-bearing compiler versions.

Candidate contracts tested include:
https://abscan.org/address/0xbc176ac2373614f9858a118917d83b139bcb3f8c#code - zksolc 1.5.7, solc v0.8.26+commit.8a97fa7a (resolves to v0.8.26-1.0.1)

https://abscan.org/address/0x4f7589c619d59443db52489dd375de63e03e671d#code - zksolc v1.3.19, solc v0.6.12+commit.27d51765 (resolves to direct v0.6.12+commit.27d51765)

https://abscan.org/address/0x0929d81a73a83b73e5de2ba63a15ce2a18addbe2#code - zksolc v1.5.15, solc v0.8.26+commit.8a97fa7a (resolves to v0.8.26-1.0.2)

@coffeexcoin coffeexcoin marked this pull request as ready for review May 13, 2026 13:09
@coffeexcoin

Copy link
Copy Markdown
Author

Happy to break this up into smaller PRs by area (compiler then lib then api etc) if that is easier for review/merging

@kuzdogan kuzdogan self-assigned this May 19, 2026
kuzdogan and others added 2 commits May 19, 2026 19:16
The function always returns false for `vm-1.5.0-a167aa3` regardless of
the `target` argument. That only happens to produce correct behavior
because every caller compares against 1.5.0; for any other target
(e.g. 1.0.0), the helper would mis-classify that pre-release as
below the threshold.

Hardcoding 1.5.0 in the name and the body makes the contract honest:
this is specifically an "is zksolc ≥ 1.5?" check, and the vm- guard
is the correct answer to that specific question.

Updates the inline explanatory comment to match.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Explain that the -gnu suffix on the Windows platform string is the
upstream MinGW filename (not a libc choice), and that the Linux
candidate list carries both glibc and musl filenames for fallback.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
kuzdogan and others added 3 commits May 20, 2026 09:45
Document the zksolc / era-solc / upstream-solc compiler model: their
naming and roles, the zksolc 1.5.0 CLI/output-selection split, the
modern vs legacy release repos, and the Linux gnu/musl libc handling.
Add a pointer comment at the top of zksolcCompiler.ts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Covers logic that previously ran only behind network-dependent
integration tests or not at all: the pre/post-1.5 standard-JSON CLI
argument mapping, the isZkSolcVersionAtLeastV15 edge cases (including
the unparseable-version fail-open), the primary/legacy download
fallback in getZkSolcExecutable, and the era-solc vs upstream-solc
routing in getZkSolcBaseSolcExecutable.

Network seams (fetchWithBackoff, getSolcExecutable) are stubbed via
module-namespace reassignment so the tests stay offline and fast.
getZkSolcStandardJsonArgs and getZkSolcBaseSolcExecutable are exported
to make them testable, consistent with the file's existing pattern of
exporting internal helpers.

zksolcCompiler.ts coverage: lines 76% -> 87%, functions 90% -> 100%.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Add a ZKSOLC.md section describing how Sourcify spawns zksolc and how
zksolc in turn spawns a solc backend via --solc, plus the zkSync
compiler-toolchain diagram. Because zksolc spawns the backend as a
native child process, the Emscripten soljson build can never be used
for EraVM verification.

That made solJsonRepoPath dead weight: it was threaded through
useZkSolcCompiler, getZkSolcBaseSolcExecutable and
getUpstreamSolcExecutable but never used -- yet still load-bearing as a
guard condition, so omitting it silently disabled upstream-solc
resolution. Remove it from those functions, from ZkSolcLocal, and from
both ZkSolcLocal call sites; the guard now checks only solcRepoPath.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Comment on lines +250 to +253
const alignmentPadding = bytecodeWithoutPrefix.substring(
metadataStart,
cborStart,
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get why the padding is returned inside "auxdata". For compatibility, I think it should not be there. It seems not decodable otherwise.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. We have two options. As a recap, zksync bytecode is always word size length (32bytes) and always has an odd number of words. For that reason the auxdata gets padded zeros.

Options:

  1. Don't return padding in the extracted "auxdata" and decode as usual
  2. Include padding in the extraced "auxdata" but remove padding when decoding

I decided to go with the option 2. becuase to me semantically the padding is part of the "auxdata" (but not valid CBOR). So that when you "ignore" this field you are left with a valid bytecode length. The alternative is to have zeros in the executional bytecode. This works most of the time because they are always zero. But this is semantically wrong, and also, disallows the verification when a bytecode has appendCBOR: false and the other has it, or two have different CBOR auxdata lengths.

Comment on lines +360 to +381
for (;;) {
try {
const contract = await this.compileAndReturnCompilationTarget();
this._metadata = parseContractMetadata(contract.metadata);
return;
} catch (error: any) {
if (
error instanceof CompilationError &&
error.code === 'compiler_error' &&
this.useNextSolcCompilerVersionCandidate()
) {
logDebug('Retrying zksolc compilation with next era-solc candidate', {
zksolcVersion: this.zksolcVersion,
solcVersion: this.solcCompilerVersion,
requestedSolcVersion: this.requestedSolcCompilerVersion,
});
continue;
}

throw error;
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This for (;;) is bounded and does terminate, but the bound isn't visible at the loop site. Termination relies entirely on the side effect of useNextSolcCompilerVersionCandidate() advancing a monotonic index over the finite solcCompilerVersionCandidates list (≤ 4 entries) and returning false once exhausted. A future refactor that makes that method reset the index or become idempotent would silently turn this into a genuine infinite loop, and nothing here would show it.

Consider making the bound explicit — e.g. iterate over the candidate list directly.

} catch (error: any) {
if (
error instanceof CompilationError &&
error.code === 'compiler_error' &&

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This retries on every compiler_error, not just era-edition-resolution failures. A contract with a genuine source-level error (the common failure case) will be re-run against every remaining era-solc candidate before finally throwing — and each retry is a full compile cycle: download the era-solc binary + spawn zksolc + zksolc spawns solc. So a contract that simply doesn't compile can cost ~3–4 binary downloads and compiles, adding real latency to the unhappy path.

Can we distinguish "wrong era edition / solc not found" from "the Solidity itself is broken" and only retry on the former? If compiler_error can't be narrowed, worth a comment noting the retry cost is intentional.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See my comment at #2788 (comment) but essentially these wrong versions just show as a bytecode mismatch, there's no real identifying factor of "you used the wrong zksolc version"

Comment on lines +333 to +351
private retryWithNextCompilerVersionCandidate(error: any): boolean {
if (
!(error instanceof VerificationError) ||
(error.code !== 'no_match' && error.code !== 'bytecode_length_mismatch')
) {
return false;
}

const shouldRetry = this.compilation.useNextCompilerVersionCandidate();
if (shouldRetry) {
logInfo('Retrying verification with next compiler version candidate', {
address: this.address,
chainId: this.sourcifyChain.chainId,
compilerVersion: this.compilation.compilerVersion,
});
}

return shouldRetry;
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain why this retry mechanism is necessary? This moves a way from our paradigm that the user should retry if a wrong compiler version is used.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please give a concise explanation. This ton of AI comments is hard to read (in case it was stated there already).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

essentially some tooling and some etherscan verifications provide a verification with a compiler version like "v0.8.26+commit.8a97fa7a" which doesn't accurately reflect the actual zksolc "edition" used (1.0.0, 1.0.1, 1.0.2)

Additionally the "+commit.hash" is a valid modifier for some older versions of zksolc but not all. Its overall not a great situation but an unfortunate reality of the data and tooling that exists out there today.

@manuelwedler manuelwedler Jun 30, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't convincing to me. Why shouldn't the user retry themselves?

If it's about our etherscan import functionality, I think the retry should be implemented on our etherscan import layer.

I mean this sounds like you are talking about importing contracts from other places. Is there any specific tool other than Etherscan?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additionally the "+commit.hash" is a valid modifier for some older versions of zksolc but not all.

This seems deterministic to me. We could have a hardcoded list or something to get the right version string.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

at the end of the day this is compensating for bad tooling (forge verify not being edition aware etc) and not technically the responsibility of sourcify - the biggest issue is that people come in and can't verify and have to manually export standard json and edit verification api params to get things to work which is a lot of hassle to get developers to verify contracts

Comment thread packages/lib-sourcify/src/Compilation/AbstractCompilation.ts Outdated
const ZKSOLC_EVM_CONTRACT_OUTPUTS = ['evm'] as const;
const SOLC_RELEASE_VERSION_REGEX =
/^v?(\d+\.\d+\.\d+)(\+commit\.[a-fA-F0-9]+)?$/;
const ERA_SOLC_VERSION_REGEX = /^v?(?:zkVM-)?(\d+\.\d+\.\d+)-(1\.0\.[0-2])$/;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This regex (plus stripLeadingV, isZkSolcVersionAtLeastV15 including the vm-1.5.0-a167aa3 special-case, and SOLC_RELEASE_VERSION_REGEX) is copy-pasted from packages/compilers/src/lib/zksolcCompiler.ts, and the two copies already differ cosmetically (capture groups vs none). These two packages must stay in lockstep — lib-sourcify decides which candidates are "supported" while the compilers package decides which binaries it fetches; silent drift means proposing candidates that can't compile, or vice-versa. Consider extracting the shared version logic into one place.

Comment thread packages/compilers/src/lib/zksolcCompiler.ts
}

if (verification.compilation.zksolc) {
additionalInput.era_solc_version =

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This writes solcCompilerVersion into era_solc_version whenever zksolc is present, but when the resolved backend is upstream solc the value is a commit-bearing v0.8.26+commit... — i.e. a non-era version stored under a field literally named era_solc_version. Either rename the field to something backend-agnostic, or only populate it when the resolved version is actually an era-solc edition. (Related to the additional_input thread on the migration.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Resolved by dropping the field entirely. The backend (era-solc edition vs upstream solc) is now captured inside the combined version string itself — zksolc:1.5.10;solc:0.8.26-1.0.2 for an era backend, zksolc:1.4.0;solc:0.8.20+commit… for upstream — so there's no misnamed era_solc_version to populate, and both backends are represented consistently.

Posted with Claude Code

});
});

describe('getZkSolcExecutable download fallback', () => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These download-fallback tests all use macosx-arm64, a single-candidate platform. The gnu→musl fall-through in the download path (gnu-primary → gnu-legacy → musl-primary → musl-legacy) for linux-amd64-gnu is never exercised here — only the pre-cached musl case is. Worth adding a linux-amd64-gnu download test that asserts the candidate/repo ordering across the libc dimension.

Comment thread services/server/src/server/cli.ts Outdated
Comment on lines +78 to +87
logger.info("Using local zksolc compiler");
const zksolcRepoPath =
(config.get("zksolcRepo") as string) || path.join("/tmp", "zksolc-repo");
const eraSolcRepoPath =
(config.get("eraSolcRepo") as string) || path.join("/tmp", "era-solc-repo");
export const zksolc = new ZkSolcLocal(
zksolcRepoPath,
eraSolcRepoPath,
solcRepoPath,
);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the zksolc compiler should be optional. Most custom Sourcify instances won't use it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in f6e5fef This also adds zksolc to private APIs and in PreRunCompilation

@manuelwedler

Copy link
Copy Markdown
Member

Most important question from my side: Why should we have retries? This deviates from our current paradigm that the user should know which is the correct compiler version.

@kuzdogan

Copy link
Copy Markdown
Member

Also note: We'll need to implement the necessary database schema changes for this. But the review still applies as the logic and DB are mostly separate.

kuzdogan and others added 8 commits June 24, 2026 14:53
- decode(): handle AuxdataStyle.ZKSYNC by stripping the leading zero
  word-alignment padding before CBOR-decoding (a CBOR map never starts
  with 0x00), so the padded auxdata value is decodable; widen the return
  type to treat ZKSYNC like SOLIDITY.
- splitAuxdata: extract a shared splitCborAuxdata helper; splitEraVmAuxdata
  reuses it to locate/validate the CBOR and only adds the EraVM
  padding-absorption, removing the duplicated length/extract/validate logic.
- Transformations: add a padding-tolerant auxdataContainsCbor helper and
  validate CBOR per auxdata position (CBOR positions validated, keccak-hash
  positions skipped) instead of a blanket validateCbor:false for ZKSYNC.
- Verification: drop the ZKSYNC-wide validateCbor opt-out.
- ZKSOLC.md: document the EraVM CBOR auxdata format, padding-in-auxdata
  rationale, decoding, per-position validation, and the EraVM odd-word rule.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The creation fetching logic is untouched by this PR, so this test is not needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… choice

Clarify that native ZKsync contracts must go through zksolc (EraVM), while
plain-solc EVM bytecode runs via the EVM emulator and uses the normal Solidity
flow. Correct the misleading 'interchangeable backends' framing: modern zksolc
(>= 1.5.8) prohibits upstream solc and requires the era-solc fork.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Document the canonical-vs-era-solc backend by zksolc version as three bands
(<1.3.19 upstream-only / 1.3.19-<1.5.8 both / >=1.5.8 era-only), cited to the
era-compiler-solidity CHANGELOG. Add the era-solc edition availability matrix
(1.0.0: 0.4.12-0.8.25; 1.0.1/1.0.2: 0.4.12-0.8.30) from the GitHub releases.

Add the edition release timeline from era-solidity's Changelog.md (1.0.2 shipped
2025-04-01, 6 days before zksolc 1.5.13), showing the 1.0.2 edition gate should
be 1.5.13 (Foundry/date-grounded) rather than Sourcify's current 1.5.0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Represent the zksolc toolchain (zksolc + era-solc/solc versions) as one
`zksolc:<zksolcVersion>;solc:<solcVersion>` string used across the verification
API, lib-sourcify, and the database:

- lib-sourcify: ZkSolcCompilation takes one compilerVersion string and parses it
  internally; the inherited compilerVersion stays the plain zksolc semver (so
  Verification's Solidity heuristics still parse it) and the resolved combined
  value is exported via resolvedCompilerVersion.
- server: zksolc is detected from the `zksolc:` prefix; drop the zksolcVersion
  request/worker/service field. A zksolc-prefixed but malformed version is
  rejected as invalid_compiler_version, and a request to a server without zksolc
  enabled returns a clear invalid_parameter error.
- DB: store the combined string in compiled_contracts.version and remove
  additional_input.era_solc_version (+ delete its migration, revert the schema
  snapshot).
- docs: ZKSOLC.md version-string grammar; apiv2.yaml request schema/description,
  CompilerVersion pattern, EraVM example, response field.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Windows zksolc binaries are also published with a -gnu (MinGW) suffix but have
no -musl build, so gate the musl fallback on the platform starting with
"linux-" rather than ending in "-gnu".

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The `targetVM = 'eravm'` field was set but never read anywhere.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
kuzdogan and others added 8 commits June 25, 2026 18:41
When a zksolc binary downloads (HTTP 200) but fails the --version validation
(e.g. a gnu binary on a musl host), delete it instead of leaving it cached in
the repo dir before falling through to the next candidate.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Move the zksolc request checks (Solidity-only, settings require a zksolc
compilerVersion, zksolc enabled on this server, chain supports zksolc) out of
the async worker and into a validateZkSolcRequest middleware, so an unservable
request returns an immediate 400 instead of a polled job error.

This drops createInvalidParameterErrorExport and the apiErrorMessage type that
was Pick-extracted from the Etherscan error variant; invalid_parameter job
errors no longer carry a custom message. Adds VerificationService.isZkSolcEnabled.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Raw merge commit with conflict markers intentionally left in place for the
5 conflicting files. This captures the unresolved 3-way merge state so the
subsequent resolution can be reviewed as a clean diff on top.

Picks up the codecov orb bump 5.4.3 -> 6.0.0 (PR argotorg#2821) that fixes the failing
'(Codecov) Validate CLI' GPG step in CI.

Conflicted files still containing <<<<<<< / ======= / >>>>>>> markers:
  - packages/bytecode-utils/src/lib/bytecode.ts
  - packages/lib-sourcify/src/Verification/Transformations.ts
  - packages/lib-sourcify/src/Verification/Verification.ts
  - services/server/src/server/services/utils/database-util.ts
  - services/server/test/unit/utils/database-util.spec.ts

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tyle)

Resolve the 5 conflicts from merging origin/staging into the zksolc PR,
combining staging's refactors with the branch's ZKSYNC support:

- bytecode-utils/bytecode.ts: use staging's getBytes() rename on the branch's
  zksync padding-stripped cborHex.
- lib-sourcify/Transformations.ts: keep staging's sorted immutable-entries
  structure and Vyper length safeguard; fold ZKSYNC into the 'replace' paths
  (zksolc emits Solidity-format immutableReferences, so it replaces at offset).
- lib-sourcify/Verification.ts: keep both the branch's ZKSYNC link-reference
  fallbacks and staging's additionalInput destructuring of jsonInput.
- server/database-util.ts: adopt staging's compilation.additionalInput ?? null
  and drop the now-redundant getAdditionalInputFromVerification helper.
- server/database-util.spec.ts: keep both tests (zksolc + Vyper immutables)
  under a single describe tree.

Verified: bytecode-utils + lib-sourcify build, server tsc --noEmit, prettier,
and the affected Transformations/database-util unit tests all pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-run path

Make zksolc (zksync EraVM) verification opt-in so custom Sourcify instances
that don't verify EraVM contracts aren't required to run it. zksolc is enabled
only when both `zksolcRepo` and `eraSolcRepo` are configured:
- remove the zksolc repo paths from the default config (disabled by default)
- keep them in the test config so the suite still exercises zksolc
- resolve the paths via config.has() in cli.ts and only build ZkSolcLocal when
  configured; drop the unused top-level zksolc export

Wire zksolc into the private API like solc/vyper/fe:
- add an optional zksolc compiler to ServerOptions and app state
- pass it through the /private stateless verification compilation

Support zksolc in PreRunCompilation (reconstruction from stored DB output).
A stored zksolc contract has language "Solidity" but targets EraVM, which has
no deployedBytecode split and uses the ZKSYNC auxdata style. Detect it from the
combined `zksolc:<v>;solc:<v>` version and mirror ZkSolcCompilation:
- ZKSYNC auxdata style
- runtime bytecode, immutable references and link references read from evm.bytecode
- compilationExportMetadata exports compiler "zksolc" and the combined version
- metadata restored via setMetadata() from the stored candidate
- add PreRunCompilation zksolc unit tests

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
zksolc verification was gated both by server config (compiler repos) and by a
per-chain `zksolc.supported` flag. Drop the per-chain dependency so zksolc is
purely a server capability: zksolc bytecode only matches EraVM chains, so a
request on a non-EraVM chain simply fails to match instead of needing an
explicit allowlist.

- remove the chain `zksolc.supported` check from validateZkSolcRequest
- remove the `zksolc` field from SourcifyChainExtension/SourcifyChain and its
  chain construction/serialization
- update tests: the request is now rejected only when zksolc is disabled on the
  server (isZkSolcEnabled), not per chain

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… code

zksolc request validation moved to the API middleware (validateZkSolcRequest),
which throws the request-level InvalidParametersError. The verification-level
"invalid_parameter" code left in VerificationErrorCode/getVerificationErrorMessage
is no longer produced by any code path, so remove it; invalid parameters are
reported with the existing request-level invalid_parameter error code.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…mpilation compiler identity

Each Compilation maps 1:1 to a compiler, so the compiler name belongs on the
compilation rather than being spread from an unknown-shape metadata object or
inferred from the language (which is ambiguous: Solidity -> solc or zksolc).

- remove compilationExportMetadata and the CompilationExportMetadata type
- add abstract compilerName getter; SolidityCompilation -> solc,
  VyperCompilation -> vyper, FeCompilation -> fe, ZkSolcCompilation -> zksolc
  (YulCompilation inherits solc)
- PreRunCompilation reconstructs any language, so it derives compilerName from
  the language (or zksolc when the stored version is a zksolc toolchain string)
- add resolvedCompilerVersion getter: defaults to compilerVersion, overridden
  by zksolc to the combined zksolc:<v>;solc:<v> string (compilerVersion stays a
  plain semver for the Solidity heuristics in Verification)
- Verification.export() sets compiler/compilerVersion explicitly
- compiler is now required on VerificationExport; drop the unused zksolc export
  field (solcCompilerVersion is already encoded in the combined version)
- move getCompilerNameFromLanguage into lib-sourcify; remove it from the server

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coffeexcoin

Copy link
Copy Markdown
Author

Most important question from my side: Why should we have retries? This deviates from our current paradigm that the user should know which is the correct compiler version.

The key problem is that the user doesn't really control this at all, and is reliant on their tooling which seems to be broken all over the place. They can at most set solc version + zksolc version in configs, but the actual semantic "edition" is not controllable or even known by the developer/user at compile time.

@kuzdogan kuzdogan moved this from Sprint - Needs Review to Sprint - Up Next in Sourcify Public Jul 2, 2026
@kuzdogan kuzdogan moved this from Sprint - Up Next to Sprint - In Progress in Sourcify Public Jul 6, 2026
kuzdogan and others added 5 commits July 7, 2026 10:58
Split the interleaved zksolc logic out of the shared bytecode.ts into a
clean 3-file DAG:

- cbor.ts:  generic CBOR-tail primitives + AuxdataStyle enum (no EraVM
            knowledge). Holds splitCborAuxdata and the extracted
            decodeSolidityCborObject that EraVM reuses.
- eravm.ts: all EraVM-specific logic — splitEraVmAuxdata, the new
            decodeEraVmAuxdata (padding strip), and ERA_VM_* constants.
            Depends only on cbor.ts.
- bytecode.ts: public facade — decode/splitAuxdata dispatch and the
            Solidity/Vyper branches. Its only EraVM awareness is two thin
            dispatch lines; it re-exports the CBOR public API so the
            package's exported surface is unchanged.

Move the four EraVM test cases + fixtures into a dedicated eravm.spec.ts.
Update the stale bytecode.ts reference in ZKSOLC.md to eravm.ts.

No behavior change: all 22 bytecode-utils tests pass and lib-sourcify
consumers need no changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…p version retries

- Add ZkSolcVerification with EraVM creation matching via the versioned bytecode
  hash (sha256 of the recompiled runtime bytecode, referenced in the
  ContractDeployer create/create2 calldata). Reuses the canonical matchBytecodes
  + constructor-args transform; creationMatch inherits the runtime match type.
- Add eraBytecodeHash() to bytecode-utils (0x0100 | lenWords | sha256[-28:]).
- Gate the four solc-only steps in Verification (source-id <0.3.6, extra-file and
  IR-ordering bugs, exact runtime-length) on the compiler (isSolidityViaSolc)
  instead of the language, so zksolc (language 'Solidity', targets EraVM) is
  excluded while Solidity and Yul stay unchanged.
- Remove the version-candidate retry/iteration from the verification flow
  (Verification retry wrapper, AbstractCompilation.useNextCompilerVersionCandidate,
  ZkSolcCompilation candidate iteration + compile retry). ZkSolcCompilation now
  takes the concrete {zksolc, solc} versions; candidate resolution will be
  reinstated at a higher (Etherscan) level.
- Instantiate ZkSolcVerification on-site in the worker (compilerName === 'zksolc').

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ompilation

It was defined in AbstractCompilation but only PreRunCompilation reconstructs
a compiler name from a stored language; single-compiler compilations know
their compiler directly. Colocate it with its only consumer. Still exported
via the PreRunCompilation barrel, so the public API is unchanged.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…inline

The runCompiler override point wrapped only the main compile path and was a
thin passthrough in every solc/vyper/fe compilation. Restore the base
compileAndReturnCompilationTarget to call compiler.compile directly (as before
the zksolc work) and let ZkSolcCompilation, which already fully overrides
compileAndReturnCompilationTarget, invoke its two-version compiler.compile
internally. Leaves the other compilations untouched.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ata structurally

Rework EraVM creation matching so it no longer borrows the Solidity
[creationCode][args] prefix model, which doesn't fit EraVM:

- Gate on the creation tx targeting the ContractDeployer system contract
  (0x…8006), so only direct deploys are matched. The check lives entirely in
  ZkSolcVerification; the base class only gains a generic protected creationTxTo
  field kept off the already-fetched creation tx.
- Replace normalizeDeployerCalldata (positional slice + concat into a fake
  creation bytecode) with decodeContractDeployerCalldata, a single ABI decode
  against the selector's param types that yields the versioned bytecode hash and
  the constructor args directly.
- Match by hash equality (decoded bytecodeHash vs eraBytecodeHash of the
  recompiled runtime bytecode), not a byte-prefix comparison.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Sprint - In Progress

Development

Successfully merging this pull request may close these issues.

3 participants