trufflesuite/truffle

View on GitHub
packages/encoder/lib/encoders.ts

Summary

Maintainability
F
1 wk
Test Coverage
import debugModule from "debug";
const debug = debugModule("encoder:encoders");

import { FixedNumber as EthersFixedNumber } from "@ethersproject/bignumber";
import { getAddress } from "@ethersproject/address";
import * as Codec from "@truffle/codec";
import * as Abi from "@truffle/abi-utils";
import * as Types from "./types";
import Big from "big.js";
import Web3Utils from "web3-utils";
import { ProviderAdapter, Provider } from "./adapter";
import * as Utils from "./utils";
import {
  NoInternalInfoError,
  UnlinkedContractError,
  NoBytecodeError,
  InvalidAddressError,
  NoNetworkError,
  ContractNotFoundError,
  ContractNotDeployedError,
  NoCompilationsForSpawnerError,
  NoFunctionByThatNameError
} from "./errors";
import type { ContractObject as Artifact } from "@truffle/contract-schema/spec";
import { Shims } from "@truffle/compile-common";
//sorry for untyped imports!
const { default: ENS, getEnsAddress } = require("@ensdomains/ensjs");

const nonIntegerMessage = "Input value was not an integer";

interface ENSCache {
  [name: string]: string | null;
}

/**
 * The ProjectEncoder class.  Can wrap values; can also encode transactions and
 * resolve overloads if sufficient information is provided.  See below for a
 * method listing.
 * @category Encoder
 */
export class ProjectEncoder {
  private provider: Provider | null;
  private ens: any | null; //any should be ENS, sorry >_>
  private registryAddress: string | undefined = undefined;
  private ensCache: ENSCache = {};
  private allocations: Codec.Evm.AllocationInfo;
  private userDefinedTypes: Codec.Format.Types.TypesById;
  private networkId: number | null;
  private referenceDeclarations: {
    [compilationId: string]: Codec.Ast.AstNodes;
  };
  private compilations: Codec.Compilations.Compilation[] | undefined;
  private contractsAndContexts: Codec.AbiData.Allocate.ContractAndContexts[] =
    [];

  /**
   * @protected
   */
  public getAllocations(): Codec.Evm.AllocationInfo {
    return this.allocations;
  }

  /**
   * @protected
   */
  public getUserDefinedTypes(): Codec.Format.Types.TypesById {
    return this.userDefinedTypes;
  }

  /**
   * @protected
   */
  public getNetworkId(): number | null {
    return this.networkId;
  }

  /**
   * @protected
   */
  public getReferenceDeclarations(): {
    [compilationId: string]: Codec.Ast.AstNodes;
  } {
    return this.referenceDeclarations;
  }

  /**
   * @protected
   */
  constructor(info: Types.EncoderInfoInternal) {
    //first, set up the basic info that we need to run
    if (info.userDefinedTypes && info.allocations) {
      debug("internal route!");
      this.userDefinedTypes = info.userDefinedTypes;
      this.allocations = info.allocations;
    } else {
      if (!info.compilations) {
        throw new NoInternalInfoError();
      }
      //check for repeat compilation IDs
      const repeatIds = Codec.Compilations.Utils.findRepeatCompilationIds(
        info.compilations
      );
      if (repeatIds.size !== 0) {
        throw new Codec.RepeatCompilationIdError([...repeatIds]);
      }
      //since that's good, save it and continue
      this.compilations = info.compilations;
      ({
        definitions: this.referenceDeclarations,
        types: this.userDefinedTypes
      } = Codec.Compilations.Utils.collectUserDefinedTypesAndTaggedOutputs(
        info.compilations
      ));
      let allocationInfo: Codec.AbiData.Allocate.ContractAllocationInfo[];
      ({ allocationInfo, contractsAndContexts: this.contractsAndContexts } =
        Codec.AbiData.Allocate.Utils.collectAllocationInfo(info.compilations));

      this.allocations = {};
      //only doing the relevant allocations: abi & calldata
      this.allocations.abi = Codec.AbiData.Allocate.getAbiAllocations(
        this.userDefinedTypes
      );
      this.allocations.calldata = Codec.AbiData.Allocate.getCalldataAllocations(
        allocationInfo,
        this.referenceDeclarations,
        this.userDefinedTypes,
        this.allocations.abi
      );
    }

    this.provider = info.provider || null;
    debug("provider: %o", this.provider);
    if (info.registryAddress !== undefined) {
      this.registryAddress = info.registryAddress;
    }
    debug("registryAddress: %o", this.registryAddress);

    this.networkId = info.networkId || null;
  }

  /**
   * @protected
   */
  public async init(): Promise<void> {
    if (this.provider) {
      debug("provider given!");
      if (this.registryAddress !== undefined) {
        debug("using custom registry address: %o", this.registryAddress);
        this.ens = new ENS({
          provider: this.provider,
          ensAddress: this.registryAddress
        });
      } else {
        //if we weren't given a registry address, we use the default one,
        //but what is that?  We have to look it up.
        //NOTE: ENS is supposed to do this for us in the constructor,
        //but due to a bug it doesn't.
        debug("using default registry address");
        const networkId = await new ProviderAdapter(
          this.provider
        ).getNetworkId();
        const registryAddress: string | undefined = getEnsAddress(networkId);
        if (registryAddress) {
          this.ens = new ENS({
            provider: this.provider,
            ensAddress: registryAddress
          });
        } else {
          //there is no default registry on this chain
          this.ens = null;
        }
      }
    } else {
      debug("no provider given, ens off");
      this.ens = null;
    }
  }

  /**
   * **This method is asynchronous.**
   *
   * This is a restricted version of [[wrap]], which only handles elementary
   * types and values (those that can be used as mapping keys in Solidity);
   * it's present here for type convenience.  See the [[wrap]] and
   * [[ContractEncoder.encodeTransaction]] documentation
   * for further information.
   */
  public async wrapElementaryValue(
    dataType: Codec.Format.Types.ElementaryType,
    input: unknown
  ): Promise<Codec.Format.Values.ElementaryValue> {
    return <Codec.Format.Values.ElementaryValue>(
      await this.wrap(dataType, input)
    );
  }

  /**
   * **This method is asynchronous.**
   *
   * This method recognizes user input for a given data type and attempts
   * to interpret it as a value of that type.  It will throw a
   * [[TypeMismatchError]] if it cannot do this.
   *
   * For documentation of the accepted forms of input, see
   * [[ContractEncoder.encodeTransaction]].
   *
   * @param dataType The data type that the given value is to be interpreted
   *   as.
   * @param input The value to be interpreted.  This can take a number of
   *   forms depending on the data type, as documented above.
   * @return The interpreted value wrapped as a [[Format.Values.Value|Value]]
   *   object.
   */
  public async wrap(
    dataType: Codec.Format.Types.Type,
    input: unknown
  ): Promise<Codec.Format.Values.Value> {
    return await this.driveGenerator(
      Codec.Wrap.wrap(dataType, input, {
        userDefinedTypes: this.userDefinedTypes,
        loose: true
      })
    );
  }

