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 23 commits into
base: np/feat/abi-refactor
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
a27c7bd
feat: ABI parser
nedsalk Dec 13, 2024
0c5f839
remove casts
nedsalk Dec 13, 2024
1b59dda
remove unnecessary throw
nedsalk Dec 13, 2024
d5df0f1
centralize abi cleanup logic
nedsalk Dec 15, 2024
2fc0a2f
remove rawUntypedPtr from swayTypeMatchers
nedsalk Dec 15, 2024
ff16608
Merge branch 'np/feat/abi-refactor' into ns/feat/abi-parser
nedsalk Dec 16, 2024
48d2a8b
rename method
nedsalk Dec 16, 2024
0c6b359
Update cleanup-abi.ts
nedsalk Dec 16, 2024
b4d6766
Update cleanup-abi.ts
nedsalk Dec 16, 2024
c4ed9d8
refactor abi type mappers
nedsalk Dec 17, 2024
3de5f80
refactor into using maps for types
nedsalk Dec 17, 2024
a307df1
refactor from array of tuples into `Map`
nedsalk Dec 17, 2024
804feb1
rename variables
nedsalk Dec 17, 2024
b6c5936
refactorings, comments
nedsalk Dec 17, 2024
a2f0004
split test up into multiple tests
nedsalk Dec 17, 2024
7d58e16
Merge branch 'np/feat/abi-refactor' into ns/feat/abi-parser
nedsalk Dec 31, 2024
83cd67f
Merge branch 'np/feat/abi-refactor' into ns/feat/abi-parser
nedsalk Jan 2, 2025
72e0d58
Merge branch 'np/feat/abi-refactor' into ns/feat/abi-parser
nedsalk Jan 8, 2025
a71f9e4
Merge branch 'np/feat/abi-refactor' into ns/feat/abi-parser
nedsalk Jan 8, 2025
f463546
Merge branch 'master' of github.com:FuelLabs/fuels-ts into ns/feat/ab…
petertonysmith94 Jan 13, 2025
358af56
Merge branch 'np/feat/abi-refactor' into ns/feat/abi-parser
nedsalk Jan 13, 2025
493ae32
Merge branch 'np/feat/abi-refactor' into ns/feat/abi-parser
nedsalk Jan 13, 2025
d93a90d
Merge branch 'np/feat/abi-refactor' into ns/feat/abi-parser
nedsalk Jan 14, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .changeset/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
"baseBranch": "master",
"updateInternalDependencies": "patch",
"ignore": [
"@fuel-ts/abi",
"fuel-gauge",
"docs",
"demo-fuels",
Expand Down
7 changes: 7 additions & 0 deletions .changeset/tender-tigers-fry.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@fuel-ts/abi": patch
nedsalk marked this conversation as resolved.
Show resolved Hide resolved
"fuels": patch
"@fuel-ts/errors": patch
---

feat: ABI parser
3 changes: 3 additions & 0 deletions apps/docs-api/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

# Modules

<!-- TODO: uncomment once deployed -->
<!-- - [abi](https://fuels-ts-docs-api.vercel.app/modules/_fuel_ts_abi.html) -->

- [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)
Expand Down
1 change: 1 addition & 0 deletions apps/docs-api/typedoc.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"$schema": "https://typedoc.org/schema.json",
"entryPointStrategy": "packages",
"entryPoints": [
"../../packages/abi",
"../../packages/abi-coder",
"../../packages/abi-typegen",
"../../packages/address",
Expand Down
4 changes: 4 additions & 0 deletions apps/docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,10 @@ export default defineConfig({
text: 'Optimized React Example',
link: '/guide/cookbook/optimized-react-example',
},
{
text: 'Working with the ABI',
link: '/guide/cookbook/working-with-the-abi',
},
],
},
{
Expand Down
3 changes: 2 additions & 1 deletion apps/docs/spell-check-custom-words.txt
Original file line number Diff line number Diff line change
Expand Up @@ -343,4 +343,5 @@ Workspaces
WSL
XOR
XORs
matcher
YAML
matcher
9 changes: 9 additions & 0 deletions apps/docs/src/guide/cookbook/snippets/parsing-the-abi.ts
Original file line number Diff line number Diff line change
@@ -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);
11 changes: 11 additions & 0 deletions apps/docs/src/guide/cookbook/working-with-the-abi.md
Original file line number Diff line number Diff line change
@@ -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.

<!-- TODO: fix links once it's live -->
<!-- AbiParser: https://fuels-ts-docs-api.vercel.app/classes/_fuel_ts_abi.AbiParser.html-->
<!-- ABI: https://fuels-ts-docs-api.vercel.app/interfaces/_fuel_ts_abi.Abi.html -->

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}
6 changes: 6 additions & 0 deletions apps/docs/src/guide/errors/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
6 changes: 6 additions & 0 deletions internal/check-imports/src/references.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,16 @@ import {
arrayify,
hexlify,
createConfig,
AbiParser,
} from 'fuels';

const { log } = console;

/**
* abi
*/
log(AbiParser);

/**
* abi-coder
*/
Expand Down
3 changes: 2 additions & 1 deletion packages/abi/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
"postbuild": "tsx ../../scripts/postbuild.ts"
},
"dependencies": {
"@fuel-ts/errors": "workspace:*"
"@fuel-ts/errors": "workspace:*",
"@fuel-ts/utils": "workspace:*"
},
"devDependencies": {}
}
1 change: 1 addition & 0 deletions packages/abi/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './coder';
export * from './gen';
export * from './parser';
9 changes: 0 additions & 9 deletions packages/abi/src/matchers/sway-type-matchers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ const testMappings: Record<keyof typeof swayTypeMatchers, `${string}-matched`> =
array: 'array-matched',
assetId: 'assetId-matched',
evmAddress: 'evmAddress-matched',
rawUntypedPtr: 'rawUntypedPtr-matched',
rawUntypedSlice: 'rawUntypedSlice-matched',
str: 'str-matched',
};
Expand Down Expand Up @@ -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';
Expand Down
3 changes: 0 additions & 3 deletions packages/abi/src/matchers/sway-type-matchers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ export type SwayType =
| 'array'
| 'assetId'
| 'evmAddress'
| 'rawUntypedPtr'
| 'rawUntypedSlice';

