fetch and compile before debugging

pull/5370/head
yann300 5 years ago
parent c15c21ba72
commit e97aeca800
  1. 12
      src/app/compiler/compiler-artefacts.js
  2. 129
      src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js
  3. 5
      src/app/compiler/compiler-utils.js
  4. 38
      src/app/tabs/debugger-tab.js
  5. 37
      src/app/tabs/debugger/debuggerUI.js
  6. 7
      src/app/tabs/test-tab.js
  7. 8
      src/remixAppManager.js

@ -33,4 +33,16 @@ module.exports = class CompilerArtefacts extends Plugin {
this.compilersArtefacts['__last'] = new CompilerAbstract(languageVersion, data, source)
})
}
addResolvedContract (address, compilerData) {
this.compilersArtefacts[address] = compilerData
}
isResolved (address) {
return this.compilersArtefacts[address] !== undefined
}
get (key) {
return this.compilersArtefacts[key]
}
}

@ -0,0 +1,129 @@
import { EventEmitter } from 'events'
import { Compiler } from 'remix-solidity'
import { canUseWorker, urlFromVersion } from './compiler-utils'
import CompilerAbstract from './compiler-abstract'
import remixLib from 'remix-lib'
class FetchAndCompile {
constructor () {
this.event = new EventEmitter()
this.compiler = null
this.unresolvedAddresses = []
this.firstResolvedAddress = null
this.sourceVerifierNetWork = ['Main', 'Rinkeby', 'Ropsten', 'Goerli']
}
/**
* Fetch compiliation metadata from source-Verify from a given @arg contractAddress - https://github.com/ethereum/source-verify
* Compile the code using Solidity compiler.
* if no contract address are passed, we default to the first resolved address.
*
* @param {string} contractAddress - Address of the contrac to resolve
* @param {string} compilersartefacts - Object containing a mapping of compilation results (byContractAddress and __last)
* @param {object} pluginAccess - any registered plugin (for making the calls)
* @return {CompilerAbstract} - compilation data targeting the given @arg contractAddress
*/
async resolve (contractAddress, compilersartefacts, pluginAccess, targetPath, web3) {
contractAddress = contractAddress || this.firstResolvedAddress
const localCompilation = () => compilersartefacts.get('__last') ? compilersartefacts.get('__last') : null
const resolved = compilersartefacts.get(contractAddress)
if (resolved) return resolved
if (this.unresolvedAddresses.includes(contractAddress)) return localCompilation()
if (this.firstResolvedAddress) return compilersartefacts.get(this.firstResolvedAddress)
// ^ for the moment we don't add compilation result for each adddress, but just for the root addres ^ . We can add this later.
// usecase is: sometimes when doing an internal call, the only available artifact is the Solidity interface.
// resolving addresses of internal call would allow to step over the source code, even if the declaration was made using an Interface.
let network
try {
network = await pluginAccess.call('network', 'detectNetwork')
} catch (e) {
return localCompilation()
}
if (!network) return localCompilation()
if (!this.sourceVerifierNetWork.includes(network.name)) return localCompilation()
// check if the contract if part of the local compilation result
const codeAtAddress = await web3.eth.getCode(contractAddress)
const compilation = localCompilation()
if (compilation) {
let found = false
compilation.visitContracts((contract) => {
found = remixLib.util.compareByteCode('0x' + contract.object.evm.deployedBytecode.object, codeAtAddress)
return found
})
if (found) {
compilersartefacts.addResolvedContract(contractAddress, compilation)
this.firstResolvedAddress = contractAddress
setTimeout(_ => this.event.emit('usingLocalCompilation', contractAddress), 0)
return compilation
}
}
let name = network.name.toLowerCase()
name === 'main' ? 'mainnet' : name // source-verifier api expect "mainnet" and not "main"
await pluginAccess.call('manager', 'activatePlugin', 'source-verification')
const data = await pluginAccess.call('source-verification', 'fetch', contractAddress, name.toLowerCase())
if (!data || !data.metadata) {
setTimeout(_ => this.event.emit('notFound', contractAddress), 0)
this.unresolvedAddresses.push(contractAddress)
return localCompilation()
}
// set the solidity contract code using metadata
await pluginAccess.call('fileManager', 'setFile', `${targetPath}/${contractAddress}/metadata.json`, JSON.stringify(data.metadata, null, '\t'))
let compilationTargets = {}
for (let file in data.metadata.sources) {
const urls = data.metadata.sources[file].urls
for (let url of urls) {
if (url.includes('ipfs')) {
let stdUrl = `ipfs://${url.split('/')[2]}`
const source = await pluginAccess.call('contentImport', 'resolve', stdUrl)
file = file.replace('browser/', '') // should be fixed in the remix IDE end.
const path = `${targetPath}/${contractAddress}/${file}`
await pluginAccess.call('fileManager', 'setFile', path, source.content)
compilationTargets[path] = { content: source.content }
break
}
}
}
// compile
const settings = {
version: data.metadata.compiler.version,
languageName: data.metadata.language,
evmVersion: data.metadata.settings.evmVersion,
optimize: data.metadata.settings.optimizer.enabled,
compilerUrl: urlFromVersion(data.metadata.compiler.version)
}
return await (() => {
return new Promise((resolve, reject) => {
setTimeout(_ => this.event.emit('compiling', settings), 0)
if (!this.compiler) this.compiler = new Compiler(() => {})
this.compiler.set('evmVersion', settings.evmVersion)
this.compiler.set('optimize', settings.optimize)
this.compiler.loadVersion(canUseWorker(settings.version), settings.compilerUrl)
this.compiler.event.register('compilationFinished', (success, compilationData, source) => {
if (!success) {
this.unresolvedAddresses.push(contractAddress)
setTimeout(_ => this.event.emit('compilationFailed', compilationData), 0)
return resolve(null)
}
const compilerData = new CompilerAbstract(settings.version, compilationData, source)
compilersartefacts.addResolvedContract(contractAddress, compilerData)
this.firstResolvedAddress = contractAddress
resolve(compilerData)
})
this.compiler.event.register('compilerLoaded', (version) => {
this.compiler.compile(compilationTargets, '')
})
})
})()
}
}
const fetchAndCompile = new FetchAndCompile()
export default fetchAndCompile