  /**
   * @protected
   */
  public async wrapForTransaction(
    method: Codec.Wrap.Method,
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Wrap.Resolution> {
    debug("wrapForTransaction");
    return await this.driveGenerator(
      Codec.Wrap.wrapForMethod(method, inputs, {
        userDefinedTypes: this.userDefinedTypes,
        ...options
      })
    );
  }

  /**
   * @protected
   */
  public async resolveAndWrap(
    methods: Codec.Wrap.Method[],
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Wrap.Resolution> {
    return await this.driveGenerator(
      Codec.Wrap.resolveAndWrap(methods, inputs, {
        userDefinedTypes: this.userDefinedTypes,
        ...options
      })
    );
  }

  private async driveGenerator<T>(
    generator: Generator<Codec.WrapRequest, T, Codec.WrapResponse>
  ): Promise<T> {
    let response: Codec.WrapResponse;
    let next = generator.next();
    while (!next.done) {
      const request = next.value;
      debug("request: %O", request);
      // @ts-ignore: HACK HACK to make typedoc work
      // (the TS is fine with strict null checks on,
      // but typedoc has it turned off, so... :-/ )
      // please remove this ts-ignore once you turn on
      // strict null checks in typedoc
      response = await this.respond(request);
      debug("response: %O", response);
      next = generator.next(response);
    }
    debug("returning: %O", next.value);
    return next.value;
  }

  private async respond(
    request: Codec.WrapRequest
  ): Promise<Codec.WrapResponse> {
    switch (request.kind) {
      case "integer":
        return this.recognizeInteger(request.input);
      case "decimal":
        return this.recognizeDecimal(request.input);
      case "address":
        return await this.recognizeAddress(request.name);
    }
  }

  /**
   * @protected
   */
  public async encodeTxNoResolution(
    method: Codec.Wrap.Method,
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Options> {
    debug("encoding transaction");
    const resolution = await this.wrapForTransaction(method, inputs, options);
    const data = <Uint8Array>(
      Codec.AbiData.Encode.encodeTupleAbiWithSelector(
        resolution.arguments,
        Codec.Conversion.toBytes(resolution.method.selector),
        this.allocations.abi
      )
    );
    //note that the data option on resolution.options is ignored;
    //perhaps we can change this in the future, but for now we keep this
    //for compatibility
    let encoded = {
      ...resolution.options,
      data: Codec.Conversion.toHexString(data)
    };
    if (method.abi.type === "constructor") {
      delete encoded.to;
    }
    return encoded;
  }

  /**
   * @protected
   */
  public async encodeTransaction(
    methods: Codec.Wrap.Method[],
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Types.TxAndAbi> {
    debug("resolve & encode");
    const resolution = await this.resolveAndWrap(methods, inputs, options);
    const data = <Uint8Array>(
      Codec.AbiData.Encode.encodeTupleAbiWithSelector(
        resolution.arguments,
        Codec.Conversion.toBytes(resolution.method.selector),
        this.allocations.abi
      )
    );
    //note that the data option on resolution.options is ignored;
    //perhaps we can change this in the future, but for now we keep this
    //for compatibility
    return {
      tx: {
        ...resolution.options,
        data: Codec.Conversion.toHexString(data)
      },
      abi: <Abi.FunctionEntry>resolution.method.abi
    };
  }

  private recognizeInteger(input: unknown): Codec.IntegerWrapResponse {
    if (Utils.isBigNumber(input)) {
      if (input.isInteger()) {
        return {
          kind: "integer" as const,
          value: BigInt(input.toFixed())
        };
      } else {
        return {
          kind: "integer" as const,
          value: null,
          reason: nonIntegerMessage,
          partiallyRecognized: true
        };
      }
    } else if (Utils.isEthersBigNumber(input)) {
      const asHexString = input.toHexString();
      const asBigInt =
        asHexString[0] === "-"
          ? -BigInt(asHexString.slice(1))
          : BigInt(asHexString);
      return {
        kind: "integer" as const,
        value: asBigInt
      };
    } else if (EthersFixedNumber.isFixedNumber(input)) {
      //they had to make this one a pain...
      const asString = input.toString();
      //problem: the string might still have trailing ".0" on the end,
      //so let's run it through something that recognizes that (hack?)
      const asBig = new Big(asString);
      if (Codec.Conversion.countDecimalPlaces(asBig) === 0) {
        return {
          kind: "integer" as const,
          value: BigInt(asBig.toFixed())
        };
      } else {
        return {
          kind: "integer" as const,
          value: null,
          reason: nonIntegerMessage,
          partiallyRecognized: true
        };
      }
    } else {
      return {
        kind: "integer" as const,
        value: null
      };
    }
  }

  private recognizeDecimal(input: unknown): Codec.DecimalWrapResponse {
    if (Utils.isBigNumber(input)) {
      if (input.isFinite()) {
        return {
          kind: "decimal" as const,
          value: new Big(input.toFixed())
        };
      } else {
        return {
          kind: "decimal" as const,
          value: null,
          reason: "Input was not a finite value",
          partiallyRecognized: true
        };
      }
    } else if (Utils.isEthersBigNumber(input)) {
      //as before, this has to come after
      return {
        kind: "decimal" as const,
        value: new Big(input.toString())
      };
    } else if (EthersFixedNumber.isFixedNumber(input)) {
      return {
        kind: "decimal" as const,
        value: new Big(input.toString())
      };
    } else {
      return {
        kind: "decimal" as const,
        value: null
      };
    }
  }

  private async recognizeAddress(
    input: string
  ): Promise<Codec.AddressWrapResponse> {
    let address: string | null = null;
    try {
      address = getAddress(input); //maybe it's an ICAP address?
      return {
        kind: "address",
        address
      };
    } catch (error) {
      debug("address error: %O", error);
      if (!error) {
        throw error; //rethrow unepxected errors
      }
      switch (error.reason) {
        case "bad address checksum":
          //note: this won't be visible because we've already
          //checked this for ourselves
          return {
            kind: "address",
            address: null,
            reason: Codec.Wrap.Messages.checksumFailedMessage,
            partiallyRecognized: true
          };
        case "bad icap checksum":
          return {
            kind: "address",
            address: null,
            reason: "ICAP address had bad checksum",
            partiallyRecognized: true
          };
        case "invalid address":
          //in this case, try resolving it as an ENS name
          const address = await this.resolveEnsName(input);
          if (address !== null) {
            return {
              kind: "address",
              address
            };
          } else {
            return {
              kind: "address",
              address: null,
              reason: "Input was not recognizable as an address or ENS name"
            };
          }
        default:
          throw error; //rethrow unexpected errors
      }
    }
  }

  private async resolveEnsName(input: string): Promise<string | null> {
    if (this.ens === null) {
      return null;
    }
    if (input in this.ensCache) {
      return this.ensCache[input];
    }
    let address: string | null;
    try {
      address = await this.ens.name(input).getAddress();
    } catch {
      //Normally I'd rethrow unexpected errors, but given the context here
      //that seems like it might be a problem
      address = null;
    }
    if (address === Codec.Evm.Utils.ZERO_ADDRESS) {
      //ENS returns zero address to indicate "not found"
      address = null;
    }
    this.ensCache[input] = address;
    return address;
  }

  /**
   * **This method is asynchronous.**
   *
   * Constructs a contract encoder for a given contract in this project.
   * @param artifact The contract the encoder is for.  If you want to
   *   encode contract creation transactions, it must have all of
   *   its libraries linked.
   *
   *   Note: The contract must be one that the encoder knows about;
   *   otherwise you will have problems.
   */
  public async forArtifact(artifact: Artifact): Promise<ContractEncoder> {
    if (!this.compilations) {
      throw new NoCompilationsForSpawnerError();
    }
    let { compilation, contract } =
      Codec.Compilations.Utils.findCompilationAndContract(
        this.compilations,
        artifact
      );
    //to be *sure* we've got the right ABI, we trust the input over what was
    //found
    contract = {
      ...contract,
      abi: artifact.abi
    };
    return new ContractEncoder(this, compilation, contract, artifact);
  }

  /**
   * **This method is asynchronous.**
   *
   * Constructs a contract encoder for a given contract in this project.
   * @param contract The contract the encoder is for.  If you want to
   *   encode contract creation transactions, it must have all of
   *   its libraries linked.
   *
   *   Note: The contract must be one that the encoder knows about;
   *   otherwise you will have problems.
   */
  public async forContract(
    contract: Types.ContractConstructorObject
  ): Promise<ContractEncoder> {
    return await this.forArtifact(contract);
  }

  /**
   * **This method is asynchronous.**
   *
   * Constructs a contract instance encoder for a given contract instance.
   * @param artifact The artifact for the contract the encoder is for.  If you
   *   want to encode contract creation transactions, it must have all of its
   *   libraries linked.
   *
   *   Note: The contract must be one that the encoder knows about;
   *   otherwise you will have problems.
   * @param address The address of the contract instance.
   *   If omitted, but the project encoder has a provider or network ID,
   *   it will be autodetected.  If there is no provider or network ID,
   *   it must be included.
   *
   *   If an invalid address is provided, this method will throw an exception.
   */
  public async forInstance(
    artifact: Artifact,
    address?: string
  ): Promise<ContractInstanceEncoder> {
    const contractEncoder = await this.forArtifact(artifact);
    return await contractEncoder.forInstance(address);
  }

  /**
   * **This method is asynchronous.**
   *
   * Constructs a contract instance encoder for a given instance of a contract in this
   * project.  Unlike [[forInstance]], this method doesn't require an artifact; it
   * will automatically detect the class of the given contract.  If it's not in
   * the project, or the encoder can't identify it, you'll get an exception.
   * @param address The address of the contract instance to encoder for.
   *   If an invalid address is provided, this method will throw an exception.
   * @param block You can include this argument to specify that this should be
   *   based on the addresses content's at a specific block (if say the contract
   *   has since self-destructed).
   */
  public async forAddress(
    address: string,
    block: Codec.BlockSpecifier = "latest"
  ): Promise<ContractInstanceEncoder> {
    //code duplication warning: this method is basically copypasted
    //from the decoder!
    if (!this.compilations) {
      throw new NoCompilationsForSpawnerError();
    }
    if (this.provider === null) {
      throw new NoNetworkError();
    }
    if (!Web3Utils.isAddress(address)) {
      throw new InvalidAddressError(address);
    }
    address = Web3Utils.toChecksumAddress(address);
    const deployedBytecode = await new ProviderAdapter(this.provider).getCode(
      address,
      block
    );
    const contractAndContexts = this.contractsAndContexts.find(
      ({ deployedContext }) =>
        deployedContext &&
        Codec.Contexts.Utils.matchContext(deployedContext, deployedBytecode)
    );
    if (!contractAndContexts) {
      throw new ContractNotFoundError(
        undefined,
        undefined,
        deployedBytecode,
        address
      );
    }
    const { contract, compilationId } = contractAndContexts;
    const compilation = this.compilations.find(
      compilation => compilation.id === compilationId
    );
    if (!compilation) {
      throw new ContractNotFoundError(
        undefined,
        undefined,
        deployedBytecode,
        address
      );
    }
    //no artifact... hope you don't need to link any bytecode!
    const contractEncoder = new ContractEncoder(this, compilation, contract);
    return await contractEncoder.forInstance(address);
  }
}

/**
 * The ContractEncoder class.
 * Can encode transactions, resolve overloads, and wrap values.
 * See below for a method listing.
 * @category Encoder
 */
export class ContractEncoder {
  private projectEncoder: ProjectEncoder;
  private contract: Codec.Compilations.Contract;
  private abi: Abi.Abi;
  private artifact: Artifact | undefined;
  private constructorBinary: string | undefined;
  private constructorContextHash: string | undefined;
  private deployedContextHash: string | undefined;
  private noBytecodeAllocations: {
    [selector: string]: Codec.AbiData.Allocate.CalldataAndReturndataAllocation;
  };
  private contractNode: Codec.Ast.AstNode;
  private compilation: Codec.Compilations.Compilation;

  /**
   * Just used for testing, currently
   * @protected
   */
  public getProjectEncoder(): ProjectEncoder {
    return this.projectEncoder;
  }

  /**
   * @protected
   */
  constructor(
    projectEncoder: ProjectEncoder,
    compilation: Codec.Compilations.Compilation,
    contract: Codec.Compilations.Contract,
    artifact?: Artifact
  ) {
    this.projectEncoder = projectEncoder;
    this.contract = contract;
    this.abi = Abi.normalize(contract.abi);
    this.artifact = artifact;
    this.compilation = compilation;
    this.contractNode = Codec.Compilations.Utils.getContractNode(
      this.contract,
      this.compilation
    );
    //set up constructor binary w/resolved link references
    const networkId = this.projectEncoder.getNetworkId();
    const bytecode = Shims.NewToLegacy.forBytecode(contract.bytecode); //sorry, codec still uses legacy, to be changed in future
    const deployedBytecode = Shims.NewToLegacy.forBytecode(
      contract.deployedBytecode
    );
    //determine linked bytecode -- we'll determine it ourself rather than
    //using contract.binary
    const links =
      networkId !== null
        ? (
            ((artifact || { networks: {} }).networks || {})[networkId] || {
              links: {}
            }
          ).links || {}
        : {};
    this.constructorBinary = Utils.link(bytecode, links);
    //now, set up context hashes
    if (bytecode && bytecode !== "0x") {
      this.constructorContextHash = Codec.Conversion.toHexString(
        Codec.Evm.Utils.keccak256({
          type: "string",
          value: bytecode //has link references unresolved
        })
      );
    }
    if (deployedBytecode && deployedBytecode !== "0x") {
      this.deployedContextHash = Codec.Conversion.toHexString(
        Codec.Evm.Utils.keccak256({
          type: "string",
          value: deployedBytecode //has link references unresolved
        })
      );
    } else {
      //if there's no bytecode, allocate input data manually
      const compiler = this.compilation.compiler || this.contract.compiler;
      this.noBytecodeAllocations = Object.values(
        Codec.AbiData.Allocate.getCalldataAllocations(
          [
            {
              abi: this.abi,
              compilationId: this.compilation.id,
              //@ts-ignore sorry this is what happens when you mix strictNullChecks on with off
              compiler,
              contractNode: this.contractNode,
              deployedContext: Codec.Contexts.Utils.makeContext(
                {
                  ...this.contract,
                  deployedBytecode: "0x" //only time this should ever appear in a context!
                  //note that we immediately discard it!
                },
                this.contractNode,
                this.compilation
              )
            }
          ],
          this.projectEncoder.getReferenceDeclarations(),
          this.projectEncoder.getUserDefinedTypes(),
          this.projectEncoder.getAllocations().abi
        ).functionAllocations
      )[0];
    }
  }

  /**
   * See [[ProjectEncoder.wrapElementaryValue]].
   */
  public async wrapElementaryValue(
    dataType: Codec.Format.Types.ElementaryType,
    input: unknown
  ): Promise<Codec.Format.Values.ElementaryValue> {
    return await this.projectEncoder.wrapElementaryValue(dataType, input);
  }

  /**
   * See [[ProjectEncoder.wrap]].
   */
  public async wrap(
    dataType: Codec.Format.Types.Type,
    input: unknown
  ): Promise<Codec.Format.Values.Value> {
    return await this.projectEncoder.wrap(dataType, input);
  }

  /**
   * **This method is asynchronous.**
   *
   * This method recognizes user input for a transaction.  It will throw
   * a [[TypeMismatchError]] if it cannot do this.  This method requires
   * that the precise function be specified; it does not perofrm overload
   * resolution.  See [[encodeTransaction]] for documentation of the accepted
   * forms of input.
   *
   * If the `allowOptions` flag is set in the `options` argument, the input may
   * contain an additional transaction options argument after the other
   * arguments.
   *
   * Note that use of the encoder for transactions to be sent to libraries is
   * presently not supported and may have unreliable results.  Limited support
   * for this is planned for future versions.
   *
   * @param method ABI entry for the transaction being prepared.  Must be one
   *   associated with this contract.  Can be for either a function or a
   *   constructor.
   * @param inputs An array of the inputs to the transaction.  May include a
   *   transaction options argument on the end if the `allowOptions` flag is
   *   set.
   * @param options Contains options to control the operation of this method.
   * @return The interpretation of the input, as a
   *   [[Resolution]] object.
   */
  public async wrapForTransaction(
    abi: Abi.FunctionEntry | Abi.ConstructorEntry,
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Wrap.Resolution> {
    const method = this.getMethod(abi);
    return await this.projectEncoder.wrapForTransaction(
      method,
      inputs,
      options
    );
  }

  /**
   * **This method is asynchronous.**
   *
   * This method attempts to perform overload resolution given user input
   * to one of several possible methods.  If the given input matches more than
   * one of these methods, it will attempt to select the best match.  See
   * [[encodeTransaction]] for documentation of the acccepted input forms.
   *
   * If it is not possible for the given input to match any of the given methods,
   * either a [[TypeMismatchError]] or a
   * [[NoOverloadsMatchedError]] will be
   * thrown.  If more than one overload matches but none can be considered the
   * unique best, you will get a
   * [[NoUniqueBestOverloadError]].
   * If due to inputting a nonexistent function name there are no overloads to
   * check, you will get a [[NoFunctionByThatNameError]].
   *
   * If the `allowOptions` flag is set in the `options` argument, the input may
   * contain an additional transaction options argument after the other
   * arguments.
   *
   * Note that use of the encoder for transactions to be sent to libraries is
   * presently not supported and may have unreliable results.  Limited support
   * for this is planned for future versions.
   *
   * **Overload resolution system**
   *
   * If it is necessary to perform overload resolution by type rather than
   * simply by length, the encoder will select among the overloads that
   * could work the one it considers to be the best match.  To be the best
   * match, it must be a best match for each argument.  An overload is
   * a best match for a given argument if the type it would assign that
   * argument is highest-priority among all types it could assign that
   * argument (selected from overloads that match overall).
   *
   * Note that when doing this the match checker will be somewhat stricter than
   * usual; inputs for structs/tuples will not be allowed to contain extra
   * keys, numeric input (including odd-length hex strings)
   * will not be accepted for dynamic-length bytestrings,
   * and if a value is given as a [[Format.Values.Value|Value]], it
   * will only match its specific type, rather than being allowed to match
   * other types as usual (unless it is itself wrapped in a type/value pair).
   *
   * The overall order of priority of types is as follows:
   * 1. transaction options
   * 2. arrays
   * 3. structs and tuples
   * 4. addresses and contracts
   * 5. bytestrings (`bytesN` and `bytes`)
   * 6. external function pointers
   * 7. numeric types
   * 8. `enum`s
   * 9. `string` [is #10 with `strictBooleans`]
   * 10. `bool` [is #9 with `strictBooleans`]
   *
   * (Note that if the encoder does not know that a certain argument is
   * supposed to be an enum, it will of course just be treated as the
   * underlying numeric type.)
   *
   * (If the `strictBooleans` option is passed, the priority order of `string`
   * and `bool` is swapped.)
   *
   * Moreover, within each category there is a priority ordering (which is
   * not always total).  Specifically:
   * * For arrays, if `S` has priority over `T`, then `S[]` has priority
   *   over `T[]`, and `S[n]` has priority over `T[n]`.  Moreover, `S[n]`
   *   has priority over `S[]` and so also over `T[]`.
   * * Structs and tuples mostly act the same as the overall arguments list; for
   *   one such type `S` to have priority over another type `T`, each
   *   member type of `S` must have priority over the corresponding member type
   *   of `T` (correspondence being determined by the order of the members).
   *   However, if the two types `S` and `T` also have exactly the same
   *   component names (and each has all of its components named), then
   *   this will also be checked with correspondence by name instead of
   *   position, and `S` will only be considered more specific than `T` if
   *   both checks pass.
   * * `bytesN` has priority over `bytesM` if `N<=M`, and has priority over
   *   `bytes`
   * * A numeric type `S` has priority over a numeric type `T` if the values
   *   representable by `S` are a subset of those representable by `T`.
   *
   * If you are not getting the overload you want, you can use explicit
   * type-value input as discussed in the documentation for
   * [[encodeTransaction]], or you can skip overload resolution and explicitly
   * select an overload by other means.  For enums you may also specify the
   * enum type as documented in [[encodeTransaction]].
   *
   * @param abisOrNameOrSig The ABI entries for the overloads, or the name or
   *   full signature of the function.  Note that if you are inputting ABI
   *   entries, they must be for functions, not constructors.  The entries must
   *   be ones associated with this contract.
   * @param inputs An array of the inputs to the transaction.  May include a
   *   transaction options argument on the end if the `allowOptions` flag is
   *   set.
   * @param options Contains options to control the operation of this method.
   * @return The interpretation of the input and the resolved method, as a
   *   [[Resolution]] object.
   */
  public async resolveAndWrap(
    abisOrNameOrSig: Abi.FunctionEntry[] | string,
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Wrap.Resolution> {
    const abis = this.getAbis(abisOrNameOrSig);
    const methods = abis.map(abi => this.getMethod(abi));
    //note we can't just write abis.map(this.getMethod)
    //because this would be undefined inside of it... I could
    //write abis.map(this.getMethod.bind(this)), but I find the
    //arrow way to be more readable
    return await this.projectEncoder.resolveAndWrap(methods, inputs, options);
  }

  /**
   * **This method is asynchronous.**
   *
   * This method is similar to [[encodeTransaction]], except that it does not
   * perform overload resolution; it takes a single ABI entry, rather than a
   * list of them or a function name.  Note that unlike [[encodeTransaction]],
   * it can also encode contract creation transactions.
   *
   * Because this method does not perform overload resolution, it only returns
   * the resulting transaction options (including the encoded `data`), and does
   * not bother returning the ABI used (as this was user-supplied.)
   *
   * If the `allowOptions` flag is set in the `options` argument, the input may
   * contain an additional transaction options argument after the other
   * arguments.  Any non-`data` options not specified in such a transaction
   * options argument will be simply omitted; it you want some options to have
   * defaults, it is up to the you to set these options as appropriate
   * afterwards.
   *
   * If the transaction options parameter has a `data` option, this option will
   * be recognized but ignored.  Similarly, when encoding a contract creation,
   * the `to` option will also be ignored.
   *
   * See [[encodeTransaction]] for documentation of most of the inputs.
   *
   * @param abi The ABI entry for the transaction to encode for.  Note it must
   *   be one for this contract.  May be for either a function or a constructor.
   * @return The resulting transaction options, including the encoded `data`.
   */
  public async encodeTxNoResolution(
    abi: Abi.FunctionEntry | Abi.ConstructorEntry,
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Options> {
    const method = this.getMethod(abi);
    return await this.projectEncoder.encodeTxNoResolution(
      method,
      inputs,
      options
    );
  }

  /**
   * **This method is asynchronous.**
   *
   * This method recognizes user input for a given set of contract methods,
   * attempts to interpret it as valid input for one of them, and then
   * encodes the result.  (That is to say, it performs overload resolution,
   * then encodes.)  Note that this method cannot be used to encode contract
   * creations; use [[encodeTxNoResolution]] for that.
   *
   * If this method cannot match the user input to any of the possible
   * overloads, it will throw a [[TypeMismatchError]] or a
   * [[NoOverloadsMatchedError]].  If more than one overload matches but none
   * can be considered the unique best, you will get a
   * [[NoUniqueBestOverloadError]].  If due to inputting a nonexistent function
   * name there are no overloads to check, you will get a
   * [[NoFunctionByThatNameError]].  See below for a full list of the accepted
   * forms of input, and see [[resolveAndWrap]] for full documentation of the
   * overload resolution system.
   *
   * Be aware that overload resolution may not always be fully reliable; if you
   * want to be absolutely certain that you get the right overload, you can use
   * [[encodeTxNoResolution]], which does not perform overload resolution, but
   * requires you to specify exactly which overload you mean.  However, you can
   * also adjust your input to this function to get the overload you want; see
   * below about `{ type: ..., value: ... }` input and enum string input for
   * details.
   *
   * If the `allowOptions` flag is set in the `options` argument, the input may
   * contain an additional transaction options argument after the other
   * arguments.  Any non-`data` options not specified in such a transaction
   * options argument will be simply omitted; it you want some options to have
   * defaults, it is up to the you to set these options as appropriate
   * afterwards.  Also, if the transaction options parameter has a `data`
   * option, this option will be recognized but ignored.
   *
   * Use of the encoder for transactions to be sent to libraries is
   * presently not supported and may have unreliable results.  Limited support
   * for this is planned for future versions.
   *
   * **Accepted forms of input**
   *
   * The `input` argument may come in a number of forms, depending on the
   * target data type.  A list of the specific inputs accepted for each type is
   * below.  However first we must note a few generic forms that inputs are
   * accepted in.
   *
   * Inputs may be given as an object of the form `{ type: ..., value: ... }`
   * (additional fields not allowed), where `type` is a string describing the
   * type, and `value` is anything that would be accepted for that type (other
   * than another type/value object).  This form of input is not very useful
   * with *this* method, but it is useful when performing overload resolution
   * (see [[resolveAndWrap]]) to restrict the overloads that will be selected
   * from.  Note that for arrays, `type` should simply be `"array"`; for
   * structs and tuples, `"struct"` or `"tuple"`; for addresses and contracts,
   * `"address"` or `"contract"`; for external functions, `"function"`; for
   * transaction options, `"options"`; and for enums, it can be either `"enum"`
   * (or the underlying uint type).  For other Solidity types, it should be the
   * name of the type; note that `"uint"`, `"int"`, `"fixed"`, `"ufixed"`, and
   * `"byte"` are accepted.  Vyper's `"decimal"` type is also accepted.
   * Also, user-defined value types use exactly the same
   * `type` field as the underlying type; this input format does not distinguish
   * between them and the underlying type.
   *
   * Note that input in the form of a [[Format.Values.Value|Value]] is
   * accepted, so long as the type is appropriate, but error results are
   * typically not accepted (exceptions are discussed below).
   *
   * Now then, the list of accepted inputs by type, excluding the above:
   *
   * **Strings**: The input may be given as string (or `String`); note that
   * strings with invalid UTF-16 will not be accepted.  It may also be given as a
   * `Uint8Array` (or anything that mimics one; see below about bytestrings), which
   * will be treated as UTF-8; note that invalid UTF-8 is allowed in this format.
   * Strings may also be given as a [[Format.Values.StringValue|StringValue]].
   *
   * **Integer types**: Input for integer types may take a variety of forms.
   * The input may be a `number` (or `Number`); note that if so it must be a
   * safe integer.  For larger integers, you must use other forms of input.
   * For instance, the input may be a `BigInt`.  The input may also be one
   * of several recognized big number classes:
   *   * [`BN`](https://github.com/indutny/bn.js)
   *   * [`Big`](https://github.com/MikeMcl/Big.js)
   *   * MikeMcl's [`BigNumber`](https://github.com/MikeMcl/bignumber.js)
   *   * Ethers's [`BigNumber` or `FixedNumber`](https://www.npmjs.com/package/@ethersproject/bignumber)
   * Of course, any numeric input, no matter the format, must be integral.
   * Input may also take the form of a numeric string (or `String`).
   * The string may be decimal, but it may also be hexadecimal with `"0x"`
   * prefix, octal with `"0o"` prefix, or binary with `"0xb"` prefix.
   * You can also use a negated hexadecimal, octal, or binary string to
   * represent a negative number.  Whitespace before or after the number is OK,
   * and you may use underscores to separate groups of digits (in any base).
   * For decimal strings, scientific notation (e.g. `1.1e4`) is also accepted.
   * It is also possible to affix one of the units `"wei"`, `"gwei"`,
   * `"shannon"`, `"finney"`, `"szabo"`, or `"ether"` (these are case-insensitive)
   * onto a decimal numeric string (you may include space inbetween the
   * quantity and the unit) to act as a multiplier (where here the
   * assumption is that 1 wei means the number 1).  You may also use a
   * unit by itself, with no specified quantity, to mean 1 of that unit.
   * (E.g., an input of `"wei"` will be interpreted as 1.)  Note that it's OK
   * if the quantity before the unit is not itself an integer, so long as the
   * overall resulting quantity is an integer; e.g., "1.1 gwei" is legal integer
   * input.  In addition to giving the input in any of these obviously numeric
   * forms, the input may also be given a a `Uint8Array` or anything that
   * mimics one (see above about bytestrings); in this case the input will
   * be interpreted as the big-endian byte representation of an unsigned
   * integer (or in other words, it will be interpreted as base 256).
   * Negative numbers cannot be represented in this way.
   * Finally, the input may be given as a
   * [[Format.Values.UintValue|UintValue]],
   * [[Format.Values.IntValue|IntValue]],
   * [[Format.Values.UfixedValue|UfixedValue]],
   * [[Format.Values.FixedValue|FixedValue]],
   * [[Format.Values.UserDefinedTypeValue|UserDefinedTypeValue]] on top of one of these,
   * or [[Format.Values.EnumValue|EnumValue]]; the type is not required to
   * match unless strict checking is on (see [[resolveAndWrap]]), in which case
   * the type must match exactly.  In addition, the input may also be a
   * [[Format.Errors.EnumErrorResult|EnumErrorResult]] so long as
   * the error is a
   * [[Format.Errors.EnumOutOfRangeError|EnumOutOfRangeError]];
   * other types of error results are not accepted.
   *
   * **Enums**: Enums accept all the same forms of input as integer types.
   * However, if the encoder is aware that a particular argument or field is in
   * fact an enum and not just an integer, it accepts one additional form of
   * input; the input may be a string (or `String`) containing the name of the
   * enumerated option.  So, for instance, given the following Solidity code:
   * ```solidity
   * contract MyContract {
   *   enum Ternary {
   *     No, Yes, Maybe
   *   }
   * }
   * ```
   * then `"Yes"` would be a valid input for an enum of type
   * `MyContract.Ternary`.  Moreover, `"Ternary.Yes"` and
   * `"MyContract.Ternary.Yes"` would also work; these latter forms will only
   * match enum types with the appropriate name and optionally defining
   * contract, so you can use these to restrict matching for overload
   * resolution, much like type/value input.  Note these forms do not require
   * the enum to be defined inside of a contract; those defined outside of
   * contracts are supported too, so long as the encoder was initialized to
   * know about them.
   *
   * **Bytestrings**: Bytestrings can be given in several forms.  Note that for
   * all forms of input, if the specified type is `bytesN`, it is OK if the
   * length of the input is shorter than N bytes; it will automatically be
   * right-padded with zero bytes in this case.  (The exception is if the input
   * is a [[Format.Values.BytesValue|BytesValue]] and strict checking is
   * on; see [[resolveAndWrap]].)  Bytestrings may be given as `"0x"`-prefixed
   * even-length hex strings (a `String` may be used in place of a string);
   * underscores may be used to separate groups of hex digits.
   * Bytestrings may also be given as a `Uint8Array`, or anything resembling a
   * `Uint8Array` -- any object with a `length` field which is a `number`, and
   * which has fields from `0` to `length-1` all `number`s from 0 to 255, will
   * be accepted.  Input may also be given as a
   * [[Format.Values.BytesValue|BytesValue]] or a
   * [[Format.Values.UserDefinedTypeValue|UserDefinedTypeValue]]
   * on top of one; the specific type does not
   * have to match unless strict checking is on.  In addition, a bytestring may be
   * given as an object with just the fields `text` and `encoding`; in this
   * case, `text` should be a string (it must not have invalid UTF-16) and
   * `encoding` an encoding to encode it as.  The only supported encoding
   * currently is `"utf8"`.  Finally, for compatibility with ethers, when
   * strict checking is off (see [[resolveAndWrap]]), a
   * bytestring of dynamic length (`bytes`) may have its input given numerically.
   * The valid formats for this are the same as for integer types, except that
   * wrapped numeric values are not accepted, numeric strings may not use
   * scientific notation or units, and the number may not be negative.  For
   * compatibility reasons, if the number zero is given as input in this way,
   * it will be treated as a bytestring consisting of a single zero byte, rather
   * than the empty bytestring.  Warning: an odd-length hex string will be
   * treated as numeric input!  (Effectively, it will be padded on the left
   * with a zero hex digit.)
   *
   * **Addresses and contracts**: Input may be given as a hex string
   * representing 20 bytes, with capitalization according to the Ethereum
   * address checksum.  The `"0x"` prefix is optional.  If the hex string
   * is all lowercase or all uppercase, however, then the checksum check will
   * be skipped, and the input accepted regardless.  Input may also be given
   * as an ICAP address; again, the checksum must be correct.  Finally, if ENS
   * resolution has been configured, input may be given as an ENS name.
   * All of these may also be given as `String`s instead of strings.
   * Input may also be given as an object with an `address` field, although the
   * contents of that address field must be a `"0x"`-prefixed hex string (not
   * `String`), and not any other address format.  Input may also be given
   * as a [[Format.Values.AddressValue|AddressValue]],
   * [[Format.Values.UserDefinedTypeValue|UserDefinedTypeValue]] on top of such, or
   * [[Format.Values.ContractValue|ContractValue]]; the specific type
   * does not matter.
   *
   * **Booleans**: Unless the `strictBooleans` option is passed, almost any
   * input is accepted (as long as it's not type/value input for a different
   * type), but how it is interpreted depends on the input.  A boolean will be
   * interpreted in the obvious way, and a `Boolean` will be unwrapped.  A
   * string will be considered true unless it is falsy or is equal (ignoring
   * case) to the string `"false"`; however, if `strictBooleans` is passed, then
   * only strings that are (ignoring case) equal to `"true"` or `"false"` will
   * be accepted.  A `String` will be
   * considered true if and only if the underlying string is.  A number will be
   * considered true so long as it is truthy, and a `Number` will be considered
   * true if and only if the underlying number is.  A
   * [[Format.Values.BoolValue|BoolValue]], or
   * [[Format.Values.UserDefinedTypeValue|UserDefinedTypeValue]] on top of such,
   * will be considered true so
   * long as it represents a true value.  Moreover, two types of
   * [[Format.Errors.BoolErrorResult|BoolErrorResult]] also count as
   * true: Those where the error is a
   * [[Format.Errors.BoolOutOfRangeError|BoolOutOfRangeError]] and
   * those where the error is a
   * [[Format.Errors.BoolPaddingError|BoolPaddingError]].  This also applies to
   * a [[Format.Errors.UserDefinedTypeValue|UserDefinedTypeErrors]] on top of one
   * of these.  All other
   * error results, and all [[Format.Values.Value|Values]] that are not
   * [[Format.Values.BoolValue|BoolValues]] or a
   * [[Format.Values.UserDefinedTypeValue|UserDefinedTypeValue]] on top of one,
   * will be rejected.  All other inputs will be considered true so long as
   * they are truthy, unless `strictBooleans` is passed, in which case they will
   * be rejected.
   *
   * **Decimal fixed-point types**: Input for fixed-point decimal types is
   * similar to input for integer types.  The differences are as follows:
   *   * Units are not accepted in numeric strings (or `String`s).
   *   * Hexadecimal, octal, and binary strings (or `String`s) are not
   *     accepted.
   *   * `Uint8Array`s, or objects that mimic them, are not accepted.
   *   * Numeric values do not have to be integral.
   * Note that if the input is a `number` (or `Number`) or MikeMcl
   * [BigNumber](https://github.com/MikeMcl/bignumber.js), it must be a finite
   * value.  Also, the number of decimal places in the input may not exceed the
   * number of decimal places allowed in the type.  Finally, just as integer
   * types do not allow `number`s (or `Number`s) that are unsafe integers as
   * input, decimal types will not accept a `number` (or `Number`) as input if
   * that `number` is outside the safe range for that type, i.e., it is large
   * enough that there may be loss of precision.  (This means that `1` is not
   * valid input for a `fixed128x18`!)  Using other, safer, forms of input is
   * encouraged.
   *
   * **User-defined value types**: These take exactly the same input as the
   * underlying type.
   *
   * **Arrays**: The input may be an array, or it may be a
   * [[Format.Values.ArrayValue|ArrayValue]].  In the latter case,
   * whether it is static-length or dynamic-length does not need to match
   * (unless strict checking is on, see [[resolveAndWrap]]).  If the `allowJson`
   * option is passed, the array may also be a JSON string.  Note that any allowed
   * format is allowed for the individual elements.
   *
   * **Structs and tuples**: The input can be given either as an array or as an
   * object; if the `allowJson` option is passed, it may also be given as a
   * JSON string for one of these (any format is allowed for the individual
   * elements).  If given as an array, the elements should be the members of
   * the struct/tuple, in order.  If given as an object, it should be keyed by
   * the struct or tuple's field names; if any of the elements of the tuple are
   * unnamed, then input cannot be given as an object.  Additional keys are
   * also allowed unless strict checking is on.  Input may also be given as a
   * [[Format.Values.StructValue|StructValue]] or
   * [[Format.Values.TupleValue|TupleValue]]; the specific type does not
   * matter.
   *
   * **External function pointers**: These may be given as an object with fields
   * `address` and `selector` (additional fields are allowed); the `address`
   * field may be anything that would be recognized as an address (see above),
   * and the `selector` field may be anything that would be recgonized as a
   * `bytes4` (see above).  Alternatively, this may be given as a bytestring
   * (even length `"0x"`-prefixed hex string or `String`) of 24 bytes,
   * specifying the address followed by the selector; in this case, the address
   * does not need to be checksummed.  Finally, input may of course also be
   * given as a
   * [[Format.Values.FunctionExternalValue|FunctionExternalValue]];
   * its more specific type does not matter.
   *
   * * Transaction options: These are given as an object with fields for the
   * desired options (you can leave options out or have them be `undefined` and
   * they'll be ignored).  Note that, in order to maintain compatibility with
   * older versions of Truffle, additional keys are accepted, but there must be
   * at least one key that belongs in a transaction options object.  Note that
   * if any field exists, and is not `undefined`, but the value of that field
   * cannot be interpreted as input of the appropriate type, the input will be
   * rejected.  Otherwise, inputs for each field can be anything that the
   * encoder will understand for this field.  Accepted fields are:
   *   * `gas`, `gasPrice`, `value`, `nonce`: These take integer input
   *     (see above).
   *   * `from`, `to`: These take address input (see above).
   *   * `data`: This takes `bytes` input (see above).
   *   * `overwrite`: This takes boolean input (see above).
   *   * `type`: This takes integer input, which must be in the range from
   *     0 to `0xbf`.
   *   * `accessList`: This takes input as if for an array of type `AccessListForAddress[]`,
   *      if `AccessListForAddress` were a struct with two fields, `address` and `storageKeys`,
   *      with `address` being an `address` and `storageKeys` being of type `uint256[]`.
   *      Yes, this means storage keys may be given numerically; it also means that if a
   *      storage key is given as a hex string representing less than 32 bytes, it will be
   *      padded on the left, rather than on the right.
   *   * `privateFor`: This one is a special case, and requires a specific
   *     form of input.  Input must be an array of base64-encoded
   *     bytestrings (as strings or `String`s), each with a decoded length of
   *     32 bytes.
   * In addition, input may also be given as a
   * [[Format.Values.OptionsValue|OptionsValue]].
   *
   * @param abisOrNameOrSig The ABI entries for the overloads, or the name
   *   or full signature of the function.  Note that if you are inputting ABI
   *   entries, they must be for functions, not constructors.  The entries must
   *   be ones associated with this contract.
   * @param input The value to be interpreted.  This can take a number of
   *   forms depending on the data type, as documented above.
   * @return An object with a `tx` field, holding the transaction options,
   *   including the encoded `data`, and an `abi` field, indicating which
   *   ABI entry was used for encoding.
   */
  public async encodeTransaction(
    abisOrNameOrSig: Abi.FunctionEntry[] | string,
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Types.TxAndAbi> {
    const abis = this.getAbis(abisOrNameOrSig);
    const methods = abis.map(abi => this.getMethod(abi));
    //note we can't just write abis.map(this.getMethod)
    //because this would be undefined inside of it... I could
    //write abis.map(this.getMethod.bind(this)), but I find the
    //arrow way to be more readable
    return await this.projectEncoder.encodeTransaction(
      methods,
      inputs,
      options
    );
  }

  /**
   * **This method is asynchronous.**
   *
   * This method is similar to [[encodeTransaction]], except that instead of
   * encoding a function transaction, it encodes a creation transaction.
   *
   * Because this method does not perform overload resolution, it only returns
   * the resulting transaction options (including the encoded `data`), and does
   * not bother returning the ABI used (as this was user-supplied.)
   *
   * If the `allowOptions` flag is set in the `options` argument, the input may
   * contain an additional transaction options argument after the other
   * arguments.  Any non-`data` options not specified in such a transaction
   * options argument will be simply omitted; it you want some options to have
   * defaults, it is up to the you to set these options as appropriate
   * afterwards.
   *
   * If the transaction options parameter has a `data` or a `to` option,
   * these option will be recognized but ignored.
   *
   * See [[encodeTransaction]] for documentation of the inputs.
   */
  public async encodeCreation(
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Options> {
    const method = this.getConstructorMethod();
    return await this.projectEncoder.encodeTxNoResolution(
      method,
      inputs,
      options
    );
  }

  /**
   * **This method is asynchronous.**
   *
   * Constructs a contract instance encoder for a given instance of the
   * contract this encoder is for.
   * @param address The address of the contract instance.
   *   If omitted, it will be autodetected.
   *   If an invalid address is provided, this method will throw an exception.
   */
  public async forInstance(address?: string): Promise<ContractInstanceEncoder> {
    if (address === undefined) {
      const networkId = this.projectEncoder.getNetworkId();
      if (networkId === null) {
        throw new NoNetworkError();
      }
      address = ((this.artifact || { networks: {} }).networks || {})[networkId]
        .address;
      if (address === undefined) {
        throw new ContractNotDeployedError(
          this.contract.contractName,
          networkId
        );
      }
    }
    return new ContractInstanceEncoder(this, address);
  }

  private getAbis(
    abisOrNameOrSig: Abi.FunctionEntry[] | string
  ): Abi.FunctionEntry[] {
    const abis: Abi.FunctionEntry[] =
      typeof abisOrNameOrSig === "string"
        ? this.abi.filter(
            (abi): abi is Abi.FunctionEntry =>
              abi.type === "function" &&
              (abi.name === abisOrNameOrSig ||
                Codec.AbiData.Utils.abiSignature(abi) === abisOrNameOrSig)
          )
        : abisOrNameOrSig;
    if (typeof abisOrNameOrSig === "string" && abis.length === 0) {
      //we don't throw this if the input was an empty list of ABIs
      //rather than a name... the user knew what they were doing if they
      //did that :P
      throw new NoFunctionByThatNameError(
        abisOrNameOrSig,
        this.contract.contractName
      );
    }
    return abis;
  }

  private getMethod(
    abi: Abi.FunctionEntry | Abi.ConstructorEntry
  ): Codec.Wrap.Method {
    abi = <Abi.FunctionEntry | Abi.ConstructorEntry>Abi.normalizeEntry(abi); //just to be absolutely certain!
    debug("got allocations");
    switch (abi.type) {
      case "constructor":
        return this.getConstructorMethod(abi);
      case "function":
        const allocations = this.projectEncoder.getAllocations();
        const selector: string = Codec.AbiData.Utils.abiSelector(abi);
        const allocation: Codec.AbiData.Allocate.CalldataAllocation = this
          .deployedContextHash
          ? //@ts-ignore: This is set up earlier
            allocations.calldata.functionAllocations[this.deployedContextHash][
              selector
            ].input
          : this.noBytecodeAllocations[selector].input;
        const inputs = allocation.arguments.map(
          input => ({ type: input.type, name: input.name || undefined }) //convert "" to undefined
        );
        return {
          name: abi.name,
          selector,
          inputs,
          abi
        };
    }
  }

  //if you already know the ABI, you can pass it in for convenience.
  //if you don't, we'll find it for you.
  private getConstructorMethod(abi?: Abi.ConstructorEntry): Codec.Wrap.Method {
    if (!abi) {
      abi = this.getConstructorAbi();
    }
    const allocations = this.projectEncoder.getAllocations();
    debug("constructor binary: %s", this.constructorBinary);
    //first check that we have constructor binary, and that it's all linked
    if (!this.constructorBinary || this.constructorBinary === "0x") {
      throw new NoBytecodeError(this.contract.contractName);
    } else if (!this.constructorBinary.match(/^0x([0-9a-fA-F]{2})+$/)) {
      throw new UnlinkedContractError(
        this.contract.contractName,
        this.artifact ? this.artifact.bytecode : undefined
      );
    }
    //otherwise, we're good to go!
    const allocation =
      //@ts-ignore: We set this up and checked this earlier
      allocations.calldata.constructorAllocations[
        <string>this.constructorContextHash
      ].input;
    const inputs = allocation.arguments.map(
      input => ({ type: input.type, name: input.name || undefined }) //convert "" to undefined
    );
    return {
      selector: this.constructorBinary,
      inputs,
      abi
    };
  }

  private getConstructorAbi(): Abi.ConstructorEntry {
    return (
      this.abi.find(
        (abi: Abi.Entry): abi is Abi.ConstructorEntry =>
          abi.type === "constructor"
      ) || Codec.AbiData.Utils.DEFAULT_CONSTRUCTOR_ABI
    );
  }
}

/**
 * The ContractInstanceEncoder class.
 * Can encode transactions, resolve overloads, and wrap values.
 * Differs from the [[ContractEncoder]] only in that it carries
 * a `to` address for non-constructor transactions.
 * See below for a method listing.
 * @category Encoder
 */
export class ContractInstanceEncoder {
  private contractEncoder: ContractEncoder;
  private toAddress: string;

  /**
   * @protected
   */
  constructor(contractEncoder: ContractEncoder, toAddress: string) {
    this.contractEncoder = contractEncoder;
    if (!Web3Utils.isAddress(toAddress)) {
      throw new InvalidAddressError(toAddress);
    }
    this.toAddress = Web3Utils.toChecksumAddress(toAddress);
  }

  /**
   * See [[ProjectEncoder.wrapElementaryValue]].
   */
  public async wrapElementaryValue(
    dataType: Codec.Format.Types.ElementaryType,
    input: unknown
  ): Promise<Codec.Format.Values.ElementaryValue> {
    return await this.contractEncoder.wrapElementaryValue(dataType, input);
  }

  /**
   * See [[ProjectEncoder.wrap]].
   */
  public async wrap(
    dataType: Codec.Format.Types.Type,
    input: unknown
  ): Promise<Codec.Format.Values.Value> {
    return await this.contractEncoder.wrap(dataType, input);
  }

  /**
   * **This method is asynchronous.**
   *
   * This method functions identically to [[ContractEncoder.wrapForTransaction]],
   * except that, when preparing a function transaction, the `to` option is
   * automatically set to this contract instance's address.  If an explicit
   * `to` address is passed as a transaction option, it will be recognized
   * but ignored.
   */
  public async wrapForTransaction(
    abi: Abi.FunctionEntry | Abi.ConstructorEntry,
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Wrap.Resolution> {
    const resolution = await this.contractEncoder.wrapForTransaction(
      abi,
      inputs,
      options
    );
    if (!resolution.options.to && abi.type === "function") {
      resolution.options.to = this.toAddress;
    }
    return resolution;
  }

  /**
   * **This method is asynchronous.**
   *
   * This method functions identically to [[ContractEncoder.resolveAndWrap]],
   * except that the `to` option is automatically set to this contract
   * instance's address.  If an explicit `to` address is passed as a
   * transaction option, it will be recognized but ignored.
   */
  public async resolveAndWrap(
    abis: Abi.FunctionEntry[] | string,
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Wrap.Resolution> {
    const resolution = await this.contractEncoder.resolveAndWrap(
      abis,
      inputs,
      options
    );
    resolution.options.to = this.toAddress;
    return resolution;
  }

  /**
   * **This method is asynchronous.**
   *
   * This method functions identically to [[ContractEncoder.encodeTxNoResolution]],
   * except that, when preparing a function transaction, the `to` option is
   * automatically set to this contract instance's address.  If an explicit
   * `to` address is passed as a transaction option, it will be recognized
   * but ignored.
   */
  public async encodeTxNoResolution(
    abi: Abi.FunctionEntry | Abi.ConstructorEntry,
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Options> {
    const encoded = await this.contractEncoder.encodeTxNoResolution(
      abi,
      inputs,
      options
    );
    //note that the to options is simply overridden
    //perhaps we can change this in the future, but for now we keep this
    //for compatibility
    if (abi.type === "function") {
      encoded.to = this.toAddress;
    } else if (abi.type === "constructor") {
      delete encoded.to;
    }
    return encoded;
  }

  /**
   * **This method is asynchronous.**
   *
   * This method functions identically to [[ContractEncoder.encodeTransaction]],
   * except that the `to` option is automatically set to this contract
   * instance's address.  If an explicit `to` address is passed as a
   * transaction option, it will be recognized but ignored.
   */
  public async encodeTransaction(
    abisOrNameOrSig: Abi.FunctionEntry[] | string,
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Types.TxAndAbi> {
    const encoded = await this.contractEncoder.encodeTransaction(
      abisOrNameOrSig,
      inputs,
      options
    );
    //note that the to options is simply overridden
    //perhaps we can change this in the future, but for now we keep this
    //for compatibility
    encoded.tx.to = this.toAddress;
    return encoded;
  }

  /**
   * **This method is asynchronous.**
   *
   * This method functions identically to [[ContractEncoder.encodeCreation]].
   * The particular contract instance is ignored, only its class is used.
   */
  public async encodeCreation(
    inputs: unknown[],
    options: Types.ResolveOptions = {}
  ): Promise<Codec.Options> {
    return await this.contractEncoder.encodeCreation(inputs, options);
  }
}