diff --git a/.changeset/purple-items-argue.md b/.changeset/purple-items-argue.md new file mode 100644 index 000000000..09799ba31 --- /dev/null +++ b/.changeset/purple-items-argue.md @@ -0,0 +1,5 @@ +--- +"@layerzerolabs/verify-contract": minor +--- + +Verify contract checks deployment ABI first, but fall back to source-based extraction if there's a mismatch diff --git a/packages/verify-contract/src/common/abi.ts b/packages/verify-contract/src/common/abi.ts index c69b1f182..3b457ca61 100644 --- a/packages/verify-contract/src/common/abi.ts +++ b/packages/verify-contract/src/common/abi.ts @@ -12,7 +12,7 @@ import { TypeName, type FunctionDefinition } from '@solidity-parser/parser/dist/ * @param args Constructor arguments * @returns */ -export const encodeContructorArguments = (abi: JsonFragment[], args: unknown[] | undefined): string | undefined => { +export const encodeConstructorArguments = (abi: JsonFragment[], args: unknown[] | undefined): string | undefined => { if (args == null || args.length === 0) { return undefined } @@ -24,7 +24,7 @@ export const encodeContructorArguments = (abi: JsonFragment[], args: unknown[] | return encodedConstructorArguments.slice(2) } -export const getContructorABIFromSource = (source: string): MinimalAbi => { +export const getConstructorABIFromSource = (source: string): MinimalAbi => { try { // First we'll parse the source code and get the AST const ast = parser.parse(source) diff --git a/packages/verify-contract/src/common/networks.ts b/packages/verify-contract/src/common/networks.ts index 7c7777872..db35c95b8 100644 --- a/packages/verify-contract/src/common/networks.ts +++ b/packages/verify-contract/src/common/networks.ts @@ -417,6 +417,12 @@ export const networks: Record = { aliases: ['stable-mainnet'], }, + 'stable-testnet': { + chainId: 2201, + apiUrl: ETHERSCAN_V2_URL, + aliases: ['stable-testnet'], + }, + // Swellchain swellchain: { chainId: 1923, @@ -484,6 +490,12 @@ export const networks: Record = { // Non-Etherscan Networks (custom explorers) // chainId is not used for these networks + // Codex + codex: { + chainId: 81224, + apiUrl: 'https://explorer.codex.xyz/', + }, + // Astar astar: { chainId: 0, diff --git a/packages/verify-contract/src/hardhat-deploy/verify.ts b/packages/verify-contract/src/hardhat-deploy/verify.ts index 266eaf761..f0e008b14 100644 --- a/packages/verify-contract/src/hardhat-deploy/verify.ts +++ b/packages/verify-contract/src/hardhat-deploy/verify.ts @@ -7,7 +7,7 @@ import { COLORS, RecordLogger, anonymizeValue, createRecordLogger } from '../com import { tryCreateScanContractUrl } from '../common/url' import { DeploymentSchema } from '../common/schema' import { extractSolcInputFromMetadata } from './schema' -import { encodeContructorArguments, getContructorABIFromSource } from '../common/abi' +import { encodeConstructorArguments, getConstructorABIFromSource } from '../common/abi' import type { VerificationArtifact, VerificationResult, @@ -100,7 +100,10 @@ export const verifyNonTarget = async ( typeof contract.constructorArguments === 'string' ? contract.constructorArguments : // For decoded constructor arguments we'll need to try and encoded them using the contract source - encodeContructorArguments(getContructorABIFromSource(source.content), contract.constructorArguments) + encodeConstructorArguments( + getConstructorABIFromSource(source.content), + contract.constructorArguments + ) // Deployment metadata contains solcInput, just a bit rearranged const solcInput = extractSolcInputFromMetadata(deployment.metadata) @@ -254,7 +257,26 @@ export const verifyTarget = async ( const licenseType = findLicenseType(source.content) // Constructor arguments need to come ABI-encoded but without the 0x - const constructorArguments = encodeContructorArguments(deployment.abi, deployment.args) + // Try using deployment ABI first, but fall back to source-based extraction if there's a mismatch + let constructorArguments: string | undefined + try { + constructorArguments = encodeConstructorArguments(deployment.abi, deployment.args) + } catch (error) { + // If encoding fails due to argument mismatch, try extracting constructor from source + // This handles cases where the ABI is incomplete but source code has the full signature + try { + const sourceBasedAbi = getConstructorABIFromSource(source.content) + constructorArguments = encodeConstructorArguments(sourceBasedAbi, deployment.args) + } catch (sourceError) { + // If both fail, log a warning and skip this contract + logger.warn( + `Skipping contract ${contractName} in ${fileName} on network ${networkName} due to constructor encoding error: ${error}. ` + + `Tried fallback to source-based extraction but that also failed: ${sourceError}` + ) + + return [] + } + } // Deployment metadata contains solcInput, just a bit rearranged const solcInput = extractSolcInputFromMetadata(deployment.metadata) diff --git a/packages/verify-contract/test/abi.test.ts b/packages/verify-contract/test/abi.test.ts index fdb1a6c3b..e3ae92221 100644 --- a/packages/verify-contract/test/abi.test.ts +++ b/packages/verify-contract/test/abi.test.ts @@ -1,17 +1,17 @@ -import { encodeContructorArguments } from '@/common/abi' +import { encodeConstructorArguments } from '@/common/abi' describe('abi', () => { - describe('encodeContructorArguments', () => { + describe('encodeConstructorArguments', () => { it('should return undefined if args are nullish', () => { - expect(encodeContructorArguments([], undefined)).toBeUndefined() + expect(encodeConstructorArguments([], undefined)).toBeUndefined() }) it('should return undefined if args are empty', () => { - expect(encodeContructorArguments([], [])).toBeUndefined() + expect(encodeConstructorArguments([], [])).toBeUndefined() }) it('should throw an error if there is no constructor fragment', () => { - expect(() => encodeContructorArguments([{}], [1])).toThrow('invalid fragment object') + expect(() => encodeConstructorArguments([{}], [1])).toThrow('invalid fragment object') }) it.each([ @@ -50,7 +50,7 @@ describe('abi', () => { ], ], ])('should return %s for arguments %j and ABI %j', (encoded, args, abi) => { - expect(encodeContructorArguments(abi, args)).toBe(encoded) + expect(encodeConstructorArguments(abi, args)).toBe(encoded) }) }) })