trufflesuite/truffle

View on GitHub
packages/codec/lib/basic/decode/index.ts

Summary

Maintainability
A
0 mins
Test Coverage
import debugModule from "debug";
const debug = debugModule("codec:basic:decode");

import read from "@truffle/codec/read";
import * as Conversion from "@truffle/codec/conversion";
import * as Format from "@truffle/codec/format";
import * as Contexts from "@truffle/codec/contexts";
import type * as Pointer from "@truffle/codec/pointer";
import type {
  DecoderRequest,
  DecoderOptions,
  EnsPrimaryNameRequest
} from "@truffle/codec/types";
import type { PaddingMode, PaddingType } from "@truffle/codec/common";
import * as Evm from "@truffle/codec/evm";
import { handleDecodingError, StopDecodingError } from "@truffle/codec/errors";
import { byteLength } from "@truffle/codec/basic/allocate";
import { decodeString } from "@truffle/codec/bytes/decode";

export function* decodeBasic(
  dataType: Format.Types.Type,
  pointer: Pointer.DataPointer,
  info: Evm.EvmInfo,
  options: DecoderOptions = {}
): Generator<DecoderRequest, Format.Values.Result, Uint8Array | null> {
  const { state } = info;
  const { strictAbiMode: strict } = options; //if this is undefined it'll still be falsy so it's OK
  const paddingMode: PaddingMode = options.paddingMode || "default";

  let bytes: Uint8Array;
  let rawBytes: Uint8Array;
  try {
    bytes = yield* read(pointer, state);
  } catch (error) {
    debug("segfault, pointer %o, state: %O", pointer, state);
    return handleDecodingError(dataType, error, strict);
  }
  rawBytes = bytes;

  debug("type %O", dataType);
  debug("pointer %o", pointer);

  switch (dataType.typeClass) {
    case "userDefinedValueType": {
      const fullType = <Format.Types.UserDefinedValueTypeType>(
        Format.Types.fullType(dataType, info.userDefinedTypes)
      );
      if (!fullType.underlyingType) {
        const error = {
          kind: "UserDefinedTypeNotFoundError" as const,
          type: fullType
        };
        if (strict || options.allowRetry) {
          throw new StopDecodingError(error, true);
          //note that we allow a retry if we couldn't locate the underlying type!
        }
        return {
          type: fullType,
          kind: "error" as const,
          error
        };
      }
      const underlyingResult = yield* decodeBasic(
        fullType.underlyingType,
        pointer,
        info,
        options
      );
      switch (
        underlyingResult.kind //yes this switch is a little unnecessary :P
      ) {
        case "value":
          //wrap the value and return
          return <Format.Values.UserDefinedValueTypeValue>{
            //no idea why need coercion here
            type: fullType,
            kind: "value" as const,
            value: underlyingResult,
            interpretations: {}
          };
        case "error":
          //wrap the error and return an error result!
          //this is inconsistent with how we handle other container types
          //(structs, arrays, mappings), where having an error in one element
          //does not cause an error in the whole thing, but to do that here
          //would cause problems for the type system :-/
          //so we'll just be inconsistent
          return <Format.Errors.UserDefinedValueTypeErrorResult>{
            //TS is being bad again :-/
            type: fullType,
            kind: "error" as const,
            error: {
              kind: "WrappedError",
              error: underlyingResult
            }
          };
      }
      break; //to satisfy TS :P
    }
    case "bool": {
      if (!checkPadding(bytes, dataType, paddingMode)) {
        let error = {
          kind: "BoolPaddingError" as const,
          paddingType: getPaddingType(dataType, paddingMode),
          raw: Conversion.toHexString(bytes)
        };
        if (strict) {
          throw new StopDecodingError(error);
        }
        return {
          type: dataType,
          kind: "error" as const,
          error
        };
      }
      bytes = removePadding(bytes, dataType, paddingMode);
      //note: the use of the BN is a little silly here,
      //but, kind of stuck with it for now
      const numeric = Conversion.toBN(bytes);
      if (numeric.eqn(0)) {
        return {
          type: dataType,
          kind: "value" as const,
          value: { asBoolean: false },
          interpretations: {}
        };
      } else if (numeric.eqn(1)) {
        return {
          type: dataType,
          kind: "value" as const,
          value: { asBoolean: true },
          interpretations: {}
        };
      } else {
        let error = {
          kind: "BoolOutOfRangeError" as const,
          rawAsBN: numeric
        };
        if (strict) {
          throw new StopDecodingError(error);
        }
        return {
          type: dataType,
          kind: "error" as const,
          error
        };
      }
    }

    case "uint":
      //first, check padding (if needed)
      if (!checkPadding(bytes, dataType, paddingMode)) {
        let error = {
          kind: "UintPaddingError" as const,
          paddingType: getPaddingType(dataType, paddingMode),
          raw: Conversion.toHexString(bytes)
        };
        if (strict) {
          throw new StopDecodingError(error);
        }
        return {
          type: dataType,
          kind: "error" as const,
          error
        };
      }
      //now, truncate to appropriate length
      bytes = removePadding(bytes, dataType, paddingMode);
      return {
        type: dataType,
        kind: "value" as const,
        value: {
          asBN: Conversion.toBN(bytes),
          rawAsBN: Conversion.toBN(rawBytes)
        },
        interpretations: {}
      };
    case "int":
      //first, check padding (if needed)
      if (!checkPadding(bytes, dataType, paddingMode)) {
        let error = {
          kind: "IntPaddingError" as const,
          paddingType: getPaddingType(dataType, paddingMode),
          raw: Conversion.toHexString(bytes)
        };
        if (strict) {
          throw new StopDecodingError(error);
        }
        return {
          type: dataType,
          kind: "error" as const,
          error
        };
      }
      //now, truncate to appropriate length (keeping the bytes on the right)
      bytes = removePadding(bytes, dataType, paddingMode);
      return {
        type: dataType,
        kind: "value" as const,
        value: {
          asBN: Conversion.toSignedBN(bytes),
          rawAsBN: Conversion.toSignedBN(rawBytes)
        },
        interpretations: {}
      };

    case "address": {
      if (!checkPadding(bytes, dataType, paddingMode)) {
        let error = {
          kind: "AddressPaddingError" as const,
          paddingType: getPaddingType(dataType, paddingMode),
          raw: Conversion.toHexString(bytes)
        };
        if (strict) {
          throw new StopDecodingError(error);
        }
        return {
          type: dataType,
          kind: "error" as const,
          error
        };
      }
      bytes = removePadding(bytes, dataType, paddingMode);
      const address = Evm.Utils.toAddress(bytes);
      let decoded: Format.Values.AddressValue = {
        type: dataType,
        kind: "value" as const,
        value: {
          asAddress: address,
          rawAsHex: Conversion.toHexString(rawBytes)
        },
        interpretations: {}
      };
      //now: attach interpretations
      const ensName = yield* reverseEnsResolve(address);
      if (ensName !== null) {
        decoded.interpretations.ensName = ensName;
      }
      //yes, this makes the contract/address distinction a little silly
      const contractValueInfo = yield* decodeContract(bytes, info);
      if (contractValueInfo.kind === "known") {
        decoded.interpretations.contractClass = contractValueInfo.class;
      }
      return decoded;
    }

    case "contract": {
      if (!checkPadding(bytes, dataType, paddingMode)) {
        let error = {
          kind: "ContractPaddingError" as const,
          paddingType: getPaddingType(dataType, paddingMode),
          raw: Conversion.toHexString(bytes)
        };
        if (strict) {
          throw new StopDecodingError(error);
        }
        return {
          type: dataType,
          kind: "error" as const,
          error
        };
      }
      bytes = removePadding(bytes, dataType, paddingMode);
      const fullType = <Format.Types.ContractType>(
        Format.Types.fullType(dataType, info.userDefinedTypes)
      );
      const contractValueInfo = yield* decodeContract(bytes, info);
      let decoded = {
        type: fullType,
        kind: "value" as const,
        value: contractValueInfo,
        interpretations: {}
      };
      //now: attach interpretations
      const ensName = yield* reverseEnsResolve(contractValueInfo.address);
      if (ensName !== null) {
        decoded.interpretations = { ensName };
      }
      return decoded;
    }

    case "bytes":
      //NOTE: we assume this is a *static* bytestring,
      //because this is decodeBasic! dynamic ones should
      //go to decodeBytes!
      let coercedDataType = <Format.Types.BytesTypeStatic>dataType;

      //first, check padding (if needed)
      if (!checkPadding(bytes, dataType, paddingMode)) {
        let error = {
          kind: "BytesPaddingError" as const,
          paddingType: getPaddingType(dataType, paddingMode),
          raw: Conversion.toHexString(bytes)
        };
        if (strict) {
          throw new StopDecodingError(error);
        }
        return {
          type: coercedDataType,
          kind: "error" as const,
          error
        };
      }
      //now, truncate to appropriate length
      bytes = removePadding(bytes, dataType, paddingMode);
      return {
        type: coercedDataType,
        kind: "value" as const,
        value: {
          asHex: Conversion.toHexString(bytes),
          rawAsHex: Conversion.toHexString(rawBytes)
        },
        interpretations: {}
      };

    case "function":
      switch (dataType.visibility) {
        case "external":
          if (!checkPadding(bytes, dataType, paddingMode)) {
            const error = {
              kind: "FunctionExternalNonStackPaddingError" as const,
              paddingType: getPaddingType(dataType, paddingMode),
              raw: Conversion.toHexString(bytes)
            };
            if (strict) {
              throw new StopDecodingError(error);
            }
            return {
              type: dataType,
              kind: "error" as const,
              error
            };
          }
          bytes = removePadding(bytes, dataType, paddingMode);
          const address = bytes.slice(0, Evm.Utils.ADDRESS_SIZE);
          const selector = bytes.slice(
            Evm.Utils.ADDRESS_SIZE,
            Evm.Utils.ADDRESS_SIZE + Evm.Utils.SELECTOR_SIZE
          );
          const valueInfo = yield* decodeExternalFunction(
            address,
            selector,
            info
          );
          let decoded = {
            type: dataType,
            kind: "value" as const,
            value: valueInfo,
            interpretations: {}
          };
          //now: attach interpretations
          const contractEnsName = yield* reverseEnsResolve(
            valueInfo.contract.address
          );
          if (contractEnsName !== null) {
            decoded.interpretations = { contractEnsName };
          }
          return decoded;
        case "internal":
          //note: we used to error if we hit this point with strict === true,
          //since internal function pointers don't go in the ABI, and strict
          //mode is intended for ABI decoding.  however, there are times when
          //we want to use strict mode to decode immutables, and immutables can
          //include internal function pointers.  so now we allow this.  yes,
          //this is a bit of an abuse of strict mode, which was after all meant
          //for ABI decoding, but oh well.
          if (!checkPadding(bytes, dataType, paddingMode)) {
            const error = {
              kind: "FunctionInternalPaddingError" as const,
              paddingType: getPaddingType(dataType, paddingMode),
              raw: Conversion.toHexString(bytes)
            };
            if (strict) {
              throw new StopDecodingError(error);
            }
            return {
              type: dataType,
              kind: "error" as const,
              error
            };
          }
          bytes = removePadding(bytes, dataType, paddingMode);
          return decodeInternalFunction(dataType, bytes, info, strict);
      }
      break; //to satisfy TypeScript

    case "enum": {
      let numeric = Conversion.toBN(bytes);
      const fullType = <Format.Types.EnumType>(
        Format.Types.fullType(dataType, info.userDefinedTypes)
      );
      if (!fullType.options) {
        let error = {
          kind: "EnumNotFoundDecodingError" as const,
          type: fullType,
          rawAsBN: numeric
        };
        if (strict || options.allowRetry) {
          throw new StopDecodingError(error, true);
          //note that we allow a retry if we couldn't locate the enum type!
        }
        return {
          type: fullType,
          kind: "error" as const,
          error
        };
      }
      //note: I'm doing the padding checks a little more manually on this one
      //so that we can have the right type of error
      const numOptions = fullType.options.length;
      const numBytes = Math.ceil(Math.log2(numOptions) / 8);
      const paddingType = getPaddingType(dataType, paddingMode);
      if (!checkPaddingDirect(bytes, numBytes, paddingType)) {
        let error = {
          kind: "EnumPaddingError" as const,
          type: fullType,
          paddingType,
          raw: Conversion.toHexString(bytes)
        };
        if (strict) {
          throw new StopDecodingError(error);
        }
        return {
          type: dataType,
          kind: "error" as const,
          error
        };
      }
      bytes = removePaddingDirect(bytes, numBytes, paddingType);
      numeric = Conversion.toBN(bytes); //alter numeric!
      if (numeric.ltn(numOptions)) {
        const name = fullType.options[numeric.toNumber()];
        //NOTE: despite the use of toNumber(), I'm NOT catching exceptions here and returning an
        //error value like elsewhere; I'm just letting this one fail.  Why?  Because if we have
        //an enum with that many options in the first place, we have bigger problems!
        return {
          type: fullType,
          kind: "value" as const,
          value: {
            name,
            numericAsBN: numeric
          },
          interpretations: {}
        };
      } else {
        let error = {
          kind: "EnumOutOfRangeError" as const,
          type: fullType,
          rawAsBN: numeric
        };
        if (strict) {
          //note:
          //if the enum is merely out of range rather than out of the ABI range,
          //we do NOT throw an error here!  instead we simply return an error value,
          //which we normally avoid doing in strict mode.  (the error will be caught
          //later at the re-encoding step instead.)  why?  because we might be running
          //in ABI mode, so we may need to abify this "value" rather than just throwing
          //it out.
          throw new StopDecodingError(error);
          //note that we do NOT allow a retry here!
          //if we *can* find the enum type but the value is out of range,
          //we *know* that it is invalid!
        }
        return {
          type: fullType,
          kind: "error" as const,
          error
        };
      }
    }

    case "fixed": {
      //first, check padding (if needed)
      if (!checkPadding(bytes, dataType, paddingMode)) {
        let error = {
          kind: "FixedPaddingError" as const,
          paddingType: getPaddingType(dataType, paddingMode),
          raw: Conversion.toHexString(bytes)
        };
        if (strict) {
          throw new StopDecodingError(error);
        }
        return {
          type: dataType,
          kind: "error" as const,
          error
        };
      }
      //now, truncate to appropriate length (keeping the bytes on the right)
      bytes = removePadding(bytes, dataType, paddingMode);
      let asBN = Conversion.toSignedBN(bytes);
      let rawAsBN = Conversion.toSignedBN(rawBytes);
      let asBig = Conversion.shiftBigDown(
        Conversion.toBig(asBN),
        dataType.places
      );
      let rawAsBig = Conversion.shiftBigDown(
        Conversion.toBig(rawAsBN),
        dataType.places
      );
      return {
        type: dataType,
        kind: "value" as const,
        value: {
          asBig,
          rawAsBig
        },
        interpretations: {}
      };
    }
    case "ufixed": {
      //first, check padding (if needed)
      if (!checkPadding(bytes, dataType, paddingMode)) {
        let error = {
          kind: "UfixedPaddingError" as const,
          paddingType: getPaddingType(dataType, paddingMode),
          raw: Conversion.toHexString(bytes)
        };
        if (strict) {
          throw new StopDecodingError(error);
        }
        return {
          type: dataType,
          kind: "error" as const,
          error
        };
      }
      //now, truncate to appropriate length (keeping the bytes on the right)
      bytes = removePadding(bytes, dataType, paddingMode);
      let asBN = Conversion.toBN(bytes);
      let rawAsBN = Conversion.toBN(rawBytes);
      let asBig = Conversion.shiftBigDown(
        Conversion.toBig(asBN),
        dataType.places
      );
      let rawAsBig = Conversion.shiftBigDown(
        Conversion.toBig(rawAsBN),
        dataType.places
      );
      return {
        type: dataType,
        kind: "value" as const,
        value: {
          asBig,
          rawAsBig
        },
        interpretations: {}
      };
    }
  }
}

