Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions .changeset/purple-items-argue.md
Original file line number Diff line number Diff line change
@@ -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
4 changes: 2 additions & 2 deletions packages/verify-contract/src/common/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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)
Expand Down
12 changes: 12 additions & 0 deletions packages/verify-contract/src/common/networks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,12 @@ export const networks: Record<string, NetworkDefinition> = {
aliases: ['stable-mainnet'],
},

'stable-testnet': {
chainId: 2201,
apiUrl: ETHERSCAN_V2_URL,
aliases: ['stable-testnet'],
},

// Swellchain
swellchain: {
chainId: 1923,
Expand Down Expand Up @@ -484,6 +490,12 @@ export const networks: Record<string, NetworkDefinition> = {
// Non-Etherscan Networks (custom explorers)
// chainId is not used for these networks

// Codex
codex: {
chainId: 81224,
apiUrl: 'https://explorer.codex.xyz/',
},
Copy link

Choose a reason for hiding this comment

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

Codex network API URL missing required /api path

The codex network's apiUrl is configured as 'https://explorer.codex.xyz/' but is missing the /api path that all other custom explorer URLs in this file include (e.g., 'https://explorer.ebi.xyz/api', 'https://explorer.gravity.xyz/api'). Since the verification code sends requests directly to this URL, contract verification attempts on the Codex network will fail because requests will be sent to the wrong endpoint.

Fix in Cursor Fix in Web


// Astar
astar: {
chainId: 0,
Expand Down
28 changes: 25 additions & 3 deletions packages/verify-contract/src/hardhat-deploy/verify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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}`
)
Comment on lines +272 to +275
Copy link

Copilot AI Dec 16, 2025

Choose a reason for hiding this comment

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

The error message concatenates error objects directly into the string, which may produce unhelpful output like '[object Object]'. Consider using error.message or a similar property to display meaningful error information.

Copilot uses AI. Check for mistakes.

return []
}
}

// Deployment metadata contains solcInput, just a bit rearranged
const solcInput = extractSolcInputFromMetadata(deployment.metadata)
Expand Down
12 changes: 6 additions & 6 deletions packages/verify-contract/test/abi.test.ts
Original file line number Diff line number Diff line change
@@ -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([
Expand Down Expand Up @@ -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)
})
})
})
Loading