Merge branch 'master' of https://github.com/ethereum/remix-project into mochatest

pull/3166/head
filip mertens 2 years ago
commit 5327e58240
  1. 19
      apps/debugger/src/app/debugger-api.ts
  2. 4
      apps/remix-ide-e2e/src/commands/waitForElementContainsText.ts
  3. 10
      apps/remix-ide-e2e/src/tests/debugger.test.ts
  4. 2
      apps/remix-ide-e2e/src/tests/solidityUnittests.test.ts
  5. 8
      libs/remix-analyzer/package.json
  6. 6
      libs/remix-astwalker/package.json
  7. 13
      libs/remix-core-plugin/src/lib/offset-line-to-column-converter.ts
  8. 10
      libs/remix-debug/package.json
  9. 10
      libs/remix-debug/src/Ethdebugger.ts
  10. 48
      libs/remix-debug/src/debugger/debugger.ts
  11. 2
      libs/remix-debug/src/solidity-decoder/decodeInfo.ts
  12. 242
      libs/remix-debug/src/solidity-decoder/internalCallTree.ts
  13. 4
      libs/remix-debug/src/solidity-decoder/localDecoder.ts
  14. 19
      libs/remix-debug/src/solidity-decoder/solidityProxy.ts
  15. 2
      libs/remix-debug/src/solidity-decoder/types/StringType.ts
  16. 12
      libs/remix-debug/src/source/offsetToLineColumnConverter.ts
  17. 23
      libs/remix-debug/src/source/sourceLocationTracker.ts
  18. 2
      libs/remix-debug/test/decoder/localDecoder.ts
  19. 86
      libs/remix-debug/test/decoder/localsTests/int.ts
  20. 4
      libs/remix-lib/package.json
  21. 6
      libs/remix-simulator/package.json
  22. 6
      libs/remix-solidity/package.json
  23. 2
      libs/remix-solidity/src/compiler/compiler-utils.ts
  24. 12
      libs/remix-tests/package.json
  25. 484
      libs/remix-tests/tests/testRunner.spec.__
  26. 5
      libs/remix-ui/app/src/lib/remix-app/components/modals/matomo.tsx
  27. 13
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  28. 2
      libs/remix-ui/debugger-ui/src/lib/idebugger-api.ts
  29. 10
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx
  30. 6
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger-head.tsx
  31. 4
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/vm-debugger.tsx
  32. 24
      libs/remix-ui/debugger-ui/src/reducers/assembly-items.ts
  33. 10
      libs/remix-ui/solidity-compiler/src/lib/compiler-container.tsx
  34. 2
      libs/remix-ui/solidity-unit-testing/src/lib/solidity-unit-testing.tsx
  35. 4
      libs/remix-url-resolver/package.json
  36. 4
      libs/remix-ws-templates/package.json