//NOTE that this function returns a ContractValueInfo, not a ContractResult
export function* decodeContract(
  addressBytes: Uint8Array,
  info: Evm.EvmInfo
): Generator<
  DecoderRequest,
  Format.Values.ContractValueInfo,
  Uint8Array | null
> {
  return (yield* decodeContractAndContext(addressBytes, info)).contractInfo;
}

function* decodeContractAndContext(
  addressBytes: Uint8Array,
  info: Evm.EvmInfo
): Generator<DecoderRequest, ContractInfoAndContext, Uint8Array | null> {
  let address = Evm.Utils.toAddress(addressBytes);
  let rawAddress = Conversion.toHexString(addressBytes);
  let codeBytes: Uint8Array = yield {
    type: "code" as const,
    address
  };
  let code = Conversion.toHexString(codeBytes);
  let context = Contexts.Utils.findContext(info.contexts, code);
  if (context !== null) {
    return {
      context,
      contractInfo: {
        kind: "known" as const,
        address,
        rawAddress,
        class: Contexts.Import.contextToType(context)
      }
    };
  } else {
    return {
      context,
      contractInfo: {
        kind: "unknown" as const,
        address,
        rawAddress
      }
    };
  }
}

//note: address can have extra zeroes on the left like elsewhere, but selector should be exactly 4 bytes
//NOTE this again returns a FunctionExternalValueInfo, not a FunctionExternalResult
export function* decodeExternalFunction(
  addressBytes: Uint8Array,
  selectorBytes: Uint8Array,
  info: Evm.EvmInfo
): Generator<
  DecoderRequest,
  Format.Values.FunctionExternalValueInfo,
  Uint8Array | null
> {
  let { contractInfo: contract, context } = yield* decodeContractAndContext(
    addressBytes,
    info
  );
  let selector = Conversion.toHexString(selectorBytes);
  if (contract.kind === "unknown") {
    return {
      kind: "unknown" as const,
      contract,
      selector
    };
  }
  let abiEntry = context.abi !== undefined ? context.abi[selector] : undefined;
  if (abiEntry === undefined) {
    return {
      kind: "invalid" as const,
      contract,
      selector
    };
  }
  return {
    kind: "known" as const,
    contract,
    selector,
    abi: abiEntry
  };
}

//this one works a bit differently -- in order to handle errors, it *does* return a FunctionInternalResult
function decodeInternalFunction(
  dataType: Format.Types.FunctionInternalType,
  bytes: Uint8Array,
  info: Evm.EvmInfo,
  strict: boolean
): Format.Values.FunctionInternalResult {
  const rawInfoFormat = info.internalFunctionsTableKind || ("index" as const);
  //we'll default to "index" if it's not specified, not because that's
  //a reasonable default (we want to avoid ever hitting a default here,
  //debugger/decoder will set their own defaults as appropriate), but because
  //index doesn't attempt to, like, parse things, so it's better for reporting
  //errors if we have no clue what's going on
  let raw: Format.Values.FunctionInternalRawInfo;
  switch (rawInfoFormat) {
    case "pcpair":
      const deployedPcBytes = bytes.slice(-Evm.Utils.PC_SIZE);
      const constructorPcBytes = bytes.slice(
        -Evm.Utils.PC_SIZE * 2,
        -Evm.Utils.PC_SIZE
      );
      const deployedPc: number = Conversion.toBN(deployedPcBytes).toNumber();
      const constructorPc: number =
        Conversion.toBN(constructorPcBytes).toNumber();
      raw = {
        kind: "pcpair",
        deployedProgramCounter: deployedPc,
        constructorProgramCounter: constructorPc
      };
      break;
    case "index":
      const index: number = Conversion.toBN(bytes).toNumber();
      raw = {
        kind: "index",
        functionIndex: index
      };
      break;
  }
  const context: Format.Types.ContractType = Contexts.Import.contextToType(
    info.currentContext
  );
  //before anything else: do we even have an internal functions table?
  //if not, we'll just return the info we have without really attemting to decode
  if (!info.internalFunctionsTable || !info.internalFunctionsTableKind) {
    //note we end up here if the table kind wasn't set; the "index" default above
    //is *only* for error handling
    return {
      type: dataType,
      kind: "value" as const,
      value: {
        kind: "unknown" as const,
        context,
        rawInformation: raw
      },
      interpretations: {}
    };
  }
  //this switch block handles exceptional cases that are only relevant in the pcpair case
  if (raw.kind === "pcpair") {
    //defining these for convenience
    const {
      deployedProgramCounter: deployedPc,
      constructorProgramCounter: constructorPc
    } = raw;
    //also before we continue: is the PC zero? if so let's just return that
    if (deployedPc === 0 && constructorPc === 0) {
      return {
        type: dataType,
        kind: "value" as const,
        value: {
          kind: "exception" as const,
          context,
          rawInformation: raw
        },
        interpretations: {}
      };
    }
    //another check: is only the deployed PC zero?
    if (deployedPc === 0 && constructorPc !== 0) {
      const error = {
        kind: "MalformedInternalFunctionError" as const,
        context,
        rawInformation: raw
      };
      if (strict) {
        throw new StopDecodingError(error);
      }
      return {
        type: dataType,
        kind: "error" as const,
        error
      };
    }
    //one last pre-check: is this a deployed-format pointer in a constructor?
    if (info.currentContext.isConstructor && constructorPc === 0) {
      const error = {
        kind: "DeployedFunctionInConstructorError" as const,
        context,
        rawInformation: raw
      };
      if (strict) {
        throw new StopDecodingError(error);
      }
      return {
        type: dataType,
        kind: "error" as const,
        error
      };
    }
  }
  //if we didn't hit any of those exceptional cases, we now attempt to look
  //up the function in the table
  let functionEntry: Evm.InternalFunction | undefined;
  switch (raw.kind) {
    case "pcpair":
      const pc = info.currentContext.isConstructor
        ? raw.constructorProgramCounter
        : raw.deployedProgramCounter;
      functionEntry = info.internalFunctionsTable[pc];
      break;
    case "index":
      functionEntry = info.internalFunctionsTable[raw.functionIndex];
      break;
  }
  if (!functionEntry) {
    //If we didn't find an entry, this is an error
    const error = {
      kind: "NoSuchInternalFunctionError" as const,
      context,
      rawInformation: raw
    };
    if (strict) {
      throw new StopDecodingError(error);
    }
    return {
      type: dataType,
      kind: "error" as const,
      error
    };
  }
  //finally, the rest of this handles the case where we did find an entry,
  //and doesn't need to switch on the raw info type anymore :)
  if (functionEntry.isDesignatedInvalid) {
    return {
      type: dataType,
      kind: "value" as const,
      value: {
        kind: "exception" as const,
        context,
        rawInformation: raw
      },
      interpretations: {}
    };
  }
  const name = functionEntry.name;
  const mutability = functionEntry.mutability;
  const definedIn = Evm.Import.functionTableEntryToType(functionEntry); //may be null
  const id = Evm.Import.makeInternalFunctionId(functionEntry);
  return {
    type: dataType,
    kind: "value" as const,
    value: {
      kind: "function" as const,
      context,
      rawInformation: raw,
      name,
      id,
      definedIn,
      mutability
    },
    interpretations: {}
  };
}

