diff --git a/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts b/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts index 1ca28b6a4f..5e2394492d 100644 --- a/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts +++ b/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts @@ -1,8 +1,11 @@ import { CompilerAbstract } from '@remix-project/remix-solidity' -import type { LookupResponse, SubmittedContract, VerificationResponse, VerificationStatus } from '../types' +import type { LookupResponse, SubmittedContract, VerificationResponse } from '../types' +// Optional function definitions export interface AbstractVerifier { + verifyProxy(submittedContract: SubmittedContract): Promise checkVerificationStatus?(receiptId: string): Promise + checkProxyVerificationStatus?(receiptId: string): Promise } export abstract class AbstractVerifier { diff --git a/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts b/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts index 9fefd6d4d3..05185bbb20 100644 --- a/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts +++ b/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts @@ -2,16 +2,6 @@ import { CompilerAbstract } from '@remix-project/remix-solidity' import { AbstractVerifier } from './AbstractVerifier' import type { LookupResponse, SubmittedContract, VerificationResponse, VerificationStatus } from '../types' -interface EtherscanVerificationRequest { - chainId?: string - codeformat: 'solidity-single-file' | 'solidity-standard-json-input' - sourceCode: string - contractaddress: string - contractname: string - compilerversion: string - constructorArguements?: string -} - interface EtherscanRpcResponse { status: '0' | '1' message: string @@ -69,6 +59,43 @@ export class EtherscanVerifier extends AbstractVerifier { return { status: 'pending', receiptId: verificationResponse.result } } + async verifyProxy(submittedContract: SubmittedContract): Promise { + if (!submittedContract.proxyAddress) { + throw new Error('SubmittedContract does not have a proxyAddress') + } + + const formData = new FormData() + formData.append('address', submittedContract.proxyAddress) + formData.append('expectedimplementation', submittedContract.address) + + const url = new URL(this.apiUrl + '/api') + url.searchParams.append('module', 'contract') + url.searchParams.append('action', 'verifyproxycontract') + if (this.apiKey) { + url.searchParams.append('apikey', this.apiKey) + } + + const response = await fetch(url.href, { + method: 'POST', + body: formData, + }) + + if (!response.ok) { + const responseText = await response.text() + console.error('Error on Etherscan API proxy verification at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + responseText) + throw new Error(responseText) + } + + const verificationResponse: EtherscanRpcResponse = await response.json() + + if (verificationResponse.status !== '1' || verificationResponse.message !== 'OK') { + console.error('Error on Etherscan API proxy verification at ' + this.apiUrl + '\nStatus: ' + verificationResponse.status + '\nMessage: ' + verificationResponse.message + '\nResult: ' + verificationResponse.result) + throw new Error(verificationResponse.result) + } + + return { status: 'pending', receiptId: verificationResponse.result } + } + async checkVerificationStatus(receiptId: string): Promise { const url = new URL(this.apiUrl + '/api') url.searchParams.append('module', 'contract') @@ -110,6 +137,47 @@ export class EtherscanVerifier extends AbstractVerifier { return { status: 'unknown', receiptId } } + async checkProxyVerificationStatus(receiptId: string): Promise { + const url = new URL(this.apiUrl + '/api') + url.searchParams.append('module', 'contract') + url.searchParams.append('action', 'checkproxyverification') + url.searchParams.append('guid', receiptId) + if (this.apiKey) { + url.searchParams.append('apikey', this.apiKey) + } + + const response = await fetch(url.href, { method: 'GET' }) + + if (!response.ok) { + const responseText = await response.text() + console.error('Error on Etherscan API check verification status at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + responseText) + throw new Error(responseText) + } + + const checkStatusResponse: EtherscanRpcResponse = await response.json() + + if (checkStatusResponse.result === 'A corresponding implementation contract was unfortunately not detected for the proxy address.') { + return { status: 'failed', receiptId, message: checkStatusResponse.result } + } + if (checkStatusResponse.result === 'Verification in progress') { + return { status: 'pending', receiptId } + } + if (checkStatusResponse.result.startsWith('The proxy\'s') && checkStatusResponse.result.endsWith('and is successfully updated.')) { + return { status: 'verified', receiptId } + } + if (checkStatusResponse.result === 'Unknown UID') { + console.error('Error on Etherscan API check proxy verification status at ' + this.apiUrl + '\nStatus: ' + checkStatusResponse.status + '\nMessage: ' + checkStatusResponse.message + '\nResult: ' + checkStatusResponse.result) + return { status: 'failed', receiptId, message: checkStatusResponse.result } + } + + if (checkStatusResponse.status !== '1' || !checkStatusResponse.message.startsWith('OK')) { + console.error('Error on Etherscan API check proxy verification status at ' + this.apiUrl + '\nStatus: ' + checkStatusResponse.status + '\nMessage: ' + checkStatusResponse.message + '\nResult: ' + checkStatusResponse.result) + throw new Error(checkStatusResponse.result) + } + + return { status: 'unknown', receiptId } + } + async lookup(contractAddress: string, chainId: string): Promise { const url = new URL(this.apiUrl + '/api') url.searchParams.append('module', 'contract')