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

chore: helper functions for common JsonAbi features #2000

Merged
merged 37 commits into from
Apr 9, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ebbbd35
chore: added custom errors for invalid Vec type
petertonysmith94 Mar 28, 2024
6606f6d
chore: added custom error when log type not found
petertonysmith94 Mar 28, 2024
de40007
chore: added custom error for components type not found
petertonysmith94 Mar 28, 2024
f1722f6
chore: added custom error for function not found
petertonysmith94 Mar 28, 2024
66549c1
chore: added custom error for output types
petertonysmith94 Mar 28, 2024
e8d77de
chore: custom error for nonEmptyInputs
petertonysmith94 Mar 28, 2024
db8514c
chore: added custom error for input types
petertonysmith94 Mar 28, 2024
062f554
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into ps…
petertonysmith94 Apr 2, 2024
7e07ee8
feat: refactored out findOrThrow
petertonysmith94 Apr 2, 2024
0448750
chore: changeset
petertonysmith94 Apr 2, 2024
6b1c546
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 2, 2024
062b9b2
Merge branch 'ps/feat/custom-errors-for-find-or-throw' of https://git…
petertonysmith94 Apr 2, 2024
2ca112f
chore: fixing error message
petertonysmith94 Apr 2, 2024
99f9940
chore: lint
petertonysmith94 Apr 2, 2024
8926bcd
chore: fix error messages in tests
petertonysmith94 Apr 2, 2024
33a2803
chore: lint
petertonysmith94 Apr 2, 2024
58f8b54
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 3, 2024
b67ebb8
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 3, 2024
9138579
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 3, 2024
6b7f841
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 3, 2024
877e720
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
danielbate Apr 4, 2024
34aab65
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into ps…
petertonysmith94 Apr 4, 2024
4c1d698
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 4, 2024
fa40b14
Update findFunctionByName signature in json-abi.ts
petertonysmith94 Apr 4, 2024
0291a7f
chore: docblocks
petertonysmith94 Apr 4, 2024
77ad077
chore: lint
petertonysmith94 Apr 4, 2024
7514337
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 4, 2024
9abdd73
chore: extracted method for finding the buffer component argument
petertonysmith94 Apr 8, 2024
f6b989a
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 8, 2024
7ea6d92
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 8, 2024
a26bff7
Consolidate errors for the `findVectorBufferArgument` function
petertonysmith94 Apr 9, 2024
3b1883c
chore: consolidated errors
petertonysmith94 Apr 9, 2024
ad3bc81
docs: added missing error code INVALID_COMPONENT
petertonysmith94 Apr 9, 2024
a5d682c
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 9, 2024
d611801
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 9, 2024
2ad7b7c
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
petertonysmith94 Apr 9, 2024
46f482a
Merge branch 'master' into ps/feat/custom-errors-for-find-or-throw
maschad Apr 9, 2024
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
5 changes: 5 additions & 0 deletions .changeset/stale-bugs-sell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@fuel-ts/abi-coder": minor
---

chore: helper functions for common JsonAbi features
5 changes: 5 additions & 0 deletions apps/docs/src/guide/errors/error-codes.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ When the program type either: does _not_ have configurable constants to be set;

Ensure the configurable constants provided are correct and are defined in ABI.

## `INVALID_COMPONENT`
When an expected component is not found in the ABI or is malformed.

Ensure that you have correctly formed Sway types for [Arrays](../types/arrays.md) and [Vectors](../types/vectors.md).

## `INVALID_CREDENTIALS`
When the password provided is incorrect.

Expand Down
28 changes: 10 additions & 18 deletions packages/abi-coder/src/FunctionFragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,9 @@ import type {
} from './types/JsonAbi';
import type { EncodingVersion } from './utils/constants';
import { ENCODING_V0, ENCODING_V1, OPTION_CODER_TYPE } from './utils/constants';
import { findFunctionByName, findNonEmptyInputs, findTypeById } from './utils/json-abi';
import type { Uint8ArrayWithDynamicData } from './utils/utilities';
import { isPointerType, unpackDynamicData, findOrThrow, isHeapType } from './utils/utilities';
import { isHeapType, isPointerType, unpackDynamicData } from './utils/utilities';

export class FunctionFragment<
TAbi extends JsonAbi = JsonAbi,
Expand All @@ -45,7 +46,8 @@ export class FunctionFragment<