function checkPadding(
  bytes: Uint8Array,
  dataType: Format.Types.Type,
  paddingMode: PaddingMode,
  userDefinedTypes?: Format.Types.TypesById
): boolean {
  const length = byteLength(dataType, userDefinedTypes);
  const paddingType = getPaddingType(dataType, paddingMode);
  if (paddingMode === "permissive") {
    switch (dataType.typeClass) {
      case "bool":
      case "enum":
      case "function":
        //these three types are checked even in permissive mode
        return checkPaddingDirect(bytes, length, paddingType);
      default:
        return true;
    }
  } else {
    return checkPaddingDirect(bytes, length, paddingType);
  }
}

function removePadding(
  bytes: Uint8Array,
  dataType: Format.Types.Type,
  paddingMode: PaddingMode,
  userDefinedTypes?: Format.Types.TypesById
): Uint8Array {
  const length = byteLength(dataType, userDefinedTypes);
  const paddingType = getPaddingType(dataType, paddingMode);
  return removePaddingDirect(bytes, length, paddingType);
}

function removePaddingDirect(
  bytes: Uint8Array,
  length: number,
  paddingType: PaddingType
) {
  switch (paddingType) {
    case "right":
      return bytes.slice(0, length);
    default:
      return bytes.slice(-length);
  }
}