@ -1,6 +1,11 @@
const semver = require('semver')
/* global Worker */
export const baseUrl = 'https://solc-bin.ethereum.org/bin'
export function urlFromVersion (version) {
return `${baseUrl}/soljson-v${version}.js`
}
/**
* Checks if the worker can be used to load a compiler.
* checks a compiler whitelist, browser support and OS.

@ -1,8 +1,9 @@
var yo = require('yo-yo')
var css = require('./styles/debugger-tab-styles')
var DebuggerUI = require('./debugger/debuggerUI')
const yo = require('yo-yo')
const css = require('./styles/debugger-tab-styles')
var globalRegistry = require('../../global/registry')
import fetchAndCompile from '../compiler/compiler-sourceVerifier-fetchAndCompile'
import toaster from '../ui/tooltip'
const DebuggerUI = require('./debugger/debuggerUI')
import { ViewPlugin } from '@remixproject/engine'
import * as packageJson from '../../../package.json'
@ -34,7 +35,32 @@ class DebuggerTab extends ViewPlugin {
<div class="${css.debuggerTabView}" id="debugView">
<div id="debugger" class="${css.debugger}"></div>
</div>`
this.debuggerUI = new DebuggerUI(this.el.querySelector('#debugger'), this.blockchain)
let compilers = globalRegistry.get('compilersartefacts').api
fetchAndCompile.event.on('compiling', (settings) => {
toaster(yo`<div><b>Recompiling and debugging with params</b><pre>${JSON.stringify(settings, null, '\t')}</pre></div>`)
})
fetchAndCompile.event.on('compilationFailed', (data) => {
toaster(yo`<div><b>Compilation failed...</b> continuing <i>without</i> source code debugging.</div>`)
})
fetchAndCompile.event.on('notFound', (contractAddress) => {
toaster(yo`<div><b>Contract ${contractAddress} not found in source code repository</b> continuing <i>without</i> source code debugging.</div>`)
})
fetchAndCompile.event.on('usingLocalCompilation', (contractAddress) => {
toaster(yo`<div><b>Using compilation result from Solidity module</b></div>`)
})
this.debuggerUI = new DebuggerUI(
this.el.querySelector('#debugger'),
this.blockchain,
adddress => fetchAndCompile.resolve(adddress, compilers, this, '.debug', this.blockchain.web3()))
this.call('manager', 'activatePlugin', 'udapp')
return this.el
}

@ -30,9 +30,10 @@ var css = csjs`
class DebuggerUI {
constructor (container, blockchain) {
constructor (container, blockchain, fetchContractAndCompile) {
this.registry = globalRegistry
this.blockchain = blockchain
this.fetchContractAndCompile = fetchContractAndCompile
this.event = new EventManager()
this.isActive = false
@ -77,8 +78,12 @@ class DebuggerUI {
self.isActive = isActive
})
this.debugger.event.register('newSourceLocation', function (lineColumnPos, rawLocation) {
self.sourceHighlighter.currentSourceLocation(lineColumnPos, rawLocation)
this.debugger.event.register('newSourceLocation', async function (lineColumnPos, rawLocation) {
const contracts = await self.fetchContractAndCompile()
if (contracts) {
const path = contracts.getSourceName(rawLocation.file)
if (path) self.sourceHighlighter.currentSourceLocationFromfileName(lineColumnPos, path)
}
})
this.debugger.event.register('debuggerUnloaded', self.unLoad.bind(this))
@ -122,15 +127,18 @@ class DebuggerUI {
async startDebugging (blockNumber, txNumber, tx) {
if (this.debugger) this.unLoad()
let compilers = this.registry.get('compilersartefacts').api
let lastCompilationResult
if (compilers['__last']) lastCompilationResult = compilers['__last']
let web3 = await this.getDebugWeb3()
this.debugger = new Debugger({
web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api,
compiler: { lastCompilationResult }
compilationResult: async (address) => {
try {
return await this.fetchContractAndCompile(address)
} catch (e) {
console.error(e)
}
return null
}
})
this.listenToEvents()
@ -147,16 +155,19 @@ class DebuggerUI {
getTrace (hash) {
return new Promise(async (resolve, reject) => {
const compilers = this.registry.get('compilersartefacts').api
let lastCompilationResult
if (compilers['__last']) lastCompilationResult = compilers['__last']
const web3 = await this.getDebugWeb3()
const debug = new Debugger({
web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api,
compiler: { lastCompilationResult }
compilationResult: async (address) => {
try {
return await this.fetchContractAndCompile(address)
} catch (e) {
console.error(e)
}
return null
}
})
debug.debugger.traceManager.traceRetriever.getTrace(hash, (error, trace) => {
if (error) return reject(error)

@ -4,7 +4,7 @@ var tooltip = require('../ui/tooltip')
var css = require('./styles/test-tab-styles')
var remixTests = require('remix-tests')
import { ViewPlugin } from '@remixproject/engine'
import { canUseWorker } from '../compiler/compiler-utils'
import { canUseWorker, baseUrl } from '../compiler/compiler-utils'
const TestTabLogic = require('./testTab/testTab')
@ -33,7 +33,6 @@ module.exports = class TestTab extends ViewPlugin {
this.runningTestsNumber = 0
this.readyTestsNumber = 0
this.areTestsRunning = false
this.baseurl = 'https://solc-bin.ethereum.org/bin'
appManager.event.on('activate', (name) => {
if (name === 'solidity') this.updateRunAction(fileManager.currentFile())
})
@ -204,7 +203,7 @@ module.exports = class TestTab extends ViewPlugin {
let runningTest = {}
runningTest[path] = { content }
const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = this.baseurl + '/' + currentVersion
const currentCompilerUrl = baseUrl + '/' + currentVersion
const compilerConfig = {
currentCompilerUrl,
evmVersion,
@ -230,7 +229,7 @@ module.exports = class TestTab extends ViewPlugin {
const runningTest = {}
runningTest[testFilePath] = { content }
const {currentVersion, evmVersion, optimize} = this.compileTab.getCurrentCompilerConfig()
const currentCompilerUrl = this.baseurl + '/' + currentVersion
const currentCompilerUrl = baseUrl + '/' + currentVersion
const compilerConfig = {
currentCompilerUrl,
evmVersion,

@ -10,10 +10,14 @@ const requiredModules = [ // services + layout views + system views
'terminal', 'settings', 'pluginManager']
export function isNative (name) {
const nativePlugins = ['vyper', 'workshops', 'ethdoc', 'etherscan']
const nativePlugins = ['vyper', 'workshops']
return nativePlugins.includes(name) || requiredModules.includes(name)
}
export function canActivate (name) {
return ['manager', 'debugger'].includes(name)
}
export class RemixAppManager extends PluginManager {
constructor (plugins) {
@ -25,7 +29,7 @@ export class RemixAppManager extends PluginManager {
}
async canActivate (from, to) {
return from.name === 'manager'
return canActivate(from.name)
}
async canDeactivate (from, to) {

Loading…
Cancel
Save