-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add deploy and verify contract #6394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
b466ae3
2e6306d
7cc59ee
5cac7aa
04ba18b
1ab13fc
7be76eb
ea3b96a
d753a8f
03f53b0
fd89f8b
a030af0
d01568e
3e11d81
8eb4b36
c9e757b
3e61f03
1e8b2ca
37817d8
c4e1ad9
a97b17a
c61dd05
6a9f89e
07a3278
0a3dad1
73b0d35
9694dae
cb610dd
ea96642
2c034cf
faaf384
74f3812
4dbdbbe
833e44f
07a64a9
f70f839
7c7e2b3
6535bf8
838dca0
74d313e
23843ca
7a5b8cc
93e169b
faca71e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,18 @@ | ||
import { PluginClient } from '@remixproject/plugin' | ||
import { createClient } from '@remixproject/plugin-webview' | ||
|
||
import EventManager from 'events' | ||
import { VERIFIERS, type ChainSettings, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier } from './types' | ||
import { VERIFIERS, type ChainSettings,Chain, type ContractVerificationSettings, type LookupResponse, type VerifierIdentifier, SubmittedContract, SubmittedContracts, VerificationReceipt } from './types' | ||
import { mergeChainSettingsWithDefaults, validConfiguration } from './utils' | ||
import { getVerifier } from './Verifiers' | ||
import { CompilerAbstract } from '@remix-project/remix-solidity' | ||
|
||
export class ContractVerificationPluginClient extends PluginClient { | ||
public internalEvents: EventManager | ||
|
||
constructor() { | ||
super() | ||
this.methods = ['lookupAndSave'] | ||
this.methods = ['lookupAndSave', 'verifyOnDeploy'] | ||
this.internalEvents = new EventManager() | ||
createClient(this) | ||
this.onload() | ||
|
@@ -62,8 +64,156 @@ export class ContractVerificationPluginClient extends PluginClient { | |
} | ||
} | ||
|
||
verifyOnDeploy = async (data: any): Promise<void> => { | ||
try { | ||
await this.call('terminal', 'log', { type: 'log', value: 'Verification process started...' }) | ||
|
||
const { chainId, currentChain, contractAddress, contractName, compilationResult, constructorArgs, etherscanApiKey } = data | ||
|
||
if (!currentChain) { | ||
await this.call('terminal', 'log', { type: 'error', value: 'Chain data was not provided for verification.' }) | ||
return | ||
} | ||
|
||
const userSettings = this.getUserSettingsFromLocalStorage() | ||
|
||
if (etherscanApiKey) { | ||
if (!userSettings.chains[chainId]) { | ||
userSettings.chains[chainId] = { verifiers: {} } | ||
} | ||
|
||
if (!userSettings.chains[chainId].verifiers.Etherscan) { | ||
userSettings.chains[chainId].verifiers.Etherscan = {} | ||
} | ||
userSettings.chains[chainId].verifiers.Etherscan.apiKey = etherscanApiKey | ||
|
||
if (!userSettings.chains[chainId].verifiers.Routescan) { | ||
userSettings.chains[chainId].verifiers.Routescan = {} | ||
} | ||
if (!userSettings.chains[chainId].verifiers.Routescan.apiKey){ | ||
userSettings.chains[chainId].verifiers.Routescan.apiKey = "placeholder" | ||
} | ||
|
||
window.localStorage.setItem("contract-verification:settings", JSON.stringify(userSettings)) | ||
|
||
} | ||
|
||
const submittedContracts: SubmittedContracts = JSON.parse(window.localStorage.getItem('contract-verification:submitted-contracts') || '{}') | ||
|
||
const filePath = Object.keys(compilationResult.data.contracts).find(path => | ||
compilationResult.data.contracts[path][contractName] | ||
) | ||
if (!filePath) throw new Error(`Could not find file path for contract ${contractName}`) | ||
|
||
const submittedContract: SubmittedContract = { | ||
id: `${chainId}-${contractAddress}`, | ||
address: contractAddress, | ||
chainId: chainId, | ||
filePath: filePath, | ||
contractName: contractName, | ||
abiEncodedConstructorArgs: constructorArgs, | ||
date: new Date().toISOString(), | ||
receipts: [] | ||
} | ||
|
||
const compilerAbstract: CompilerAbstract = compilationResult | ||
const chainSettings = mergeChainSettingsWithDefaults(chainId, userSettings) | ||
|
||
if (validConfiguration(chainSettings, 'Sourcify')) { | ||
await this._verifyWithProvider('Sourcify', submittedContract, compilerAbstract, chainId, chainSettings) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you need to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was done intentionally to ensure that the logs are output sequentially and to avoid potential race conditions when creating the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
yes that makes sense. |
||
} | ||
|
||
if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.toLowerCase().includes('routescan'))) { | ||
await this._verifyWithProvider('Routescan', submittedContract, compilerAbstract, chainId, chainSettings) | ||
} | ||
|
||
if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.toLowerCase().includes('blockscout'))) { | ||
await this._verifyWithProvider('Blockscout', submittedContract, compilerAbstract, chainId, chainSettings) | ||
} | ||
|
||
if (currentChain.explorers && currentChain.explorers.some(explorer => explorer.name.toLowerCase().includes('etherscan'))) { | ||
if (etherscanApiKey) { | ||
if (!chainSettings.verifiers.Etherscan) chainSettings.verifiers.Etherscan = {} | ||
chainSettings.verifiers.Etherscan.apiKey = etherscanApiKey | ||
await this._verifyWithProvider('Etherscan', submittedContract, compilerAbstract, chainId, chainSettings) | ||
} else { | ||
await this.call('terminal', 'log', { type: 'warn', value: 'Etherscan verification skipped: API key not found in global Settings.' }) | ||
} | ||
} | ||
|
||
submittedContracts[submittedContract.id] = submittedContract | ||
|
||
window.localStorage.setItem('contract-verification:submitted-contracts', JSON.stringify(submittedContracts)) | ||
this.internalEvents.emit('submissionUpdated') | ||
} catch (error) { | ||
await this.call('terminal', 'log', { type: 'error', value: `An unexpected error occurred during verification: ${error.message}` }) | ||
} | ||
} | ||
|
||
private _verifyWithProvider = async ( | ||
providerName: VerifierIdentifier, | ||
submittedContract: SubmittedContract, | ||
compilerAbstract: CompilerAbstract, | ||
chainId: string, | ||
chainSettings: ChainSettings | ||
): Promise<void> => { | ||
let receipt: VerificationReceipt | ||
const verifierSettings = chainSettings.verifiers[providerName] | ||
const verifier = getVerifier(providerName, verifierSettings) | ||
|
||
try { | ||
if (validConfiguration(chainSettings, providerName)) { | ||
|
||
await this.call('terminal', 'log', { type: 'log', value: `Verifying with ${providerName}...` }) | ||
|
||
if (providerName === 'Etherscan' || providerName === 'Routescan' || providerName === 'Blockscout') { | ||
await new Promise(resolve => setTimeout(resolve, 10000)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why do you need to wait here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is a delay between the contract deployment and the time when the block explorer recognizes the deployed information. If the verification script runs too soon, the explorer may not yet have indexed the deployed contract, resulting in an “Unable to locate ContractCode” error. |
||
} | ||
|
||
if (verifier && typeof verifier.verify === 'function') { | ||
const result = await verifier.verify(submittedContract, compilerAbstract) | ||
|
||
receipt = { | ||
receiptId: result.receiptId || undefined, | ||
verifierInfo: { name: providerName, apiUrl: verifier.apiUrl }, | ||
status: result.status, | ||
message: result.message, | ||
lookupUrl: result.lookupUrl, | ||
contractId: submittedContract.id, | ||
isProxyReceipt: false, | ||
failedChecks: 0 | ||
} | ||
|
||
const successMessage = `${providerName} verification successful.` | ||
await this.call('terminal', 'log', { type: 'info', value: successMessage }) | ||
|
||
if (result.lookupUrl) { | ||
const textMessage = `${result.lookupUrl}` | ||
await this.call('terminal', 'log', { type: 'info', value: textMessage }) | ||
} | ||
} else { | ||
throw new Error(`${providerName} verifier is not properly configured or does not support direct verification.`) | ||
} | ||
} | ||
} catch (e) { | ||
receipt = { | ||
verifierInfo: { name: providerName, apiUrl: verifier?.apiUrl || 'N/A' }, | ||
status: 'failed', | ||
message: e.message, | ||
contractId: submittedContract.id, | ||
isProxyReceipt: false, | ||
failedChecks: 0 | ||
} | ||
await this.call('terminal', 'log', { type: 'error', value: `${providerName} verification failed: ${e.message}` }) | ||
} finally { | ||
if (receipt) { | ||
submittedContract.receipts.push(receipt) | ||
} | ||
} | ||
} | ||
|
||
private getUserSettingsFromLocalStorage(): ContractVerificationSettings { | ||
const fallbackSettings = { chains: {} }; | ||
const fallbackSettings = { chains: {} } | ||
try { | ||
const settings = window.localStorage.getItem("contract-verification:settings") | ||
return settings ? JSON.parse(settings) : fallbackSettings | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
'use strict' | ||
import { NightwatchBrowser } from 'nightwatch' | ||
import init from '../helpers/init' | ||
|
||
declare global { | ||
interface Window { testplugin: { name: string, url: string }; } | ||
} | ||
|
||
module.exports = { | ||
'@disabled': true, | ||
before: function (browser: NightwatchBrowser, done: VoidFunction) { | ||
init(browser, done, null) | ||
}, | ||
|
||
'Should NOT display the "Verify Contract" checkbox on an unsupported network (Remix VM) #group1': function (browser: NightwatchBrowser) { | ||
browser | ||
.waitForElementVisible('*[data-id="remixIdeSidePanel"]') | ||
.clickLaunchIcon('filePanel') | ||
.click('*[data-id="treeViewLitreeViewItemcontracts"]') | ||
.openFile('contracts/1_Storage.sol') | ||
.clickLaunchIcon('udapp') | ||
.waitForElementVisible('*[data-id="Deploy - transact (not payable)"]') | ||
.waitForElementNotPresent({ | ||
selector: '#deployAndRunVerifyContract', | ||
timeout: 5000 | ||
}) | ||
.end() | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be nice to have receipts for submittedContract and store it in local storage, such that it also shows up in the plugin's UI. See the
VerifyView
for reference.I also thought of refactoring the
VerifyView
once, since a lot of the logic from there could actually be reused for this feature.Anyway, very nice that you tackled this feature!