function checkPaddingDirect(
  bytes: Uint8Array,
  length: number,
  paddingType: PaddingType
) {
  switch (paddingType) {
    case "left":
      return checkPaddingLeft(bytes, length);
    case "right":
      return checkPaddingRight(bytes, length);
    case "signed":
      return checkPaddingSigned(bytes, length);
    case "signedOrLeft":
      return (
        checkPaddingSigned(bytes, length) || checkPaddingLeft(bytes, length)
      );
  }
}

function getPaddingType(
  dataType: Format.Types.Type,
  paddingMode: PaddingMode
): PaddingType {
  switch (paddingMode) {
    case "right":
      return "right";
    case "default":
    case "permissive":
      return defaultPaddingType(dataType);
    case "zero": {
      const defaultType = defaultPaddingType(dataType);
      return defaultType === "signed" ? "left" : defaultType;
    }
    case "defaultOrZero": {
      const defaultType = defaultPaddingType(dataType);
      return defaultType === "signed" ? "signedOrLeft" : defaultType;
    }
  }
}

function defaultPaddingType(dataType: Format.Types.Type): PaddingType {
  switch (dataType.typeClass) {
    case "bytes":
      return "right";
    case "int":
    case "fixed":
      return "signed";
    case "function":
      if (dataType.visibility === "external") {
        return "right";
      }
    //otherwise, fall through to default
    default:
      return "left";
  }
}

