Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6195b9a
Add zksolc compilation plumbing to lib-sourcify
coffeexcoin May 6, 2026
6a3211b
add zksolc compiler plumbing
coffeexcoin May 6, 2026
002068d
Support legacy zksolc binary downloads
coffeexcoin May 6, 2026
f49f8f8
Add local zksolc compiler adapter
coffeexcoin May 6, 2026
36e3c73
Wire zksolc verification worker path
coffeexcoin May 6, 2026
a7cec6c
Add zksolc verification API route
coffeexcoin May 6, 2026
ebbb16c
Use standard verification route for zksolc
coffeexcoin May 12, 2026
c946dc1
Gate zksolc verification by chain support
coffeexcoin May 12, 2026
8689606
Route zksolc through json input worker
coffeexcoin May 12, 2026
f37c181
Thread zksolc through compilation factory
coffeexcoin May 12, 2026
e4dfc6d
Add zksolc verification plumbing
coffeexcoin May 12, 2026
f2098ca
Prefer exact upstream solc for zksolc
coffeexcoin May 12, 2026
8033213
Move zksolc database migration to Sourcify
coffeexcoin May 12, 2026
0216a4e
Handle zksolc stdin EPIPE during fallback
coffeexcoin May 12, 2026
7ae73b9
Fix zksolc worker validation and CI tests
coffeexcoin May 12, 2026
a862181
Refine async invalid parameter error typing
coffeexcoin May 12, 2026
01861fb
Use auxdata style for zksync verification
coffeexcoin May 12, 2026
cd7af08
Rename isZkSolcVersionAtLeast → isZkSolcVersionAtLeastV15
kuzdogan May 19, 2026
3d7db0d
Comment zksolc -gnu/-musl binary naming
kuzdogan May 20, 2026
4db76cb
Add ZKSOLC.md compiler-model doc
kuzdogan May 20, 2026
def8ea2
Add deterministic unit tests for the zksolc compiler adapter
kuzdogan May 20, 2026
65e1bd9
Document zksolc/solc interface and drop unused solJsonRepoPath
kuzdogan May 20, 2026
1ad9de5
fix CBOR/metadata detection and parsing
coffeexcoin May 21, 2026
647f5ce
Support zksolc generic artifacts
coffeexcoin May 21, 2026
c7523db
Merge branch 'staging' into feat/zksync
coffeexcoin May 27, 2026
02698b9
fix merge
coffeexcoin May 27, 2026
45bb1a7
zksync: decode padded EraVM CBOR auxdata and dedup the split
kuzdogan Jun 24, 2026
a2a3949
docs(apiv2): move zksolc note to end of verify description
kuzdogan Jun 24, 2026
9e0a713
test: remove unnecessary getCreatorTx chainId 40 test
kuzdogan Jun 24, 2026
f740d70
docs(zksolc): document EraVM-native vs EVM-emulated paths and backend…
kuzdogan Jun 24, 2026
1da3965
docs(zksolc): add verified backend bands and era-solc edition timeline
kuzdogan Jun 25, 2026
acffe90
feat(zksolc): single combined compiler-version string for the toolchain
kuzdogan Jun 25, 2026
9f11772
fix(zksolc): only add -musl fallback for Linux platforms
kuzdogan Jun 25, 2026
805b6f7
refactor(zksolc): remove unused targetVM field
kuzdogan Jun 25, 2026
2bded9c
fix(zksolc): remove unusable downloaded binary on failed version check
kuzdogan Jun 25, 2026
e95a484
refactor(zksolc): validate zksolc requests in middleware, not the worker
kuzdogan Jun 25, 2026
cf9bd96
[UNRESOLVED] Merge origin/staging into pr/coffeexcoin/2788
kuzdogan Jun 26, 2026
4cce9c8
[RESOLVE MERGE] Resolve staging conflicts for zksolc (ZKSYNC auxdataS…
kuzdogan Jun 26, 2026
f6e5fef
feat(zksolc): make zksolc optional, add it to the private API and pre…
kuzdogan Jun 26, 2026
11b3abe
feat(zksolc): gate zksolc on server config, not per-chain support
kuzdogan Jun 26, 2026
6e803cf
fix(zksolc): drop the dead verification-level invalid_parameter error…
kuzdogan Jun 26, 2026
8201dd1
refactor(lib-sourcify): replace compilationExportMetadata with per-co…
kuzdogan Jun 26, 2026
5f9acad
refactor(bytecode-utils): isolate EraVM/zksolc auxdata into eravm.ts
kuzdogan Jul 7, 2026
7b1ded7
feat(zksolc): isolate EraVM verification into ZkSolcVerification, dro…
kuzdogan Jul 8, 2026
b9899b6
refactor(lib-sourcify): move getCompilerNameFromLanguage into PreRunC…
kuzdogan Jul 8, 2026
5015b27
refactor(lib-sourcify): drop runCompiler seam, call compiler.compile …
kuzdogan Jul 8, 2026
40d0a37
refactor(zksolc): gate EraVM creation match on tx.to and decode calld…
kuzdogan Jul 8, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
263 changes: 23 additions & 240 deletions packages/bytecode-utils/src/lib/bytecode.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
import { getBytes, hexlify } from 'ethers';
import bs58 from 'bs58';
import { getBytes } from 'ethers';
import * as CBOR from 'cbor-x';
import semver from 'semver';

import {
AuxdataStyle,
decodeSolidityCborObject,
ensureHexPrefix,
splitCborAuxdata,
validateBytecode,
type SolidityDecodedObject,
} from './cbor';
import { decodeEraVmAuxdata, splitEraVmAuxdata } from './eravm';

// Re-export the public CBOR API so it stays available from the package root.
export { AuxdataStyle, isCborEncoded } from './cbor';
export type { SolidityDecodedObject } from './cbor';
export { eraBytecodeHash } from './eravm';

type CBOR = {
bytes: string;
length: number;
};

export type SolidityDecodedObject = {
// Known CBOR fields that are defined in the spec
ipfs?: string;
solcVersion?: string;
experimental?: boolean;
bzzr0?: string;
bzzr1?: string;
// Any other CBOR field that is not explicitly defined above. This is a catch-all for future extensions.
[key: string]: string | Uint8Array | undefined | boolean;
};

export type VyperDecodedObject = {
integrity?: string;
runtimeSize?: number;
Expand All @@ -27,15 +30,6 @@ export type VyperDecodedObject = {
vyperVersion: string;
};

export enum AuxdataStyle {
SOLIDITY = 'solidity',
VYPER = 'vyper',
VYPER_LT_0_3_10 = 'vyper_lt_0_3_10',
VYPER_LT_0_3_5 = 'vyper_lt_0_3_5',
VYPER_LT_0_3_4 = 'vyper_lt_0_3_4',
FE = 'fe',
}

export const getVyperAuxdataStyle = (
compilerVersion: string,
):
Expand Down Expand Up @@ -94,7 +88,7 @@ export const getAuxdataStyle = (
export const decode = <T extends AuxdataStyle>(
bytecode: string,
auxdataStyle: T,
): T extends AuxdataStyle.SOLIDITY
): T extends AuxdataStyle.SOLIDITY | AuxdataStyle.ZKSYNC
? SolidityDecodedObject
: VyperDecodedObject => {
if (bytecode.length === 0) {
Expand Down Expand Up @@ -154,41 +148,9 @@ export const decode = <T extends AuxdataStyle>(
vyperVersion: cborDecodedObject.vyper.join('.'),
} as any;
} else if (auxdataStyle === AuxdataStyle.SOLIDITY) {
// cbor decode the object and get a json
const cborDecodedObject = CBOR.decode(getBytes(`0x${auxdata}`));

const result: SolidityDecodedObject = {};
// Decode all the parameters from the json
Object.keys(cborDecodedObject).forEach((key: string) => {
switch (key) {
case 'ipfs': {
const ipfsCID = bs58.encode(cborDecodedObject.ipfs);
result.ipfs = ipfsCID;
break;
}
case 'solc': {
// nightly builds are string encoded
if (typeof cborDecodedObject.solc === 'string') {
result.solcVersion = cborDecodedObject.solc;
} else {
result.solcVersion = cborDecodedObject.solc.join('.');
}
break;
}
case 'experimental': {
result.experimental = cborDecodedObject.experimental;
break;
}
case 'bzzr0':
case 'bzzr1':
default: {
result[key] = hexlify(cborDecodedObject[key]);
break;
}
}
});

return result as any;
return decodeSolidityCborObject(auxdata) as any;
} else if (auxdataStyle === AuxdataStyle.ZKSYNC) {
return decodeEraVmAuxdata(auxdata) as any;
} else {
throw Error('Invalid auxdata style');
}
Expand Down Expand Up @@ -217,188 +179,9 @@ export const splitAuxdata = (
return [bytecode];
}

const bytesLength = 4;
const cborBytesLength = getCborBytesLength(
bytecode,
auxdataStyle,
bytesLength,
);

if (
isCborLengthInvalid(auxdataStyle, bytecode, cborBytesLength, bytesLength)
) {
return [bytecode];
}

const auxdata = extractAuxdata(
bytecode,
auxdataStyle,
cborBytesLength,
bytesLength,
);
const executionBytecode = extractExecutionBytecode(
bytecode,
auxdataStyle,
cborBytesLength,
bytesLength,
);

if (isCborEncoded(auxdata)) {
const cborLengthHex = getCborLengthHex(bytecode, auxdataStyle, bytesLength);
return [executionBytecode, auxdata, cborLengthHex];
if (auxdataStyle === AuxdataStyle.ZKSYNC) {
return splitEraVmAuxdata(bytecode);
}

return [bytecode];
};

/**
* Validates that the bytecode is not empty.
*
* @param bytecode - The bytecode string to validate
*/
const validateBytecode = (bytecode: string) => {
if (bytecode.length === 0) {
throw Error('Bytecode cannot be null');
}
};

/**
* Ensures the bytecode string starts with '0x'.
*
* @param bytecode - The bytecode string
* @returns The bytecode string with '0x' prefix
*/
const ensureHexPrefix = (bytecode: string): string => {
return bytecode.startsWith('0x') ? bytecode : `0x${bytecode}`;
};

/**
* Determines the length of the CBOR auxdata in bytes.
*
* @param bytecode - The complete bytecode string
* @param auxdataStyle - The style of auxdata
* @param bytesLength - The length of bytes used to encode the CBOR length
* @returns An object containing the CBOR bytes length and a flag for legacy Vyper
*/
const getCborBytesLength = (
bytecode: string,
auxdataStyle: AuxdataStyle,
bytesLength: number,
): number => {
if (auxdataStyle === AuxdataStyle.VYPER_LT_0_3_5) {
return 22; // For Vyper 0.3.4, the CBOR length is fixed because it contains only the version
}
const cborLengthHex = bytecode.slice(-bytesLength);
return parseInt(cborLengthHex, 16) * 2;
};

/**
* Checks if the CBOR length is invalid based on the bytecode length.
*
* @param auxdataStyle - The auxdata style (Solidity or Vyper)
* @param bytecode - The complete bytecode string
* @param cborBytesLength - The length of CBOR auxdata in bytes
* @param bytesLength - The length of bytes used to encode the CBOR length
* @returns True if the CBOR length is invalid, otherwise false
*/
const isCborLengthInvalid = (
auxdataStyle: AuxdataStyle,
bytecode: string,
cborBytesLength: number,
bytesLength: number,
): boolean => {
if (auxdataStyle === AuxdataStyle.VYPER) {
// Vyper includes the trailing length bytes in cborBytesLength,
// so we only subtract cborBytesLength (no separate bytesLength).
return bytecode.length - cborBytesLength <= 0;
}
return bytecode.length - bytesLength - cborBytesLength <= 0;
};

/**
* Extracts the auxdata from the bytecode based on the auxdata style.
*
* @param bytecode - The complete bytecode string
* @param auxdataStyle - The style of auxdata
* @param cborBytesLength - The length of CBOR auxdata in bytes
* @param bytesLength - The length of bytes used to encode the CBOR length
* @returns The extracted auxdata as a hex string
*/
const extractAuxdata = (
bytecode: string,
auxdataStyle: AuxdataStyle,
cborBytesLength: number,
bytesLength: number,
): string => {
switch (auxdataStyle) {
case AuxdataStyle.VYPER_LT_0_3_10:
case AuxdataStyle.SOLIDITY:
return bytecode.substring(
bytecode.length - bytesLength - cborBytesLength,
bytecode.length - bytesLength,
);
case AuxdataStyle.VYPER:
return bytecode.substring(
bytecode.length - cborBytesLength,
bytecode.length - bytesLength,
);
case AuxdataStyle.VYPER_LT_0_3_5:
return bytecode.substring(bytecode.length - 22, bytecode.length);
default:
throw Error('Unsupported auxdata style');
}
};

/**
* Extracts the execution bytecode from the complete bytecode string.
*
* @param bytecode - The complete bytecode string
* @param cborBytesLength - The length of CBOR auxdata in bytes
* @param bytesLength - The length of bytes used to encode the CBOR length
* @returns The execution bytecode as a hex string
*/
const extractExecutionBytecode = (
bytecode: string,
auxdataStyle: AuxdataStyle,
cborBytesLength: number,
bytesLength: number,
): string => {
if (auxdataStyle === AuxdataStyle.VYPER) {
// Vyper includes the trailing length bytes in cborBytesLength,
// so we only subtract cborBytesLength to avoid double-counting.
return bytecode.substring(0, bytecode.length - cborBytesLength);
}
return bytecode.substring(0, bytecode.length - bytesLength - cborBytesLength);
};

/**
* Attempts to decode the auxdata to verify if it's CBOR-encoded.
*
* @param auxdata - The auxdata string to decode
* @returns True if auxdata is CBOR-encoded, otherwise false
*/
export const isCborEncoded = (auxdata: string): boolean => {
try {
CBOR.decode(getBytes(`0x${auxdata}`));
return true;
} catch {
return false;
}
};

/**
* Retrieves the CBOR length from the bytecode based on the auxdata style.
*
* @param bytecode - The complete bytecode string
* @param auxdataStyle - The style of auxdata
* @param bytesLength - The length of bytes used to encode the CBOR length
* @returns The CBOR length as a hex string
*/
const getCborLengthHex = (
bytecode: string,
auxdataStyle: AuxdataStyle,
bytesLength: number,
): string => {
if (auxdataStyle === AuxdataStyle.VYPER_LT_0_3_5) return '';
return bytecode.slice(-bytesLength);
return splitCborAuxdata(bytecode, auxdataStyle);
};
Loading