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  }