function checkPaddingRight(bytes: Uint8Array, length: number): boolean {
  let padding = bytes.slice(length); //cut off the first length bytes
  return padding.every(paddingByte => paddingByte === 0);
}

//exporting this one for use in stack.ts
export function checkPaddingLeft(bytes: Uint8Array, length: number): boolean {
  let padding = bytes.slice(0, -length); //cut off the last length bytes
  return padding.every(paddingByte => paddingByte === 0);
}

function checkPaddingSigned(bytes: Uint8Array, length: number): boolean {
  let padding = bytes.slice(0, -length); //padding is all but the last length bytes
  let value = bytes.slice(-length); //meanwhile the actual value is those last length bytes
  let signByte = value[0] & 0x80 ? 0xff : 0x00;
  return padding.every(paddingByte => paddingByte === signByte);
}

function* reverseEnsResolve(
  address: string
): Generator<
  EnsPrimaryNameRequest,
  Format.Values.StringValueInfo | null,
  Uint8Array | null
> {
  const nameAsBytes = yield { type: "ens-primary-name" as const, address };
  return nameAsBytes !== null ? decodeString(nameAsBytes) : null;
}

//the following types are intended for internal use only
/**
 * @hidden
 */
export interface ContractInfoAndContext {
  contractInfo: Format.Values.ContractValueInfo;
  context?: Contexts.Context;
}