Send contracts for verification for each verifier

pull/5285/head
Kaan Uzdoğan 5 months ago committed by Aniket
parent 52c656f59b
commit 7fa4c5fb5e
  1. 14
      apps/contract-verification/src/app/AppContext.tsx
  2. 34
      apps/contract-verification/src/app/Receipts/EtherscanReceipt.tsx
  3. 36
      apps/contract-verification/src/app/Receipts/SourcifyReceipt.tsx
  4. 9
      apps/contract-verification/src/app/Receipts/props.ts
  5. 5
      apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts
  6. 24
      apps/contract-verification/src/app/Verifiers/EtherscanVerifier.tsx
  7. 15
      apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts
  8. 8
      apps/contract-verification/src/app/app.tsx
  9. 12
      apps/contract-verification/src/app/routes.tsx
  10. 58
      apps/contract-verification/src/app/types/VerificationTypes.ts
  11. 37
      apps/contract-verification/src/app/views/ReceiptsView.tsx
  12. 70
      apps/contract-verification/src/app/views/VerifyView.tsx

@ -1,6 +1,6 @@
import React from 'react' import React from 'react'
import {ThemeType} from './types' import {ThemeType} from './types'
import {Chain, VerifiedContract} from './types/VerificationTypes' import {Chain, SubmittedContracts} from './types/VerificationTypes'
import {CompilerAbstract} from '@remix-project/remix-solidity' import {CompilerAbstract} from '@remix-project/remix-solidity'
import {AbstractVerifier} from './Verifiers/AbstractVerifier' import {AbstractVerifier} from './Verifiers/AbstractVerifier'
@ -11,12 +11,12 @@ type AppContextType = {
chains: Chain[] chains: Chain[]
compilationOutput: {[key: string]: CompilerAbstract} | undefined compilationOutput: {[key: string]: CompilerAbstract} | undefined
selectedContractFileAndName: string | undefined selectedContractFileAndName: string | undefined
setSelectedContractFileAndName: (contract: string) => void setSelectedContractFileAndName: React.Dispatch<React.SetStateAction<string>>
targetFileName: string | undefined targetFileName: string | undefined
verifiedContracts: VerifiedContract[]
setVerifiedContracts: (verifiedContracts: VerifiedContract[]) => void
verifiers: AbstractVerifier[] verifiers: AbstractVerifier[]
setVerifiers: (verifiers: AbstractVerifier[]) => void setVerifiers: React.Dispatch<React.SetStateAction<AbstractVerifier[]>>
submittedContracts: SubmittedContracts
setSubmittedContracts: React.Dispatch<React.SetStateAction<SubmittedContracts>>
} }
// Provide a default value with the appropriate types // Provide a default value with the appropriate types
@ -30,10 +30,10 @@ const defaultContextValue: AppContextType = {
selectedContractFileAndName: undefined, selectedContractFileAndName: undefined,
setSelectedContractFileAndName: (contract: string) => {}, setSelectedContractFileAndName: (contract: string) => {},
targetFileName: undefined, targetFileName: undefined,
verifiedContracts: [],
setVerifiedContracts: (verifiedContracts: VerifiedContract[]) => {},
verifiers: [], verifiers: [],
setVerifiers: (verifiers: AbstractVerifier[]) => {}, setVerifiers: (verifiers: AbstractVerifier[]) => {},
submittedContracts: {},
setSubmittedContracts: (submittedContracts: SubmittedContracts) => {},
} }
// Create the context with the type // Create the context with the type

@ -0,0 +1,34 @@
import React, {useState, useEffect} from 'react'
import {EtherscanVerifier} from '../Verifiers/EtherscanVerifier'
import {ReceiptProps} from './props'
export const EtherscanReceipt: React.FC<ReceiptProps> = ({verifyPromise, address, chainId, verifier}) => {
const [status, setStatus] = useState<string | null>(null)
const [submissionDate] = useState(new Date())
useEffect(() => {
// Handle the promise here or perform other side effects
verifyPromise
.then(() => {
// Handle promise resolution
// Update status based on the result
})
.catch(() => {
// Handle promise rejection
})
// This effect should only run once on mount, hence the empty dependency array
}, [verifyPromise])
return (
<div>
<h1>Verification Receipt</h1>
<p>Address: {address}</p>
<p>Chain ID: {chainId}</p>
<p>Submission Date: {submissionDate.toLocaleString()}</p>
<p>Status: {status ? status : 'Pending'}</p>
</div>
)
}
export default EtherscanReceipt

