Merge branch 'fixgroth' of https://github.com/ethereum/remix-project into popuppanelfix

pull/5371/head
bunsenstraat 2 weeks ago
commit 9d08d116df
  1. 2
      apps/contract-verification/src/app/Verifiers/AbstractVerifier.ts
  2. 2
      apps/contract-verification/src/app/Verifiers/BlockscoutVerifier.ts
  3. 22
      apps/contract-verification/src/app/Verifiers/EtherscanVerifier.ts
  4. 14
      apps/contract-verification/src/app/Verifiers/RoutescanVerifier.ts
  5. 4
      apps/contract-verification/src/app/Verifiers/index.ts
  6. 2
      apps/contract-verification/src/app/components/AccordionReceipt.tsx
  7. 4
      apps/contract-verification/src/app/types/VerificationTypes.ts
  8. 112
      apps/contract-verification/src/app/utils/default-apis.json
  9. 11
      apps/contract-verification/src/app/utils/default-settings.ts
  10. 25
      apps/contract-verification/src/app/views/LookupView.tsx
  11. 2
      apps/contract-verification/src/app/views/ReceiptsView.tsx
  12. 6
      apps/contract-verification/src/app/views/SettingsView.tsx
  13. 11
      apps/contract-verification/src/app/views/VerifyView.tsx
  14. 2
      apps/remix-ide-e2e/nightwatch-chrome.ts

@ -3,7 +3,7 @@ import type { LookupResponse, SubmittedContract, VerificationResponse } from '..
// Optional function definitions
export interface AbstractVerifier {
verifyProxy(submittedContract: SubmittedContract): Promise<VerificationResponse>
verifyProxy?(submittedContract: SubmittedContract): Promise<VerificationResponse>
checkVerificationStatus?(receiptId: string): Promise<VerificationResponse>
checkProxyVerificationStatus?(receiptId: string): Promise<VerificationResponse>
}

