fetch and compile before debugging

pull/1/head
yann300 5 years ago
parent 3b36515037
commit bc35cbe1b5
  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) 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') const semver = require('semver')
/* global Worker */ /* 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 if the worker can be used to load a compiler.
* checks a compiler whitelist, browser support and OS. * checks a compiler whitelist, browser support and OS.

@ -1,8 +1,9 @@
var yo = require('yo-yo') const yo = require('yo-yo')
var css = require('./styles/debugger-tab-styles') const css = require('./styles/debugger-tab-styles')
var globalRegistry = require('../../global/registry')
var DebuggerUI = require('./debugger/debuggerUI') import fetchAndCompile from '../compiler/compiler-sourceVerifier-fetchAndCompile'
import toaster from '../ui/tooltip'
const DebuggerUI = require('./debugger/debuggerUI')
import { ViewPlugin } from '@remixproject/engine' import { ViewPlugin } from '@remixproject/engine'
import * as packageJson from '../../../package.json' import * as packageJson from '../../../package.json'
@ -34,7 +35,32 @@ class DebuggerTab extends ViewPlugin {
<div class="${css.debuggerTabView}" id="debugView"> <div class="${css.debuggerTabView}" id="debugView">
<div id="debugger" class="${css.debugger}"></div> <div id="debugger" class="${css.debugger}"></div>
</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 return this.el
} }

@ -30,9 +30,10 @@ var css = csjs`
class DebuggerUI { class DebuggerUI {
constructor (container, blockchain) { constructor (container, blockchain, fetchContractAndCompile) {
this.registry = globalRegistry this.registry = globalRegistry
this.blockchain = blockchain this.blockchain = blockchain
this.fetchContractAndCompile = fetchContractAndCompile
this.event = new EventManager() this.event = new EventManager()
this.isActive = false this.isActive = false
@ -77,8 +78,12 @@ class DebuggerUI {
self.isActive = isActive self.isActive = isActive
}) })
this.debugger.event.register('newSourceLocation', function (lineColumnPos, rawLocation) { this.debugger.event.register('newSourceLocation', async function (lineColumnPos, rawLocation) {
self.sourceHighlighter.currentSourceLocation(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)) this.debugger.event.register('debuggerUnloaded', self.unLoad.bind(this))
@ -122,15 +127,18 @@ class DebuggerUI {
async startDebugging (blockNumber, txNumber, tx) { async startDebugging (blockNumber, txNumber, tx) {
if (this.debugger) this.unLoad() 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() let web3 = await this.getDebugWeb3()
this.debugger = new Debugger({ this.debugger = new Debugger({
web3, web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api, 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() this.listenToEvents()
@ -147,16 +155,19 @@ class DebuggerUI {
getTrace (hash) { getTrace (hash) {
return new Promise(async (resolve, reject) => { 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 web3 = await this.getDebugWeb3()
const debug = new Debugger({ const debug = new Debugger({
web3, web3,
offsetToLineColumnConverter: this.registry.get('offsettolinecolumnconverter').api, 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) => { debug.debugger.traceManager.traceRetriever.getTrace(hash, (error, trace) => {
if (error) return reject(error) if (error) return reject(error)

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

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

Loading…
Cancel
Save