From a27c7bd28be1ba89e37e5836bef4d82b60e4fbe2 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Fri, 13 Dec 2024 17:19:38 +0100 Subject: [PATCH 01/14] feat: ABI parser --- .changeset/config.json | 1 - .changeset/tender-tigers-fry.md | 7 + apps/docs-api/index.md | 3 + apps/docs-api/typedoc.json | 1 + apps/docs/.vitepress/config.ts | 4 + .../cookbook/snippets/parsing-the-abi.ts | 9 + .../guide/cookbook/working-with-the-abi.md | 11 + apps/docs/src/guide/errors/index.md | 6 + internal/check-imports/src/references.ts | 6 + packages/abi/package.json | 3 +- packages/abi/src/index.ts | 1 + packages/abi/src/parse/AbiParser.ts | 1 - .../abi/src/parse/specifications/v1/index.ts | 1 - packages/abi/src/parse/types.ts | 1 - packages/abi/src/parser/abi-parser.ts | 44 + packages/abi/src/parser/abi.ts | 142 ++ packages/abi/src/parser/index.ts | 3 + .../abi/src/parser/specifications/index.ts | 2 + .../parser/specifications/v1/map-attribute.ts | 23 + .../src/parser/specifications/v1/parser.ts | 79 + .../specifications/v1/resolvable-type.ts | 439 +++++ .../parser/specifications/v1/resolved-type.ts | 119 ++ .../parser/specifications/v1/specification.ts | 98 + packages/abi/typedoc.json | 6 + packages/errors/src/error-codes.ts | 1 + packages/fuel-gauge/package.json | 3 +- packages/fuel-gauge/src/abi/abi-parser.json | 1706 +++++++++++++++++ .../fuel-gauge/src/abi/abi-parser.test.ts | 40 + .../test/fixtures/forc-projects/Forc.toml | 1 + .../fixtures/forc-projects/parser/Forc.toml | 4 + .../fixtures/forc-projects/parser/src/main.sw | 64 + packages/fuels/package.json | 1 + packages/fuels/src/index.ts | 1 + pnpm-lock.yaml | 15 +- 34 files changed, 2833 insertions(+), 13 deletions(-) create mode 100644 .changeset/tender-tigers-fry.md create mode 100644 apps/docs/src/guide/cookbook/snippets/parsing-the-abi.ts create mode 100644 apps/docs/src/guide/cookbook/working-with-the-abi.md delete mode 100644 packages/abi/src/parse/AbiParser.ts delete mode 100644 packages/abi/src/parse/specifications/v1/index.ts delete mode 100644 packages/abi/src/parse/types.ts create mode 100644 packages/abi/src/parser/abi-parser.ts create mode 100644 packages/abi/src/parser/abi.ts create mode 100644 packages/abi/src/parser/index.ts create mode 100644 packages/abi/src/parser/specifications/index.ts create mode 100644 packages/abi/src/parser/specifications/v1/map-attribute.ts create mode 100644 packages/abi/src/parser/specifications/v1/parser.ts create mode 100644 packages/abi/src/parser/specifications/v1/resolvable-type.ts create mode 100644 packages/abi/src/parser/specifications/v1/resolved-type.ts create mode 100644 packages/abi/src/parser/specifications/v1/specification.ts create mode 100644 packages/abi/typedoc.json create mode 100644 packages/fuel-gauge/src/abi/abi-parser.json create mode 100644 packages/fuel-gauge/src/abi/abi-parser.test.ts create mode 100644 packages/fuel-gauge/test/fixtures/forc-projects/parser/Forc.toml create mode 100644 packages/fuel-gauge/test/fixtures/forc-projects/parser/src/main.sw diff --git a/.changeset/config.json b/.changeset/config.json index a9ca6cac83c..ea7d2ddbcae 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -7,7 +7,6 @@ "baseBranch": "master", "updateInternalDependencies": "patch", "ignore": [ - "@fuel-ts/abi", "fuel-gauge", "docs", "demo-fuels", diff --git a/.changeset/tender-tigers-fry.md b/.changeset/tender-tigers-fry.md new file mode 100644 index 00000000000..2e270bb05e9 --- /dev/null +++ b/.changeset/tender-tigers-fry.md @@ -0,0 +1,7 @@ +--- +"@fuel-ts/abi": patch +"fuels": patch +"@fuel-ts/errors": patch +--- + +feat: ABI parser diff --git a/apps/docs-api/index.md b/apps/docs-api/index.md index 6c796e4769d..d30de618cac 100644 --- a/apps/docs-api/index.md +++ b/apps/docs-api/index.md @@ -12,6 +12,9 @@ # Modules + + + - [abi-coder](https://fuels-ts-docs-api.vercel.app/modules/_fuel_ts_abi_coder.html) - [abi-typegen](https://fuels-ts-docs-api.vercel.app/modules/_fuel_ts_abi_typegen.html) - [account](https://fuels-ts-docs-api.vercel.app/modules/_fuel_ts_account.html) diff --git a/apps/docs-api/typedoc.json b/apps/docs-api/typedoc.json index 80cfb47a49a..f04aba511f4 100644 --- a/apps/docs-api/typedoc.json +++ b/apps/docs-api/typedoc.json @@ -2,6 +2,7 @@ "$schema": "https://typedoc.org/schema.json", "entryPointStrategy": "packages", "entryPoints": [ + "../../packages/abi", "../../packages/abi-coder", "../../packages/abi-typegen", "../../packages/address", diff --git a/apps/docs/.vitepress/config.ts b/apps/docs/.vitepress/config.ts index f1a007accd1..65599e54783 100644 --- a/apps/docs/.vitepress/config.ts +++ b/apps/docs/.vitepress/config.ts @@ -429,6 +429,10 @@ export default defineConfig({ text: 'Splitting UTXOs', link: '/guide/cookbook/splitting-utxos', }, + { + text: 'Working with the ABI', + link: '/guide/cookbook/working-with-the-abi', + }, ], }, { diff --git a/apps/docs/src/guide/cookbook/snippets/parsing-the-abi.ts b/apps/docs/src/guide/cookbook/snippets/parsing-the-abi.ts new file mode 100644 index 00000000000..8b54490de56 --- /dev/null +++ b/apps/docs/src/guide/cookbook/snippets/parsing-the-abi.ts @@ -0,0 +1,9 @@ +// #region full +import { AbiParser } from 'fuels'; +import type { Abi, AbiSpecificationV1 } from 'fuels'; + +import { Counter } from '../../../typegend'; + +const parsedAbi: Abi = AbiParser.parse(Counter.abi as AbiSpecificationV1); +// #endregion full +console.log('Parsed ABI:', parsedAbi); diff --git a/apps/docs/src/guide/cookbook/working-with-the-abi.md b/apps/docs/src/guide/cookbook/working-with-the-abi.md new file mode 100644 index 00000000000..3824f20c576 --- /dev/null +++ b/apps/docs/src/guide/cookbook/working-with-the-abi.md @@ -0,0 +1,11 @@ +# Working with the ABI + +Building a Sway program with `forc build` outputs multiple files, one of which is a JSON representation of the program's ABI. Because ABI specifications can change from one `forc` version to another, working directly with the ABI is cumbersome due to having to manage all ABI specification versions in order to ensure proper functionality. + + + + + +To mitigate this, The SDK provides [`AbiParser`](#working-with-the-abi) which can parse all ABI specification versions and output an object that conforms to the [`Abi`](#working-with-the-abi) interface. The SDK also internally uses this `Abi` interface for implementing its encoding/decoding and TS type generation. + +<<< @./snippets/parsing-the-abi.ts#full{ts:line-numbers} diff --git a/apps/docs/src/guide/errors/index.md b/apps/docs/src/guide/errors/index.md index c20604f3217..a043c622ca4 100644 --- a/apps/docs/src/guide/errors/index.md +++ b/apps/docs/src/guide/errors/index.md @@ -18,6 +18,12 @@ When the arguments supplied to the function do not match the minimum required in Check that the arguments supplied to the function match the required type. +### `ABI_SPECIFICATION_INVALID` + +When the ABI specification provided is invalid. + +Check that the ABI specification is valid. + ### `ACCOUNT_REQUIRED` When an [`Account`](https://fuels-ts-docs-api.vercel.app/classes/_fuel_ts_account.Account.html) is required for an operation. This will usually be in the form of a [`Wallet`](../wallets/index.md). diff --git a/internal/check-imports/src/references.ts b/internal/check-imports/src/references.ts index aa4fbbe098a..b1a1479a66c 100644 --- a/internal/check-imports/src/references.ts +++ b/internal/check-imports/src/references.ts @@ -37,10 +37,16 @@ import { arrayify, hexlify, createConfig, + AbiParser, } from 'fuels'; const { log } = console; +/** + * abi + */ +log(AbiParser); + /** * abi-coder */ diff --git a/packages/abi/package.json b/packages/abi/package.json index 8d784a019f0..0fd951ba435 100644 --- a/packages/abi/package.json +++ b/packages/abi/package.json @@ -26,7 +26,8 @@ "postbuild": "tsx ../../scripts/postbuild.ts" }, "dependencies": { - "@fuel-ts/errors": "workspace:*" + "@fuel-ts/errors": "workspace:*", + "@fuel-ts/utils": "workspace:*" }, "devDependencies": {} } diff --git a/packages/abi/src/index.ts b/packages/abi/src/index.ts index 22506bf91e0..463cc711a6c 100644 --- a/packages/abi/src/index.ts +++ b/packages/abi/src/index.ts @@ -1,2 +1,3 @@ export * from './coder'; export * from './gen'; +export * from './parser'; diff --git a/packages/abi/src/parse/AbiParser.ts b/packages/abi/src/parse/AbiParser.ts deleted file mode 100644 index 10051c76805..00000000000 --- a/packages/abi/src/parse/AbiParser.ts +++ /dev/null @@ -1 +0,0 @@ -// Placeholder diff --git a/packages/abi/src/parse/specifications/v1/index.ts b/packages/abi/src/parse/specifications/v1/index.ts deleted file mode 100644 index 10051c76805..00000000000 --- a/packages/abi/src/parse/specifications/v1/index.ts +++ /dev/null @@ -1 +0,0 @@ -// Placeholder diff --git a/packages/abi/src/parse/types.ts b/packages/abi/src/parse/types.ts deleted file mode 100644 index 10051c76805..00000000000 --- a/packages/abi/src/parse/types.ts +++ /dev/null @@ -1 +0,0 @@ -// Placeholder diff --git a/packages/abi/src/parser/abi-parser.ts b/packages/abi/src/parser/abi-parser.ts new file mode 100644 index 00000000000..31d230ce3cb --- /dev/null +++ b/packages/abi/src/parser/abi-parser.ts @@ -0,0 +1,44 @@ +import { FuelError } from '@fuel-ts/errors'; + +import type { Abi } from './abi'; +import type { AbiSpecificationV1 } from './specifications'; +import { AbiParserV1 } from './specifications'; + +/** + * A typed ABI object or a stringified json of a Sway program's ABI + */ +export type AbiSpecification = AbiSpecificationV1; + +export class AbiParser { + /** + * ABI specifications transpilers + */ + private static specifications = { + '1': AbiParserV1.parse, + } as const; + + /** + * Parses an ABI in JSON format. + * + * @param abi - a JSON ABI of a Sway program + * @returns an public interface for the Abi + */ + static parse(abi: AbiSpecification): Abi { + if (typeof abi.specVersion !== 'string') { + throw new FuelError( + FuelError.CODES.ABI_SPECIFICATION_INVALID, + 'Invalid ABI: the specification version is not a string.' + ); + } + + const parse = AbiParser.specifications[abi.specVersion]; + if (!parse) { + throw new FuelError( + FuelError.CODES.ABI_SPECIFICATION_INVALID, + `Invalid ABI: Unsupported ABI specification version ("${abi.specVersion}").` + ); + } + + return parse(abi); + } +} diff --git a/packages/abi/src/parser/abi.ts b/packages/abi/src/parser/abi.ts new file mode 100644 index 00000000000..345851ec354 --- /dev/null +++ b/packages/abi/src/parser/abi.ts @@ -0,0 +1,142 @@ +/** + * This interface serves as a representation of the ABI format outputted by `forc build` + * that won't be changing with the introduction of new abi specifications in Sway. + * Its purpose is to provide a stable interface for users to work with, + * which won't be affected by changing ABI specification versions. + */ +export interface Abi { + encodingVersion: string; + programType: 'contract' | 'predicate' | 'script' | 'library'; + /** + * Metadata types describe the structure of the types used in the `concreteTypes` field. + * One metadata type can be referenced multiple times if it is used in multiple concrete types. + */ + metadataTypes: AbiMetadataType[]; + /** + * Concrete types are types that are used in: + * function inputs/outputs, configurables, logged types, or message types. + * + * Their structure is fully known and they do not contain any unresolved generic parameters. + */ + concreteTypes: AbiConcreteType[]; + functions: AbiFunction[]; + loggedTypes: AbiLoggedType[]; + messageTypes: AbiMessageType[]; + configurables: AbiConfigurable[]; +} + +export interface AbiConcreteType { + swayType: string; + concreteTypeId: string; + /** + * The components field is populated when the type is any non-primitive type. + * That includes structs, enums, arrays, and tuples. + */ + components?: AbiTypeComponent[]; + /** + * A concrete type can be an implementation of a metadata type, + * in which case the `metadata` field is populated. + * If the underlying metadata type has type parameters (is generic), + * the `typeArguments` field corresponds to those type parameters. + */ + metadata?: { + metadataTypeId: number; + /** + * Type arguments used to resolve the type parameters in the metadata type. + * They are ordered in the same way as the type parameters in the metadata type. + */ + typeArguments?: AbiConcreteType[]; + }; +} + +export interface AbiMetadataType { + swayType: string; + metadataTypeId: number; + /** + * The components field is populated when the type is any non-primitive type. + * That includes structs, enums, arrays, and tuples. + */ + components?: AbiTypeComponent[]; + /** + * The existence of type parameters indicates that the metadata type is generic. + */ + typeParameters?: AbiMetadataType[]; +} + +export interface AbiTypeComponent { + name: string; + type: AbiConcreteType | AbiAppliedMetadataType; +} + +/** + * AbiAppliedMetadataType point to a metadata type but aren't the same as metadata types, + * as the metadata type describes the structure of the type, + * whereas the component is an actual implementation of that type. + */ +export interface AbiAppliedMetadataType { + swayType: string; + components?: AbiTypeComponent[]; + metadata: { + metadataTypeId: number; + typeArguments?: AbiTypeArgument[]; + }; +} + +export type AbiTypeArgument = AbiConcreteType | AbiAppliedMetadataType; + +export interface AbiFunctionInput { + name: string; + type: AbiConcreteType; +} + +export interface AbiFunction { + name: string; + inputs: AbiFunctionInput[]; + output: AbiConcreteType; + attributes?: readonly AbiFunctionAttribute[]; +} + +export interface AbiLoggedType { + logId: string; + type: AbiConcreteType; +} + +export interface AbiMessageType { + messageId: string; + type: AbiConcreteType; +} + +export interface AbiConfigurable { + name: string; + offset: number; + type: AbiConcreteType; +} + +export type AbiFunctionAttribute = + | StorageAttr + | PayableAttr + | TestAttr + | InlineAttr + | DocCommentAttr; + +export interface PayableAttr { + readonly name: 'payable'; +} + +export interface StorageAttr { + readonly name: 'storage'; + readonly arguments: readonly ('read' | 'write')[]; +} + +export interface TestAttr { + readonly name: 'test'; +} +export interface InlineAttr { + readonly name: 'inline'; + readonly arguments: 'never' | 'always'; +} + +export interface DocCommentAttr { + readonly name: 'doc-comment'; + readonly arguments: readonly string[]; +} diff --git a/packages/abi/src/parser/index.ts b/packages/abi/src/parser/index.ts new file mode 100644 index 00000000000..053e0c0a003 --- /dev/null +++ b/packages/abi/src/parser/index.ts @@ -0,0 +1,3 @@ +export { AbiParser, type AbiSpecification } from './abi-parser'; +export * from './abi'; +export * from './specifications/v1/specification'; diff --git a/packages/abi/src/parser/specifications/index.ts b/packages/abi/src/parser/specifications/index.ts new file mode 100644 index 00000000000..788ae99a778 --- /dev/null +++ b/packages/abi/src/parser/specifications/index.ts @@ -0,0 +1,2 @@ +export { AbiParserV1 } from './v1/parser'; +export * from './v1/specification'; diff --git a/packages/abi/src/parser/specifications/v1/map-attribute.ts b/packages/abi/src/parser/specifications/v1/map-attribute.ts new file mode 100644 index 00000000000..b14ea2e83c0 --- /dev/null +++ b/packages/abi/src/parser/specifications/v1/map-attribute.ts @@ -0,0 +1,23 @@ +import { assertUnreachable } from '@fuel-ts/utils'; + +import type { AbiFunctionAttribute } from '../../abi'; + +import type { AbiFunctionAttributeV1 } from './specification'; + +export const mapAttribute = (attribute: AbiFunctionAttributeV1): AbiFunctionAttribute => { + const { name, arguments: args } = attribute; + + switch (name) { + case 'inline': + return { name, arguments: args[0] }; + case 'storage': + return { name: 'storage', arguments: args }; + case 'doc-comment': + return { name, arguments: args }; + case 'payable': + case 'test': + return { name }; + default: + return assertUnreachable(attribute); + } +}; diff --git a/packages/abi/src/parser/specifications/v1/parser.ts b/packages/abi/src/parser/specifications/v1/parser.ts new file mode 100644 index 00000000000..49453b210bd --- /dev/null +++ b/packages/abi/src/parser/specifications/v1/parser.ts @@ -0,0 +1,79 @@ +import { FuelError } from '@fuel-ts/errors'; + +import type { Abi, AbiConcreteType, AbiMetadataType } from '../../abi'; + +import { mapAttribute } from './map-attribute'; +import { ResolvableType } from './resolvable-type'; +import { ResolvedType } from './resolved-type'; +import type { + AbiConfigurableV1, + AbiFunctionInputV1, + AbiFunctionV1, + AbiLoggedTypeV1, + AbiMessageTypeV1, + AbiSpecificationV1, +} from './specification'; + +export class AbiParserV1 { + static parse(abi: AbiSpecificationV1): Abi { + const resolvableTypes = abi.metadataTypes + .map((metadataType) => new ResolvableType(abi, metadataType.metadataTypeId, undefined)) + .filter( + (resolveableType) => + resolveableType.swayType !== 'struct std::vec::RawVec' && + resolveableType.swayType !== 'struct std::bytes::RawBytes' + ); + + const concreteTypes = abi.concreteTypes.map((concreteType) => { + const resolvableType = resolvableTypes.find( + (resolvable) => resolvable.metadataTypeId === concreteType.metadataTypeId + ); + + const resolvedType = resolvableType + ? resolvableType.resolve(concreteType) + : new ResolvedType({ swayType: concreteType.type, typeId: concreteType.concreteTypeId }); + + return resolvedType.toAbiType() as AbiConcreteType; + }); + + const getType = (concreteTypeId: string) => { + const type = concreteTypes.find((abiType) => abiType.concreteTypeId === concreteTypeId); + if (type === undefined) { + throw new FuelError( + FuelError.CODES.TYPE_ID_NOT_FOUND, + `A type with concrete type id of "${concreteTypeId}" was not found.` + ); + } + return type; + }; + + return { + metadataTypes: resolvableTypes.map((rt) => rt.toAbiType() as AbiMetadataType), + concreteTypes, + encodingVersion: abi.encodingVersion, + programType: abi.programType as Abi['programType'], + functions: abi.functions.map((fn: AbiFunctionV1) => ({ + attributes: fn.attributes?.map(mapAttribute) ?? undefined, + name: fn.name, + output: getType(fn.output), + inputs: fn.inputs.map((input: AbiFunctionInputV1) => ({ + name: input.name, + type: getType(input.concreteTypeId), + })), + })), + loggedTypes: abi.loggedTypes.map((loggedType: AbiLoggedTypeV1) => ({ + logId: loggedType.logId, + type: getType(loggedType.concreteTypeId), + })), + messageTypes: abi.messagesTypes.map((messageType: AbiMessageTypeV1) => ({ + messageId: messageType.messageId, + type: getType(messageType.concreteTypeId), + })), + configurables: abi.configurables.map((configurable: AbiConfigurableV1) => ({ + name: configurable.name, + offset: configurable.offset, + type: getType(configurable.concreteTypeId), + })), + }; + } +} diff --git a/packages/abi/src/parser/specifications/v1/resolvable-type.ts b/packages/abi/src/parser/specifications/v1/resolvable-type.ts new file mode 100644 index 00000000000..f1841ac8475 --- /dev/null +++ b/packages/abi/src/parser/specifications/v1/resolvable-type.ts @@ -0,0 +1,439 @@ +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'; +import type { + AbiComponentV1, + AbiConcreteTypeV1, + AbiMetadataTypeV1, + AbiSpecificationV1, + AbiTypeArgumentV1, +} from './specification'; + +interface ResolvableComponent { + name: string; + type: ResolvableType | ResolvedType; +} + +export class ResolvableType { + private metadataType: AbiMetadataTypeV1; + swayType: string; + components: ResolvableComponent[] | undefined; + + constructor( + private abi: AbiSpecificationV1, + public metadataTypeId: number, + public typeParamsArgsMap: Array<[number, ResolvedType | ResolvableType]> | undefined + ) { + this.metadataType = this.findMetadataType(metadataTypeId); + this.swayType = this.metadataType.type; + this.typeParamsArgsMap ??= this.metadataType.typeParameters?.map((tp) => [ + tp, + new ResolvableType(this.abi, tp, undefined), + ]); + + let components = this.metadataType.components; + + /** + * Vectors consist of multiple components, + * 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). + */ + if (swayTypeMatchers.vector(this.metadataType.type)) { + components = components + ?.map((component) => (component.name === 'buf' ? component.typeArguments?.[0] : undefined)) + .filter((x) => x !== undefined) as AbiComponentV1[]; + } + + this.components = 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; + } + + /** + * Find a metadata type by its ID. + * @param metadataTypeId - The ID of the metadata type to find. + * @returns The metadata type. + * + * @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 + ); + if (!metadataType) { + throw new FuelError( + FuelError.CODES.TYPE_NOT_FOUND, + `Metadata type with id ${metadataTypeId} not found` + ); + } + return metadataType; + } + + /** + * Find a concrete type by its ID. + * @param concreteTypeId - The ID of the concrete type to find. + * @returns The concrete type. + * + * @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 + ); + if (!concreteType) { + throw new FuelError( + FuelError.CODES.TYPE_NOT_FOUND, + `Concrete type with id ${concreteTypeId} not found` + ); + } + return concreteType; + } + + private static mapTypeParametersAndArgs( + metadataType: AbiMetadataTypeV1, + args: (ResolvableType | ResolvedType)[] + ): Array<[number, ResolvedType | ResolvableType]> | undefined { + return metadataType.typeParameters?.map((typeParameter, idx) => [typeParameter, args[idx]]); + } + + private handleComponent( + parent: ResolvableType, + component: AbiComponentV1 | AbiTypeArgumentV1 + ): ResolvableComponent { + const name = (component as AbiComponentV1).name; + + const isConcreteType = typeof component.typeId === 'string'; + + if (isConcreteType) { + const concreteType = this.findConcreteType(component.typeId); + return { + name, + type: this.resolveConcreteType(concreteType), + }; + } + + const metadataType = this.findMetadataType(component.typeId); + return { + name, + type: this.handleMetadataType(parent, metadataType, component.typeArguments), + }; + } + + /** + * Concrete types are *resolved* because everything is known about them. + */ + private resolveConcreteType(type: AbiConcreteTypeV1): ResolvedType { + /** + * If the concrete type doesn't have a linked metadata type, we can resolve it immediately. + * This is the case for e.g. u8, u16, ... + */ + if (type.metadataTypeId === undefined) { + return new ResolvedType({ + swayType: type.type, + typeId: type.concreteTypeId, + }); + } + /** + * The concrete type has an associated metadata type. + * If it's not generic (no type arguments), + * we'll create a ResolvableType with that metadata type, and then resolve it immediately. + * 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( + type.concreteTypeId, + undefined + ); + } + + /** + * The concrete type's underlying metadata type is generic. + * We must resolve all its type parameters with the provided type arguments of the concrete type, + * and then resolve the metadata type itself. + */ + const metadataType = this.findMetadataType(type.metadataTypeId); + + const concreteTypeArgs = type.typeArguments.map((typeArgument) => { + const concreteTypeArg = this.findConcreteType(typeArgument); + return this.resolveConcreteType(concreteTypeArg); + }); + + return new ResolvableType( + this.abi, + type.metadataTypeId, + ResolvableType.mapTypeParametersAndArgs(metadataType, concreteTypeArgs) + ).resolveInternal(type.concreteTypeId, undefined); + } + + /** + * Metadata types are *handled* and not *resolved* because they might be generic, + * in which case they cannot be resolved. + * If they're not generic, they can be immediately resolved. + */ + private handleMetadataType( + parent: ResolvableType, + metadataType: AbiMetadataTypeV1, + typeArguments: AbiComponentV1['typeArguments'] + ): ResolvableType | ResolvedType { + /** + * If the type is generic, we can't resolve it and thus we create a `ResolvableType` from it. + * This propagates to the parent type, forcing it to be a `ResolvableType` as well, + * as it can't be resolved until this generic type is substituted with a type argument. + */ + if (swayTypeMatchers.generic(metadataType.type)) { + /** + * This search solves the case where an e.g. `generic T` is being substituted by `generic E`. + * This can happen when a generic type is nested in another generic type and they have differently-named type parameters. + * e.g. `GenericStruct` is nested in `Vec`: `struct MyStruct { a: Vec }` + * We check in the parent's typeParamsArgsMap if the metadata type we're solving for + * has been substituted with a different generic type, and then we use that generic type. + */ + const resolvableTypeParameter = parent.typeParamsArgsMap?.find( + ([typeParameterId]) => typeParameterId === metadataType.metadataTypeId + )?.[1]; + + return ( + resolvableTypeParameter ?? + new ResolvableType(this.abi, metadataType.metadataTypeId, undefined) + ); + } + + if (!metadataType.components) { + /** + * types like u8, u16 can make their way into metadata types + * 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( + metadataType.metadataTypeId, + undefined + ); + } + + const typeArgs = typeArguments?.map( + (typeArgument) => this.handleComponent(parent, typeArgument).type + ); + + const resolvable = new ResolvableType( + this.abi, + metadataType.metadataTypeId, + !typeArgs?.length + ? undefined + : ResolvableType.mapTypeParametersAndArgs(metadataType, typeArgs) + ); + + /** + * If any component is unresolved, this means that the metadata type is generic. + * We can't resolve it yet, so we return the resolvable type. + * If all components are resolved, we can resolve the metadata type immediately. + */ + const isGeneric = resolvable.components?.some( + (component) => component.type instanceof ResolvableType + ); + + return isGeneric + ? resolvable + : resolvable.resolveInternal(metadataType.metadataTypeId, undefined); + } + + private resolveInternal( + typeId: string | number, + typeParamsArgsMap: Array<[number, ResolvedType]> | undefined + ): ResolvedType { + const resolvedType = new ResolvedType({ + swayType: this.swayType, + typeId, + metadataType: this.metadataType, + }); + + /** + * A type without components can be immediately resolved. + */ + if (!this.components) { + return resolvedType; + } + + /** + * Before resolving the components, + * we need to substitute the type parameters of the underlying metadata type + * with the type arguments of the concrete type, + * so that we can substitute the generic components with them later. + */ + const typeArgs = this.resolveTypeArgs(typeParamsArgsMap); + + const components: ResolvedComponent[] = this.components.map((component) => { + const { name, type } = component; + + if (type instanceof ResolvedType) { + return component as ResolvedComponent; + } + + /** + * Here the component's type is a `ResolvableType`. + * If the component is a generic type parameter itself, + * its corresponding type argument will be found in the typeArgs, + * which will be used to substitute the component with. + */ + const resolvedGenericType = typeArgs?.find( + ([typeParameterId]) => type.metadataTypeId === typeParameterId + )?.[1]; + + if (resolvedGenericType) { + return { + name, + type: resolvedGenericType, + }; + } + + return { + name, + /** + * The component is a `ResolvableType`, but it's not a generic type parameter itself. + * This means that one of its components (or component's components) + * is a generic type. + * We need to resolve that first before resolving the component. + * + * Note that we are passing in the original `typeParamsArgsMap` by default, + * which will be used to substitute the component's generic type parameters + * with the appropriate type arguments. + * + * The non-default case of passing `typeArgs` happens only for tuples/arrays + * which contain structs with implicit generics, + * e.g. `(bool, StructWithImplicitGenerics)` + */ + type: type.resolveInternal(type.metadataTypeId, typeParamsArgsMap ?? typeArgs), + }; + }); + + resolvedType.components = components; + resolvedType.typeParamsArgsMap = typeArgs; + + return resolvedType; + } + + private resolveTypeArgs( + typeParamsArgsMap: Array<[number, ResolvedType]> | undefined + ): [number, ResolvedType][] | undefined { + /** + * This case only happens when the metadata type is *implicitly* generic. + * The type itself doesn't have any type parameters that should be resolved, + * but its components are still generic types. + * This happens in the following type: + * `struct StructWithImplicitGenerics { a: [E; 3], b: (E, F)}`. + */ + if (this.typeParamsArgsMap === undefined) { + return typeParamsArgsMap; + } + + /** + * We resolve the type parameters of the underlying metadata type + * with the type arguments of the concrete type. + */ + return this.typeParamsArgsMap.map(([tp, value]) => { + /** + * Some type parameters can already be resolved + * e.g. `struct MyStruct { a: DoubleGeneric }` + * where the second type parameter of DoubleGeneric is already known. + */ + if (value instanceof ResolvedType) { + return [tp, value]; + } + + const resolved = typeParamsArgsMap?.find( + ([typeParameterId]) => typeParameterId === value.metadataTypeId + ); + + /** + * The type parameter is either directly substituted with a type argument, + * or it's metadata type which accepts the type argument, + * so that metadata type needs to be resolved first. + */ + return resolved ?? [tp, value.resolveInternal(value.metadataTypeId, typeParamsArgsMap)]; + }); + } + + public resolve(concreteType: AbiConcreteTypeV1) { + const concreteTypeArgs = concreteType.typeArguments?.map((typeArgument) => { + const concreteTypeArg = this.findConcreteType(typeArgument); + return this.resolveConcreteType(concreteTypeArg); + }); + + const typeParamsArgsMap = concreteTypeArgs + ? (ResolvableType.mapTypeParametersAndArgs(this.metadataType, concreteTypeArgs) as Array< + [number, ResolvedType] + >) + : undefined; + + return this.resolveInternal(concreteType.concreteTypeId, typeParamsArgsMap); + } +} diff --git a/packages/abi/src/parser/specifications/v1/resolved-type.ts b/packages/abi/src/parser/specifications/v1/resolved-type.ts new file mode 100644 index 00000000000..3b74a805ed5 --- /dev/null +++ b/packages/abi/src/parser/specifications/v1/resolved-type.ts @@ -0,0 +1,119 @@ +import type { AbiConcreteType, AbiTypeArgument, AbiTypeComponent } from '../../abi'; + +import type { AbiMetadataTypeV1 } from './specification'; + +export interface ResolvedComponent { + name: string; + type: ResolvedType; +} + +export class ResolvedType { + public swayType: string; + public typeId: string | number; + public components: ResolvedComponent[] | undefined; + public typeParamsArgsMap: Array<[number, ResolvedType]> | undefined; + private metadataType: AbiMetadataTypeV1 | undefined; + + constructor(params: { + swayType: string; + typeId: string | number; + components?: ResolvedComponent[]; + typeParamsArgsMap?: Array<[number, ResolvedType]>; + metadataType?: AbiMetadataTypeV1; + }) { + this.swayType = params.swayType; + this.typeId = params.typeId; + this.components = params.components; + 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; + } +} diff --git a/packages/abi/src/parser/specifications/v1/specification.ts b/packages/abi/src/parser/specifications/v1/specification.ts new file mode 100644 index 00000000000..f360fbba8bb --- /dev/null +++ b/packages/abi/src/parser/specifications/v1/specification.ts @@ -0,0 +1,98 @@ +/** + * Types for Fuel JSON ABI Format specification v1, as defined on: + * https://github.com/FuelLabs/fuel-specs/blob/master/src/abi/json-abi-format.md + */ +export interface AbiSpecificationV1 { + readonly specVersion: '1'; + readonly encodingVersion: string; + readonly programType: string; + readonly concreteTypes: readonly AbiConcreteTypeV1[]; + readonly metadataTypes: readonly AbiMetadataTypeV1[]; + readonly functions: readonly AbiFunctionV1[]; + readonly loggedTypes: readonly AbiLoggedTypeV1[]; + readonly messagesTypes: readonly AbiMessageTypeV1[]; + readonly configurables: readonly AbiConfigurableV1[]; +} + +export interface AbiConcreteTypeV1 { + readonly type: string; + readonly concreteTypeId: string; + readonly metadataTypeId?: number; + readonly typeArguments?: readonly string[]; +} + +export interface AbiMetadataTypeV1 { + readonly type: string; + readonly metadataTypeId: number; + readonly components?: readonly AbiComponentV1[]; + readonly typeParameters?: readonly number[]; +} + +export interface AbiComponentV1 extends AbiTypeArgumentV1 {} + +export interface AbiTypeArgumentV1 { + readonly name: string; + readonly typeId: number | string; // the type metadata declaration ID or type concrete declaration hash based ID of the type of the component. + readonly typeArguments?: readonly AbiTypeArgumentV1[]; +} + +export interface AbiFunctionV1 { + readonly name: string; + readonly inputs: readonly AbiFunctionInputV1[]; + readonly output: string; + readonly attributes: readonly AbiFunctionAttributeV1[] | null; +} + +export interface AbiFunctionInputV1 { + readonly name: string; + readonly concreteTypeId: string; +} + +export interface AbiLoggedTypeV1 { + readonly logId: string; + // the _type concrete declaration_ hash based ID of the value being logged. + readonly concreteTypeId: string; +} + +export interface AbiMessageTypeV1 { + readonly messageId: string; + readonly concreteTypeId: string; +} + +export interface AbiConfigurableV1 { + readonly name: string; + readonly concreteTypeId: string; + readonly offset: number; +} + +export type AbiFunctionAttributeV1 = + | StorageAttrV1 + | PayableAttrV1 + | TestAttrV1 + | InlineAttrV1 + | DocCommentAttrV1; + +export interface PayableAttrV1 { + readonly name: 'payable'; + readonly arguments: readonly []; +} + +export interface StorageAttrV1 { + readonly name: 'storage'; + readonly arguments: readonly ('read' | 'write')[]; +} + +export interface TestAttrV1 { + readonly name: 'test'; + readonly arguments: readonly []; +} + +export interface InlineAttrV1 { + readonly name: 'inline'; + readonly arguments: readonly ['never'] | readonly ['always']; +} + +export interface DocCommentAttrV1 { + readonly name: 'doc-comment'; + readonly arguments: string[]; +} diff --git a/packages/abi/typedoc.json b/packages/abi/typedoc.json new file mode 100644 index 00000000000..a8ec6b825f0 --- /dev/null +++ b/packages/abi/typedoc.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://typedoc.org/schema.json", + "extends": ["../../typedoc.base.json"], + "entryPoints": ["src/index.ts"], + "readme": "none" +} diff --git a/packages/errors/src/error-codes.ts b/packages/errors/src/error-codes.ts index e7eda6f1f50..18227351503 100644 --- a/packages/errors/src/error-codes.ts +++ b/packages/errors/src/error-codes.ts @@ -13,6 +13,7 @@ export enum ErrorCode { TYPE_NOT_SUPPORTED = 'type-not-supported', INVALID_DECODE_VALUE = 'invalid-decode-value', JSON_ABI_ERROR = 'json-abi-error', + ABI_SPECIFICATION_INVALID = 'abi-specification-invalid', TYPE_ID_NOT_FOUND = 'type-id-not-found', BIN_FILE_NOT_FOUND = 'bin-file-not-found', CODER_NOT_FOUND = 'coder-not-found', diff --git a/packages/fuel-gauge/package.json b/packages/fuel-gauge/package.json index 68460c1b76a..740c95be9a7 100644 --- a/packages/fuel-gauge/package.json +++ b/packages/fuel-gauge/package.json @@ -12,8 +12,7 @@ }, "license": "Apache-2.0", "dependencies": { - "fuels": "workspace:*", - "@fuel-ts/abi": "workspace:*" + "fuels": "workspace:*" }, "devDependencies": { "@fuel-ts/account": "workspace:*", diff --git a/packages/fuel-gauge/src/abi/abi-parser.json b/packages/fuel-gauge/src/abi/abi-parser.json new file mode 100644 index 00000000000..676d2904955 --- /dev/null +++ b/packages/fuel-gauge/src/abi/abi-parser.json @@ -0,0 +1,1706 @@ +{ + "metadataTypes": [ + { + "metadataTypeId": 0, + "swayType": "(_, _)", + "components": [ + { + "name": "__tuple_element", + "type": { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + }, + { + "name": "__tuple_element", + "type": { + "swayType": "generic F", + "metadata": { + "metadataTypeId": 5 + } + } + } + ] + }, + { + "metadataTypeId": 1, + "swayType": "(_, _)", + "components": [ + { + "name": "__tuple_element", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "__tuple_element", + "type": { + "swayType": "struct StructWithImplicitGenerics", + "metadata": { + "metadataTypeId": 12, + "typeArguments": [ + { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "swayType": "bool" + }, + { + "swayType": "b256", + "metadata": { + "metadataTypeId": 3 + } + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "[_; 3]", + "metadata": { + "metadataTypeId": 2 + }, + "components": [ + { + "name": "__array_element", + "type": { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + } + ] + } + }, + { + "name": "b", + "type": { + "swayType": "(_, _)", + "metadata": { + "metadataTypeId": 0 + }, + "components": [ + { + "name": "__tuple_element", + "type": { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + }, + { + "name": "__tuple_element", + "type": { + "swayType": "generic F", + "metadata": { + "metadataTypeId": 5 + } + } + } + ] + } + } + ] + } + } + ] + }, + { + "metadataTypeId": 2, + "swayType": "[_; 3]", + "components": [ + { + "name": "__array_element", + "type": { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + } + ] + }, + { + "metadataTypeId": 3, + "swayType": "b256" + }, + { + "metadataTypeId": 4, + "swayType": "generic E" + }, + { + "metadataTypeId": 5, + "swayType": "generic F" + }, + { + "metadataTypeId": 6, + "swayType": "generic T" + }, + { + "metadataTypeId": 7, + "swayType": "raw untyped ptr" + }, + { + "metadataTypeId": 8, + "swayType": "struct DoubleGeneric", + "components": [ + { + "name": "a", + "type": { + "swayType": "generic T", + "metadata": { + "metadataTypeId": 6 + } + } + }, + { + "name": "b", + "type": { + "swayType": "generic F", + "metadata": { + "metadataTypeId": 5 + } + } + } + ], + "typeParameters": [ + { + "metadataTypeId": 6, + "swayType": "generic T" + }, + { + "metadataTypeId": 5, + "swayType": "generic F" + } + ] + }, + { + "metadataTypeId": 9, + "swayType": "struct GenericStruct", + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "generic T", + "metadata": { + "metadataTypeId": 6 + } + } + } + ], + "typeParameters": [ + { + "metadataTypeId": 6, + "swayType": "generic T" + } + ] + }, + { + "metadataTypeId": 10, + "swayType": "struct NestedGenericStruct", + "components": [ + { + "name": "a", + "type": { + "swayType": "struct std::vec::Vec", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + } + ] + } + } + ] + } + }, + { + "name": "b", + "type": { + "swayType": "struct std::vec::Vec", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + } + ] + } + }, + { + "name": "c", + "type": { + "swayType": "struct DoubleGeneric", + "metadata": { + "metadataTypeId": 8, + "typeArguments": [ + { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + }, + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + }, + { + "name": "b", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + } + ], + "typeParameters": [ + { + "metadataTypeId": 4, + "swayType": "generic E" + } + ] + }, + { + "metadataTypeId": 11, + "swayType": "struct SimpleStruct", + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + }, + { + "metadataTypeId": 12, + "swayType": "struct StructWithImplicitGenerics", + "components": [ + { + "name": "a", + "type": { + "swayType": "[_; 3]", + "metadata": { + "metadataTypeId": 2 + }, + "components": [ + { + "name": "__array_element", + "type": { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + } + ] + } + }, + { + "name": "b", + "type": { + "swayType": "(_, _)", + "metadata": { + "metadataTypeId": 0 + }, + "components": [ + { + "name": "__tuple_element", + "type": { + "swayType": "generic E", + "metadata": { + "metadataTypeId": 4 + } + } + }, + { + "name": "__tuple_element", + "type": { + "swayType": "generic F", + "metadata": { + "metadataTypeId": 5 + } + } + } + ] + } + } + ], + "typeParameters": [ + { + "metadataTypeId": 4, + "swayType": "generic E" + }, + { + "metadataTypeId": 5, + "swayType": "generic F" + } + ] + }, + { + "metadataTypeId": 14, + "swayType": "struct std::vec::Vec", + "components": [ + { + "name": "", + "type": { + "swayType": "generic T", + "metadata": { + "metadataTypeId": 6 + } + } + } + ], + "typeParameters": [ + { + "metadataTypeId": 6, + "swayType": "generic T" + } + ] + }, + { + "metadataTypeId": 15, + "swayType": "u32" + }, + { + "metadataTypeId": 16, + "swayType": "u64" + } + ], + "concreteTypes": [ + { + "concreteTypeId": "9dc54ad1b685b6abf04fbcc93696e440452944466e2dfd64b5df956a13ad2027", + "swayType": "(_, _)", + "metadata": { + "metadataTypeId": 1 + }, + "components": [ + { + "name": "__tuple_element", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "__tuple_element", + "type": { + "swayType": "struct StructWithImplicitGenerics", + "metadata": { + "metadataTypeId": 12, + "typeArguments": [ + { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "swayType": "bool" + }, + { + "swayType": "b256", + "metadata": { + "metadataTypeId": 3 + } + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "[_; 3]", + "metadata": { + "metadataTypeId": 2 + }, + "components": [ + { + "name": "__array_element", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + }, + { + "name": "b", + "type": { + "swayType": "(_, _)", + "metadata": { + "metadataTypeId": 0 + }, + "components": [ + { + "name": "__tuple_element", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "__tuple_element", + "type": { + "swayType": "b256", + "metadata": { + "metadataTypeId": 3 + } + } + } + ] + } + } + ] + } + } + ] + }, + { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "swayType": "bool" + }, + { + "concreteTypeId": "8fa64aacdb756049c3c90d3a5fa8d1a7ebedefc8dc2f347e67bb26ef4c7140a7", + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "688b6ed7fc2c45e135ea9a2ce11e3f5313a4c057ba5d616e3381937605ea81e4", + "swayType": "struct std::vec::Vec", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "swayType": "struct SimpleStruct", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct SimpleStruct", + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + } + ] + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "struct std::vec::Vec", + "concreteTypeId": "688b6ed7fc2c45e135ea9a2ce11e3f5313a4c057ba5d616e3381937605ea81e4", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "swayType": "struct SimpleStruct", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct SimpleStruct", + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + } + ] + } + } + ] + }, + { + "concreteTypeId": "cc3087210794115a9b7e470ad5b5d554808a3a88aa003ae80bae7b0dc4505f50", + "swayType": "struct NestedGenericStruct", + "metadata": { + "metadataTypeId": 10, + "typeArguments": [ + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "struct std::vec::Vec", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + } + ] + } + } + ] + } + }, + { + "name": "b", + "type": { + "swayType": "struct std::vec::Vec", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + } + ] + } + }, + { + "name": "c", + "type": { + "swayType": "struct DoubleGeneric", + "metadata": { + "metadataTypeId": 8, + "typeArguments": [ + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + }, + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + }, + { + "name": "b", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + } + ] + }, + { + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "swayType": "struct SimpleStruct", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + }, + { + "concreteTypeId": "426345c4ea93db9f08eeb9fe6047ef0273294bfb1140600a0660be9f2a08d750", + "swayType": "struct StructWithImplicitGenerics", + "metadata": { + "metadataTypeId": 12, + "typeArguments": [ + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + }, + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "[_; 3]", + "metadata": { + "metadataTypeId": 2 + }, + "components": [ + { + "name": "__array_element", + "type": { + "swayType": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + } + ] + } + }, + { + "name": "b", + "type": { + "swayType": "(_, _)", + "metadata": { + "metadataTypeId": 0 + }, + "components": [ + { + "name": "__tuple_element", + "type": { + "swayType": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + }, + { + "name": "__tuple_element", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + } + ] + }, + { + "concreteTypeId": "688b6ed7fc2c45e135ea9a2ce11e3f5313a4c057ba5d616e3381937605ea81e4", + "swayType": "struct std::vec::Vec", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "swayType": "struct SimpleStruct", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct SimpleStruct", + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + } + ] + }, + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + }, + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + } + ], + "encodingVersion": "1", + "programType": "contract", + "functions": [ + { + "name": "generic_structs", + "output": { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "swayType": "bool" + }, + "inputs": [ + { + "name": "arg1", + "type": { + "concreteTypeId": "8fa64aacdb756049c3c90d3a5fa8d1a7ebedefc8dc2f347e67bb26ef4c7140a7", + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "688b6ed7fc2c45e135ea9a2ce11e3f5313a4c057ba5d616e3381937605ea81e4", + "swayType": "struct std::vec::Vec", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "swayType": "struct SimpleStruct", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct SimpleStruct", + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + } + ] + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "struct std::vec::Vec", + "concreteTypeId": "688b6ed7fc2c45e135ea9a2ce11e3f5313a4c057ba5d616e3381937605ea81e4", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "swayType": "struct SimpleStruct", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct SimpleStruct", + "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", + "metadata": { + "metadataTypeId": 11 + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + } + ] + } + } + ] + } + }, + { + "name": "arg2", + "type": { + "concreteTypeId": "cc3087210794115a9b7e470ad5b5d554808a3a88aa003ae80bae7b0dc4505f50", + "swayType": "struct NestedGenericStruct", + "metadata": { + "metadataTypeId": 10, + "typeArguments": [ + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "struct std::vec::Vec", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + } + ] + } + } + ] + } + }, + { + "name": "b", + "type": { + "swayType": "struct std::vec::Vec", + "metadata": { + "metadataTypeId": 14, + "typeArguments": [ + { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + ] + }, + "components": [ + { + "name": "", + "type": { + "swayType": "struct GenericStruct", + "metadata": { + "metadataTypeId": 9, + "typeArguments": [ + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "b", + "type": { + "swayType": "u32", + "metadata": { + "metadataTypeId": 15 + } + } + }, + { + "name": "c", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + } + ] + } + }, + { + "name": "c", + "type": { + "swayType": "struct DoubleGeneric", + "metadata": { + "metadataTypeId": 8, + "typeArguments": [ + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + }, + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + }, + { + "name": "b", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + } + ] + } + } + ] + }, + { + "name": "implicit_generic_struct", + "output": { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "swayType": "bool" + }, + "inputs": [ + { + "name": "arg1", + "type": { + "concreteTypeId": "426345c4ea93db9f08eeb9fe6047ef0273294bfb1140600a0660be9f2a08d750", + "swayType": "struct StructWithImplicitGenerics", + "metadata": { + "metadataTypeId": 12, + "typeArguments": [ + { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + }, + { + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", + "swayType": "u16" + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "[_; 3]", + "metadata": { + "metadataTypeId": 2 + }, + "components": [ + { + "name": "__array_element", + "type": { + "swayType": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + } + ] + } + }, + { + "name": "b", + "type": { + "swayType": "(_, _)", + "metadata": { + "metadataTypeId": 0 + }, + "components": [ + { + "name": "__tuple_element", + "type": { + "swayType": "u8", + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b" + } + }, + { + "name": "__tuple_element", + "type": { + "swayType": "u16", + "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef" + } + } + ] + } + } + ] + } + }, + { + "name": "arg2", + "type": { + "concreteTypeId": "9dc54ad1b685b6abf04fbcc93696e440452944466e2dfd64b5df956a13ad2027", + "swayType": "(_, _)", + "metadata": { + "metadataTypeId": 1 + }, + "components": [ + { + "name": "__tuple_element", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "__tuple_element", + "type": { + "swayType": "struct StructWithImplicitGenerics", + "metadata": { + "metadataTypeId": 12, + "typeArguments": [ + { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "swayType": "bool" + }, + { + "swayType": "b256", + "metadata": { + "metadataTypeId": 3 + } + } + ] + }, + "components": [ + { + "name": "a", + "type": { + "swayType": "[_; 3]", + "metadata": { + "metadataTypeId": 2 + }, + "components": [ + { + "name": "__array_element", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + } + ] + } + }, + { + "name": "b", + "type": { + "swayType": "(_, _)", + "metadata": { + "metadataTypeId": 0 + }, + "components": [ + { + "name": "__tuple_element", + "type": { + "swayType": "bool", + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903" + } + }, + { + "name": "__tuple_element", + "type": { + "swayType": "b256", + "metadata": { + "metadataTypeId": 3 + } + } + } + ] + } + } + ] + } + } + ] + } + } + ] + } + ], + "loggedTypes": [ + { + "logId": "13213829929622723620", + "type": { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "swayType": "bool" + } + } + ], + "messageTypes": [ + { + "messageId": "0", + "type": { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "swayType": "bool" + } + }, + { + "messageId": "1", + "type": { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "swayType": "bool" + } + } + ], + "configurables": [ + { + "name": "U8_VALUE", + "offset": 4768, + "type": { + "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", + "swayType": "u8" + } + } + ] +} diff --git a/packages/fuel-gauge/src/abi/abi-parser.test.ts b/packages/fuel-gauge/src/abi/abi-parser.test.ts new file mode 100644 index 00000000000..7c2c5fbc576 --- /dev/null +++ b/packages/fuel-gauge/src/abi/abi-parser.test.ts @@ -0,0 +1,40 @@ +import { AbiParser, type AbiSpecification } from 'fuels'; + +import { Parser } from '../../test/typegen'; + +import expected from './abi-parser.json'; + +/** + * @group node + * @group browser + */ +describe('AbiParser', () => { + test('parses as expected', () => { + const parsed = AbiParser.parse(Parser.abi as AbiSpecification); + + /** + * This is split up instead of comparing the whole object + * so that we can see the specific differences + * because the JSON is huge and the diff gets cut off in the terminal. + * + * I have also assigned the values to an object with a properly named property + * so that it's easier to understand where it's failing when a diff is shown. + */ + expect({ encodingVersion: parsed.encodingVersion }).toEqual({ + encodingVersion: expected.encodingVersion, + }); + expect({ programType: parsed.programType }).toEqual({ programType: expected.programType }); + expect({ metadataTypes: parsed.metadataTypes }).toEqual({ + metadataTypes: expected.metadataTypes, + }); + expect({ concreteTypes: parsed.concreteTypes }).toEqual({ + concreteTypes: expected.concreteTypes, + }); + expect({ functions: parsed.functions }).toEqual({ functions: expected.functions }); + expect({ loggedTypes: parsed.loggedTypes }).toEqual({ loggedTypes: expected.loggedTypes }); + expect({ messageTypes: parsed.messageTypes }).toEqual({ messageTypes: expected.messageTypes }); + expect({ configurables: parsed.configurables }).toEqual({ + configurables: expected.configurables, + }); + }); +}); diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml b/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml index f4f48fc715c..d59ecd6d9c0 100644 --- a/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml +++ b/packages/fuel-gauge/test/fixtures/forc-projects/Forc.toml @@ -3,6 +3,7 @@ members = [ "abi-contract", "abi-script", "abi-predicate", + "parser", "advanced-logging", "advanced-logging-abi", "advanced-logging-other-contract", diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/parser/Forc.toml b/packages/fuel-gauge/test/fixtures/forc-projects/parser/Forc.toml new file mode 100644 index 00000000000..591f549398f --- /dev/null +++ b/packages/fuel-gauge/test/fixtures/forc-projects/parser/Forc.toml @@ -0,0 +1,4 @@ +[project] +authors = ["Fuel Labs "] +license = "Apache-2.0" +name = "parser" diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/parser/src/main.sw b/packages/fuel-gauge/test/fixtures/forc-projects/parser/src/main.sw new file mode 100644 index 00000000000..7c178f20bd9 --- /dev/null +++ b/packages/fuel-gauge/test/fixtures/forc-projects/parser/src/main.sw @@ -0,0 +1,64 @@ +contract; +use std::message::send_typed_message; + +struct GenericStruct { + a: bool, + b: u32, + c: T, +} + +pub struct DoubleGeneric { + pub a: T, + pub b: F, +} + +struct NestedGenericStruct { + a: Vec>, + b: Vec>, + c: DoubleGeneric, +} + +struct SimpleStruct { + a: bool, +} + +pub struct StructWithImplicitGenerics { + pub a: [E; 3], + pub b: (E, F), +} + +configurable { + U8_VALUE: u8 = 10, +} + +abi VoidContract { + fn generic_structs( + arg1: GenericStruct>, + arg2: NestedGenericStruct, + ) -> bool; + fn implicit_generic_struct( + arg1: StructWithImplicitGenerics, + arg2: (bool, StructWithImplicitGenerics), + ) -> bool; +} + +impl VoidContract for Contract { + fn generic_structs( + arg1: GenericStruct>, + arg2: NestedGenericStruct, + ) -> bool { + log(arg1.a); + send_typed_message( + 0x0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20, + arg1.a, + 123, + ); + true + } + fn implicit_generic_struct( + arg1: StructWithImplicitGenerics, + arg2: (bool, StructWithImplicitGenerics), + ) -> bool { + true + } +} diff --git a/packages/fuels/package.json b/packages/fuels/package.json index f45ef4266b3..df1935c81c4 100644 --- a/packages/fuels/package.json +++ b/packages/fuels/package.json @@ -62,6 +62,7 @@ }, "license": "Apache-2.0", "dependencies": { + "@fuel-ts/abi": "workspace:*", "@fuel-ts/abi-coder": "workspace:*", "@fuel-ts/abi-typegen": "workspace:*", "@fuel-ts/account": "workspace:*", diff --git a/packages/fuels/src/index.ts b/packages/fuels/src/index.ts index e13cabc50c4..02a546b367f 100644 --- a/packages/fuels/src/index.ts +++ b/packages/fuels/src/index.ts @@ -17,3 +17,4 @@ export * from '@fuel-ts/account'; export * from '@fuel-ts/transactions/configs'; export * from '@fuel-ts/account/configs'; export * from '@fuel-ts/recipes'; +export * from '@fuel-ts/abi'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2a3464d294f..4f702e3112e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -640,6 +640,9 @@ importers: '@fuel-ts/errors': specifier: workspace:* version: link:../errors + '@fuel-ts/utils': + specifier: workspace:* + version: link:../utils packages/abi-coder: dependencies: @@ -920,9 +923,6 @@ importers: packages/fuel-gauge: dependencies: - '@fuel-ts/abi': - specifier: workspace:* - version: link:../abi fuels: specifier: workspace:* version: link:../fuels @@ -948,6 +948,9 @@ importers: packages/fuels: dependencies: + '@fuel-ts/abi': + specifier: workspace:* + version: link:../abi '@fuel-ts/abi-coder': specifier: workspace:* version: link:../abi-coder @@ -27249,7 +27252,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -27304,7 +27307,7 @@ snapshots: enhanced-resolve: 5.17.1 eslint: 8.57.0 eslint-module-utils: 2.11.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 is-core-module: 2.15.1 @@ -27414,7 +27417,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.30.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.6.3))(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 From 0c5f839d480ee0570bd1ebeb58d60f1b4b7055a6 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Fri, 13 Dec 2024 17:34:32 +0100 Subject: [PATCH 02/14] remove casts --- packages/abi/src/parser/specifications/v1/parser.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/abi/src/parser/specifications/v1/parser.ts b/packages/abi/src/parser/specifications/v1/parser.ts index 49453b210bd..aba7aae5c3e 100644 --- a/packages/abi/src/parser/specifications/v1/parser.ts +++ b/packages/abi/src/parser/specifications/v1/parser.ts @@ -1,6 +1,6 @@ import { FuelError } from '@fuel-ts/errors'; -import type { Abi, AbiConcreteType, AbiMetadataType } from '../../abi'; +import type { Abi } from '../../abi'; import { mapAttribute } from './map-attribute'; import { ResolvableType } from './resolvable-type'; @@ -33,7 +33,7 @@ export class AbiParserV1 { ? resolvableType.resolve(concreteType) : new ResolvedType({ swayType: concreteType.type, typeId: concreteType.concreteTypeId }); - return resolvedType.toAbiType() as AbiConcreteType; + return resolvedType.toAbiType(); }); const getType = (concreteTypeId: string) => { @@ -48,7 +48,7 @@ export class AbiParserV1 { }; return { - metadataTypes: resolvableTypes.map((rt) => rt.toAbiType() as AbiMetadataType), + metadataTypes: resolvableTypes.map((rt) => rt.toAbiType()), concreteTypes, encodingVersion: abi.encodingVersion, programType: abi.programType as Abi['programType'], From 1b59dda204794f6cd8a61baf4451355f60070ad2 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Fri, 13 Dec 2024 17:36:51 +0100 Subject: [PATCH 03/14] remove unnecessary throw --- .../abi/src/parser/specifications/v1/parser.ts | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/abi/src/parser/specifications/v1/parser.ts b/packages/abi/src/parser/specifications/v1/parser.ts index aba7aae5c3e..7a9f5f461bb 100644 --- a/packages/abi/src/parser/specifications/v1/parser.ts +++ b/packages/abi/src/parser/specifications/v1/parser.ts @@ -1,6 +1,4 @@ -import { FuelError } from '@fuel-ts/errors'; - -import type { Abi } from '../../abi'; +import type { Abi, AbiConcreteType } from '../../abi'; import { mapAttribute } from './map-attribute'; import { ResolvableType } from './resolvable-type'; @@ -36,16 +34,9 @@ export class AbiParserV1 { return resolvedType.toAbiType(); }); - const getType = (concreteTypeId: string) => { - const type = concreteTypes.find((abiType) => abiType.concreteTypeId === concreteTypeId); - if (type === undefined) { - throw new FuelError( - FuelError.CODES.TYPE_ID_NOT_FOUND, - `A type with concrete type id of "${concreteTypeId}" was not found.` - ); - } - return type; - }; + const getType = (concreteTypeId: string) => + // this will always be defined because it's in the context of the same ABI + concreteTypes.find((abiType) => abiType.concreteTypeId === concreteTypeId) as AbiConcreteType; return { metadataTypes: resolvableTypes.map((rt) => rt.toAbiType()), From d5df0f1f89e21b88f058e8fc8bc2a7714e238113 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Sun, 15 Dec 2024 20:34:16 +0100 Subject: [PATCH 04/14] centralize abi cleanup logic --- .../parser/specifications/v1/cleanup-abi.ts | 53 +++++++++++ .../src/parser/specifications/v1/parser.ts | 26 +++-- .../specifications/v1/resolvable-type.ts | 18 +--- packages/fuel-gauge/src/abi/abi-parser.json | 94 ++++++++++++------- .../fixtures/forc-projects/parser/src/main.sw | 5 + 5 files changed, 131 insertions(+), 65 deletions(-) create mode 100644 packages/abi/src/parser/specifications/v1/cleanup-abi.ts diff --git a/packages/abi/src/parser/specifications/v1/cleanup-abi.ts b/packages/abi/src/parser/specifications/v1/cleanup-abi.ts new file mode 100644 index 00000000000..018400f49af --- /dev/null +++ b/packages/abi/src/parser/specifications/v1/cleanup-abi.ts @@ -0,0 +1,53 @@ +import type { AbiSpecificationV1 } from './specification'; + +/** + * Both RawVec and RawBytes are private sway std library types + * that can never be used directly in sway, + * and the only reason they're in the abi is because they're used internally by Vec and Bytes + * and not ignored when forc builds the outputs. + * We can safely ignore them and simplify the `Vec` and `Bytes` types. + * This makes it simpler for us to consume these types in typegen and coder, + * as well as for others consuming the parsed abi, + * who now don't have to worry about this unnecessary complexity. + * `raw untyped ptr` is also in the abi only because of RawVec and RawBytes, + * so we ignore that as well. + */ +const IGNORED_TYPES = ['struct std::vec::RawVec', 'struct std::bytes::RawBytes', 'raw untyped ptr']; + +export function cleanupAbi(abi: AbiSpecificationV1): AbiSpecificationV1 { + return { + ...abi, + metadataTypes: abi.metadataTypes + .filter((metadataType) => !IGNORED_TYPES.includes(metadataType.type)) + .map((metadataType) => { + switch (metadataType.type) { + /** + * Vectors consist of multiple components, + * 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). + */ + case 'struct std::vec::Vec': + return { + ...metadataType, + components: metadataType.components?.[0].typeArguments, + }; + + /** + * We treat Bytes as a special type + * that is handled only based on its type name ('struct std::bytes::Bytes') + * and not its components. + */ + case 'struct std::bytes::Bytes': + return { + type: metadataType.type, + metadataTypeId: metadataType.metadataTypeId, + }; + default: + return metadataType; + } + }), + }; +} diff --git a/packages/abi/src/parser/specifications/v1/parser.ts b/packages/abi/src/parser/specifications/v1/parser.ts index 7a9f5f461bb..b4351c018a8 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 { cleanupAbi } from './cleanup-abi'; import { mapAttribute } from './map-attribute'; import { ResolvableType } from './resolvable-type'; import { ResolvedType } from './resolved-type'; @@ -14,15 +15,12 @@ import type { export class AbiParserV1 { static parse(abi: AbiSpecificationV1): Abi { - const resolvableTypes = abi.metadataTypes - .map((metadataType) => new ResolvableType(abi, metadataType.metadataTypeId, undefined)) - .filter( - (resolveableType) => - resolveableType.swayType !== 'struct std::vec::RawVec' && - resolveableType.swayType !== 'struct std::bytes::RawBytes' - ); + const cleanAbi = cleanupAbi(abi); + const resolvableTypes = cleanAbi.metadataTypes.map( + (metadataType) => new ResolvableType(cleanAbi, metadataType.metadataTypeId, undefined) + ); - const concreteTypes = abi.concreteTypes.map((concreteType) => { + const concreteTypes = cleanAbi.concreteTypes.map((concreteType) => { const resolvableType = resolvableTypes.find( (resolvable) => resolvable.metadataTypeId === concreteType.metadataTypeId ); @@ -41,9 +39,9 @@ export class AbiParserV1 { return { metadataTypes: resolvableTypes.map((rt) => rt.toAbiType()), concreteTypes, - encodingVersion: abi.encodingVersion, - programType: abi.programType as Abi['programType'], - functions: abi.functions.map((fn: AbiFunctionV1) => ({ + encodingVersion: cleanAbi.encodingVersion, + programType: cleanAbi.programType as Abi['programType'], + functions: cleanAbi.functions.map((fn: AbiFunctionV1) => ({ attributes: fn.attributes?.map(mapAttribute) ?? undefined, name: fn.name, output: getType(fn.output), @@ -52,15 +50,15 @@ export class AbiParserV1 { type: getType(input.concreteTypeId), })), })), - loggedTypes: abi.loggedTypes.map((loggedType: AbiLoggedTypeV1) => ({ + loggedTypes: cleanAbi.loggedTypes.map((loggedType: AbiLoggedTypeV1) => ({ logId: loggedType.logId, type: getType(loggedType.concreteTypeId), })), - messageTypes: abi.messagesTypes.map((messageType: AbiMessageTypeV1) => ({ + messageTypes: cleanAbi.messagesTypes.map((messageType: AbiMessageTypeV1) => ({ messageId: messageType.messageId, type: getType(messageType.concreteTypeId), })), - configurables: abi.configurables.map((configurable: AbiConfigurableV1) => ({ + configurables: cleanAbi.configurables.map((configurable: AbiConfigurableV1) => ({ name: configurable.name, offset: configurable.offset, type: getType(configurable.concreteTypeId), diff --git a/packages/abi/src/parser/specifications/v1/resolvable-type.ts b/packages/abi/src/parser/specifications/v1/resolvable-type.ts index f1841ac8475..66b50037121 100644 --- a/packages/abi/src/parser/specifications/v1/resolvable-type.ts +++ b/packages/abi/src/parser/specifications/v1/resolvable-type.ts @@ -35,23 +35,7 @@ export class ResolvableType { new ResolvableType(this.abi, tp, undefined), ]); - let components = this.metadataType.components; - - /** - * Vectors consist of multiple components, - * 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). - */ - if (swayTypeMatchers.vector(this.metadataType.type)) { - components = components - ?.map((component) => (component.name === 'buf' ? component.typeArguments?.[0] : undefined)) - .filter((x) => x !== undefined) as AbiComponentV1[]; - } - - this.components = components?.map((c) => this.handleComponent(this, c)); + this.components = this.metadataType.components?.map((c) => this.handleComponent(this, c)); } toComponentType(): AbiTypeComponent['type'] { diff --git a/packages/fuel-gauge/src/abi/abi-parser.json b/packages/fuel-gauge/src/abi/abi-parser.json index 676d2904955..373715abe6c 100644 --- a/packages/fuel-gauge/src/abi/abi-parser.json +++ b/packages/fuel-gauge/src/abi/abi-parser.json @@ -140,10 +140,6 @@ "metadataTypeId": 6, "swayType": "generic T" }, - { - "metadataTypeId": 7, - "swayType": "raw untyped ptr" - }, { "metadataTypeId": 8, "swayType": "struct DoubleGeneric", @@ -194,7 +190,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -224,7 +220,7 @@ "type": { "swayType": "struct std::vec::Vec", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "swayType": "struct GenericStruct", @@ -252,7 +248,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -298,7 +294,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -322,7 +318,7 @@ "type": { "swayType": "struct std::vec::Vec", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "swayType": "struct GenericStruct", @@ -348,7 +344,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -390,7 +386,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -533,7 +529,11 @@ ] }, { - "metadataTypeId": 14, + "metadataTypeId": 13, + "swayType": "struct std::bytes::Bytes" + }, + { + "metadataTypeId": 16, "swayType": "struct std::vec::Vec", "components": [ { @@ -554,11 +554,11 @@ ] }, { - "metadataTypeId": 15, + "metadataTypeId": 17, "swayType": "u32" }, { - "metadataTypeId": 16, + "metadataTypeId": 18, "swayType": "u64" } ], @@ -661,7 +661,7 @@ "concreteTypeId": "688b6ed7fc2c45e135ea9a2ce11e3f5313a4c057ba5d616e3381937605ea81e4", "swayType": "struct std::vec::Vec", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", @@ -718,7 +718,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -728,7 +728,7 @@ "swayType": "struct std::vec::Vec", "concreteTypeId": "688b6ed7fc2c45e135ea9a2ce11e3f5313a4c057ba5d616e3381937605ea81e4", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", @@ -791,7 +791,7 @@ "type": { "swayType": "struct std::vec::Vec", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "swayType": "struct GenericStruct", @@ -817,7 +817,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -859,7 +859,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -881,7 +881,7 @@ "type": { "swayType": "struct std::vec::Vec", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "swayType": "struct GenericStruct", @@ -907,7 +907,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -949,7 +949,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -1081,11 +1081,18 @@ } ] }, + { + "concreteTypeId": "cdd87b7d12fe505416570c294c884bca819364863efe3bf539245fa18515fbbb", + "swayType": "struct std::bytes::Bytes", + "metadata": { + "metadataTypeId": 13 + } + }, { "concreteTypeId": "688b6ed7fc2c45e135ea9a2ce11e3f5313a4c057ba5d616e3381937605ea81e4", "swayType": "struct std::vec::Vec", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", @@ -1139,6 +1146,25 @@ "encodingVersion": "1", "programType": "contract", "functions": [ + { + "name": "bytes", + "output": { + "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", + "swayType": "bool" + }, + "inputs": [ + { + "name": "arg", + "type": { + "concreteTypeId": "cdd87b7d12fe505416570c294c884bca819364863efe3bf539245fa18515fbbb", + "swayType": "struct std::bytes::Bytes", + "metadata": { + "metadataTypeId": 13 + } + } + } + ] + }, { "name": "generic_structs", "output": { @@ -1158,7 +1184,7 @@ "concreteTypeId": "688b6ed7fc2c45e135ea9a2ce11e3f5313a4c057ba5d616e3381937605ea81e4", "swayType": "struct std::vec::Vec", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", @@ -1215,7 +1241,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -1225,7 +1251,7 @@ "swayType": "struct std::vec::Vec", "concreteTypeId": "688b6ed7fc2c45e135ea9a2ce11e3f5313a4c057ba5d616e3381937605ea81e4", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "concreteTypeId": "75f7f7a06026cab5d7a70984d1fde56001e83505e3a091ff9722b92d7f56d8be", @@ -1291,7 +1317,7 @@ "type": { "swayType": "struct std::vec::Vec", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "swayType": "struct GenericStruct", @@ -1317,7 +1343,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -1359,7 +1385,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -1381,7 +1407,7 @@ "type": { "swayType": "struct std::vec::Vec", "metadata": { - "metadataTypeId": 14, + "metadataTypeId": 16, "typeArguments": [ { "swayType": "struct GenericStruct", @@ -1407,7 +1433,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -1449,7 +1475,7 @@ "type": { "swayType": "u32", "metadata": { - "metadataTypeId": 15 + "metadataTypeId": 17 } } }, @@ -1696,7 +1722,7 @@ "configurables": [ { "name": "U8_VALUE", - "offset": 4768, + "offset": 5360, "type": { "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", "swayType": "u8" diff --git a/packages/fuel-gauge/test/fixtures/forc-projects/parser/src/main.sw b/packages/fuel-gauge/test/fixtures/forc-projects/parser/src/main.sw index 7c178f20bd9..44d4262645a 100644 --- a/packages/fuel-gauge/test/fixtures/forc-projects/parser/src/main.sw +++ b/packages/fuel-gauge/test/fixtures/forc-projects/parser/src/main.sw @@ -1,4 +1,5 @@ contract; +use std::bytes::Bytes; use std::message::send_typed_message; struct GenericStruct { @@ -40,6 +41,7 @@ abi VoidContract { arg1: StructWithImplicitGenerics, arg2: (bool, StructWithImplicitGenerics), ) -> bool; + fn bytes(arg: Bytes) -> bool; } impl VoidContract for Contract { @@ -61,4 +63,7 @@ impl VoidContract for Contract { ) -> bool { true } + fn bytes(arg: Bytes) -> bool { + true + } } From 2fc0a2f230b66e39f75ef370cb7858c298e27078 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Sun, 15 Dec 2024 21:01:22 +0100 Subject: [PATCH 05/14] remove rawUntypedPtr from swayTypeMatchers --- packages/abi/src/matchers/sway-type-matchers.test.ts | 9 --------- packages/abi/src/matchers/sway-type-matchers.ts | 3 --- 2 files changed, 12 deletions(-) diff --git a/packages/abi/src/matchers/sway-type-matchers.test.ts b/packages/abi/src/matchers/sway-type-matchers.test.ts index 46cc40cd475..ea6f8bfa77a 100644 --- a/packages/abi/src/matchers/sway-type-matchers.test.ts +++ b/packages/abi/src/matchers/sway-type-matchers.test.ts @@ -27,7 +27,6 @@ const testMappings: Record = array: 'array-matched', assetId: 'assetId-matched', evmAddress: 'evmAddress-matched', - rawUntypedPtr: 'rawUntypedPtr-matched', rawUntypedSlice: 'rawUntypedSlice-matched', str: 'str-matched', }; @@ -231,14 +230,6 @@ describe('sway type matchers', () => { await verifyOtherMatchersDontMatch(key, swayType); }); - test('rawUntypedPtr', async () => { - const key = 'rawUntypedPtr'; - const swayType = 'raw untyped ptr'; - - expect(matcher({ swayType })).toEqual(`${key}-matched`); - await verifyOtherMatchersDontMatch(key, swayType); - }); - test('rawUntypedSlice', async () => { const key = 'rawUntypedSlice'; const swayType = 'raw untyped slice'; diff --git a/packages/abi/src/matchers/sway-type-matchers.ts b/packages/abi/src/matchers/sway-type-matchers.ts index 9337b4cfb7e..c48f10c3c4e 100644 --- a/packages/abi/src/matchers/sway-type-matchers.ts +++ b/packages/abi/src/matchers/sway-type-matchers.ts @@ -24,7 +24,6 @@ export type SwayType = | 'array' | 'assetId' | 'evmAddress' - | 'rawUntypedPtr' | 'rawUntypedSlice'; type Matcher = (type: string) => boolean; @@ -69,7 +68,6 @@ const result: Matcher = (type) => type === 'enum std::result::Result'; export const ENUM_REGEX = /^enum (.+::)?(?.+)$/m; const enumMatcher: Matcher = (type) => !option(type) && !result(type) && ENUM_REGEX.test(type); -const rawUntypedPtr: Matcher = (type) => type === 'raw untyped ptr'; const rawUntypedSlice: Matcher = (type) => type === 'raw untyped slice'; export const swayTypeMatchers: Record = { @@ -100,7 +98,6 @@ export const swayTypeMatchers: Record = { option, result, - rawUntypedPtr, rawUntypedSlice, }; From 48d2a8bd1d32aa952af4c9859301dc378627994c Mon Sep 17 00:00:00 2001 From: nedsalk Date: Mon, 16 Dec 2024 22:32:47 +0100 Subject: [PATCH 06/14] rename method --- .../specifications/v1/resolvable-type.ts | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/abi/src/parser/specifications/v1/resolvable-type.ts b/packages/abi/src/parser/specifications/v1/resolvable-type.ts index 66b50037121..68e2be117a1 100644 --- a/packages/abi/src/parser/specifications/v1/resolvable-type.ts +++ b/packages/abi/src/parser/specifications/v1/resolvable-type.ts @@ -35,7 +35,9 @@ export class ResolvableType { new ResolvableType(this.abi, tp, undefined), ]); - this.components = this.metadataType.components?.map((c) => this.handleComponent(this, c)); + this.components = this.metadataType.components?.map((c) => + this.createResolvableComponent(this, c) + ); } toComponentType(): AbiTypeComponent['type'] { @@ -150,26 +152,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), }; } @@ -265,7 +265,7 @@ export class ResolvableType { } const typeArgs = typeArguments?.map( - (typeArgument) => this.handleComponent(parent, typeArgument).type + (typeArgument) => this.createResolvableComponent(parent, typeArgument).type ); const resolvable = new ResolvableType( From 0c6b3593574a83f92f4559aaab284a0527c35aa1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nedim=20Salki=C4=87?= Date: Mon, 16 Dec 2024 22:43:49 +0100 Subject: [PATCH 07/14] Update cleanup-abi.ts --- packages/abi/src/parser/specifications/v1/cleanup-abi.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/abi/src/parser/specifications/v1/cleanup-abi.ts b/packages/abi/src/parser/specifications/v1/cleanup-abi.ts index 018400f49af..b356107e8bb 100644 --- a/packages/abi/src/parser/specifications/v1/cleanup-abi.ts +++ b/packages/abi/src/parser/specifications/v1/cleanup-abi.ts @@ -27,7 +27,6 @@ export function cleanupAbi(abi: AbiSpecificationV1): AbiSpecificationV1 { * 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). */ case 'struct std::vec::Vec': return { From b4d67663eb45593f8b07176604a7d7f0ae318b1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nedim=20Salki=C4=87?= Date: Mon, 16 Dec 2024 22:44:11 +0100 Subject: [PATCH 08/14] Update cleanup-abi.ts --- packages/abi/src/parser/specifications/v1/cleanup-abi.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/abi/src/parser/specifications/v1/cleanup-abi.ts b/packages/abi/src/parser/specifications/v1/cleanup-abi.ts index b356107e8bb..1d517f43707 100644 --- a/packages/abi/src/parser/specifications/v1/cleanup-abi.ts +++ b/packages/abi/src/parser/specifications/v1/cleanup-abi.ts @@ -26,7 +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 + * as it's then easier to reason about the vector. */ case 'struct std::vec::Vec': return { From c4ed9d8d5a5fa19b230c018f04dc02fb64f6f1aa Mon Sep 17 00:00:00 2001 From: nedsalk Date: Tue, 17 Dec 2024 10:26:35 +0100 Subject: [PATCH 09/14] refactor abi type mappers --- .../specifications/v1/abi-type-mappers.ts | 94 +++++++++++++++++++ .../src/parser/specifications/v1/parser.ts | 8 +- .../specifications/v1/resolvable-type.ts | 70 +------------- .../parser/specifications/v1/resolved-type.ts | 93 +----------------- 4 files changed, 102 insertions(+), 163 deletions(-) create mode 100644 packages/abi/src/parser/specifications/v1/abi-type-mappers.ts 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 00000000000..385c80d1060 --- /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/parser.ts b/packages/abi/src/parser/specifications/v1/parser.ts index b4351c018a8..36b05fc737f 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,6 +17,7 @@ import type { export class AbiParserV1 { static parse(abi: AbiSpecificationV1): Abi { const cleanAbi = cleanupAbi(abi); + const resolvableTypes = cleanAbi.metadataTypes.map( (metadataType) => new ResolvableType(cleanAbi, metadataType.metadataTypeId, undefined) ); @@ -29,7 +31,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 +39,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 68e2be117a1..8a297214e6e 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'; @@ -13,13 +12,13 @@ import type { 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; @@ -40,71 +39,6 @@ export class ResolvableType { ); } - 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; - } - /** * Find a metadata type by its ID. * @param metadataTypeId - The ID of the metadata type to find. diff --git a/packages/abi/src/parser/specifications/v1/resolved-type.ts b/packages/abi/src/parser/specifications/v1/resolved-type.ts index 3b74a805ed5..45dc800b93d 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; - } } From 3de5f802f4b39be5a8bf890ad236946e266b1188 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Tue, 17 Dec 2024 10:35:19 +0100 Subject: [PATCH 10/14] refactor into using maps for types --- .../src/parser/specifications/v1/parser.ts | 7 ++++- .../specifications/v1/resolvable-type.ts | 31 ++++++++++--------- 2 files changed, 22 insertions(+), 16 deletions(-) diff --git a/packages/abi/src/parser/specifications/v1/parser.ts b/packages/abi/src/parser/specifications/v1/parser.ts index 36b05fc737f..0048ea802f3 100644 --- a/packages/abi/src/parser/specifications/v1/parser.ts +++ b/packages/abi/src/parser/specifications/v1/parser.ts @@ -18,8 +18,13 @@ 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) => { diff --git a/packages/abi/src/parser/specifications/v1/resolvable-type.ts b/packages/abi/src/parser/specifications/v1/resolvable-type.ts index 8a297214e6e..6f85aad3371 100644 --- a/packages/abi/src/parser/specifications/v1/resolvable-type.ts +++ b/packages/abi/src/parser/specifications/v1/resolvable-type.ts @@ -8,7 +8,6 @@ import type { AbiComponentV1, AbiConcreteTypeV1, AbiMetadataTypeV1, - AbiSpecificationV1, AbiTypeArgumentV1, } from './specification'; @@ -23,7 +22,10 @@ export class ResolvableType { components: ResolvableComponent[] | undefined; constructor( - private abi: AbiSpecificationV1, + private abiTypeMaps: { + metadataTypes: Map; + concreteTypes: Map; + }, public metadataTypeId: number, public typeParamsArgsMap: Array<[number, ResolvedType | ResolvableType]> | undefined ) { @@ -31,7 +33,7 @@ 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) => @@ -47,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, @@ -67,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, @@ -128,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 ); @@ -147,7 +147,7 @@ export class ResolvableType { }); return new ResolvableType( - this.abi, + this.abiTypeMaps, type.metadataTypeId, ResolvableType.mapTypeParametersAndArgs(metadataType, concreteTypeArgs) ).resolveInternal(type.concreteTypeId, undefined); @@ -182,7 +182,7 @@ export class ResolvableType { return ( resolvableTypeParameter ?? - new ResolvableType(this.abi, metadataType.metadataTypeId, undefined) + new ResolvableType(this.abiTypeMaps, metadataType.metadataTypeId, undefined) ); } @@ -192,10 +192,11 @@ 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( @@ -203,7 +204,7 @@ export class ResolvableType { ); const resolvable = new ResolvableType( - this.abi, + this.abiTypeMaps, metadataType.metadataTypeId, !typeArgs?.length ? undefined From a307df1ba5025cf1a24115e89258dce023019c87 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Tue, 17 Dec 2024 14:45:04 +0100 Subject: [PATCH 11/14] refactor from array of tuples into `Map` --- .../specifications/v1/abi-type-mappers.ts | 8 ++- .../specifications/v1/resolvable-type.ts | 61 +++++++++++-------- .../parser/specifications/v1/resolved-type.ts | 4 +- 3 files changed, 43 insertions(+), 30 deletions(-) diff --git a/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts b/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts index 385c80d1060..4be87328a56 100644 --- a/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts +++ b/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts @@ -15,7 +15,7 @@ function mapMetadata(type: ResolvableType | ResolvedType) { }; if (type.typeParamsArgsMap && type.metadataType?.typeParameters?.length) { - result.typeArguments = type.typeParamsArgsMap.map((t) => toTypeArgument(t[1])); + result.typeArguments = [...type.typeParamsArgsMap.values()].map((rt) => toTypeArgument(rt)); } return result; @@ -81,8 +81,10 @@ export function toAbiType(t: ResolvableType | ResolvedType): AbiConcreteType | A metadataTypeId: t.metadataType?.metadataTypeId as number, }; - if (t.typeParamsArgsMap) { - result.typeParameters = t.typeParamsArgsMap.map(([, rt]) => toAbiType(rt) as AbiMetadataType); + if (t.typeParamsArgsMap && t.metadataType?.typeParameters?.length) { + result.typeParameters = [...t.typeParamsArgsMap.values()].map( + (rt) => toAbiType(rt) as AbiMetadataType + ); } } diff --git a/packages/abi/src/parser/specifications/v1/resolvable-type.ts b/packages/abi/src/parser/specifications/v1/resolvable-type.ts index 6f85aad3371..249a074fb6e 100644 --- a/packages/abi/src/parser/specifications/v1/resolvable-type.ts +++ b/packages/abi/src/parser/specifications/v1/resolvable-type.ts @@ -27,14 +27,18 @@ export class ResolvableType { concreteTypes: Map; }, public metadataTypeId: number, - public typeParamsArgsMap: Array<[number, ResolvedType | ResolvableType]> | undefined + public typeParamsArgsMap: Map | undefined ) { this.metadataType = this.findMetadataType(metadataTypeId); this.swayType = this.metadataType.type; - this.typeParamsArgsMap ??= this.metadataType.typeParameters?.map((tp) => [ - tp, - new ResolvableType(this.abiTypeMaps, tp, undefined), - ]); + this.typeParamsArgsMap ??= + this.metadataType.typeParameters && + new Map( + this.metadataType.typeParameters.map((tp) => [ + tp, + new ResolvableType(this.abiTypeMaps, tp, undefined), + ]) + ); this.components = this.metadataType.components?.map((c) => this.createResolvableComponent(this, c) @@ -82,8 +86,13 @@ export class ResolvableType { private static mapTypeParametersAndArgs( metadataType: AbiMetadataTypeV1, args: (ResolvableType | ResolvedType)[] - ): Array<[number, ResolvedType | ResolvableType]> | undefined { - return metadataType.typeParameters?.map((typeParameter, idx) => [typeParameter, args[idx]]); + ): Map | undefined { + return ( + metadataType.typeParameters && + new Map( + metadataType.typeParameters.map((typeParameter, idx) => [typeParameter, args[idx]]) + ) + ); } private createResolvableComponent( @@ -176,9 +185,7 @@ export class ResolvableType { * We check in the parent's typeParamsArgsMap if the metadata type we're solving for * has been substituted with a different generic type, and then we use that generic type. */ - const resolvableTypeParameter = parent.typeParamsArgsMap?.find( - ([typeParameterId]) => typeParameterId === metadataType.metadataTypeId - )?.[1]; + const resolvableTypeParameter = parent.typeParamsArgsMap?.get(metadataType.metadataTypeId); return ( resolvableTypeParameter ?? @@ -227,7 +234,7 @@ export class ResolvableType { private resolveInternal( typeId: string | number, - typeParamsArgsMap: Array<[number, ResolvedType]> | undefined + typeParamsArgsMap: Map | undefined ): ResolvedType { const resolvedType = new ResolvedType({ swayType: this.swayType, @@ -263,9 +270,7 @@ export class ResolvableType { * its corresponding type argument will be found in the typeArgs, * which will be used to substitute the component with. */ - const resolvedGenericType = typeArgs?.find( - ([typeParameterId]) => type.metadataTypeId === typeParameterId - )?.[1]; + const resolvedGenericType = typeArgs?.get(type.metadataTypeId); if (resolvedGenericType) { return { @@ -301,8 +306,8 @@ export class ResolvableType { } private resolveTypeArgs( - typeParamsArgsMap: Array<[number, ResolvedType]> | undefined - ): [number, ResolvedType][] | undefined { + typeParamsArgsMap: Map | undefined + ): Map | undefined { /** * This case only happens when the metadata type is *implicitly* generic. * The type itself doesn't have any type parameters that should be resolved, @@ -314,31 +319,36 @@ export class ResolvableType { return typeParamsArgsMap; } + const newMap = new Map(); + /** * We resolve the type parameters of the underlying metadata type * with the type arguments of the concrete type. */ - return this.typeParamsArgsMap.map(([tp, value]) => { + this.typeParamsArgsMap.forEach((value, tp) => { /** * Some type parameters can already be resolved * e.g. `struct MyStruct { a: DoubleGeneric }` * where the second type parameter of DoubleGeneric is already known. */ if (value instanceof ResolvedType) { - return [tp, value]; + newMap.set(tp, value); + return; } - const resolved = typeParamsArgsMap?.find( - ([typeParameterId]) => typeParameterId === value.metadataTypeId - ); - /** * The type parameter is either directly substituted with a type argument, * or it's metadata type which accepts the type argument, * so that metadata type needs to be resolved first. */ - return resolved ?? [tp, value.resolveInternal(value.metadataTypeId, typeParamsArgsMap)]; + const resolved = + typeParamsArgsMap?.get(value.metadataTypeId) ?? + value.resolveInternal(value.metadataTypeId, typeParamsArgsMap); + + newMap.set(value.metadataTypeId, resolved); }); + + return newMap; } public resolve(concreteType: AbiConcreteTypeV1) { @@ -348,8 +358,9 @@ export class ResolvableType { }); const typeParamsArgsMap = concreteTypeArgs - ? (ResolvableType.mapTypeParametersAndArgs(this.metadataType, concreteTypeArgs) as Array< - [number, ResolvedType] + ? (ResolvableType.mapTypeParametersAndArgs(this.metadataType, concreteTypeArgs) as Map< + number, + ResolvedType >) : undefined; diff --git a/packages/abi/src/parser/specifications/v1/resolved-type.ts b/packages/abi/src/parser/specifications/v1/resolved-type.ts index 45dc800b93d..6465d10f6d5 100644 --- a/packages/abi/src/parser/specifications/v1/resolved-type.ts +++ b/packages/abi/src/parser/specifications/v1/resolved-type.ts @@ -9,14 +9,14 @@ export class ResolvedType { public swayType: string; public typeId: string | number; public components: ResolvedComponent[] | undefined; - public typeParamsArgsMap: Array<[number, ResolvedType]> | undefined; + public typeParamsArgsMap: Map | undefined; public metadataType: AbiMetadataTypeV1 | undefined; constructor(params: { swayType: string; typeId: string | number; components?: ResolvedComponent[]; - typeParamsArgsMap?: Array<[number, ResolvedType]>; + typeParamsArgsMap?: Map; metadataType?: AbiMetadataTypeV1; }) { this.swayType = params.swayType; From 804feb19ec56ebcadfb518b00c51792fa30c0d1a Mon Sep 17 00:00:00 2001 From: nedsalk Date: Tue, 17 Dec 2024 14:47:43 +0100 Subject: [PATCH 12/14] rename variables --- .../specifications/v1/resolvable-type.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/abi/src/parser/specifications/v1/resolvable-type.ts b/packages/abi/src/parser/specifications/v1/resolvable-type.ts index 249a074fb6e..f05f4040e5b 100644 --- a/packages/abi/src/parser/specifications/v1/resolvable-type.ts +++ b/packages/abi/src/parser/specifications/v1/resolvable-type.ts @@ -34,9 +34,9 @@ export class ResolvableType { this.typeParamsArgsMap ??= this.metadataType.typeParameters && new Map( - this.metadataType.typeParameters.map((tp) => [ - tp, - new ResolvableType(this.abiTypeMaps, tp, undefined), + this.metadataType.typeParameters.map((typeParameter) => [ + typeParameter, + new ResolvableType(this.abiTypeMaps, typeParameter, undefined), ]) ); @@ -325,14 +325,14 @@ export class ResolvableType { * We resolve the type parameters of the underlying metadata type * with the type arguments of the concrete type. */ - this.typeParamsArgsMap.forEach((value, tp) => { + this.typeParamsArgsMap.forEach((arg, typeParameter) => { /** * Some type parameters can already be resolved * e.g. `struct MyStruct { a: DoubleGeneric }` * where the second type parameter of DoubleGeneric is already known. */ - if (value instanceof ResolvedType) { - newMap.set(tp, value); + if (arg instanceof ResolvedType) { + newMap.set(typeParameter, arg); return; } @@ -342,10 +342,10 @@ export class ResolvableType { * so that metadata type needs to be resolved first. */ const resolved = - typeParamsArgsMap?.get(value.metadataTypeId) ?? - value.resolveInternal(value.metadataTypeId, typeParamsArgsMap); + typeParamsArgsMap?.get(arg.metadataTypeId) ?? + arg.resolveInternal(arg.metadataTypeId, typeParamsArgsMap); - newMap.set(value.metadataTypeId, resolved); + newMap.set(arg.metadataTypeId, resolved); }); return newMap; From b6c5936bf8a93727ee0ea3fee67383dd1ef0bb5c Mon Sep 17 00:00:00 2001 From: nedsalk Date: Tue, 17 Dec 2024 15:05:35 +0100 Subject: [PATCH 13/14] refactorings, comments --- .../src/parser/specifications/v1/abi-type-mappers.ts | 8 +++----- .../src/parser/specifications/v1/resolvable-type.ts | 10 +++++++--- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts b/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts index 4be87328a56..5dad5545b11 100644 --- a/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts +++ b/packages/abi/src/parser/specifications/v1/abi-type-mappers.ts @@ -21,14 +21,12 @@ function mapMetadata(type: ResolvableType | ResolvedType) { 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'; + const isResolvedType = 'typeId' in type; + + return isResolvedType && typeof type.typeId === 'string'; } function mapComponentType(component: ResolvableComponent): AbiTypeComponent { diff --git a/packages/abi/src/parser/specifications/v1/resolvable-type.ts b/packages/abi/src/parser/specifications/v1/resolvable-type.ts index f05f4040e5b..04f297a9bf0 100644 --- a/packages/abi/src/parser/specifications/v1/resolvable-type.ts +++ b/packages/abi/src/parser/specifications/v1/resolvable-type.ts @@ -338,8 +338,8 @@ export class ResolvableType { /** * The type parameter is either directly substituted with a type argument, - * or it's metadata type which accepts the type argument, - * so that metadata type needs to be resolved first. + * or it's a metadata type which accepts the type argument, + * so that metadata type will be resolved and subsitute the type parameter. */ const resolved = typeParamsArgsMap?.get(arg.metadataTypeId) ?? @@ -351,7 +351,11 @@ export class ResolvableType { return newMap; } - public resolve(concreteType: AbiConcreteTypeV1) { + /** + * Resolves the instance of `ResolvableType` with the specific concrete type's data. + * @returns a `ResolvedType` in which all its components are resolved. + */ + public resolve(concreteType: AbiConcreteTypeV1): ResolvedType { const concreteTypeArgs = concreteType.typeArguments?.map((typeArgument) => { const concreteTypeArg = this.findConcreteType(typeArgument); return this.resolveConcreteType(concreteTypeArg); From a2f00041994cc587769c7c69e7b74ee343d39524 Mon Sep 17 00:00:00 2001 From: nedsalk Date: Tue, 17 Dec 2024 18:36:25 +0100 Subject: [PATCH 14/14] split test up into multiple tests --- .../fuel-gauge/src/abi/abi-parser.test.ts | 65 +++++++++++-------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/packages/fuel-gauge/src/abi/abi-parser.test.ts b/packages/fuel-gauge/src/abi/abi-parser.test.ts index 7c2c5fbc576..07498ae19ce 100644 --- a/packages/fuel-gauge/src/abi/abi-parser.test.ts +++ b/packages/fuel-gauge/src/abi/abi-parser.test.ts @@ -9,32 +9,43 @@ import expected from './abi-parser.json'; * @group browser */ describe('AbiParser', () => { - test('parses as expected', () => { - const parsed = AbiParser.parse(Parser.abi as AbiSpecification); - - /** - * This is split up instead of comparing the whole object - * so that we can see the specific differences - * because the JSON is huge and the diff gets cut off in the terminal. - * - * I have also assigned the values to an object with a properly named property - * so that it's easier to understand where it's failing when a diff is shown. - */ - expect({ encodingVersion: parsed.encodingVersion }).toEqual({ - encodingVersion: expected.encodingVersion, - }); - expect({ programType: parsed.programType }).toEqual({ programType: expected.programType }); - expect({ metadataTypes: parsed.metadataTypes }).toEqual({ - metadataTypes: expected.metadataTypes, - }); - expect({ concreteTypes: parsed.concreteTypes }).toEqual({ - concreteTypes: expected.concreteTypes, - }); - expect({ functions: parsed.functions }).toEqual({ functions: expected.functions }); - expect({ loggedTypes: parsed.loggedTypes }).toEqual({ loggedTypes: expected.loggedTypes }); - expect({ messageTypes: parsed.messageTypes }).toEqual({ messageTypes: expected.messageTypes }); - expect({ configurables: parsed.configurables }).toEqual({ - configurables: expected.configurables, - }); + test('parses encoding version as expected', () => { + const parsed = AbiParser.parse(Parser.abi as AbiSpecification); + expect(parsed.encodingVersion).toEqual(expected.encodingVersion); + }); + + test('parses program type as expected', () => { + const parsed = AbiParser.parse(Parser.abi as AbiSpecification); + expect(parsed.programType).toEqual(expected.programType); + }); + + test('parses metadata types as expected', () => { + const parsed = AbiParser.parse(Parser.abi as AbiSpecification); + expect(parsed.metadataTypes).toEqual(expected.metadataTypes); + }); + + test('parses concrete types as expected', () => { + const parsed = AbiParser.parse(Parser.abi as AbiSpecification); + expect(parsed.concreteTypes).toEqual(expected.concreteTypes); + }); + + test('parses functions as expected', () => { + const parsed = AbiParser.parse(Parser.abi as AbiSpecification); + expect(parsed.functions).toEqual(expected.functions); + }); + + test('parses logged types as expected', () => { + const parsed = AbiParser.parse(Parser.abi as AbiSpecification); + expect(parsed.loggedTypes).toEqual(expected.loggedTypes); + }); + + test('parses message types as expected', () => { + const parsed = AbiParser.parse(Parser.abi as AbiSpecification); + expect(parsed.messageTypes).toEqual(expected.messageTypes); + }); + + test('parses configurables as expected', () => { + const parsed = AbiParser.parse(Parser.abi as AbiSpecification); + expect(parsed.configurables).toEqual(expected.configurables); }); });