@ -26,7 +26,7 @@ export class BlockscoutVerifier extends EtherscanVerifier {
super(apiUrl, apiUrl, undefined)
}
getContractCodeUrl(address: string): string {
getContractCodeUrl(address: string, chainId: string): string {
const url = new URL(this.explorerUrl + `/address/${address}`)
url.searchParams.append('tab', 'contract')
return url.href

@ -74,9 +74,10 @@ export class EtherscanVerifier extends AbstractVerifier {
}
const verificationResponse: EtherscanRpcResponse = await response.json()
const lookupUrl = this.getContractCodeUrl(submittedContract.address, submittedContract.chainId)
if (verificationResponse.result.includes('already verified')) {
return { status: 'already verified', receiptId: null, lookupUrl: this.getContractCodeUrl(submittedContract.address) }
return { status: 'already verified', receiptId: null, lookupUrl }
}
if (verificationResponse.status !== '1' || verificationResponse.message !== 'OK') {
@ -84,7 +85,6 @@ export class EtherscanVerifier extends AbstractVerifier {
throw new Error(verificationResponse.result)
}
const lookupUrl = this.getContractCodeUrl(submittedContract.address)
return { status: 'pending', receiptId: verificationResponse.result, lookupUrl }
}
@ -117,6 +117,10 @@ export class EtherscanVerifier extends AbstractVerifier {
const verificationResponse: EtherscanRpcResponse = await response.json()
if (verificationResponse.message === 'Smart-contract not found or is not verified') {
return { status: 'failed', receiptId: null, message: verificationResponse.message }
}
if (verificationResponse.status !== '1' || verificationResponse.message !== 'OK') {
console.error('Error on Etherscan API proxy verification at ' + this.apiUrl + '\nStatus: ' + verificationResponse.status + '\nMessage: ' + verificationResponse.message + '\nResult: ' + verificationResponse.result)
throw new Error(verificationResponse.result)
@ -144,7 +148,7 @@ export class EtherscanVerifier extends AbstractVerifier {
const checkStatusResponse: EtherscanCheckStatusResponse = await response.json()
if (checkStatusResponse.result.startsWith('Fail - Unable to verify')) {
if (checkStatusResponse.result.startsWith('Fail - Unable to verify') || (checkStatusResponse.result as string) === 'Error: contract does not exist') {
return { status: 'failed', receiptId, message: checkStatusResponse.result }
}
if (checkStatusResponse.result === 'Pending in queue') {
@ -229,23 +233,23 @@ export class EtherscanVerifier extends AbstractVerifier {
const lookupResponse: EtherscanGetSourceCodeResponse = await response.json()
if (lookupResponse.result[0].ABI === 'Contract source code not verified' || !lookupResponse.result[0].SourceCode || (lookupResponse.result as unknown as string) === 'Contract source code not verified') {
return { status: 'not verified' }
}
if (lookupResponse.status !== '1' || !lookupResponse.message.startsWith('OK')) {
const errorResponse = lookupResponse as unknown as EtherscanRpcResponse
console.error('Error on Etherscan API lookup at ' + this.apiUrl + '\nStatus: ' + errorResponse.status + '\nMessage: ' + errorResponse.message + '\nResult: ' + errorResponse.result)
throw new Error(errorResponse.result)
}
if (lookupResponse.result[0].ABI === 'Contract source code not verified' || !lookupResponse.result[0].SourceCode) {
return { status: 'not verified' }
}
const lookupUrl = this.getContractCodeUrl(contractAddress)
const lookupUrl = this.getContractCodeUrl(contractAddress, chainId)
const { sourceFiles, targetFilePath } = this.processReceivedFiles(lookupResponse.result[0], contractAddress, chainId)
return { status: 'verified', lookupUrl, sourceFiles, targetFilePath }
}
getContractCodeUrl(address: string): string {
getContractCodeUrl(address: string, chainId: string): string {
const url = new URL(this.explorerUrl + `/address/${address}#code`)
return url.href
}

@ -0,0 +1,14 @@
import { EtherscanVerifier } from './EtherscanVerifier'
export class RoutescanVerifier extends EtherscanVerifier {
LOOKUP_STORE_DIR = 'routescan-verified'
// Routescan does not support proxy verification
verifyProxy = undefined
checkProxyVerificationStatus = undefined
getContractCodeUrl(address: string, chainId: string): string {
const url = new URL(this.explorerUrl + `/address/${address}/contract/${chainId}/code`)
return url.href
}
}

@ -3,11 +3,13 @@ import { AbstractVerifier } from './AbstractVerifier'
import { BlockscoutVerifier } from './BlockscoutVerifier'
import { EtherscanVerifier } from './EtherscanVerifier'
import { SourcifyVerifier } from './SourcifyVerifier'
import { RoutescanVerifier } from './RoutescanVerifier'
export { AbstractVerifier } from './AbstractVerifier'
export { BlockscoutVerifier } from './BlockscoutVerifier'
export { SourcifyVerifier } from './SourcifyVerifier'
export { EtherscanVerifier } from './EtherscanVerifier'
export { RoutescanVerifier } from './RoutescanVerifier'
export function getVerifier(identifier: VerifierIdentifier, settings: VerifierSettings): AbstractVerifier {
switch (identifier) {
@ -26,5 +28,7 @@ export function getVerifier(identifier: VerifierIdentifier, settings: VerifierSe
return new EtherscanVerifier(settings.apiUrl, settings.explorerUrl, settings.apiKey)
case 'Blockscout':
return new BlockscoutVerifier(settings.apiUrl)
case 'Routescan':
return new RoutescanVerifier(settings.apiUrl, settings.explorerUrl, settings.apiKey)
}
}

@ -88,7 +88,7 @@ const ReceiptsBody = ({ receipts }: { receipts: VerificationReceipt[] }) => {
return (
<ul className="list-group">
{receipts.map((receipt) => (
<li className="list-group-item">
<li key={`${receipt.contractId}-${receipt.verifierInfo.name}${receipt.isProxyReceipt ? '-proxy' : ''}-${receipt.receiptId}`} className="list-group-item">
<CustomTooltip placement="top" tooltipClasses=" text-break" tooltipText={`API: ${receipt.verifierInfo.apiUrl}`}>
<span className="font-weight-bold medium">{receipt.verifierInfo.name}</span>
</CustomTooltip>

@ -17,8 +17,8 @@ export interface Chain {
infoURL?: string
}
export type VerifierIdentifier = 'Sourcify' | 'Etherscan' | 'Blockscout'
export const VERIFIERS: VerifierIdentifier[] = ['Sourcify', 'Etherscan', 'Blockscout']
export type VerifierIdentifier = 'Sourcify' | 'Etherscan' | 'Blockscout' | 'Routescan'
export const VERIFIERS: VerifierIdentifier[] = ['Sourcify', 'Etherscan', 'Blockscout', 'Routescan']
export interface VerifierInfo {
name: VerifierIdentifier

@ -572,5 +572,117 @@
"81247166294": {
"apiUrl": "https://testnet.otoscan.io"
}
},
"Routescan": {
"mainnetExplorerUrl": "https://routescan.io",
"testnetExplorerUrl": "https://testnet.routescan.io",
"apiUrl": "https://api.routescan.io/v2/network/${CHAIN_TYPE}/evm/${CHAIN_ID}/etherscan",
"8453": { "type": "mainnet" },
"167000": { "type": "mainnet" },
"357": { "type": "mainnet" },
"1": { "type": "mainnet" },
"19": { "type": "mainnet" },
"10": { "type": "mainnet" },
"81457": { "type": "mainnet" },
"53935": { "type": "mainnet" },
"432204": { "type": "mainnet" },
"480": { "type": "mainnet" },
"14": { "type": "mainnet" },
"5000": { "type": "mainnet" },
"254": { "type": "mainnet" },
"43114": { "type": "mainnet" },
"7777777": { "type": "mainnet" },
"324": { "type": "mainnet" },
"7560": { "type": "mainnet" },
"185": { "type": "mainnet" },
"888888888": { "type": "mainnet" },
"34443": { "type": "mainnet" },
"88888": { "type": "mainnet" },
"20240603": { "type": "mainnet" },
"6119": { "type": "mainnet" },
"291": { "type": "mainnet" },
"252": { "type": "mainnet" },
"1088": { "type": "mainnet" },
"8008": { "type": "mainnet" },
"288": { "type": "mainnet" },
"65536": { "type": "mainnet" },
"424": { "type": "mainnet" },
"183": { "type": "mainnet" },
"33979": { "type": "mainnet" },
"10849": { "type": "mainnet" },
"2044": { "type": "mainnet" },
"8888": { "type": "mainnet" },
"1853": { "type": "mainnet" },
"56288": { "type": "mainnet" },
"710420": { "type": "mainnet" },
"4337": { "type": "mainnet" },
"333000333": { "type": "mainnet" },
"3011": { "type": "mainnet" },
"1234": { "type": "mainnet" },
"504441": { "type": "mainnet" },
"7887": { "type": "mainnet" },
"7979": { "type": "mainnet" },
"10507": { "type": "mainnet" },
"5566": { "type": "mainnet" },
"151": { "type": "mainnet" },
"62707": { "type": "mainnet" },
"70953": { "type": "mainnet" },
"64165": { "type": "testnet" },
"49321": { "type": "testnet" },
"80084": { "type": "testnet" },
"84532": { "type": "testnet" },
"70805": { "type": "testnet" },
"421614": { "type": "testnet" },
"11155111": { "type": "testnet" },
"1946": { "type": "testnet" },
"17000": { "type": "testnet" },
"11155420": { "type": "testnet" },
"16": { "type": "testnet" },
"168587773": { "type": "testnet" },
"919": { "type": "testnet" },
"999999999": { "type": "testnet" },
"4801": { "type": "testnet" },
"2233": { "type": "testnet" },
"114": { "type": "testnet" },
"4460": { "type": "testnet" },
"2522": { "type": "testnet" },
"20241133": { "type": "testnet" },
"233": { "type": "testnet" },
"28122024": { "type": "testnet" },
"10888": { "type": "testnet" },
"80008": { "type": "testnet" },
"3397901": { "type": "testnet" },
"9728": { "type": "testnet" },
"1687": { "type": "testnet" },
"28882": { "type": "testnet" },
"88882": { "type": "testnet" },
"43113": { "type": "testnet" },
"164": { "type": "testnet" },
"111557560": { "type": "testnet" },
"167009": { "type": "testnet" },
"920637907288165": { "type": "testnet" },
"153": { "type": "testnet" },
"335": { "type": "testnet" },
"432201": { "type": "testnet" },
"9270": { "type": "testnet" },
"7589": { "type": "testnet" },
"686669576": { "type": "testnet" },
"431234": { "type": "testnet" },
"3939": { "type": "testnet" },
"26659": { "type": "testnet" },
"3012": { "type": "testnet" },
"555666": { "type": "testnet" },
"7210": { "type": "testnet" },
"173750": { "type": "testnet" },
"7222": { "type": "testnet" },
"779672": { "type": "testnet" },
"749": { "type": "testnet" },
"167008": { "type": "testnet" },
"31335": { "type": "testnet" },
"80085": { "type": "testnet" },
"10880": { "type": "testnet" },
"55551": { "type": "testnet" },
"25043": { "type": "testnet" },
"8082": { "type": "testnet" }
}
}

@ -13,6 +13,17 @@ export function mergeChainSettingsWithDefaults(chainId: string, userSettings: Co
let defaultsForVerifier: VerifierSettings
if (verifierId === 'Sourcify') {
defaultsForVerifier = DEFAULT_APIS['Sourcify']
} else if (verifierId === 'Routescan') {
const routescanDefaults = DEFAULT_APIS['Routescan']
if (!routescanDefaults[chainId]) {
defaultsForVerifier = {}
} else {
const explorerUrl = routescanDefaults[chainId]?.type === 'mainnet' ? routescanDefaults.mainnetExplorerUrl : routescanDefaults.testnetExplorerUrl
const apiUrl = routescanDefaults.apiUrl.replace('${CHAIN_TYPE}', routescanDefaults[chainId]?.type).replace('${CHAIN_ID}', chainId)
defaultsForVerifier = { explorerUrl, apiUrl }
}
} else {
defaultsForVerifier = DEFAULT_APIS[verifierId][chainId] ?? {}
}

@ -61,9 +61,9 @@ export const LookupView = () => {
}
const sendToMatomo = async (eventAction: string, eventName: string) => {
await clientInstance.call('matomo' as any, 'track', ['trackEvent', 'ContractVerification', eventAction, eventName]);
await clientInstance.call('matomo' as any, 'track', ['trackEvent', 'ContractVerification', eventAction, eventName])
}
const handleOpenInRemix = async (lookupResponse: LookupResponse) => {
for (const source of lookupResponse.sourceFiles ?? []) {
try {
@ -74,7 +74,7 @@ export const LookupView = () => {
}
try {
await clientInstance.call('fileManager', 'open', lookupResponse.targetFilePath)
await sendToMatomo('lookup', "openInRemix On: " + selectedChain)
await sendToMatomo('lookup', 'openInRemix On: ' + selectedChain)
} catch (err) {
console.error(`Error focusing file ${lookupResponse.targetFilePath}: ${err.message}`)
}
@ -84,20 +84,13 @@ export const LookupView = () => {
<>
<form onSubmit={handleLookup}>
<SearchableChainDropdown label="Chain" id="network-dropdown" selectedChain={selectedChain} setSelectedChain={setSelectedChain} />
<ContractAddressInput
label="Contract Address"
id="contract-address"
contractAddress={contractAddress}
setContractAddress={setContractAddress}
contractAddressError={contractAddressError}
setContractAddressError={setContractAddressError}
/>
<ContractAddressInput label="Contract Address" id="contract-address" contractAddress={contractAddress} setContractAddress={setContractAddress} contractAddressError={contractAddressError} setContractAddressError={setContractAddressError} />
<button type="submit" className="btn w-100 btn-primary" disabled={submitDisabled}>
Lookup
</button>
</form>
<div className="pt-3">
{ chainSettings &&
{chainSettings &&
VERIFIERS.map((verifierId) => {
if (!validConfiguration(chainSettings, verifierId)) {
return (
@ -131,8 +124,9 @@ export const LookupView = () => {
return (
<div key={verifierId} className="pt-4">
<div>
<span className="font-weight-bold">{verifierId}</span> <span className="text-secondary">{chainSettings.verifiers[verifierId].apiUrl}</span>
<div className="d-flex align-items-center">
<span className="font-weight-bold">{verifierId}&nbsp;</span>
<span className="text-secondary d-inline-block text-truncate mw-100">{chainSettings.verifiers[verifierId].apiUrl}</span>
</div>
{!!loadingVerifiers[verifierId] && (
<div className="pt-2 d-flex justify-content-center">
@ -159,8 +153,7 @@ export const LookupView = () => {
)}
</div>
)
})
}
})}
</div>
</>
)

@ -9,7 +9,7 @@ export const ReceiptsView = () => {
return (
<div>
{contracts.length > 0 ? contracts.map((contract, index) => (
<AccordionReceipt contract={contract} index={index} />
<AccordionReceipt key={contract.id} contract={contract} index={index} />
)) : <div className="text-center mt-5" data-id="noContractsSubmitted">No contracts submitted for verification</div>}
</div>
)

@ -47,6 +47,12 @@ export const SettingsView = () => {
<span className="font-weight-bold">Blockscout - {selectedChain.name}</span>
<ConfigInput label="Instance URL" id="blockscout-api-url" secret={false} initialValue={chainSettings.verifiers['Blockscout']?.apiUrl ?? ''} saveResult={(result) => handleChange('Blockscout', 'apiUrl', result)} />
</div>
<div className="p-2 my-2 border">
<span className="font-weight-bold">Routescan - {selectedChain.name}</span>
<ConfigInput label="API Key (optional)" id="routescan-api-key" secret={true} initialValue={chainSettings.verifiers['Routescan']?.apiKey ?? ''} saveResult={(result) => handleChange('Routescan', 'apiKey', result)} />
<ConfigInput label="API URL" id="routescan-api-url" secret={false} initialValue={chainSettings.verifiers['Routescan']?.apiUrl ?? ''} saveResult={(result) => handleChange('Routescan', 'apiUrl', result)} />
<ConfigInput label="Explorer URL" id="routescan-explorer-url" secret={false} initialValue={chainSettings.verifiers['Routescan']?.explorerUrl ?? ''} saveResult={(result) => handleChange('Routescan', 'explorerUrl', result)} />
</div>
</div>
)}
</>

@ -41,14 +41,13 @@ export const VerifyView = () => {
setEnabledVerifiers({ ...enabledVerifiers, [verifierId]: checked })
}
const sendToMatomo = async (eventAction: string, eventName: string) => {
await clientInstance.call("matomo" as any, 'track', ['trackEvent', 'ContractVerification', eventAction, eventName]);
}
const handleVerify = async (e) => {
e.preventDefault()
const { triggerFilePath, filePath, contractName } = selectedContract
const compilerAbstract = compilationOutput[triggerFilePath]
if (!compilerAbstract) {
@ -68,9 +67,7 @@ export const VerifyView = () => {
name: verifierId as VerifierIdentifier,
}
receipts.push({ verifierInfo, status: 'pending', contractId, isProxyReceipt: false, failedChecks: 0 })
if (enabledVerifiers.Blockscout) await sendToMatomo('verify', "verifyWith: Blockscout On: " + selectedChain?.chainId + " IsProxy: " + (hasProxy && !proxyAddress))
if (enabledVerifiers.Etherscan) await sendToMatomo('verify', "verifyWithEtherscan On: " + selectedChain?.chainId + " IsProxy: " + (hasProxy && !proxyAddress))
if (enabledVerifiers.Sourcify) await sendToMatomo('verify', "verifyWithSourcify On: " + selectedChain?.chainId + " IsProxy: " + (hasProxy && !proxyAddress))
await sendToMatomo('verify', `verifyWith${verifierId} On: ${selectedChain?.chainId} IsProxy: ${hasProxy && proxyAddress}`)
}
const newSubmittedContract: SubmittedContract = {
@ -256,7 +253,7 @@ export const VerifyView = () => {
</span>
</CustomTooltip>
) : (
<span className="text-secondary">{chainSettings.verifiers[verifierId].apiUrl}</span>
<span className="text-secondary d-inline-block text-truncate mw-100">{chainSettings.verifiers[verifierId].apiUrl}</span>
)}
</div>
</div>
@ -269,7 +266,7 @@ export const VerifyView = () => {
!selectedContract ? "Please select the contract (compile if needed)." :
((hasProxy && !!proxyAddressError) || (hasProxy && !proxyAddress)) ? "Please provide a valid proxy contract address." :
"Please provide all necessary data to verify") // Is not expected to be a case
: "Verify with selected tools"}>
: "Verify with selected tools"}>
<button type="submit" className="w-100 btn btn-primary mt-3" disabled={submitDisabled}>
Verify
</button>

@ -21,7 +21,7 @@ module.exports = {
'default': {
globals: {
waitForConditionTimeout: 10000,
asyncHookTimeout: 120000
asyncHookTimeout: 10000000
},
screenshots: {
enabled: true,

Loading…
Cancel
Save