<commit_before>import debugModule from "debug";
const debug = debugModule("decoder:interface:contract-decoder");

import * as DecodeUtils from "truffle-decode-utils";
import AsyncEventEmitter from "async-eventemitter";
import Web3 from "web3";
import { ContractObject } from "truffle-contract-schema/spec";
import BN from "bn.js";
import { EvmInfo } from "../types/evm";
import * as general from "../allocate/general";
import * as storage from "../allocate/storage";
import { StoragePointer, isStoragePointer } from "../types/pointer";
import { StorageAllocations, StorageMemberAllocations, StorageMemberAllocation } from "../types/allocation";
import { Slot, isWordsLength } from "../types/storage";
import { DecoderRequest, isStorageRequest, isCodeRequest } from "../types/request";
import decode from "../decode";
import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-decode-utils";
import { BlockType, Transaction } from "web3/eth/types";
import { EventLog, Log } from "web3/types";
import { Provider } from "web3/providers";
import abiDecoder from "abi-decoder";
import isEqual from "lodash.isequal"; //util.isDeepStrictEqual doesn't exist in Node 8

export interface EvmMapping {
  name: string;
  type: string;
  id: number;
  keyType: string;
  valueType: string;
  members: {
    [key: string]: EvmVariable;
  };
};

export interface EvmStruct {
  name: string;
  type: string;
  members: {
    [name: string]: DecodedVariable;
  };
};

export interface EvmEnum {
  type: string;
  value: string;
};

type EvmVariable = BN | string | boolean | EvmMapping | EvmStruct | EvmEnum;

interface DecodedVariable {
  name: string;
  type: string;
  value: EvmVariable;
};

interface ContractState {
  name: string;
  balance: BN;
  nonce: BN;
  code: string;
  variables: {
    [name: string]: DecodedVariable
  };
};

interface ContractEvent {
  logIndex: number;
  name: string;
  blockHash: string;
  blockNumber: number;
  transactionHash: string;
  transactionIndex: number;
  variables: {
    [name: string]: DecodedVariable
  }
};

export interface ContractMapping {
  [nodeId: number]: ContractObject;
};

//note: may return undefined
function getContractNode(contract: ContractObject): AstDefinition {
  return (contract.ast || {nodes: []}).nodes.find(
    (contractNode: AstDefinition) =>
    contractNode.nodeType === "ContractDefinition"
    && (contractNode.name === contract.contractName
      || contractNode.name === contract.contract_name)
  );
}

export default class TruffleContractDecoder extends AsyncEventEmitter {
  private web3: Web3;

  private contract: ContractObject;
  private contractNode: AstDefinition;
  private contractNetwork: string;
  private contractAddress: string;
  private contractCode: string;
  private relevantContracts: ContractObject[];

  private contracts: ContractMapping = {};
  private contractNodes: AstReferences = {};
  private contexts: DecodeUtils.Contexts.DecoderContexts = {};

  private referenceDeclarations: AstReferences;
  private storageAllocations: StorageAllocations;

  private eventDefinitions: AstReferences;
  private eventDefinitionIdsByName: {
    [name: string]: number
  };

  private stateVariableReferences: StorageMemberAllocations;

  private mappingKeys: Slot[] = [];

  constructor(contract: ContractObject, relevantContracts: ContractObject[], provider: Provider, address: string) {
    super();

    this.web3 = new Web3(provider);

    this.contract = contract;
    this.relevantContracts = relevantContracts;

    this.contractNetwork = Object.keys(this.contract.networks)[0];
    this.contractAddress = address !== undefined
      ? address
      : this.contract.networks[this.contractNetwork].address;

    this.contractNode = getContractNode(this.contract);

    this.contracts[this.contractNode.id] = this.contract;
    this.contractNodes[this.contractNode.id] = this.contractNode;
    if(this.contract.deployedBinary) { //just to be safe
      this.contexts[this.contractNode.id] = this.makeContext(this.contract, this.contractNode);
    }
    abiDecoder.addABI(this.contract.abi);

    for(let relevantContract of this.relevantContracts) {
      abiDecoder.addABI(relevantContract.abi);
      let node: AstDefinition = getContractNode(relevantContract);
      if(node !== undefined) {
        this.contracts[node.id] = relevantContract;
        this.contractNodes[node.id] = node;
        if(relevantContract.deployedBinary) {
          this.contexts[node.id] = this.makeContext(relevantContract, node);
        }
      }
    }

    this.contexts = <DecodeUtils.Contexts.DecoderContexts>DecodeUtils.Contexts.normalizeContexts(this.contexts);
  }

  private makeContext(contract: ContractObject, node: AstDefinition) {
    //we want the non-constructor context here
    return {
      contractName: contract.contractName,
      binary: contract.deployedBytecode,
      contractId: node.id,
      contractKind: node.contractKind,
      isConstructor: false,
      abi: DecodeUtils.Contexts.abiToFunctionAbiWithSignatures(contract.abi)
    };
  }