constructor(jsonAbi: JsonAbi, name: FnName) {
this.jsonAbi = jsonAbi;
this.jsonFn = findOrThrow(this.jsonAbi.functions, (f) => f.name === name);
this.jsonFn = findFunctionByName(this.jsonAbi, name);

this.name = name;
this.signature = FunctionFragment.getSignature(this.jsonAbi, this.jsonFn);
this.selector = FunctionFragment.getFunctionSelector(this.signature);
Expand Down Expand Up @@ -74,15 +76,13 @@ export class FunctionFragment<
}

#isInputDataPointer(): boolean {
const inputTypes = this.jsonFn.inputs.map((i) =>
this.jsonAbi.types.find((t) => t.typeId === i.type)
);
const inputTypes = this.jsonFn.inputs.map((i) => findTypeById(this.jsonAbi, i.type));

return this.jsonFn.inputs.length > 1 || isPointerType(inputTypes[0]?.type || '');
}

#isOutputDataHeap(): boolean {
const outputType = findOrThrow(this.jsonAbi.types, (t) => t.typeId === this.jsonFn.output.type);
const outputType = findTypeById(this.jsonAbi, this.jsonFn.output.type);

return isHeapType(outputType?.type || '');
}
Expand All @@ -107,10 +107,7 @@ export class FunctionFragment<
FunctionFragment.verifyArgsAndInputsAlign(values, this.jsonFn.inputs, this.jsonAbi);

const shallowCopyValues = values.slice();

const nonEmptyInputs = this.jsonFn.inputs.filter(
(x) => findOrThrow(this.jsonAbi.types, (t) => t.typeId === x.type).type !== '()'
);
const nonEmptyInputs = findNonEmptyInputs(this.jsonAbi, this.jsonFn.inputs);

if (Array.isArray(values) && nonEmptyInputs.length !== values.length) {
shallowCopyValues.length = this.jsonFn.inputs.length;
Expand Down Expand Up @@ -140,7 +137,7 @@ export class FunctionFragment<
return;
}

const inputTypes = inputs.map((i) => findOrThrow(abi.types, (t) => t.typeId === i.type));
const inputTypes = inputs.map((input) => findTypeById(abi, input.type));
const optionalInputs = inputTypes.filter(
(x) => x.type === OPTION_CODER_TYPE || x.type === '()'
);
Expand All @@ -162,9 +159,7 @@ export class FunctionFragment<

