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!: improve () and Option<T> type handling #2777

Merged
merged 54 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
7c53192
chore: organized workspace alphabetically
petertonysmith94 Jul 16, 2024
a971ce7
feat: added void contract
petertonysmith94 Jul 16, 2024
05828d4
feat: added void test
petertonysmith94 Jul 16, 2024
b65f423
chore: added `VOID_TYPE`
petertonysmith94 Jul 16, 2024
fd41c5f
feat: converted `void` tests to echo based, added NativeEnums
petertonysmith94 Jul 16, 2024
63e69e9
feat: added `VoidCoder`
petertonysmith94 Jul 16, 2024
b326369
chore: changeset
petertonysmith94 Jul 16, 2024
c0e5d7b
chore: simplify the `EnumCoder::isNativeEnum` check
petertonysmith94 Jul 16, 2024
5da1c61
chore: update void test
petertonysmith94 Jul 17, 2024
ee2e2b7
feat: implemented EmptyType for typegen, matched to `undefined`
petertonysmith94 Jul 22, 2024
2493c85
feat: added e2e tests around the void arguments
petertonysmith94 Jul 22, 2024
2fdf058
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into ps…
petertonysmith94 Jul 22, 2024
9c9aa3c
chore: fixing tests
petertonysmith94 Jul 22, 2024
c26cd9e
Merge branch 'master' into ps/feat/void-coder
petertonysmith94 Jul 22, 2024
52264c0
Merge branch 'master' into ps/feat/void-coder
petertonysmith94 Jul 23, 2024
db19ec7
feat: implemented `getMandatoryInputs`
petertonysmith94 Jul 23, 2024
ba228cf
feat: added optional argument parsing
petertonysmith94 Jul 24, 2024
6144e9e
chore: added Option ABI type testing
petertonysmith94 Jul 24, 2024
61484f5
chore: added forcBuildFlag to test
petertonysmith94 Jul 24, 2024
d1a40a7
feat: implemented option function parameters
petertonysmith94 Jul 24, 2024
beae16f
chore: removed optional arguments from tests
petertonysmith94 Jul 24, 2024
b3d7048
chore: made void return type for EmptyType
petertonysmith94 Jul 24, 2024
e5cd92f
chore: added missing test group
petertonysmith94 Jul 24, 2024
8361c38
deps: added `ramda` to `abi-coder`
petertonysmith94 Jul 24, 2024
7893de2
chore: implemented optional parameters for coder
petertonysmith94 Jul 24, 2024
fe39961
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into ps…
petertonysmith94 Jul 24, 2024
859c874
chore: fixing lock file
petertonysmith94 Jul 24, 2024
92b03c8
chore: lint
petertonysmith94 Jul 24, 2024
5324433
chore: renamed `findNonEmptyInputs` to `findNonVoidInputs`
petertonysmith94 Jul 24, 2024
57c363f
chore: iterate over all inputs for `decodeArguments`
petertonysmith94 Jul 24, 2024
00b589a
chore: removed void check for `decodeOutput`
petertonysmith94 Jul 24, 2024
be0cdd5
chore: added test for optional option
petertonysmith94 Jul 24, 2024
17b961d
chore: housekeeping
petertonysmith94 Jul 24, 2024
c49d247
Add option tests
petertonysmith94 Jul 24, 2024
96b5289
Linting
petertonysmith94 Jul 24, 2024
00ece32
chore: add script test with options (WIP)
petertonysmith94 Jul 25, 2024
09fa2eb
chore: added script options test
petertonysmith94 Jul 29, 2024
b270501
chore: renamed `getMandatoryInputs` -> `getFunctionInputs`
petertonysmith94 Jul 29, 2024
caeab8b
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into ps…
petertonysmith94 Jul 29, 2024
4b8bc21
chore: fixed lockfile
petertonysmith94 Jul 29, 2024
2d24bcc
chore: updated changeset
petertonysmith94 Jul 29, 2024
67852ff
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into ps…
petertonysmith94 Jul 29, 2024
8267d44
chore: fix lock file
petertonysmith94 Jul 29, 2024
f035b8c
reinstall ramda
petertonysmith94 Jul 29, 2024
3dd8f6b
Removed dependency on ramda
petertonysmith94 Jul 29, 2024
4fcb37c
Merge branch 'master' into ps/feat/void-coder
maschad Jul 30, 2024
2f5b4bb
Added `@group browser` tests
petertonysmith94 Jul 30, 2024
71a1309
chore: implemented optimizations for `getFunctionInputs`
petertonysmith94 Jul 30, 2024
1c32d0f
chore: implemented `padValuesWithUndefined`
petertonysmith94 Jul 30, 2024
a385918
chore: added missing test groupings
petertonysmith94 Jul 30, 2024
b19c359
Merge branch 'master' into ps/feat/void-coder
petertonysmith94 Jul 30, 2024
3838beb
Merge branch 'master' into ps/feat/void-coder
petertonysmith94 Jul 30, 2024
23824de
Merge branch 'master' into ps/feat/void-coder
petertonysmith94 Jul 30, 2024
1b15522
Merge branch 'master' of https://github.com/FuelLabs/fuels-ts into ps…
petertonysmith94 Jul 30, 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
6 changes: 6 additions & 0 deletions .changeset/tender-birds-tap.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@fuel-ts/abi-coder": patch
"@fuel-ts/abi-typegen": patch
---