@ -0,0 +1,36 @@
import React, {useState, useEffect} from 'react'
import {SourcifyVerifier} from '../Verifiers/SourcifyVerifier'
import {SourcifyVerificationStatus} from '../types/VerificationTypes'
import {ReceiptProps} from './props'
// A receipt is something to be rendered
export const SourcifyReceipt: React.FC<ReceiptProps> = ({verifyPromise, address, chainId, verifier}) => {
const [status, setStatus] = useState<SourcifyVerificationStatus | null>(null)
const [submissionDate] = useState(new Date()) // This will be set once and not change
useEffect(() => {
// You might want to handle the promise here or perform other side effects
verifyPromise
.then(() => {
// Handle promise resolution
// Update status based on the result
})
.catch(() => {
// Handle promise rejection
})
// This effect should only run once on mount, hence the empty dependency array
}, [verifyPromise])
return (
<div>
<h1>Verification Receipt</h1>
<p>Address: {address}</p>
<p>Chain ID: {chainId}</p>
<p>Submission Date: {submissionDate.toLocaleString()}</p>
<p>Status: {status ? status : 'Pending'}</p>
</div>
)
}
export default SourcifyReceipt

@ -0,0 +1,9 @@
import {EtherscanVerifier} from '../Verifiers/EtherscanVerifier'
import {SourcifyVerifier} from '../Verifiers/SourcifyVerifier'
export interface ReceiptProps {
verifyPromise: Promise<any>
address: string
chainId: string
verifier: EtherscanVerifier | SourcifyVerifier
}

@ -1,4 +1,6 @@
import {CompilerAbstract} from '@remix-project/remix-solidity' import {CompilerAbstract} from '@remix-project/remix-solidity'
import {SourcifyReceipt} from '../Receipts/SourcifyReceipt'
import {EtherscanReceipt} from '../Receipts/EtherscanReceipt'
export abstract class AbstractVerifier { export abstract class AbstractVerifier {
name: string name: string
@ -11,5 +13,6 @@ export abstract class AbstractVerifier {
this.enabled = true this.enabled = true
} }
abstract verify(chainId: string, address: string, compilationOutput: {[fileName: string]: CompilerAbstract}, selectedContractFileAndName: string): Promise<any> abstract verify(chainId: string, address: string, compilerAbstract: CompilerAbstract, selectedContractFileAndName: string): Promise<any>
abstract lookup(): Promise<any>
} }

@ -1,5 +1,7 @@
import {CompilerAbstract} from '@remix-project/remix-solidity' import {CompilerAbstract} from '@remix-project/remix-solidity'
import {AbstractVerifier} from './AbstractVerifier' import {AbstractVerifier} from './AbstractVerifier'
import {EtherscanReceipt} from '../Receipts/EtherscanReceipt'
import {EtherscanResponse} from '../types/VerificationTypes'
export class EtherscanVerifier extends AbstractVerifier { export class EtherscanVerifier extends AbstractVerifier {
apiKey: string apiKey: string
@ -9,11 +11,10 @@ export class EtherscanVerifier extends AbstractVerifier {
this.apiKey = apiKey this.apiKey = apiKey
} }
async verify(chainId: string, address: string, compilationOutput: {[fileName: string]: CompilerAbstract}, selectedContractFileAndName: string) { async verify(chainId: string, address: string, compilerAbstract: CompilerAbstract, selectedContractFileAndName: string) {
const CODE_FORMAT = 'solidity-standard-json-input' const CODE_FORMAT = 'solidity-standard-json-input'
const [selectedFileName, selectedContractName] = selectedContractFileAndName.split(':') const [selectedFileName, selectedContractName] = selectedContractFileAndName.split(':')
const compilerAbstract = compilationOutput?.[selectedFileName || '']
// TODO: Handle version Vyper contracts. This relies on Solidity metadata. // TODO: Handle version Vyper contracts. This relies on Solidity metadata.
const metadata = JSON.parse(compilerAbstract.data.contracts[selectedFileName][selectedContractName].metadata) const metadata = JSON.parse(compilerAbstract.data.contracts[selectedFileName][selectedContractName].metadata)
const body = { const body = {
@ -39,14 +40,25 @@ export class EtherscanVerifier extends AbstractVerifier {
}) })
if (!response.ok) { if (!response.ok) {
throw new Error(`Error on Etherscan verification at ${this.apiUrl}: Status:${response.status} Response: ${await response.text()}`) throw new Error(`Request error Status:${response.status} Response: ${await response.text()}`)
} }
const data = await response.json()
const data: EtherscanResponse = await response.json()
console.log(data)
if (data.status !== '1' || data.message !== 'OK') { if (data.status !== '1' || data.message !== 'OK') {
throw new Error(`Error on Etherscan verification at ${this.apiUrl}: ${data.message}`) console.error(`Error on Etherscan verification at ${this.apiUrl}: ${data.result}`)
throw new Error(data.result)
} }
return data.result return data
}
async lookup(): Promise<any> {
// Implement the lookup logic here
console.log('Etherscan lookup started')
// Placeholder logic for lookup
const lookupResult = {} // Replace with actual lookup logic
console.log('Etherscan lookup completed')
return lookupResult
} }
} }

