Use objects as ContractDropdown values

pull/5285/head
Manuel Wedler 8 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 setThemeType: (themeType: ThemeType) => void
chains: Chain[] chains: Chain[]
compilationOutput: { [key: string]: CompilerAbstract } | undefined compilationOutput: { [key: string]: CompilerAbstract } | undefined
selectedContractFileAndName: string | undefined
setSelectedContractFileAndName: React.Dispatch<React.SetStateAction<string>>
targetFileName: string | undefined targetFileName: string | undefined
verifiers: AbstractVerifier[] verifiers: AbstractVerifier[]
setVerifiers: React.Dispatch<React.SetStateAction<AbstractVerifier[]>> setVerifiers: React.Dispatch<React.SetStateAction<AbstractVerifier[]>>
@ -27,8 +25,6 @@ const defaultContextValue: AppContextType = {
}, },
chains: [], chains: [],
compilationOutput: undefined, compilationOutput: undefined,
selectedContractFileAndName: undefined,
setSelectedContractFileAndName: (contract: string) => {},
targetFileName: undefined, targetFileName: undefined,
verifiers: [], verifiers: [],
setVerifiers: (verifiers: AbstractVerifier[]) => {}, setVerifiers: (verifiers: AbstractVerifier[]) => {},

@ -1,5 +1,5 @@
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import { VerifierIdentifier } from '../types/VerificationTypes' import { SubmittedContract, VerifierIdentifier } from '../types/VerificationTypes'
export abstract class AbstractVerifier { export abstract class AbstractVerifier {
apiUrl: string apiUrl: string
@ -10,6 +10,6 @@ export abstract class AbstractVerifier {
this.enabled = true 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> abstract lookup(): Promise<any>
} }

@ -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 { EtherscanRequest, EtherscanResponse } from '../types/VerificationTypes' import { EtherscanRequest, EtherscanResponse, SubmittedContract } from '../types/VerificationTypes'
export class EtherscanVerifier extends AbstractVerifier { export class EtherscanVerifier extends AbstractVerifier {
apiKey?: string apiKey?: string
@ -10,18 +10,17 @@ export class EtherscanVerifier extends AbstractVerifier {
this.apiKey = apiKey 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 CODE_FORMAT = 'solidity-standard-json-input'
const [_triggerFilePath, selectedFilePath, selectedContractName] = selectedContractFileAndName.split(':')
// 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[selectedFilePath][selectedContractName].metadata) const metadata = JSON.parse(compilerAbstract.data.contracts[submittedContract.filePath][submittedContract.contractName].metadata)
const body: EtherscanRequest = { const body: EtherscanRequest = {
chainId, chainId: submittedContract.chainId,
codeformat: CODE_FORMAT, codeformat: CODE_FORMAT,
sourceCode: JSON.stringify(compilerAbstract.input), sourceCode: JSON.stringify(compilerAbstract.input),
contractaddress: address, contractaddress: submittedContract.address,
contractname: selectedContractFileAndName, contractname: submittedContract.filePath + ':' + submittedContract.contractName,
compilerversion: metadata.compiler.version, compilerversion: metadata.compiler.version,
} }

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

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

@ -2,24 +2,26 @@ import React, { useEffect } from 'react'
import { ethers } from 'ethers' import { ethers } from 'ethers'
import { AppContext } from '../AppContext' import { AppContext } from '../AppContext'
import { ContractDropdownSelection } from './ContractDropdown'
const abiCoder = new ethers.utils.AbiCoder() const abiCoder = new ethers.utils.AbiCoder()
interface ConstructorArgumentsProps { interface ConstructorArgumentsProps {
abiEncodedConstructorArgs: string abiEncodedConstructorArgs: string
setAbiEncodedConstructorArgs: React.Dispatch<React.SetStateAction<string>> setAbiEncodedConstructorArgs: React.Dispatch<React.SetStateAction<string>>
selectedContract: ContractDropdownSelection
} }
export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiEncodedConstructorArgs, setAbiEncodedConstructorArgs }) => { export const ConstructorArguments: React.FC<ConstructorArgumentsProps> = ({ abiEncodedConstructorArgs, setAbiEncodedConstructorArgs, selectedContract }) => {
const { selectedContractFileAndName, compilationOutput } = React.useContext(AppContext) const { compilationOutput } = React.useContext(AppContext)
const [constructorArgsValues, setConstructorArgsValues] = React.useState<string[]>([]) const [constructorArgsValues, setConstructorArgsValues] = React.useState<string[]>([])
const [abiEncodingError, setAbiEncodingError] = React.useState<string | null>('') const [abiEncodingError, setAbiEncodingError] = React.useState<string | null>('')
const [toggleRawInput, setToggleRawInput] = React.useState<boolean>(false) 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 selectedCompilerAbstract = triggerFilePath && compilationOutput[triggerFilePath]
const selectedContract = selectedCompilerAbstract?.data?.contracts?.[filePath]?.[contractName] const compiledContract = selectedCompilerAbstract?.data?.contracts?.[filePath]?.[contractName]
const abi = selectedContract?.abi 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. // 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 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]) }, [constructorArgsValues, constructorArgs])
if (!selectedContractFileAndName) return null if (!selectedContract) return null
if (!compilationOutput && Object.keys(compilationOutput).length === 0) return null if (!compilationOutput && Object.keys(compilationOutput).length === 0) return null
// No render if no constructor args // No render if no constructor args
if (!constructorArgs || constructorArgs.length === 0) return null if (!constructorArgs || constructorArgs.length === 0) return null

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

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

Loading…
Cancel
Save