feat!: improve `()` and `Option<T>` type handling
31 changes: 21 additions & 10 deletions apps/docs-snippets/src/guide/types/options.test.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import type { Contract } from 'fuels';
import { launchTestNode } from 'fuels/test-utils';

import { DocSnippetProjectsEnum } from '../../../test/fixtures/forc-projects';
import { createAndDeployContractFromProject } from '../../utils';
import { SumOptionU8Abi__factory } from '../../../test/typegen';
import bytecode from '../../../test/typegen/contracts/SumOptionU8Abi.hex';

function setupContract() {
return launchTestNode({
contractsConfigs: [{ deployer: SumOptionU8Abi__factory, bytecode }],
});
}

/**
* @group node
* @group browser
petertonysmith94 marked this conversation as resolved.
Show resolved Hide resolved
*/
describe(__filename, () => {
let contract: Contract;

beforeAll(async () => {
contract = await createAndDeployContractFromProject(DocSnippetProjectsEnum.SUM_OPTION_U8);
});

describe('options', () => {
it('should successfully execute contract call to sum 2 option inputs (2 INPUTS)', async () => {
using launched = await setupContract();
const {
contracts: [contract],
} = launched;

// #region options-1
// Sway Option<u8>
// #region options-3
Expand All @@ -29,6 +35,11 @@ describe(__filename, () => {
});

it('should successfully execute contract call to sum 2 option inputs (1 INPUT)', async () => {
using launched = await setupContract();
const {
contracts: [contract],
} = launched;

// #region options-4
const input: number | undefined = 5;

Expand Down
81 changes: 19 additions & 62 deletions packages/abi-coder/src/FunctionFragment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,11 @@ import { ResolvedAbiType } from './ResolvedAbiType';
import type { DecodedValue, InputValue } from './encoding/coders/AbstractCoder';
import { StdStringCoder } from './encoding/coders/StdStringCoder';
import { TupleCoder } from './encoding/coders/TupleCoder';
import type {
JsonAbi,
JsonAbiArgument,
JsonAbiFunction,
JsonAbiFunctionAttribute,
} from './types/JsonAbi';
import type { JsonAbi, JsonAbiFunction, JsonAbiFunctionAttribute } from './types/JsonAbi';
import type { EncodingVersion } from './utils/constants';
import { OPTION_CODER_TYPE } from './utils/constants';
import {
findFunctionByName,
findNonEmptyInputs,
findTypeById,
getEncodingVersion,
} from './utils/json-abi';
import { getFunctionInputs } from './utils/getFunctionInputs';
import { findFunctionByName, findNonVoidInputs, getEncodingVersion } from './utils/json-abi';
import { padValuesWithUndefined } from './utils/padValuesWithUndefined';

export class FunctionFragment<
TAbi extends JsonAbi = JsonAbi,
Expand Down Expand Up @@ -66,59 +57,30 @@ export class FunctionFragment<
}

encodeArguments(values: InputValue[]): Uint8Array {
FunctionFragment.verifyArgsAndInputsAlign(values, this.jsonFn.inputs, this.jsonAbi);

const shallowCopyValues = values.slice();
const nonEmptyInputs = findNonEmptyInputs(this.jsonAbi, this.jsonFn.inputs);

if (Array.isArray(values) && nonEmptyInputs.length !== values.length) {
shallowCopyValues.length = this.jsonFn.inputs.length;
shallowCopyValues.fill(undefined as unknown as InputValue, values.length);
const inputs = getFunctionInputs({ jsonAbi: this.jsonAbi, inputs: this.jsonFn.inputs });
const mandatoryInputLength = inputs.filter((i) => !i.isOptional).length;
if (values.length < mandatoryInputLength) {
throw new FuelError(
ErrorCode.ABI_TYPES_AND_VALUES_MISMATCH,
`Invalid number of arguments. Expected a minimum of ${mandatoryInputLength} arguments, received ${values.length}`
);
}

const coders = nonEmptyInputs.map((t) =>
const coders = this.jsonFn.inputs.map((t) =>
AbiCoder.getCoder(this.jsonAbi, t, {
encoding: this.encoding,
})
);

return new TupleCoder(coders).encode(shallowCopyValues);
}

private static verifyArgsAndInputsAlign(
args: InputValue[],
inputs: readonly JsonAbiArgument[],
abi: JsonAbi
) {
if (args.length === inputs.length) {
return;
}

const inputTypes = inputs.map((input) => findTypeById(abi, input.type));
const optionalInputs = inputTypes.filter(
(x) => x.type === OPTION_CODER_TYPE || x.type === '()'
);
if (optionalInputs.length === inputTypes.length) {
return;
}
if (inputTypes.length - optionalInputs.length === args.length) {
return;
}

const errorMsg = `Mismatch between provided arguments and expected ABI inputs. Provided ${
args.length
} arguments, but expected ${inputs.length - optionalInputs.length} (excluding ${
optionalInputs.length
} optional inputs).`;

throw new FuelError(ErrorCode.ABI_TYPES_AND_VALUES_MISMATCH, errorMsg);
const argumentValues = padValuesWithUndefined(values, this.jsonFn.inputs);
return new TupleCoder(coders).encode(argumentValues);
}

decodeArguments(data: BytesLike) {
const bytes = arrayify(data);
const nonEmptyInputs = findNonEmptyInputs(this.jsonAbi, this.jsonFn.inputs);
const nonVoidInputs = findNonVoidInputs(this.jsonAbi, this.jsonFn.inputs);

if (nonEmptyInputs.length === 0) {
if (nonVoidInputs.length === 0) {
// The VM is current return 0x0000000000000000, but we should treat it as undefined / void
if (bytes.length === 0) {
return undefined;
Expand All @@ -129,19 +91,19 @@ export class FunctionFragment<
`Types/values length mismatch during decode. ${JSON.stringify({
count: {
types: this.jsonFn.inputs.length,
nonEmptyInputs: nonEmptyInputs.length,
nonVoidInputs: nonVoidInputs.length,
values: bytes.length,
},
value: {
args: this.jsonFn.inputs,
nonEmptyInputs,
nonVoidInputs,
values: bytes,
},
})}`
);
}

const result = nonEmptyInputs.reduce(
const result = this.jsonFn.inputs.reduce(
(obj: { decoded: unknown[]; offset: number }, input) => {
const coder = AbiCoder.getCoder(this.jsonAbi, input, { encoding: this.encoding });
const [decodedValue, decodedValueByteSize] = coder.decode(bytes, obj.offset);
Expand All @@ -158,11 +120,6 @@ export class FunctionFragment<
}

decodeOutput(data: BytesLike): [DecodedValue | undefined, number] {
const outputAbiType = findTypeById(this.jsonAbi, this.jsonFn.output.type);
if (outputAbiType.type === '()') {
return [undefined, 0];
}

const bytes = arrayify(data);
const coder = AbiCoder.getCoder(this.jsonAbi, this.jsonFn.output, {
encoding: this.encoding,
Expand Down
12 changes: 3 additions & 9 deletions packages/abi-coder/src/encoding/coders/EnumCoder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,12 @@ import { toNumber } from '@fuel-ts/math';
import { concat } from '@fuel-ts/utils';
import type { RequireExactlyOne } from 'type-fest';

import { OPTION_CODER_TYPE } from '../../utils/constants';
import { OPTION_CODER_TYPE, VOID_TYPE } from '../../utils/constants';
import { hasNestedOption } from '../../utils/utilities';

import type { TypesOfCoder } from './AbstractCoder';
import { Coder } from './AbstractCoder';
import { BigNumberCoder } from './BigNumberCoder';
import type { TupleCoder } from './TupleCoder';

export type InputValueOf<TCoders extends Record<string, Coder>> = RequireExactlyOne<{
[P in keyof TCoders]: TypesOfCoder<TCoders[P]>['Input'];
Expand Down Expand Up @@ -42,14 +41,9 @@ export class EnumCoder<TCoders extends Record<string, Coder>> extends Coder<
this.#shouldValidateLength = !(this.type === OPTION_CODER_TYPE || hasNestedOption(coders));
}

// We parse a native enum as an empty tuple, so we are looking for a tuple with no child coders.
// The '()' is enough but the child coders is a stricter check.
// Checks that we're handling a native enum that is of type void.
#isNativeEnum(coder: Coder): boolean {
if (this.type !== OPTION_CODER_TYPE && coder.type === '()') {
const tupleCoder = coder as TupleCoder<[]>;
return tupleCoder.coders.length === 0;
}
return false;
return this.type !== OPTION_CODER_TYPE && coder.type === VOID_TYPE;
}

#encodeNativeEnum(value: string): Uint8Array {
Expand Down
38 changes: 38 additions & 0 deletions packages/abi-coder/src/encoding/coders/VoidCoder.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { VoidCoder } from './VoidCoder';

/**
* @group node
petertonysmith94 marked this conversation as resolved.
Show resolved Hide resolved
* @group browser
*/
describe('VoidCoder', () => {
it('should have properties', () => {
const coder = new VoidCoder();
expect(coder.name).toEqual('void');
expect(coder.type).toEqual('()');
expect(coder.encodedLength).toEqual(0);
});

describe('encode', () => {
it('should return an empty Uint8Array', () => {
const input = undefined;
const expected = new Uint8Array([]);

const coder = new VoidCoder();
const value = coder.encode(input);
expect(value).toEqual(expected);
});
});

describe('decode', () => {
it('should return an undefined result', () => {
const input = new Uint8Array([]);
const expected = undefined;
const expectedOffset = 0;

const coder = new VoidCoder();
const [value, offset] = coder.decode(input, 0);
expect(value).toEqual(expected);
expect(offset).toEqual(expectedOffset);
});
});
});
17 changes: 17 additions & 0 deletions packages/abi-coder/src/encoding/coders/VoidCoder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { VOID_TYPE } from '../../utils/constants';

import { Coder } from './AbstractCoder';

export class VoidCoder extends Coder<undefined, undefined> {
constructor() {
super('void', VOID_TYPE, 0);
}

encode(_value: undefined): Uint8Array {
return new Uint8Array([]);
}

decode(_data: Uint8Array, offset: number): [undefined, number] {
return [undefined, offset];
}
}
4 changes: 4 additions & 0 deletions packages/abi-coder/src/encoding/strategies/getCoderV1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
U64_CODER_TYPE,
U8_CODER_TYPE,
VEC_CODER_TYPE,
VOID_TYPE,
arrayRegEx,
enumRegEx,
stringRegEx,
Expand All @@ -44,6 +45,7 @@ import { StringCoder } from '../coders/StringCoder';
import { StructCoder } from '../coders/StructCoder';
import { TupleCoder } from '../coders/TupleCoder';
import { VecCoder } from '../coders/VecCoder';
import { VoidCoder } from '../coders/VoidCoder';

import { getCoders } from './getCoders';

Expand Down Expand Up @@ -82,6 +84,8 @@ export const getCoder: GetCoderFn = (
return new StdStringCoder();
case STR_SLICE_CODER_TYPE:
return new StrSliceCoder();
case VOID_TYPE:
return new VoidCoder();
default:
break;
}
Expand Down
2 changes: 2 additions & 0 deletions packages/abi-coder/src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ export const VEC_CODER_TYPE = 'struct Vec';
export const BYTES_CODER_TYPE = 'struct Bytes';
export const STD_STRING_CODER_TYPE = 'struct String';
export const STR_SLICE_CODER_TYPE = 'str';
export const VOID_TYPE = '()';
export const OPTION_REGEX: RegExp = /^enum (std::option::)?Option$/m;
export const stringRegEx = /str\[(?<length>[0-9]+)\]/;
export const arrayRegEx = /\[(?<item>[\w\s\\[\]]+);\s*(?<length>[0-9]+)\]/;
export const structRegEx = /^struct (?<name>\w+)$/;
Expand Down
Loading
Loading