@ -1,7 +1,7 @@
import Web3 from 'web3' import Web3 from 'web3'
import remixDebug, { TransactionDebugger as Debugger } from '@remix-project/remix-debug' import remixDebug, { TransactionDebugger as Debugger } from '@remix-project/remix-debug'
import { CompilerAbstract } from '@remix-project/remix-solidity' import { CompilerAbstract } from '@remix-project/remix-solidity'
import { lineText } from '@remix-ui/editor'
export const DebuggerApiMixin = (Base) => class extends Base { export const DebuggerApiMixin = (Base) => class extends Base {
@ -39,10 +39,25 @@ export const DebuggerApiMixin = (Base) => class extends Base {
async discardHighlight () { async discardHighlight () {
await this.call('editor', 'discardHighlight') await this.call('editor', 'discardHighlight')
await this.call('editor', 'discardLineTexts' as any)
} }
async highlight (lineColumnPos, path) { async highlight (lineColumnPos, path, rawLocation, stepDetail, lineGasCost) {
await this.call('editor', 'highlight', lineColumnPos, path, '', { focus: true }) await this.call('editor', 'highlight', lineColumnPos, path, '', { focus: true })
const label = `${stepDetail.op} costs ${stepDetail.gasCost} gas - this line costs ${lineGasCost} gas - ${stepDetail.gas} gas left`
const linetext: lineText = {
content: label,
position: lineColumnPos,
hide: false,
className: 'text-muted small',
afterContentClassName: 'text-muted small fas fa-gas-pump pl-4',
from: 'debugger',
hoverMessage: [{
value: label,
},
],
}
await this.call('editor', 'addLineText' as any, linetext, path)
} }
async getFile (path) { async getFile (path) {

@ -4,8 +4,10 @@ import EventEmitter from 'events'
class WaitForElementContainsText extends EventEmitter { class WaitForElementContainsText extends EventEmitter {
command (this: NightwatchBrowser, id: string, value: string, timeout = 10000): NightwatchBrowser { command (this: NightwatchBrowser, id: string, value: string, timeout = 10000): NightwatchBrowser {
let waitId // eslint-disable-line let waitId // eslint-disable-line
let currentValue
const runid = setInterval(() => { const runid = setInterval(() => {
this.api.getText(id, (result) => { this.api.getText(id, (result) => {
currentValue = result.value
if (typeof result.value === 'string' && result.value.indexOf(value) !== -1) { if (typeof result.value === 'string' && result.value.indexOf(value) !== -1) {
clearInterval(runid) clearInterval(runid)
clearTimeout(waitId) clearTimeout(waitId)
@ -17,7 +19,7 @@ class WaitForElementContainsText extends EventEmitter {
waitId = setTimeout(() => { waitId = setTimeout(() => {
clearInterval(runid) clearInterval(runid)
this.api.assert.fail(`TimeoutError: An error occurred while running .waitForElementContainsText() command on ${id} after ${timeout} milliseconds`) this.api.assert.fail(`TimeoutError: An error occurred while running .waitForElementContainsText() command on ${id} after ${timeout} milliseconds. expected: ${value} - got: ${currentValue}`)
}, timeout) }, timeout)
return this return this
} }

@ -97,11 +97,6 @@ module.exports = {
locateStrategy: 'xpath', locateStrategy: 'xpath',
selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"545")]', selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"545")]',
}) })
.goToVMTraceStep(10)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"10")]',
})
.getEditorValue((content) => { .getEditorValue((content) => {
browser.assert.ok(content.indexOf(`constructor (string memory name_, string memory symbol_) { browser.assert.ok(content.indexOf(`constructor (string memory name_, string memory symbol_) {
_name = name_; _name = name_;
@ -109,6 +104,11 @@ module.exports = {
}`) !== -1, }`) !== -1,
'current displayed content is not from the ERC20 source code') 'current displayed content is not from the ERC20 source code')
}) })
.goToVMTraceStep(10)
.waitForElementVisible({
locateStrategy: 'xpath',
selector: '//*[@data-id="treeViewLivm trace step" and contains(.,"10")]',
})
}, },
'Should display correct source highlighting while debugging a contract which has ABIEncoderV2 #group2': function (browser: NightwatchBrowser) { 'Should display correct source highlighting while debugging a contract which has ABIEncoderV2 #group2': function (browser: NightwatchBrowser) {

@ -290,7 +290,7 @@ module.exports = {
.waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000) .waitForElementContainsText('*[data-id="sidePanelSwapitTitle"]', 'DEBUGGER', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000)
.waitForElementVisible('*[data-id="dropdownPanelSolidityLocals"]').pause(1000) .waitForElementVisible('*[data-id="dropdownPanelSolidityLocals"]').pause(1000)
.waitForElementContainsText('*[data-id="solidityLocals"]', 'no locals', 60000) .waitForElementContainsText('*[data-id="solidityLocals"]', 'No data available', 60000)
.goToVMTraceStep(316) .goToVMTraceStep(316)
.waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'checkWinningProposalFailed()', 60000)
.waitForElementContainsText('*[data-id="functionPanel"]', 'vote(proposal)', 60000) .waitForElementContainsText('*[data-id="functionPanel"]', 'vote(proposal)', 60000)

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-analyzer", "name": "@remix-project/remix-analyzer",
"version": "0.5.28", "version": "0.5.29",
"description": "Tool to perform static analysis on Solidity smart contracts", "description": "Tool to perform static analysis on Solidity smart contracts",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -22,8 +22,8 @@
"@ethereumjs/block": "^3.5.1", "@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.49", "@remix-project/remix-astwalker": "^0.0.50",
"@remix-project/remix-lib": "^0.5.19", "@remix-project/remix-lib": "^0.5.20",
"async": "^2.6.2", "async": "^2.6.2",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",
"ethers": "^5.4.2", "ethers": "^5.4.2",
@ -52,5 +52,5 @@
"typescript": "^3.7.5" "typescript": "^3.7.5"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2cec195f5a3678b17745155536a1714d9b1a5694" "gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-astwalker", "name": "@remix-project/remix-astwalker",
"version": "0.0.49", "version": "0.0.50",
"description": "Tool to walk through Solidity AST", "description": "Tool to walk through Solidity AST",
"main": "src/index.js", "main": "src/index.js",
"scripts": { "scripts": {
@ -37,7 +37,7 @@
"@ethereumjs/block": "^3.5.1", "@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.19", "@remix-project/remix-lib": "^0.5.20",
"@types/tape": "^4.2.33", "@types/tape": "^4.2.33",
"async": "^2.6.2", "async": "^2.6.2",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",
@ -54,5 +54,5 @@
"tap-spec": "^5.0.0" "tap-spec": "^5.0.0"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2cec195f5a3678b17745155536a1714d9b1a5694" "gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
} }

@ -13,9 +13,11 @@ const profile = {
export class OffsetToLineColumnConverter extends Plugin { export class OffsetToLineColumnConverter extends Plugin {
lineBreakPositionsByContent: Record<number, Array<number>> lineBreakPositionsByContent: Record<number, Array<number>>
sourceMappingDecoder: any sourceMappingDecoder: any
offsetConvertion: any
constructor () { constructor () {
super(profile) super(profile)
this.lineBreakPositionsByContent = {} this.lineBreakPositionsByContent = {}
this.offsetConvertion = {}
this.sourceMappingDecoder = sourceMappingDecoder this.sourceMappingDecoder = sourceMappingDecoder
} }
@ -45,7 +47,15 @@ export class OffsetToLineColumnConverter extends Plugin {
} }
} }
} }
return this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
const token = `${rawLocation.start}:${rawLocation.length}:${file}`
if (this.offsetConvertion[token]) {
return this.offsetConvertion[token]
} else {
const convertion = this.sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
this.offsetConvertion[token] = convertion
return convertion
}
} }
/** /**
@ -64,6 +74,7 @@ export class OffsetToLineColumnConverter extends Plugin {
*/ */
clear () { clear () {
this.lineBreakPositionsByContent = {} this.lineBreakPositionsByContent = {}
this.offsetConvertion = {}
} }
/** /**

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-debug", "name": "@remix-project/remix-debug",
"version": "0.5.19", "version": "0.5.20",
"description": "Tool to debug Ethereum transactions", "description": "Tool to debug Ethereum transactions",
"contributors": [ "contributors": [
{ {
@ -22,9 +22,9 @@
"@ethereumjs/common": "^2.5.0", "@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-astwalker": "^0.0.49", "@remix-project/remix-astwalker": "^0.0.50",
"@remix-project/remix-lib": "^0.5.19", "@remix-project/remix-lib": "^0.5.20",
"@remix-project/remix-simulator": "^0.2.19", "@remix-project/remix-simulator": "^0.2.20",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.2", "async": "^2.6.2",
"color-support": "^1.1.3", "color-support": "^1.1.3",
@ -68,5 +68,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-debug#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2cec195f5a3678b17745155536a1714d9b1a5694" "gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
} }

@ -32,9 +32,11 @@ export class Ethdebugger {
storageResolver storageResolver
callTree callTree
breakpointManager breakpointManager
offsetToLineColumnConverter
constructor (opts) { constructor (opts) {
this.compilationResult = opts.compilationResult || function (contractAddress) { return null } this.compilationResult = opts.compilationResult || function (contractAddress) { return null }
this.offsetToLineColumnConverter = opts.offsetToLineColumnConverter
this.web3 = opts.web3 this.web3 = opts.web3
this.opts = opts this.opts = opts
@ -49,7 +51,8 @@ export class Ethdebugger {
this.traceManager, this.traceManager,
this.solidityProxy, this.solidityProxy,
this.codeManager, this.codeManager,
{ ...opts, includeLocalVariables }) { ...opts, includeLocalVariables },
this.offsetToLineColumnConverter)
} }
setManagers () { setManagers () {
@ -63,7 +66,8 @@ export class Ethdebugger {
this.traceManager, this.traceManager,
this.solidityProxy, this.solidityProxy,
this.codeManager, this.codeManager,
{ ...this.opts, includeLocalVariables }) { ...this.opts, includeLocalVariables },
this.offsetToLineColumnConverter)
} }
resolveStep (index) { resolveStep (index) {
@ -71,7 +75,7 @@ export class Ethdebugger {
} }
setCompilationResult (compilationResult) { setCompilationResult (compilationResult) {
this.solidityProxy.reset((compilationResult && compilationResult.data) || {}) this.solidityProxy.reset((compilationResult && compilationResult.data) || {}, (compilationResult && compilationResult.source && compilationResult.source.sources) || {})
} }
async sourceLocationFromVMTraceIndex (address, stepIndex) { async sourceLocationFromVMTraceIndex (address, stepIndex) {

@ -14,6 +14,8 @@ export class Debugger {
breakPointManager breakPointManager
step_manager // eslint-disable-line camelcase step_manager // eslint-disable-line camelcase
vmDebuggerLogic vmDebuggerLogic
currentFile = -1
currentLine = -1
constructor (options) { constructor (options) {
this.event = new EventManager() this.event = new EventManager()
@ -26,7 +28,8 @@ export class Debugger {
this.debugger = new Ethdebugger({ this.debugger = new Ethdebugger({
web3: options.web3, web3: options.web3,
debugWithGeneratedSources: options.debugWithGeneratedSources, debugWithGeneratedSources: options.debugWithGeneratedSources,
compilationResult: this.compilationResult compilationResult: this.compilationResult,
offsetToLineColumnConverter: this.offsetToLineColumnConverter
}) })
const { traceManager, callTree, solidityProxy } = this.debugger const { traceManager, callTree, solidityProxy } = this.debugger
@ -73,35 +76,56 @@ export class Debugger {
const compilationResultForAddress = await this.compilationResult(address) const compilationResultForAddress = await this.compilationResult(address)
if (!compilationResultForAddress) { if (!compilationResultForAddress) {
this.event.trigger('newSourceLocation', [null]) this.event.trigger('newSourceLocation', [null])
this.currentFile = -1
this.currentLine = -1
this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null])
return return
} }
this.debugger.callTree.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, index, compilationResultForAddress.data.contracts).then(async (rawLocation) => { this.debugger.callTree.getValidSourceLocationFromVMTraceIndexFromCache(address, index, compilationResultForAddress.data.contracts).then(async (rawLocationAndOpcode) => {
if (compilationResultForAddress && compilationResultForAddress.data) { if (compilationResultForAddress && compilationResultForAddress.data) {
const rawLocation = rawLocationAndOpcode.sourceLocation
const stepDetail = rawLocationAndOpcode.stepDetail
const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address) const generatedSources = this.debugger.callTree.sourceLocationTracker.getGeneratedSourcesFromAddress(address)
const astSources = Object.assign({}, compilationResultForAddress.data.sources)
const sources = Object.assign({}, compilationResultForAddress.source.sources) const lineColumnPos = rawLocationAndOpcode.lineColumnPos
if (generatedSources) {
for (const genSource of generatedSources) { let lineGasCostObj = null
astSources[genSource.name] = { id: genSource.id, ast: genSource.ast } try {
sources[genSource.name] = { content: genSource.contents } lineGasCostObj = await this.debugger.callTree.getGasCostPerLine(rawLocation.file, lineColumnPos.start.line)
} } catch (e) {
console.log(e)
} }
const lineColumnPos = await this.offsetToLineColumnConverter.offsetToLineColumn(rawLocation, rawLocation.file, sources, astSources) this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address, stepDetail, (lineGasCostObj && lineGasCostObj.gasCost) || -1])
this.event.trigger('newSourceLocation', [lineColumnPos, rawLocation, generatedSources, address])
this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [rawLocation]) this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [rawLocation])
if (this.currentFile !== rawLocation.file || this.currentLine !== lineColumnPos.start.line) {
const instructionIndexes = lineGasCostObj.indexes.map((index) => { // translate from vmtrace index to instruction index
return this.debugger.codeManager.getInstructionIndex(address, index)
})
this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [instructionIndexes, lineColumnPos.start.line ])
this.currentFile = rawLocation.file
this.currentLine = lineColumnPos.start.line
}
} else { } else {
this.event.trigger('newSourceLocation', [null]) this.event.trigger('newSourceLocation', [null])
this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [null]) this.currentFile = -1
this.currentLine = -1
this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null])
} }
}).catch((_error) => { }).catch((_error) => {
this.event.trigger('newSourceLocation', [null]) this.event.trigger('newSourceLocation', [null])
this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [null]) this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [null])
this.currentFile = -1
this.currentLine = -1
this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null])
}) })
// }) // })
} catch (error) { } catch (error) {
this.event.trigger('newSourceLocation', [null]) this.event.trigger('newSourceLocation', [null])
this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [null]) this.vmDebuggerLogic.event.trigger('sourceLocationChanged', [null])
this.currentFile = -1
this.currentLine = -1
this.vmDebuggerLogic.event.trigger('lineGasCostChanged', [null])
return console.log(error) return console.log(error)
} }
} }

@ -241,9 +241,7 @@ function getStructMembers (type, stateDefinitions, contractName, location) {
if (type.indexOf('.') === -1) { if (type.indexOf('.') === -1) {
type = contractName + '.' + type type = contractName + '.' + type
} }
if (!contractName) {
contractName = type.split('.')[0] contractName = type.split('.')[0]
}
const state = stateDefinitions[contractName] const state = stateDefinitions[contractName]
if (state) { if (state) {
for (const dec of state.stateDefinitions) { for (const dec of state.stateDefinitions) {

@ -7,6 +7,16 @@ import { parseType } from './decodeInfo'
import { isContractCreation, isCallInstruction, isCreateInstruction, isJumpDestInstruction } from '../trace/traceHelper' import { isContractCreation, isCallInstruction, isCreateInstruction, isJumpDestInstruction } from '../trace/traceHelper'
import { extractLocationFromAstVariable } from './types/util' import { extractLocationFromAstVariable } from './types/util'
export type StepDetail = {
depth: number,
gas: number | string,
gasCost: number,
memory: number[],
op: string,
pc: number,
stack: number[],
}
/** /**
* Tree representing internal jump into function. * Tree representing internal jump into function.
* Triggers `callTreeReady` event when tree is ready * Triggers `callTreeReady` event when tree is ready
@ -27,6 +37,15 @@ export class InternalCallTree {
functionDefinitionByFile functionDefinitionByFile
astWalker astWalker
reducedTrace reducedTrace
locationAndOpcodePerVMTraceIndex: {
[Key: number]: any
}
gasCostPerLine
offsetToLineColumnConverter
pendingConstructorExecutionAt: number
pendingConstructorId: number
pendingConstructor
constructorsStartExecution
/** /**
* constructor * constructor
@ -37,14 +56,16 @@ export class InternalCallTree {
* @param {Object} codeManager - code manager * @param {Object} codeManager - code manager
* @param {Object} opts - { includeLocalVariables, debugWithGeneratedSources } * @param {Object} opts - { includeLocalVariables, debugWithGeneratedSources }
*/ */
constructor (debuggerEvent, traceManager, solidityProxy, codeManager, opts) { constructor (debuggerEvent, traceManager, solidityProxy, codeManager, opts, offsetToLineColumnConverter?) {
this.includeLocalVariables = opts.includeLocalVariables this.includeLocalVariables = opts.includeLocalVariables
this.debugWithGeneratedSources = opts.debugWithGeneratedSources this.debugWithGeneratedSources = opts.debugWithGeneratedSources
this.event = new EventManager() this.event = new EventManager()
this.solidityProxy = solidityProxy this.solidityProxy = solidityProxy
this.traceManager = traceManager this.traceManager = traceManager
this.offsetToLineColumnConverter = offsetToLineColumnConverter
this.sourceLocationTracker = new SourceLocationTracker(codeManager, { debugWithGeneratedSources: opts.debugWithGeneratedSources }) this.sourceLocationTracker = new SourceLocationTracker(codeManager, { debugWithGeneratedSources: opts.debugWithGeneratedSources })
debuggerEvent.register('newTraceLoaded', (trace) => { debuggerEvent.register('newTraceLoaded', (trace) => {
const time = Date.now()
this.reset() this.reset()
if (!this.solidityProxy.loaded()) { if (!this.solidityProxy.loaded()) {
this.event.trigger('callTreeBuildFailed', ['compilation result not loaded. Cannot build internal call tree']) this.event.trigger('callTreeBuildFailed', ['compilation result not loaded. Cannot build internal call tree'])
@ -52,11 +73,17 @@ export class InternalCallTree {
// each recursive call to buildTree represent a new context (either call, delegatecall, internal function) // each recursive call to buildTree represent a new context (either call, delegatecall, internal function)
const calledAddress = traceManager.getCurrentCalledAddressAt(0) const calledAddress = traceManager.getCurrentCalledAddressAt(0)
const isCreation = isContractCreation(calledAddress) const isCreation = isContractCreation(calledAddress)
buildTree(this, 0, '', true, isCreation).then((result) => {
const scopeId = '1'
this.scopeStarts[0] = scopeId
this.scopes[scopeId] = { firstStep: 0, locals: {}, isCreation, gasCost: 0 }
buildTree(this, 0, scopeId, isCreation).then((result) => {
if (result.error) { if (result.error) {
this.event.trigger('callTreeBuildFailed', [result.error]) this.event.trigger('callTreeBuildFailed', [result.error])
} else { } else {
createReducedTrace(this, traceManager.trace.length - 1) createReducedTrace(this, traceManager.trace.length - 1)
console.log('call tree build lasts ', (Date.now() - time) / 1000)
this.event.trigger('callTreeReady', [this.scopes, this.scopeStarts]) this.event.trigger('callTreeReady', [this.scopes, this.scopeStarts])
} }
}, (reason) => { }, (reason) => {
@ -85,10 +112,16 @@ export class InternalCallTree {
this.functionCallStack = [] this.functionCallStack = []
this.functionDefinitionsByScope = {} this.functionDefinitionsByScope = {}
this.scopeStarts = {} this.scopeStarts = {}
this.gasCostPerLine = {}
this.variableDeclarationByFile = {} this.variableDeclarationByFile = {}
this.functionDefinitionByFile = {} this.functionDefinitionByFile = {}
this.astWalker = new AstWalker() this.astWalker = new AstWalker()
this.reducedTrace = [] this.reducedTrace = []
this.locationAndOpcodePerVMTraceIndex = {}
this.pendingConstructorExecutionAt = -1
this.pendingConstructorId = -1
this.constructorsStartExecution = {}
this.pendingConstructor = null
} }
/** /**
@ -123,6 +156,7 @@ export class InternalCallTree {
const scope = this.findScope(vmtraceIndex) const scope = this.findScope(vmtraceIndex)
if (!scope) return [] if (!scope) return []
let scopeId = this.scopeStarts[scope.firstStep] let scopeId = this.scopeStarts[scope.firstStep]
const scopeDetail = this.scopes[scopeId]
const functions = [] const functions = []
if (!scopeId) return functions if (!scopeId) return functions
let i = 0 let i = 0
@ -132,7 +166,7 @@ export class InternalCallTree {
if (i > 1000) throw new Error('retrieFunctionStack: recursion too deep') if (i > 1000) throw new Error('retrieFunctionStack: recursion too deep')
const functionDefinition = this.functionDefinitionsByScope[scopeId] const functionDefinition = this.functionDefinitionsByScope[scopeId]
if (functionDefinition !== undefined) { if (functionDefinition !== undefined) {
functions.push(functionDefinition) functions.push({ ...functionDefinition, ...scopeDetail })
} }
const parent = this.parentScope(scopeId) const parent = this.parentScope(scopeId)
if (!parent) break if (!parent) break
@ -141,31 +175,41 @@ export class InternalCallTree {
return functions return functions
} }
async extractSourceLocation (step) { async extractSourceLocation (step: number, address?: string) {
try { try {
const address = this.traceManager.getCurrentCalledAddressAt(step) if (!address) address = this.traceManager.getCurrentCalledAddressAt(step)
const location = await this.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts) return await this.sourceLocationTracker.getSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts)
return location
} catch (error) { } catch (error) {
throw new Error('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error) throw new Error('InternalCallTree - Cannot retrieve sourcelocation for step ' + step + ' ' + error)
} }
} }
async extractValidSourceLocation (step) { async extractValidSourceLocation (step: number, address?: string) {
try { try {
const address = this.traceManager.getCurrentCalledAddressAt(step) if (!address) address = this.traceManager.getCurrentCalledAddressAt(step)
const location = await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts) return await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndex(address, step, this.solidityProxy.contracts)
return location
} catch (error) { } catch (error) {
throw new Error('InternalCallTree - Cannot retrieve valid sourcelocation for step ' + step + ' ' + error) throw new Error('InternalCallTree - Cannot retrieve valid sourcelocation for step ' + step + ' ' + error)
} }
} }
async getValidSourceLocationFromVMTraceIndexFromCache (address: string, step: number, contracts: any) {
return await this.sourceLocationTracker.getValidSourceLocationFromVMTraceIndexFromCache(address, step, contracts, this.locationAndOpcodePerVMTraceIndex)
}
async getGasCostPerLine(file: number, line: number) {
if (this.gasCostPerLine[file] && this.gasCostPerLine[file][line]) {
return this.gasCostPerLine[file][line]
}
throw new Error('Could not find gas cost per line')
}
} }
async function buildTree (tree, step, scopeId, isExternalCall, isCreation) { async function buildTree (tree, step, scopeId, isCreation, functionDefinition?, contractObj?, sourceLocation?, validSourceLocation?) {
let subScope = 1 let subScope = 1
tree.scopeStarts[step] = scopeId if (functionDefinition) {
tree.scopes[scopeId] = { firstStep: step, locals: {}, isCreation } await registerFunctionParameters(tree, functionDefinition, step, scopeId, contractObj, validSourceLocation)
}
function callDepthChange (step, trace) { function callDepthChange (step, trace) {
if (step + 1 < trace.length) { if (step + 1 < trace.length) {
@ -183,30 +227,104 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) {
included.file === source.file) included.file === source.file)
} }
let currentSourceLocation = { start: -1, length: -1, file: -1 } let currentSourceLocation = sourceLocation || { start: -1, length: -1, file: -1, jump: '-' }
let previousSourceLocation = currentSourceLocation let previousSourceLocation = currentSourceLocation
let previousValidSourceLocation = validSourceLocation || currentSourceLocation
while (step < tree.traceManager.trace.length) { while (step < tree.traceManager.trace.length) {
let sourceLocation let sourceLocation
let newLocation = false let validSourceLocation
let address
try { try {
sourceLocation = await tree.extractSourceLocation(step) address = tree.traceManager.getCurrentCalledAddressAt(step)
sourceLocation = await tree.extractSourceLocation(step, address)
if (!includedSource(sourceLocation, currentSourceLocation)) { if (!includedSource(sourceLocation, currentSourceLocation)) {
tree.reducedTrace.push(step) tree.reducedTrace.push(step)
currentSourceLocation = sourceLocation currentSourceLocation = sourceLocation
newLocation = true
} }
const amountOfSources = tree.sourceLocationTracker.getTotalAmountOfSources(address, tree.solidityProxy.contracts)
if (tree.sourceLocationTracker.isInvalidSourceLocation(currentSourceLocation, amountOfSources)) { // file is -1 or greater than amount of sources
validSourceLocation = previousValidSourceLocation
} else
validSourceLocation = currentSourceLocation
} catch (e) { } catch (e) {
return { outStep: step, error: 'InternalCallTree - Error resolving source location. ' + step + ' ' + e } return { outStep: step, error: 'InternalCallTree - Error resolving source location. ' + step + ' ' + e }
} }
if (!sourceLocation) { if (!sourceLocation) {
return { outStep: step, error: 'InternalCallTree - No source Location. ' + step } return { outStep: step, error: 'InternalCallTree - No source Location. ' + step }
} }
const isCallInstrn = isCallInstruction(tree.traceManager.trace[step]) const stepDetail: StepDetail = tree.traceManager.trace[step]
const isCreateInstrn = isCreateInstruction(tree.traceManager.trace[step]) const nextStepDetail: StepDetail = tree.traceManager.trace[step + 1]
if (stepDetail && nextStepDetail) {
stepDetail.gasCost = parseInt(stepDetail.gas as string) - parseInt(nextStepDetail.gas as string)
}
// gas per line
let lineColumnPos
if (tree.offsetToLineColumnConverter) {
try {
const generatedSources = tree.sourceLocationTracker.getGeneratedSourcesFromAddress(address)
const astSources = Object.assign({}, tree.solidityProxy.sources)
const sources = Object.assign({}, tree.solidityProxy.sourcesCode)
if (generatedSources) {
for (const genSource of generatedSources) {
astSources[genSource.name] = { id: genSource.id, ast: genSource.ast }
sources[genSource.name] = { content: genSource.contents }
}
}
lineColumnPos = await tree.offsetToLineColumnConverter.offsetToLineColumn(validSourceLocation, validSourceLocation.file, sources, astSources)
if (!tree.gasCostPerLine[validSourceLocation.file]) tree.gasCostPerLine[validSourceLocation.file] = {}
if (!tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line]) {
tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line] = {
gasCost: 0,
indexes: []
}
}
tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line].gasCost += stepDetail.gasCost
tree.gasCostPerLine[validSourceLocation.file][lineColumnPos.start.line].indexes.push(step)
} catch (e) {
console.log(e)
}
}
tree.locationAndOpcodePerVMTraceIndex[step] = { sourceLocation, stepDetail, lineColumnPos }
tree.scopes[scopeId].gasCost += stepDetail.gasCost
const contractObj = await tree.solidityProxy.contractObjectAtAddress(address)
const generatedSources = getGeneratedSources(tree, scopeId, contractObj)
const functionDefinition = resolveFunctionDefinition(tree, sourceLocation, generatedSources)
const isInternalTxInstrn = isCallInstruction(stepDetail)
const isCreateInstrn = isCreateInstruction(stepDetail)
// we are checking if we are jumping in a new CALL or in an internal function // we are checking if we are jumping in a new CALL or in an internal function
if (isCallInstrn || sourceLocation.jump === 'i') {
const constructorExecutionStarts = tree.pendingConstructorExecutionAt > -1 && tree.pendingConstructorExecutionAt < validSourceLocation.start
if (functionDefinition && functionDefinition.kind === 'constructor' && tree.pendingConstructorExecutionAt === -1 && !tree.constructorsStartExecution[functionDefinition.id]) {
tree.pendingConstructorExecutionAt = validSourceLocation.start
tree.pendingConstructorId = functionDefinition.id
tree.pendingConstructor = functionDefinition
// from now on we'll be waiting for a change in the source location which will mark the beginning of the constructor execution.
// constructorsStartExecution allows to keep track on which constructor has already been executed.
}
const internalfunctionCall = functionDefinition && previousSourceLocation.jump === 'i'
if (constructorExecutionStarts || isInternalTxInstrn || internalfunctionCall) {
try { try {
const externalCallResult = await buildTree(tree, step + 1, scopeId === '' ? subScope.toString() : scopeId + '.' + subScope, isCallInstrn, isCreateInstrn) const newScopeId = scopeId === '' ? subScope.toString() : scopeId + '.' + subScope
tree.scopeStarts[step] = newScopeId
tree.scopes[newScopeId] = { firstStep: step, locals: {}, isCreation, gasCost: 0 }
// for the ctor we we are at the start of its trace, we have to replay this step in order to catch all the locals:
const nextStep = constructorExecutionStarts ? step : step + 1
if (constructorExecutionStarts) {
tree.constructorsStartExecution[tree.pendingConstructorId] = tree.pendingConstructorExecutionAt
tree.pendingConstructorExecutionAt = -1
tree.pendingConstructorId = -1
await registerFunctionParameters(tree, tree.pendingConstructor, step, newScopeId, contractObj, previousValidSourceLocation)
tree.pendingConstructor = null
}
const externalCallResult = await buildTree(tree, nextStep, newScopeId, isCreateInstrn, functionDefinition, contractObj, sourceLocation, validSourceLocation)
if (externalCallResult.error) { if (externalCallResult.error) {
return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error } return { outStep: step, error: 'InternalCallTree - ' + externalCallResult.error }
} else { } else {
@ -216,7 +334,7 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) {
} catch (e) { } catch (e) {
return { outStep: step, error: 'InternalCallTree - ' + e.message } return { outStep: step, error: 'InternalCallTree - ' + e.message }
} }
} else if ((isExternalCall && callDepthChange(step, tree.traceManager.trace)) || (!isExternalCall && sourceLocation.jump === 'o')) { } else if (callDepthChange(step, tree.traceManager.trace) || (sourceLocation.jump === 'o' && functionDefinition)) {
// if not, we might be returning from a CALL or internal function. This is what is checked here. // if not, we might be returning from a CALL or internal function. This is what is checked here.
tree.scopes[scopeId].lastStep = step tree.scopes[scopeId].lastStep = step
return { outStep: step + 1 } return { outStep: step + 1 }
@ -224,9 +342,10 @@ async function buildTree (tree, step, scopeId, isExternalCall, isCreation) {
// if not, we are in the current scope. // 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` // We check in `includeVariableDeclaration` if there is a new local variable in scope for this specific `step`
if (tree.includeLocalVariables) { if (tree.includeLocalVariables) {
await includeVariableDeclaration(tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) await includeVariableDeclaration(tree, step, sourceLocation, scopeId, contractObj, generatedSources)
} }
previousSourceLocation = sourceLocation previousSourceLocation = sourceLocation
previousValidSourceLocation = validSourceLocation
step++ step++
} }
} }
@ -245,10 +364,33 @@ function getGeneratedSources (tree, scopeId, contractObj) {
return null return null
} }
async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, newLocation, previousSourceLocation) { async function registerFunctionParameters (tree, functionDefinition, step, scopeId, contractObj, sourceLocation) {
const contractObj = await tree.solidityProxy.contractObjectAt(step) tree.functionCallStack.push(step)
const functionDefinitionAndInputs = { functionDefinition, inputs: [] }
// means: the previous location was a function definition && JUMPDEST
// => we are at the beginning of the function and input/output are setup
try {
const stack = tree.traceManager.getStackAt(step)
const states = tree.solidityProxy.extractStatesDefinitions()
if (functionDefinition.parameters) {
const inputs = functionDefinition.parameters
const outputs = functionDefinition.returnParameters
// input params
if (inputs && inputs.parameters) {
functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractObj, sourceLocation, stack.length, inputs.parameters.length, -1)
}
// output params
if (outputs) addParams(outputs, tree, scopeId, states, contractObj, sourceLocation, stack.length, 0, 1)
}
} catch (error) {
console.log(error)
}
tree.functionDefinitionsByScope[scopeId] = functionDefinitionAndInputs
}
async function includeVariableDeclaration (tree, step, sourceLocation, scopeId, contractObj, generatedSources) {
let states = null let states = null
const generatedSources = getGeneratedSources(tree, scopeId, contractObj)
const variableDeclarations = resolveVariableDeclaration(tree, sourceLocation, generatedSources) const variableDeclarations = resolveVariableDeclaration(tree, sourceLocation, generatedSources)
// using the vm trace step, the current source location and the ast, // 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 // we check if the current vm trace step target a new ast node of type VariableDeclaration
@ -278,49 +420,6 @@ async function includeVariableDeclaration (tree, step, sourceLocation, scopeId,
} }
} }
} }
// 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)
if (!functionDefinition) return
const previousIsJumpDest2 = isJumpDestInstruction(tree.traceManager.trace[step - 2])
const previousIsJumpDest1 = isJumpDestInstruction(tree.traceManager.trace[step - 1])
const isConstructor = functionDefinition.kind === 'constructor'
if (newLocation && (previousIsJumpDest1 || previousIsJumpDest2 || isConstructor)) {
tree.functionCallStack.push(step)
const functionDefinitionAndInputs = { functionDefinition, inputs: [] }
// means: the previous location was a function definition && JUMPDEST
// => we are at the beginning of the function and input/output are setup
try {
const stack = tree.traceManager.getStackAt(step)
states = tree.solidityProxy.extractStatesDefinitions()
if (functionDefinition.parameters) {
const inputs = functionDefinition.parameters
const outputs = functionDefinition.returnParameters
// for (const element of functionDefinition.parameters) {
// if (element.nodeType === 'ParameterList') {
// if (!inputs) inputs = element
// else {
// outputs = element
// break
// }
// }
// }
// input params
if (inputs && inputs.parameters) {
functionDefinitionAndInputs.inputs = addParams(inputs, tree, scopeId, states, contractObj, previousSourceLocation, stack.length, inputs.parameters.length, -1)
}
// output params
if (outputs) addParams(outputs, tree, scopeId, states, contractObj, previousSourceLocation, stack.length, 0, 1)
}
} catch (error) {
console.log(error)
}
tree.functionDefinitionsByScope[scopeId] = functionDefinitionAndInputs
}
} }
// this extract all the variable declaration for a given ast and file // this extract all the variable declaration for a given ast and file
@ -388,7 +487,8 @@ function addParams (parameterList, tree, scopeId, states, contractObj, sourceLoc
type: parseType(param.typeDescriptions.typeString, states, contractName, location), type: parseType(param.typeDescriptions.typeString, states, contractName, location),
stackDepth: stackDepth, stackDepth: stackDepth,
sourceLocation: sourceLocation, sourceLocation: sourceLocation,
abi: contractObj.contract.abi abi: contractObj.contract.abi,
isParameter: true
} }
params.push(attributesName) params.push(attributesName)
} }

@ -11,7 +11,7 @@ export async function solidityLocals (vmtraceIndex, internalTreeCall, stack, mem
let anonymousIncr = 1 let anonymousIncr = 1
for (const local in scope.locals) { for (const local in scope.locals) {
const variable = scope.locals[local] const variable = scope.locals[local]
if (variable.stackDepth < stack.length && variable.sourceLocation.start <= currentSourceLocation.start) { if (variable.stackDepth < stack.length && (variable.sourceLocation.start <= currentSourceLocation.start || variable.isParameter)) {
let name = variable.name let name = variable.name
if (name.indexOf('$') !== -1) { if (name.indexOf('$') !== -1) {
name = '<' + anonymousIncr + '>' name = '<' + anonymousIncr + '>'
@ -21,7 +21,7 @@ export async function solidityLocals (vmtraceIndex, internalTreeCall, stack, mem
locals[name] = await variable.type.decodeFromStack(variable.stackDepth, stack, memory, storageResolver, calldata, cursor, variable) locals[name] = await variable.type.decodeFromStack(variable.stackDepth, stack, memory, storageResolver, calldata, cursor, variable)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
locals[name] = { error: '<decoding failed - ' + e.message + '>' } locals[name] = { error: '<decoding failed - ' + e.message + '>', type: variable && variable.type && variable.type.typeName || 'unknown' }
} }
} }
} }

@ -10,6 +10,8 @@ export class SolidityProxy {
getCode getCode
sources sources
contracts contracts
compilationResult
sourcesCode
constructor ({ getCurrentCalledAddressAt, getCode }) { constructor ({ getCurrentCalledAddressAt, getCode }) {
this.cache = new Cache() this.cache = new Cache()
@ -23,9 +25,10 @@ export class SolidityProxy {
* *
* @param {Object} compilationResult - result os a compilatiion (diectly returned by the compiler) * @param {Object} compilationResult - result os a compilatiion (diectly returned by the compiler)
*/ */
reset (compilationResult) { reset (compilationResult, sources?) {
this.sources = compilationResult.sources this.sources = compilationResult.sources // ast
this.contracts = compilationResult.contracts this.contracts = compilationResult.contracts
if (sources) this.sourcesCode = sources
this.cache.reset() this.cache.reset()
} }
@ -44,8 +47,18 @@ export class SolidityProxy {
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name * @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name
* @param {Function} cb - callback returns (error, contractName) * @param {Function} cb - callback returns (error, contractName)
*/ */
async contractObjectAt (vmTraceIndex) { async contractObjectAt (vmTraceIndex: number) {
const address = this.getCurrentCalledAddressAt(vmTraceIndex) const address = this.getCurrentCalledAddressAt(vmTraceIndex)
return this.contractObjectAtAddress(address)
}
/**
* retrieve the compiled contract name at the @arg vmTraceIndex (cached)
*
* @param {Int} vmTraceIndex - index in the vm trave where to resolve the executed contract name
* @param {Function} cb - callback returns (error, contractName)
*/
async contractObjectAtAddress (address: string) {
if (this.cache.contractObjectByAddress[address]) { if (this.cache.contractObjectByAddress[address]) {
return this.cache.contractObjectByAddress[address] return this.cache.contractObjectByAddress[address]
} }

@ -25,7 +25,7 @@ export class StringType extends DynamicByteArray {
return await super.decodeFromStack(stackDepth, stack, memory, storageResolver, calldata, cursor, variableDetails) return await super.decodeFromStack(stackDepth, stack, memory, storageResolver, calldata, cursor, variableDetails)
} catch (e) { } catch (e) {
console.log(e) console.log(e)
return { error: '<decoding failed - ' + e.message + '>' } return { error: '<decoding failed - ' + e.message + '>', type: this.typeName }
} }
} }

@ -4,9 +4,11 @@ import { getLinebreakPositions, convertOffsetToLineColumn } from './sourceMappin
export class OffsetToColumnConverter { export class OffsetToColumnConverter {
lineBreakPositionsByContent lineBreakPositionsByContent
sourceMappingDecoder sourceMappingDecoder
offsetConvertion
constructor (compilerEvent) { constructor (compilerEvent) {
this.lineBreakPositionsByContent = {} this.lineBreakPositionsByContent = {}
this.offsetConvertion = {}
if (compilerEvent) { if (compilerEvent) {
compilerEvent.register('compilationFinished', (success, data, source, input, version) => { compilerEvent.register('compilationFinished', (success, data, source, input, version) => {
this.clear() this.clear()
@ -26,10 +28,18 @@ export class OffsetToColumnConverter {
} }
} }
} }
return convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file]) const token = `${rawLocation.start}:${rawLocation.length}:${file}`
if (this.offsetConvertion[token]) {
return this.offsetConvertion[token]
} else {
const convertion = convertOffsetToLineColumn(rawLocation, this.lineBreakPositionsByContent[file])
this.offsetConvertion[token] = convertion
return convertion
}
} }
clear () { clear () {
this.lineBreakPositionsByContent = {} this.lineBreakPositionsByContent = {}
this.offsetConvertion = {}
} }
} }

@ -87,13 +87,34 @@ export class SourceLocationTracker {
(map.file > amountOfSources - 1) this indicates the current file index exceed the total number of files. (map.file > amountOfSources - 1) this indicates the current file index exceed the total number of files.
this happens when generated sources should not be considered. this happens when generated sources should not be considered.
*/ */
while (vmtraceStepIndex >= 0 && (map.file === -1 || map.file > amountOfSources - 1)) { while (vmtraceStepIndex >= 0 && this.isInvalidSourceLocation(map, amountOfSources)) {
map = await this.getSourceLocationFromVMTraceIndex(address, vmtraceStepIndex, contracts) map = await this.getSourceLocationFromVMTraceIndex(address, vmtraceStepIndex, contracts)
vmtraceStepIndex = vmtraceStepIndex - 1 vmtraceStepIndex = vmtraceStepIndex - 1
} }
return map return map
} }
isInvalidSourceLocation (sourceLocation, amountOfSources) {
return sourceLocation.file === -1 || sourceLocation.file > amountOfSources - 1
}
async getValidSourceLocationFromVMTraceIndexFromCache (address: string, vmtraceStepIndex: number, contracts: any, cache: Map<number, any>) {
const amountOfSources = this.getTotalAmountOfSources(address, contracts)
let map: any = { file: -1 }
/*
(map.file === -1) this indicates that it isn't associated with a known source code
(map.file > amountOfSources - 1) this indicates the current file index exceed the total number of files.
this happens when generated sources should not be considered.
*/
const originStep = cache[vmtraceStepIndex]
while (vmtraceStepIndex >= 0 && (map.file === -1 || map.file > amountOfSources - 1)) {
map = cache[vmtraceStepIndex].sourceLocation
vmtraceStepIndex = vmtraceStepIndex - 1
originStep.sourceLocation = map
}
return originStep
}
clearCache () { clearCache () {
this.sourceMapByAddress = {} this.sourceMapByAddress = {}
} }

@ -23,7 +23,7 @@ tape('solidity', function (t) {
async function test (st, privateKey) { async function test (st, privateKey) {
var output = compiler.compile(compilerInput(intLocal.contract)) var output = compiler.compile(compilerInput(intLocal.contract))
output = JSON.parse(output) output = JSON.parse(output)
await intLocalTest(st, privateKey, output.contracts['test.sol']['intLocal'].evm.bytecode.object, output) await intLocalTest(st, privateKey, output.contracts['test.sol']['intLocal'].evm.bytecode.object, output, intLocal.contract)
output = compiler.compile(compilerInput(miscLocal.contract)) output = compiler.compile(compilerInput(miscLocal.contract))
output = JSON.parse(output) output = JSON.parse(output)
await miscLocalTest(st, privateKey, output.contracts['test.sol']['miscLocal'].evm.bytecode.object, output) await miscLocalTest(st, privateKey, output.contracts['test.sol']['miscLocal'].evm.bytecode.object, output)

@ -7,9 +7,10 @@ import { contractCreationToken } from '../../../src/trace/traceHelper'
import { SolidityProxy } from '../../../src/solidity-decoder/solidityProxy' import { SolidityProxy } from '../../../src/solidity-decoder/solidityProxy'
import { InternalCallTree } from '../../../src/solidity-decoder/internalCallTree' import { InternalCallTree } from '../../../src/solidity-decoder/internalCallTree'
import { EventManager } from '../../../src/eventManager' import { EventManager } from '../../../src/eventManager'
import * as sourceMappingDecoder from '../../../src/source/sourceMappingDecoder'
import * as helper from './helper' import * as helper from './helper'
module.exports = function (st, privateKey, contractBytecode, compilationResult) { module.exports = function (st, privateKey, contractBytecode, compilationResult, contractCode) {
return new Promise(async (resolve) => { return new Promise(async (resolve) => {
let web3 = await (vmCall as any).getWeb3(); let web3 = await (vmCall as any).getWeb3();
(vmCall as any).sendTx(web3, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, hash) { (vmCall as any).sendTx(web3, { nonce: 0, privateKey: privateKey }, null, 0, contractBytecode, function (error, hash) {
@ -27,22 +28,37 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult)
var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) }) var solidityProxy = new SolidityProxy({ getCurrentCalledAddressAt: traceManager.getCurrentCalledAddressAt.bind(traceManager), getCode: codeManager.getCode.bind(codeManager) })
solidityProxy.reset(compilationResult) solidityProxy.reset(compilationResult)
var debuggerEvent = new EventManager() var debuggerEvent = new EventManager()
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }) const offsetToLineColumnConverter = {
offsetToLineColumn: (rawLocation) => {
return new Promise((resolve) => {
const lineBreaks = sourceMappingDecoder.getLinebreakPositions(contractCode)
resolve(sourceMappingDecoder.convertOffsetToLineColumn(rawLocation, lineBreaks))
})
}
}
var callTree = new InternalCallTree(debuggerEvent, traceManager, solidityProxy, codeManager, { includeLocalVariables: true }, offsetToLineColumnConverter)
callTree.event.register('callTreeBuildFailed', (error) => { callTree.event.register('callTreeBuildFailed', (error) => {
st.fail(error) st.fail(error)
}) })
callTree.event.register('callTreeNotReady', (reason) => { callTree.event.register('callTreeNotReady', (reason) => {
st.fail(reason) st.fail(reason)
}) })
callTree.event.register('callTreeReady', (scopes, scopeStarts) => { callTree.event.register('callTreeReady', async (scopes, scopeStarts) => {
try { try {
let functions1 = callTree.retrieveFunctionsStack(102)
let functions2 = callTree.retrieveFunctionsStack(115) // test gas cost per line
st.equals((await callTree.getGasCostPerLine(0, 16)).gasCost, 11)
st.equals((await callTree.getGasCostPerLine(0, 32)).gasCost, 84)
let functions1 = callTree.retrieveFunctionsStack(103)
let functions2 = callTree.retrieveFunctionsStack(116)
let functions3 = callTree.retrieveFunctionsStack(13) let functions3 = callTree.retrieveFunctionsStack(13)
st.equals(functions1.length, 1) st.equals(functions1.length, 2)
st.equals(functions2.length, 2) st.equals(functions2.length, 3)
st.equals(functions3.length, 0) st.equals(functions3.length, 1)
st.equal(functions1[0].gasCost, 54)
st.equals(Object.keys(functions1[0])[0], 'functionDefinition') st.equals(Object.keys(functions1[0])[0], 'functionDefinition')
st.equals(Object.keys(functions1[0])[1], 'inputs') st.equals(Object.keys(functions1[0])[1], 'inputs')
@ -58,33 +74,33 @@ module.exports = function (st, privateKey, contractBytecode, compilationResult)
st.equals(functions2[0].functionDefinition.name, 'level12') st.equals(functions2[0].functionDefinition.name, 'level12')
st.equals(functions2[1].functionDefinition.name, 'level11') st.equals(functions2[1].functionDefinition.name, 'level11')
st.equals(scopeStarts[0], '') st.equals(scopeStarts[0], '1')
st.equals(scopeStarts[13], '1') st.equals(scopeStarts[10], '1.1')
st.equals(scopeStarts[102], '2') st.equals(scopeStarts[102], '1.1.1')
st.equals(scopeStarts[115], '2.1') st.equals(scopeStarts[115], '1.1.1.1')
st.equals(scopeStarts[136], '3') st.equals(scopeStarts[136], '1.1.2')
st.equals(scopeStarts[153], '4') st.equals(scopeStarts[153], '1.1.3')
st.equals(scopeStarts[166], '4.1') st.equals(scopeStarts[166], '1.1.3.1')
st.equals(scopes[''].locals['ui8'].type.typeName, 'uint8') st.equals(scopes['1.1'].locals['ui8'].type.typeName, 'uint8')
st.equals(scopes[''].locals['ui16'].type.typeName, 'uint16') st.equals(scopes['1.1'].locals['ui16'].type.typeName, 'uint16')
st.equals(scopes[''].locals['ui32'].type.typeName, 'uint32') st.equals(scopes['1.1'].locals['ui32'].type.typeName, 'uint32')
st.equals(scopes[''].locals['ui64'].type.typeName, 'uint64') st.equals(scopes['1.1'].locals['ui64'].type.typeName, 'uint64')
st.equals(scopes[''].locals['ui128'].type.typeName, 'uint128') st.equals(scopes['1.1'].locals['ui128'].type.typeName, 'uint128')
st.equals(scopes[''].locals['ui256'].type.typeName, 'uint256') st.equals(scopes['1.1'].locals['ui256'].type.typeName, 'uint256')
st.equals(scopes[''].locals['ui'].type.typeName, 'uint256') st.equals(scopes['1.1'].locals['ui'].type.typeName, 'uint256')
st.equals(scopes[''].locals['i8'].type.typeName, 'int8') st.equals(scopes['1.1'].locals['i8'].type.typeName, 'int8')
st.equals(scopes[''].locals['i16'].type.typeName, 'int16') st.equals(scopes['1.1'].locals['i16'].type.typeName, 'int16')
st.equals(scopes[''].locals['i32'].type.typeName, 'int32') st.equals(scopes['1.1'].locals['i32'].type.typeName, 'int32')
st.equals(scopes[''].locals['i64'].type.typeName, 'int64') st.equals(scopes['1.1'].locals['i64'].type.typeName, 'int64')
st.equals(scopes[''].locals['i128'].type.typeName, 'int128') st.equals(scopes['1.1'].locals['i128'].type.typeName, 'int128')
st.equals(scopes[''].locals['i256'].type.typeName, 'int256') st.equals(scopes['1.1'].locals['i256'].type.typeName, 'int256')
st.equals(scopes[''].locals['i'].type.typeName, 'int256') st.equals(scopes['1.1'].locals['i'].type.typeName, 'int256')
st.equals(scopes[''].locals['ishrink'].type.typeName, 'int32') st.equals(scopes['1.1'].locals['ishrink'].type.typeName, 'int32')
st.equals(scopes['2'].locals['ui8'].type.typeName, 'uint8') st.equals(scopes['1.1.1'].locals['ui8'].type.typeName, 'uint8')
st.equals(scopes['2.1'].locals['ui81'].type.typeName, 'uint8') st.equals(scopes['1.1.1.1'].locals['ui81'].type.typeName, 'uint8')
st.equals(scopes['3'].locals['ui81'].type.typeName, 'uint8') st.equals(scopes['1.1.2'].locals['ui81'].type.typeName, 'uint8')
st.equals(scopes['4'].locals['ui8'].type.typeName, 'uint8') st.equals(scopes['1.1.3'].locals['ui8'].type.typeName, 'uint8')
st.equals(scopes['4.1'].locals['ui81'].type.typeName, 'uint8') st.equals(scopes['1.1.3.1'].locals['ui81'].type.typeName, 'uint8')
} catch (e) { } catch (e) {
st.fail(e.message) st.fail(e.message)
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-lib", "name": "@remix-project/remix-lib",
"version": "0.5.19", "version": "0.5.20",
"description": "Library to various Remix tools", "description": "Library to various Remix tools",
"contributors": [ "contributors": [
{ {
@ -54,5 +54,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-lib#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2cec195f5a3678b17745155536a1714d9b1a5694" "gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-simulator", "name": "@remix-project/remix-simulator",
"version": "0.2.19", "version": "0.2.20",
"description": "Ethereum IDE and tools for the web", "description": "Ethereum IDE and tools for the web",
"contributors": [ "contributors": [
{ {
@ -18,7 +18,7 @@
"@ethereumjs/common": "^2.5.0", "@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.19", "@remix-project/remix-lib": "^0.5.20",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^3.1.0", "async": "^3.1.0",
"body-parser": "^1.18.2", "body-parser": "^1.18.2",
@ -67,5 +67,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-simulator#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2cec195f5a3678b17745155536a1714d9b1a5694" "gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-solidity", "name": "@remix-project/remix-solidity",
"version": "0.5.5", "version": "0.5.6",
"description": "Tool to load and run Solidity compiler", "description": "Tool to load and run Solidity compiler",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -18,7 +18,7 @@
"@ethereumjs/block": "^3.5.1", "@ethereumjs/block": "^3.5.1",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.19", "@remix-project/remix-lib": "^0.5.20",
"async": "^2.6.2", "async": "^2.6.2",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"ethereumjs-util": "^7.0.10", "ethereumjs-util": "^7.0.10",
@ -61,5 +61,5 @@
}, },
"homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme", "homepage": "https://github.com/ethereum/remix-project/tree/master/libs/remix-solidity#readme",
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2cec195f5a3678b17745155536a1714d9b1a5694" "gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
} }

@ -52,7 +52,7 @@ export function canUseWorker (selectedVersion) {
} }
function browserSupportWorker () { function browserSupportWorker () {
return document.location.protocol !== 'file:' && Worker !== undefined return document ? document.location.protocol !== 'file:' && Worker !== undefined : false
} }
// returns a promise for minixhr // returns a promise for minixhr

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-tests", "name": "@remix-project/remix-tests",
"version": "0.2.19", "version": "0.2.20",
"description": "Tool to test Solidity smart contracts", "description": "Tool to test Solidity smart contracts",
"main": "src/index.js", "main": "src/index.js",
"types": "./src/index.d.ts", "types": "./src/index.d.ts",
@ -40,10 +40,10 @@
"@ethereumjs/common": "^2.5.0", "@ethereumjs/common": "^2.5.0",
"@ethereumjs/tx": "^3.3.2", "@ethereumjs/tx": "^3.3.2",
"@ethereumjs/vm": "^5.5.3", "@ethereumjs/vm": "^5.5.3",
"@remix-project/remix-lib": "^0.5.19", "@remix-project/remix-lib": "^0.5.20",
"@remix-project/remix-simulator": "^0.2.19", "@remix-project/remix-simulator": "^0.2.20",
"@remix-project/remix-solidity": "^0.5.5", "@remix-project/remix-solidity": "^0.5.6",
"@remix-project/remix-url-resolver": "^0.0.40", "@remix-project/remix-url-resolver": "^0.0.41",
"ansi-gray": "^0.1.1", "ansi-gray": "^0.1.1",
"async": "^2.6.0", "async": "^2.6.0",
"axios": "1.1.2", "axios": "1.1.2",
@ -81,5 +81,5 @@
"typescript": "^3.3.1" "typescript": "^3.3.1"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2cec195f5a3678b17745155536a1714d9b1a5694" "gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
} }

@ -0,0 +1,484 @@
import * as async from 'async'
import Web3 from 'web3';
import * as assert from 'assert'
import { Provider, extend } from '@remix-project/remix-simulator'
import { compileFileOrFiles } from '../src/compiler'
import { deployAll } from '../src/deployer'
import { runTest, compilationInterface } from '../src/index'
import { ResultsInterface, TestCbInterface, ResultCbInterface } from '../src/index'
// deepEqualExcluding allows us to exclude specific keys whose values vary.
// In this specific test, we'll use this helper to exclude `time` keys.
// Assertions for the existance of these will be made at the correct places.
function deepEqualExcluding(a: any, b: any, excludedKeys: string[]) {
function removeKeysFromObject(obj: any, excludedKeys: string[]) {
if (obj !== Object(obj)) {
return obj
}
if(Object.prototype.toString.call(obj) !== '[object Array]') {
obj = Object.assign({}, obj)
for (const key of excludedKeys) {
delete obj[key]
}
return obj
}
let newObj = []
for (const idx in obj) {
newObj[idx] = removeKeysFromObject(obj[idx], excludedKeys);
}
return newObj
}
let aStripped: any = removeKeysFromObject(a, excludedKeys);
let bStripped: any = removeKeysFromObject(b, excludedKeys);
assert.deepEqual(aStripped, bStripped)
}
let accounts: string[]
let provider: any = new Provider()
async function compileAndDeploy(filename: string, callback: Function) {
let web3: Web3 = new Web3()
let sourceASTs: any = {}
await provider.init()
web3.setProvider(provider)
extend(web3)
let compilationData: object
async.waterfall([
function getAccountList(next: Function): void {
web3.eth.getAccounts((_err: Error | null | undefined, _accounts: string[]) => {
accounts = _accounts
web3.eth.defaultAccount = accounts[0]
next(_err)
})
},
function compile(next: Function): void {
compileFileOrFiles(filename, false, { accounts }, null, next)
},
function deployAllContracts(compilationResult: compilationInterface, asts, next: Function): void {
for(const filename in asts) {
if(filename.endsWith('_test.sol'))
sourceASTs[filename] = asts[filename].ast
}
try {
compilationData = compilationResult
deployAll(compilationResult, web3, accounts, false, null, next)
} catch (e) {
throw e
}
}
], function (_err: Error | null | undefined, contracts: any): void {
callback(null, compilationData, contracts, sourceASTs, accounts, web3)
})
}
// Use `export NODE_OPTIONS="--max-old-space-size=4096"` if there is a JavaScript heap out of memory issue
describe('testRunner', () => {
let tests: any[] = [], results: ResultsInterface;
const testCallback: TestCbInterface = (err, test) => {
if (err) { throw err }
if (test.type === 'testPass' || test.type === 'testFailure') {
assert.ok(test.time, 'test time not reported')
assert.ok(!Number.isInteger(test.time || 0), 'test time should not be an integer')
}
tests.push(test)
}
const resultsCallback: Function = (done) => {
return (err, _results) => {
if (err) { throw err }
results = _results
done()
}
}
describe('#runTest', () => {
describe('assert library OK method tests', () => {
const filename: string = __dirname + '/examples_0/assert_ok_test.sol'
beforeAll((done) => {
compileAndDeploy(filename, (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) => {
runTest('AssertOkTest', contracts.AssertOkTest, compilationData[filename]['AssertOkTest'], asts[filename], { accounts, web3 }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should have 1 passing test', () => {
assert.equal(results.passingNum, 1)
})
it('should have 1 failing test', () => {
assert.equal(results.failureNum, 1)
})
const hhLogs1 = [ [ "AssertOkTest", "okPassTest"]]
const hhLogs2 = [ [ "AssertOkTest", "okFailTest"]]
it('should return', () => {
deepEqualExcluding(tests, [
{ type: 'accountList', value: accounts },
{ type: 'contract', value: 'AssertOkTest', filename: __dirname + '/examples_0/assert_ok_test.sol' },
{ type: 'testPass', debugTxHash: '0x5b665752a4faf83229259b9b2811d3295be0af633b0051d4b90042283ef55707', value: 'Ok pass test', filename: __dirname + '/examples_0/assert_ok_test.sol', context: 'AssertOkTest', hhLogs: hhLogs1 },
{ type: 'testFailure', debugTxHash: '0xa0a30ad042a7fc3495f72be7ba788d705888ffbbec7173f60bb27e07721510f2',value: 'Ok fail test', filename: __dirname + '/examples_0/assert_ok_test.sol', errMsg: 'okFailTest fails', context: 'AssertOkTest', hhLogs: hhLogs2, assertMethod: 'ok', location: '366:36:0', expected: 'true', returned: 'false'},
], ['time', 'web3'])
})
})
describe('assert library EQUAL method tests', () => {
const filename: string = __dirname + '/examples_0/assert_equal_test.sol'
beforeAll((done) => {
compileAndDeploy(filename, (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) => {
runTest('AssertEqualTest', contracts.AssertEqualTest, compilationData[filename]['AssertEqualTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should have 6 passing test', () => {
assert.equal(results.passingNum, 6)
})
it('should have 6 failing test', () => {
assert.equal(results.failureNum, 6)
})
it('should return', () => {
deepEqualExcluding(tests, [
{ type: 'accountList', value: accounts },
{ type: 'contract', value: 'AssertEqualTest', filename: __dirname + '/examples_0/assert_equal_test.sol' },
{ type: 'testPass', debugTxHash: '0xbe77baee10f8a044a68fbb83abf57ce1ca63b8739f3b2dcd122dab0ee44fd0e9', value: 'Equal uint pass test', filename: __dirname + '/examples_0/assert_equal_test.sol', context: 'AssertEqualTest' },
{ type: 'testFailure', debugTxHash: '0xfc9691b0cd0a49dbefed5cd3fad32158dd229e5bb7b0eb11da3c72054eafae8b', value: 'Equal uint fail test', filename: __dirname + '/examples_0/assert_equal_test.sol', errMsg: 'equalUintFailTest fails', context: 'AssertEqualTest', assertMethod: 'equal', location: '273:57:0', expected: '2', returned: '1'},
{ type: 'testPass', debugTxHash: '0xbc142789e5a51841781536a9291c9022896b0c7453140fdc98996638c0d76045', value: 'Equal int pass test', filename: __dirname + '/examples_0/assert_equal_test.sol', context: 'AssertEqualTest' },
{ type: 'testFailure', debugTxHash: '0xcc28211a4ab3149b1122fb47f45529a4edddbafa076d2338cc3754ab0629eaa1', value: 'Equal int fail test', filename: __dirname + '/examples_0/assert_equal_test.sol', errMsg: 'equalIntFailTest fails', context: 'AssertEqualTest', assertMethod: 'equal', location: '493:45:0', expected: '2', returned: '-1'},
{ type: 'testPass', debugTxHash: '0x4f5570fc7da86f09aafb436ff3b4c46aa885f71680a233234433d0ef0346206b', value: 'Equal bool pass test', filename: __dirname + '/examples_0/assert_equal_test.sol', context: 'AssertEqualTest' },
{ type: 'testFailure', debugTxHash: '0x28dc2d146dee77a2d6446efb088e5f9d008a3c7a14116e798401b68470da017f', value: 'Equal bool fail test', filename: __dirname + '/examples_0/assert_equal_test.sol', errMsg: 'equalBoolFailTest fails', context: 'AssertEqualTest', assertMethod: 'equal', location: '708:52:0', expected: false, returned: true},
{ type: 'testPass', debugTxHash: '0x0abc8fa8831efa3a8c82c758d045c1382f71b6a7f7e9135ffbe9e40059f84617', value: 'Equal address pass test', filename: __dirname + '/examples_0/assert_equal_test.sol', context: 'AssertEqualTest' },
{ type: 'testFailure', debugTxHash: '0x5ec200fb053539b8165a6b6ab36e9229a870c4752b0d6ff2786c4d5a66d5b35d', value: 'Equal address fail test', filename: __dirname + '/examples_0/assert_equal_test.sol', errMsg: 'equalAddressFailTest fails', context: 'AssertEqualTest', assertMethod: 'equal', location: '1015:130:0', expected: '0x1c6637567229159d1eFD45f95A6675e77727E013', returned: '0x7994f14563F39875a2F934Ce42cAbF48a93FdDA9'},
{ type: 'testPass', debugTxHash: '0xb6c34f5baa6916569d122bcb1210fcd07fb126f4b859fea58db5328e5f1dab85', value: 'Equal bytes32 pass test', filename: __dirname + '/examples_0/assert_equal_test.sol', context: 'AssertEqualTest' },
{ type: 'testFailure', debugTxHash: '0xb3af74a384b8b6ddacbc03a480ae48e233415b1570717ca7023530023a871be6', value: 'Equal bytes32 fail test', filename: __dirname + '/examples_0/assert_equal_test.sol', errMsg: 'equalBytes32FailTest fails', context: 'AssertEqualTest', assertMethod: 'equal', location: '1670:48:0', expected: '0x72656d6978000000000000000000000000000000000000000000000000000000', returned: '0x72656d6979000000000000000000000000000000000000000000000000000000'},
{ type: 'testPass', debugTxHash: '0x8537e74941b511b5c745b398e55435748adcdf637659247e0d573fb681ee4833', value: 'Equal string pass test', filename: __dirname + '/examples_0/assert_equal_test.sol', context: 'AssertEqualTest' },
{ type: 'testFailure', debugTxHash: '0x30d44498e63ac51f1412062b849144c103e19a4dc9daf81c5e84bd984ef738a6', value: 'Equal string fail test', filename: __dirname + '/examples_0/assert_equal_test.sol', errMsg: 'equalStringFailTest fails', context: 'AssertEqualTest', assertMethod: 'equal', location: '1916:81:0', expected: 'remix-tests', returned: 'remix'}
], ['time', 'web3'])
})
})
describe('assert library NOTEQUAL method tests', () => {
const filename: string = __dirname + '/examples_0/assert_notEqual_test.sol'
beforeAll((done) => {
compileAndDeploy(filename, (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) => {
runTest('AssertNotEqualTest', contracts.AssertNotEqualTest, compilationData[filename]['AssertNotEqualTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should have 6 passing test', () => {
assert.equal(results.passingNum, 6)
})
it('should have 6 failing test', () => {
assert.equal(results.failureNum, 6)
})
it('should return', () => {
deepEqualExcluding(tests, [
{ type: 'accountList', value: accounts },
{ type: 'contract', value: 'AssertNotEqualTest', filename: __dirname + '/examples_0/assert_notEqual_test.sol' },
{ type: 'testPass', debugTxHash: '0xb0ac5cde13a5005dc1b4efbb66fb3a5d6f0697467aedd6165ed1c8ee552939bc', value: 'Not equal uint pass test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', context: 'AssertNotEqualTest' },
{ type: 'testFailure', debugTxHash: '0x69d25ed9b9a010e97e0f7282313d4018c77a8873fd5f2e0b12483701febdd6b4', value: 'Not equal uint fail test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', errMsg: 'notEqualUintFailTest fails', context: 'AssertNotEqualTest', assertMethod: 'notEqual', location: '288:63:0', expected: '1', returned: '1'},
{ type: 'testPass', debugTxHash: '0x19a743cfb44b273c78b3271d603412d31087974309a70595bc5d15097a52c5c5', value: 'Not equal int pass test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', context: 'AssertNotEqualTest' },
{ type: 'testFailure', debugTxHash: '0x8dfce61b71a9ea65fb00c471370413779626f290b71d41f8be8408674e64f4d2', value: 'Not equal int fail test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', errMsg: 'notEqualIntFailTest fails', context: 'AssertNotEqualTest', assertMethod: 'notEqual', location: '525:52:0', expected: '-2', returned: '-2'},
{ type: 'testPass', debugTxHash: '0x12bc9eb3a653ebe4c7ab954c144dab4848295c88d71d17cb86a41e8a004419ba', value: 'Not equal bool pass test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', context: 'AssertNotEqualTest' },
{ type: 'testFailure', debugTxHash: '0x901b9cd631f8f29841243a257d1914060b9c36d88ee7d8b624de76dad4a34c85', value: 'Not equal bool fail test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', errMsg: 'notEqualBoolFailTest fails', context: 'AssertNotEqualTest', assertMethod: 'notEqual', location: '760:57:0', expected: true, returned: true},
{ type: 'testPass', debugTxHash: '0xf9e48bac26d3a2871ceb92974b897cae61e60bbc4db115b7e8eff4ac0390e4a5', value: 'Not equal address pass test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', context: 'AssertNotEqualTest' },
{ type: 'testFailure', debugTxHash: '0x4e83a17426bc79b147ddd30a5434a2430a8302b3ce78b6979088ac5eceac5d3c', value: 'Not equal address fail test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', errMsg: 'notEqualAddressFailTest fails', context: 'AssertNotEqualTest', assertMethod: 'notEqual', location: '1084:136:0', expected: 0x7994f14563F39875a2F934Ce42cAbF48a93FdDA9, returned: 0x7994f14563F39875a2F934Ce42cAbF48a93FdDA9},
{ type: 'testPass', debugTxHash: '0xfb4b30bb8373eeb6b09e38ad07880b86654f72780b41d7cf7554a112a04a0f53', value: 'Not equal bytes32 pass test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', context: 'AssertNotEqualTest' },
{ type: 'testFailure', debugTxHash: '0x43edf8bc68229415ee63f63f5303351a0dfecf9f3eb2ec35ffd2c10c60cf7005', value: 'Not equal bytes32 fail test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', errMsg: 'notEqualBytes32FailTest fails', context: 'AssertNotEqualTest', assertMethod: 'notEqual', location: '1756:54:0', expected: '0x72656d6978000000000000000000000000000000000000000000000000000000', returned: '0x72656d6978000000000000000000000000000000000000000000000000000000'},
{ type: 'testPass', debugTxHash: '0x712932edc040596e2b02ddc06a48b773f5fccc7346d55cefc5d1c52528ce3168', value: 'Not equal string pass test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', context: 'AssertNotEqualTest' },
{ type: 'testFailure', debugTxHash: '0x961ce7425fdd4049aeb678ea20a0441eb837c1fe26b0d010593fc07d668321e6', value: 'Not equal string fail test', filename: __dirname + '/examples_0/assert_notEqual_test.sol', errMsg: 'notEqualStringFailTest fails', context: 'AssertNotEqualTest', assertMethod: 'notEqual', location: '2026:81:0', expected: 'remix', returned: 'remix'},
], ['time', 'web3'])
})
})
describe('assert library GREATERTHAN method tests', () => {
const filename: string = __dirname + '/examples_0/assert_greaterThan_test.sol'
beforeAll((done) => {
compileAndDeploy(filename, (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) => {
runTest('AssertGreaterThanTest', contracts.AssertGreaterThanTest, compilationData[filename]['AssertGreaterThanTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should have 4 passing test', () => {
assert.equal(results.passingNum, 4)
})
it('should have 4 failing test', () => {
assert.equal(results.failureNum, 4)
})
it('should return', () => {
deepEqualExcluding(tests, [
{ type: 'accountList', value: accounts },
{ type: 'contract', value: 'AssertGreaterThanTest', filename: __dirname + '/examples_0/assert_greaterThan_test.sol' },
{ type: 'testPass', debugTxHash: '0x81cf46560b522280ac60bd5c8efedad4598957d33435a898c23eefb13ca6104f', value: 'Greater than uint pass test', filename: __dirname + '/examples_0/assert_greaterThan_test.sol', context: 'AssertGreaterThanTest' },
{ type: 'testFailure', debugTxHash: '0x7090dc27ac06e1afd66963992bdd9188200d0404d43b95cfa5d925bbe6eba3ed', value: 'Greater than uint fail test', filename: __dirname + '/examples_0/assert_greaterThan_test.sol', errMsg: 'greaterThanUintFailTest fails', context: 'AssertGreaterThanTest', assertMethod: 'greaterThan', location: '303:69:0', expected: '4', returned: '1'},
{ type: 'testPass', debugTxHash: '0xd57b40ed464763baf128f8a72efcd198e825e0b8f498cef90aed23045d0196db', value: 'Greater than int pass test', filename: __dirname + '/examples_0/assert_greaterThan_test.sol', context: 'AssertGreaterThanTest' },
{ type: 'testFailure', debugTxHash: '0x6c7b84bd8fc452b7839e11129d3818fa69dfd9b914e55556b39fdc68b70fc1b5', value: 'Greater than int fail test', filename: __dirname + '/examples_0/assert_greaterThan_test.sol', errMsg: 'greaterThanIntFailTest fails', context: 'AssertGreaterThanTest', assertMethod: 'greaterThan', location: '569:67:0', expected: '1', returned: '-1'},
{ type: 'testPass', debugTxHash: '0xc5883db70b83a1d3afff24a9f0555de3edd7776e5ec157cc2110e426e5be2594', value: 'Greater than uint int pass test', filename: __dirname + '/examples_0/assert_greaterThan_test.sol', context: 'AssertGreaterThanTest' },
{ type: 'testFailure', debugTxHash: '0xa534085a6bbdcf73a68bdef6a1421218c11ac0ec1af398f9445defeea31cea6e', value: 'Greater than uint int fail test', filename: __dirname + '/examples_0/assert_greaterThan_test.sol', errMsg: 'greaterThanUintIntFailTest fails', context: 'AssertGreaterThanTest', assertMethod: 'greaterThan', location: '845:71:0', expected: '2', returned: '1'},
{ type: 'testPass', debugTxHash: '0x36a7139445d76f6072fab4cc0717461068276748622c0dfc3f092d548b197de8', value: 'Greater than int uint pass test', filename: __dirname + '/examples_0/assert_greaterThan_test.sol', context: 'AssertGreaterThanTest' },
{ type: 'testFailure', debugTxHash: '0x7890f7b8f2eabae581b6f70d55d2d3bfa64ddd7753d5f892dbdf86ced96945fe', value: 'Greater than int uint fail test', filename: __dirname + '/examples_0/assert_greaterThan_test.sol', errMsg: 'greaterThanIntUintFailTest fails', context: 'AssertGreaterThanTest', assertMethod: 'greaterThan', location: '1125:76:0', expected: '115792089237316195423570985008687907853269984665640564039457584007913129639836', returned: '100'}
], ['time', 'web3'])
})
})
describe('assert library LESSERTHAN method tests', () => {
const filename: string = __dirname + '/examples_0/assert_lesserThan_test.sol'
beforeAll((done) => {
compileAndDeploy(filename, (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) => {
runTest('AssertLesserThanTest', contracts.AssertLesserThanTest, compilationData[filename]['AssertLesserThanTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should have 4 passing test', () => {
assert.equal(results.passingNum, 4)
})
it('should have 4 failing test', () => {
assert.equal(results.failureNum, 4)
})
it('should return', () => {
deepEqualExcluding(tests, [
{ type: 'accountList', value: accounts },
{ type: 'contract', value: 'AssertLesserThanTest', filename: __dirname + '/examples_0/assert_lesserThan_test.sol' },
{ type: 'testPass', debugTxHash: '0x47875047c1fff8a7b1cc1603418960cd2a3afe8a9c59337b19fb463a85d6e47e', value: 'Lesser than uint pass test', filename: __dirname + '/examples_0/assert_lesserThan_test.sol', context: 'AssertLesserThanTest' },
{ type: 'testFailure', debugTxHash: '0xf6fd459d0b28d0d85c56dd69d953331291e1c234c8a263150252e35da0ed6671', value: 'Lesser than uint fail test', filename: __dirname + '/examples_0/assert_lesserThan_test.sol', errMsg: 'lesserThanUintFailTest fails', context: 'AssertLesserThanTest', assertMethod: 'lesserThan', location: '298:67:0', expected: '2', returned: '4'},
{ type: 'testPass', debugTxHash: '0x761d95111764af396634474899ff1db218d5e514d6de6bc3260af15b1f5b929f', value: 'Lesser than int pass test', filename: __dirname + '/examples_0/assert_lesserThan_test.sol', context: 'AssertLesserThanTest' },
{ type: 'testFailure', debugTxHash: '0xc17697ef2df92c22707639caa9355bdf0d98dbb18157e72b8b257bb0eb2beb4e', value: 'Lesser than int fail test', filename: __dirname + '/examples_0/assert_lesserThan_test.sol', errMsg: 'lesserThanIntFailTest fails', context: 'AssertLesserThanTest', assertMethod: 'lesserThan', location: '557:65:0', expected: '-1', returned: '1'},
{ type: 'testPass', debugTxHash: '0xf0721b28c547c1c64948661d677cf6afc10d139315726280162a984f2f7e5d9c', value: 'Lesser than uint int pass test', filename: __dirname + '/examples_0/assert_lesserThan_test.sol', context: 'AssertLesserThanTest' },
{ type: 'testFailure', debugTxHash: '0x0757289229b58043c101cb311df8db16d1b30944747493e1491daa9aca6aa30e', value: 'Lesser than uint int fail test', filename: __dirname + '/examples_0/assert_lesserThan_test.sol', errMsg: 'lesserThanUintIntFailTest fails', context: 'AssertLesserThanTest', assertMethod: 'lesserThan', location: '826:71:0', expected: '-1', returned: '115792089237316195423570985008687907853269984665640564039457584007913129639935'},
{ type: 'testPass', debugTxHash: '0x316feb8f80c04b12194262dd80b6b004232eab00d7f7c3846badf6e92684e177', value: 'Lesser than int uint pass test', filename: __dirname + '/examples_0/assert_lesserThan_test.sol', context: 'AssertLesserThanTest' },
{ type: 'testFailure', debugTxHash: '0x65c5ab3cb85f163eefe3321cc4444defa99154d3cbe415b9384bbd2627449b6a', value: 'Lesser than int uint fail test', filename: __dirname + '/examples_0/assert_lesserThan_test.sol', errMsg: 'lesserThanIntUintFailTest fails', context: 'AssertLesserThanTest', assertMethod: 'lesserThan', location: '1105:69:0', expected: '1', returned: '1'},
], ['time', 'web3'])
})
})
describe('test with beforeAll', () => {
const filename: string = __dirname + '/examples_1/simple_storage_test.sol'
beforeAll((done) => {
compileAndDeploy(filename, (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) => {
runTest('MyTest', contracts.MyTest, compilationData[filename]['MyTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should have 3 passing test', () => {
assert.equal(results.passingNum, 3)
})
it('should have 1 failing test', () => {
assert.equal(results.failureNum, 1)
})
it('should return 6 messages', () => {
deepEqualExcluding(tests, [
{ type: 'accountList', value: accounts },
{ type: 'contract', value: 'MyTest', filename: __dirname + '/examples_1/simple_storage_test.sol' },
{ type: 'testPass', debugTxHash: '0x5a805403a12f0431c5dd190d31a87eb62758f09dddc0c6ee7ee6899c5e7eba71', value: 'Initial value should be100', filename: __dirname + '/examples_1/simple_storage_test.sol', context: 'MyTest' },
{ type: 'testPass', debugTxHash: '0xd0ae7cb5a3a0f5e8f7bf90129e3daba36f649a5c1176ad54609f7b7615bef7dd', value: 'Initial value should not be200', filename: __dirname + '/examples_1/simple_storage_test.sol', context: 'MyTest' },
{ type: 'testFailure', debugTxHash: '0xb0d613434f2fd7060f97d4ca8bbfd8f2deeebed83062a25044f0237bd38b3229', value: 'Should trigger one fail', filename: __dirname + '/examples_1/simple_storage_test.sol', errMsg: 'uint test 1 fails', context: 'MyTest', assertMethod: 'equal', location: '532:51:1', expected: '2', returned: '1'},
{ type: 'testPass', debugTxHash: '0x5ee675ec81b550386b2fdd359ae3530e49dd3e02145e877a4a5f68753ac4e341', value: 'Should trigger one pass', filename: __dirname + '/examples_1/simple_storage_test.sol', context: 'MyTest' }
], ['time', 'web3'])
})
})
describe('test with beforeEach', () => {
const filename: string = __dirname + '/examples_2/simple_storage_test.sol'
beforeAll(done => {
compileAndDeploy(filename, function (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) {
runTest('MyTest', contracts.MyTest, compilationData[filename]['MyTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should have 2 passing tests', () => {
assert.equal(results.passingNum, 2)
})
it('should 0 failing tests', () => {
assert.equal(results.failureNum, 0)
})
it('should return 4 messages', () => {
deepEqualExcluding(tests, [
{ type: 'accountList', value: accounts },
{ type: 'contract', value: 'MyTest', filename: __dirname + '/examples_2/simple_storage_test.sol' },
{ type: 'testPass', debugTxHash: '0xa700d29204f1ddb40ef66f151c44387f905d405b6da10380111a751451af2fe1', value: 'Initial value should be100', filename: __dirname + '/examples_2/simple_storage_test.sol', context: 'MyTest' },
{ type: 'testPass', debugTxHash: '0x2c037b78a435e5964615f838ea65f077f3b15d8552d514b3551d0fb87419e444', value: 'Value is set200', filename: __dirname + '/examples_2/simple_storage_test.sol', context: 'MyTest' }
], ['time', 'web3'])
})
})
// Test string equality
describe('test string equality', () => {
const filename: string = __dirname + '/examples_3/simple_string_test.sol'
beforeAll(done => {
compileAndDeploy(filename, function (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) {
runTest('StringTest', contracts.StringTest, compilationData[filename]['StringTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should 2 passing tests', () => {
assert.equal(results.passingNum, 2)
})
it('should return 4 messages', () => {
deepEqualExcluding(tests, [
{ type: 'accountList', value: accounts },
{ type: 'contract', value: 'StringTest', filename: __dirname + '/examples_3/simple_string_test.sol' },
{ type: 'testPass', debugTxHash: '0x4e160dbc81f88d3d87b39d81651c42b0ea8e3aaa10c1a57394467e073bbcb2a4', value: 'Initial value should be hello world', filename: __dirname + '/examples_3/simple_string_test.sol', context: 'StringTest' },
{ type: 'testPass', debugTxHash: '0x47030578c5bcb990d837356430697d061a02813e3322fa3323f6b5f78176eea6', value: 'Value should not be hello wordl', filename: __dirname + '/examples_3/simple_string_test.sol', context: 'StringTest' }
], ['time', 'web3'])
})
})
// Test multiple directory import in test contract
describe('test multiple directory import in test contract', () => {
const filename: string = __dirname + '/examples_5/test/simple_storage_test.sol'
beforeAll(done => {
compileAndDeploy(filename, function (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) {
runTest('StorageResolveTest', contracts.StorageResolveTest, compilationData[filename]['StorageResolveTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should 3 passing tests', () => {
assert.equal(results.passingNum, 3)
})
it('should return 4 messages', () => {
deepEqualExcluding(tests, [
{ type: 'accountList', value: accounts },
{ type: 'contract', value: 'StorageResolveTest', filename: __dirname + '/examples_5/test/simple_storage_test.sol' },
{ type: 'testPass', debugTxHash: '0x85e901e9160c4a17725d020f030c7cbb020d36da1fda8422d990391df3cbfcbb', value: 'Initial value should be100', filename: __dirname + '/examples_5/test/simple_storage_test.sol', context: 'StorageResolveTest' },
{ type: 'testPass', debugTxHash: '0x1abb2456746b416cddcaf2f3fe960103e740e9772c47a0f1d65d48394facb21a', value: 'Check if even', filename: __dirname + '/examples_5/test/simple_storage_test.sol', context: 'StorageResolveTest' },
{ type: 'testPass', debugTxHash: '0xfcc2332a24d2780390e85a06343fab81c4dc20c12cf5455d746641a9c3e8db03', value: 'Check if odd', filename: __dirname + '/examples_5/test/simple_storage_test.sol', context: 'StorageResolveTest' }
], ['time', 'web3'])
})
})
//Test SafeMath library methods
describe('test SafeMath library', () => {
const filename: string = __dirname + '/examples_4/SafeMath_test.sol'
beforeAll(done => {
compileAndDeploy(filename, function (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) {
runTest('SafeMathTest', contracts.SafeMathTest, compilationData[filename]['SafeMathTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should have 10 passing tests', () => {
assert.equal(results.passingNum, 10)
})
it('should have 0 failing tests', () => {
assert.equal(results.failureNum, 0)
})
})
//Test signed/unsigned integer weight
describe('test number weight', () => {
const filename: string = __dirname + '/number/number_test.sol'
beforeAll(done => {
compileAndDeploy(filename, function (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) {
runTest('IntegerTest', contracts.IntegerTest, compilationData[filename]['IntegerTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should have 6 passing tests', () => {
assert.equal(results.passingNum, 6)
})
it('should have 2 failing tests', () => {
assert.equal(results.failureNum, 2)
})
})
// Test Transaction with custom sender & value
describe('various sender', () => {
const filename: string = __dirname + '/various_sender/sender_and_value_test.sol'
beforeAll(done => {
compileAndDeploy(filename, function (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) {
runTest('SenderAndValueTest', contracts.SenderAndValueTest, compilationData[filename]['SenderAndValueTest'], asts[filename], { accounts }, testCallback, resultsCallback(done))
})
})
afterAll(() => { tests = [] })
it('should have 17 passing tests', () => {
assert.equal(results.passingNum, 17)
})
it('should have 0 failing tests', () => {
assert.equal(results.failureNum, 0)
})
})
// Test `runTest` method without sending contract object (should throw error)
describe('runTest method without contract json interface', () => {
const filename: string = __dirname + '/various_sender/sender_and_value_test.sol'
const errorCallback: Function = (done) => {
return (err, _results) => {
if (err && err.message.includes('Contract interface not available')) {
results = _results
done()
}
else throw err
}
}
beforeAll(done => {
compileAndDeploy(filename, function (_err: Error | null | undefined, compilationData: object, contracts: any, asts: any, accounts: string[], web3: any) {
runTest('SenderAndValueTest', undefined, compilationData[filename]['SenderAndValueTest'], asts[filename], { accounts }, testCallback, errorCallback(done))
})
})
it('should have 0 passing tests', () => {
assert.equal(results.passingNum, 0)
})
it('should have 0 failing tests', () => {
assert.equal(results.failureNum, 0)
})
})
})
})

@ -1,6 +1,11 @@
import React, { useContext, useEffect, useState } from 'react' import React, { useContext, useEffect, useState } from 'react'
import { AppContext } from '../../context/context' import { AppContext } from '../../context/context'
import { useDialogDispatchers } from '../../context/provider' import { useDialogDispatchers } from '../../context/provider'
declare global {
interface Window {
_paq: any
}
}
const _paq = window._paq = window._paq || [] const _paq = window._paq = window._paq || []
const MatomoDialog = (props) => { const MatomoDialog = (props) => {

@ -121,7 +121,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
}) })
}) })
debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address) => { debuggerInstance.event.register('newSourceLocation', async (lineColumnPos, rawLocation, generatedSources, address, stepDetail, lineGasCost) => {
if (!lineColumnPos) { if (!lineColumnPos) {
await debuggerModule.discardHighlight() await debuggerModule.discardHighlight()
setState(prevState => { setState(prevState => {
@ -158,7 +158,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
return { ...prevState, sourceLocationStatus: '' } return { ...prevState, sourceLocationStatus: '' }
}) })
await debuggerModule.discardHighlight() await debuggerModule.discardHighlight()
await debuggerModule.highlight(lineColumnPos, path) await debuggerModule.highlight(lineColumnPos, path, rawLocation, stepDetail, lineGasCost)
} }
} }
}) })
@ -266,13 +266,14 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
console.log(e.message) console.log(e.message)
} }
const localCache = {}
const debuggerInstance = new Debugger({ const debuggerInstance = new Debugger({
web3, web3,
offsetToLineColumnConverter: debuggerModule.offsetToLineColumnConverter, offsetToLineColumnConverter: debuggerModule.offsetToLineColumnConverter,
compilationResult: async (address) => { compilationResult: async (address) => {
try { try {
const ret = await debuggerModule.fetchContractAndCompile(address, currentReceipt) if (!localCache[address]) localCache[address] = await debuggerModule.fetchContractAndCompile(address, currentReceipt)
return ret return localCache[address]
} catch (e) { } catch (e) {
// debuggerModule.showMessage('Debugging error', 'Unable to fetch a transaction.') // debuggerModule.showMessage('Debugging error', 'Unable to fetch a transaction.')
console.error(e) console.error(e)
@ -395,8 +396,8 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
{ state.debugging && <StepManager stepManager={ stepManager } /> } { state.debugging && <StepManager stepManager={ stepManager } /> }
</div> </div>
<div className="debuggerPanels" ref={panelsRef}> <div className="debuggerPanels" ref={panelsRef}>
{ state.debugging && <VmDebuggerHead vmDebugger={ vmDebugger } /> } { state.debugging && <VmDebuggerHead debugging={state.debugging} vmDebugger={ vmDebugger } /> }
{ state.debugging && <VmDebugger vmDebugger={ vmDebugger } currentBlock={ state.currentBlock } currentReceipt={ state.currentReceipt } currentTransaction={ state.currentTransaction } /> } { state.debugging && <VmDebugger debugging={state.debugging} vmDebugger={ vmDebugger } currentBlock={ state.currentBlock } currentReceipt={ state.currentReceipt } currentTransaction={ state.currentTransaction } /> }
</div> </div>
</div> </div>
) )

@ -44,7 +44,7 @@ export interface IDebuggerApi {
onEditorContentChanged: (listener: onEditorContentChanged) => void onEditorContentChanged: (listener: onEditorContentChanged) => void
onEnvChanged: (listener: onEnvChangedListener) => void onEnvChanged: (listener: onEnvChangedListener) => void
discardHighlight: () => Promise<void> discardHighlight: () => Promise<void>
highlight: (lineColumnPos: LineColumnLocation, path: string) => Promise<void> highlight: (lineColumnPos: LineColumnLocation, path: string, rawLocation: any, stepDetail: any, highlight: any) => Promise<void>
fetchContractAndCompile: (address: string, currentReceipt: TransactionReceipt) => Promise<CompilerAbstract> fetchContractAndCompile: (address: string, currentReceipt: TransactionReceipt) => Promise<CompilerAbstract>
getFile: (path: string) => Promise<string> getFile: (path: string) => Promise<string>
setFile: (path: string, content: string) => Promise<void> setFile: (path: string, content: string) => Promise<void>

@ -1,3 +1,4 @@
import { stateDecoder } from 'dist/libs/remix-debug/src/solidity-decoder'
import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line import React, { useState, useRef, useEffect, useReducer } from 'react' // eslint-disable-line
import { initialState, reducer } from '../../reducers/assembly-items' import { initialState, reducer } from '../../reducers/assembly-items'
import './styles/assembly-items.css' import './styles/assembly-items.css'
@ -9,6 +10,7 @@ export const AssemblyItems = ({ registerEvent }) => {
const [nextSelectedItems, setNextSelectedItems] = useState([1]) const [nextSelectedItems, setNextSelectedItems] = useState([1])
const [returnInstructionIndexes, setReturnInstructionIndexes] = useState([]) const [returnInstructionIndexes, setReturnInstructionIndexes] = useState([])
const [outOfGasInstructionIndexes, setOutOfGasInstructionIndexes] = useState([]) const [outOfGasInstructionIndexes, setOutOfGasInstructionIndexes] = useState([])
const [opcodeTooltipText, setOpcodeTooltipText] = useState('')
const refs = useRef({}) const refs = useRef({})
const asmItemsRef = useRef(null) const asmItemsRef = useRef(null)
@ -16,6 +18,10 @@ export const AssemblyItems = ({ registerEvent }) => {
registerEvent && registerEvent('codeManagerChanged', (code, address, index, nextIndexes, returnInstructionIndexes, outOfGasInstructionIndexes) => { registerEvent && registerEvent('codeManagerChanged', (code, address, index, nextIndexes, returnInstructionIndexes, outOfGasInstructionIndexes) => {
dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index, nextIndexes, returnInstructionIndexes, outOfGasInstructionIndexes } }) dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index, nextIndexes, returnInstructionIndexes, outOfGasInstructionIndexes } })
}) })
registerEvent && registerEvent('lineGasCostChanged', (instructionsIndexes: number[], line: []) => {
dispatch({ type: 'FETCH_INDEXES_FOR_NEW_LINE', payload: { currentLineIndexes: instructionsIndexes || [], line } })
})
}, []) }, [])
useEffect(() => { useEffect(() => {
@ -129,7 +135,9 @@ export const AssemblyItems = ({ registerEvent }) => {
<div className="pl-2 my-1 small instructions" data-id="asmitems" id='asmitems' ref={asmItemsRef}> <div className="pl-2 my-1 small instructions" data-id="asmitems" id='asmitems' ref={asmItemsRef}>
{ {
assemblyItems.display.map((item, i) => { assemblyItems.display.map((item, i) => {
return <div className="px-1" key={i} ref={ref => { refs.current[i] = ref }}><span>{item}</span></div> return <div className="px-1" key={i} ref={ref => { refs.current[i] = ref }}>
<span>{item}</span>{assemblyItems.currentLineIndexes.includes(i) ? <span><i><b> - LINE {assemblyItems.line + 1}</b></i></span> : ' - '}
</div>
}) })
} }
</div> </div>

@ -5,7 +5,7 @@ import StepDetail from './step-detail' // eslint-disable-line
import SolidityState from './solidity-state' // eslint-disable-line import SolidityState from './solidity-state' // eslint-disable-line
import SolidityLocals from './solidity-locals' // eslint-disable-line import SolidityLocals from './solidity-locals' // eslint-disable-line
export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent } }) => { export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent }, debugging }) => {
const [functionPanel, setFunctionPanel] = useState(null) const [functionPanel, setFunctionPanel] = useState(null)
const [stepDetail, setStepDetail] = useState({ const [stepDetail, setStepDetail] = useState({
'vm trace step': '-', 'vm trace step': '-',
@ -31,7 +31,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent } })
const functions = [] const functions = []
for (const func of stack) { for (const func of stack) {
functions.push(func.functionDefinition.name + '(' + func.inputs.join(', ') + ')') functions.push((func.functionDefinition.name || func.functionDefinition.kind) + '(' + func.inputs.join(', ') + ')' + ' - ' + func.gasCost + ' gas')
} }
setFunctionPanel(() => functions) setFunctionPanel(() => functions)
}) })
@ -95,7 +95,7 @@ export const VmDebuggerHead = ({ vmDebugger: { registerEvent, triggerEvent } })
return { ...solidityLocals, message } return { ...solidityLocals, message }
}) })
}) })
}, [registerEvent]) }, [debugging])
return ( return (
<div id='vmheadView' className="mt-1 px-2 d-flex"> <div id='vmheadView' className="mt-1 px-2 d-flex">

@ -8,7 +8,7 @@ import ReturnValuesPanel from './dropdown-panel' // eslint-disable-line
import FullStoragesChangesPanel from './full-storages-changes' // eslint-disable-line import FullStoragesChangesPanel from './full-storages-changes' // eslint-disable-line
import GlobalVariables from './global-variables' // eslint-disable-line import GlobalVariables from './global-variables' // eslint-disable-line
export const VmDebugger = ({ vmDebugger: { registerEvent }, currentBlock, currentReceipt, currentTransaction }) => { export const VmDebugger = ({ vmDebugger: { registerEvent }, currentBlock, currentReceipt, currentTransaction, debugging }) => {
const [calldataPanel, setCalldataPanel] = useState(null) const [calldataPanel, setCalldataPanel] = useState(null)
const [memoryPanel, setMemoryPanel] = useState(null) const [memoryPanel, setMemoryPanel] = useState(null)
const [callStackPanel, setCallStackPanel] = useState(null) const [callStackPanel, setCallStackPanel] = useState(null)
@ -49,7 +49,7 @@ export const VmDebugger = ({ vmDebugger: { registerEvent }, currentBlock, curren
registerEvent && registerEvent('traceStorageUpdate', (calldata) => { registerEvent && registerEvent('traceStorageUpdate', (calldata) => {
setFullStoragesChangesPanel(() => calldata) setFullStoragesChangesPanel(() => calldata)
}) })
}, [registerEvent]) }, [debugging])
return ( return (
<div id='vmdebugger' className="d-flex"> <div id='vmdebugger' className="d-flex">

@ -13,6 +13,7 @@ export const initialState = {
}, },
display: [], display: [],
index: 0, index: 0,
initialIndex: 0,
nextIndexes: [-1], nextIndexes: [-1],
returnInstructionIndexes: [], returnInstructionIndexes: [],
outOfGasInstructionIndexes: [], outOfGasInstructionIndexes: [],
@ -20,7 +21,10 @@ export const initialState = {
bottom: 0, bottom: 0,
isRequesting: false, isRequesting: false,
isSuccessful: false, isSuccessful: false,
hasError: null hasError: null,
absoluteCurrentLineIndexes: [],
currentLineIndexes: [],
line: -1
} }
const reducedOpcode = (opCodes, payload) => { const reducedOpcode = (opCodes, payload) => {
@ -31,6 +35,7 @@ const reducedOpcode = (opCodes, payload) => {
return { return {
index: opCodes.index - bottom, index: opCodes.index - bottom,
nextIndexes: opCodes.nextIndexes.map(index => index - bottom), nextIndexes: opCodes.nextIndexes.map(index => index - bottom),
currentLineIndexes: (opCodes.absoluteCurrentLineIndexes && opCodes.absoluteCurrentLineIndexes.map(index => index - bottom)) || [],
display: opCodes.code.slice(bottom, top), display: opCodes.code.slice(bottom, top),
returnInstructionIndexes: payload.returnInstructionIndexes.map((index) => index.instructionIndex - bottom), returnInstructionIndexes: payload.returnInstructionIndexes.map((index) => index.instructionIndex - bottom),
outOfGasInstructionIndexes: payload.outOfGasInstructionIndexes.map((index) => index.instructionIndex - bottom) outOfGasInstructionIndexes: payload.outOfGasInstructionIndexes.map((index) => index.instructionIndex - bottom)
@ -49,20 +54,23 @@ export const reducer = (state = initialState, action: Action) => {
} }
case 'FETCH_OPCODES_SUCCESS': { case 'FETCH_OPCODES_SUCCESS': {
const opCodes = action.payload.address === state.opCodes.address ? { const opCodes = action.payload.address === state.opCodes.address ? {
...state.opCodes, index: action.payload.index, nextIndexes: action.payload.nextIndexes ...state.opCodes, index: action.payload.index, nextIndexes: action.payload.nextIndexes, absoluteCurrentLineIndexes: state.absoluteCurrentLineIndexes
} : deepEqual(action.payload.code, state.opCodes.code) ? state.opCodes : action.payload } : deepEqual(action.payload.code, state.opCodes.code) ? state.opCodes : action.payload
const reduced = reducedOpcode(opCodes, action.payload) const reduced = reducedOpcode(opCodes, action.payload)
return { return {
...state,
opCodes, opCodes,
display: reduced.display, display: reduced.display,
initialIndex: action.payload.index,
index: reduced.index, index: reduced.index,
nextIndexes: reduced.nextIndexes, nextIndexes: reduced.nextIndexes,
isRequesting: false, isRequesting: false,
isSuccessful: true, isSuccessful: true,
hasError: null, hasError: null,
returnInstructionIndexes: reduced.returnInstructionIndexes, returnInstructionIndexes: reduced.returnInstructionIndexes,
outOfGasInstructionIndexes: reduced.outOfGasInstructionIndexes outOfGasInstructionIndexes: reduced.outOfGasInstructionIndexes,
currentLineIndexes: reduced.currentLineIndexes
} }
} }
case 'FETCH_OPCODES_ERROR': { case 'FETCH_OPCODES_ERROR': {
@ -73,6 +81,16 @@ export const reducer = (state = initialState, action: Action) => {
hasError: action.payload hasError: action.payload
} }
} }
case 'FETCH_INDEXES_FOR_NEW_LINE': {
let bottom = state.initialIndex - 10
bottom = bottom < 0 ? 0 : bottom
return {
...state,
absoluteCurrentLineIndexes: action.payload.currentLineIndexes,
currentLineIndexes: action.payload.currentLineIndexes.map(index => index - bottom),
line: action.payload.line
}
}
default: default:
throw new Error() throw new Error()
} }

@ -5,6 +5,7 @@ import { CompilerContainerProps } from './types'
import { ConfigurationSettings } from '@remix-project/remix-lib-ts' import { ConfigurationSettings } from '@remix-project/remix-lib-ts'
import { checkSpecialChars, CustomTooltip, extractNameFromKey } from '@remix-ui/helper' import { checkSpecialChars, CustomTooltip, extractNameFromKey } from '@remix-ui/helper'
import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promisedMiniXhr } from '@remix-project/remix-solidity' import { canUseWorker, baseURLBin, baseURLWasm, urlFromVersion, pathToURL, promisedMiniXhr } from '@remix-project/remix-solidity'
import { compilerReducer, compilerInitialState } from './reducers/compiler' import { compilerReducer, compilerInitialState } from './reducers/compiler'
import { resetEditorMode, listenToEvents } from './actions/compiler' import { resetEditorMode, listenToEvents } from './actions/compiler'
import { getValidLanguage } from '@remix-project/remix-solidity' import { getValidLanguage } from '@remix-project/remix-solidity'
@ -732,6 +733,7 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
setToggleExpander(!toggleExpander) setToggleExpander(!toggleExpander)
} }
return ( return (
<section> <section>
<article> <article>
@ -944,9 +946,13 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
{(configFilePath === '' && state.useFileConfiguration) && <div> No config file selected</div>} {(configFilePath === '' && state.useFileConfiguration) && <div> No config file selected</div>}
</div>} </div>}
> >
<div className="d-flex align-items-center justify-content-center">
{ <i ref={compileIcon} className="fas fa-sync remixui_iconbtn ml-4" aria-hidden="true"></i> }
<div className="d-flex justify-content-between align-items-center">
<span> <span>
{ <i ref={compileIcon} className="fas fa-sync remixui_iconbtn" aria-hidden="true"></i> }
<FormattedMessage id='solidity.compile' defaultMessage='Compile' /> <FormattedMessage id='solidity.compile' defaultMessage='Compile' />
</span>
<span className="ml-1">
{typeof state.compiledFileName === 'string' {typeof state.compiledFileName === 'string'
? extractNameFromKey(state.compiledFileName) || ? extractNameFromKey(state.compiledFileName) ||
`<${intl.formatMessage({ `<${intl.formatMessage({
@ -958,6 +964,8 @@ export const CompilerContainer = (props: CompilerContainerProps) => {
defaultMessage: 'no file selected', defaultMessage: 'no file selected',
})}>`} })}>`}
</span> </span>
</div>
</div>
</CustomTooltip> </CustomTooltip>
</button> </button>
<div className='d-flex align-items-center'> <div className='d-flex align-items-center'>

@ -259,7 +259,7 @@ export const SolidityUnitTesting = (props: Record<string, any>) => { // eslint-d
finalLogs = finalLogs + '&emsp;' + formattedLog + '\n' finalLogs = finalLogs + '&emsp;' + formattedLog + '\n'
} }
_paq.push(['trackEvent', 'solidityUnitTesting', 'hardhat', 'console.log']) _paq.push(['trackEvent', 'solidityUnitTesting', 'hardhat', 'console.log'])
testTab.call('terminal', 'log', { type: 'log', value: finalLogs }) testTab.call('terminal', 'logHtml', { type: 'log', value: finalLogs })
} }
const discardHighlight = async () => { const discardHighlight = async () => {

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-url-resolver", "name": "@remix-project/remix-url-resolver",
"version": "0.0.40", "version": "0.0.41",
"description": "Solidity import url resolver engine", "description": "Solidity import url resolver engine",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -41,5 +41,5 @@
"typescript": "^3.1.6" "typescript": "^3.1.6"
}, },
"typings": "src/index.d.ts", "typings": "src/index.d.ts",
"gitHead": "2cec195f5a3678b17745155536a1714d9b1a5694" "gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
} }

@ -1,6 +1,6 @@
{ {
"name": "@remix-project/remix-ws-templates", "name": "@remix-project/remix-ws-templates",
"version": "1.0.6", "version": "1.0.7",
"description": "Create a Remix IDE workspace using different templates", "description": "Create a Remix IDE workspace using different templates",
"main": "src/index.js", "main": "src/index.js",
"types": "src/index.d.ts", "types": "src/index.d.ts",
@ -27,5 +27,5 @@
"ethers": "^5.4.2", "ethers": "^5.4.2",
"web3": "^1.5.1" "web3": "^1.5.1"
}, },
"gitHead": "2cec195f5a3678b17745155536a1714d9b1a5694" "gitHead": "569f7d53f6f218d99aa8281929b541ab7e00058f"
} }
Loading…
Cancel
Save