Fetch source files from Sourcify lookup

pull/5285/head
Manuel Wedler 4 months ago committed by Aniket
parent fd8de4e1e8
commit b8c189e6e7
  1. 74
      apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts
  2. 8
      apps/contract-verification/src/app/types/VerificationTypes.ts

@ -1,6 +1,9 @@
import { CompilerAbstract, SourcesCode } from '@remix-project/remix-solidity' import { CompilerAbstract, SourcesCode } 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'
import { ethers } from 'ethers'
const SOURCIFY_DIR = 'sourcify-verified'
interface SourcifyVerificationRequest { interface SourcifyVerificationRequest {
address: string address: string
@ -10,7 +13,7 @@ interface SourcifyVerificationRequest {
chosenContract?: string chosenContract?: string
} }
type SourcifyVerificationStatus = 'perfect' | 'partial' | null type SourcifyVerificationStatus = 'perfect' | 'full' | 'partial' | null
interface SourcifyVerificationResponse { interface SourcifyVerificationResponse {
result: [ result: [
@ -27,14 +30,18 @@ interface SourcifyVerificationResponse {
} }
interface SourcifyErrorResponse { interface SourcifyErrorResponse {
error: 'string' error: string
}
interface SourcifyFile {
name: string
path: string
content: string
} }
interface SourcifyLookupResponse { interface SourcifyLookupResponse {
address: string status: Exclude<SourcifyVerificationStatus, null>
// Includes either chainIds or status key files: SourcifyFile[]
chainIds?: Array<{ chainId: string; status: Exclude<SourcifyVerificationStatus, null> }>
status?: 'false'
} }
export class SourcifyVerifier extends AbstractVerifier { export class SourcifyVerifier extends AbstractVerifier {
@ -88,7 +95,7 @@ export class SourcifyVerifier extends AbstractVerifier {
// Map to a user-facing status message // Map to a user-facing status message
let status: VerificationStatus = 'unknown' let status: VerificationStatus = 'unknown'
if (verificationResponse.result[0].status === 'perfect') { if (verificationResponse.result[0].status === 'perfect' || verificationResponse.result[0].status === 'full') {
status = 'fully verified' status = 'fully verified'
} else if (verificationResponse.result[0].status === 'partial') { } else if (verificationResponse.result[0].status === 'partial') {
status = 'partially verified' status = 'partially verified'
@ -98,37 +105,70 @@ export class SourcifyVerifier extends AbstractVerifier {
} }
async lookup(contractAddress: string, chainId: string): Promise<LookupResponse> { async lookup(contractAddress: string, chainId: string): Promise<LookupResponse> {
const url = new URL(this.apiUrl + '/check-all-by-addresses') const url = new URL(this.apiUrl + `/files/any/${chainId}/${contractAddress}`)
url.searchParams.append('addresses', contractAddress)
url.searchParams.append('chainIds', chainId)
const response = await fetch(url.href, { method: 'GET' }) const response = await fetch(url.href, { method: 'GET' })
if (!response.ok) { if (!response.ok) {
const errorResponse: SourcifyErrorResponse = await response.json() const errorResponse: SourcifyErrorResponse = await response.json()
if (errorResponse.error === 'Files have not been found!') {
return { status: 'not verified' }
}
console.error('Error on Sourcify lookup at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + JSON.stringify(errorResponse)) console.error('Error on Sourcify lookup at ' + this.apiUrl + '\nStatus: ' + response.status + '\nResponse: ' + JSON.stringify(errorResponse))
throw new Error(errorResponse.error) throw new Error(errorResponse.error)
} }
const lookupResponse: SourcifyLookupResponse = (await response.json())[0] const lookupResponse: SourcifyLookupResponse = await response.json()
let status: VerificationStatus = 'unknown' let status: VerificationStatus = 'unknown'
let lookupUrl: string | undefined = undefined let lookupUrl: string | undefined = undefined
if (lookupResponse.status === 'false') { if (lookupResponse.status === 'perfect' || lookupResponse.status === 'full') {
status = 'not verified'
} else if (lookupResponse.chainIds?.[0].status === 'perfect') {
status = 'fully verified' status = 'fully verified'
lookupUrl = this.getContractCodeUrl(contractAddress, chainId, true) lookupUrl = this.getContractCodeUrl(contractAddress, chainId, true)
} else if (lookupResponse.chainIds?.[0].status === 'partial') { } else if (lookupResponse.status === 'partial') {
status = 'partially verified' status = 'partially verified'
lookupUrl = this.getContractCodeUrl(contractAddress, chainId, false) lookupUrl = this.getContractCodeUrl(contractAddress, chainId, false)
} }
return { status, lookupUrl } const { sourceFiles, targetFilePath } = this.processReceivedFiles(lookupResponse.files, contractAddress)
return { status, lookupUrl, sourceFiles, targetFilePath }
} }
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 } {
const result: SourceFile[] = []
let targetFilePath: string
const filePrefix = `/${SOURCIFY_DIR}/${contractAddress}`
for (const file of files) {
let filePath: string
for (const a of [contractAddress, ethers.utils.getAddress(contractAddress)]) {
const matching = file.path.match(`/${a}/(.*)$`)
if (matching) {
filePath = matching[1]
break
}
}
if (filePath) {
result.push({ path: `${filePrefix}/${filePath}`, content: file.content })
}
if (file.name === 'metadata.json') {
const metadata = JSON.parse(file.content)
const compilationTarget = metadata.settings.compilationTarget
const contractPath = Object.keys(compilationTarget)[0]
targetFilePath = `${filePrefix}/sources/${contractPath}`
}
}
return { sourceFiles: result, targetFilePath }
}
} }

@ -64,7 +64,15 @@ export interface VerificationResponse {
message?: string message?: string
} }
export interface SourceFile {
// Should be in the correct format for creating the files in Remix
path: string
content: string
}
export interface LookupResponse { export interface LookupResponse {
status: VerificationStatus status: VerificationStatus
lookupUrl?: string lookupUrl?: string
sourceFiles?: SourceFile[]
targetFilePath?: string
} }

Loading…
Cancel
Save