From 2df585c2402b3bd4cd497a5a2da68dee18c1cf94 Mon Sep 17 00:00:00 2001 From: Manuel Wedler Date: Thu, 4 Jul 2024 11:00:16 +0200 Subject: [PATCH] Decouple SubmittedContract from verifier logic to make it easy to store them in local storage --- .../src/app/Verifiers/AbstractVerifier.ts | 7 +-- .../src/app/Verifiers/EtherscanVerifier.ts | 7 +-- .../src/app/Verifiers/SourcifyVerifier.ts | 4 -- .../src/app/Verifiers/index.ts | 22 +++++++ apps/contract-verification/src/app/app.tsx | 10 ++-- .../src/app/components/AccordionReceipt.tsx | 6 +- .../src/app/types/VerificationTypes.ts | 27 +++++---- .../src/app/views/VerifyView.tsx | 59 +++++++++++-------- 8 files changed, 87 insertions(+), 55 deletions(-) create mode 100644 apps/contract-verification/src/app/Verifiers/index.ts diff --git a/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts b/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts index 651eec6a3a..131c5e29a1 100644 --- a/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts +++ b/apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts @@ -1,15 +1,12 @@ import { CompilerAbstract } from '@remix-project/remix-solidity' -import { SourcifyReceipt } from '../Receipts/SourcifyReceipt' -import { EtherscanReceipt } from '../Receipts/EtherscanReceipt' +import { VerifierIdentifier } from '../types/VerificationTypes' export abstract class AbstractVerifier { - name: string apiUrl: string enabled: boolean - constructor(apiUrl: string, name: string) { + constructor(apiUrl: string) { this.apiUrl = apiUrl - this.name = name this.enabled = true } diff --git a/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts b/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts index f839010110..d8dd4a55c6 100644 --- a/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts +++ b/apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts @@ -1,13 +1,12 @@ import { CompilerAbstract } from '@remix-project/remix-solidity' import { AbstractVerifier } from './AbstractVerifier' -import { EtherscanReceipt } from '../Receipts/EtherscanReceipt' import { EtherscanRequest, EtherscanResponse } from '../types/VerificationTypes' export class EtherscanVerifier extends AbstractVerifier { - apiKey: string + apiKey?: string - constructor(apiUrl: string, name: string = 'Etherscan', apiKey: string) { - super(apiUrl, name) + constructor(apiUrl: string, apiKey?: string) { + super(apiUrl) this.apiKey = apiKey } diff --git a/apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts b/apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts index a26c3ffb7d..23bdeb39ca 100644 --- a/apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts +++ b/apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts @@ -4,10 +4,6 @@ import { SourcifyReceipt } from '../Receipts/SourcifyReceipt' import { SourcifyVerificationError, SourcifyVerificationResponse } from '../types/VerificationTypes' export class SourcifyVerifier extends AbstractVerifier { - constructor(apiUrl: string, name: string = 'Sourcify') { - super(apiUrl, name) - } - async verify(chainId: string, address: string, compilerAbstract: CompilerAbstract, selectedContractFileAndName: string) { const [_triggerFileName, selectedFilePath, selectedContractName] = selectedContractFileAndName.split(':') const metadataStr = compilerAbstract.data.contracts[selectedFilePath][selectedContractName].metadata diff --git a/apps/contract-verification/src/app/Verifiers/index.ts b/apps/contract-verification/src/app/Verifiers/index.ts new file mode 100644 index 0000000000..263f1b3586 --- /dev/null +++ b/apps/contract-verification/src/app/Verifiers/index.ts @@ -0,0 +1,22 @@ +import { VerifierIdentifier, VerifierSettings } from '../types/VerificationTypes' +import { AbstractVerifier } from './AbstractVerifier' +import { EtherscanVerifier } from './EtherscanVerifier' +import { SourcifyVerifier } from './SourcifyVerifier' + +export { AbstractVerifier } from './AbstractVerifier' +export { SourcifyVerifier } from './SourcifyVerifier' +export { EtherscanVerifier } from './EtherscanVerifier' + +export function getVerifier(identifier: VerifierIdentifier, settings: VerifierSettings): AbstractVerifier { + switch (identifier) { + case 'Sourcify': + return new SourcifyVerifier(settings.apiUrl) + case 'Etherscan': + if (!settings.apiKey) { + throw new Error('The Etherscan verifier requires an API key.') + } + return new EtherscanVerifier(settings.apiUrl, settings.apiKey) + case 'Blockscout': + return new EtherscanVerifier(settings.apiUrl) + } +} diff --git a/apps/contract-verification/src/app/app.tsx b/apps/contract-verification/src/app/app.tsx index ff81b1be56..4bd677a143 100644 --- a/apps/contract-verification/src/app/app.tsx +++ b/apps/contract-verification/src/app/app.tsx @@ -33,8 +33,8 @@ const App = () => { useEffect(() => { // const sourcifyVerifier = new SourcifyVerifier('http://sourcify.dev/server/', 'Sourcify') - const sourcifyVerifier = new SourcifyVerifier('http://localhost:5555/', 'Sourcify Localhost') - const etherscanVerifier = new EtherscanVerifier('https://api.etherscan.io', 'Etherscan', 'API_KEY') + const sourcifyVerifier = new SourcifyVerifier('http://localhost:5555/') + const etherscanVerifier = new EtherscanVerifier('https://api.etherscan.io', 'API_KEY') setVerifiers([sourcifyVerifier, etherscanVerifier]) // TODO: Fix 'compilationFinished' event types. The interface is outdated at https://github.com/ethereum/remix-plugin/blob/master/packages/api/src/lib/compiler/api.ts. It does not include data, input, or version. See the current parameters: https://github.com/ethereum/remix-project/blob/9f6c5be882453a555055f07171701459e4ae88a4/libs/remix-solidity/src/compiler/compiler.ts#L189 @@ -46,10 +46,10 @@ const App = () => { }) // Subscribe to compilations - plugin.on('compilerArtefacts' as any, 'compilationSaved', (compilerAbstract: { [key: string]: CompilerAbstract }) => { + plugin.on('compilerArtefacts' as any, 'compilationSaved', (compilerAbstracts: { [key: string]: CompilerAbstract }) => { console.log('compilerArtefacts.compilationSaved') - console.log(compilerAbstract) - setCompilationOutput((prev) => ({ ...(prev || {}), ...compilerAbstract })) + console.log(compilerAbstracts) + setCompilationOutput((prev) => ({ ...(prev || {}), ...compilerAbstracts })) }) // TODO: Is there a way to get all compilations from the `build-info` files without having to compile again? diff --git a/apps/contract-verification/src/app/components/AccordionReceipt.tsx b/apps/contract-verification/src/app/components/AccordionReceipt.tsx index 1cb3565725..827f357c4a 100644 --- a/apps/contract-verification/src/app/components/AccordionReceipt.tsx +++ b/apps/contract-verification/src/app/components/AccordionReceipt.tsx @@ -84,7 +84,7 @@ const ReceiptsBody = ({ contract }: { contract: SubmittedContract }) => {
Submission: - {contract.date.toLocaleString()} + {new Date(contract.date).toLocaleString()}
@@ -100,8 +100,8 @@ const ReceiptsBody = ({ contract }: { contract: SubmittedContract }) => { {contract.receipts.map((receipt) => ( - - + + diff --git a/apps/contract-verification/src/app/types/VerificationTypes.ts b/apps/contract-verification/src/app/types/VerificationTypes.ts index 41b32a9d19..d5e28c3a69 100644 --- a/apps/contract-verification/src/app/types/VerificationTypes.ts +++ b/apps/contract-verification/src/app/types/VerificationTypes.ts @@ -1,10 +1,3 @@ -import { CompilerAbstract } from '@remix-project/remix-solidity' -import { AbstractVerifier } from '../Verifiers/AbstractVerifier' -import { SourcifyVerifier } from '../Verifiers/SourcifyVerifier' -import { EtherscanVerifier } from '../Verifiers/EtherscanVerifier' - -export type SourcifyVerificationStatus = 'perfect' | 'partial' | null - interface Currency { name: string symbol: string @@ -24,9 +17,21 @@ export interface Chain { infoURL?: string } +export type VerifierIdentifier = "Sourcify" | "Etherscan" | "Blockscout" + +export interface VerifierSettings { + apiUrl: string + apiKey?: string +} + +export interface VerifierInfo { + name: VerifierIdentifier + apiUrl: string +} + export interface VerificationReceipt { receiptId?: string - verifier: AbstractVerifier + verifierInfo: VerifierInfo status: SourcifyVerificationStatus | 'error' | null message?: string } @@ -38,8 +43,7 @@ export interface SubmittedContract { contractName: string chainId: string address: string - compilerAbstract: CompilerAbstract - date: Date + date: string receipts: VerificationReceipt[] } @@ -50,6 +54,7 @@ export interface SubmittedProxyContract { proxy: SubmittedContract } +// This and all nested subtypes should be pure interfaces, so they can be converted to JSON easily export interface SubmittedContracts { [id: string]: SubmittedContract | SubmittedProxyContract } @@ -62,6 +67,8 @@ export function isContract(contract: SubmittedContract | SubmittedProxyContract) return contract.type === 'contract' } +export type SourcifyVerificationStatus = 'perfect' | 'partial' | null + export interface SourcifyVerificationResponse { result: [ { diff --git a/apps/contract-verification/src/app/views/VerifyView.tsx b/apps/contract-verification/src/app/views/VerifyView.tsx index 6cb99858fa..5eae6c6208 100644 --- a/apps/contract-verification/src/app/views/VerifyView.tsx +++ b/apps/contract-verification/src/app/views/VerifyView.tsx @@ -2,11 +2,12 @@ import React, { useEffect, useState } from 'react' import { AppContext } from '../AppContext' import { SearchableChainDropdown, ContractDropdown, ContractAddressInput } from '../components' -import { Chain, SubmittedContract, VerificationReceipt } from '../types/VerificationTypes' +import { Chain, SubmittedContract, VerificationReceipt, VerifierInfo } from '../types/VerificationTypes' import { SourcifyVerifier } from '../Verifiers/SourcifyVerifier' import { EtherscanVerifier } from '../Verifiers/EtherscanVerifier' import { useNavigate } from 'react-router-dom' import { ConstructorArguments } from '../components/ConstructorArguments' +import { getVerifier } from '../Verifiers' export const VerifyView = () => { const { compilationOutput, verifiers, setVerifiers, selectedContractFileAndName, setSubmittedContracts } = React.useContext(AppContext) @@ -32,16 +33,21 @@ export const VerifyView = () => { const date = new Date() // A receipt for each verifier - const receipts: VerificationReceipt[] = enabledVerifiers.map((verifier) => ({ verifier, status: null, receiptId: null, message: null })) + const receipts: VerificationReceipt[] = enabledVerifiers.map((verifier) => { + const verifierInfo: VerifierInfo = { + apiUrl: verifier.apiUrl, + name: verifier instanceof SourcifyVerifier ? 'Sourcify' : 'Etherscan', + } + return { verifierInfo, status: null, receiptId: null, message: null } + }) const newSubmittedContract: SubmittedContract = { type: 'contract', - id: selectedChain?.chainId + '-' + contractAddress + '-' + date.toString(), + id: selectedChain?.chainId + '-' + contractAddress + '-' + date.toUTCString(), address: contractAddress, chainId: selectedChain?.chainId.toString(), filePath, contractName, - compilerAbstract, - date, + date: date.toUTCString(), receipts, } setSubmittedContracts((prev) => ({ ...prev, [newSubmittedContract.id]: newSubmittedContract })) @@ -53,7 +59,8 @@ export const VerifyView = () => { // Verify for each verifier. forEach does not wait for await and each promise will execute in parallel receipts.forEach(async (receipt) => { - const { verifier } = receipt + const { verifierInfo } = receipt + const verifier = getVerifier(verifierInfo.name, { apiUrl: verifierInfo.apiUrl }) if (verifier instanceof SourcifyVerifier) { try { const response = await verifier.verify(selectedChain?.chainId.toString(), contractAddress, compilerAbstract, selectedContractFileAndName) @@ -95,24 +102,28 @@ export const VerifyView = () => {
{verifiers?.length > 0 && - verifiers.map((verifier) => ( -
- { - verifier.enabled = e.target.checked - // Trigger a re-render - setVerifiers([...verifiers]) - }} - /> - -
- ))} + verifiers.map((verifier) => { + // Temporary fix. The verifier options should be rendered from a constant later. + const name = verifier instanceof SourcifyVerifier ? 'Sourcify' : 'Etherscan' + return ( +
+ { + verifier.enabled = e.target.checked + // Trigger a re-render + setVerifiers([...verifiers]) + }} + /> + +
+ ) + })}
{receipt.verifier.name}{receipt.verifier.apiUrl}{receipt.verifierInfo.name}{receipt.verifierInfo.apiUrl} {receipt.status} {receipt.message} {receipt.receiptId}