text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.abiUtils = void 0;
  const ethereum_types_1 = require("ethereum-types");
  const _ = require("lodash");
  const configured_bignumber_1 = require("./configured_bignumber");
  // Note(albrow): This function is unexported in ethers.js. Copying it here for
  // now.
  // Source: https://github.com/ethers-io/ethers.js/blob/884593ab76004a808bf8097e9753fb5f8dcc3067/contracts/interface.js#L30
  function parseEthersParams(params) {
      const names = [];
      const types = [];
      params.forEach((param) => {
          if (param.components != null) {
              let suffix = '';
              const arrayBracket = param.type.indexOf('[');
              if (arrayBracket >= 0) {
                  suffix = param.type.substring(arrayBracket);
              }
              const result = parseEthersParams(param.components);
              names.push({ name: param.name || null, names: result.names });
              types.push(`tuple(${result.types.join(',')})${suffix}`);
          }
          else {
              names.push(param.name || null);
              types.push(param.type);
          }
      });
      return {
          names,
          types,
      };
  }
  // returns true if x is equal to y and false otherwise. Performs some minimal
  // type conversion and data massaging for x and y, depending on type. name and
  // type should typically be derived from parseEthersParams.
  function isAbiDataEqual(name, type, x, y) {
      if (x === undefined && y === undefined) {
          return true;
      }
      else if (x === undefined && y !== undefined) {
          return false;
      }
      else if (x !== undefined && y === undefined) {
          return false;
      }
      if (_.endsWith(type, '[]')) {
          // For array types, we iterate through the elements and check each one
          // individually. Strangely, name does not need to be changed in this
          // case.
          if (x.length !== y.length) {
              return false;
          }
          const newType = _.trimEnd(type, '[]');
          for (let i = 0; i < x.length; i++) {
              if (!isAbiDataEqual(name, newType, x[i], y[i])) {
                  return false;
              }
          }
          return true;
      }
      if (_.startsWith(type, 'tuple(')) {
          if (_.isString(name)) {
              throw new Error('Internal error: type was tuple but names was a string');
          }
          else if (name === null) {
              throw new Error('Internal error: type was tuple but names was null');
          }
          // For tuples, we iterate through the underlying values and check each
          // one individually.
          const types = splitTupleTypes(type);
          if (types.length !== name.names.length) {
              throw new Error(`Internal error: parameter types/names length mismatch (${types.length} != ${name.names.length})`);
          }
          for (let i = 0; i < types.length; i++) {
              // For tuples, name is an object with a names property that is an
              // array. As an example, for orders, name looks like:
              //
              //  {
              //      name: 'orders',
              //      names: [
              //          'makerAddress',
              //          // ...
              //          'takerAssetData'
              //      ]
              //  }
              //
              const nestedName = _.isString(name.names[i])
                  ? name.names[i]
                  : name.names[i].name;
              if (!isAbiDataEqual(name.names[i], types[i], x[nestedName], y[nestedName])) {
                  return false;
              }
          }
          return true;
      }
      else if (type === 'address' || type === 'bytes') {
          // HACK(albrow): ethers.js returns the checksummed address even when
          // initially passed in a non-checksummed address. To account for that,
          // we convert to lowercase before comparing.
          return _.isEqual(_.toLower(x), _.toLower(y));
      }
      else if (_.startsWith(type, 'uint') || _.startsWith(type, 'int')) {
          return new configured_bignumber_1.BigNumber(x).eq(new configured_bignumber_1.BigNumber(y));
      }
      return _.isEqual(x, y);
  }
  // splitTupleTypes splits a tuple type string (of the form `tuple(X)` where X is
  // any other type or list of types) into its component types. It works with
  // nested tuples, so, e.g., `tuple(tuple(uint256,address),bytes32)` will yield:
  // `['tuple(uint256,address)', 'bytes32']`. It expects exactly one tuple type as
  // an argument (not an array).
  function splitTupleTypes(type) {
      if (_.endsWith(type, '[]')) {
          throw new Error('Internal error: array types are not supported');
      }
      else if (!_.startsWith(type, 'tuple(')) {
          throw new Error(`Internal error: expected tuple type but got non-tuple type: ${type}`);
      }
      // Trim the outtermost tuple().
      const trimmedType = type.substring('tuple('.length, type.length - 1);
      const types = [];
      let currToken = '';
      let parenCount = 0;
      // Tokenize the type string while keeping track of parentheses.
      for (const char of trimmedType) {
          switch (char) {
              case '(':
                  parenCount += 1;
                  currToken += char;
                  break;
              case ')':
                  parenCount -= 1;
                  currToken += char;
                  break;
              case ',':
                  if (parenCount === 0) {
                      types.push(currToken);
                      currToken = '';
                      break;
                  }
                  else {
                      currToken += char;
                      break;
                  }
              default:
                  currToken += char;
                  break;
          }
      }
      types.push(currToken);
      return types;
  }
  exports.abiUtils = {
      parseEthersParams,
      isAbiDataEqual,
      splitTupleTypes,
      parseFunctionParam(param) {
          if (param.type === 'tuple') {
              // Parse out tuple types into {type_1, type_2, ..., type_N}
              const tupleComponents = param.components;
              const paramString = _.map(tupleComponents, component => exports.abiUtils.parseFunctionParam(component));
              const tupleParamString = `{${paramString}}`;
              return tupleParamString;
          }
          return param.type;
      },
      getFunctionSignature(methodAbi) {
          const functionName = methodAbi.name;
          const parameterTypeList = _.map(methodAbi.inputs, (param) => exports.abiUtils.parseFunctionParam(param));
          const functionSignature = `${functionName}(${parameterTypeList})`;
          return functionSignature;
      },
      /**
       * Solidity supports function overloading whereas TypeScript does not.
       * See: https://solidity.readthedocs.io/en/v0.4.21/contracts.html?highlight=overload#function-overloading
       * In order to support overloaded functions, we suffix overloaded function names with an index.
       * This index should be deterministic, regardless of function ordering within the smart contract. To do so,
       * we assign indexes based on the alphabetical order of function signatures.
       *
       * E.g
       * ['f(uint)', 'f(uint,byte32)']
       * Should always be renamed to:
       * ['f1(uint)', 'f2(uint,byte32)']
       * Regardless of the order in which these these overloaded functions are declared within the contract ABI.
       */
      renameOverloadedMethods(inputContractAbi) {
          const contractAbi = _.cloneDeep(inputContractAbi);
          const methodAbis = contractAbi.filter((abi) => abi.type === ethereum_types_1.AbiType.Function);
          // Sort method Abis into alphabetical order, by function signature
          const methodAbisOrdered = _.sortBy(methodAbis, [
              (methodAbi) => {
                  const functionSignature = exports.abiUtils.getFunctionSignature(methodAbi);
                  return functionSignature;
              },
          ]);
          // Group method Abis by name (overloaded methods will be grouped together, in alphabetical order)
          const methodAbisByName = {};
          _.each(methodAbisOrdered, methodAbi => {
              (methodAbisByName[methodAbi.name] || (methodAbisByName[methodAbi.name] = [])).push(methodAbi);
          });
          // Rename overloaded methods to overloadedMethodName1, overloadedMethodName2, ...
          _.each(methodAbisByName, methodAbisWithSameName => {
              _.each(methodAbisWithSameName, (methodAbi, i) => {
                  if (methodAbisWithSameName.length > 1) {
                      const overloadedMethodId = i + 1;
                      const sanitizedMethodName = `${methodAbi.name}${overloadedMethodId}`;
                      const indexOfExistingAbiWithSanitizedMethodNameIfExists = _.findIndex(methodAbis, currentMethodAbi => currentMethodAbi.name === sanitizedMethodName);
                      if (indexOfExistingAbiWithSanitizedMethodNameIfExists >= 0) {
                          const methodName = methodAbi.name;
                          throw new Error(`Failed to rename overloaded method '${methodName}' to '${sanitizedMethodName}'. A method with this name already exists.`);
                      }
                      methodAbi.name = sanitizedMethodName;
                  }
              });
          });
          return contractAbi;
      },
  };
  //# sourceMappingURL=abi_utils.js.map