type Matcher = (type: string) => boolean;
Expand Down Expand Up @@ -69,7 +68,6 @@ const result: Matcher = (type) => type === 'enum std::result::Result';
export const ENUM_REGEX = /^enum (.+::)?(?<name>.+)$/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<SwayType, Matcher> = {
Expand Down Expand Up @@ -100,7 +98,6 @@ export const swayTypeMatchers: Record<SwayType, Matcher> = {
option,
result,

rawUntypedPtr,
rawUntypedSlice,
};

Expand Down
1 change: 0 additions & 1 deletion packages/abi/src/parse/AbiParser.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/abi/src/parse/specifications/v1/index.ts

This file was deleted.

1 change: 0 additions & 1 deletion packages/abi/src/parse/types.ts

This file was deleted.

44 changes: 44 additions & 0 deletions packages/abi/src/parser/abi-parser.ts
Original file line number Diff line number Diff line change
@@ -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') {
maschad marked this conversation as resolved.
Show resolved Hide resolved
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);
}
}
142 changes: 142 additions & 0 deletions packages/abi/src/parser/abi.ts
Original file line number Diff line number Diff line change
@@ -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 {
nedsalk marked this conversation as resolved.
Show resolved Hide resolved
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[];
}
3 changes: 3 additions & 0 deletions packages/abi/src/parser/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export { AbiParser, type AbiSpecification } from './abi-parser';
export * from './abi';
export * from './specifications/v1/specification';
2 changes: 2 additions & 0 deletions packages/abi/src/parser/specifications/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { AbiParserV1 } from './v1/parser';
export * from './v1/specification';
Loading
Loading