pull/1324/head
filip mertens 4 years ago
commit 2d7fbc8189
  1. 2
      apps/remix-ide-e2e/src/tests/pluginManager.spec.ts
  2. 13
      apps/remix-ide/src/app/tabs/hardhat-provider.js
  3. 2
      apps/remix-ide/src/remixAppManager.js
  4. 27
      libs/remix-debug/src/code/codeManager.ts
  5. 4
      libs/remix-debug/src/debugger/VmDebugger.ts
  6. 11
      libs/remix-debug/src/trace/traceAnalyser.ts
  7. 14
      libs/remix-debug/src/trace/traceCache.ts
  8. 8
      libs/remix-debug/src/trace/traceManager.ts
  9. 2
      libs/remix-lib/src/web3Provider/web3VmProvider.ts
  10. 2
      libs/remix-ui/debugger-ui/src/lib/debugger-ui.tsx
  11. 101
      libs/remix-ui/debugger-ui/src/lib/vm-debugger/assembly-items.tsx
  12. 34
      libs/remix-ui/debugger-ui/src/reducers/assembly-items.ts

@ -5,7 +5,7 @@ import init from '../helpers/init'
const testData = {
pluginName: 'remixIde',
pluginDisplayName: 'Remix IDE',
pluginUrl: 'https://zokrates-remix-plugin.netlify.app/'
pluginUrl: 'https://zokrates.github.io/zokrates-remix-plugin/'
}
module.exports = {

@ -17,11 +17,13 @@ export default class HardhatProvider extends Plugin {
constructor (blockchain) {
super(profile)
this.provider = null
this.blocked = false // used to block any call when trying to recover after a failed connection.
this.blockchain = blockchain
}
onDeactivation () {
this.provider = null
this.blocked = false
}
hardhatProviderDialogBody () {
@ -39,6 +41,8 @@ export default class HardhatProvider extends Plugin {
sendAsync (data) {
return new Promise((resolve, reject) => {
if (this.blocked) return reject(new Error('provider unable to connect'))
// If provider is not set, allow to open modal only when provider is trying to connect
if (!this.provider) {
modalDialogCustom.prompt('Hardhat node request', this.hardhatProviderDialogBody(), 'http://127.0.0.1:8545', (target) => {
this.provider = new Web3.providers.HttpProvider(target)
@ -54,9 +58,16 @@ export default class HardhatProvider extends Plugin {
sendAsyncInternal (data, resolve, reject) {
if (this.provider) {
this.provider[this.provider.sendAsync ? 'sendAsync' : 'send'](data, (error, message) => {
// Check the case where current environment is VM on UI and it still sends RPC requests
// This will be displayed on UI tooltip as 'cannot get account list: Environment Updated !!'
if (this.blockchain.getProvider() !== 'Hardhat Provider' && data.method !== 'net_listening') return reject(new Error('Environment Updated !!'))
this.provider[this.provider.sendAsync ? 'sendAsync' : 'send'](data, async (error, message) => {
if (error) {
this.blocked = true
modalDialogCustom.alert('Hardhat Provider', `Error while connecting to the hardhat provider: ${error.message}`)
await this.call('udapp', 'setEnvironmentMode', 'vm')
this.provider = null
setTimeout(_ => { this.blocked = false }, 1000) // we wait 1 second for letting remix to switch to vm
return reject(error)
}
resolve(message)

@ -14,7 +14,7 @@ const requiredModules = [ // services + layout views + system views
const dependentModules = ['git', 'hardhat'] // module which shouldn't be manually activated (e.g git is activated by remixd)
export function isNative (name) {
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity']
const nativePlugins = ['vyper', 'workshops', 'debugger', 'remixd', 'menuicons', 'solidity', 'hardhat-provider']
return nativePlugins.includes(name) || requiredModules.includes(name)
}

@ -145,15 +145,38 @@ export class CodeManager {
})
}
private retrieveIndexAndTrigger (codeMananger, address, step, code) {
private async retrieveIndexAndTrigger (codeMananger, address, step, code) {
let result
let next
const returnInstructionIndexes = []
const outOfGasInstructionIndexes = []
try {
result = codeMananger.getInstructionIndex(address, step)
next = codeMananger.getInstructionIndex(address, step + 1)
let values = this.traceManager.getAllStopIndexes()
if (values) {
for (const value of values) {
if (value.address === address) {
returnInstructionIndexes.push({ instructionIndex: this.getInstructionIndex(address, value.index), address })
}
}
}
values = this.traceManager.getAllOutofGasIndexes()
if (values) {
for (const value of values) {
if (value.address === address) {
outOfGasInstructionIndexes.push({ instructionIndex: this.getInstructionIndex(address, value.index), address })
}
}
}
} catch (error) {
return console.log(error)
}
try {
codeMananger.event.trigger('changed', [code, address, result])
codeMananger.event.trigger('changed', [code, address, result, next, returnInstructionIndexes, outOfGasInstructionIndexes])
} catch (e) {
console.log('dispatching event failed', e)
}

@ -59,8 +59,8 @@ export class VmDebuggerLogic {
}
listenToCodeManagerEvents () {
this._codeManager.event.register('changed', (code, address, index) => {
this.event.trigger('codeManagerChanged', [code, address, index])
this._codeManager.event.register('changed', (code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes) => {
this.event.trigger('codeManagerChanged', [code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes])
})
}

@ -49,6 +49,17 @@ export class TraceAnalyser {
this.traceCache.pushReturnValue(index, returnParamsObj)
}
if (traceHelper.isReturnInstruction(step) || traceHelper.isStopInstruction(step) || traceHelper.isRevertInstruction(step)) {
this.traceCache.pushStopIndex(index, this.traceCache.currentCall.call.address)
}
try {
if (parseInt(step.gas) - parseInt(step.gasCost) <= 0 || step.error === 'OutOfGas') {
this.traceCache.pushOutOfGasIndex(index, this.traceCache.currentCall.call.address)
}
} catch (e) {
console.error(e)
}
}
buildCalldata (index, step, tx, newContext) {

@ -5,6 +5,8 @@ const { sha3_256 } = util
export class TraceCache {
returnValues
stopIndexes
outofgasIndexes
currentCall
callsTree
callsData
@ -24,6 +26,8 @@ export class TraceCache {
// ...Changes contains index in the vmtrace of the corresponding changes
this.returnValues = {}
this.stopIndexes = []
this.outofgasIndexes = []
this.currentCall = null
this.callsTree = null
this.callsData = {}
@ -59,7 +63,7 @@ export class TraceCache {
this.currentCall.call.reverted = reverted
}
var parent = this.currentCall.parent
this.currentCall = parent ? { call: parent.call, parent: parent.parent } : null
if (parent) this.currentCall = { call: parent.call, parent: parent.parent }
return
}
const call = {
@ -78,6 +82,14 @@ export class TraceCache {
this.currentCall = { call: call, parent: this.currentCall }
}
pushOutOfGasIndex (index, address) {
this.outofgasIndexes.push({ index, address })
}
pushStopIndex (index, address) {
this.stopIndexes.push({ index, address })
}
pushReturnValue (step, value) {
this.returnValues[step] = value
}

@ -209,6 +209,14 @@ export class TraceManager {
return this.trace[stepIndex].pc
}
getAllStopIndexes () {
return this.traceCache.stopIndexes
}
getAllOutofGasIndexes () {
return this.traceCache.outofgasIndexes
}
getReturnValue (stepIndex) {
try {
this.checkRequestedStep(stepIndex)

@ -138,7 +138,7 @@ export class Web3VmProvider {
async txProcessed (data) {
const lastOp = this.vmTraces[this.processingHash].structLogs[this.processingIndex - 1]
if (lastOp) {
lastOp.error = lastOp.op !== 'RETURN' && lastOp.op !== 'STOP' && lastOp.op !== 'thisDESTRUCT'
lastOp.error = lastOp.op !== 'RETURN' && lastOp.op !== 'STOP' && lastOp.op !== 'DESTRUCT'
}
this.vmTraces[this.processingHash].gas = '0x' + data.gasUsed.toString(16)

@ -274,7 +274,7 @@ export const DebuggerUI = (props: DebuggerUIProps) => {
<div className="px-2">
<div>
<p className="my-2 debuggerLabel">Debugger Configuration</p>
<div className="mt-2 debuggerConfig custom-control custom-checkbox">
<div className="mt-2 mb-2 debuggerConfig custom-control custom-checkbox">
<input className="custom-control-input" id="debugGeneratedSourcesInput" onChange={({ target: { checked } }) => {
setState(prevState => {
return { ...prevState, opt: { debugWithGeneratedSources: checked } }

@ -4,41 +4,116 @@ import './styles/assembly-items.css'
export const AssemblyItems = ({ registerEvent }) => {
const [assemblyItems, dispatch] = useReducer(reducer, initialState)
const [absoluteSelectedIndex, setAbsoluteSelectedIndex] = useState(0)
const [selectedItem, setSelectedItem] = useState(0)
const [nextSelectedItem, setNextSelectedItem] = useState(1)
const [returnInstructionIndexes, setReturnInstructionIndexes] = useState([])
const [outOfGasInstructionIndexes, setOutOfGasInstructionIndexes] = useState([])
const refs = useRef({})
const asmItemsRef = useRef(null)
useEffect(() => {
registerEvent && registerEvent('codeManagerChanged', (code, address, index) => {
dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index } })
registerEvent && registerEvent('codeManagerChanged', (code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes) => {
dispatch({ type: 'FETCH_OPCODES_SUCCESS', payload: { code, address, index, nextIndex, returnInstructionIndexes, outOfGasInstructionIndexes } })
})
}, [])
useEffect(() => {
if (selectedItem !== assemblyItems.index) {
if (absoluteSelectedIndex !== assemblyItems.index) {
clearItems()
indexChanged(assemblyItems.index)
nextIndexChanged(assemblyItems.nextIndex)
returnIndexes(assemblyItems.returnInstructionIndexes)
outOfGasIndexes(assemblyItems.outOfGasInstructionIndexes)
}
}, [assemblyItems.index])
const indexChanged = (index: number) => {
if (index < 0) return
let currentItem = refs.current[selectedItem] ? refs.current[selectedItem] : null
}, [assemblyItems.opCodes.index])
const clearItem = (currentItem) => {
if (currentItem) {
currentItem.removeAttribute('selected')
currentItem.removeAttribute('style')
if (currentItem.firstChild) {
currentItem.firstChild.removeAttribute('style')
}
const codeView = asmItemsRef.current
}
}
const clearItems = () => {
clearItem(refs.current[selectedItem] ? refs.current[selectedItem] : null)
clearItem(refs.current[nextSelectedItem] ? refs.current[nextSelectedItem] : null)
returnInstructionIndexes.map((index) => {
if (index < 0) return
clearItem(refs.current[index] ? refs.current[index] : null)
})
outOfGasInstructionIndexes.map((index) => {
if (index < 0) return
clearItem(refs.current[index] ? refs.current[index] : null)
})
}
const indexChanged = (index: number) => {
if (index < 0) return
currentItem = codeView.children[index]
currentItem.style.setProperty('border-color', 'var(--primary)')
currentItem.style.setProperty('border-style', 'solid')
const codeView = asmItemsRef.current
const currentItem = codeView.children[index]
if (currentItem) {
currentItem.style.setProperty('background-color', 'var(--primary)')
currentItem.style.setProperty('color', 'var(--light)')
currentItem.setAttribute('selected', 'selected')
codeView.scrollTop = currentItem.offsetTop - parseInt(codeView.offsetTop)
setSelectedItem(index)
}
setSelectedItem(index)
setAbsoluteSelectedIndex(assemblyItems.opCodes.index)
}
const nextIndexChanged = (index: number) => {
if (index < 0) return
const codeView = asmItemsRef.current
const currentItem = codeView.children[index]
if (currentItem) {
currentItem.style.setProperty('border-color', 'var(--secondary)')
currentItem.style.setProperty('border-style', 'dotted')
currentItem.setAttribute('selected', 'selected')
}
setNextSelectedItem(index)
}
const returnIndexes = (indexes) => {
indexes.map((index) => {
if (index < 0) return
const codeView = asmItemsRef.current
const currentItem = codeView.children[index]
if (currentItem) {
currentItem.style.setProperty('border-color', 'var(--warning)')
currentItem.style.setProperty('border-style', 'dotted')
currentItem.setAttribute('selected', 'selected')
}
})
setReturnInstructionIndexes(indexes)
}
const outOfGasIndexes = (indexes) => {
indexes.map((index) => {
if (index < 0) return
const codeView = asmItemsRef.current
const currentItem = codeView.children[index]
if (currentItem) {
currentItem.style.setProperty('border-color', 'var(--danger)')
currentItem.style.setProperty('border-style', 'dotted')
currentItem.setAttribute('selected', 'selected')
}
})
setOutOfGasInstructionIndexes(indexes)
}
return (

@ -13,6 +13,9 @@ export const initialState = {
},
display: [],
index: 0,
nextIndex: -1,
returnInstructionIndexes: [],
outOfGasInstructionIndexes: [],
top: 0,
bottom: 0,
isRequesting: false,
@ -20,6 +23,20 @@ export const initialState = {
hasError: null
}
const reducedOpcode = (opCodes, payload) => {
const length = 100
let bottom = opCodes.index - 10
bottom = bottom < 0 ? 0 : bottom
const top = bottom + length
return {
index: opCodes.index - bottom,
nextIndex: opCodes.nextIndex - bottom,
display: opCodes.code.slice(bottom, top),
returnInstructionIndexes: payload.returnInstructionIndexes.map((index) => index.instructionIndex - bottom),
outOfGasInstructionIndexes: payload.outOfGasInstructionIndexes.map((index) => index.instructionIndex - bottom)
}
}
export const reducer = (state = initialState, action: Action) => {
switch (action.type) {
case 'FETCH_OPCODES_REQUEST': {
@ -32,21 +49,20 @@ export const reducer = (state = initialState, action: Action) => {
}
case 'FETCH_OPCODES_SUCCESS': {
const opCodes = action.payload.address === state.opCodes.address ? {
...state.opCodes, index: action.payload.index
...state.opCodes, index: action.payload.index, nextIndex: action.payload.nextIndex
} : deepEqual(action.payload.code, state.opCodes.code) ? state.opCodes : action.payload
const top = opCodes.index - 3 > 0 ? opCodes.index - 3 : 0
const bottom = opCodes.index + 4 < opCodes.code.length ? opCodes.index + 4 : opCodes.code.length
const display = opCodes.code.slice(top, bottom)
const reduced = reducedOpcode(opCodes, action.payload)
return {
opCodes,
display,
index: display.findIndex(code => code === opCodes.code[opCodes.index]),
top,
bottom,
display: reduced.display,
index: reduced.index,
nextIndex: reduced.nextIndex,
isRequesting: false,
isSuccessful: true,
hasError: null
hasError: null,
returnInstructionIndexes: reduced.returnInstructionIndexes,
outOfGasInstructionIndexes: reduced.outOfGasInstructionIndexes
}
}
case 'FETCH_OPCODES_ERROR': {

Loading…
Cancel
Save