decodeArguments(data: BytesLike) {
const bytes = arrayify(data);
const nonEmptyInputs = this.jsonFn.inputs.filter(
(x) => findOrThrow(this.jsonAbi.types, (t) => t.typeId === x.type).type !== '()'
);
const nonEmptyInputs = findNonEmptyInputs(this.jsonAbi, this.jsonFn.inputs);

if (nonEmptyInputs.length === 0) {
// The VM is current return 0x0000000000000000, but we should treat it as undefined / void
Expand Down Expand Up @@ -206,10 +201,7 @@ export class FunctionFragment<
}

decodeOutput(data: BytesLike): [DecodedValue | undefined, number] {
const outputAbiType = findOrThrow(
this.jsonAbi.types,
(t) => t.typeId === this.jsonFn.output.type
);
const outputAbiType = findTypeById(this.jsonAbi, this.jsonFn.output.type);
if (outputAbiType.type === '()') {
return [undefined, 0];
}
Expand Down
40 changes: 17 additions & 23 deletions packages/abi-coder/src/Interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { AbiCoder } from './AbiCoder';
import { FunctionFragment } from './FunctionFragment';
import type { InputValue } from './encoding/coders/AbstractCoder';
import type { JsonAbi, JsonAbiConfigurable } from './types/JsonAbi';
import { findOrThrow } from './utils/utilities';
import { findTypeById } from './utils/json-abi';

export class Interface<TAbi extends JsonAbi = JsonAbi> {
readonly functions!: Record<string, FunctionFragment>;
Expand Down Expand Up @@ -75,24 +75,27 @@ export class Interface<TAbi extends JsonAbi = JsonAbi> {
}

decodeLog(data: BytesLike, logId: number): any {
const { loggedType } = findOrThrow(this.jsonAbi.loggedTypes, (type) => type.logId === logId);
const loggedType = this.jsonAbi.loggedTypes.find((type) => type.logId === logId);
if (!loggedType) {
throw new FuelError(
ErrorCode.LOG_TYPE_NOT_FOUND,
`Log type with logId '${logId}' doesn't exist in the ABI.`
);
}
petertonysmith94 marked this conversation as resolved.
Show resolved Hide resolved

return AbiCoder.decode(this.jsonAbi, loggedType, arrayify(data), 0, {
return AbiCoder.decode(this.jsonAbi, loggedType.loggedType, arrayify(data), 0, {
encoding: this.jsonAbi.encoding,
});
}

encodeConfigurable(name: string, value: InputValue) {
const configurable = findOrThrow(
this.jsonAbi.configurables,
(c) => c.name === name,
() => {
throw new FuelError(
ErrorCode.CONFIGURABLE_NOT_FOUND,
`A configurable with the '${name}' was not found in the ABI.`
);
}
);
const configurable = this.jsonAbi.configurables.find((c) => c.name === name);
if (!configurable) {
throw new FuelError(
ErrorCode.CONFIGURABLE_NOT_FOUND,
`A configurable with the '${name}' was not found in the ABI.`
);
}

return AbiCoder.encode(this.jsonAbi, configurable.configurableType, value, {
isRightPadded: true,
Expand All @@ -101,15 +104,6 @@ export class Interface<TAbi extends JsonAbi = JsonAbi> {
}

getTypeById(typeId: number) {
return findOrThrow(
this.jsonAbi.types,
(t) => t.typeId === typeId,
() => {
throw new FuelError(
ErrorCode.TYPE_NOT_FOUND,
`Type with typeId '${typeId}' doesn't exist in the ABI.`
);
}
);
return findTypeById(this.jsonAbi, typeId);
}
}
22 changes: 4 additions & 18 deletions packages/abi-coder/src/ResolvedAbiType.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { ErrorCode, FuelError } from '@fuel-ts/errors';

import type { JsonAbi, JsonAbiArgument } from './types/JsonAbi';
import { arrayRegEx, enumRegEx, genericRegEx, stringRegEx, structRegEx } from './utils/constants';
import { findOrThrow } from './utils/utilities';
import { findTypeById } from './utils/json-abi';

export class ResolvedAbiType {
readonly abi: JsonAbi;
Expand All @@ -13,22 +11,10 @@ export class ResolvedAbiType {

constructor(abi: JsonAbi, argument: JsonAbiArgument) {
this.abi = abi;
const type = findOrThrow(
abi.types,
(t) => t.typeId === argument.type,
() => {
throw new FuelError(
ErrorCode.TYPE_NOT_FOUND,
`Type does not exist in the provided abi: ${JSON.stringify({
argument,
abi: this.abi,
})}`
);
}
);

this.name = argument.name;

const type = findTypeById(abi, argument.type);
this.type = type.type;
this.originalTypeArguments = argument.typeArguments;
this.components = ResolvedAbiType.getResolvedGenericComponents(
Expand Down Expand Up @@ -96,7 +82,7 @@ export class ResolvedAbiType {
};
}

const argType = findOrThrow(abi.types, (t) => t.typeId === arg.type);
const argType = findTypeById(abi, arg.type);
const implicitTypeParameters = this.getImplicitGenericTypeParameters(abi, argType.components);

if (implicitTypeParameters && implicitTypeParameters.length > 0) {
Expand All @@ -122,7 +108,7 @@ export class ResolvedAbiType {
const implicitGenericParameters: number[] = implicitGenericParametersParam ?? [];

args.forEach((a) => {
const argType = findOrThrow(abi.types, (t) => t.typeId === a.type);
const argType = findTypeById(abi, a.type);

if (genericRegEx.test(argType.type)) {
implicitGenericParameters.push(argType.typeId);
Expand Down
10 changes: 2 additions & 8 deletions packages/abi-coder/src/encoding/strategies/getCoderV0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
structRegEx,
tupleRegEx,
} from '../../utils/constants';
import { findOrThrow } from '../../utils/utilities';
import { findVectorBufferArgument } from '../../utils/json-abi';
import type { Coder } from '../coders/AbstractCoder';
import { ArrayCoder } from '../coders/v0/ArrayCoder';
import { B256Coder } from '../coders/v0/B256Coder';
Expand Down Expand Up @@ -111,13 +111,7 @@ export const getCoder: GetCoderFn = (
}

if (resolvedAbiType.type === VEC_CODER_TYPE) {
const arg = findOrThrow(components, (c) => c.name === 'buf').originalTypeArguments?.[0];
if (!arg) {
throw new FuelError(
ErrorCode.INVALID_COMPONENT,
`The provided Vec type is missing the 'type argument'.`
);
}
const arg = findVectorBufferArgument(components);
const argType = new ResolvedAbiType(resolvedAbiType.abi, arg);

const itemCoder = getCoder(argType, { isSmallBytes: true, encoding: ENCODING_V0 });
Expand Down
10 changes: 2 additions & 8 deletions packages/abi-coder/src/encoding/strategies/getCoderV1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
structRegEx,
tupleRegEx,
} from '../../utils/constants';
import { findOrThrow } from '../../utils/utilities';
import { findVectorBufferArgument } from '../../utils/json-abi';
import type { Coder } from '../coders/AbstractCoder';
import { ArrayCoder } from '../coders/v0/ArrayCoder';
import { B256Coder } from '../coders/v0/B256Coder';
Expand Down Expand Up @@ -114,13 +114,7 @@ export const getCoder: GetCoderFn = (
}

if (resolvedAbiType.type === VEC_CODER_TYPE) {
const arg = findOrThrow(components, (c) => c.name === 'buf').originalTypeArguments?.[0];
if (!arg) {
throw new FuelError(
ErrorCode.INVALID_COMPONENT,
`The provided Vec type is missing the 'type argument'.`
);
}
const arg = findVectorBufferArgument(components);
const argType = new ResolvedAbiType(resolvedAbiType.abi, arg);

const itemCoder = getCoder(argType, { isSmallBytes: true, encoding: ENCODING_V0 });
Expand Down
128 changes: 128 additions & 0 deletions packages/abi-coder/src/utils/json-abi.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import type { ResolvedAbiType } from '../ResolvedAbiType';
import type { JsonAbi, JsonAbiArgument } from '../types/JsonAbi';

import {
findFunctionByName,
findNonEmptyInputs,
findTypeById,
findVectorBufferArgument,
} from './json-abi';

const MOCK_ABI: JsonAbi = {
types: [
{ typeId: 1, type: '()', components: [], typeParameters: [] },
{ typeId: 2, type: 'u256', components: [], typeParameters: [] },
],
functions: [
{ name: 'foo', attributes: [], inputs: [], output: { name: '', type: 1, typeArguments: [] } },
],
loggedTypes: [],
configurables: [],
};

/**
* @group node
* @group browser
*/
describe('json-abi', () => {
describe('findFunctionByName', () => {
it('should find a function by name', () => {
const expected = {
name: 'foo',
attributes: [],
inputs: [],
output: { name: '', type: 1, typeArguments: [] },
};

const actual = findFunctionByName(MOCK_ABI, 'foo');

expect(actual).toEqual(expected);
});

it('should throw an error if the function is not found', () => {
expect(() => findFunctionByName(MOCK_ABI, 'bar')).toThrowError(
`Function with name 'bar' doesn't exist in the ABI`
);
});
});

describe('findTypeById', () => {
it('should find a type by id', () => {
const expected = {
typeId: 1,
type: '()',
components: [],
typeParameters: [],
};

const actual = findTypeById(MOCK_ABI, 1);

expect(actual).toEqual(expected);
});

it('should throw an error if the type is not found', () => {
expect(() => findTypeById(MOCK_ABI, -1)).toThrowError(
`Type with typeId '-1' doesn't exist in the ABI.`
);
});
});

describe('findNonEmptyInputs', () => {
it('should find non-empty inputs', () => {
const inputs: JsonAbiArgument[] = [
{ name: 'a', type: 1, typeArguments: [] },
{ name: 'b', type: 2, typeArguments: [] },
];
const expected = [{ name: 'b', type: 2, typeArguments: [] }];

const actual = findNonEmptyInputs(MOCK_ABI, inputs);

expect(actual).toEqual(expected);
});

it('should throw an error if the type is not found', () => {
const inputs: JsonAbiArgument[] = [{ name: 'a', type: -1, typeArguments: [] }];

expect(() => findNonEmptyInputs(MOCK_ABI, inputs)).toThrowError(
`Type with typeId '-1' doesn't exist in the ABI.`
);
});
});

describe('findVectorBufferArgument', () => {
it('should throw, when there are no components with the name of `buf', () => {
const components: ResolvedAbiType[] = [];

expect(() => findVectorBufferArgument(components)).toThrowError(
`The Vec type provided is missing or has a malformed 'buf' component.`
);
});

it('should throw, when the buffer component is missing type arguments', () => {
const components: ResolvedAbiType[] = [
{
name: 'buf',
originalTypeArguments: [],
} as unknown as ResolvedAbiType,
];

expect(() => findVectorBufferArgument(components)).toThrowError(
`The Vec type provided is missing or has a malformed 'buf' component.`
);
});

it('should return the buffer argument', () => {
const components: ResolvedAbiType[] = [
{
name: 'buf',
originalTypeArguments: [{ name: 'u256', components: [], typeParameters: [] }],
} as unknown as ResolvedAbiType,
];

const expected = { name: 'u256', components: [], typeParameters: [] };
const actual = findVectorBufferArgument(components);

expect(actual).toEqual(expected);
});
});
});
Loading
Loading