fetch from etherscan while debugging

pull/2122/head
yann300 3 years ago
parent 77528e58ce
commit 92ce47364f
  1. 3
      apps/remix-ide/src/app/plugins/config.ts
  2. 52
      libs/remix-core-plugin/src/lib/compiler-fetch-and-compile.ts
  3. 57
      libs/remix-core-plugin/src/lib/helpers/fetch-etherscan.ts
  4. 46
      libs/remix-core-plugin/src/lib/helpers/fetch-sourcify.ts
  5. 8
      libs/remix-solidity/src/compiler/compiler-input.ts
  6. 2
      libs/remix-ui/editor/src/lib/remix-ui-editor.tsx
  7. 21
      libs/remix-ui/settings/src/lib/constants.ts
  8. 58
      libs/remix-ui/settings/src/lib/remix-ui-settings.tsx
  9. 8
      libs/remix-ui/settings/src/lib/settingsAction.ts

@ -18,7 +18,8 @@ export class ConfigPlugin extends Plugin {
const queryParams = new QueryParams()
const params = queryParams.get()
const config = Registry.getInstance().get('config').api
const param = params[name] ? params[name] : config.get(name)
let param = params[name] ? params[name] : config.get(name)
param = param ? param : config.get('settings/' + name)
if (param === 'true') return true
if (param === 'false') return false
return param

@ -3,6 +3,8 @@ import { Plugin } from '@remixproject/engine'
import { compile } from '@remix-project/remix-solidity'
import { util } from '@remix-project/remix-lib'
import { toChecksumAddress } from 'ethereumjs-util'
import { fetchContractFromEtherscan } from './helpers/fetch-etherscan'
import { fetchContractFromSourcify } from './helpers/fetch-sourcify'
const profile = {
name: 'fetchAndCompile',
@ -68,48 +70,28 @@ export class FetchAndCompile extends Plugin {
let data
try {
data = await this.call('sourcify', 'fetchByNetwork', contractAddress, network.id)
data = await fetchContractFromSourcify(this, network, contractAddress, targetPath)
} catch (e) {
setTimeout(_ => this.emit('notFound', contractAddress), 0) // plugin framework returns a time out error although it actually didn't find the source...
this.unresolvedAddresses.push(contractAddress)
return localCompilation()
}
if (!data || !data.metadata) {
setTimeout(_ => this.emit('notFound', contractAddress), 0)
this.unresolvedAddresses.push(contractAddress)
return localCompilation()
console.log(e) // and fallback to getting the compilation result from etherscan
}
// set the solidity contract code using metadata
await this.call('fileManager', 'setFile', `${targetPath}/${network.id}/${contractAddress}/metadata.json`, JSON.stringify(data.metadata, null, '\t'))
const compilationTargets = {}
for (let file in data.metadata.sources) {
const urls = data.metadata.sources[file].urls
for (const url of urls) {
if (url.includes('ipfs')) {
const stdUrl = `ipfs://${url.split('/')[2]}`
const source = await this.call('contentImport', 'resolve', stdUrl)
if (await this.call('contentImport', 'isExternalUrl', file)) {
// nothing to do, the compiler callback will handle those
} else {
file = file.replace('browser/', '') // should be fixed in the remix IDE end.
const path = `${targetPath}/${network.id}/${contractAddress}/${file}`
await this.call('fileManager', 'setFile', path, source.content)
compilationTargets[path] = { content: source.content }
}
break
}
if (!data) {
try {
data = await fetchContractFromEtherscan(this, network, contractAddress, targetPath)
} catch (e) {
setTimeout(_ => this.emit('notFound', contractAddress), 0) // plugin framework returns a time out error although it actually didn't find the source...
this.unresolvedAddresses.push(contractAddress)
return localCompilation()
}
}
// compile
const settings = {
version: data.metadata.compiler.version,
language: data.metadata.language,
evmVersion: data.metadata.settings.evmVersion,
optimize: data.metadata.settings.optimizer.enabled,
runs: data.metadata.settings.optimizer.runs
if (!data) {
setTimeout(_ => this.emit('notFound', contractAddress), 0)
this.unresolvedAddresses.push(contractAddress)
return localCompilation()
}
const { settings, compilationTargets } = data
try {
setTimeout(_ => this.emit('compiling', settings), 0)
const compData = await compile(

@ -0,0 +1,57 @@
export const fetchContractFromEtherscan = async (plugin, network, contractAddress, targetPath) => {
let data
const compilationTargets = {}
const etherscanKey = await plugin.call('config', 'getAppParameter', 'etherscan-access-token')
if (etherscanKey) {
const endpoint = network.id == 1 ? 'api.etherscan.io' : 'api-' + network.name + '.etherscan.io'
data = await fetch('https://' + endpoint + '/api?module=contract&action=getsourcecode&address=' + contractAddress + '&apikey=' + etherscanKey)
data = await data.json()
if (data.message !== 'OK') throw new Error('unable to retrieve contract data ' + data.message)
if (data.result.length) {
if (data.result[0].SourceCode === '') throw new Error('contract not verified')
if (data.result[0].SourceCode.startsWith('{')) {
data.result[0].SourceCode = JSON.parse(data.result[0].SourceCode.replace(/(?:\r\n|\r|\n)/g, '').replace(/^{{/,'{').replace(/}}$/,'}'))
}
}
}
if (!data || !data.result) {
return null
}
if (typeof data.result[0].SourceCode === 'string') {
const fileName = `${targetPath}/${network.id}/${contractAddress}/${data.result[0].ContractName}.sol`
await plugin.call('fileManager', 'setFile', fileName , data.result[0].SourceCode)
compilationTargets[fileName] = { content: data.result[0].SourceCode }
} else if (typeof data.result[0].SourceCode == 'object') {
const sources = data.result[0].SourceCode.sources
for (let [file, source] of Object.entries(sources)) { // eslint-disable-line
file = file.replace('browser/', '') // should be fixed in the remix IDE end.
file = file.replace(/^\//g, '') // remove first slash.
if (await plugin.call('contentImport', 'isExternalUrl', file)) {
// nothing to do, the compiler callback will handle those
} else {
const path = `${targetPath}/${network.id}/${contractAddress}/${file}`
const content = (source as any).content
await plugin.call('fileManager', 'setFile', path, content)
compilationTargets[path] = { content }
}
}
}
let runs = 0
try {
runs = parseInt(data.result[0].Runs)
} catch (e) {}
const settings = {
version: data.result[0].CompilerVersion.replace(/^v/, ''),
language: 'Solidity',
evmVersion: data.result[0].EVMVersion.toLowerCase(),
optimize: data.result[0].OptimizationUsed === '1',
runs
}
return {
settings,
compilationTargets
}
}

@ -0,0 +1,46 @@
export const fetchContractFromSourcify = async (plugin, network, contractAddress, targetPath) => {
let data
const compilationTargets = {}
try {
data = await plugin.call('sourcify', 'fetchByNetwork', contractAddress, network.id)
} catch (e) {
console.log(e) // and fallback to getting the compilation result from etherscan
}
if (!data || !data.metadata) {
return null
}
// set the solidity contract code using metadata
await plugin.call('fileManager', 'setFile', `${targetPath}/${network.id}/${contractAddress}/metadata.json`, JSON.stringify(data.metadata, null, '\t'))
for (let file in data.metadata.sources) {
const urls = data.metadata.sources[file].urls
for (const url of urls) {
if (url.includes('ipfs')) {
const stdUrl = `ipfs://${url.split('/')[2]}`
const source = await plugin.call('contentImport', 'resolve', stdUrl)
file = file.replace('browser/', '') // should be fixed in the remix IDE end.
if (await plugin.call('contentImport', 'isExternalUrl', file)) {
// nothing to do, the compiler callback will handle those
} else {
const path = `${targetPath}/${network.id}/${contractAddress}/${file}`
await plugin.call('fileManager', 'setFile', path, source.content)
compilationTargets[path] = { content: source.content }
}
break
}
}
}
const settings = {
version: data.metadata.compiler.version,
language: data.metadata.language,
evmVersion: data.metadata.settings.evmVersion,
optimize: data.metadata.settings.optimizer.enabled,
runs: data.metadata.settings.optimizer.runs
}
return {
settings,
compilationTargets
}
}

@ -19,9 +19,13 @@ export default (sources: Source, opts: CompilerInputOptions): string => {
}
}
}
}
}
if (opts.evmVersion) {
o.settings.evmVersion = opts.evmVersion
if (opts.evmVersion.toLowerCase() == 'default') {
opts.evmVersion = null
} else {
o.settings.evmVersion = opts.evmVersion
}
}
if (opts.language) {
o.language = opts.language

@ -263,7 +263,7 @@ export const EditorUI = (props: EditorUIProps) => {
range: new monacoRef.current.Range(decoration.position.start.line + 1, decoration.position.start.column + 1, decoration.position.end.line + 1, decoration.position.end.column + 1),
options: {
isWholeLine,
inlineClassName: `alert-info border-0 highlightLine${decoration.position.start.line + 1}`
linesDecorationsClassName: `alert-info border-0 selectionHighlight highlightLine${decoration.position.start.line + 1}`
}
}
}

@ -2,13 +2,34 @@ export const generateContractMetadataText = 'Generate contract metadata. Generat
export const textSecondary = 'text-secondary'
export const textDark = 'text-dark'
export const warnText = 'Be sure the endpoint is opened before enabling it. \nThis mode allows a user to provide a passphrase in the Remix interface without having to unlock the account. Although this is very convenient, you should completely trust the backend you are connected to (Geth, Parity, ...). Remix never persists any passphrase'.split('\n').map(s => s.trim()).join(' ')
export const gitAccessTokenTitle = 'Github Access Token'
export const gitAccessTokenText = 'Manage the access token used to publish to Gist and retrieve Github contents.'
export const gitAccessTokenText2 = 'Go to github token page (link below) to create a new token and save it in Remix. Make sure this token has only \'create gist\' permission.'
export const gitAccessTokenLink = 'https://github.com/settings/tokens'
export const etherscanTokenTitle = 'EtherScan Access Token'
export const etherscanTokenLink = 'https://etherscan.io/myapikey'
export const etherscanAccessTokenText = 'Manage the access token used to interact with Etherscan.'
export const etherscanAccessTokenText2 = 'Go to etherscan token page (link below) to create a new token and save it in Remix.'
export const ethereunVMText = 'Always use Javascript VM at load'
export const wordWrapText = 'Word wrap in editor'
export const enablePersonalModeText = ' Enable Personal Mode for web3 provider. Transaction sent over Web3 will use the web3.personal API.\n'
export const matomoAnalytics = 'Enable Matomo Analytics. We do not collect personally identifiable information (PII). The info is used to improve the site’s UX & UI. See more about '
export const swarmSettingsTitle = 'Swarm Settings'
export const swarmSettingsText = 'Swarm Settings'
export const labels = {
'gist': {
'link': gitAccessTokenLink,
'title': gitAccessTokenTitle,
'message1': gitAccessTokenText,
'message2': gitAccessTokenText2,
'key': 'gist-access-token'
},
'etherscan': {
'link': etherscanTokenLink,
'title': etherscanTokenTitle,
'message1':etherscanAccessTokenText,
'message2':etherscanAccessTokenText2,
'key': 'etherscan-access-token'
}
}

@ -1,7 +1,7 @@
import React, { useState, useReducer, useEffect, useCallback } from 'react' // eslint-disable-line
import { CopyToClipboard } from '@remix-ui/clipboard' // eslint-disable-line
import { enablePersonalModeText, ethereunVMText, generateContractMetadataText, gitAccessTokenLink, gitAccessTokenText, gitAccessTokenText2, gitAccessTokenTitle, matomoAnalytics, swarmSettingsTitle, textDark, textSecondary, warnText, wordWrapText } from './constants'
import { enablePersonalModeText, ethereunVMText, labels, generateContractMetadataText, matomoAnalytics, textDark, textSecondary, warnText, wordWrapText, swarmSettingsTitle } from './constants'
import './remix-ui-settings.css'
import { ethereumVM, generateContractMetadat, personal, textWrapEventAction, useMatomoAnalytics, saveTokenToast, removeTokenToast, saveSwarmSettingsToast } from './settingsAction'
@ -21,19 +21,28 @@ export interface RemixUiSettingsProps {
export const RemixUiSettings = (props: RemixUiSettingsProps) => {
const [, dispatch] = useReducer(settingReducer, initialState)
const [state, dispatchToast] = useReducer(toastReducer, toastInitialState)
const [tokenValue, setTokenValue] = useState('')
const [tokenValue, setTokenValue] = useState({})
const [themeName, ] = useState('')
const [privateBeeAddress, setPrivateBeeAddress] = useState('')
const [postageStampId, setPostageStampId] = useState('')
useEffect(() => {
const token = props.config.get('settings/gist-access-token')
const token = props.config.get('settings/' + labels['gist'].key)
if (token === undefined) {
props.config.set('settings/generate-contract-metadata', true)
dispatch({ type: 'contractMetadata', payload: { name: 'contractMetadata', isChecked: true, textClass: textDark } })
}
if (token) {
setTokenValue(token)
setTokenValue(prevState => {
return { ...prevState, gist: token }
})
}
const etherscantoken = props.config.get('settings/' + labels['etherscan'].key)
if (etherscantoken) {
setTokenValue(prevState => {
return { ...prevState, etherscan: etherscantoken }
})
}
const configPrivateBeeAddress = props.config.get('settings/swarm-private-bee-address')
if (configPrivateBeeAddress) {
@ -125,42 +134,48 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
)
}
const saveToken = () => {
saveTokenToast(props.config, dispatchToast, tokenValue)
// api key settings
const saveToken = (type: string) => {
saveTokenToast(props.config, dispatchToast, tokenValue[type], labels[type].key)
}
const removeToken = () => {
setTokenValue('')
removeTokenToast(props.config, dispatchToast)
const removeToken = (type: string) => {
setTokenValue(prevState => {
return { ...prevState, type: ''}
})
removeTokenToast(props.config, dispatchToast, labels[type].key)
}
const handleSaveTokenState = useCallback(
(event) => {
setTokenValue(event.target.value)
(event, type) => {
setTokenValue(prevState => {
return { ...prevState, [type]: event.target.value}
})
},
[tokenValue]
)
const gistToken = () => (
const token = (type: string) => (
<div className="border-top">
<div className="card-body pt-3 pb-2">
<h6 className="card-title">{ gitAccessTokenTitle }</h6>
<p className="mb-1">{ gitAccessTokenText }</p>
<p className="">{ gitAccessTokenText2 }</p>
<p className="mb-1"><a className="text-primary" target="_blank" href="https://github.com/settings/tokens">{ gitAccessTokenLink }</a></p>
<h6 className="card-title">{ labels[type].title }</h6>
<p className="mb-1">{ labels[type].message1 }</p>
<p className="">{ labels[type].message2 }</p>
<p className="mb-1"><a className="text-primary" target="_blank" href={labels[type].link}>{ labels[type].link }</a></p>
<div className=""><label>TOKEN:</label>
<div className="text-secondary mb-0 h6">
<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" className="form-control" onChange={handleSaveTokenState} value={ tokenValue } />
<input id="gistaccesstoken" data-id="settingsTabGistAccessToken" type="password" className="form-control" onChange={(e) => handleSaveTokenState(e, type)} value={ tokenValue[type] } />
<div className="d-flex justify-content-end pt-2">
<CopyToClipboard content={tokenValue} data-id='copyToClipboardCopyIcon' />
<input className="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onClick={() => saveToken()} value="Save" type="button" disabled={tokenValue === ''}></input>
<button className="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete Github access token" onClick={() => removeToken()}>Remove</button>
<input className="btn btn-sm btn-primary ml-2" id="savegisttoken" data-id="settingsTabSaveGistToken" onClick={() => saveToken(type)} value="Save" type="button" disabled={tokenValue === ''}></input>
<button className="btn btn-sm btn-secondary ml-2" id="removegisttoken" data-id="settingsTabRemoveGistToken" title="Delete Github access token" onClick={() => removeToken('gist')}>Remove</button>
</div>
</div></div>
</div>
</div>
)
// swarm settings
const handleSavePrivateBeeAddress = useCallback(
(event) => {
setPrivateBeeAddress(event.target.value)
@ -206,8 +221,9 @@ export const RemixUiSettings = (props: RemixUiSettingsProps) => {
return (
<div>
{state.message ? <Toaster message= {state.message}/> : null}
{generalConfig()}
{gistToken()}
{generalConfig()}
{token('gist')}
{token('etherscan')}
{swarmSettings()}
<RemixUiThemeModule themeModule={props._deps.themeModule} />
</div>

@ -41,13 +41,13 @@ export const useMatomoAnalytics = (config, checked, dispatch) => {
}
}
export const saveTokenToast = (config, dispatch, tokenValue) => {
config.set('settings/gist-access-token', tokenValue)
export const saveTokenToast = (config, dispatch, tokenValue, key) => {
config.set('settings/' + key, tokenValue)
dispatch({ type: 'save', payload: { message: 'Access token has been saved' } })
}
export const removeTokenToast = (config, dispatch) => {
config.set('settings/gist-access-token', '')
export const removeTokenToast = (config, dispatch, key) => {
config.set('settings/' + key, '')
dispatch({ type: 'removed', payload: { message: 'Access token removed' } })
}

Loading…
Cancel
Save