aboutsummaryrefslogtreecommitdiffstats
path: root/packages/sol-tracing-utils/src/trace_info_subprovider.ts
blob: b75fc7bf7eccef04e6ff6cbef52cb3a5853d0664 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
import { NodeType } from '@0x/web3-wrapper';
import * as _ from 'lodash';

import { constants } from './constants';
import { getContractAddressToTraces } from './trace';
import { TraceCollectionSubprovider } from './trace_collection_subprovider';
import { TraceInfo, TraceInfoExistingContract, TraceInfoNewContract } from './types';

// TraceInfoSubprovider is extended by subproviders which need to work with one
// TraceInfo at a time. It has one abstract method: _handleTraceInfoAsync, which
// is called for each TraceInfo.
export abstract class TraceInfoSubprovider extends TraceCollectionSubprovider {
    protected abstract _handleTraceInfoAsync(traceInfo: TraceInfo): Promise<void>;
    protected async _recordTxTraceAsync(address: string, data: string | undefined, txHash: string): Promise<void> {
        await this._web3Wrapper.awaitTransactionMinedAsync(txHash, 0);
        const nodeType = await this._web3Wrapper.getNodeTypeAsync();
        let trace;
        if (nodeType === NodeType.Geth) {
            // For very large traces we use a custom tracer that outputs a format compatible with a
            // regular trace. We only need the 2nd item on the stack when the instruction is a call.
            // By not including other stack values, we drastically limit the amount of data to be collected.
            // There are no good docs about how to write those tracers, but you can find some example ones here:
            // https://github.com/ethereum/go-ethereum/tree/master/eth/tracers/internal/tracers
            const tracer = `
                {
                    data: [],
                    step: function(log) {
                        const op = log.op.toString();
                        const opn = 0 | log.op.toNumber();
                        const pc = 0 | log.getPC();
                        const depth = 0 | log.getDepth();
                        const gasCost = 0 | log.getCost();
                        const gas = 0 | log.getGas();
                        const isCall = opn == 0xf1 || opn == 0xf2 || opn == 0xf4 || opn == 0xf5;
                        const stack = isCall ? ['0x'+log.stack.peek(1).toString(16), null] : null;
                        this.data.push({ pc, gasCost, depth, op, stack, gas });
                    },
                    fault: function() { },
                    result: function() { return {structLogs: this.data}; }
                }
            `;
            trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, { tracer, timeout: '600s' });
        } else {
            /**
             * Ganache doesn't support custom tracers yet.
             */
            trace = await this._web3Wrapper.getTransactionTraceAsync(txHash, {
                disableMemory: true,
                disableStack: false,
                disableStorage: true,
            });
        }
        const contractAddressToTraces = getContractAddressToTraces(trace.structLogs, address);
        const subcallAddresses = _.keys(contractAddressToTraces);
        if (address === constants.NEW_CONTRACT) {
            for (const subcallAddress of subcallAddresses) {
                let traceInfo: TraceInfoNewContract | TraceInfoExistingContract;
                if (subcallAddress === 'NEW_CONTRACT') {
                    const traceForThatSubcall = contractAddressToTraces[subcallAddress];
                    traceInfo = {
                        subtrace: traceForThatSubcall,
                        txHash,
                        address: subcallAddress,
                        bytecode: data as string,
                    };
                } else {
                    const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress);
                    const traceForThatSubcall = contractAddressToTraces[subcallAddress];
                    traceInfo = {
                        subtrace: traceForThatSubcall,
                        txHash,
                        address: subcallAddress,
                        runtimeBytecode,
                    };
                }
                await this._handleTraceInfoAsync(traceInfo);
            }
        } else {
            for (const subcallAddress of subcallAddresses) {
                const runtimeBytecode = await this._web3Wrapper.getContractCodeAsync(subcallAddress);
                const traceForThatSubcall = contractAddressToTraces[subcallAddress];
                const traceInfo: TraceInfoExistingContract = {
                    subtrace: traceForThatSubcall,
                    txHash,
                    address: subcallAddress,
                    runtimeBytecode,
                };
                await this._handleTraceInfoAsync(traceInfo);
            }
        }
    }
}