diff --git a/packages/abi-to-sol/src/declarations/find.ts b/packages/abi-to-sol/src/declarations/find.ts index 076b5ea..34b2541 100644 --- a/packages/abi-to-sol/src/declarations/find.ts +++ b/packages/abi-to-sol/src/declarations/find.ts @@ -1,7 +1,6 @@ import type * as Abi from "@truffle/abi-utils"; -import { abiTupleSignature } from "@truffle/abi-utils"; -import { Type, isType } from "../type"; +import { Parameter, isParameter } from "../parameter"; import { Identifier } from "./identifier"; import { Kind, HasBindings } from "./kind"; @@ -13,113 +12,121 @@ export const find = ( ): Kind => { const { type } = parameter; - if (!isType(type)) { + if (!isParameter(parameter)) { throw new Error( - `Parameter type \`${type}\` is not a valid ABI type` + `Parameter type \`${parameter.type}\` is not a valid ABI type` ); } - if (Type.isElementary(type)) { - const { internalType } = parameter; - - const identifier = Identifier.fromInternalType(internalType); - if (type === internalType || !identifier) { - return { - type, - ...( - internalType - ? { hints: { internalType } } - : {} - ) - }; - } - - const reference = Identifier.toReference(identifier); - - const kind = declarations.byIdentifierReference[reference]; + if (Parameter.isElementary(parameter)) { + return findElementary(parameter, declarations); + } - if (!kind) { - throw new Error( - `Unknown declaration with identifier reference ${reference}` - ); - } + if (Parameter.isArray(parameter)) { + return findArray(parameter, declarations); + } - return kind; + if (Parameter.isTuple(parameter)) { + return findTuple(parameter, declarations); } - if (Type.isArray(type)) { - const itemType = Type.Array.underlying(type); - - const partial: Omit, "itemKind"> = Type.Array.isStatic(type) - ? { length: Type.Array.length(type) } - : {}; - - let itemInternalType; - { - const { internalType = "" } = parameter; - const match = type.match(/^(.+)\[[^\]]*\]$/); - if (match) { - const [_, underlying] = match; - itemInternalType = underlying; - } - } + throw new Error(`Unknown type ${type}`); +} - const itemKind = find({ - ...parameter, +const findElementary = ( + parameter: Parameter.Elementary, + declarations: Declarations +): Kind => { + if (!Parameter.isUserDefinedValueType(parameter)) { + const { type, internalType } = parameter; + return { + type, ...( - itemInternalType && itemInternalType !== "tuple" - ? { internalType: itemInternalType } + internalType + ? { hints: { internalType } } : {} - ), - type: itemType - }, declarations); + ) + } + } - return { - ...partial, - itemKind - }; + const { name, scope } = Parameter.UserDefinedValueType.recognize( + parameter + ); + const identifier = Identifier.UserDefinedValueType.create({ name, scope }); + const reference = Identifier.toReference(identifier); + + const kind = declarations.byIdentifierReference[reference]; + + if (!kind) { + throw new Error( + `Unknown declaration with identifier reference ${reference}` + ); } - if (Type.isTuple(type)) { - const { - internalType, - components = [] // default satisfies typechecker; value should exist - } = parameter; - const identifier = Identifier.fromInternalType(internalType); - const signature = abiTupleSignature(components); - - if (identifier) { - const reference = Identifier.toReference(identifier); - - const kind = declarations.byIdentifierReference[reference]; - if (!kind) { - throw new Error( - `Unknown declaration with identifier reference ${reference}` - ); - } - - return kind; - } + return kind; +}; +const findArray = ( + parameter: Parameter.Array, + declarations: Declarations +): Kind => { + const itemParameter = Parameter.Array.item(parameter); + + const itemKind = find(itemParameter, declarations); + + return { + itemKind, + ...( + Parameter.Array.isStatic(parameter) + ? { length: Parameter.Array.Static.length(parameter) } + : {} + ) + }; +} - // reaching here guarantees no internalType specified for `parameter` - // so only match declarations that also have no internalType +const findTuple = ( + parameter: Parameter.Tuple, + declarations: Declarations +): Kind => { + const { + signature, + name, + scope + } = Parameter.Tuple.recognize(parameter); - const kind = Object.values(declarations.byIdentifierReference) - .find(kind => - Kind.isStruct(kind) && - kind.signature === signature && - !kind.hints?.internalType - ) + const identifier = name + ? Identifier.Struct.create({ name, scope }) + : undefined; + + if (identifier) { + const reference = Identifier.toReference(identifier); + + const kind = declarations.byIdentifierReference[reference]; if (!kind) { throw new Error( - `Unknown declaration with tuple signature ${signature}` + `Unknown declaration with identifier reference ${reference}` ); } return kind; } - throw new Error(`Unknown type ${type}`); + // reaching here guarantees no internalType specified for `parameter` + // so only match declarations that also have no internalType + + const kind = Object.values(declarations.byIdentifierReference) + .find(kind => + Kind.isStruct(kind) && + kind.signature === signature && + !kind.hints?.internalType + ) + + if (!kind) { + throw new Error( + `Unknown declaration with tuple signature ${signature}` + ); + } + + return kind; } diff --git a/packages/abi-to-sol/src/declarations/fromParameter.ts b/packages/abi-to-sol/src/declarations/fromParameter.ts index 2a74017..907df78 100644 --- a/packages/abi-to-sol/src/declarations/fromParameter.ts +++ b/packages/abi-to-sol/src/declarations/fromParameter.ts @@ -1,11 +1,9 @@ import type * as Abi from "@truffle/abi-utils"; -import { abiTupleSignature } from "@truffle/abi-utils"; -import { Identifier } from "./identifier"; -import { Type, isType } from "../type"; +import { Parameter, isParameter } from "../parameter"; +import { Identifier } from "./identifier"; import { Kind, MissingBindings } from "./kind"; - import { Declarations, empty, merge, from } from "./types"; export interface FromParameterResult { @@ -16,147 +14,161 @@ export interface FromParameterResult { export const fromParameter = ( parameter: Abi.Parameter ): FromParameterResult => { - const { type } = parameter; - - if (!isType(type)) { + if (!isParameter(parameter)) { throw new Error( - `Parameter type \`${type}\` is not a valid ABI type` + `Parameter type \`${parameter.type}\` is not a valid ABI type` ); } - if (Type.isElementary(type)) { - const { internalType } = parameter; - const identifier = Identifier.fromInternalType(internalType); - - if (internalType && internalType !== type && identifier) { - const parameterKind: Kind.UserDefinedValueType = { - type, - hints: { internalType }, - identifier - }; - - return { - declarations: from(parameterKind), - parameterKind - } - } + if (Parameter.isElementary(parameter)) { + return fromElementaryParameter(parameter); + } + + if (Parameter.isArray(parameter)) { + return fromArrayParameter(parameter); + } + + if (Parameter.isTuple(parameter)) { + return fromTupleParameter(parameter); + } - const parameterKind: Kind.Elementary = { + throw new Error(`Unexpectedly could not convert Abi.Parameter to Kind`); +}; + +const fromElementaryParameter = ( + parameter: Parameter.Elementary +): FromParameterResult => { + if (Parameter.isUserDefinedValueType(parameter)) { + const { name, scope } = Parameter.UserDefinedValueType.recognize( + parameter + ); + const identifier = Identifier.UserDefinedValueType.create({ name, scope }); + + const { type, internalType } = parameter; + + const parameterKind: Kind.UserDefinedValueType = { type, - ...( - internalType - ? { hints: { internalType } } - : {} - ) + hints: { internalType }, + identifier }; return { - declarations: empty(), - parameterKind + parameterKind, + declarations: from(parameterKind) } } - if (Type.isArray(type)) { - const itemType = Type.Array.underlying(type); - - const partial: Omit, "itemKind"> = Type.Array.isStatic(type) - ? { length: Type.Array.length(type) } - : {}; - - let itemInternalType; - { - const { internalType = "" } = parameter; - const match = type.match(/^(.+)\[[^\]]*\]$/); - if (match) { - const [_, underlying] = match; - itemInternalType = underlying; - } - } + const { type, internalType } = parameter; - const { parameterKind: itemKind, declarations } = fromParameter({ - ...parameter, - ...( - itemInternalType && itemInternalType !== "tuple" - ? { internalType: itemInternalType } - : {} - ), - type: itemType - }); - - const parameterKind: Kind.Array = { - ...partial, - itemKind - }; + const parameterKind: Kind.Elementary = { + type, + ...( + internalType + ? { hints: { internalType } } + : {} + ) + }; - return { - declarations, - parameterKind - }; + return { + parameterKind, + declarations: from(parameterKind) } +} - if (Type.isTuple(type)) { - const { - internalType, - components = [] // default satisfies typechecker; value should exist - } = parameter; - const identifier = Identifier.fromInternalType(internalType); - const signature = abiTupleSignature(components); - - const partial: Omit, "members"> = { - signature, - ...( - internalType - ? { hints: { internalType } } - : {} - ), - ...( - identifier - ? { identifier } - : {} - ) - }; - const membersResults: { - member: Kind.Struct.Member; - declarations: Declarations - }[] = components.map(({ name, ...parameter }) => { - const partial: Omit, "kind"> = name - ? { name } - : {}; - - // coerce just to satisfy typechecker... the rest of a component, - // besides `name`, should be valid Abi.Parameter (at least for this - // function's purpose) - const { parameterKind: kind, declarations } = fromParameter( - parameter as Abi.Parameter - ) - - return { - member: { - ...partial, - kind - }, - declarations - } - }); - - const members = membersResults.map( - ({ member }) => member - ); +const fromArrayParameter = ( + parameter: Parameter.Array +): FromParameterResult => { + const itemParameter = Parameter.Array.item(parameter); + + const { + parameterKind: itemKind, + declarations + } = fromParameter(itemParameter); + + const parameterKind: Kind.Array = { + itemKind, + ...( + Parameter.Array.isStatic(parameter) + ? { length: Parameter.Array.Static.length(parameter) } + : {} + ) + }; + + return { + declarations, + parameterKind + }; +}; - const parameterKind = { - ...partial, - members - }; +const fromTupleParameter = ( + parameter: Parameter.Tuple +): FromParameterResult => { + const { + internalType, + components + } = parameter; + + const { + signature, + name, + scope + } = Parameter.Tuple.recognize(parameter); + + const identifier = name + ? Identifier.Struct.create({ name, scope }) + : undefined; + + const memberResults: { + member: Kind.Struct.Member; + declarations: Declarations + }[] = components.map(component => { + const { name } = component; - const declarations = membersResults - .map(({ declarations }) => declarations) - .reduce(merge, from(parameterKind)); + const { + parameterKind: kind, + declarations + } = fromParameter(component); return { - declarations, - parameterKind - }; - } - - throw new Error(`Unexpectedly could not convert Abi.Parameter to Kind`); -}; + member: { + kind, + ...( + name + ? { name } + : {} + ) + }, + declarations + } + }); + + const members = memberResults.map(({ member }) => member); + const membersDeclarations = memberResults + .map(({ declarations }) => declarations) + .reduce(merge, empty()); + + const parameterKind = { + signature, + members, + ...( + internalType + ? { hints: { internalType } } + : {} + ), + ...( + identifier + ? { identifier } + : {} + ) + }; + + const declarations = merge( + membersDeclarations, + from(parameterKind) + ); + + return { + declarations, + parameterKind + }; +} diff --git a/packages/abi-to-sol/src/declarations/identifier.ts b/packages/abi-to-sol/src/declarations/identifier.ts index 966d171..e7e8ea6 100644 --- a/packages/abi-to-sol/src/declarations/identifier.ts +++ b/packages/abi-to-sol/src/declarations/identifier.ts @@ -1,13 +1,18 @@ export type Identifier = | Identifier.Interface | Identifier.Struct - | Identifier.Udvt; + | Identifier.UserDefinedValueType; export namespace Identifier { + export interface Properties { + name: string; + scope?: string; + } + export type Class = | Struct.Class | Interface.Class - | Udvt.Class; + | UserDefinedValueType.Class; export interface Interface { class: Interface.Class; @@ -18,6 +23,13 @@ export namespace Identifier { export namespace Interface { export type Class = "interface"; + export const create = ({ + name + }: Omit): Identifier.Interface => ({ + class: "interface", + name + }); + export type Reference = `${ Identifier["class"] }--${ @@ -33,16 +45,43 @@ export namespace Identifier { export namespace Struct { export type Class = "struct"; + + export const create = ({ + name, + scope + }: Properties): Identifier.Struct => ({ + class: "struct", + name, + ...( + scope + ? { container: Identifier.Interface.create({ name: scope }) } + : {} + ) + }); } - export interface Udvt { - class: Udvt.Class; + export interface UserDefinedValueType { + class: UserDefinedValueType.Class; name: string; container?: Interface; } - export namespace Udvt { + + export namespace UserDefinedValueType { export type Class = "udvt"; + + export const create = ({ + name, + scope + }: Properties): Identifier.UserDefinedValueType => ({ + class: "udvt", + name, + ...( + scope + ? { container: Identifier.Interface.create({ name: scope }) } + : {} + ) + }); } export type Reference = diff --git a/packages/abi-to-sol/src/parameter.ts b/packages/abi-to-sol/src/parameter.ts new file mode 100644 index 0000000..a5765d0 --- /dev/null +++ b/packages/abi-to-sol/src/parameter.ts @@ -0,0 +1,188 @@ +import * as Abi from "@truffle/abi-utils"; +import { abiTupleSignature } from "@truffle/abi-utils"; + +import { Type, isType } from "./type"; + +export type Parameter = Abi.Parameter & { + type: Type +}; + +export const isParameter = ( + parameter: Abi.Parameter +): parameter is Parameter => isType(parameter.type); + +export namespace Parameter { + export type Elementary = Abi.Parameter & { + type: Type.Elementary + }; + + export const isElementary = ( + parameter: Parameter + ): parameter is Elementary => Type.isElementary(parameter.type); + + export type UserDefinedValueType = Elementary & { + internalType: string + } + + export const isUserDefinedValueType = ( + parameter: Parameter + ): parameter is UserDefinedValueType => + isElementary(parameter) && + !!parameter.internalType && + parameter.internalType !== parameter.type && + UserDefinedValueType.internalTypePattern.test(parameter.internalType); + + export namespace UserDefinedValueType { + export const internalTypePattern = new RegExp( + /^(([a-zA-Z$_][a-zA-Z0-9$_]*)\.)?([a-zA-Z$_][a-zA-Z0-9$_]*)$/ + ); + + export type RecognizeResult

= ( + P extends Parameter.UserDefinedValueType + ? { + name: string; + scope?: string + } + : { + name: string; + scope?: string + } | undefined + ); + + export const recognize =

( + parameter: P + ): RecognizeResult

=> { + const { type, internalType } = parameter; + + if (!internalType || internalType === type) { + return undefined as RecognizeResult

; + } + + const match = internalType.match(internalTypePattern); + if (!match) { + return undefined as RecognizeResult

; + } + + const scope = match[2]; + const name = match[3]; + + return { + name, + ...( + scope + ? { scope } + : {} + ) + } as RecognizeResult

; + }; + } + + export type Array = Parameter & { + type: Type.Array + }; + + export const isArray = ( + parameter: Parameter + ): parameter is Parameter.Array => Type.isArray(parameter.type); + + export namespace Array { + export const item = ( + parameter: Parameter.Array + ): Parameter => { + const type = Type.Array.underlying(parameter.type); + + let internalType; + { + const match = (parameter.internalType || "").match(/^(.+)\[[^\]]*\]$/); + if (match) { + const [_, underlying] = match; + internalType = underlying; + } + } + + return { + ...parameter, + type, + ...( + internalType && internalType !== "tuple" + ? { internalType } + : {} + ) + }; + }; + + export type Static = Parameter.Array & { + type: Type.Array.Static + }; + + export const isStatic = ( + parameter: Parameter.Array + ): parameter is Parameter.Array.Static => + Type.Array.isStatic(parameter.type); + + export namespace Static { + export const length = ( + parameter: Parameter.Array.Static + ): number => Type.Array.length(parameter.type); + } + } + + export type Tuple = Parameter & { + type: Type.Tuple; + components: Exclude; + } + + export const isTuple = ( + parameter: Parameter + ): parameter is Parameter.Tuple => Type.isTuple(parameter.type); + + + export namespace Tuple { + export const internalTypePattern = new RegExp( + /^struct (([a-zA-Z$_][a-zA-Z0-9$_]*)\.)?([a-zA-Z$_][a-zA-Z0-9$_]*)$/ + ); + + export type TupleRecognizeResult = { + signature: string; + name?: string; + scope?: string + } + + export type RecognizeResult

= + P extends Parameter.Tuple + ? TupleRecognizeResult + : TupleRecognizeResult | undefined; + + export const recognize =

( + parameter: P + ): RecognizeResult

=> { + if (!Parameter.isTuple(parameter)) { + return undefined as RecognizeResult

; + } + + const signature = abiTupleSignature(parameter.components); + + if (!parameter.internalType) { + return { signature }; + } + + const match = parameter.internalType.match(internalTypePattern); + if (!match) { + return { signature }; + } + + const scope = match[2]; + const name = match[3]; + + return { + signature, + name, + ...( + scope + ? { scope } + : {} + ) + }; + }; + } +}