-
Notifications
You must be signed in to change notification settings - Fork 230
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add etherscan as verification interface (#1518)
* feat: add etherscan as verification interface * fix: put proper chain id for zksync network * fix: update tests and add enable zksync verify url flag * chore: update readme file --------- Co-authored-by: Marko Arambasic <makiarambasic@gmail.com>
- Loading branch information
1 parent
d503f24
commit cb38fa2
Showing
36 changed files
with
2,052 additions
and
1,220 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export const TRYING_VERIFICATION_WITH_FULL_COMPILER_INPUT = ( | ||
contractName: string, | ||
) => `We tried verifying your contract ${contractName} without including any unrelated one, but it failed. | ||
Trying again with the full solc input used to compile and deploy it. | ||
This means that unrelated contracts may be displayed on the explorer.`; | ||
|
||
export const PROVIDED_CHAIN_IS_NOT_SUPPORTED_FOR_VERIFICATION = (chainId: number) => | ||
`The provided chain with id ${chainId} is not supported by default!`; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import { ZkSyncVerifyPluginError } from '../errors'; | ||
|
||
export class ZksyncContractVerificationInvalidStatusCodeError extends ZkSyncVerifyPluginError { | ||
constructor(url: string, statusCode: number, responseText: string) { | ||
super(`Failed to send contract verification request. | ||
Endpoint URL: ${url} | ||
The HTTP server response is not ok. Status code: ${statusCode} Response text: ${responseText}`); | ||
} | ||
} |
196 changes: 196 additions & 0 deletions
196
packages/hardhat-zksync-verify/src/explorers/service.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
import { EthereumProvider, HardhatRuntimeEnvironment } from 'hardhat/types'; | ||
import chalk from 'chalk'; | ||
import { ChainConfig } from '@nomicfoundation/hardhat-verify/types'; | ||
import { delay, encodeArguments, nextAttemptDelay, retrieveContractBytecode } from '../utils'; | ||
import { ZkSyncVerifyPluginError } from '../errors'; | ||
import { Bytecode } from '../solc/bytecode'; | ||
import { | ||
COMPILER_VERSION_NOT_SUPPORTED, | ||
CONST_ARGS_ARRAY_ERROR, | ||
JSON_INPUT_CODE_FORMAT, | ||
PENDING_CONTRACT_INFORMATION_MESSAGE, | ||
TASK_COMPILE, | ||
TASK_VERIFY_GET_COMPILER_VERSIONS, | ||
TASK_VERIFY_GET_CONTRACT_INFORMATION, | ||
} from '../constants'; | ||
import { ContractInformation } from '../solc/types'; | ||
import { getMinimalResolvedFiles, getSolidityStandardJsonInput } from '../plugin'; | ||
import { ZkSyncEtherscanExplorerVerifyRequest, ZkSyncExplorerVerifyRequest } from './verify-contract-request'; | ||
import { VerificationStatusResponse } from './verification-status-response'; | ||
import { PROVIDED_CHAIN_IS_NOT_SUPPORTED_FOR_VERIFICATION } from './constants'; | ||
|
||
export type VerificationServiceVerificationIdReturnType = string | number; | ||
export type VerificationServiceVerifyRequest = ZkSyncExplorerVerifyRequest | ZkSyncEtherscanExplorerVerifyRequest; | ||
export type VerificationServiceInitialVerifyRequest = ZkSyncExplorerVerifyRequest; | ||
export type VerificationServiceVerificationStatus = VerificationStatusResponse; | ||
export interface VerificationServiceVerifyResponse< | ||
V extends VerificationServiceVerificationIdReturnType = VerificationServiceVerificationIdReturnType, | ||
> { | ||
verificationId: V; | ||
contractVerifyDataInfo: ContractVerifyDataInfo; | ||
} | ||
|
||
export interface ContractVerifyDataInfo { | ||
contractName: string; | ||
contractAddress: string; | ||
} | ||
|
||
export abstract class VerificationService< | ||
ReturnVerificationIdType extends | ||
VerificationServiceVerificationIdReturnType = VerificationServiceVerificationIdReturnType, | ||
ContractVerifyRequestType extends VerificationServiceVerifyRequest = VerificationServiceVerifyRequest, | ||
VerificationStatusType extends VerificationServiceVerificationStatus = VerificationServiceVerificationStatus, | ||
VerificationServiceVerifyResponseType = VerificationServiceVerifyResponse<ReturnVerificationIdType>, | ||
> { | ||
constructor( | ||
protected hre: HardhatRuntimeEnvironment, | ||
protected verifyUrl: string, | ||
protected browserUrl?: string, | ||
) {} | ||
protected abstract generateRequest( | ||
initialRequest: VerificationServiceInitialVerifyRequest, | ||
): ContractVerifyRequestType; | ||
protected abstract getVerificationId( | ||
initialRequest: VerificationServiceInitialVerifyRequest, | ||
): Promise<ReturnVerificationIdType>; | ||
public abstract getVerificationStatus( | ||
verificationId: ReturnVerificationIdType, | ||
contractVerifyDataInfo: ContractVerifyDataInfo, | ||
): Promise<VerificationStatusType>; | ||
protected abstract getSupportedCompilerVersions(): Promise<string[]>; | ||
protected abstract getSolcVersion(contractInformation: ContractInformation): Promise<string>; | ||
protected abstract getContractBorwserUrl(address: string): string | undefined; | ||
|
||
public static async getCurrentChainConfig( | ||
ethereumProvider: EthereumProvider, | ||
customChains: ChainConfig[], | ||
builtinChains: ChainConfig[], | ||
): Promise<ChainConfig> { | ||
const currentChainId = parseInt(await ethereumProvider.send('eth_chainId'), 16); | ||
|
||
const currentChainConfig = [...[...customChains].reverse(), ...builtinChains].find( | ||
({ chainId }) => chainId === currentChainId, | ||
); | ||
|
||
if (currentChainConfig === undefined) { | ||
throw new ZkSyncVerifyPluginError(PROVIDED_CHAIN_IS_NOT_SUPPORTED_FOR_VERIFICATION(currentChainId)); | ||
} | ||
|
||
return currentChainConfig; | ||
} | ||
|
||
public async verify( | ||
address: string, | ||
contract: string, | ||
constructorArguments: any, | ||
libraries: any, | ||
noCompile: boolean, | ||
isWithFullContext: boolean = false, | ||
): Promise<VerificationServiceVerifyResponseType> { | ||
const { isAddress } = await import('@ethersproject/address'); | ||
if (!isAddress(address)) { | ||
throw new ZkSyncVerifyPluginError(`${address} is an invalid address.`); | ||
} | ||
|
||
const deployedBytecodeHex = await retrieveContractBytecode(address, this.hre); | ||
const deployedBytecode = new Bytecode(deployedBytecodeHex); | ||
|
||
if (!noCompile) { | ||
await this.hre.run(TASK_COMPILE, { quiet: true }); | ||
} | ||
|
||
const compilerVersions: string[] = await this.hre.run(TASK_VERIFY_GET_COMPILER_VERSIONS); | ||
|
||
const contractInformation: ContractInformation = await this.hre.run(TASK_VERIFY_GET_CONTRACT_INFORMATION, { | ||
contract, | ||
deployedBytecode, | ||
matchingCompilerVersions: compilerVersions, | ||
libraries, | ||
}); | ||
|
||
const optimizationUsed = contractInformation.compilerInput.settings.optimizer.enabled ?? false; | ||
|
||
let deployArgumentsEncoded; | ||
if (!Array.isArray(constructorArguments)) { | ||
if (constructorArguments.startsWith('0x')) { | ||
deployArgumentsEncoded = constructorArguments; | ||
} else { | ||
throw new ZkSyncVerifyPluginError(chalk.red(CONST_ARGS_ARRAY_ERROR)); | ||
} | ||
} else { | ||
deployArgumentsEncoded = `0x${await encodeArguments( | ||
contractInformation.contractOutput.abi, | ||
constructorArguments, | ||
)}`; | ||
} | ||
|
||
const compilerPossibleVersions = await this.getSupportedCompilerVersions(); | ||
const compilerVersion: string = contractInformation.solcVersion; | ||
if (!compilerPossibleVersions.includes(compilerVersion)) { | ||
throw new ZkSyncVerifyPluginError(COMPILER_VERSION_NOT_SUPPORTED); | ||
} | ||
|
||
const request: VerificationServiceInitialVerifyRequest = { | ||
contractAddress: address, | ||
sourceCode: getSolidityStandardJsonInput( | ||
this.hre, | ||
await getMinimalResolvedFiles(this.hre, contractInformation.sourceName), | ||
contractInformation.compilerInput, | ||
), | ||
codeFormat: JSON_INPUT_CODE_FORMAT, | ||
contractName: `${contractInformation.sourceName}:${contractInformation.contractName}`, | ||
compilerSolcVersion: await this.getSolcVersion(contractInformation), | ||
compilerZksolcVersion: `v${contractInformation.contractOutput.metadata.zk_version}`, | ||
constructorArguments: deployArgumentsEncoded, | ||
optimizationUsed, | ||
}; | ||
if (isWithFullContext) { | ||
request.sourceCode.sources = contractInformation.compilerInput.sources; | ||
} | ||
|
||
const verificationId = await this.getVerificationId(request); | ||
|
||
console.info(chalk.cyan(`Your verification ID is: ${verificationId}`)); | ||
|
||
return { | ||
contractVerifyDataInfo: { | ||
contractName: request.contractName, | ||
contractAddress: request.contractAddress, | ||
}, | ||
verificationId, | ||
} as VerificationServiceVerifyResponseType; | ||
} | ||
|
||
public async getVerificationStatusWithRetry( | ||
verificationId: ReturnVerificationIdType, | ||
contractVerifyDataInfo: ContractVerifyDataInfo, | ||
maxRetries = 11, | ||
baseRetries = 5, | ||
baseDelayInMs = 2000, | ||
): Promise<VerificationStatusType> { | ||
let retries = 0; | ||
let response: VerificationStatusType; | ||
|
||
while (true) { | ||
response = await this.getVerificationStatus(verificationId, contractVerifyDataInfo); | ||
|
||
if (response.isPending()) { | ||
retries += 1; | ||
if (retries > maxRetries) { | ||
throw new ZkSyncVerifyPluginError(PENDING_CONTRACT_INFORMATION_MESSAGE(this.browserUrl)); | ||
} | ||
|
||
const delayInMs = nextAttemptDelay(retries, baseDelayInMs, baseRetries); | ||
await delay(delayInMs); | ||
} else { | ||
break; | ||
} | ||
} | ||
|
||
return response; | ||
} | ||
} | ||
|
||
export interface VerificationServiceVerificationIdResponse { | ||
isOk(): boolean; | ||
} |
8 changes: 8 additions & 0 deletions
8
packages/hardhat-zksync-verify/src/explorers/verification-status-response.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export interface VerificationStatusResponse { | ||
isPending(): boolean; | ||
isFailure(): boolean; | ||
isSuccess(): boolean; | ||
getError(): string | undefined; | ||
errorExists(): boolean; | ||
isOk(): boolean; | ||
} |
27 changes: 27 additions & 0 deletions
27
packages/hardhat-zksync-verify/src/explorers/verify-contract-request.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { CompilerInput } from 'hardhat/types'; | ||
|
||
export interface ZkSyncExplorerVerifyRequest { | ||
contractAddress: string; | ||
contractName: string; | ||
sourceCode: CompilerInput; | ||
codeFormat: string; | ||
compilerSolcVersion: string; | ||
compilerZksolcVersion: string; | ||
optimizationUsed: boolean; | ||
constructorArguments: string; | ||
} | ||
|
||
export interface ZkSyncEtherscanExplorerVerifyRequest { | ||
action: 'verifysourcecode'; | ||
module: 'contract'; | ||
apikey: string; | ||
compilermode: 'zksync'; | ||
zksolcVersion: string; | ||
contractaddress: string; | ||
contractname: string; | ||
sourceCode: CompilerInput; | ||
codeformat: string; | ||
compilerversion: string; | ||
optimizationUsed?: string; | ||
constructorArguements: string; | ||
} |
20 changes: 20 additions & 0 deletions
20
packages/hardhat-zksync-verify/src/explorers/zksync-block-explorer/chain-config.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
import { ChainConfig } from '@nomicfoundation/hardhat-verify/types'; | ||
|
||
export const builtinChains: ChainConfig[] = [ | ||
{ | ||
network: 'zksyncmainnet', | ||
chainId: 324, | ||
urls: { | ||
apiURL: 'https://zksync2-mainnet-explorer.zksync.io/contract_verification', | ||
browserURL: 'https://explorer.zksync.io/', | ||
}, | ||
}, | ||
{ | ||
network: 'zksyncsepolia', | ||
chainId: 300, | ||
urls: { | ||
apiURL: 'https://explorer.sepolia.era.zksync.dev/contract_verification', | ||
browserURL: 'https://sepolia.explorer.zksync.io/', | ||
}, | ||
}, | ||
]; |
Oops, something went wrong.