diff --git a/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts b/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts new file mode 100644 index 0000000000..385c80d106 --- /dev/null +++ b/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts @@ -0,0 +1,94 @@ +/* eslint-disable @typescript-eslint/no-use-before-define */ +import type { + AbiConcreteType, + AbiMetadataType, + AbiTypeArgument, + AbiTypeComponent, +} from '../../abi'; + +import type { ResolvableComponent, ResolvableType } from './resolvable-type'; +import type { ResolvedType } from './resolved-type'; + +function mapMetadata(type: ResolvableType | ResolvedType) { + const result: AbiTypeComponent['type']['metadata'] = { + metadataTypeId: type.metadataType?.metadataTypeId as number, + }; + + if (type.typeParamsArgsMap && type.metadataType?.typeParameters?.length) { + result.typeArguments = type.typeParamsArgsMap.map((t) => toTypeArgument(t[1])); + } + + return result; +} + +function isResolvedType(type: ResolvableType | ResolvedType): type is ResolvedType { + return 'typeId' in type; +} + +function isResolvedConcreteType( + type: ResolvableType | ResolvedType +): type is ResolvedType & { typeId: string } { + return isResolvedType(type) && typeof type.typeId === 'string'; +} + +function mapComponentType(component: ResolvableComponent): AbiTypeComponent { + const { name, type } = component; + + let result: AbiTypeComponent['type']; + + if (isResolvedConcreteType(type)) { + result = { + swayType: type.swayType, + concreteTypeId: type.typeId, + }; + if (type.metadataType) { + result.metadata = mapMetadata(type) as AbiConcreteType['metadata']; + } + } else { + result = { + swayType: type.swayType, + metadata: mapMetadata(type), + }; + } + + if (type.components) { + result.components = type.components.map(mapComponentType); + } + + return { name, type: result }; +} + +function toTypeArgument(type: ResolvableType | ResolvedType): AbiTypeArgument { + // type args and components follow the same mapping logic + return mapComponentType({ name: '', type }).type; +} + +export function toAbiType(t: ResolvableType | ResolvedType): AbiConcreteType | AbiMetadataType { + let result: AbiConcreteType | AbiMetadataType; + + if (isResolvedConcreteType(t)) { + result = { + concreteTypeId: t.typeId, + swayType: t.swayType, + }; + + if (t.metadataType) { + result.metadata = mapMetadata(t) as AbiConcreteType['metadata']; + } + } else { + result = { + swayType: t.swayType, + metadataTypeId: t.metadataType?.metadataTypeId as number, + }; + + if (t.typeParamsArgsMap) { + result.typeParameters = t.typeParamsArgsMap.map(([, rt]) => toAbiType(rt) as AbiMetadataType); + } + } + + if (t.components) { + result.components = t.components.map(mapComponentType); + } + + return result; +} diff --git a/packages/abi/src/parser/specifications/v1/cleanup-abi.ts b/packages/abi/src/parser/specifications/v1/cleanup-abi.ts index 018400f49a..1d517f4370 100644 --- a/packages/abi/src/parser/specifications/v1/cleanup-abi.ts +++ b/packages/abi/src/parser/specifications/v1/cleanup-abi.ts @@ -26,8 +26,7 @@ export function cleanupAbi(abi: AbiSpecificationV1): AbiSpecificationV1 { * but we only care about the `buf`'s first type argument * which defines the type of the vector data. * Everything else is being ignored, - * as it's then easier to reason about the vector - * (you just treat is as a regular struct). + * as it's then easier to reason about the vector. */ case 'struct std::vec::Vec': return { diff --git a/packages/abi/src/parser/specifications/v1/parser.ts b/packages/abi/src/parser/specifications/v1/parser.ts index b4351c018a..0048ea802f 100644 --- a/packages/abi/src/parser/specifications/v1/parser.ts +++ b/packages/abi/src/parser/specifications/v1/parser.ts @@ -1,5 +1,6 @@ -import type { Abi, AbiConcreteType } from '../../abi'; +import type { Abi, AbiConcreteType, AbiMetadataType } from '../../abi'; +import { toAbiType } from './abi-type-mappers'; import { cleanupAbi } from './cleanup-abi'; import { mapAttribute } from './map-attribute'; import { ResolvableType } from './resolvable-type'; @@ -16,8 +17,14 @@ import type { export class AbiParserV1 { static parse(abi: AbiSpecificationV1): Abi { const cleanAbi = cleanupAbi(abi); + + const abiTypeMaps = { + metadataTypes: new Map(cleanAbi.metadataTypes.map((type) => [type.metadataTypeId, type])), + concreteTypes: new Map(cleanAbi.concreteTypes.map((type) => [type.concreteTypeId, type])), + }; + const resolvableTypes = cleanAbi.metadataTypes.map( - (metadataType) => new ResolvableType(cleanAbi, metadataType.metadataTypeId, undefined) + (metadataType) => new ResolvableType(abiTypeMaps, metadataType.metadataTypeId, undefined) ); const concreteTypes = cleanAbi.concreteTypes.map((concreteType) => { @@ -29,7 +36,7 @@ export class AbiParserV1 { ? resolvableType.resolve(concreteType) : new ResolvedType({ swayType: concreteType.type, typeId: concreteType.concreteTypeId }); - return resolvedType.toAbiType(); + return toAbiType(resolvedType) as AbiConcreteType; }); const getType = (concreteTypeId: string) => @@ -37,7 +44,7 @@ export class AbiParserV1 { concreteTypes.find((abiType) => abiType.concreteTypeId === concreteTypeId) as AbiConcreteType; return { - metadataTypes: resolvableTypes.map((rt) => rt.toAbiType()), + metadataTypes: resolvableTypes.map((rt) => toAbiType(rt) as AbiMetadataType), concreteTypes, encodingVersion: cleanAbi.encodingVersion, programType: cleanAbi.programType as Abi['programType'], diff --git a/packages/abi/src/parser/specifications/v1/resolvable-type.ts b/packages/abi/src/parser/specifications/v1/resolvable-type.ts index 66b5003712..6f85aad337 100644 --- a/packages/abi/src/parser/specifications/v1/resolvable-type.ts +++ b/packages/abi/src/parser/specifications/v1/resolvable-type.ts @@ -1,7 +1,6 @@ import { FuelError } from '@fuel-ts/errors'; import { swayTypeMatchers } from '../../../matchers/sway-type-matchers'; -import type { AbiTypeComponent, AbiMetadataType, AbiTypeArgument } from '../../abi'; import type { ResolvedComponent } from './resolved-type'; import { ResolvedType } from './resolved-type'; @@ -9,22 +8,24 @@ import type { AbiComponentV1, AbiConcreteTypeV1, AbiMetadataTypeV1, - AbiSpecificationV1, AbiTypeArgumentV1, } from './specification'; -interface ResolvableComponent { +export interface ResolvableComponent { name: string; type: ResolvableType | ResolvedType; } export class ResolvableType { - private metadataType: AbiMetadataTypeV1; + metadataType: AbiMetadataTypeV1; swayType: string; components: ResolvableComponent[] | undefined; constructor( - private abi: AbiSpecificationV1, + private abiTypeMaps: { + metadataTypes: Map; + concreteTypes: Map; + }, public metadataTypeId: number, public typeParamsArgsMap: Array<[number, ResolvedType | ResolvableType]> | undefined ) { @@ -32,75 +33,12 @@ export class ResolvableType { this.swayType = this.metadataType.type; this.typeParamsArgsMap ??= this.metadataType.typeParameters?.map((tp) => [ tp, - new ResolvableType(this.abi, tp, undefined), + new ResolvableType(this.abiTypeMaps, tp, undefined), ]); - this.components = this.metadataType.components?.map((c) => this.handleComponent(this, c)); - } - - toComponentType(): AbiTypeComponent['type'] { - const result: AbiTypeComponent['type'] = { - swayType: this.swayType, - metadata: { - metadataTypeId: this.metadataTypeId, - }, - }; - - if (this.components) { - result.components = this.components.map((component) => ({ - name: component.name, - type: component.type.toComponentType(), - })); - } - if (this.typeParamsArgsMap) { - result.metadata.typeArguments = this.typeParamsArgsMap.map(([, rt]) => rt.toTypeArgument()); - } - - return result; - } - - toTypeArgument(): AbiTypeArgument { - const result: AbiTypeArgument = { - swayType: this.swayType, - metadata: { - metadataTypeId: this.metadataTypeId, - }, - }; - - if (this.typeParamsArgsMap) { - result.metadata.typeArguments = this.typeParamsArgsMap.map(([, ta]) => ta.toTypeArgument()); - } - - if (this.components) { - result.components = this.components.map((component) => ({ - name: component.name, - type: component.type.toComponentType(), - })); - } - - return result; - } - - toAbiType(): AbiMetadataType { - const result: AbiMetadataType = { - metadataTypeId: this.metadataTypeId, - swayType: this.swayType, - }; - - if (this.components) { - result.components = this.components?.map((component) => ({ - name: component.name, - type: component.type.toComponentType(), - })) as AbiTypeComponent[]; - } - - if (this.typeParamsArgsMap) { - result.typeParameters = this.typeParamsArgsMap.map( - ([, rt]) => rt.toAbiType() as AbiMetadataType - ); - } - - return result; + this.components = this.metadataType.components?.map((c) => + this.createResolvableComponent(this, c) + ); } /** @@ -111,9 +49,8 @@ export class ResolvableType { * @throws If the metadata type can not be found in the ABI. */ private findMetadataType(metadataTypeId: number): AbiMetadataTypeV1 { - const metadataType = this.abi.metadataTypes.find( - (type) => type.metadataTypeId === metadataTypeId - ); + const metadataType = this.abiTypeMaps.metadataTypes.get(metadataTypeId); + if (!metadataType) { throw new FuelError( FuelError.CODES.TYPE_NOT_FOUND, @@ -131,9 +68,8 @@ export class ResolvableType { * @throws If the concrete type can not be found in the ABI. */ private findConcreteType(concreteTypeId: string): AbiConcreteTypeV1 { - const concreteType = this.abi.concreteTypes.find( - (type) => type.concreteTypeId === concreteTypeId - ); + const concreteType = this.abiTypeMaps.concreteTypes.get(concreteTypeId); + if (!concreteType) { throw new FuelError( FuelError.CODES.TYPE_NOT_FOUND, @@ -150,26 +86,24 @@ export class ResolvableType { return metadataType.typeParameters?.map((typeParameter, idx) => [typeParameter, args[idx]]); } - private handleComponent( + private createResolvableComponent( parent: ResolvableType, - component: AbiComponentV1 | AbiTypeArgumentV1 + { typeId, typeArguments, name }: AbiComponentV1 | AbiTypeArgumentV1 ): ResolvableComponent { - const name = (component as AbiComponentV1).name; - - const isConcreteType = typeof component.typeId === 'string'; + const isConcreteType = typeof typeId === 'string'; if (isConcreteType) { - const concreteType = this.findConcreteType(component.typeId); + const concreteType = this.findConcreteType(typeId); return { name, type: this.resolveConcreteType(concreteType), }; } - const metadataType = this.findMetadataType(component.typeId); + const metadataType = this.findMetadataType(typeId); return { name, - type: this.handleMetadataType(parent, metadataType, component.typeArguments), + type: this.handleMetadataType(parent, metadataType, typeArguments), }; } @@ -194,7 +128,7 @@ export class ResolvableType { * This would be the case for e.g. non-generic structs and enums. */ if (!type.typeArguments) { - return new ResolvableType(this.abi, type.metadataTypeId, undefined).resolveInternal( + return new ResolvableType(this.abiTypeMaps, type.metadataTypeId, undefined).resolveInternal( type.concreteTypeId, undefined ); @@ -213,7 +147,7 @@ export class ResolvableType { }); return new ResolvableType( - this.abi, + this.abiTypeMaps, type.metadataTypeId, ResolvableType.mapTypeParametersAndArgs(metadataType, concreteTypeArgs) ).resolveInternal(type.concreteTypeId, undefined); @@ -248,7 +182,7 @@ export class ResolvableType { return ( resolvableTypeParameter ?? - new ResolvableType(this.abi, metadataType.metadataTypeId, undefined) + new ResolvableType(this.abiTypeMaps, metadataType.metadataTypeId, undefined) ); } @@ -258,18 +192,19 @@ export class ResolvableType { * if they aren't used _directly_ in a function-input/function-output/log/configurable/messageType * These types are characterized by not having components and we can resolve them as-is */ - return new ResolvableType(this.abi, metadataType.metadataTypeId, undefined).resolveInternal( + return new ResolvableType( + this.abiTypeMaps, metadataType.metadataTypeId, undefined - ); + ).resolveInternal(metadataType.metadataTypeId, undefined); } const typeArgs = typeArguments?.map( - (typeArgument) => this.handleComponent(parent, typeArgument).type + (typeArgument) => this.createResolvableComponent(parent, typeArgument).type ); const resolvable = new ResolvableType( - this.abi, + this.abiTypeMaps, metadataType.metadataTypeId, !typeArgs?.length ? undefined diff --git a/packages/abi/src/parser/specifications/v1/resolved-type.ts b/packages/abi/src/parser/specifications/v1/resolved-type.ts index 3b74a805ed..45dc800b93 100644 --- a/packages/abi/src/parser/specifications/v1/resolved-type.ts +++ b/packages/abi/src/parser/specifications/v1/resolved-type.ts @@ -1,5 +1,3 @@ -import type { AbiConcreteType, AbiTypeArgument, AbiTypeComponent } from '../../abi'; - import type { AbiMetadataTypeV1 } from './specification'; export interface ResolvedComponent { @@ -12,7 +10,7 @@ export class ResolvedType { public typeId: string | number; public components: ResolvedComponent[] | undefined; public typeParamsArgsMap: Array<[number, ResolvedType]> | undefined; - private metadataType: AbiMetadataTypeV1 | undefined; + public metadataType: AbiMetadataTypeV1 | undefined; constructor(params: { swayType: string; @@ -27,93 +25,4 @@ export class ResolvedType { this.typeParamsArgsMap = params.typeParamsArgsMap; this.metadataType = params.metadataType; } - - public toComponentType(): AbiTypeComponent['type'] { - let result: AbiTypeComponent['type']; - - if (typeof this.typeId === 'string') { - result = { - swayType: this.swayType, - concreteTypeId: this.typeId, - }; - } else { - result = { - swayType: this.swayType, - metadata: { - metadataTypeId: this.typeId, - }, - }; - } - - if (this.metadataType) { - result.metadata = { - metadataTypeId: this.metadataType.metadataTypeId, - }; - if (this.typeParamsArgsMap && this.metadataType?.typeParameters?.length) { - result.metadata.typeArguments = this.typeParamsArgsMap.map((t) => t[1].toTypeArgument()); - } - } - - if (this.components) { - result.components = this.components.map((c) => ({ - name: c.name, - type: c.type.toComponentType(), - })); - } - - return result; - } - - toTypeArgument(): AbiTypeArgument { - if (typeof this.typeId === 'string') { - return this.toAbiType(); - } - - const result: AbiTypeArgument = { - swayType: this.swayType, - metadata: { - metadataTypeId: this.typeId, - }, - }; - - if (this.typeParamsArgsMap) { - result.metadata.typeArguments = this.typeParamsArgsMap.map(([, rt]) => rt.toTypeArgument()); - } - - if (this.components) { - result.components = this.components.map((component) => ({ - name: component.name, - type: component.type.toComponentType(), - })); - } - - return result; - } - - public toAbiType(): AbiConcreteType { - const res: AbiConcreteType = { - concreteTypeId: this.typeId as string, - swayType: this.swayType, - }; - - if (this.metadataType) { - res.metadata = { - metadataTypeId: this.metadataType.metadataTypeId, - }; - if (this.typeParamsArgsMap) { - res.metadata.typeArguments = this.typeParamsArgsMap.map( - (t) => t[1].toAbiType() as AbiConcreteType - ); - } - } - - if (this.components) { - res.components = this.components.map((c) => ({ - name: c.name, - type: c.type.toComponentType(), - })); - } - - return res; - } }