[RPC] Add javascript engine 'duktape' to tracer module (#3461)
* add js engine 'duktape' to tracer module * fixes test error Co-authored-by: Rongjian Lan <rongjian.lan@gmail.com>pull/3468/head
parent
9dba6df985
commit
3237f43adc
@ -0,0 +1,86 @@ |
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// 4byteTracer searches for 4byte-identifiers, and collects them for post-processing.
|
||||||
|
// It collects the methods identifiers along with the size of the supplied data, so
|
||||||
|
// a reversed signature can be matched against the size of the data.
|
||||||
|
//
|
||||||
|
// Example:
|
||||||
|
// > debug.traceTransaction( "0x214e597e35da083692f5386141e69f47e973b2c56e7a8073b1ea08fd7571e9de", {tracer: "4byteTracer"})
|
||||||
|
// {
|
||||||
|
// 0x27dc297e-128: 1,
|
||||||
|
// 0x38cc4831-0: 2,
|
||||||
|
// 0x524f3889-96: 1,
|
||||||
|
// 0xadf59f99-288: 1,
|
||||||
|
// 0xc281d19e-0: 1
|
||||||
|
// }
|
||||||
|
{ |
||||||
|
// ids aggregates the 4byte ids found.
|
||||||
|
ids : {}, |
||||||
|
|
||||||
|
// callType returns 'false' for non-calls, or the peek-index for the first param
|
||||||
|
// after 'value', i.e. meminstart.
|
||||||
|
callType: function(opstr){ |
||||||
|
switch(opstr){ |
||||||
|
case "CALL": case "CALLCODE": |
||||||
|
// gas, addr, val, memin, meminsz, memout, memoutsz
|
||||||
|
return 3; // stack ptr to memin
|
||||||
|
|
||||||
|
case "DELEGATECALL": case "STATICCALL": |
||||||
|
// gas, addr, memin, meminsz, memout, memoutsz
|
||||||
|
return 2; // stack ptr to memin
|
||||||
|
} |
||||||
|
return false; |
||||||
|
}, |
||||||
|
|
||||||
|
// store save the given indentifier and datasize.
|
||||||
|
store: function(id, size){ |
||||||
|
var key = "" + toHex(id) + "-" + size; |
||||||
|
this.ids[key] = this.ids[key] + 1 || 1; |
||||||
|
}, |
||||||
|
|
||||||
|
// step is invoked for every opcode that the VM executes.
|
||||||
|
step: function(log, db) { |
||||||
|
// Skip any opcodes that are not internal calls
|
||||||
|
var ct = this.callType(log.op.toString()); |
||||||
|
if (!ct) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// Skip any pre-compile invocations, those are just fancy opcodes
|
||||||
|
if (isPrecompiled(toAddress(log.stack.peek(1).toString(16)))) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// Gather internal call details
|
||||||
|
var inSz = log.stack.peek(ct + 1).valueOf(); |
||||||
|
if (inSz >= 4) { |
||||||
|
var inOff = log.stack.peek(ct).valueOf(); |
||||||
|
this.store(log.memory.slice(inOff, inOff + 4), inSz-4); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// fault is invoked when the actual execution of an opcode fails.
|
||||||
|
fault: function(log, db) { }, |
||||||
|
|
||||||
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
|
// the final result of the tracing.
|
||||||
|
result: function(ctx) { |
||||||
|
// Save the outer calldata also
|
||||||
|
if (ctx.input.length >= 4) { |
||||||
|
this.store(slice(ctx.input, 0, 4), ctx.input.length-4) |
||||||
|
} |
||||||
|
return this.ids; |
||||||
|
}, |
||||||
|
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,47 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
{ |
||||||
|
// hist is the counters of opcode bigrams
|
||||||
|
hist: {}, |
||||||
|
// lastOp is last operation
|
||||||
|
lastOp: '', |
||||||
|
// execution depth of last op
|
||||||
|
lastDepth: 0, |
||||||
|
// step is invoked for every opcode that the VM executes.
|
||||||
|
step: function(log, db) { |
||||||
|
var op = log.op.toString(); |
||||||
|
var depth = log.getDepth(); |
||||||
|
if (depth == this.lastDepth){ |
||||||
|
var key = this.lastOp+'-'+op; |
||||||
|
if (this.hist[key]){ |
||||||
|
this.hist[key]++; |
||||||
|
} |
||||||
|
else { |
||||||
|
this.hist[key] = 1; |
||||||
|
} |
||||||
|
} |
||||||
|
this.lastOp = op; |
||||||
|
this.lastDepth = depth; |
||||||
|
}, |
||||||
|
// fault is invoked when the actual execution of an opcode fails.
|
||||||
|
fault: function(log, db) {}, |
||||||
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
|
// the final result of the tracing.
|
||||||
|
result: function(ctx) { |
||||||
|
return this.hist; |
||||||
|
}, |
||||||
|
} |
@ -0,0 +1,252 @@ |
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// callTracer is a full blown transaction tracer that extracts and reports all
|
||||||
|
// the internal calls made by a transaction, along with any useful information.
|
||||||
|
{ |
||||||
|
// callstack is the current recursive call stack of the EVM execution.
|
||||||
|
callstack: [{}], |
||||||
|
|
||||||
|
// descended tracks whether we've just descended from an outer transaction into
|
||||||
|
// an inner call.
|
||||||
|
descended: false, |
||||||
|
|
||||||
|
// step is invoked for every opcode that the VM executes.
|
||||||
|
step: function(log, db) { |
||||||
|
// Capture any errors immediately
|
||||||
|
var error = log.getError(); |
||||||
|
if (error !== undefined) { |
||||||
|
this.fault(log, db); |
||||||
|
return; |
||||||
|
} |
||||||
|
// We only care about system opcodes, faster if we pre-check once
|
||||||
|
var syscall = (log.op.toNumber() & 0xf0) == 0xf0; |
||||||
|
if (syscall) { |
||||||
|
var op = log.op.toString(); |
||||||
|
} |
||||||
|
// If a new contract is being created, add to the call stack
|
||||||
|
if (syscall && (op == 'CREATE' || op == "CREATE2")) { |
||||||
|
var inOff = log.stack.peek(1).valueOf(); |
||||||
|
var inEnd = inOff + log.stack.peek(2).valueOf(); |
||||||
|
|
||||||
|
// Assemble the internal call report and store for completion
|
||||||
|
var call = { |
||||||
|
type: op, |
||||||
|
from: toHex(log.contract.getAddress()), |
||||||
|
input: toHex(log.memory.slice(inOff, inEnd)), |
||||||
|
gasIn: log.getGas(), |
||||||
|
gasCost: log.getCost(), |
||||||
|
value: '0x' + log.stack.peek(0).toString(16) |
||||||
|
}; |
||||||
|
this.callstack.push(call); |
||||||
|
this.descended = true |
||||||
|
return; |
||||||
|
} |
||||||
|
// If a contract is being self destructed, gather that as a subcall too
|
||||||
|
if (syscall && op == 'SELFDESTRUCT') { |
||||||
|
var left = this.callstack.length; |
||||||
|
if (this.callstack[left-1].calls === undefined) { |
||||||
|
this.callstack[left-1].calls = []; |
||||||
|
} |
||||||
|
this.callstack[left-1].calls.push({ |
||||||
|
type: op, |
||||||
|
from: toHex(log.contract.getAddress()), |
||||||
|
to: toHex(toAddress(log.stack.peek(0).toString(16))), |
||||||
|
gasIn: log.getGas(), |
||||||
|
gasCost: log.getCost(), |
||||||
|
value: '0x' + db.getBalance(log.contract.getAddress()).toString(16) |
||||||
|
}); |
||||||
|
return |
||||||
|
} |
||||||
|
// If a new method invocation is being done, add to the call stack
|
||||||
|
if (syscall && (op == 'CALL' || op == 'CALLCODE' || op == 'DELEGATECALL' || op == 'STATICCALL')) { |
||||||
|
// Skip any pre-compile invocations, those are just fancy opcodes
|
||||||
|
var to = toAddress(log.stack.peek(1).toString(16)); |
||||||
|
if (isPrecompiled(to)) { |
||||||
|
return |
||||||
|
} |
||||||
|
var off = (op == 'DELEGATECALL' || op == 'STATICCALL' ? 0 : 1); |
||||||
|
|
||||||
|
var inOff = log.stack.peek(2 + off).valueOf(); |
||||||
|
var inEnd = inOff + log.stack.peek(3 + off).valueOf(); |
||||||
|
|
||||||
|
// Assemble the internal call report and store for completion
|
||||||
|
var call = { |
||||||
|
type: op, |
||||||
|
from: toHex(log.contract.getAddress()), |
||||||
|
to: toHex(to), |
||||||
|
input: toHex(log.memory.slice(inOff, inEnd)), |
||||||
|
gasIn: log.getGas(), |
||||||
|
gasCost: log.getCost(), |
||||||
|
outOff: log.stack.peek(4 + off).valueOf(), |
||||||
|
outLen: log.stack.peek(5 + off).valueOf() |
||||||
|
}; |
||||||
|
if (op != 'DELEGATECALL' && op != 'STATICCALL') { |
||||||
|
call.value = '0x' + log.stack.peek(2).toString(16); |
||||||
|
} |
||||||
|
this.callstack.push(call); |
||||||
|
this.descended = true |
||||||
|
return; |
||||||
|
} |
||||||
|
// If we've just descended into an inner call, retrieve it's true allowance. We
|
||||||
|
// need to extract if from within the call as there may be funky gas dynamics
|
||||||
|
// with regard to requested and actually given gas (2300 stipend, 63/64 rule).
|
||||||
|
if (this.descended) { |
||||||
|
if (log.getDepth() >= this.callstack.length) { |
||||||
|
this.callstack[this.callstack.length - 1].gas = log.getGas(); |
||||||
|
} else { |
||||||
|
// TODO(karalabe): The call was made to a plain account. We currently don't
|
||||||
|
// have access to the true gas amount inside the call and so any amount will
|
||||||
|
// mostly be wrong since it depends on a lot of input args. Skip gas for now.
|
||||||
|
} |
||||||
|
this.descended = false; |
||||||
|
} |
||||||
|
// If an existing call is returning, pop off the call stack
|
||||||
|
if (syscall && op == 'REVERT') { |
||||||
|
this.callstack[this.callstack.length - 1].error = "execution reverted"; |
||||||
|
return; |
||||||
|
} |
||||||
|
if (log.getDepth() == this.callstack.length - 1) { |
||||||
|
// Pop off the last call and get the execution results
|
||||||
|
var call = this.callstack.pop(); |
||||||
|
|
||||||
|
if (call.type == 'CREATE' || call.type == "CREATE2") { |
||||||
|
// If the call was a CREATE, retrieve the contract address and output code
|
||||||
|
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost - log.getGas()).toString(16); |
||||||
|
delete call.gasIn; delete call.gasCost; |
||||||
|
|
||||||
|
var ret = log.stack.peek(0); |
||||||
|
if (!ret.equals(0)) { |
||||||
|
call.to = toHex(toAddress(ret.toString(16))); |
||||||
|
call.output = toHex(db.getCode(toAddress(ret.toString(16)))); |
||||||
|
} else if (call.error === undefined) { |
||||||
|
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
|
||||||
|
} |
||||||
|
} else { |
||||||
|
// If the call was a contract call, retrieve the gas usage and output
|
||||||
|
if (call.gas !== undefined) { |
||||||
|
call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16); |
||||||
|
} |
||||||
|
var ret = log.stack.peek(0); |
||||||
|
if (!ret.equals(0)) { |
||||||
|
call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen)); |
||||||
|
} else if (call.error === undefined) { |
||||||
|
call.error = "internal failure"; // TODO(karalabe): surface these faults somehow
|
||||||
|
} |
||||||
|
delete call.gasIn; delete call.gasCost; |
||||||
|
delete call.outOff; delete call.outLen; |
||||||
|
} |
||||||
|
if (call.gas !== undefined) { |
||||||
|
call.gas = '0x' + bigInt(call.gas).toString(16); |
||||||
|
} |
||||||
|
// Inject the call into the previous one
|
||||||
|
var left = this.callstack.length; |
||||||
|
if (this.callstack[left-1].calls === undefined) { |
||||||
|
this.callstack[left-1].calls = []; |
||||||
|
} |
||||||
|
this.callstack[left-1].calls.push(call); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// fault is invoked when the actual execution of an opcode fails.
|
||||||
|
fault: function(log, db) { |
||||||
|
// If the topmost call already reverted, don't handle the additional fault again
|
||||||
|
if (this.callstack[this.callstack.length - 1].error !== undefined) { |
||||||
|
return; |
||||||
|
} |
||||||
|
// Pop off the just failed call
|
||||||
|
var call = this.callstack.pop(); |
||||||
|
call.error = log.getError(); |
||||||
|
|
||||||
|
// Consume all available gas and clean any leftovers
|
||||||
|
if (call.gas !== undefined) { |
||||||
|
call.gas = '0x' + bigInt(call.gas).toString(16); |
||||||
|
call.gasUsed = call.gas |
||||||
|
} |
||||||
|
delete call.gasIn; delete call.gasCost; |
||||||
|
delete call.outOff; delete call.outLen; |
||||||
|
|
||||||
|
// Flatten the failed call into its parent
|
||||||
|
var left = this.callstack.length; |
||||||
|
if (left > 0) { |
||||||
|
if (this.callstack[left-1].calls === undefined) { |
||||||
|
this.callstack[left-1].calls = []; |
||||||
|
} |
||||||
|
this.callstack[left-1].calls.push(call); |
||||||
|
return; |
||||||
|
} |
||||||
|
// Last call failed too, leave it in the stack
|
||||||
|
this.callstack.push(call); |
||||||
|
}, |
||||||
|
|
||||||
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
|
// the final result of the tracing.
|
||||||
|
result: function(ctx, db) { |
||||||
|
var result = { |
||||||
|
type: ctx.type, |
||||||
|
from: toHex(ctx.from), |
||||||
|
to: toHex(ctx.to), |
||||||
|
value: '0x' + ctx.value.toString(16), |
||||||
|
gas: '0x' + bigInt(ctx.gas).toString(16), |
||||||
|
gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16), |
||||||
|
input: toHex(ctx.input), |
||||||
|
output: toHex(ctx.output), |
||||||
|
time: ctx.time, |
||||||
|
}; |
||||||
|
if (this.callstack[0].calls !== undefined) { |
||||||
|
result.calls = this.callstack[0].calls; |
||||||
|
} |
||||||
|
if (this.callstack[0].error !== undefined) { |
||||||
|
result.error = this.callstack[0].error; |
||||||
|
} else if (ctx.error !== undefined) { |
||||||
|
result.error = ctx.error; |
||||||
|
} |
||||||
|
if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) { |
||||||
|
delete result.output; |
||||||
|
} |
||||||
|
return this.finalize(result); |
||||||
|
}, |
||||||
|
|
||||||
|
// finalize recreates a call object using the final desired field oder for json
|
||||||
|
// serialization. This is a nicety feature to pass meaningfully ordered results
|
||||||
|
// to users who don't interpret it, just display it.
|
||||||
|
finalize: function(call) { |
||||||
|
var sorted = { |
||||||
|
type: call.type, |
||||||
|
from: call.from, |
||||||
|
to: call.to, |
||||||
|
value: call.value, |
||||||
|
gas: call.gas, |
||||||
|
gasUsed: call.gasUsed, |
||||||
|
input: call.input, |
||||||
|
output: call.output, |
||||||
|
error: call.error, |
||||||
|
time: call.time, |
||||||
|
calls: call.calls, |
||||||
|
} |
||||||
|
for (var key in sorted) { |
||||||
|
if (sorted[key] === undefined) { |
||||||
|
delete sorted[key]; |
||||||
|
} |
||||||
|
} |
||||||
|
if (sorted.calls !== undefined) { |
||||||
|
for (var i=0; i<sorted.calls.length; i++) { |
||||||
|
sorted.calls[i] = this.finalize(sorted.calls[i]); |
||||||
|
} |
||||||
|
} |
||||||
|
return sorted; |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,93 @@ |
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// evmdisTracer returns sufficient information from a trace to perform evmdis-style
|
||||||
|
// disassembly.
|
||||||
|
{ |
||||||
|
stack: [{ops: []}], |
||||||
|
|
||||||
|
npushes: {0: 0, 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1, 7: 1, 8: 1, 9: 1, 10: 1, 11: 1, 16: 1, 17: 1, 18: 1, 19: 1, 20: 1, 21: 1, 22: 1, 23: 1, 24: 1, 25: 1, 26: 1, 32: 1, 48: 1, 49: 1, 50: 1, 51: 1, 52: 1, 53: 1, 54: 1, 55: 0, 56: 1, 57: 0, 58: 1, 59: 1, 60: 0, 64: 1, 65: 1, 66: 1, 67: 1, 68: 1, 69: 1, 80: 0, 81: 1, 82: 0, 83: 0, 84: 1, 85: 0, 86: 0, 87: 0, 88: 1, 89: 1, 90: 1, 91: 0, 96: 1, 97: 1, 98: 1, 99: 1, 100: 1, 101: 1, 102: 1, 103: 1, 104: 1, 105: 1, 106: 1, 107: 1, 108: 1, 109: 1, 110: 1, 111: 1, 112: 1, 113: 1, 114: 1, 115: 1, 116: 1, 117: 1, 118: 1, 119: 1, 120: 1, 121: 1, 122: 1, 123: 1, 124: 1, 125: 1, 126: 1, 127: 1, 128: 2, 129: 3, 130: 4, 131: 5, 132: 6, 133: 7, 134: 8, 135: 9, 136: 10, 137: 11, 138: 12, 139: 13, 140: 14, 141: 15, 142: 16, 143: 17, 144: 2, 145: 3, 146: 4, 147: 5, 148: 6, 149: 7, 150: 8, 151: 9, 152: 10, 153: 11, 154: 12, 155: 13, 156: 14, 157: 15, 158: 16, 159: 17, 160: 0, 161: 0, 162: 0, 163: 0, 164: 0, 240: 1, 241: 1, 242: 1, 243: 0, 244: 0, 255: 0}, |
||||||
|
|
||||||
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
|
// the final result of the tracing.
|
||||||
|
result: function() { return this.stack[0].ops; }, |
||||||
|
|
||||||
|
// fault is invoked when the actual execution of an opcode fails.
|
||||||
|
fault: function(log, db) { }, |
||||||
|
|
||||||
|
// step is invoked for every opcode that the VM executes.
|
||||||
|
step: function(log, db) { |
||||||
|
var frame = this.stack[this.stack.length - 1]; |
||||||
|
|
||||||
|
var error = log.getError(); |
||||||
|
if (error) { |
||||||
|
frame["error"] = error; |
||||||
|
} else if (log.getDepth() == this.stack.length) { |
||||||
|
opinfo = { |
||||||
|
op: log.op.toNumber(), |
||||||
|
depth : log.getDepth(), |
||||||
|
result: [], |
||||||
|
}; |
||||||
|
if (frame.ops.length > 0) { |
||||||
|
var prevop = frame.ops[frame.ops.length - 1]; |
||||||
|
for(var i = 0; i < this.npushes[prevop.op]; i++) |
||||||
|
prevop.result.push(log.stack.peek(i).toString(16)); |
||||||
|
} |
||||||
|
switch(log.op.toString()) { |
||||||
|
case "CALL": case "CALLCODE": |
||||||
|
var instart = log.stack.peek(3).valueOf(); |
||||||
|
var insize = log.stack.peek(4).valueOf(); |
||||||
|
opinfo["gas"] = log.stack.peek(0).valueOf(); |
||||||
|
opinfo["to"] = log.stack.peek(1).toString(16); |
||||||
|
opinfo["value"] = log.stack.peek(2).toString(); |
||||||
|
opinfo["input"] = log.memory.slice(instart, instart + insize); |
||||||
|
opinfo["error"] = null; |
||||||
|
opinfo["return"] = null; |
||||||
|
opinfo["ops"] = []; |
||||||
|
this.stack.push(opinfo); |
||||||
|
break; |
||||||
|
case "DELEGATECALL": case "STATICCALL": |
||||||
|
var instart = log.stack.peek(2).valueOf(); |
||||||
|
var insize = log.stack.peek(3).valueOf(); |
||||||
|
opinfo["op"] = log.op.toString(); |
||||||
|
opinfo["gas"] = log.stack.peek(0).valueOf(); |
||||||
|
opinfo["to"] = log.stack.peek(1).toString(16); |
||||||
|
opinfo["input"] = log.memory.slice(instart, instart + insize); |
||||||
|
opinfo["error"] = null; |
||||||
|
opinfo["return"] = null; |
||||||
|
opinfo["ops"] = []; |
||||||
|
this.stack.push(opinfo); |
||||||
|
break; |
||||||
|
case "RETURN": |
||||||
|
var out = log.stack.peek(0).valueOf(); |
||||||
|
var outsize = log.stack.peek(1).valueOf(); |
||||||
|
frame.return = log.memory.slice(out, out + outsize); |
||||||
|
break; |
||||||
|
case "STOP": case "SUICIDE": |
||||||
|
frame.return = log.memory.slice(0, 0); |
||||||
|
break; |
||||||
|
case "JUMPDEST": |
||||||
|
opinfo["pc"] = log.getPC(); |
||||||
|
} |
||||||
|
if(log.op.isPush()) { |
||||||
|
opinfo["len"] = log.op.toNumber() - 0x5e; |
||||||
|
} |
||||||
|
frame.ops.push(opinfo); |
||||||
|
} else { |
||||||
|
this.stack = this.stack.slice(0, log.getDepth()); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// noopTracer is just the barebone boilerplate code required from a JavaScript
|
||||||
|
// object to be usable as a transaction tracer.
|
||||||
|
{ |
||||||
|
// step is invoked for every opcode that the VM executes.
|
||||||
|
step: function(log, db) { }, |
||||||
|
|
||||||
|
// fault is invoked when the actual execution of an opcode fails.
|
||||||
|
fault: function(log, db) { }, |
||||||
|
|
||||||
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
|
// the final result of the tracing.
|
||||||
|
result: function(ctx, db) { return {}; } |
||||||
|
} |
@ -0,0 +1,32 @@ |
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// opcountTracer is a sample tracer that just counts the number of instructions
|
||||||
|
// executed by the EVM before the transaction terminated.
|
||||||
|
{ |
||||||
|
// count tracks the number of EVM instructions executed.
|
||||||
|
count: 0, |
||||||
|
|
||||||
|
// step is invoked for every opcode that the VM executes.
|
||||||
|
step: function(log, db) { this.count++ }, |
||||||
|
|
||||||
|
// fault is invoked when the actual execution of an opcode fails.
|
||||||
|
fault: function(log, db) { }, |
||||||
|
|
||||||
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
|
// the final result of the tracing.
|
||||||
|
result: function(ctx, db) { return this.count } |
||||||
|
} |
@ -0,0 +1,108 @@ |
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// prestateTracer outputs sufficient information to create a local execution of
|
||||||
|
// the transaction from a custom assembled genesis block.
|
||||||
|
{ |
||||||
|
// prestate is the genesis that we're building.
|
||||||
|
prestate: null, |
||||||
|
|
||||||
|
// lookupAccount injects the specified account into the prestate object.
|
||||||
|
lookupAccount: function(addr, db){ |
||||||
|
var acc = toHex(addr); |
||||||
|
if (this.prestate[acc] === undefined) { |
||||||
|
this.prestate[acc] = { |
||||||
|
balance: '0x' + db.getBalance(addr).toString(16), |
||||||
|
nonce: db.getNonce(addr), |
||||||
|
code: toHex(db.getCode(addr)), |
||||||
|
storage: {} |
||||||
|
}; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// lookupStorage injects the specified storage entry of the given account into
|
||||||
|
// the prestate object.
|
||||||
|
lookupStorage: function(addr, key, db){ |
||||||
|
var acc = toHex(addr); |
||||||
|
var idx = toHex(key); |
||||||
|
|
||||||
|
if (this.prestate[acc].storage[idx] === undefined) { |
||||||
|
this.prestate[acc].storage[idx] = toHex(db.getState(addr, key)); |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
|
// the final result of the tracing.
|
||||||
|
result: function(ctx, db) { |
||||||
|
// At this point, we need to deduct the 'value' from the
|
||||||
|
// outer transaction, and move it back to the origin
|
||||||
|
this.lookupAccount(ctx.from, db); |
||||||
|
|
||||||
|
var fromBal = bigInt(this.prestate[toHex(ctx.from)].balance.slice(2), 16); |
||||||
|
var toBal = bigInt(this.prestate[toHex(ctx.to)].balance.slice(2), 16); |
||||||
|
|
||||||
|
this.prestate[toHex(ctx.to)].balance = '0x'+toBal.subtract(ctx.value).toString(16); |
||||||
|
this.prestate[toHex(ctx.from)].balance = '0x'+fromBal.add(ctx.value).toString(16); |
||||||
|
|
||||||
|
// Decrement the caller's nonce, and remove empty create targets
|
||||||
|
this.prestate[toHex(ctx.from)].nonce--; |
||||||
|
if (ctx.type == 'CREATE') { |
||||||
|
// We can blibdly delete the contract prestate, as any existing state would
|
||||||
|
// have caused the transaction to be rejected as invalid in the first place.
|
||||||
|
delete this.prestate[toHex(ctx.to)]; |
||||||
|
} |
||||||
|
// Return the assembled allocations (prestate)
|
||||||
|
return this.prestate; |
||||||
|
}, |
||||||
|
|
||||||
|
// step is invoked for every opcode that the VM executes.
|
||||||
|
step: function(log, db) { |
||||||
|
// Add the current account if we just started tracing
|
||||||
|
if (this.prestate === null){ |
||||||
|
this.prestate = {}; |
||||||
|
// Balance will potentially be wrong here, since this will include the value
|
||||||
|
// sent along with the message. We fix that in 'result()'.
|
||||||
|
this.lookupAccount(log.contract.getAddress(), db); |
||||||
|
} |
||||||
|
// Whenever new state is accessed, add it to the prestate
|
||||||
|
switch (log.op.toString()) { |
||||||
|
case "EXTCODECOPY": case "EXTCODESIZE": case "BALANCE": |
||||||
|
this.lookupAccount(toAddress(log.stack.peek(0).toString(16)), db); |
||||||
|
break; |
||||||
|
case "CREATE": |
||||||
|
var from = log.contract.getAddress(); |
||||||
|
this.lookupAccount(toContract(from, db.getNonce(from)), db); |
||||||
|
break; |
||||||
|
case "CREATE2": |
||||||
|
var from = log.contract.getAddress(); |
||||||
|
// stack: salt, size, offset, endowment
|
||||||
|
var offset = log.stack.peek(1).valueOf() |
||||||
|
var size = log.stack.peek(2).valueOf() |
||||||
|
var end = offset + size |
||||||
|
this.lookupAccount(toContract2(from, log.stack.peek(3).toString(16), log.memory.slice(offset, end)), db); |
||||||
|
break; |
||||||
|
case "CALL": case "CALLCODE": case "DELEGATECALL": case "STATICCALL": |
||||||
|
this.lookupAccount(toAddress(log.stack.peek(1).toString(16)), db); |
||||||
|
break; |
||||||
|
case 'SSTORE':case 'SLOAD': |
||||||
|
this.lookupStorage(log.contract.getAddress(), toWord(log.stack.peek(0).toString(16)), db); |
||||||
|
break; |
||||||
|
} |
||||||
|
}, |
||||||
|
|
||||||
|
// fault is invoked when the actual execution of an opcode fails.
|
||||||
|
fault: function(log, db) {} |
||||||
|
} |
@ -0,0 +1,22 @@ |
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// disable go:generate to pass "./scripts/travis_go_checker.sh"
|
||||||
|
// go:generate go-bindata -nometadata -o assets.go -pkg tracers -ignore tracers.go -ignore assets.go ./...
|
||||||
|
// go:generate gofmt -s -w assets.go
|
||||||
|
|
||||||
|
// Package tracers contains the actual JavaScript tracer assets.
|
||||||
|
package tracers |
@ -0,0 +1,49 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
{ |
||||||
|
// hist is the map of trigram counters
|
||||||
|
hist: {}, |
||||||
|
// lastOp is last operation
|
||||||
|
lastOps: ['',''], |
||||||
|
lastDepth: 0, |
||||||
|
// step is invoked for every opcode that the VM executes.
|
||||||
|
step: function(log, db) { |
||||||
|
var depth = log.getDepth(); |
||||||
|
if (depth != this.lastDepth){ |
||||||
|
this.lastOps = ['','']; |
||||||
|
this.lastDepth = depth; |
||||||
|
return; |
||||||
|
} |
||||||
|
var op = log.op.toString(); |
||||||
|
var key = this.lastOps[0]+'-'+this.lastOps[1]+'-'+op; |
||||||
|
if (this.hist[key]){ |
||||||
|
this.hist[key]++; |
||||||
|
} |
||||||
|
else { |
||||||
|
this.hist[key] = 1; |
||||||
|
} |
||||||
|
this.lastOps[0] = this.lastOps[1]; |
||||||
|
this.lastOps[1] = op; |
||||||
|
}, |
||||||
|
// fault is invoked when the actual execution of an opcode fails.
|
||||||
|
fault: function(log, db) {}, |
||||||
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
|
// the final result of the tracing.
|
||||||
|
result: function(ctx) { |
||||||
|
return this.hist; |
||||||
|
}, |
||||||
|
} |
@ -0,0 +1,43 @@ |
|||||||
|
// Copyright 2018 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
{ |
||||||
|
// hist is the map of opcodes to counters
|
||||||
|
hist: {}, |
||||||
|
// nops counts number of ops
|
||||||
|
nops: 0, |
||||||
|
// step is invoked for every opcode that the VM executes.
|
||||||
|
step: function(log, db) { |
||||||
|
var op = log.op.toString(); |
||||||
|
if (this.hist[op]){ |
||||||
|
this.hist[op]++; |
||||||
|
} |
||||||
|
else { |
||||||
|
this.hist[op] = 1; |
||||||
|
} |
||||||
|
this.nops++; |
||||||
|
}, |
||||||
|
// fault is invoked when the actual execution of an opcode fails.
|
||||||
|
fault: function(log, db) {}, |
||||||
|
|
||||||
|
// result is invoked when all the opcodes have been iterated over and returns
|
||||||
|
// the final result of the tracing.
|
||||||
|
result: function(ctx) { |
||||||
|
if(this.nops > 0){ |
||||||
|
return this.hist; |
||||||
|
} |
||||||
|
}, |
||||||
|
} |
File diff suppressed because one or more lines are too long
@ -0,0 +1,53 @@ |
|||||||
|
// Copyright 2017 The go-ethereum Authors
|
||||||
|
// This file is part of the go-ethereum library.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
// Package tracers is a collection of JavaScript transaction tracers.
|
||||||
|
package tracers |
||||||
|
|
||||||
|
import ( |
||||||
|
"strings" |
||||||
|
"unicode" |
||||||
|
|
||||||
|
"github.com/harmony-one/harmony/hmy/tracers/internal/tracers" |
||||||
|
) |
||||||
|
|
||||||
|
// all contains all the built in JavaScript tracers by name.
|
||||||
|
var all = make(map[string]string) |
||||||
|
|
||||||
|
// camel converts a snake cased input string into a camel cased output.
|
||||||
|
func camel(str string) string { |
||||||
|
pieces := strings.Split(str, "_") |
||||||
|
for i := 1; i < len(pieces); i++ { |
||||||
|
pieces[i] = string(unicode.ToUpper(rune(pieces[i][0]))) + pieces[i][1:] |
||||||
|
} |
||||||
|
return strings.Join(pieces, "") |
||||||
|
} |
||||||
|
|
||||||
|
// init retrieves the JavaScript transaction tracers included in go-ethereum.
|
||||||
|
func init() { |
||||||
|
for _, file := range tracers.AssetNames() { |
||||||
|
name := camel(strings.TrimSuffix(file, ".js")) |
||||||
|
all[name] = string(tracers.MustAsset(file)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// tracer retrieves a specific JavaScript tracer by name.
|
||||||
|
func tracer(name string) (string, bool) { |
||||||
|
if tracer, ok := all[name]; ok { |
||||||
|
return tracer, true |
||||||
|
} |
||||||
|
return "", false |
||||||
|
} |
Loading…
Reference in new issue