|
1 | | -// Copyright 2017 The go-ethereum Authors |
| 1 | +// Copyright 2021 The go-ethereum Authors |
2 | 2 | // This file is part of the go-ethereum library. |
3 | 3 | // |
4 | 4 | // The go-ethereum library is free software: you can redistribute it and/or modify |
|
14 | 14 | // You should have received a copy of the GNU Lesser General Public License |
15 | 15 | // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. |
16 | 16 |
|
17 | | -// callTracer is a full blown transaction tracer that extracts and reports all |
18 | | -// the internal calls made by a transaction, along with any useful information. |
| 17 | + |
| 18 | +// callFrameTracer uses the new call frame tracing methods to report useful information |
| 19 | +// about internal messages of a transaction. |
19 | 20 | { |
20 | | - // callstack is the current recursive call stack of the EVM execution. |
21 | 21 | callstack: [{}], |
22 | | - |
23 | | - // descended tracks whether we've just descended from an outer transaction into |
24 | | - // an inner call. |
25 | | - descended: false, |
26 | | - |
27 | | - // step is invoked for every opcode that the VM executes. |
28 | | - step: function(log, db) { |
29 | | - // Capture any errors immediately |
30 | | - var error = log.getError(); |
31 | | - if (error !== undefined) { |
32 | | - this.fault(log, db); |
33 | | - return; |
34 | | - } |
35 | | - // We only care about system opcodes, faster if we pre-check once |
36 | | - var syscall = (log.op.toNumber() & 0xf0) == 0xf0; |
37 | | - if (syscall) { |
38 | | - var op = log.op.toString(); |
39 | | - } |
40 | | - // If a new contract is being created, add to the call stack |
41 | | - if (syscall && (op == 'CREATE' || op == "CREATE2")) { |
42 | | - var inOff = log.stack.peek(1).valueOf(); |
43 | | - var inEnd = inOff + log.stack.peek(2).valueOf(); |
44 | | - |
45 | | - // Assemble the internal call report and store for completion |
46 | | - var call = { |
47 | | - type: op, |
48 | | - from: toHex(log.contract.getAddress()), |
49 | | - input: toHex(log.memory.slice(inOff, inEnd)), |
50 | | - gasIn: log.getGas(), |
51 | | - gasCost: log.getCost(), |
52 | | - value: '0x' + log.stack.peek(0).toString(16) |
53 | | - }; |
54 | | - this.callstack.push(call); |
55 | | - this.descended = true |
56 | | - return; |
57 | | - } |
58 | | - // If a contract is being self destructed, gather that as a subcall too |
59 | | - if (syscall && op == 'SELFDESTRUCT') { |
60 | | - var left = this.callstack.length; |
61 | | - if (this.callstack[left-1].calls === undefined) { |
62 | | - this.callstack[left-1].calls = []; |
63 | | - } |
64 | | - this.callstack[left-1].calls.push({ |
65 | | - type: op, |
66 | | - from: toHex(log.contract.getAddress()), |
67 | | - to: toHex(toAddress(log.stack.peek(0).toString(16))), |
68 | | - gasIn: log.getGas(), |
69 | | - gasCost: log.getCost(), |
70 | | - value: '0x' + db.getBalance(log.contract.getAddress()).toString(16) |
71 | | - }); |
72 | | - return |
73 | | - } |
74 | | - // If a new method invocation is being done, add to the call stack |
75 | | - if (syscall && (op == 'CALL' || op == 'CALLCODE' || op == 'DELEGATECALL' || op == 'STATICCALL')) { |
76 | | - var to = toAddress(log.stack.peek(1).toString(16)); |
77 | | - |
78 | | - // We don't skip any pre-compile invocations unlike the official |
79 | | - // geth tracer. This can silence meaningful transfers. |
80 | | - // if (isPrecompiled(to)) { |
81 | | - // return |
82 | | - // } |
83 | | - |
84 | | - var off = (op == 'DELEGATECALL' || op == 'STATICCALL' ? 0 : 1); |
85 | | - |
86 | | - var inOff = log.stack.peek(2 + off).valueOf(); |
87 | | - var inEnd = inOff + log.stack.peek(3 + off).valueOf(); |
88 | | - |
89 | | - // Assemble the internal call report and store for completion |
90 | | - var call = { |
91 | | - type: op, |
92 | | - from: toHex(log.contract.getAddress()), |
93 | | - to: toHex(to), |
94 | | - input: toHex(log.memory.slice(inOff, inEnd)), |
95 | | - gasIn: log.getGas(), |
96 | | - gasCost: log.getCost(), |
97 | | - outOff: log.stack.peek(4 + off).valueOf(), |
98 | | - outLen: log.stack.peek(5 + off).valueOf() |
99 | | - }; |
100 | | - if (op != 'DELEGATECALL' && op != 'STATICCALL') { |
101 | | - call.value = '0x' + log.stack.peek(2).toString(16); |
102 | | - } |
103 | | - this.callstack.push(call); |
104 | | - this.descended = true |
105 | | - return; |
106 | | - } |
107 | | - // If we've just descended into an inner call, retrieve it's true allowance. We |
108 | | - // need to extract if from within the call as there may be funky gas dynamics |
109 | | - // with regard to requested and actually given gas (2300 stipend, 63/64 rule). |
110 | | - if (this.descended) { |
111 | | - if (log.getDepth() >= this.callstack.length) { |
112 | | - this.callstack[this.callstack.length - 1].gas = log.getGas(); |
113 | | - } else { |
114 | | - // TODO(karalabe): The call was made to a plain account. We currently don't |
115 | | - // have access to the true gas amount inside the call and so any amount will |
116 | | - // mostly be wrong since it depends on a lot of input args. Skip gas for now. |
117 | | - } |
118 | | - this.descended = false; |
119 | | - } |
120 | | - // If an existing call is returning, pop off the call stack |
121 | | - if (syscall && op == 'REVERT') { |
122 | | - this.callstack[this.callstack.length - 1].error = "execution reverted"; |
123 | | - return; |
124 | | - } |
125 | | - if (log.getDepth() == this.callstack.length - 1) { |
126 | | - // Pop off the last call and get the execution results |
127 | | - var call = this.callstack.pop(); |
128 | | - |
129 | | - if (call.type == 'CREATE' || call.type == "CREATE2") { |
130 | | - // If the call was a CREATE, retrieve the contract address and output code |
131 | | - call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost - log.getGas()).toString(16); |
132 | | - delete call.gasIn; delete call.gasCost; |
133 | | - |
134 | | - var ret = log.stack.peek(0); |
135 | | - if (!ret.equals(0)) { |
136 | | - call.to = toHex(toAddress(ret.toString(16))); |
137 | | - call.output = toHex(db.getCode(toAddress(ret.toString(16)))); |
138 | | - } else if (call.error === undefined) { |
139 | | - call.error = "internal failure"; // TODO(karalabe): surface these faults somehow |
140 | | - } |
141 | | - } else { |
142 | | - // If the call was a contract call, retrieve the gas usage and output |
143 | | - if (call.gas !== undefined) { |
144 | | - call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16); |
145 | | - } |
146 | | - var ret = log.stack.peek(0); |
147 | | - if (!ret.equals(0)) { |
148 | | - call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen)); |
149 | | - } else if (call.error === undefined) { |
150 | | - call.error = "internal failure"; // TODO(karalabe): surface these faults somehow |
151 | | - } |
152 | | - delete call.gasIn; delete call.gasCost; |
153 | | - delete call.outOff; delete call.outLen; |
154 | | - } |
155 | | - if (call.gas !== undefined) { |
156 | | - call.gas = '0x' + bigInt(call.gas).toString(16); |
157 | | - } |
158 | | - // Inject the call into the previous one |
159 | | - var left = this.callstack.length; |
160 | | - if (this.callstack[left-1].calls === undefined) { |
161 | | - this.callstack[left-1].calls = []; |
162 | | - } |
163 | | - this.callstack[left-1].calls.push(call); |
164 | | - } |
165 | | - }, |
166 | | - |
167 | | - // fault is invoked when the actual execution of an opcode fails. |
168 | | - fault: function(log, db) { |
169 | | - // If the topmost call already reverted, don't handle the additional fault again |
170 | | - if (this.callstack[this.callstack.length - 1].error !== undefined) { |
171 | | - return; |
172 | | - } |
173 | | - // Pop off the just failed call |
174 | | - var call = this.callstack.pop(); |
175 | | - call.error = log.getError(); |
176 | | - |
177 | | - // Consume all available gas and clean any leftovers |
178 | | - if (call.gas !== undefined) { |
179 | | - call.gas = '0x' + bigInt(call.gas).toString(16); |
180 | | - call.gasUsed = call.gas |
181 | | - } |
182 | | - delete call.gasIn; delete call.gasCost; |
183 | | - delete call.outOff; delete call.outLen; |
184 | | - |
185 | | - // Flatten the failed call into its parent |
186 | | - var left = this.callstack.length; |
187 | | - if (left > 0) { |
188 | | - if (this.callstack[left-1].calls === undefined) { |
189 | | - this.callstack[left-1].calls = []; |
190 | | - } |
191 | | - this.callstack[left-1].calls.push(call); |
192 | | - return; |
193 | | - } |
194 | | - // Last call failed too, leave it in the stack |
195 | | - this.callstack.push(call); |
196 | | - }, |
197 | | - |
198 | | - // result is invoked when all the opcodes have been iterated over and returns |
199 | | - // the final result of the tracing. |
| 22 | + fault: function(log, db) {}, |
200 | 23 | result: function(ctx, db) { |
| 24 | + // Prepare outer message info |
201 | 25 | var result = { |
202 | 26 | type: ctx.type, |
203 | 27 | from: toHex(ctx.from), |
|
207 | 31 | gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16), |
208 | 32 | input: toHex(ctx.input), |
209 | 33 | output: toHex(ctx.output), |
210 | | - time: ctx.time, |
211 | | - }; |
| 34 | + } |
212 | 35 | if (this.callstack[0].calls !== undefined) { |
213 | | - result.calls = this.callstack[0].calls; |
| 36 | + result.calls = this.callstack[0].calls |
214 | 37 | } |
215 | 38 | if (this.callstack[0].error !== undefined) { |
216 | | - result.error = this.callstack[0].error; |
| 39 | + result.error = this.callstack[0].error |
217 | 40 | } else if (ctx.error !== undefined) { |
218 | | - result.error = ctx.error; |
| 41 | + result.error = ctx.error |
219 | 42 | } |
220 | 43 | if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) { |
221 | | - delete result.output; |
| 44 | + delete result.output |
222 | 45 | } |
223 | | - return this.finalize(result); |
224 | | - }, |
225 | 46 |
|
| 47 | + return this.finalize(result) |
| 48 | + }, |
| 49 | + enter: function(frame) { |
| 50 | + var call = { |
| 51 | + type: frame.getType(), |
| 52 | + from: toHex(frame.getFrom()), |
| 53 | + to: toHex(frame.getTo()), |
| 54 | + input: toHex(frame.getInput()), |
| 55 | + gas: '0x' + bigInt(frame.getGas()).toString('16'), |
| 56 | + } |
| 57 | + if (frame.getValue() !== undefined){ |
| 58 | + call.value='0x' + bigInt(frame.getValue()).toString(16) |
| 59 | + } |
| 60 | + this.callstack.push(call) |
| 61 | + }, |
| 62 | + exit: function(frameResult) { |
| 63 | + var len = this.callstack.length |
| 64 | + if (len > 1) { |
| 65 | + var call = this.callstack.pop() |
| 66 | + call.gasUsed = '0x' + bigInt(frameResult.getGasUsed()).toString('16') |
| 67 | + var error = frameResult.getError() |
| 68 | + if (error === undefined) { |
| 69 | + call.output = toHex(frameResult.getOutput()) |
| 70 | + } else { |
| 71 | + call.error = error |
| 72 | + if (call.type === 'CREATE' || call.type === 'CREATE2') { |
| 73 | + delete call.to |
| 74 | + } |
| 75 | + } |
| 76 | + len -= 1 |
| 77 | + if (this.callstack[len-1].calls === undefined) { |
| 78 | + this.callstack[len-1].calls = [] |
| 79 | + } |
| 80 | + this.callstack[len-1].calls.push(call) |
| 81 | + } |
| 82 | + }, |
226 | 83 | // finalize recreates a call object using the final desired field oder for json |
227 | 84 | // serialization. This is a nicety feature to pass meaningfully ordered results |
228 | 85 | // to users who don't interpret it, just display it. |
|
242 | 99 | } |
243 | 100 | for (var key in sorted) { |
244 | 101 | if (sorted[key] === undefined) { |
245 | | - delete sorted[key]; |
| 102 | + delete sorted[key] |
246 | 103 | } |
247 | 104 | } |
248 | 105 | if (sorted.calls !== undefined) { |
249 | 106 | for (var i=0; i<sorted.calls.length; i++) { |
250 | | - sorted.calls[i] = this.finalize(sorted.calls[i]); |
| 107 | + sorted.calls[i] = this.finalize(sorted.calls[i]) |
251 | 108 | } |
252 | 109 | } |
253 | | - return sorted; |
| 110 | + return sorted |
254 | 111 | } |
255 | 112 | } |
0 commit comments