Use objects as ContractDropdown values

pull/5285/head
Manuel Wedler 5 months ago committed by Aniket
parent 2df585c240
commit 831fc88a5d
  1. 4
      apps/contract-verification/src/app/AppContext.tsx
  2. 4
      apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts
  3. 13
      apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts
  4. 19
      apps/contract-verification/src/app/Verifiers/SourcifyVerifier.ts
  5. 10
      apps/contract-verification/src/app/app.tsx
  6. 14
      apps/contract-verification/src/app/components/ConstructorArguments.tsx
  7. 19
      apps/contract-verification/src/app/components/ContractDropdown.tsx
  8. 18
      apps/contract-verification/src/app/views/VerifyView.tsx

@ -10,8 +10,6 @@ type AppContextType = {
setThemeType: (themeType: ThemeType) => void
chains: Chain[]
compilationOutput: { [key: string]: CompilerAbstract } | undefined
selectedContractFileAndName: string | undefined
setSelectedContractFileAndName: React.Dispatch<React.SetStateAction<string>>
targetFileName: string | undefined
verifiers: AbstractVerifier[]
setVerifiers: React.Dispatch<React.SetStateAction<AbstractVerifier[]>>
@ -27,8 +25,6 @@ const defaultContextValue: AppContextType = {
},
chains: [],
compilationOutput: undefined,
selectedContractFileAndName: undefined,
setSelectedContractFileAndName: (contract: string) => {},
targetFileName: undefined,
verifiers: [],
setVerifiers: (verifiers: AbstractVerifier[]) => {},

@ -1,5 +1,5 @@
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { VerifierIdentifier } from '../types/VerificationTypes'
import { SubmittedContract, VerifierIdentifier } from '../types/VerificationTypes'
export abstract class AbstractVerifier {
apiUrl: string
@ -10,6 +10,6 @@ export abstract class AbstractVerifier {
this.enabled = true
}
abstract verify(chainId: string, address: string, compilerAbstract: CompilerAbstract, selectedContractFileAndName: string): Promise<any>
abstract verify(submittedContract: SubmittedContract, compilerAbstract: CompilerAbstract): Promise<any>
abstract lookup(): Promise<any>
}

@ -1,6 +1,6 @@
import { CompilerAbstract } from '@remix-project/remix-solidity'
import { AbstractVerifier } from './AbstractVerifier'
import { EtherscanRequest, EtherscanResponse } from '../types/VerificationTypes'
import { EtherscanRequest, EtherscanResponse, SubmittedContract } from '../types/VerificationTypes'
export class EtherscanVerifier extends AbstractVerifier {
apiKey?: string
@ -10,18 +10,17 @@ export class EtherscanVerifier extends AbstractVerifier {
this.apiKey = apiKey
}
async verify(chainId: string, address: string, compilerAbstract: CompilerAbstract, selectedContractFileAndName: string, abiEncodedConstructorArgs?: string) {
async verify(submittedContract: SubmittedContract, compilerAbstract: CompilerAbstract, abiEncodedConstructorArgs?: string) {
const CODE_FORMAT = 'solidity-standard-json-input'
const [_triggerFilePath, selectedFilePath, selectedContractName] = selectedContractFileAndName.split(':')
// TODO: Handle version Vyper contracts. This relies on Solidity metadata.
const metadata = JSON.parse(compilerAbstract.data.contracts[selectedFilePath][selectedContractName].metadata)
const metadata = JSON.parse(compilerAbstract.data.contracts[submittedContract.filePath][submittedContract.contractName].metadata)
const body: EtherscanRequest = {
chainId,
chainId: submittedContract.chainId,
codeformat: CODE_FORMAT,
sourceCode: JSON.stringify(compilerAbstract.input),
contractaddress: address,
contractname: selectedContractFileAndName,
contractaddress: submittedContract.address,
contractname: submittedContract.filePath + ':' + submittedContract.contractName,
compilerversion: metadata.compiler.version,
}

@ -1,19 +1,18 @@
import { CompilerAbstract, SourcesCode } from '@remix-project/remix-solidity'
import { AbstractVerifier } from './AbstractVerifier'
import { SourcifyReceipt } from '../Receipts/SourcifyReceipt'
import { SourcifyVerificationError, SourcifyVerificationResponse } from '../types/VerificationTypes'
import { SourcifyVerificationError, SourcifyVerificationResponse, SubmittedContract } from '../types/VerificationTypes'
export class SourcifyVerifier extends AbstractVerifier {
async verify(chainId: string, address: string, compilerAbstract: CompilerAbstract, selectedContractFileAndName: string) {
const [_triggerFileName, selectedFilePath, selectedContractName] = selectedContractFileAndName.split(':')
const metadataStr = compilerAbstract.data.contracts[selectedFilePath][selectedContractName].metadata
async verify(submittedContract: SubmittedContract, compilerAbstract: CompilerAbstract) {
const metadataStr = compilerAbstract.data.contracts[submittedContract.filePath][submittedContract.contractName].metadata
const sources = compilerAbstract.source.sources
console.log('selectedFilePath:', selectedFilePath)
console.log('selectedContractName:', selectedContractName)
console.log('selectedFilePath:', submittedContract.filePath)
console.log('selectedContractName:', submittedContract.contractName)
console.log('compilerAbstract:', compilerAbstract)
console.log('selectedContractMetadataStr:', metadataStr)
console.log('chainId:', chainId)
console.log('address:', address)
console.log('chainId:', submittedContract.chainId)
console.log('address:', submittedContract.address)
// from { "filename.sol": {content: "contract MyContract { ... }"} }
// to { "filename.sol": "contract MyContract { ... }" }
@ -22,8 +21,8 @@ export class SourcifyVerifier extends AbstractVerifier {
return acc
}, {})
const body = {
chainId,
address,
chainId: submittedContract.chainId,
address: submittedContract.address,
files: {
'metadata.json': metadataStr,
...formattedSources,

@ -12,6 +12,7 @@ import { CompilerAbstract } from '@remix-project/remix-solidity'
import { SourcifyVerifier } from './Verifiers/SourcifyVerifier'
import { EtherscanVerifier } from './Verifiers/EtherscanVerifier'
import { AbstractVerifier } from './Verifiers/AbstractVerifier'
import { ContractDropdownSelection } from './components/ContractDropdown'
const plugin = new ContractVerificationPluginClient()
@ -21,16 +22,9 @@ const App = () => {
const [chains, setChains] = useState<Chain[]>([]) // State to hold the chains data
const [targetFileName, setTargetFileName] = useState('')
const [compilationOutput, setCompilationOutput] = useState<{ [key: string]: CompilerAbstract } | undefined>()
// Contract file and name in format contracts/Storage.sol:contracts/Owner.sol:Owner
// TODO: What happens if contract or filepath contains ":"" ?
const [selectedContractFileAndName, setSelectedContractFileAndName] = useState<string | undefined>()
const [verifiers, setVerifiers] = useState<AbstractVerifier[]>([])
const [submittedContracts, setSubmittedContracts] = useState<SubmittedContracts>({})
useEffect(() => {
console.log('Selected Contract File And Name Changed', selectedContractFileAndName)
}, [selectedContractFileAndName])
useEffect(() => {
// const sourcifyVerifier = new SourcifyVerifier('http://sourcify.dev/server/', 'Sourcify')
const sourcifyVerifier = new SourcifyVerifier('http://localhost:5555/')
@ -67,7 +61,7 @@ const App = () => {
}, [])
return (
<AppContext.Provider value={{ themeType, setThemeType, chains, compilationOutput, selectedContractFileAndName, setSelectedContractFileAndName, targetFileName, verifiers, setVerifiers, submittedContracts, setSubmittedContracts }}>
<AppContext.Provider value={{ themeType, setThemeType, chains, compilationOutput, targetFileName, verifiers, setVerifiers, submittedContracts, setSubmittedContracts }}>
<DisplayRoutes />
</AppContext.Provider>
)

@ -2,24 +2,26 @@ import React, { useEffect } from 'react'
import { ethers } from 'ethers'
import { AppContext } from '../AppContext'
import { ContractDropdownSelection } from './ContractDropdown'
const abiCoder = new ethers.utils.AbiCoder()
interface ConstructorArgumentsProps {
abiEncodedConstructorArgs: string
setAbiEncodedConstructorArgs: React.Dispatch<React.SetStateAction<string>>
selectedContract: ContractDropdownSelection
}
export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiEncodedConstructorArgs, setAbiEncodedConstructorArgs }) => {
const { selectedContractFileAndName, compilationOutput } = React.useContext(AppContext)
export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiEncodedConstructorArgs, setAbiEncodedConstructorArgs, selectedContract }) => {
const { compilationOutput } = React.useContext(AppContext)
const [constructorArgsValues, setConstructorArgsValues] = React.useState<string[]>([])
const [abiEncodingError, setAbiEncodingError] = React.useState<string | null>('')
const [toggleRawInput, setToggleRawInput] = React.useState<boolean>(false)
const [triggerFilePath, filePath, contractName] = selectedContractFileAndName ? selectedContractFileAndName.split(':') : []
const { triggerFilePath, filePath, contractName } = selectedContract
const selectedCompilerAbstract = triggerFilePath && compilationOutput[triggerFilePath]
const selectedContract = selectedCompilerAbstract?.data?.contracts?.[filePath]?.[contractName]
const abi = selectedContract?.abi
const compiledContract = selectedCompilerAbstract?.data?.contracts?.[filePath]?.[contractName]
const abi = compiledContract?.abi
// Wanted to use execution.txHelper.getConstructorInterface from @remix-project/remix-lib but getting errors: 'error getting eth provider options', 'global is not defined' etc.
const constructorArgs = abi && abi.find((a) => a.type === 'constructor') && abi.find((a) => a.type === 'constructor').inputs
@ -56,7 +58,7 @@ export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiE
}
}, [constructorArgsValues, constructorArgs])
if (!selectedContractFileAndName) return null
if (!selectedContract) return null
if (!compilationOutput && Object.keys(compilationOutput).length === 0) return null
// No render if no constructor args
if (!constructorArgs || constructorArgs.length === 0) return null

@ -1,19 +1,22 @@
import React, { useEffect, useState, useContext } from 'react'
import './ContractDropdown.css'
import { AppContext } from '../AppContext'
interface ContractDropdownItem {
value: string
name: string
export interface ContractDropdownSelection {
triggerFilePath: string
filePath: string
contractName: string
}
interface ContractDropdownProps {
label: string
id: string
setSelectedContract: (selection: ContractDropdownSelection) => void
}
// Chooses one contract from the compilation output.
export const ContractDropdown: React.FC<ContractDropdownProps> = ({ label, id }) => {
const { setSelectedContractFileAndName, compilationOutput } = useContext(AppContext)
export const ContractDropdown: React.FC<ContractDropdownProps> = ({ label, id, setSelectedContract }) => {
const { compilationOutput } = useContext(AppContext)
useEffect(() => {
console.log('CompiilationOutput chainged', compilationOutput)
@ -26,14 +29,14 @@ export const ContractDropdown: React.FC<ContractDropdownProps> = ({ label, id })
const contractsInFile = contracts[firstFilePath]
if (contractsInFile && Object.keys(contractsInFile).length) {
const firstContractName = Object.keys(contractsInFile)[0]
setSelectedContractFileAndName(triggerFilePath + ':' + firstFilePath + ':' + firstContractName)
setSelectedContract({ triggerFilePath, filePath: firstFilePath, contractName: firstContractName })
}
}
}, [compilationOutput])
const handleSelectContract = (event: React.ChangeEvent<HTMLSelectElement>) => {
console.log('selecting ', event.target.value)
setSelectedContractFileAndName(event.target.value)
setSelectedContract(JSON.parse(event.target.value))
}
const hasContracts = compilationOutput && Object.keys(compilationOutput).length > 0
@ -51,7 +54,7 @@ export const ContractDropdown: React.FC<ContractDropdownProps> = ({ label, id })
[File]: {fileName2}
</option>
{Object.keys(compilationOutput[compilationTriggerFileName].data.contracts[fileName2]).map((contractName) => (
<option key={fileName2 + ':' + contractName} value={compilationTriggerFileName + ':' + fileName2 + ':' + contractName}>
<option key={fileName2 + ':' + contractName} value={JSON.stringify({ triggerFilePath: compilationTriggerFileName, filePath: fileName2, contractName: contractName })}>
{'\u00A0\u00A0\u00A0' + contractName} {/* Indentation for contract names */}
</option>
))}

@ -8,12 +8,14 @@ import { EtherscanVerifier } from '../Verifiers/EtherscanVerifier'
import { useNavigate } from 'react-router-dom'
import { ConstructorArguments } from '../components/ConstructorArguments'
import { getVerifier } from '../Verifiers'
import { ContractDropdownSelection } from '../components/ContractDropdown'
export const VerifyView = () => {
const { compilationOutput, verifiers, setVerifiers, selectedContractFileAndName, setSubmittedContracts } = React.useContext(AppContext)
const { compilationOutput, verifiers, setVerifiers, setSubmittedContracts } = React.useContext(AppContext)
const [contractAddress, setContractAddress] = useState('')
const [selectedChain, setSelectedChain] = useState<Chain | undefined>()
const [abiEncodedConstructorArgs, setAbiEncodedConstructorArgs] = React.useState<string>('')
const [selectedContract, setSelectedContract] = useState<ContractDropdownSelection | undefined>()
const navigate = useNavigate()
useEffect(() => {
@ -23,12 +25,12 @@ export const VerifyView = () => {
const handleVerify = async (e) => {
e.preventDefault() // Don't change the page
console.log('selectedContractFileAndName', selectedContractFileAndName)
const [triggerFilePath, filePath, contractName] = selectedContractFileAndName.split(':')
console.log('selectedContract', selectedContract)
const { triggerFilePath, filePath, contractName } = selectedContract
const enabledVerifiers = verifiers.filter((verifier) => verifier.enabled)
const compilerAbstract = compilationOutput[triggerFilePath]
if (!compilerAbstract) {
throw new Error(`Error: Compilation output not found for ${selectedContractFileAndName}`)
throw new Error(`Error: Compilation output not found for ${triggerFilePath}`)
}
const date = new Date()
@ -63,7 +65,7 @@ export const VerifyView = () => {
const verifier = getVerifier(verifierInfo.name, { apiUrl: verifierInfo.apiUrl })
if (verifier instanceof SourcifyVerifier) {
try {
const response = await verifier.verify(selectedChain?.chainId.toString(), contractAddress, compilerAbstract, selectedContractFileAndName)
const response = await verifier.verify(newSubmittedContract, compilerAbstract)
receipt.status = response.result[0].status
} catch (e) {
const err = e as Error
@ -72,7 +74,7 @@ export const VerifyView = () => {
}
} else if (verifier instanceof EtherscanVerifier) {
try {
const response = await verifier.verify(selectedChain?.chainId.toString(), contractAddress, compilerAbstract, selectedContractFileAndName, abiEncodedConstructorArgs)
const response = await verifier.verify(newSubmittedContract, compilerAbstract, abiEncodedConstructorArgs)
receipt.status = 'perfect'
} catch (e) {
const err = e as Error
@ -94,7 +96,7 @@ export const VerifyView = () => {
<ContractAddressInput label="Contract Address" id="contract-address" setContractAddress={setContractAddress} contractAddress={contractAddress} />
<ContractDropdown label="Contract Name" id="contract-dropdown-1" />
<ContractDropdown label="Contract Name" id="contract-dropdown-1" setSelectedContract={setSelectedContract} />
<button type="submit" className="btn btn-primary">
Verify
@ -126,7 +128,7 @@ export const VerifyView = () => {
})}
</div>
<div>
<ConstructorArguments abiEncodedConstructorArgs={abiEncodedConstructorArgs} setAbiEncodedConstructorArgs={setAbiEncodedConstructorArgs} />
<ConstructorArguments abiEncodedConstructorArgs={abiEncodedConstructorArgs} setAbiEncodedConstructorArgs={setAbiEncodedConstructorArgs} selectedContract={selectedContract} />
</div>
</form>
)

Loading…
Cancel
Save