packages/codec/lib/wrap/wrap.ts
import debugModule from "debug";
const debug = debugModule("codec:wrap:wrap");
import * as Format from "@truffle/codec/format";
import { TypeMismatchError } from "./errors";
import type { WrapRequest, WrapResponse } from "../types";
import type { Case, TupleLikeType, TupleLikeValue, WrapOptions } from "./types";
import { wrapWithCases } from "./dispatch";
import * as Messages from "./messages";
import * as Conversion from "@truffle/codec/conversion";
import * as Utils from "./utils";
import type { Options } from "@truffle/codec/common";
import { integerCases } from "./integer";
import { decimalCases } from "./decimal";
import { boolCases } from "./bool";
import { bytesCases } from "./bytes";
import { addressCases } from "./address";
import { stringCases } from "./string";
import { functionExternalCases } from "./function";
//this file contains the main wrap function, as well as the cases
//for arrays, tuples, udvts, and tx options. all other types get their
//own file.
const arrayCasesBasic: Case<
Format.Types.ArrayType,
Format.Values.ArrayValue,
WrapRequest
>[] = [
arrayFromArray,
arrayFromCodecArrayValue,
arrayFromJson,
arrayFailureCase
];
export const arrayCases: Case<
Format.Types.ArrayType,
Format.Values.ArrayValue,
WrapRequest
>[] = [arrayFromTypeValueInput, ...arrayCasesBasic];
const tupleCasesBasic: Case<TupleLikeType, TupleLikeValue, WrapRequest>[] = [
tupleFromArray,
tupleFromCodecTupleLikeValue,
tupleFromObject,
tupleFromJson,
tupleFailureCase
];
export const tupleCases: Case<TupleLikeType, TupleLikeValue, WrapRequest>[] = [
tupleFromTypeValueInput,
...tupleCasesBasic
];
const txOptionsCasesBasic: Case<
Format.Types.OptionsType,
Format.Values.OptionsValue,
WrapRequest
>[] = [optionsFromCodecOptionsValue, optionsFromObject, optionsFailureCase];
export const txOptionsCases: Case<
Format.Types.OptionsType,
Format.Values.OptionsValue,
WrapRequest
>[] = [optionsFromTypeValueInput, ...txOptionsCasesBasic];
export const udvtCases: Case<
Format.Types.UserDefinedValueTypeType,
Format.Values.UserDefinedValueTypeValue,
WrapRequest
>[] = [
//no separate case for udvtFromUdvtValue,
//since underlying already handles this
udvtFromUnderlying
];
export function* wrap(
dataType: Format.Types.Type,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, Format.Values.Value, WrapResponse> {
if (!wrapOptions.name) {
wrapOptions = { ...wrapOptions, name: "<input>" };
}
switch (dataType.typeClass) {
case "uint":
case "int":
case "enum":
return yield* wrapWithCases(dataType, input, wrapOptions, integerCases);
case "fixed":
case "ufixed":
return yield* wrapWithCases(dataType, input, wrapOptions, decimalCases);
case "bool":
return yield* wrapWithCases(dataType, input, wrapOptions, boolCases);
case "bytes":
return yield* wrapWithCases(dataType, input, wrapOptions, bytesCases);
case "address":
case "contract":
//these are treated the same
return yield* wrapWithCases(dataType, input, wrapOptions, addressCases);
case "string":
return yield* wrapWithCases(dataType, input, wrapOptions, stringCases);
case "function":
//special check: weed out internal functions
if (dataType.visibility === "internal") {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
5, //it doesn't matter, but we'll make this error high specificity
`Wrapping/encoding for internal function pointers is not supported`
);
}
//otherwise, go ahead
return yield* wrapWithCases(
dataType,
input,
wrapOptions,
functionExternalCases
);
case "array":
return yield* wrapWithCases(dataType, input, wrapOptions, arrayCases);
case "struct":
case "tuple":
//these are treated the same as well
return yield* wrapWithCases(dataType, input, wrapOptions, tupleCases);
case "userDefinedValueType":
return yield* wrapWithCases(dataType, input, wrapOptions, udvtCases);
case "options":
return yield* wrapWithCases(dataType, input, wrapOptions, txOptionsCases);
default:
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
5, //it doesn't matter, but we'll make this error high specificity
`Wrapping/encoding for type ${Format.Types.typeStringWithoutLocation(
dataType
)} is not supported`
);
}
}
//array cases
function* arrayFromArray(
dataType: Format.Types.ArrayType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, Format.Values.ArrayValue, WrapResponse> {
if (!Array.isArray(input)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not an array"
);
}
if (dataType.kind === "static" && !dataType.length.eqn(input.length)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
5,
Messages.wrongArrayLengthMessage(dataType.length, input.length)
);
}
//can't do yield in a map, so manual loop here
let value: Format.Values.Value[] = [];
for (let index = 0; index < input.length; index++) {
value.push(
yield* wrap(dataType.baseType, input[index], {
...wrapOptions,
name: `${wrapOptions.name}[${index}]`, //set new name for components
specificityFloor: 5 //errors in components are quite specific!
})
);
}
return {
type: dataType,
kind: "value" as const,
value,
interpretations: {}
};
}
function* arrayFromCodecArrayValue(
dataType: Format.Types.ArrayType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, Format.Values.ArrayValue, WrapResponse> {
if (!Utils.isWrappedResult(input)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not a wrapped result"
);
}
if (input.type.typeClass !== "array") {
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
);
}
//we won't bother with detailed typechecking as much of it is handled
//either in the call to arrayFromArray or in the wrapping of the
//individual elements; we will check dynamic vs static though as that
//isn't handled elsewhere
if (!wrapOptions.loose && input.type.kind === dataType.kind) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
5,
Messages.wrappedTypeMessage(input.type)
);
}
//note that we do *not* just copy over input.value, but rather we
//defer to arrayFromArray; this is because there might be some elements
//where the type is not the same but is compatible
const value = (<Format.Values.ArrayValue>input).value;
return yield* arrayFromArray(dataType, value, wrapOptions);
}
function* arrayFromJson(
dataType: Format.Types.ArrayType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, Format.Values.ArrayValue, WrapResponse> {
if (!wrapOptions.allowJson) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"JSON input must be explicitly enabled"
);
}
if (typeof input !== "string") {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not a string"
);
}
let parsedInput: unknown;
try {
parsedInput = JSON.parse(input);
} catch (error) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
5,
`Input was not valid JSON: ${error.message}`
);
}
return yield* arrayFromArray(dataType, parsedInput, wrapOptions);
}
function* arrayFromTypeValueInput(
dataType: Format.Types.ArrayType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, Format.Values.ArrayValue, WrapResponse> {
if (!Utils.isTypeValueInput(input)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not a type/value pair"
);
}
if (input.type !== "array") {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
5,
Messages.specifiedTypeMessage(input.type)
);
}
//don't turn on loose here, only do that for non-container types!
return yield* wrapWithCases(
dataType,
input.value,
wrapOptions,
arrayCasesBasic
);
}
function* arrayFailureCase(
dataType: Format.Types.ArrayType,
input: unknown,
wrapOptions: WrapOptions
): Generator<never, never, WrapResponse> {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
2,
"Input was not an array, type/value pair or wrapped array"
);
}
//tuple/struct cases;
//note even with loose turned off, we won't distinguish
//between tuples and structs
function* tupleFromArray(
dataType: TupleLikeType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, TupleLikeValue, WrapResponse> {
//first: obtain the types of the members
if (!Array.isArray(input)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not an array"
);
}
debug("input is array");
const memberTypes = memberTypesForType(
dataType,
wrapOptions.userDefinedTypes
);
if (memberTypes.length !== input.length) {
debug("input is wrong-length array");
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
5,
Messages.wrongArrayLengthMessage(memberTypes.length, input.length)
);
}
//can't do yield in a map, so manual loop here
let value: Format.Values.OptionallyNamedValue[] = [];
for (let index = 0; index < input.length; index++) {
const memberName = memberTypes[index].name;
debug("wrapping %s", memberName);
value.push({
name: memberName,
value: yield* wrap(memberTypes[index].type, input[index], {
...wrapOptions,
name: memberName
? wrapOptions.name.match(/^<.*>$/) //hack?
? memberName
: `${wrapOptions.name}.${memberName}`
: `${wrapOptions.name}[${index}]`,
specificityFloor: 5
})
});
}
//we need to coerce here because TS doesn't know that if it's a struct
//then everything has a name
return <TupleLikeValue>{
type: dataType,
kind: "value" as const,
value,
interpretations: {}
};
}
function* tupleFromObject(
dataType: TupleLikeType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, TupleLikeValue, WrapResponse> {
if (!Utils.isPlainObject(input)) {
//just checks that it's an object & not null
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not a non-null object"
);
}
if (!wrapOptions.loose && Utils.isTypeValueInput(input)) {
//let's exclude these unless loose is turned on
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was a type/value pair"
);
}
if (!wrapOptions.loose && Utils.isWrappedResult(input)) {
//similarly here
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was a wrapped result"
);
}
const memberTypes = memberTypesForType(
dataType,
wrapOptions.userDefinedTypes
);
if (memberTypes.some(({ name }) => !name)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
4,
"Plain object input is allowed only when all elements of tuple are named"
);
}
let unusedKeys = new Set(Object.keys(input));
let value: Format.Values.OptionallyNamedValue[] = [];
for (let index = 0; index < memberTypes.length; index++) {
//note we had better process these in order!
const memberName = memberTypes[index].name;
if (!(memberName in input)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
4,
`Missing key from tuple or struct: ${memberName}`
);
}
unusedKeys.delete(memberName);
value.push({
name: memberName,
value: yield* wrap(memberTypes[index].type, input[memberName], {
...wrapOptions,
name: `${wrapOptions.name}.${memberName}`,
specificityFloor: 4 //not sure this warrants a 5
})
});
}
if (!wrapOptions.loose) {
if (unusedKeys.size > 0) {
//choose one arbitrarily
const exampleKey = unusedKeys.values().next().value;
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
4,
`Unknown key ${exampleKey} included`
);
}
}
//we need to coerce here because TS doesn't know that if it's a struct
//then everything has a name
return <TupleLikeValue>{
type: dataType,
kind: "value" as const,
value
};
}
function* tupleFromJson(
dataType: TupleLikeType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, TupleLikeValue, WrapResponse> {
if (!wrapOptions.allowJson) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"JSON input must be explicitly enabled"
);
}
if (typeof input !== "string") {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not a string"
);
}
let parsedInput: unknown;
try {
parsedInput = JSON.parse(input);
} catch (error) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
5,
`Input was not valid JSON: ${error.message}`
);
}
debug("input is JSON");
debug("parses to: %O", parsedInput);
return yield* wrapWithCases(dataType, parsedInput, wrapOptions, [
tupleFromObject,
tupleFromArray
]);
}
function* tupleFromCodecTupleLikeValue(
dataType: TupleLikeType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, TupleLikeValue, WrapResponse> {
if (!Utils.isWrappedResult(input)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not a wrapped result"
);
}
if (input.type.typeClass !== "tuple" && input.type.typeClass !== "struct") {
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
);
}
//not going to do much typechecking here as it'll be handled in the call
//to tupleFromArray
//Typescript complains if I try to say it can be either struct or
//tuple, so, uh, let's just tell it it's a tuple <shrug>
const coercedInput = <Format.Values.TupleValue>input; //HACK!
//note that we do *not* just copy over input.value, but rather we
//defer to tupleFromArray; this is because there might be some elements
//where the type is not the same but is compatible
return yield* tupleFromArray(
dataType,
coercedInput.value.map(({ value }) => value),
wrapOptions
);
}
function* tupleFromTypeValueInput(
dataType: TupleLikeType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, TupleLikeValue, WrapResponse> {
if (!Utils.isTypeValueInput(input)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not a type/value pair"
);
}
if (input.type !== "tuple" && input.type !== "struct") {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
5,
Messages.specifiedTypeMessage(input.type)
);
}
//don't turn on loose here, only do that for non-container types!
return yield* wrapWithCases(
dataType,
input.value,
wrapOptions,
tupleCasesBasic
);
}
function* tupleFailureCase(
dataType: TupleLikeType,
input: unknown,
wrapOptions: WrapOptions
): Generator<never, never, WrapResponse> {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
2,
"Input was not an array, plain object, type/value pair or wrapped tuple or struct"
);
}
function memberTypesForType(
dataType: TupleLikeType,
userDefinedTypes: Format.Types.TypesById
): Format.Types.OptionallyNamedType[] {
switch (dataType.typeClass) {
case "tuple":
return dataType.memberTypes;
break;
case "struct":
debug("wrapping for struct %s", dataType.typeName);
return (<Format.Types.StructType>(
Format.Types.fullType(dataType, userDefinedTypes)
)).memberTypes;
}
}
//udvt cases
function* udvtFromUnderlying(
dataType: Format.Types.UserDefinedValueTypeType,
input: unknown,
wrapOptions: WrapOptions
): Generator<
WrapRequest,
Format.Values.UserDefinedValueTypeValue,
WrapResponse
> {
const { underlyingType } = <Format.Types.UserDefinedValueTypeType>(
Format.Types.fullType(dataType, wrapOptions.userDefinedTypes)
);
const value = yield* wrap(underlyingType, input, wrapOptions);
return {
type: dataType,
kind: "value",
value: <Format.Values.BuiltInValueValue>value,
interpretations: {}
};
}
//tx options cases
function* optionsFromObject(
dataType: Format.Types.OptionsType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, Format.Values.OptionsValue, WrapResponse> {
if (!Utils.isPlainObject(input)) {
//just checks that it's an object & not null
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not a non-null object"
);
}
debug("options input is object: %O", input);
debug("wrapOptions: %O", wrapOptions);
if (!wrapOptions.loose && Utils.isWrappedResult(input)) {
//similarly here
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was a wrapped result"
);
}
//now... the main case
let value: Options = {};
const uintKeys = [
"gas",
"gasPrice",
"value",
"nonce",
"maxFeePerGas",
"maxPriorityFeePerGas"
] as const;
const uint8Keys = ["type"] as const;
const addressKeys = ["from", "to"] as const;
const bytesKeys = ["data"] as const;
const boolKeys = ["overwrite"] as const;
const accessListKeys = ["accessList"] as const;
const specialKeys = ["privateFor"];
const allKeys = [
...uintKeys,
...uint8Keys,
...addressKeys,
...bytesKeys,
...boolKeys,
...accessListKeys,
...specialKeys
];
const badKey = Object.keys(input).find(key => !allKeys.includes(key));
const goodKey = Object.keys(input).find(key => allKeys.includes(key));
if (badKey !== undefined && !wrapOptions.oldOptionsBehavior) {
//note we allow extra keys if oldOptionsBehavior is on -- this is a HACK
//to preserve existing behavior of Truffle Contract (perhaps we can
//change this in Truffle 6)
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
4,
`Transaction options included unknown option ${badKey}`
);
}
if (wrapOptions.oldOptionsBehavior && goodKey === undefined) {
//similarly, if oldOptionsBehavior is on, we require at least
//one *legit* key (again, HACK to preserve existing behavior,
//maybe remove this in Truffle 6)
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
4,
`Transaction options included no recognized options`
);
}
//otherwise, if all keys are transaction options, let's process them...
//part 1: uint options
for (const key of uintKeys) {
//note we check input[key] !== undefined, rather than key in input,
//because if one of them is undefined we want to just allow that but ignore it
if (input[key] !== undefined) {
const wrappedOption = <Format.Values.UintValue>(
yield* wrapWithCases(
{ typeClass: "uint", bits: 256 },
input[key],
{ ...wrapOptions, name: `${wrapOptions.name}.${key}` },
integerCases
)
);
value[key] = wrappedOption.value.asBN;
}
}
//part 2: uint8 options (just type for now)
for (const key of uint8Keys) {
if (input[key] !== undefined) {
const wrappedOption = <Format.Values.UintValue>(
yield* wrapWithCases(
{ typeClass: "uint", bits: 8 },
input[key],
{ ...wrapOptions, name: `${wrapOptions.name}.${key}` },
integerCases
)
);
const asBN = wrappedOption.value.asBN;
//since this is just type right now, we're going to reject illegal types
if (asBN.gten(0xc0)) {
//not making a constant for this, this is its only use here
throw new TypeMismatchError(
dataType,
input,
`${wrapOptions.name}.type`,
4,
"Transaction types must be less than 0xc0"
);
}
//for compatibility, we give type as a hex string rather than
//leaving it as a BN. Since it's unsigned we don't have to
//worry about negatives.
value[key] = Conversion.toHexString(asBN);
}
}
//part 3: address options
for (const key of addressKeys) {
if (input[key] !== undefined) {
const wrappedOption = <Format.Values.AddressValue>(
yield* wrapWithCases(
{ typeClass: "address", kind: "general" },
input[key],
{ ...wrapOptions, name: `${wrapOptions.name}.${key}` },
addressCases
)
);
value[key] = wrappedOption.value.asAddress;
}
}
//part 4: bytestring options
for (const key of bytesKeys) {
if (input[key] !== undefined) {
const wrappedOption = yield* wrapWithCases(
{ typeClass: "bytes", kind: "dynamic" },
input[key],
{ ...wrapOptions, name: `${wrapOptions.name}.${key}` },
bytesCases
);
value[key] = wrappedOption.value.asHex;
}
}
//part 5: boolean options
for (const key of boolKeys) {
if (input[key] !== undefined) {
const wrappedOption = yield* wrapWithCases(
{ typeClass: "bool" },
input[key],
{ ...wrapOptions, name: `${wrapOptions.name}.${key}` },
boolCases
);
value[key] = wrappedOption.value.asBoolean;
}
}
//part 6: the access list
for (const key of accessListKeys) {
if (input[key] !== undefined) {
const wrappedOption = yield* wrapWithCases(
{
typeClass: "array",
kind: "dynamic",
baseType: {
typeClass: "tuple",
memberTypes: [
{
name: "address",
type: {
typeClass: "address",
kind: "general"
}
},
{
name: "storageKeys",
type: {
typeClass: "array",
kind: "dynamic",
baseType: {
//we use uint256 rather than bytes32 to allow
//abbreviating and left-padding
typeClass: "uint",
bits: 256
}
}
}
]
}
},
input[key],
{ ...wrapOptions, name: `${wrapOptions.name}.${key}` },
arrayCases
);
value[key] = Format.Utils.Inspect.nativizeAccessList(wrappedOption);
}
}
//part 7: the special case of privateFor
if (input.privateFor !== undefined) {
//this doesn't correspond to any of our usual types, so we have to handle it specially
if (!Array.isArray(input.privateFor)) {
throw new TypeMismatchError(
dataType,
input,
`${wrapOptions.name}.privateFor`,
4,
"Transaction option privateFor should be an array of base64-encoded bytestrings of 32 bytes"
);
}
value.privateFor = input.privateFor.map(
(publicKey: unknown, index: number) => {
if (Utils.isBoxedString(publicKey)) {
publicKey = publicKey.valueOf();
}
if (typeof publicKey !== "string") {
throw new TypeMismatchError(
dataType,
input,
`${wrapOptions.name}.privateFor`,
4,
`Public key at index ${index} is not a string`
);
}
if (!Utils.isBase64(publicKey)) {
throw new TypeMismatchError(
dataType,
input,
`${wrapOptions.name}.privateFor`,
4,
`Public key at index ${index} is not base64-encoded`
);
}
const length = Utils.base64Length(publicKey);
if (length !== 32) {
throw new TypeMismatchError(
dataType,
input,
`${wrapOptions.name}.privateFor`,
4,
`Public key at index ${index} should encode a bytestring of 32 bytes; got ${length} bytes instead`
);
}
return publicKey;
}
);
}
return {
type: dataType,
kind: "value" as const,
value,
interpretations: {}
};
}
function* optionsFromCodecOptionsValue(
dataType: Format.Types.OptionsType,
input: unknown,
wrapOptions: WrapOptions
): Generator<never, Format.Values.OptionsValue, WrapResponse> {
if (!Utils.isWrappedResult(input)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not a wrapped result"
);
}
if (input.type.typeClass !== "options") {
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 value = (<Format.Values.OptionsValue>input).value;
//unlike in the array or tuple cases, here should not have
//to worry about compatible-but-not-identical types, so it's
//safe to just copy value over
return {
type: dataType,
kind: "value" as const,
value,
interpretations: {}
};
}
function* optionsFromTypeValueInput(
dataType: Format.Types.OptionsType,
input: unknown,
wrapOptions: WrapOptions
): Generator<WrapRequest, Format.Values.OptionsValue, WrapResponse> {
if (!Utils.isTypeValueInput(input)) {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
1,
"Input was not a type/value pair"
);
}
if (input.type !== "options") {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
5,
Messages.specifiedTypeMessage(input.type)
);
}
//because options, unlike other containers, has specific types, we *will* turn on loose
return yield* wrapWithCases(
dataType,
input.value,
{ ...wrapOptions, loose: true },
txOptionsCasesBasic
);
}
function* optionsFailureCase(
dataType: Format.Types.OptionsType,
input: unknown,
wrapOptions: WrapOptions
): Generator<never, never, WrapResponse> {
throw new TypeMismatchError(
dataType,
input,
wrapOptions.name,
2,
"Transaction options input was not a plain object, type/value pair or wrapped options object"
);
}