trufflesuite/truffle

View on GitHub
packages/codec/lib/wrap/function.ts

Summary

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

import * as Format from "@truffle/codec/format";
import { wrapWithCases } from "./dispatch";
import { TypeMismatchError } from "./errors";
import type { WrapRequest, WrapResponse } from "../types";
import type { Case, WrapOptions } from "./types";
import * as Messages from "./messages";
import * as Utils from "./utils";
import * as EvmUtils from "@truffle/codec/evm/utils";
import Web3Utils from "web3-utils";

import { addressCases } from "./address";
import { bytesCases } from "./bytes";

const functionExternalCasesBasic: Case<
  Format.Types.FunctionExternalType,
  Format.Values.FunctionExternalValue,
  WrapRequest
>[] = [
  functionFromFunctionExternalInput,
  functionFromHexString,
  functionFromCodecFunctionExternalValue,
  functionFailureCase
];

export const functionExternalCases: Case<
  Format.Types.FunctionExternalType,
  Format.Values.FunctionExternalValue,
  WrapRequest
>[] = [functionFromTypeValueInput, ...functionExternalCasesBasic];

function* functionFromFunctionExternalInput(
  dataType: Format.Types.FunctionExternalType,
  input: unknown,
  wrapOptions: WrapOptions
): Generator<WrapRequest, Format.Values.FunctionExternalValue, WrapResponse> {
  if (!Utils.isFunctionExternalInput(input)) {
    throw new TypeMismatchError(
      dataType,
      input,
      wrapOptions.name,
      1,
      "Input was not an object with address & selector"
    );
  }
  const wrappedAddress = (yield* wrapWithCases(
    { typeClass: "address", kind: "general" },
    input.address,
    {
      ...wrapOptions,
      name: `${wrapOptions.name}.address`,
      specificityFloor: 5
    },
    addressCases
  )) as Format.Values.AddressValue;
  const address = wrappedAddress.value.asAddress;
  const wrappedSelector = yield* wrapWithCases(
    { typeClass: "bytes", kind: "static", length: 4 },
    input.selector,
    {
      ...wrapOptions,
      name: `${wrapOptions.name}.selector`,
      specificityFloor: 5
    },
    bytesCases
  );
  const selector = wrappedSelector.value.asHex;
  //note validation & normalization have already been performed
  return {
    type: dataType,
    kind: "value" as const,
    value: {
      kind: "unknown" as const,
      contract: {
        kind: "unknown" as const,
        address
      },
      selector
    },
    interpretations: {}
  };
}

function* functionFromCodecFunctionExternalValue(
  dataType: Format.Types.FunctionExternalType,
  input: unknown,
  wrapOptions: WrapOptions
): Generator<never, Format.Values.FunctionExternalValue, WrapResponse> {
  if (!Utils.isWrappedResult(input)) {
    throw new TypeMismatchError(
      dataType,
      input,
      wrapOptions.name,
      1,
      "Input was not a wrapped result"
    );
  }
  if (
    input.type.typeClass !== "function" ||
    input.type.visibility !== "external"
  ) {
    throw new TypeMismatchError(
      dataType,
      input,
      wrapOptions.name,
      5,
      Messages.wrappedTypeMessage(input.type)
    );
  }
  if (input.kind !== "value") {
    throw new TypeMismatchError(
      dataType,
      input,
      wrapOptions.name,
      5,
      Messages.errorResultMessage
    );
  }
  const coercedInput = <Format.Values.FunctionExternalValue>input;
  const address = coercedInput.value.contract.address;
  const selector = coercedInput.value.selector;
  //we can skip validation & normalization here
  return {
    type: dataType,
    kind: "value" as const,
    value: {
      kind: "unknown" as const,
      contract: {
        kind: "unknown" as const,
        address
      },
      selector
    },
    interpretations: {}
  };
}

function* functionFromHexString(
  dataType: Format.Types.FunctionExternalType,
  input: unknown,
  wrapOptions: WrapOptions
): Generator<never, Format.Values.FunctionExternalValue, WrapResponse> {
  if (typeof input !== "string") {
    throw new TypeMismatchError(
      dataType,
      input,
      wrapOptions.name,
      1,
      "Input was not a string"
    );
  }
  if (!Utils.isByteString(input)) {
    throw new TypeMismatchError(
      dataType,
      input,
      wrapOptions.name,
      5,
      "Input was a string, but not a valid even-length hex string"
    );
  }
  if (
    input.length !==
    2 + 2 * (EvmUtils.ADDRESS_SIZE + EvmUtils.SELECTOR_SIZE)
  ) {
    throw new TypeMismatchError(
      dataType,
      input,
      wrapOptions.name,
      5,
      Messages.wrongLengthMessage(
        "external function was given as a string but",
        EvmUtils.ADDRESS_SIZE + EvmUtils.SELECTOR_SIZE,
        (input.length - 2) / 2
      )
    );
  }
  let address: string = input
    .slice(0, EvmUtils.ADDRESS_SIZE * 2 + 2)
    .toLowerCase();
  const selector =
    "0x" + input.slice(EvmUtils.ADDRESS_SIZE * 2 + 2).toLowerCase();
  //address & selector must now have the correct length, and we are deliberately *not*
  //checking the checksum on address in this case.  So, the only thing remaining
  //to do is to normalize address.
  address = Web3Utils.toChecksumAddress(address);
  //...and return
  return {
    type: dataType,
    kind: "value" as const,
    value: {
      kind: "unknown" as const,
      contract: {
        kind: "unknown" as const,
        address
      },
      selector
    },
    interpretations: {}
  };
}

function* functionFromTypeValueInput(
  dataType: Format.Types.FunctionExternalType,
  input: unknown,
  wrapOptions: WrapOptions
): Generator<WrapRequest, Format.Values.FunctionExternalValue, WrapResponse> {
  if (!Utils.isTypeValueInput(input)) {
    throw new TypeMismatchError(
      dataType,
      input,
      wrapOptions.name,
      1,
      "Input was not a type/value pair"
    );
  }
  if (input.type !== "function") {
    throw new TypeMismatchError(
      dataType,
      input,
      wrapOptions.name,
      5,
      Messages.specifiedTypeMessage(input.type)
    );
  }
  //extract value & try again, with loose option turned on
  return yield* wrapWithCases(
    dataType,
    input.value,
    { ...wrapOptions, loose: true },
    functionExternalCasesBasic
  );
}

function* functionFailureCase(
  dataType: Format.Types.FunctionExternalType,
  input: unknown,
  wrapOptions: WrapOptions
): Generator<never, never, WrapResponse> {
  throw new TypeMismatchError(
    dataType,
    input,
    wrapOptions.name,
    2,
    "Input should be one of: an object with address and selector; a 24-byte hex string; a type/value pair; or a wrapped external function"
  );
}