  public async init(): Promise<void> {
    debug("init called");
    this.referenceDeclarations = general.getReferenceDeclarations(Object.values(this.contractNodes));

    this.eventDefinitions = general.getEventDefinitions(Object.values(this.contractNodes));
    this.eventDefinitionIdsByName = {};
    for (let id in this.eventDefinitions) {
      this.eventDefinitionIdsByName[this.eventDefinitions[id].name] = parseInt(id);
        //this parseInt shouldn't be necessary, but TypeScript refuses to believe
        //that id must be a number even though the definition of AstReferences
        //says so<commit_msg>Fix getting network in contract decoder<commit_after>import debugModule from "debug";
const debug = debugModule("decoder:interface:contract-decoder");

import * as DecodeUtils from "truffle-decode-utils";
import AsyncEventEmitter from "async-eventemitter";
import Web3 from "web3";
import { ContractObject } from "truffle-contract-schema/spec";
import BN from "bn.js";
import { EvmInfo } from "../types/evm";
import * as general from "../allocate/general";
import * as storage from "../allocate/storage";
import { StoragePointer, isStoragePointer } from "../types/pointer";
import { StorageAllocations, StorageMemberAllocations, StorageMemberAllocation } from "../types/allocation";
import { Slot, isWordsLength } from "../types/storage";
import { DecoderRequest, isStorageRequest, isCodeRequest } from "../types/request";
import decode from "../decode";
import { Definition as DefinitionUtils, EVM, AstDefinition, AstReferences } from "truffle-decode-utils";
import { BlockType, Transaction } from "web3/eth/types";
import { EventLog, Log } from "web3/types";
import { Provider } from "web3/providers";
import abiDecoder from "abi-decoder";
import isEqual from "lodash.isequal"; //util.isDeepStrictEqual doesn't exist in Node 8

export interface EvmMapping {
  name: string;
  type: string;
  id: number;
  keyType: string;
  valueType: string;
  members: {
    [key: string]: EvmVariable;
  };
};

export interface EvmStruct {
  name: string;
  type: string;
  members: {
    [name: string]: DecodedVariable;
  };
};

export interface EvmEnum {
  type: string;
  value: string;
};

type EvmVariable = BN | string | boolean | EvmMapping | EvmStruct | EvmEnum;

interface DecodedVariable {
  name: string;
  type: string;
  value: EvmVariable;
};

interface ContractState {
  name: string;
  balance: BN;
  nonce: BN;
  code: string;
  variables: {
    [name: string]: DecodedVariable
  };
};

interface ContractEvent {
  logIndex: number;
  name: string;
  blockHash: string;
  blockNumber: number;
  transactionHash: string;
  transactionIndex: number;
  variables: {
    [name: string]: DecodedVariable
  }
};

export interface ContractMapping {
  [nodeId: number]: ContractObject;
};

//note: may return undefined
function getContractNode(contract: ContractObject): AstDefinition {
  return (contract.ast || {nodes: []}).nodes.find(
    (contractNode: AstDefinition) =>
    contractNode.nodeType === "ContractDefinition"
    && (contractNode.name === contract.contractName
      || contractNode.name === contract.contract_name)
  );
}

export default class TruffleContractDecoder extends AsyncEventEmitter {
  private web3: Web3;

  private contract: ContractObject;
  private contractNode: AstDefinition;
  private contractNetwork: string;
  private contractAddress: string;
  private contractCode: string;
  private relevantContracts: ContractObject[];

  private contracts: ContractMapping = {};
  private contractNodes: AstReferences = {};
  private contexts: DecodeUtils.Contexts.DecoderContexts = {};

  private referenceDeclarations: AstReferences;
  private storageAllocations: StorageAllocations;

  private eventDefinitions: AstReferences;
  private eventDefinitionIdsByName: {
    [name: string]: number
  };

  private stateVariableReferences: StorageMemberAllocations;

  private mappingKeys: Slot[] = [];

  constructor(contract: ContractObject, relevantContracts: ContractObject[], provider: Provider, address: string) {
    super();

    this.web3 = new Web3(provider);

    this.contract = contract;
    this.relevantContracts = relevantContracts;

    if(address !== undefined) {
      this.contractAddress === address;
    }
    this.contractNode = getContractNode(this.contract);

    this.contracts[this.contractNode.id] = this.contract;
    this.contractNodes[this.contractNode.id] = this.contractNode;
    if(this.contract.deployedBinary) { //just to be safe
      this.contexts[this.contractNode.id] = this.makeContext(this.contract, this.contractNode);
    }
    abiDecoder.addABI(this.contract.abi);

    for(let relevantContract of this.relevantContracts) {
      abiDecoder.addABI(relevantContract.abi);
      let node: AstDefinition = getContractNode(relevantContract);
      if(node !== undefined) {
        this.contracts[node.id] = relevantContract;
        this.contractNodes[node.id] = node;
        if(relevantContract.deployedBinary) {
          this.contexts[node.id] = this.makeContext(relevantContract, node);
        }
      }
    }

    this.contexts = <DecodeUtils.Contexts.DecoderContexts>DecodeUtils.Contexts.normalizeContexts(this.contexts);
  }

  private makeContext(contract: ContractObject, node: AstDefinition) {
    //we want the non-constructor context here
    return {
      contractName: contract.contractName,
      binary: contract.deployedBytecode,
      contractId: node.id,
      contractKind: node.contractKind,
      isConstructor: false,
      abi: DecodeUtils.Contexts.abiToFunctionAbiWithSignatures(contract.abi)
    };
  }

  public async init(): Promise<void> {
    this.contractNetwork = (await this.web3.eth.net.getId()).toString();
    if(this.contractAddress === undefined) {
      this.contractAddress = this.contract.networks[this.contractNetwork].address;
    }

    debug("init called");
    this.referenceDeclarations = general.getReferenceDeclarations(Object.values(this.contractNodes));

    this.eventDefinitions = general.getEventDefinitions(Object.values(this.contractNodes));
    this.eventDefinitionIdsByName = {};
    for (let id in this.eventDefinitions) {
      this.eventDefinitionIdsByName[this.eventDefinitions[id].name] = parseInt(id);
        //this parseInt shouldn't be necessary, but TypeScript refuses to believe
        //that id must be a number even though the definition of AstReferences
        //says so
{'fraction_non_alphanumeric': 0.11070527871988868, 'fraction_numerical': 0.00191320984433429, 'mean_word_length': 4.314232902033272, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 9, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '675269', 'n_tokens_mistral': 3285, 'n_tokens_neox': 3197, 'n_words': 798}