Get source code with Blockscout lookup

pull/5285/head
Manuel Wedler 4 months ago committed by Aniket
parent be69dd417d
commit 03746cf574
  1. 38
      apps/contract-verification/src/app/Verifiers/BlockscoutVerifier.ts
  2. 58
      apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts
  3. 6
      apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts

@ -1,6 +1,26 @@
import { SourceFile } from '../types'
import { EtherscanVerifier } from './EtherscanVerifier' import { EtherscanVerifier } from './EtherscanVerifier'
// Etherscan and Blockscout return different objects from the getsourcecode method
interface BlockscoutSource {
AdditionalSources: Array<{ SourceCode: string; Filename: string }>
ConstructorArguments: string
OptimizationRuns: number
IsProxy: string
SourceCode: string
ABI: string
ContractName: string
CompilerVersion: string
OptimizationUsed: string
Runs: string
EVMVersion: string
FileName: string
Address: string
}
export class BlockscoutVerifier extends EtherscanVerifier { export class BlockscoutVerifier extends EtherscanVerifier {
LOOKUP_STORE_DIR = 'blockscout-verified'
constructor(apiUrl: string) { constructor(apiUrl: string) {
// apiUrl and explorerUrl are the same for Blockscout // apiUrl and explorerUrl are the same for Blockscout
super(apiUrl, apiUrl, undefined) super(apiUrl, apiUrl, undefined)
@ -8,7 +28,23 @@ export class BlockscoutVerifier extends EtherscanVerifier {
getContractCodeUrl(address: string): string { getContractCodeUrl(address: string): string {
const url = new URL(this.explorerUrl + `/address/${address}`) const url = new URL(this.explorerUrl + `/address/${address}`)
// url.searchParams.append('tab', 'contract') url.searchParams.append('tab', 'contract')
return url.href return url.href
} }
processReceivedFiles(source: unknown, contractAddress: string): { sourceFiles: SourceFile[]; targetFilePath?: string } {
const blockscoutSource = source as BlockscoutSource
const result: SourceFile[] = []
const filePrefix = `/${this.LOOKUP_STORE_DIR}/${contractAddress}`
const targetFilePath = `${filePrefix}/${blockscoutSource.FileName}`
result.push({ content: blockscoutSource.SourceCode, path: targetFilePath })
for (const additional of blockscoutSource.AdditionalSources ?? []) {
result.push({ content: additional.SourceCode, path: `${filePrefix}/${additional.Filename}` })
}
return { sourceFiles: result, targetFilePath }
}
} }

@ -1,6 +1,6 @@
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import { AbstractVerifier } from './AbstractVerifier' import { AbstractVerifier } from './AbstractVerifier'
import type { LookupResponse, SubmittedContract, VerificationResponse, VerificationStatus } from '../types' import type { LookupResponse, SourceFile, SubmittedContract, VerificationResponse, VerificationStatus } from '../types'
interface EtherscanRpcResponse { interface EtherscanRpcResponse {
status: '0' | '1' status: '0' | '1'
@ -14,7 +14,31 @@ interface EtherscanCheckStatusResponse {
result: 'Pending in queue' | 'Pass - Verified' | 'Fail - Unable to verify' | 'Already Verified' | 'Unknown UID' result: 'Pending in queue' | 'Pass - Verified' | 'Fail - Unable to verify' | 'Already Verified' | 'Unknown UID'
} }
interface EtherscanSource {
SourceCode: string
ABI: string
ContractName: string
CompilerVersion: string
OptimizationUsed: string
Runs: string
ConstructorArguments: string
EVMVersion: string
Library: string
LicenseType: string
Proxy: string
Implementation: string
SwarmSource: string
}
interface EtherscanGetSourceCodeResponse {
status: '0' | '1'
message: string
result: EtherscanSource[]
}
export class EtherscanVerifier extends AbstractVerifier { export class EtherscanVerifier extends AbstractVerifier {
LOOKUP_STORE_DIR = 'etherscan-verified'
constructor(apiUrl: string, explorerUrl: string, protected apiKey?: string) { constructor(apiUrl: string, explorerUrl: string, protected apiKey?: string) {
super(apiUrl, explorerUrl) super(apiUrl, explorerUrl)
} }
@ -184,7 +208,7 @@ export class EtherscanVerifier extends AbstractVerifier {
async lookup(contractAddress: string, chainId: string): Promise<LookupResponse> { async lookup(contractAddress: string, chainId: string): Promise<LookupResponse> {
const url = new URL(this.apiUrl + '/api') const url = new URL(this.apiUrl + '/api')
url.searchParams.append('module', 'contract') url.searchParams.append('module', 'contract')
url.searchParams.append('action', 'getabi') url.searchParams.append('action', 'getsourcecode')
url.searchParams.append('address', contractAddress) url.searchParams.append('address', contractAddress)
if (this.apiKey) { if (this.apiKey) {
url.searchParams.append('apikey', this.apiKey) url.searchParams.append('apikey', this.apiKey)
@ -198,22 +222,36 @@ export class EtherscanVerifier extends AbstractVerifier {
throw new Error(responseText) throw new Error(responseText)
} }
const lookupResponse: EtherscanRpcResponse = await response.json() const lookupResponse: EtherscanGetSourceCodeResponse = await response.json()
const lookupUrl = this.getContractCodeUrl(contractAddress) if (lookupResponse.status !== '1' || !lookupResponse.message.startsWith('OK')) {
const errorResponse = lookupResponse as unknown as EtherscanRpcResponse
console.error('Error on Etherscan API lookup at ' + this.apiUrl + '\nStatus: ' + errorResponse.status + '\nMessage: ' + errorResponse.message + '\nResult: ' + errorResponse.result)
throw new Error(errorResponse.result)
}
if (lookupResponse.result === 'Contract source code not verified') { if (lookupResponse.result[0].ABI === 'Contract source code not verified' || !lookupResponse.result[0].SourceCode) {
return { status: 'not verified', lookupUrl } return { status: 'not verified' }
} else if (lookupResponse.status !== '1' || !lookupResponse.message.startsWith('OK')) {
console.error('Error on Etherscan API lookup at ' + this.apiUrl + '\nStatus: ' + lookupResponse.status + '\nMessage: ' + lookupResponse.message + '\nResult: ' + lookupResponse.result)
throw new Error(lookupResponse.result)
} }
return { status: 'verified', lookupUrl } const lookupUrl = this.getContractCodeUrl(contractAddress)
console.log(lookupResponse)
const { sourceFiles, targetFilePath } = this.processReceivedFiles(lookupResponse.result[0], contractAddress)
return { status: 'verified', lookupUrl, sourceFiles, targetFilePath }
} }
getContractCodeUrl(address: string): string { getContractCodeUrl(address: string): string {
const url = new URL(this.explorerUrl + `/address/${address}#code`) const url = new URL(this.explorerUrl + `/address/${address}#code`)
return url.href return url.href
} }
processReceivedFiles(source: EtherscanSource, contractAddress: string): { sourceFiles: SourceFile[]; targetFilePath?: string } {
const result: SourceFile[] = []
const filePrefix = `/${this.LOOKUP_STORE_DIR}/${contractAddress}`
// TODO
const targetFilePath = ''
return { sourceFiles: result, targetFilePath }
}
} }

@ -43,7 +43,7 @@ interface SourcifyLookupResponse {
} }
export class SourcifyVerifier extends AbstractVerifier { export class SourcifyVerifier extends AbstractVerifier {
SOURCIFY_DIR = 'sourcify-verified' LOOKUP_STORE_DIR = 'sourcify-verified'
async verify(submittedContract: SubmittedContract, compilerAbstract: CompilerAbstract): Promise<VerificationResponse> { async verify(submittedContract: SubmittedContract, compilerAbstract: CompilerAbstract): Promise<VerificationResponse> {
const metadataStr = compilerAbstract.data.contracts[submittedContract.filePath][submittedContract.contractName].metadata const metadataStr = compilerAbstract.data.contracts[submittedContract.filePath][submittedContract.contractName].metadata
@ -138,14 +138,14 @@ export class SourcifyVerifier extends AbstractVerifier {
} }
getContractCodeUrl(address: string, chainId: string, fullMatch: boolean): string { getContractCodeUrl(address: string, chainId: string, fullMatch: boolean): string {
const url = new URL(this.explorerUrl + `/contracts/${fullMatch ? 'full_match' : 'partial_match'}/${chainId}/${address}`) const url = new URL(this.explorerUrl + `/contracts/${fullMatch ? 'full_match' : 'partial_match'}/${chainId}/${address}/`)
return url.href return url.href
} }
processReceivedFiles(files: SourcifyFile[], contractAddress: string): { sourceFiles: SourceFile[]; targetFilePath?: string } { processReceivedFiles(files: SourcifyFile[], contractAddress: string): { sourceFiles: SourceFile[]; targetFilePath?: string } {
const result: SourceFile[] = [] const result: SourceFile[] = []
let targetFilePath: string let targetFilePath: string
const filePrefix = `/${this.SOURCIFY_DIR}/${contractAddress}` const filePrefix = `/${this.LOOKUP_STORE_DIR}/${contractAddress}`
for (const file of files) { for (const file of files) {
let filePath: string let filePath: string

Loading…
Cancel
Save