diff --git a/apps/remix-ide/src/app/compiler/compiler-helpers.js b/apps/remix-ide/src/app/compiler/compiler-helpers.js index d1f8399607..db7d5988f3 100644 --- a/apps/remix-ide/src/app/compiler/compiler-helpers.js +++ b/apps/remix-ide/src/app/compiler/compiler-helpers.js @@ -3,10 +3,10 @@ import { canUseWorker, urlFromVersion } from './compiler-utils' import { Compiler } from '@remix-project/remix-solidity' import CompilerAbstract from './compiler-abstract' -export const compile = async (compilationTargets, settings) => { +export const compile = async (compilationTargets, settings, contentResolverCallback) => { const res = await (() => { return new Promise((resolve, reject) => { - const compiler = new Compiler(() => {}) + const compiler = new Compiler(contentResolverCallback) compiler.set('evmVersion', settings.evmVersion) compiler.set('optimize', settings.optimize) compiler.set('language', settings.language) diff --git a/apps/remix-ide/src/app/compiler/compiler-imports.js b/apps/remix-ide/src/app/compiler/compiler-imports.js index 18da5fa8e9..4db80049fc 100644 --- a/apps/remix-ide/src/app/compiler/compiler-imports.js +++ b/apps/remix-ide/src/app/compiler/compiler-imports.js @@ -12,7 +12,7 @@ const profile = { name: 'contentImport', displayName: 'content import', version: packageJson.version, - methods: ['resolve', 'resolveAndSave'] + methods: ['resolve', 'resolveAndSave', 'isExternalUrl'] } module.exports = class CompilerImports extends Plugin { @@ -30,6 +30,11 @@ module.exports = class CompilerImports extends Plugin { return /^([^/]+)/.exec(url) } + isExternalUrl (url) { + const handlers = this.handlers() + return handlers.some(handler => handler.match.exec(url)) + } + /** * resolve the content of @arg url. This only resolves external URLs. * diff --git a/apps/remix-ide/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js b/apps/remix-ide/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js index 60b8cacc78..5748cea971 100644 --- a/apps/remix-ide/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js +++ b/apps/remix-ide/src/app/compiler/compiler-sourceVerifier-fetchAndCompile.js @@ -92,10 +92,14 @@ export default class FetchAndCompile extends Plugin { if (url.includes('ipfs')) { const stdUrl = `ipfs://${url.split('/')[2]}` const source = await this.call('contentImport', 'resolve', stdUrl) - file = file.replace('browser/', '') // should be fixed in the remix IDE end. - const path = `${targetPath}/${name}/${contractAddress}/${file}` - await this.call('fileManager', 'setFile', path, source.content) - compilationTargets[path] = { content: source.content } + 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}/${name}/${contractAddress}/${file}` + await this.call('fileManager', 'setFile', path, source.content) + compilationTargets[path] = { content: source.content } + } break } } @@ -111,7 +115,10 @@ export default class FetchAndCompile extends Plugin { } try { setTimeout(_ => this.emit('compiling', settings), 0) - const compData = await compile(compilationTargets, settings) + const compData = await compile( + compilationTargets, + settings, + (url, cb) => this.call('contentImport', 'resolveAndSave', url).then((result) => cb(null, result)).catch((error) => cb(error.message))) compilersartefacts.addResolvedContract(contractAddress, compData) return compData } catch (e) { diff --git a/apps/remix-ide/src/app/tabs/debugger-tab.js b/apps/remix-ide/src/app/tabs/debugger-tab.js index f962881370..b471ecb320 100644 --- a/apps/remix-ide/src/app/tabs/debugger-tab.js +++ b/apps/remix-ide/src/app/tabs/debugger-tab.js @@ -140,8 +140,8 @@ class DebuggerTab extends ViewPlugin { fetchContractAndCompile (address, receipt) { const target = (address && remixDebug.traceHelper.isContractCreation(address)) ? receipt.contractAddress : address - - return this.call('fetchAndCompile', 'resolve', target || receipt.contractAddress || receipt.to, '.debug', this.blockchain.web3()) + const targetAddress = target || receipt.contractAddress || receipt.to + return this.call('fetchAndCompile', 'resolve', targetAddress, 'browser/.debug', this.blockchain.web3()) } // debugger () { diff --git a/libs/remix-debug/src/code/breakpointManager.ts b/libs/remix-debug/src/code/breakpointManager.ts index bf8649d55d..1e5abab48b 100644 --- a/libs/remix-debug/src/code/breakpointManager.ts +++ b/libs/remix-debug/src/code/breakpointManager.ts @@ -15,8 +15,7 @@ export class BreakpointManager { solidityProxy breakpoints locationToRowConverter - previousLine - + /** * constructor * @@ -92,22 +91,26 @@ export class BreakpointManager { let previousSourceLocation let currentStep = fromStep + direction let lineHadBreakpoint = false + let initialLine while (currentStep > 0 && currentStep < trace.length) { try { previousSourceLocation = sourceLocation sourceLocation = await this.callTree.extractValidSourceLocation(currentStep) } catch (e) { - return console.log('cannot jump to breakpoint ' + e) + console.log('cannot jump to breakpoint ' + e) + currentStep += direction + continue } const lineColumn = await this.locationToRowConverter(sourceLocation) - if (this.previousLine !== lineColumn.start.line) { + if (!initialLine) initialLine = lineColumn + + if (initialLine.start.line !== lineColumn.start.line) { if (direction === -1 && lineHadBreakpoint) { // TODO : improve this when we will build the correct structure before hand lineHadBreakpoint = false if (this.hitLine(currentStep + 1, previousSourceLocation, sourceLocation, trace)) { return } } - this.previousLine = lineColumn.start.line if (this.hasBreakpointAtLine(sourceLocation.file, lineColumn.start.line)) { lineHadBreakpoint = true if (direction === 1 && this.hitLine(currentStep, sourceLocation, previousSourceLocation, trace)) { diff --git a/libs/remix-debug/src/debugger/debugger.ts b/libs/remix-debug/src/debugger/debugger.ts index 9117a888bc..477f875ca2 100644 --- a/libs/remix-debug/src/debugger/debugger.ts +++ b/libs/remix-debug/src/debugger/debugger.ts @@ -82,7 +82,7 @@ export class Debugger { } } var lineColumnPos = this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources) - this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources]) + this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address]) } else { this.event.trigger('newSourceLocation', [null]) } diff --git a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts index b9665ff39a..eadf59723d 100644 --- a/libs/remix-debug/src/solidity-decoder/internalCallTree.ts +++ b/libs/remix-debug/src/solidity-decoder/internalCallTree.ts @@ -224,7 +224,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { // if not, we are in the current scope. // We check in `includeVariableDeclaration` if there is a new local variable in scope for this specific `step` if (tree.includeLocalVariables) { - includeVariableDeclaration(tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) + await includeVariableDeclaration(tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) } previousSourceLocation = sourceLocation step++ @@ -249,32 +249,36 @@ async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, const contractObj = await tree.solidityProxy.contractObjectAt(step) let states = null const generatedSources = getGeneratedSources(tree, scopeId, contractObj) - - const variableDeclaration = resolveVariableDeclaration(tree, sourceLocation, generatedSources) + const variableDeclarations = resolveVariableDeclaration(tree, sourceLocation, generatedSources) // using the vm trace step, the current source location and the ast, // we check if the current vm trace step target a new ast node of type VariableDeclaration // that way we know that there is a new local variable from here. - if (variableDeclaration && !tree.scopes[scopeId].locals[variableDeclaration.name]) { - try { - const stack = tree.traceManager.getStackAt(step) - // the stack length at this point is where the value of the new local variable will be stored. - // so, either this is the direct value, or the offset in memory. That depends on the type. - if (variableDeclaration.name !== '') { - states = tree.solidityProxy.extractStatesDefinitions() - var location = extractLocationFromAstVariable(variableDeclaration) - location = location === 'default' ? 'storage' : location - // we push the new local variable in our tree - tree.scopes[scopeId].locals[variableDeclaration.name] = { - name: variableDeclaration.name, - type: parseType(variableDeclaration.typeDescriptions.typeString, states, contractObj.name, location), - stackDepth: stack.length, - sourceLocation: sourceLocation + if (variableDeclarations && variableDeclarations.length) { + for (const variableDeclaration of variableDeclarations) { + if (variableDeclaration && !tree.scopes[scopeId].locals[variableDeclaration.name]) { + try { + const stack = tree.traceManager.getStackAt(step) + // the stack length at this point is where the value of the new local variable will be stored. + // so, either this is the direct value, or the offset in memory. That depends on the type. + if (variableDeclaration.name !== '') { + states = tree.solidityProxy.extractStatesDefinitions() + var location = extractLocationFromAstVariable(variableDeclaration) + location = location === 'default' ? 'storage' : location + // we push the new local variable in our tree + tree.scopes[scopeId].locals[variableDeclaration.name] = { + name: variableDeclaration.name, + type: parseType(variableDeclaration.typeDescriptions.typeString, states, contractObj.name, location), + stackDepth: stack.length, + sourceLocation: sourceLocation + } + } + } catch (error) { + console.log(error) } } - } catch (error) { - console.log(error) } } + // we check here if we are at the beginning inside a new function. // if that is the case, we have to add to locals tree the inputs and output params const functionDefinition = resolveFunctionDefinition(tree, previousSourceLocation, generatedSources) @@ -351,8 +355,10 @@ function extractVariableDeclarations (ast, astWalker) { const ret = {} astWalker.walkFull(ast, (node) => { if (node.nodeType === 'VariableDeclaration' || node.nodeType === 'YulVariableDeclaration') { - ret[node.src] = node + ret[node.src] = [node] } + const hasChild = node.initialValue && (node.nodeType === 'VariableDeclarationStatement' || node.nodeType === 'YulVariableDeclarationStatement') + if (hasChild) ret[node.initialValue.src] = node.declarations }) return ret } diff --git a/libs/remix-debug/src/trace/traceHelper.ts b/libs/remix-debug/src/trace/traceHelper.ts index aefa8a2957..7de74cd999 100644 --- a/libs/remix-debug/src/trace/traceHelper.ts +++ b/libs/remix-debug/src/trace/traceHelper.ts @@ -15,7 +15,7 @@ export function resolveCalledAddress (vmTraceIndex, trace) { } export function isCallInstruction (step) { - return step.op === 'CALL' || step.op === 'CALLCODE' || step.op === 'CREATE' || step.op === 'DELEGATECALL' + return ['CALL', 'STATICCALL', 'CALLCODE', 'CREATE', 'DELEGATECALL'].includes(step.op) } export function isCreateInstruction (step) { diff --git a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx index 72e79383db..4f45e11f83 100644 --- a/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx +++ b/libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx @@ -74,12 +74,11 @@ export const DebuggerUI = (props: DebuggerUIProps) => { }) }) - debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources) => { + debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address) => { if (!lineColumnPos) return const contracts = await debuggerModule.fetchContractAndCompile( - currentReceipt.contractAddress || currentReceipt.to, + address || currentReceipt.contractAddress || currentReceipt.to, currentReceipt) - if (contracts) { let path = contracts.getSourceName(rawLocation.file) if (!path) { @@ -153,7 +152,8 @@ export const DebuggerUI = (props: DebuggerUIProps) => { offsetToLineColumnConverter: debuggerModule.offsetToLineColumnConverter, compilationResult: async (address) => { try { - return await debuggerModule.fetchContractAndCompile(address, currentReceipt) + const ret = await debuggerModule.fetchContractAndCompile(address, currentReceipt) + return ret } catch (e) { console.error(e) }