Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ABI parser #3089

Open
wants to merge 40 commits into
base: np/feat/abi-refactor
Choose a base branch
from
Open

feat: ABI parser #3089

wants to merge 40 commits into from

Conversation

nedsalk
Copy link
Contributor

@nedsalk nedsalk commented Sep 3, 2024

Summary

Adds the parsing functionality for an ABI. The Abi interface outputted by AbiParser will be the (hopefully) unchanging interface that will be used both by us in coders and typers, as well as external users who wish to work with the abi but not be affected by changing specifications.

This PR doesn't have any tests as the outputs of AbiParser.parse are tested in #3249 and #3402 which are based off of this PR.

import type { Abi, AbiSpecification } from '@fuel-ts/abi'
import { AbiParser } from '@fuel-ts/abi'

const jsonAbi: AbiSpecification = '{ "specVersion": "1" }' 
const abi: Abi = AbiParser.parse(jsonAbi);

Checklist

  • All changes are covered by tests (or not applicable)
  • All changes are documented (or not applicable)
  • I reviewed the entire PR myself (preferably, on GH UI)
  • I described all Breaking Changes (or there's none)

@nedsalk nedsalk added the feat Issue is a feature label Sep 3, 2024
@nedsalk nedsalk self-assigned this Sep 3, 2024
Copy link

vercel bot commented Sep 3, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
create-fuels-template ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 3, 2024 10:28am
fuels-template ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 3, 2024 10:28am
ts-api-docs ❌ Failed (Inspect) Dec 3, 2024 10:28am
ts-docs ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 3, 2024 10:28am
ts-docs-api ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 3, 2024 10:28am
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
create-fuels-counter-example ⬜️ Ignored (Inspect) Dec 3, 2024 10:28am

Copy link

codspeed-hq bot commented Sep 25, 2024

CodSpeed Performance Report

Merging #3089 will not alter performance

Comparing ns/feat/abi-parser (fe56f53) with np/feat/abi-refactor (8ce4810)

Summary

✅ 18 untouched benchmarks

public toAbiType(): AbiType {
return {
swayType: this.type,
concreteTypeId: this.typeId as string,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we cast this as a string?

Will there be instances that the concreteTypeId can be a metadataTypeId?

Comment on lines +31 to +38
export interface AbiComponentV1 extends AbiTypeArgumentV1 {
readonly name: string;
}

export interface AbiTypeArgumentV1 {
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[];
}
Copy link
Contributor

@petertonysmith94 petertonysmith94 Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm going to unresolve this one and agree with @Torres-ssf suggestion:

As forc generates the ABI with the name in the typeArguments - I believe we should match this behaviour.

cc @nedsalk

packages/abi/src/parser/specifications/v1/specification.ts Outdated Show resolved Hide resolved
packages/abi/src/parser/specifications/v1/specification.ts Outdated Show resolved Hide resolved
packages/abi/src/parser/specifications/v1/specification.ts Outdated Show resolved Hide resolved
@@ -0,0 +1,90 @@
export interface Abi {
specVersion: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this ABI is "normalized" and not to spec, do we want to make the specVersion reflect this?

Suggested change
specVersion: string;
specVersion: 'normalized';


export class ResolvableType {
private metadataType: AbiMetadataTypeV1;
type: string;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
type: string;
swayType: string;

Suggest changing this variable to be consistent across the codebase.

packages/abi/src/parser/abi-parser.ts Show resolved Hide resolved
encodingVersion: abi.encodingVersion,
programType: abi.programType,
functions: abi.functions.map((fn: AbiFunctionV1) => ({
attributes: fn.attributes?.map(mapAttribute) ?? undefined,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose it could throw - providing the attributes are malformed.

Copy link
Contributor

@Torres-ssf Torres-ssf left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First pass. I am still getting familiarized with the code and going through the review process.

The code contains complex algorithms with minimal comments, making it challenging to understand or modify.

To improve clarity and maintainability, we should consider adding detailed comments to explain each case and include TSDoc for the class methods.

Could you confirm if my understanding of these terms is accurate?

  • ResolvableType: Represents a type that may contain generics or unresolved components.

  • ResolvedType: Represents a fully resolved type where all generics and type parameters have respective concrete types.

Should we add tests for the new class ResolvableType?


const resolvedGenericType = typeArgs?.find(
([tp]) => (c.type as ResolvableType).metadataTypeId === tp
)?.[1];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use .[1];? We need to properly document this.

Comment on lines +201 to +204
private resolveInternal(
typeId: string | number,
typeParamsArgsMap: Array<[number, ResolvedType]> | undefined
): ResolvedType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method contains complex logic and appears to address multiple concerns. Would it be possible to refactor it into smaller helper functions, each with a single responsibility?

Even if these helpers are only used once, giving them descriptive names can greatly enhance the code's readability and make its purpose easier to understand.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup +1 here, can appreciate the logic is there but just breaking the code down more will really benefit resolvable-type.ts/resolved-type.ts.


return (
resolved ?? [
+typeParameter,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you using a + here? Could typeParameter be null or a string instead of a number? It’s worth documenting this for clarity.

if (swayTypeMatchers.generic(mt.type)) {
const resolvedTypeParameter = parent.typeParamsArgsMap?.find(
([tp]) => tp === mt.metadataTypeId
)?.[1];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why .[1]; ? We need to document all these edge cases properly.

abi: AbiSpecificationV1,
type: AbiConcreteTypeV1
): ResolvedType {
if (type.metadataTypeId === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why explicitly compare this to equals to undefined?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some utility functions used across the code for isConcreteType and isMetadataType that checks the condition I think would make this more readable.

return metadataType.typeParameters?.map((typeParameter, idx) => [typeParameter, args[idx]]);
}

private static handleComponent(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe we can rename this method to createResolvableComponent as it reflects its purpose clearly

Comment on lines +23 to +24
resolveableType.type !== 'struct std::vec::RawVec' &&
resolveableType.type !== 'struct std::bytes::RawBytes'
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the reasoning for these filters? Are these std types filtered anywhere else? In the current version, we have various exceptions littered around, would be good to have these centralised.

Comment on lines +49 to +64
/**
* 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) => {
if (component.name === 'buf') {
return component.typeArguments?.[0];
}
return component;
}) as AbiComponentV1[];
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could this not be handled at the vec component level rather than the constructor of the resolvable type for a better separation of concerns, I think this file should be type agnostic.

};
}

constructor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, please can the constructor be the first public method.

): ResolvableComponent {
const name = (component as AbiComponentV1).name;

if (typeof component.typeId === 'string') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please can this go in a utility/helper function for isContectyType, typeId being a string is an odd classification and I think that'll bring clarity to the code.

abi: AbiSpecificationV1,
type: AbiConcreteTypeV1
): ResolvedType {
if (type.metadataTypeId === undefined) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some utility functions used across the code for isConcreteType and isMetadataType that checks the condition I think would make this more readable.

});
}

if (!type.typeArguments) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Again, why does this mean we need to resolve the internal type?

* in which case they cannot be resolved.
* If they're not generic, they can be immediately resolved.
*/
private handleMetadataType(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned here, I think this function could also benefit from some docs above the conditions, just denoting why certain things have to happen at certain points.

@@ -0,0 +1,90 @@
export interface Abi {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the parsed types? And ../specifications/specification.ts contains the spec types? If so I'd be pro removing the ABI prefix and replacing/adding a Parsed prefix.

Comment on lines +201 to +204
private resolveInternal(
typeId: string | number,
typeParamsArgsMap: Array<[number, ResolvedType]> | undefined
): ResolvedType {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup +1 here, can appreciate the logic is there but just breaking the code down more will really benefit resolvable-type.ts/resolved-type.ts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feat Issue is a feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants