Skip to content

Commit 8e818e8

Browse files
authored
Merge pull request #285 from BitGo/win-7813
chore: add sourcify verification support
2 parents 09618b5 + c40b85d commit 8e818e8

File tree

7 files changed

+1210
-1692
lines changed

7 files changed

+1210
-1692
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,4 @@ output.json
1616
#Hardhat files
1717
artifacts
1818
cache
19-
typechain
19+
typechain-types

deployUtils.ts

Lines changed: 263 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -214,96 +214,288 @@ export function saveOutput(output: DeploymentAddresses) {
214214
fs.writeFileSync(OUTPUT_FILE, JSON.stringify(output, null, 2));
215215
}
216216

217+
const VERIFICATION_CONFIG = {
218+
CONFIRMATION_BLOCKS: 5,
219+
MAX_RETRIES: 10,
220+
RETRY_DELAY_MS: 60_000 // 1 minute
221+
} as const;
222+
223+
/**
224+
* Checks if an error indicates the contract is already verified
225+
*/
226+
function isAlreadyVerifiedError(error: any): boolean {
227+
return error.message.toLowerCase().includes('already verified');
228+
}
229+
230+
/**
231+
* Creates a delay for the specified number of milliseconds
232+
*/
233+
function delay(ms: number): Promise<void> {
234+
return new Promise((resolve) => setTimeout(resolve, ms));
235+
}
236+
237+
/**
238+
* Waits for contract confirmations
239+
*/
240+
async function waitForConfirmations(
241+
contract: BaseContract,
242+
contractName: string,
243+
confirmationBlocks: number = VERIFICATION_CONFIG.CONFIRMATION_BLOCKS
244+
): Promise<string> {
245+
logger.info(
246+
`Waiting for ${confirmationBlocks} block confirmations for ${contractName}...`
247+
);
248+
249+
const deployTx = contract.deploymentTransaction();
250+
await deployTx?.wait(confirmationBlocks);
251+
252+
const contractAddress = await contract.getAddress();
253+
logger.success(
254+
`Contract ${contractName} confirmed after ${confirmationBlocks} confirmations at ${contractAddress}`
255+
);
256+
257+
return contractAddress;
258+
}
259+
260+
/**
261+
* Checks if the current network supports Sourcify verification
262+
*/
263+
async function isSourcifyNetwork(
264+
hre: HardhatRuntimeEnvironment
265+
): Promise<boolean> {
266+
const { sourcifyNetworks } = await import('./hardhat.config');
267+
const network = await hre.ethers.provider.getNetwork();
268+
const chainId = Number(network.chainId);
269+
270+
return chainId in sourcifyNetworks;
271+
}
272+
273+
/**
274+
* Gets the Sourcify configuration for a specific network
275+
*/
276+
async function getSourcifyConfig(hre: HardhatRuntimeEnvironment): Promise<{
277+
enabled: boolean;
278+
apiUrl: string;
279+
browserUrl: string;
280+
}> {
281+
const { sourcifyNetworks } = await import('./hardhat.config');
282+
const network = await hre.ethers.provider.getNetwork();
283+
const chainId = Number(network.chainId);
284+
285+
// Check if network has custom Sourcify configuration
286+
const networkConfig = (
287+
sourcifyNetworks as Record<number, { apiUrl: string; browserUrl: string }>
288+
)[chainId];
289+
if (networkConfig) {
290+
return {
291+
enabled: true,
292+
apiUrl: networkConfig.apiUrl,
293+
browserUrl: networkConfig.browserUrl
294+
};
295+
} else {
296+
throw new Error('No Sourcify configuration available for this network');
297+
}
298+
}
299+
300+
/**
301+
* Attempts standard Hardhat verification
302+
*/
303+
async function attemptStandardVerification(
304+
hre: HardhatRuntimeEnvironment,
305+
contractAddress: string,
306+
contractName: string,
307+
constructorArguments: string[]
308+
): Promise<void> {
309+
const artifact = await hre.artifacts.readArtifact(contractName);
310+
const verificationString = `${artifact.sourceName}:${artifact.contractName}`;
311+
312+
logger.info('Attempting verification with standard Hardhat verifier...');
313+
314+
await hre.run('verify:verify', {
315+
address: contractAddress,
316+
constructorArguments,
317+
contract: verificationString
318+
});
319+
320+
logger.success('Standard verification successful!');
321+
}
322+
323+
/**
324+
* Attempts custom Etherscan verification as fallback
325+
*/
326+
async function attemptCustomVerification(
327+
hre: HardhatRuntimeEnvironment,
328+
contractAddress: string,
329+
contractName: string,
330+
constructorArguments: string[]
331+
): Promise<void> {
332+
logger.info('Falling back to custom verifier...');
333+
334+
await verifyOnCustomEtherscan({
335+
hre,
336+
contractAddress,
337+
contractName,
338+
constructorArguments
339+
});
340+
341+
logger.success('Custom verifier fallback successful!');
342+
}
343+
344+
/**
345+
* Attempts Sourcify verification via hardhat-verify with dynamic configuration
346+
*/
347+
async function attemptSourcifyVerification(
348+
hre: HardhatRuntimeEnvironment,
349+
contractAddress: string,
350+
contractName: string,
351+
constructorArguments: string[]
352+
): Promise<void> {
353+
logger.info('Using Sourcify verification...');
354+
355+
// Get the appropriate Sourcify configuration for this network
356+
const sourcifyConfig = await getSourcifyConfig(hre);
357+
358+
const originalConfig = hre.config.sourcify;
359+
hre.config.sourcify = sourcifyConfig;
360+
361+
try {
362+
const artifact = await hre.artifacts.readArtifact(contractName);
363+
const verificationString = `${artifact.sourceName}:${artifact.contractName}`;
364+
365+
logger.info(`Verifying with fully qualified name: ${verificationString}`);
366+
logger.info(`Using Sourcify API: ${sourcifyConfig.apiUrl}`);
367+
368+
await hre.run('verify:sourcify', {
369+
address: contractAddress,
370+
constructorArguments,
371+
contract: verificationString
372+
});
373+
logger.success('Sourcify verification successful!');
374+
} finally {
375+
hre.config.sourcify = originalConfig;
376+
}
377+
}
378+
379+
/**
380+
* Attempts a single verification (standard + custom + hedera fallback)
381+
* Returns true if successful, false if should retry, throws if already verified
382+
*/
383+
async function attemptVerification(
384+
hre: HardhatRuntimeEnvironment,
385+
contractAddress: string,
386+
contractName: string,
387+
constructorArguments: string[]
388+
): Promise<boolean> {
389+
// Try sourcify verification if network supports it
390+
try {
391+
if (await isSourcifyNetwork(hre)) {
392+
await attemptSourcifyVerification(
393+
hre,
394+
contractAddress,
395+
contractName,
396+
constructorArguments
397+
);
398+
return true; // Success
399+
}
400+
} catch (sourcifyError: any) {
401+
if (isAlreadyVerifiedError(sourcifyError)) {
402+
logger.success('Contract is already verified.');
403+
throw new Error('ALREADY_VERIFIED');
404+
}
405+
logger.warn(`Sourcify verification failed: ${sourcifyError.message}`);
406+
return false; // Should retry
407+
}
408+
409+
// Try standard verification
410+
try {
411+
await attemptStandardVerification(
412+
hre,
413+
contractAddress,
414+
contractName,
415+
constructorArguments
416+
);
417+
return true; // Success
418+
} catch (standardError: any) {
419+
if (isAlreadyVerifiedError(standardError)) {
420+
logger.success('Contract is already verified.');
421+
throw new Error('ALREADY_VERIFIED');
422+
}
423+
424+
logger.warn(`Standard verifier failed: ${standardError.message}`);
425+
}
426+
427+
// Try custom verification as fallback
428+
try {
429+
await attemptCustomVerification(
430+
hre,
431+
contractAddress,
432+
contractName,
433+
constructorArguments
434+
);
435+
return true; // Success
436+
} catch (customError: any) {
437+
if (isAlreadyVerifiedError(customError)) {
438+
logger.success('Contract is already verified.');
439+
throw new Error('ALREADY_VERIFIED'); // Special error to indicate success
440+
}
441+
442+
logger.warn(`Custom verifier fallback also failed: ${customError.message}`);
443+
}
444+
445+
return false; // Should retry
446+
}
447+
217448
/**
218449
* Waits for contract confirmation and then verifies it on a block explorer.
219-
* It first tries the standard Hardhat verifier and falls back to a custom verifier if the first one fails.
220-
* The entire process is wrapped in a retry loop.
450+
* Uses standard Hardhat verifier, custom verifier, and Sourcify verification.
221451
*/
222452
export async function waitAndVerify(
223453
hre: HardhatRuntimeEnvironment,
224454
contract: BaseContract,
225455
contractName: string,
226456
constructorArguments: string[] = []
227-
) {
457+
): Promise<void> {
228458
if (!hre) {
229459
const errorMsg = 'Hardhat Runtime Environment (hre) is not defined.';
230460
logger.error(errorMsg);
231461
throw new Error(errorMsg);
232462
}
233463

234-
const confirmationCount = 5;
235-
logger.info(
236-
`Waiting for ${confirmationCount} block confirmations for ${contractName}...`
237-
);
238-
const artifact = await hre.artifacts.readArtifact(contractName);
239-
const verificationString = `${artifact.sourceName}:${artifact.contractName}`;
240-
console.log(`Verification string: ${verificationString}`);
241-
242-
const deployTx = contract.deploymentTransaction();
243-
await deployTx?.wait(confirmationCount);
244-
245-
logger.success(
246-
`Contract ${contractName} confirmed on the network after ${confirmationCount} confirmations.`
247-
);
248-
const contractAddress = await contract.getAddress();
249-
logger.success(`Contract confirmed on the network at ${contractAddress}.`);
464+
// Wait for block confirmations
465+
const contractAddress = await waitForConfirmations(contract, contractName);
250466

251-
const maxRetries = 20;
252-
const retryDelay = 180000; // 180 seconds
467+
// Perform verification with retry logic
468+
for (let attempt = 1; attempt <= VERIFICATION_CONFIG.MAX_RETRIES; attempt++) {
469+
logger.info(`Verification attempt #${attempt} for ${contractName}...`);
253470

254-
for (let i = 0; i < maxRetries; i++) {
255-
logger.info(`Verification attempt #${i + 1} for ${contractName}...`);
256471
try {
257-
// --- Primary Attempt: Standard Verifier ---
258-
logger.info('Attempting verification with standard Hardhat verifier...');
259-
await hre.run('verify:verify', {
260-
address: contractAddress,
261-
constructorArguments: constructorArguments,
262-
contract: verificationString
263-
});
264-
logger.success('Standard verification successful!');
265-
return; // Success, exit the loop.
266-
} catch (standardError: any) {
267-
logger.warn(`Standard verifier failed: ${standardError.message}`);
268-
269-
if (standardError.message.toLowerCase().includes('already verified')) {
270-
logger.success('Contract is already verified.');
271-
return;
272-
}
472+
const success = await attemptVerification(
473+
hre,
474+
contractAddress,
475+
contractName,
476+
constructorArguments
477+
);
273478

274-
// --- Fallback Attempt: Custom Verifier ---
275-
logger.info('Falling back to custom verifier...');
276-
try {
277-
await verifyOnCustomEtherscan({
278-
hre,
279-
contractAddress: contractAddress, // Use the fetched address
280-
contractName: contractName,
281-
constructorArguments: constructorArguments
282-
});
283-
logger.success('Custom verifier fallback successful!');
284-
return; // Success, exit the loop.
285-
} catch (customError: any) {
286-
if (customError.message.toLowerCase().includes('already verified')) {
287-
logger.success('Contract is already verified.');
288-
return;
289-
}
290-
291-
logger.warn(
292-
`Custom verifier fallback also failed: ${customError.message}`
293-
);
294-
if (i < maxRetries - 1) {
295-
logger.info(
296-
`Waiting ${
297-
retryDelay / 1000
298-
} seconds before retrying entire process...`
299-
);
300-
await new Promise((resolve) => setTimeout(resolve, retryDelay));
301-
} else {
302-
logger.error(
303-
`All verification retries failed. Please verify manually later.`
304-
);
305-
}
479+
if (success) {
480+
logger.success(`Verification succeeded on attempt #${attempt}.`);
481+
return; // Verification succeeded
482+
}
483+
} catch (error: any) {
484+
if (error.message === 'ALREADY_VERIFIED') {
485+
logger.success(`Contract already verified on attempt #${attempt}.`);
486+
return; // Already verified - success
487+
} else {
488+
logger.warn(`Unexpected error during verification: ${error.message}`);
306489
}
307490
}
491+
492+
// If we get here, verification failed and we should retry
493+
if (attempt < VERIFICATION_CONFIG.MAX_RETRIES) {
494+
const delaySeconds = VERIFICATION_CONFIG.RETRY_DELAY_MS / 1000;
495+
logger.info(`Waiting ${delaySeconds} seconds before retrying...`);
496+
await delay(VERIFICATION_CONFIG.RETRY_DELAY_MS);
497+
}
308498
}
499+
500+
logger.error(`Verification process failed: Please verify manually later.`);
309501
}

0 commit comments

Comments
 (0)