github.com/0xPolygon/supernets2-node@v0.0.0-20230711153321-2fe574524eaa/state/runtime/instrumentation/js/internal/tracers/call_tracer_legacy.js (about) 1 // Copyright 2017 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 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. 19 { 20 // callstack is the current recursive call stack of the EVM execution. 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 // Skip any pre-compile invocations, those are just fancy opcodes 77 var to = toAddress(log.stack.peek(1).toString(16)); 78 if (isPrecompiled(to)) { 79 return 80 } 81 var off = (op == 'DELEGATECALL' || op == 'STATICCALL' ? 0 : 1); 82 83 var inOff = log.stack.peek(2 + off).valueOf(); 84 var inEnd = inOff + log.stack.peek(3 + off).valueOf(); 85 86 // Assemble the internal call report and store for completion 87 var call = { 88 type: op, 89 from: toHex(log.contract.getAddress()), 90 to: toHex(to), 91 input: toHex(log.memory.slice(inOff, inEnd)), 92 gasIn: log.getGas(), 93 gasCost: log.getCost(), 94 outOff: log.stack.peek(4 + off).valueOf(), 95 outLen: log.stack.peek(5 + off).valueOf() 96 }; 97 if (op != 'DELEGATECALL' && op != 'STATICCALL') { 98 call.value = '0x' + log.stack.peek(2).toString(16); 99 } 100 this.callstack.push(call); 101 this.descended = true 102 return; 103 } 104 // If we've just descended into an inner call, retrieve it's true allowance. We 105 // need to extract if from within the call as there may be funky gas dynamics 106 // with regard to requested and actually given gas (2300 stipend, 63/64 rule). 107 if (this.descended) { 108 if (log.getDepth() >= this.callstack.length) { 109 this.callstack[this.callstack.length - 1].gas = log.getGas(); 110 } else { 111 // TODO(karalabe): The call was made to a plain account. We currently don't 112 // have access to the true gas amount inside the call and so any amount will 113 // mostly be wrong since it depends on a lot of input args. Skip gas for now. 114 } 115 this.descended = false; 116 } 117 // If an existing call is returning, pop off the call stack 118 if (syscall && op == 'REVERT') { 119 this.callstack[this.callstack.length - 1].error = "execution reverted"; 120 return; 121 } 122 if (log.getDepth() == this.callstack.length - 1) { 123 // Pop off the last call and get the execution results 124 var call = this.callstack.pop(); 125 126 if (call.type == 'CREATE' || call.type == "CREATE2") { 127 // If the call was a CREATE, retrieve the contract address and output code 128 call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost - log.getGas()).toString(16); 129 delete call.gasIn; delete call.gasCost; 130 131 var ret = log.stack.peek(0); 132 if (!ret.equals(0)) { 133 call.to = toHex(toAddress(ret.toString(16))); 134 call.output = toHex(db.getCode(toAddress(ret.toString(16)))); 135 } else if (call.error === undefined) { 136 call.error = "internal failure"; // TODO(karalabe): surface these faults somehow 137 } 138 } else { 139 // If the call was a contract call, retrieve the gas usage and output 140 if (call.gas !== undefined) { 141 call.gasUsed = '0x' + bigInt(call.gasIn - call.gasCost + call.gas - log.getGas()).toString(16); 142 } 143 var ret = log.stack.peek(0); 144 if (!ret.equals(0)) { 145 call.output = toHex(log.memory.slice(call.outOff, call.outOff + call.outLen)); 146 } else if (call.error === undefined) { 147 call.error = "internal failure"; // TODO(karalabe): surface these faults somehow 148 } 149 delete call.gasIn; delete call.gasCost; 150 delete call.outOff; delete call.outLen; 151 } 152 if (call.gas !== undefined) { 153 call.gas = '0x' + bigInt(call.gas).toString(16); 154 } 155 // Inject the call into the previous one 156 var left = this.callstack.length; 157 if (this.callstack[left-1].calls === undefined) { 158 this.callstack[left-1].calls = []; 159 } 160 this.callstack[left-1].calls.push(call); 161 } 162 }, 163 164 // fault is invoked when the actual execution of an opcode fails. 165 fault: function(log, db) { 166 // If the topmost call already reverted, don't handle the additional fault again 167 if (this.callstack[this.callstack.length - 1].error !== undefined) { 168 return; 169 } 170 // Pop off the just failed call 171 var call = this.callstack.pop(); 172 call.error = log.getError(); 173 174 // Consume all available gas and clean any leftovers 175 if (call.gas !== undefined) { 176 call.gas = '0x' + bigInt(call.gas).toString(16); 177 call.gasUsed = call.gas 178 } 179 delete call.gasIn; delete call.gasCost; 180 delete call.outOff; delete call.outLen; 181 182 // Flatten the failed call into its parent 183 var left = this.callstack.length; 184 if (left > 0) { 185 if (this.callstack[left-1].calls === undefined) { 186 this.callstack[left-1].calls = []; 187 } 188 this.callstack[left-1].calls.push(call); 189 return; 190 } 191 // Last call failed too, leave it in the stack 192 this.callstack.push(call); 193 }, 194 195 // result is invoked when all the opcodes have been iterated over and returns 196 // the final result of the tracing. 197 result: function(ctx, db) { 198 var result = { 199 type: ctx.type, 200 from: toHex(ctx.from), 201 to: toHex(ctx.to), 202 value: '0x' + ctx.value.toString(16), 203 gas: '0x' + bigInt(ctx.gas).toString(16), 204 gasUsed: '0x' + bigInt(ctx.gasUsed).toString(16), 205 input: toHex(ctx.input), 206 output: toHex(ctx.output), 207 time: ctx.time, 208 }; 209 if (this.callstack[0].calls !== undefined) { 210 result.calls = this.callstack[0].calls; 211 } 212 if (this.callstack[0].error !== undefined) { 213 result.error = this.callstack[0].error; 214 } else if (ctx.error !== undefined) { 215 result.error = ctx.error; 216 } 217 if (result.error !== undefined && (result.error !== "execution reverted" || result.output ==="0x")) { 218 delete result.output; 219 } 220 return this.finalize(result); 221 }, 222 223 // finalize recreates a call object using the final desired field oder for json 224 // serialization. This is a nicety feature to pass meaningfully ordered results 225 // to users who don't interpret it, just display it. 226 finalize: function(call) { 227 var sorted = { 228 type: call.type, 229 from: call.from, 230 to: call.to, 231 value: call.value, 232 gas: call.gas, 233 gasUsed: call.gasUsed, 234 input: call.input, 235 output: call.output, 236 error: call.error, 237 time: call.time, 238 calls: call.calls, 239 } 240 for (var key in sorted) { 241 if (sorted[key] === undefined) { 242 delete sorted[key]; 243 } 244 } 245 if (sorted.calls !== undefined) { 246 for (var i=0; i<sorted.calls.length; i++) { 247 sorted.calls[i] = this.finalize(sorted.calls[i]); 248 } 249 } 250 return sorted; 251 } 252 }