@ -1,14 +1,15 @@
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 {SourcifyReceipt} from '../Receipts/SourcifyReceipt'
import {SourcifyVerificationError, SourcifyVerificationResponse} from '../types/VerificationTypes'
export class SourcifyVerifier extends AbstractVerifier { export class SourcifyVerifier extends AbstractVerifier {
constructor(apiUrl: string, name: string = 'Sourcify') { constructor(apiUrl: string, name: string = 'Sourcify') {
super(apiUrl, name) super(apiUrl, name)
} }
async verify(chainId: string, address: string, compilationOutput: {[fileName: string]: CompilerAbstract}, selectedContractFileAndName: string): Promise<any> { async verify(chainId: string, address: string, compilerAbstract: CompilerAbstract, selectedContractFileAndName: string) {
const [selectedFileName, selectedContractName] = selectedContractFileAndName.split(':') const [selectedFileName, selectedContractName] = selectedContractFileAndName.split(':')
const compilerAbstract = compilationOutput?.[selectedFileName || '']
const metadataStr = compilerAbstract.data.contracts[selectedFileName][selectedContractName].metadata const metadataStr = compilerAbstract.data.contracts[selectedFileName][selectedContractName].metadata
const sources = compilerAbstract.source.sources const sources = compilerAbstract.source.sources
console.log('selectedFileName:', selectedFileName) console.log('selectedFileName:', selectedFileName)
@ -44,13 +45,13 @@ export class SourcifyVerifier extends AbstractVerifier {
}) })
if (!response.ok) { if (!response.ok) {
throw new Error(`Error on Sourcify verification at ${this.apiUrl}: Status:${response.status} Response: ${await response.text()}`) const errorResponse: SourcifyVerificationError = await response.json()
console.error('Error on Sourcify verification at', this.apiUrl, 'Status:', response.status, 'Response:', JSON.stringify(errorResponse))
throw new Error(errorResponse.error)
} }
const data = await response.json() const jsonResponse: SourcifyVerificationResponse = await response.json()
console.log(data) return jsonResponse
return data.result
} }
async lookup(): Promise<any> { async lookup(): Promise<any> {

@ -7,11 +7,11 @@ import DisplayRoutes from './routes'
import {ThemeType} from './types' import {ThemeType} from './types'
import './App.css' import './App.css'
import {Chain, VerifiedContract} from './types/VerificationTypes' import {Chain, SubmittedContracts} from './types/VerificationTypes'
import {CompilerAbstract} from '@remix-project/remix-solidity' import {CompilerAbstract} from '@remix-project/remix-solidity'
import {AbstractVerifier} from './Verifiers/AbstractVerifier'
import {SourcifyVerifier} from './Verifiers/SourcifyVerifier' import {SourcifyVerifier} from './Verifiers/SourcifyVerifier'
import {EtherscanVerifier} from './Verifiers/EtherscanVerifier' import {EtherscanVerifier} from './Verifiers/EtherscanVerifier'
import {AbstractVerifier} from './Verifiers/AbstractVerifier'
const plugin = new ContractVerificationPluginClient() const plugin = new ContractVerificationPluginClient()
@ -23,8 +23,8 @@ const App = () => {
const [compilationOutput, setCompilationOutput] = useState<{[key: string]: CompilerAbstract} | undefined>() const [compilationOutput, setCompilationOutput] = useState<{[key: string]: CompilerAbstract} | undefined>()
// Contract file and name in format contracts/Storage.sol:Storage // Contract file and name in format contracts/Storage.sol:Storage
const [selectedContractFileAndName, setSelectedContractFileAndName] = useState<string | undefined>() const [selectedContractFileAndName, setSelectedContractFileAndName] = useState<string | undefined>()
const [verifiedContracts, setVerifiedContracts] = useState<VerifiedContract[]>([])
const [verifiers, setVerifiers] = useState<AbstractVerifier[]>([]) const [verifiers, setVerifiers] = useState<AbstractVerifier[]>([])
const [submittedContracts, setSubmittedContracts] = useState<SubmittedContracts>({})
useEffect(() => { useEffect(() => {
console.log('Selected Contract File And Name Changed', selectedContractFileAndName) console.log('Selected Contract File And Name Changed', selectedContractFileAndName)
@ -66,7 +66,7 @@ const App = () => {
}, []) }, [])
return ( return (
<AppContext.Provider value={{themeType, setThemeType, chains, compilationOutput, selectedContractFileAndName, setSelectedContractFileAndName, targetFileName, verifiedContracts, setVerifiedContracts, verifiers, setVerifiers}}> <AppContext.Provider value={{themeType, setThemeType, chains, compilationOutput, selectedContractFileAndName, setSelectedContractFileAndName, targetFileName, verifiers, setVerifiers, submittedContracts, setSubmittedContracts}}>
<DisplayRoutes /> <DisplayRoutes />
</AppContext.Provider> </AppContext.Provider>
) )

@ -1,8 +1,9 @@
import React from 'react' import React from 'react'
import {HashRouter as Router, Route, Routes, RouteProps} from 'react-router-dom' import {HashRouter as Router, Route, Routes} from 'react-router-dom'
import {VerifyView} from './views' import {VerifyView} from './views'
import {DefaultLayout} from './layouts' import {DefaultLayout} from './layouts'
import {ReceiptsView} from './views/ReceiptsView'
const DisplayRoutes = () => ( const DisplayRoutes = () => (
<Router> <Router>
@ -15,6 +16,15 @@ const DisplayRoutes = () => (
</DefaultLayout> </DefaultLayout>
} }
/> />
<Route
path="/receipts"
element={
<DefaultLayout from="/" title="Receipts">
<ReceiptsView />
</DefaultLayout>
}
/>
</Routes> </Routes>
</Router> </Router>
) )

@ -1,14 +1,9 @@
import {CompilerAbstract} from '@remix-project/remix-solidity'
import {AbstractVerifier} from '../Verifiers/AbstractVerifier' import {AbstractVerifier} from '../Verifiers/AbstractVerifier'
import {SourcifyVerifier} from '../Verifiers/SourcifyVerifier'
import {EtherscanVerifier} from '../Verifiers/EtherscanVerifier'
export interface VerifiedContract { export type SourcifyVerificationStatus = 'perfect' | 'partial' | null
name: string
address: string
chainId: string
date: Date
verifier: AbstractVerifier
status: string
receipt?: string
}
interface Currency { interface Currency {
name: string name: string
@ -28,3 +23,48 @@ export interface Chain {
faucets?: string[] faucets?: string[]
infoURL?: string infoURL?: string
} }
export interface VerificationReceipt {
receiptId?: string
verifier: AbstractVerifier
status: SourcifyVerificationStatus | 'error' | null
message?: string
}
export interface SubmittedContract {
id: string
filePath: string
contractName: string
chainId: string
address: string
compilerAbstract: CompilerAbstract
date: Date
receipts: VerificationReceipt[]
}
export interface SubmittedContracts {
[id: string]: SubmittedContract
}
export interface SourcifyVerificationResponse {
result: [
{
address: string
chainId: string
status: SourcifyVerificationStatus
libraryMap: {
[key: string]: string
}
}
]
}
export interface SourcifyVerificationError {
error: 'string'
}
export interface EtherscanResponse {
status: '0' | '1'
message: string
result: string
}

@ -0,0 +1,37 @@
import React from 'react'
import {AppContext} from '../AppContext'
export const ReceiptsView = () => {
const {submittedContracts} = React.useContext(AppContext)
return (
<div className="my-4">
{Object.values(submittedContracts).map((contract) => (
<div key={contract.address}>
<div>Contract Address: {contract.address}</div>
<div>Chain ID: {contract.chainId}</div>
<div>
filePath: {contract.filePath} contractName: {contract.contractName}
</div>
<div>Submission Date: {contract.date.toLocaleString()}</div>
<div>
Receipts:{' '}
<ul>
{contract.receipts.map((receipt) => (
<li key={`${contract.address}-${receipt.verifier.name}`}>
<ul>
<li>Verifier: {receipt.verifier.name}</li>
<li>API URL: {receipt.verifier.apiUrl}</li>
<li>Status: {receipt.status}</li>
<li>Receipt ID: {receipt.receiptId}</li>
<li>Message: {receipt.message}</li>
</ul>
</li>
))}
</ul>
</div>
</div>
))}
</div>
)
}

@ -3,15 +3,18 @@ import React, {useEffect, useState} from 'react'
import {AppContext} from '../AppContext' import {AppContext} from '../AppContext'
import {SearchableDropdown} from '../components' import {SearchableDropdown} from '../components'
import {ContractDropdown} from '../components/ContractDropdown' import {ContractDropdown} from '../components/ContractDropdown'
// INSERT_YOUR_CODE
import {ethers} from 'ethers/' import {ethers} from 'ethers/'
import {Chain} from '../types/VerificationTypes' import {Chain, SubmittedContract, VerificationReceipt} from '../types/VerificationTypes'
import {SourcifyVerifier} from '../Verifiers/SourcifyVerifier'
import {EtherscanVerifier} from '../Verifiers/EtherscanVerifier'
import {useNavigate} from 'react-router-dom'
export const VerifyView = () => { export const VerifyView = () => {
const {chains, compilationOutput, verifiers, setVerifiers, selectedContractFileAndName} = React.useContext(AppContext) const {chains, compilationOutput, verifiers, setVerifiers, selectedContractFileAndName, setSubmittedContracts} = React.useContext(AppContext)
const [contractAddress, setContractAddress] = useState('') const [contractAddress, setContractAddress] = useState('')
const [contractAddressError, setContractAddressError] = useState('') const [contractAddressError, setContractAddressError] = useState('')
const [selectedChain, setSelectedChain] = useState<Chain | undefined>() const [selectedChain, setSelectedChain] = useState<Chain | undefined>()
const navigate = useNavigate()
useEffect(() => { useEffect(() => {
console.log('Selected chain changed', selectedChain) console.log('Selected chain changed', selectedChain)
@ -33,12 +36,60 @@ export const VerifyView = () => {
const handleVerify = async (e) => { const handleVerify = async (e) => {
e.preventDefault() // Don't change the page e.preventDefault() // Don't change the page
const sourcifyPromises = verifiers.map((verifier) => {
return verifier.verify(selectedChain.chainId.toString(), contractAddress, compilationOutput, selectedContractFileAndName)
})
const results = await Promise.all(sourcifyPromises) const [filePath, contractName] = selectedContractFileAndName.split(':')
console.log('results', results) const enabledVerifiers = verifiers.filter((verifier) => verifier.enabled)
const compilerAbstract = compilationOutput[filePath]
if (!compilerAbstract) {
throw new Error(`Error: Compilation output not found for ${selectedContractFileAndName}`)
}
const date = new Date()
// A receipt for each verifier
const receipts: VerificationReceipt[] = enabledVerifiers.map((verifier) => ({verifier, status: null, receiptId: null, message: null}))
const newSubmittedContract: SubmittedContract = {
id: selectedChain?.chainId + '-' + contractAddress + '-' + date.toString(),
address: contractAddress,
chainId: selectedChain?.chainId.toString(),
filePath,
contractName,
compilerAbstract,
date,
receipts,
}
setSubmittedContracts((prev) => ({...prev, [newSubmittedContract.id]: newSubmittedContract}))
console.log('newSubmittedContract:', newSubmittedContract)
// Take user to receipt view
navigate('/receipts')
// Verify for each verifier. forEach does not wait for await and each promise will execute in parallel
receipts.forEach(async (receipt) => {
const {verifier} = receipt
if (verifier instanceof SourcifyVerifier) {
try {
const response = await verifier.verify(selectedChain?.chainId.toString(), contractAddress, compilerAbstract, selectedContractFileAndName)
receipt.status = response.result[0].status
} catch (e) {
const err = e as Error
receipt.status = 'error'
receipt.message = err.message
}
} else if (verifier instanceof EtherscanVerifier) {
try {
const response = await verifier.verify(selectedChain?.chainId.toString(), contractAddress, compilerAbstract, selectedContractFileAndName)
receipt.status = 'perfect'
} catch (e) {
const err = e as Error
receipt.status = 'error'
receipt.message = err.message
}
}
// Update the UI
setSubmittedContracts((prev) => ({...prev, [newSubmittedContract.id]: newSubmittedContract}))
})
} }
const handleAddressChange = (event: React.ChangeEvent<HTMLInputElement>) => { const handleAddressChange = (event: React.ChangeEvent<HTMLInputElement>) => {
@ -89,8 +140,7 @@ export const VerifyView = () => {
checked={verifier.enabled} checked={verifier.enabled}
onChange={(e) => { onChange={(e) => {
verifier.enabled = e.target.checked verifier.enabled = e.target.checked
// Trigger a re-render or state update if necessary // Trigger a re-render
// For example, you might need to update the state that holds the verifiers
setVerifiers([...verifiers]) setVerifiers([...verifiers])
}} }}
/> />

Loading…
Cancel
Save